Merge remote-tracking branch 'origin/release/next-userspace' into lf/revive-resume

This commit is contained in:
Liam Fitzgerald 2021-07-26 09:43:53 +10:00
commit 77c2b5d46c
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
65 changed files with 1315 additions and 849 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:02677dba866b8c165c10bffc409cef6a74c979edb83cade6cab2f67a50bb0fc1 oid sha256:a36f646746ed95440fa7586e1c51c5402a2e117386ec82fdb6263a1c20b2fd65
size 12852855 size 12867464

View File

@ -26,6 +26,6 @@
<div id="portal-root"></div> <div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script> <script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script> <script src="/~landscape/js/session.js"></script>
<script src="/~btc/js/bundle/index.3e8bcc150ebd820dd3b2.js"></script> <script src="/~btc/js/bundle/index.02730169f3d73fd22950.js"></script>
</body> </body>
</html> </html>

View File

@ -5,8 +5,8 @@
/- glob, *resource /- glob, *resource
/+ default-agent, verb, dbug /+ default-agent, verb, dbug
|% |%
++ landscape-hash 0vrbiqe.v6al2.0b4jc.u9vp7.k1e0i ++ landscape-hash 0v5.48552.hdu43.jrjb7.jd19b.74vpc
++ btc-wallet-hash 0v7.v4dng.o33qi.kc497.5jc02.ke5es ++ btc-wallet-hash 0v2.ifoe4.fbv35.aigir.66su4.fbspu
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))] +$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ state-1 [%1 =globs:glob] +$ state-1 [%1 =globs:glob]
+$ all-states +$ all-states

View File

@ -180,10 +180,7 @@
== ==
:: ::
++ add-nodes ++ add-nodes
|= $: =time |= [=time =resource:store nodes=(map index:store node:store)]
=resource:store
nodes=(map index:store node:store)
==
^- (quip card _state) ^- (quip card _state)
|^ |^
=/ [=graph:store mark=(unit mark:store)] =/ [=graph:store mark=(unit mark:store)]
@ -618,392 +615,283 @@
~/ %graph-store-peek ~/ %graph-store-peek
|= =path |= =path
^- (unit (unit cage)) ^- (unit (unit cage))
|^ ?+ path (on-peek:def path)
?> (team:title our.bowl src.bowl) [%x %export ~] ``noun+!>(state)
?+ path (on-peek:def path)
[%x %graph-mark @ @ ~]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ result=(unit marked-graph:store)
(~(get by graphs) [ship term])
?~ result [~ ~]
``noun+!>(`(unit mark)`q.u.result)
:: ::
[%x %keys ~] [%x %keys ~]
:- ~ :- ~ :- %graph-update-2 :- ~ :- ~ :- %graph-update-2
!>(`update:store`[now.bowl [%keys ~(key by graphs)]]) !>(`update:store`[now.bowl [%keys ~(key by graphs)]])
:: ::
[%x %tags ~] [%x %tag-queries *]
:- ~ :- ~ :- %graph-update-2
!>(`update:store`[now.bowl [%tags ~(key by tag-queries)]])
::
[%x %tag-queries ~]
:- ~ :- ~ :- %graph-update-2
!>(`update:store`[now.bowl [%tag-queries tag-queries]])
::
[%x %graph @ @ ~]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ result=(unit marked-graph:store)
(~(get by graphs) [ship term])
?~ result [~ ~]
:- ~ :- ~ :- %graph-update-2 :- ~ :- ~ :- %graph-update-2
!> ^- update:store !> ^- update:store
:- now.bowl :- now.bowl
[%add-graph [ship term] `graph:store`p.u.result q.u.result %.y] ?+ t.t.path (on-peek:def path)
~ [%tag-queries tag-queries]
[%tags ~] [%tags ~(key by tag-queries)]
==
:: ::
:: note: near-duplicate of /x/graph
::
[%x %archive @ @ ~] [%x %archive @ @ ~]
=/ =ship (slav %p i.t.t.path) =/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path =/ =term i.t.t.t.path
=/ result=(unit marked-graph:store) =/ marked-graph=(unit marked-graph:store)
(~(get by archive) [ship term])
?~ result
~& no-archived-graph+[ship term]
[~ ~]
:- ~ :- ~ :- %graph-update-2
!> ^- update:store
:- now.bowl
[%add-graph [ship term] `graph:store`p.u.result q.u.result %.y]
::
[%x %export ~]
``noun+!>(state)
::
[%x %graph-subset @ @ @ @ ~]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ start=(unit atom) (rush i.t.t.t.t.path dem:ag)
=/ end=(unit atom) (rush i.t.t.t.t.t.path dem:ag)
=/ graph=(unit marked-graph:store)
(~(get by graphs) [ship term]) (~(get by graphs) [ship term])
?~ graph [~ ~] ?~ marked-graph [~ ~]
=* graph p.u.marked-graph
=* mark q.u.marked-graph
:- ~ :- ~ :- %graph-update-2 :- ~ :- ~ :- %graph-update-2
!> ^- update:store !>(`update:store`[now.bowl [%add-graph [ship term] graph mark %.y]])
:- now.bowl
:+ %add-nodes
[ship term]
%- ~(gas by *(map index:store node:store))
%+ turn (tap:orm `graph:store`(lot:orm p.u.graph start end))
|= [=atom =node:store]
^- [index:store node:store]
[~[atom] node]
:: ::
[%x %node-exists @ @ @ *] [%x %update-log @ @ *]
=/ =ship (slav %p i.t.t.path) =/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path =/ =term i.t.t.t.path
=/ =index:store =/ update-log
(turn t.t.t.t.path (cury slav %ud)) (~(get by update-logs) [ship term])
=/ node=(unit node:store) ?~ update-log [~ ~]
(get-node ship term index) :- ~ :- ~ :- %noun
``noun+!>(`?`?=(^ node)) !>
:: ?+ t.t.t.t.path (on-peek:def path)
[%x %node @ @ @ *] ~ `update-log:store`u.update-log
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ =index:store
(turn t.t.t.t.path (cury slav %ud))
=/ node=(unit node:store) (get-node ship term index)
?~ node [~ ~]
:- ~ :- ~ :- %graph-update-2
!> ^- update:store
:- now.bowl
:+ %add-nodes
[ship term]
(~(gas by *(map index:store node:store)) [index u.node] ~)
::
[%x %node-siblings ?(%older %younger) @ @ @ *]
|^
=/ older ?=(%older i.t.t.path)
=/ =ship (slav %p i.t.t.t.path)
=/ =term i.t.t.t.t.path
=/ count (slav %ud i.t.t.t.t.t.path)
=/ =index:store
(turn t.t.t.t.t.t.path (cury slav %ud))
=/ parent=index:store
(scag (dec (lent index)) index)
=/ graph
(get-node-children ship term parent)
?~ graph [~ ~]
:- ~ :- ~ :- %graph-update-2
!> ^- update:store
:- now.bowl
:+ %add-nodes
[ship term]
%- ~(gas by *(map index:store node:store))
%+ turn
?: older
(tab:orm u.graph `(rear index) count)
:: TODO time complexity not desirable for %younger case
::
%+ slag (safe-sub (lent -) count)
%- tap:orm
%+ lot:orm u.graph
[~ `(snag (dec (lent index)) index)]
|= [=atom =node:store]
^- [index:store node:store]
[(snoc parent atom) node]
:: ::
++ safe-sub [%latest ~]
|= [a=@ b=@] ^- (unit time)
^- @ %+ biff update-log
?: (gte b a) |= =update-log:store
0 (bind (pry:orm-log:store update-log) head)
(sub a b) ::
-- [%subset @ @ ~]
^- update-log:store
=* start i.t.t.t.t.t.path
=* end i.t.t.t.t.t.t.path
%^ lot:orm-log
u.update-log
(slaw %da start)
(slaw %da end)
==
:: ::
[%x %shallow-children @ @ *] [%x %graph @ @ *]
=/ =ship (slav %p i.t.t.path) =/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path =/ =term i.t.t.t.path
=/ =index:store =/ marked-graph=(unit marked-graph:store)
(turn t.t.t.t.path (cury slav %ud)) (~(get by graphs) [ship term])
=/ children ?~ marked-graph [~ ~]
(get-node-children ship term index) =* graph p.u.marked-graph
?~ children [~ ~] =* mark q.u.marked-graph
:- ~ :- ~ :- %graph-update-2 ?+ t.t.t.t.path (on-peek:def path)
!> ^- update:store ~
:+ now.bowl %add-nodes :- ~ :- ~ :- %graph-update-2
:- [ship term] !>(`update:store`[now.bowl [%add-graph [ship term] graph mark %.y]])
%- ~(gas by *(map index:store node:store)) ::
%+ turn (tap:orm u.children) [%mark ~]
|= [=atom =node:store] ``noun+!>(`(unit ^mark)`mark)
^- [index:store node:store] ::
:- (snoc index atom) [%subset ?(%lone %kith) @ @ ~]
node(children [%empty ~]) =/ start=(unit atom) (rush i.t.t.t.t.t.t.path dem:ag)
:: =/ end=(unit atom) (rush i.t.t.t.t.t.t.t.path dem:ag)
[%x ?(%newest %oldest) @ @ @ *]
=/ newest ?=(%newest i.t.path)
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ count=@ud
(slav %ud i.t.t.t.t.path)
=/ =index:store
(turn t.t.t.t.t.path (cury slav %ud))
=/ children
(get-node-children ship term index)
?~ children [~ ~]
:- ~ :- ~ :- %graph-update-2
!> ^- update:store
:- now.bowl
:+ %add-nodes
[ship term]
%- ~(gas by *(map index:store node:store))
%+ turn
%+ scag count
?: newest
(tap:orm u.children)
(bap:orm u.children)
|= [=atom =node:store]
^- [index:store node:store]
[(snoc index atom) node]
::
[%x %node-children-subset @ @ @ @ @ *]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ start=(unit atom) (rush i.t.t.t.t.path dem:ag)
=/ end=(unit atom) (rush i.t.t.t.t.t.path dem:ag)
=/ =index:store
(turn t.t.t.t.t.t.path |=(=cord (slav %ud cord)))
=/ node=(unit node:store) (get-node ship term index)
?~ node [~ ~]
?- -.children.u.node
%empty [~ ~]
%graph
:- ~ :- ~ :- %graph-update-2 :- ~ :- ~ :- %graph-update-2
!> ^- update:store !> ^- update:store
:- now.bowl :^ now.bowl %add-nodes [ship term]
:+ %add-nodes
[ship term]
%- ~(gas by *(map index:store node:store)) %- ~(gas by *(map index:store node:store))
%+ turn (tap:orm `graph:store`(lot:orm p.children.u.node end start)) %+ turn (tap:orm (lot:orm graph start end))
|= [=atom =node:store] |= [=atom =node:store]
^- [index:store node:store] ^- [index:store node:store]
[(snoc index atom) node] :- atom^~
== ?: ?=(%kith i.t.t.t.t.t.path)
:: node
[%x %deep-nodes-older-than @ @ @ @ ~] node(children [%empty ~])
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ count=(unit atom) (rush i.t.t.t.t.path dem:ag)
=/ start=(unit atom) (rush i.t.t.t.t.t.path dem:ag)
?: ?=(~ count)
[~ ~]
=/ result=(unit marked-graph:store)
(~(get by graphs) [ship term])
?~ result
[~ ~]
:- ~ :- ~ :- %graph-update-2
!> ^- update:store
:- now.bowl
:+ %add-nodes
[ship term]
=* a u.count
=/ b=(list (pair atom node:store))
(tab:orm p.u.result start u.count)
=| c=index:store
=| d=(map index:store node:store)
=| e=@ud
=- d
|- ^- [e=@ud d=(map index:store node:store)]
?: ?|(?=(~ b) =(e a))
[e d]
=* atom p.i.b
=* node q.i.b
=. c (snoc c atom)
?- -.children.node
%empty
$(b t.b, e +(e), d (~(put by d) c node), c (snip c))
:: ::
%graph [%node *]
=/ f $(b (tab:orm p.children.node ~ (sub a e))) |^
?: =(e.f a) f =* pax t.t.t.t.t.path
%_ $ ?+ pax (on-peek:def path)
b t.b [%exists ^]
e +(e.f) =/ =index:store
d (~(put by d.f) c node(children [%empty ~])) (turn t.pax (cury slav %ud))
c (snip c) =/ node (get-node graph index)
== ``noun+!>(`?`?=(^ node))
== ::
:: [%index ?(%lone %kith) ^]
[%x %firstborn @ @ @ *] =/ =index:store
|^ (turn t.t.pax (cury slav %ud))
=/ =ship (slav %p i.t.t.path) =/ node (get-node graph index)
=/ =term i.t.t.t.path ?~ node [~ ~]
=/ =index:store
(turn t.t.t.t.path (cury slav %ud))
?> ?=(^ index)
=/ result=(unit marked-graph:store)
(~(get by graphs) [ship term])
?~ result
[~ ~]
%- (bond |.(`(unit (unit cage))`[~ ~]))
%+ biff
(collect-parents p.u.result index ship term)
(corl some collect-firstborn)
::
++ collect-parents
|= [=graph:store =index:store =ship =term]
^- %- unit
[node:store index:store (map index:store node:store) ^ship ^term]
=| =(map index:store node:store)
=| =node:store
=| ind=index:store
=/ len (lent index)
|-
?: (gte (lent ind) len)
`[node ind map ship term]
?> ?=(^ index)
=* atom i.index
?. (has:orm graph atom)
~
=: node (got:orm graph atom)
ind (snoc ind atom)
==
?: ?=(%empty -.children.node)
?. (gte (lent ind) len)
~
:- ~
:* node ind
(~(put by map) ind node)
ship term
==
%_ $
index t.index
graph p.children.node
map (~(put by map) ind node(children empty+~))
==
::
++ collect-firstborn
|= [=node:store =index:store mp=(map index:store node:store) =ship =term]
^- (unit (unit cage))
?: ?=(%empty -.children.node)
:- ~ :- ~ :- %graph-update-2 :- ~ :- ~ :- %graph-update-2
!> ^- update:store !> ^- update:store
[now.bowl [%add-nodes [ship term] mp]] :^ now.bowl %add-nodes [ship term]
=/ item=[k=atom v=node:store] %- ~(gas by *(map index:store node:store))
(need (ram:orm p.children.node)) :_ ~ :- index
=. index (snoc index k.item) ?: ?=(%kith i.t.pax) u.node
$(mp (~(put by mp) index v.item(children empty+~)), node v.item) u.node(children [%empty ~])
-- ::
:: [%children ?(%lone %kith) @ @ *]
[%x %update-log-subset @ @ @ @ ~] =/ start=(unit atom) (rush i.t.t.path dem:ag)
=/ =ship (slav %p i.t.t.path) =/ end=(unit atom) (rush i.t.t.t.path dem:ag)
=/ =term i.t.t.t.path =/ =index:store
=/ start=(unit time) (slaw %da i.t.t.t.t.path) (turn t.t.t.t.pax (cury slav %ud))
=/ end=(unit time) (slaw %da i.t.t.t.t.t.path) =/ node (get-node graph index)
=/ update-log=(unit update-log:store) (~(get by update-logs) [ship term]) ?: ?& ?=(~ node)
?~ update-log [~ ~] ?=(^ index)
:: orm-log is ordered backwards, so swap start and end ==
``noun+!>(`update-log:store`(lot:orm-log u.update-log end start)) [~ ~]
:: =/ children=graph:store
[%x %update-log @ @ ~] ?~ node
=/ =ship (slav %p i.t.t.path) graph
=/ =term i.t.t.t.path ?: ?=(%empty -.children.u.node)
=/ update-log=(unit update-log:store) (~(get by update-logs) [ship term]) ~
?~ update-log [~ ~] p.children.u.node
``noun+!>(`update-log:store`u.update-log) :- ~ :- ~ :- %graph-update-2
:: !> ^- update:store
[%x %peek-update-log @ @ ~] :^ now.bowl %add-nodes [ship term]
=/ =ship (slav %p i.t.t.path) %- ~(gas by *(map index:store node:store))
=/ =term i.t.t.t.path %+ turn (tap:orm (lot:orm children end start))
=/ m-update-log=(unit update-log:store) |= [=atom =node:store]
(~(get by update-logs) [ship term]) ^- [index:store node:store]
:- ~ :- ~ :- %noun :- (snoc index atom)
!> ^- (unit time) ?: ?=(%kith i.t.pax) node
%+ biff m-update-log node(children [%empty ~])
|= =update-log:store ::
=/ result=(unit [=time =update:store]) [%siblings ?(%older %newer %oldest %newest) ?(%lone %kith) @ *]
(pry:orm-log:store update-log) =/ count (slav %ud i.t.t.t.pax)
(bind result head) =/ =index:store
== (turn t.t.t.t.pax (cury slav %ud))
:: =/ parent=index:store (snip index)
++ get-node-children =/ node
|= [=ship =term =index:store] (get-node graph ?:(?=(?(%oldest %newest) i.t.pax) index parent))
^- (unit graph:store) =/ children=graph:store
?: ?=(~ index) ?~ node
=/ graph graph
(~(get by graphs) [ship term]) ?: ?=(%empty -.children.u.node)
?~ graph ~ ~
`p.u.graph p.children.u.node
=/ node :- ~ :- ~ :- %graph-update-2
(get-node ship term index) !> ^- update:store
?~ node ~ :^ now.bowl %add-nodes [ship term]
?: ?=(%empty -.children.u.node) %- ~(gas by *(map index:store node:store))
~ %+ turn
`p.children.u.node ?- i.t.pax
:: %oldest (scag count (bap:orm children))
++ get-node %older (tab:orm children `(rear index) count)
|= [=ship =term =index:store] %newest (scag count (tap:orm children))
^- (unit node:store) ::
=/ parent-graph=(unit marked-graph:store) %newer
(~(get by graphs) [ship term]) %+ slag (safe-sub (lent -) count)
?~ parent-graph ~ (tap:orm (lot:orm children ~ `(rear index)))
=/ node=(unit node:store) ~ ==
=/ =graph:store p.u.parent-graph |= [=atom =node:store]
|- ^- [index:store node:store]
?~ index :- %- snoc
node :_ atom
?~ t.index ?:(?=(?(%newest %oldest) i.t.pax) index parent)
(get:orm graph i.index) ?: ?=(%kith i.t.t.pax) node
=. node (get:orm graph i.index) node(children [%empty ~])
?~ node ~ ::
?- -.children.u.node [%firstborn ^]
%empty ~ |^
%graph $(graph p.children.u.node, index t.index) =/ =index:store
(turn t.pax (cury slav %ud))
%- (bond |.(`(unit (unit cage))`[~ ~]))
%+ biff
(collect-parents graph index)
(corl some collect-firstborn)
::
++ collect-parents
|= [=graph:store =index:store]
^- (unit [node:store index:store (map index:store node:store)])
=| =(map index:store node:store)
=| =node:store
=| ind=index:store
=/ len (lent index)
|-
?: (gte (lent ind) len)
`[node ind map]
?> ?=(^ index)
=* atom i.index
?. (has:orm graph atom)
~
=: node (got:orm graph atom)
ind (snoc ind atom)
==
?: ?=(%empty -.children.node)
?. (gte (lent ind) len)
~
`[node ind (~(put by map) ind node)]
%_ $
index t.index
graph p.children.node
map (~(put by map) ind node(children empty+~))
==
::
++ collect-firstborn
|= [=node:store =index:store =(map index:store node:store)]
^- (unit (unit cage))
?: ?=(%empty -.children.node)
:- ~ :- ~ :- %graph-update-2
!>(`update:store`[now.bowl [%add-nodes [ship term] map]])
=/ item=[k=atom v=node:store]
(need (ram:orm p.children.node))
=. index (snoc index k.item)
$(map (~(put by map) index v.item(children empty+~)), node v.item)
--
==
::
++ get-node
|= [=graph:store =index:store]
^- (unit node:store)
=| node=(unit node:store)
|-
?~ index node
?~ t.index (get:orm graph i.index)
=. node (get:orm graph i.index)
?~ node ~
?: ?=(%empty -.children.u.node)
~
$(graph p.children.u.node, index t.index)
::
++ safe-sub
|= [a=@ b=@]
^- @
?:((gte b a) 0 (sub a b))
--
::
[%depth-first @ @ ~]
=/ count=(unit atom) (rush i.t.t.t.t.path dem:ag)
=/ start=(unit atom) (rush i.t.t.t.t.t.path dem:ag)
?: ?=(~ count)
[~ ~]
:- ~ :- ~ :- %graph-update-2
!> ^- update:store
:^ now.bowl %add-nodes [ship term]
=* a u.count
=/ b=(list (pair atom node:store))
(tab:orm graph start u.count)
=| c=index:store
=| d=(map index:store node:store)
=| e=@ud
=- d
|- ^- [e=@ud d=(map index:store node:store)]
?: ?|(?=(~ b) =(e a))
[e d]
=* atom p.i.b
=* node q.i.b
=. c (snoc c atom)
?- -.children.node
%empty
$(b t.b, e +(e), d (~(put by d) c node), c (snip c))
::
%graph
=/ f $(b (tab:orm p.children.node ~ (sub a e)))
?: =(e.f a) f
%_ $
b t.b
e +(e.f)
d (~(put by d.f) c node(children [%empty ~]))
c (snip c)
==
==
== ==
--
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card _this)
?+ wire (on-arvo:def wire sign-arvo)
::
:: old wire, do nothing
[%graph *] [~ this]
[%validator @ ~] [~ this]
[%try-rejoin @ *] [~ this]
== ==
:: ::
++ on-arvo on-arvo:def
++ on-agent on-agent:def ++ on-agent on-agent:def
++ on-leave on-leave:def ++ on-leave on-leave:def
++ on-fail on-fail:def ++ on-fail on-fail:def

View File

@ -289,7 +289,7 @@
:: ::
++ md-fact ++ md-fact
|= [=mark =vase] |= [=mark =vase]
?. ?=(%metadata-update-1 mark) jn-core ?. ?=(%metadata-update-2 mark) jn-core
=+ !<(=update:metadata vase) =+ !<(=update:metadata vase)
?. ?=(%initial-group -.update) jn-core ?. ?=(%initial-group -.update) jn-core
?. =(group.update rid) jn-core ?. =(group.update rid) jn-core

View File

@ -285,7 +285,7 @@
^- $-(indexed-post:graph-store (unit notif-kind:hook)) ^- $-(indexed-post:graph-store (unit notif-kind:hook))
=+ %^ scry [our now]:bowl =+ %^ scry [our now]:bowl
,mark=(unit mark) ,mark=(unit mark)
/gx/graph-store/graph-mark/(scot %p entity.rid)/[name.rid]/noun /gx/graph-store/graph/(scot %p entity.rid)/[name.rid]/mark/noun
?~ mark ?~ mark
|=(=indexed-post:graph-store ~) |=(=indexed-post:graph-store ~)
(scry-notif-conversion [our now]:bowl q.byk.bowl u.mark) (scry-notif-conversion [our now]:bowl q.byk.bowl u.mark)

View File

@ -113,7 +113,7 @@
(group-update !<(update:group-store q.cage.sign)) (group-update !<(update:group-store q.cage.sign))
[cards this] [cards this]
:: ::
%metadata-update-1 %metadata-update-2
=^ cards state =^ cards state
(metadata-update !<(update:metadata q.cage.sign)) (metadata-update !<(update:metadata q.cage.sign))
[cards this] [cards this]

View File

@ -2,7 +2,7 @@
/+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln /+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln
|% |%
+$ state +$ state
$: %13 $: %14
drum=state:drum drum=state:drum
helm=state:helm helm=state:helm
kiln=state:kiln kiln=state:kiln
@ -10,12 +10,13 @@
+$ any-state +$ any-state
$% state $% state
[ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)] [ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)]
[%7 drum=state:drum helm=state:helm kiln=state:kiln] [%7 drum=state:drum helm=state:helm kiln=state-1:kiln]
[%8 drum=state:drum helm=state:helm kiln=state:kiln] [%8 drum=state:drum helm=state:helm kiln=state-1:kiln]
[%9 drum=state:drum helm=state:helm kiln=state:kiln] [%9 drum=state:drum helm=state:helm kiln=state-1:kiln]
[%10 drum=state:drum helm=state:helm kiln=state:kiln] [%10 drum=state:drum helm=state:helm kiln=state-1:kiln]
[%11 drum=state:drum helm=state:helm kiln=state:kiln] [%11 drum=state:drum helm=state:helm kiln=state-1:kiln]
[%12 drum=state:drum helm=state:helm kiln=state:kiln] [%12 drum=state:drum helm=state:helm kiln=state-1:kiln]
[%13 drum=state:drum helm=state:helm kiln=state-1:kiln]
== ==
+$ any-state-tuple +$ any-state-tuple
$: drum=any-state:drum $: drum=any-state:drum

View File

@ -24,6 +24,6 @@
<div id="portal-root"></div> <div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script> <script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script> <script src="/~landscape/js/session.js"></script>
<script src="/~landscape/js/bundle/index.8074ae0006fba19803f5.js"></script> <script src="/~landscape/js/bundle/index.25a7d11c5e34f8db1f8c.js"></script>
</body> </body>
</html> </html>

View File

@ -38,7 +38,7 @@
update:metadata update:metadata
%metadata-update %metadata-update
%metadata-push-hook %metadata-push-hook
1 1 2 2
%.n %.n
== ==
+$ state-zero +$ state-zero
@ -180,7 +180,7 @@
%kick [watch-store^~ state] %kick [watch-store^~ state]
:: ::
%fact %fact
?> ?=(%metadata-update-1 p.cage.sign) ?> ?=(%metadata-update-2 p.cage.sign)
=+ !<(=update:metadata q.cage.sign) =+ !<(=update:metadata q.cage.sign)
?. ?=(%initial-group -.update) `state ?. ?=(%initial-group -.update) `state
`state(previews (~(del by previews) group.update)) `state(previews (~(del by previews) group.update))
@ -325,7 +325,7 @@
%+ turn ~(tap by associations) %+ turn ~(tap by associations)
|= [=md-resource:metadata =association:metadata] |= [=md-resource:metadata =association:metadata]
%+ poke-our:pass:io %metadata-store %+ poke-our:pass:io %metadata-store
:- %metadata-update-1 :- %metadata-update-2
!> ^- update:metadata !> ^- update:metadata
[%remove resource md-resource] [%remove resource md-resource]
:: ::

View File

@ -14,7 +14,7 @@
update:store update:store
%metadata-update %metadata-update
%metadata-pull-hook %metadata-pull-hook
1 1 2 2
== ==
:: ::
+$ agent (push-hook:push-hook config) +$ agent (push-hook:push-hook config)
@ -94,7 +94,7 @@
^- (quip card (unit vase)) ^- (quip card (unit vase))
=/ =update:store !<(update:store vas) =/ =update:store !<(update:store vas)
:- ~ :- ~
?. ?=(?(%add %remove) -.update) ?. ?=(?(%add %remove %edit) -.update)
~ ~
=/ role=(unit (unit role-tag)) =/ role=(unit (unit role-tag))
(role-for-ship:grp group.update src.bowl) (role-for-ship:grp group.update src.bowl)

View File

@ -153,18 +153,145 @@
++ on-poke ++ on-poke
|= [=mark =vase] |= [=mark =vase]
^- (quip card _this) ^- (quip card _this)
?> (team:title our.bowl src.bowl) ?> (team:title [our src]:bowl)
|^
=^ cards state =^ cards state
?+ mark (on-poke:def mark vase) ?+ mark (on-poke:def mark vase)
?(%metadata-action %metadata-update-1) ?(%metadata-action %metadata-update-2)
(poke-metadata-update:mc !<(update:store vase)) (poke-metadata-update !<(update:store vase))
:: ::
%import %import
(poke-import:mc q.vase) (poke-import q.vase)
:: ::
%noun ~& +.state `state %noun ~& +.state `state
== ==
[cards this] [cards this]
::
++ poke-metadata-update
|= upd=update:store
^- (quip card _state)
|^
?+ -.upd ~|(%bad-poke !!)
%add (handle-add +.upd)
%remove (handle-remove +.upd)
%edit (handle-edit +.upd)
%initial-group (handle-initial-group +.upd)
==
::
++ handle-add
|= [group=resource =md-resource:store =metadatum:store]
^- (quip card _state)
:- %- send-diff:mc
[%add group md-resource metadatum]
%= state
associations
(~(put by associations) md-resource [group metadatum])
::
app-indices
%+ ~(put ju app-indices)
app-name.md-resource
[group resource.md-resource]
::
resource-indices
(~(put by resource-indices) md-resource group)
::
group-indices
(~(put ju group-indices) group md-resource)
==
::
++ handle-edit
|= [group=resource =md-resource:store =edit-field:store]
^- (quip card _state)
=/ [new-group=resource =metadatum:store]
~| %no-assoc-for-edit
(~(got by associations) md-resource)
~| %cant-reassign-groups
?> =(new-group group)
=. metadatum
?- -.edit-field
%title metadatum(title title.edit-field)
%description metadatum(description description.edit-field)
%color metadatum(color color.edit-field)
%picture metadatum(picture url.edit-field)
%hidden metadatum(hidden hidden.edit-field)
%preview metadatum(preview preview.edit-field)
%vip metadatum(vip vip.edit-field)
==
:- (send-diff:mc %add group md-resource metadatum)
%_ state
associations (~(put by associations) md-resource group metadatum)
==
::
++ handle-remove
|= [group=resource =md-resource:store]
^- (quip card _state)
:- (send-diff:mc [%remove group md-resource])
%= state
associations
(~(del by associations) md-resource)
::
app-indices
%+ ~(del ju app-indices)
app-name.md-resource
[group resource.md-resource]
::
resource-indices
(~(del by resource-indices) md-resource)
::
group-indices
(~(del ju group-indices) group md-resource)
==
::
++ handle-initial-group
|= [group=resource =associations:store]
=/ assocs=(list [=md-resource:store grp=resource =metadatum:store])
~(tap by associations)
:- (send-diff:mc %initial-group group associations)
|-
?~ assocs
state
=, assocs
?> =(group grp.i)
=^ cards state
(handle-add group [md-resource metadatum]:i)
$(assocs t)
--
::
++ poke-import
|= arc=*
^- (quip card _state)
|^
=^ cards state
(on-load:mc !>([%9 (remake-metadata ;;(tree-metadata +.arc))]))
:_ state
%+ weld cards
%+ turn ~(tap in ~(key by group-indices))
|= rid=resource
%- poke-our
?: =(entity.rid our.bowl)
:- %metadata-push-hook
push-hook-action+!>([%add rid])
:- %metadata-pull-hook
pull-hook-action+!>([%add [entity .]:rid])
::
++ poke-our
|= [app=term =cage]
^- card
[%pass / %agent [our.bowl app] %poke cage]
::
+$ tree-metadata
$: associations=(tree [md-resource:store [resource metadatum:store]])
~
==
::
++ remake-metadata
|= tm=tree-metadata
^- base-state-3
:* (remake-map associations.tm)
~
==
--
--
:: ::
++ on-watch ++ on-watch
|= =path |= =path
@ -174,7 +301,7 @@
=/ cards=(list card) =/ cards=(list card)
?+ path (on-watch:def path) ?+ path (on-watch:def path)
[%all ~] [%all ~]
(give %metadata-update-1 !>([%associations associations])) (give %metadata-update-2 !>([%associations associations]))
:: ::
[%updates ~] [%updates ~]
~ ~
@ -182,7 +309,7 @@
[%app-name @ ~] [%app-name @ ~]
=/ =app-name:store i.t.path =/ =app-name:store i.t.path
=/ app-indices (metadata-for-app:mc app-name) =/ app-indices (metadata-for-app:mc app-name)
(give %metadata-update-1 !>([%associations app-indices])) (give %metadata-update-2 !>([%associations app-indices]))
== ==
[cards this] [cards this]
:: ::
@ -418,109 +545,6 @@
ship+path.md-resource ship+path.md-resource
[[path [%graph new-path]] m(module app)] [[path [%graph new-path]] m(module app)]
-- --
::
:: TODO: refactor into a |^ inside the agent core
++ poke-metadata-update
|= upd=update:store
^- (quip card _state)
?> (team:title [our src]:bowl)
?+ -.upd !!
%add (handle-add +.upd)
%remove (handle-remove +.upd)
%initial-group (handle-initial-group +.upd)
==
::
:: TODO: refactor into a |^ inside the agent core
++ poke-import
|= arc=*
^- (quip card _state)
|^
=^ cards state
(on-load !>([%9 (remake-metadata ;;(tree-metadata +.arc))]))
:_ state
%+ weld cards
%+ turn ~(tap in ~(key by group-indices))
|= rid=resource
%- poke-our
?: =(entity.rid our.bowl)
:- %metadata-push-hook
push-hook-action+!>([%add rid])
:- %metadata-pull-hook
pull-hook-action+!>([%add [entity .]:rid])
::
++ poke-our
|= [app=term =cage]
^- card
[%pass / %agent [our.bowl app] %poke cage]
::
+$ tree-metadata
$: associations=(tree [md-resource:store [resource metadatum:store]])
~
==
::
++ remake-metadata
|= tm=tree-metadata
^- base-state-3
:* (remake-map associations.tm)
~
==
--
::
++ handle-add
|= [group=resource =md-resource:store =metadatum:store]
^- (quip card _state)
:- %- send-diff
[%add group md-resource metadatum]
%= state
associations
(~(put by associations) md-resource [group metadatum])
::
app-indices
%+ ~(put ju app-indices)
app-name.md-resource
[group resource.md-resource]
::
resource-indices
(~(put by resource-indices) md-resource group)
::
group-indices
(~(put ju group-indices) group md-resource)
==
::
++ handle-remove
|= [group=resource =md-resource:store]
^- (quip card _state)
:- (send-diff [%remove group md-resource])
%= state
associations
(~(del by associations) md-resource)
::
app-indices
%+ ~(del ju app-indices)
app-name.md-resource
[group resource.md-resource]
::
resource-indices
(~(del by resource-indices) md-resource)
::
group-indices
(~(del ju group-indices) group md-resource)
==
::
++ handle-initial-group
|= [group=resource =associations:store]
=/ assocs=(list [=md-resource:store grp=resource =metadatum:store])
~(tap by associations)
:- (send-diff %initial-group group associations)
|-
?~ assocs
state
=, assocs
?> =(group grp.i)
=^ cards state
(handle-add group [md-resource metadatum]:i)
$(assocs t)
::
++ metadata-for-app ++ metadata-for-app
|= =app-name:store |= =app-name:store
^- associations:store ^- associations:store
@ -556,6 +580,6 @@
++ update-subscribers ++ update-subscribers
|= [pax=path =update:store] |= [pax=path =update:store]
^- (list card) ^- (list card)
[%give %fact ~[pax] %metadata-update-1 !>(update)]~ [%give %fact ~[pax] %metadata-update-2 !>(update)]~
-- --
-- --

View File

@ -0,0 +1,20 @@
:: Kiln: Fuse local desk from (optionally-)foreign sources
::
:::: /hoon/fuse/hood/gen
::
/+ *hood-kiln
/* help-text %txt /gen/hood/fuse/help/txt
=, clay
::
::::
::
=>
|%
+$ fuse-list-arg $@(~ [des=desk ~])
--
:- %say
|= [* [arg=fuse-list-arg] ~]
:- %kiln-fuse-list
?~ arg
~
`des.arg

View File

@ -2,14 +2,59 @@
:: ::
:::: /hoon/fuse/hood/gen :::: /hoon/fuse/hood/gen
:: ::
/+ *hood-kiln
/* help-text %txt /gen/hood/fuse/help/txt /* help-text %txt /gen/hood/fuse/help/txt
=, clay =, clay
:: ::
:::: ::::
:: ::
=>
|%
+$ fuse-arg
$: des=desk
:: specified as [germ path] instead of [path germ] so
:: users can write mate//=home= instead of [/=home= %mate]
::
res=[?([%cancel ~] [bas=path con=(list [germ path])])]
==
::
++ parse-fuse-source
|= bec=beak
^- fuse-source
:: This is a slight overload of the label, but
:: it provides a nicer interface for the user so
:: we'll go with it.
::
?: ?=([%tas *] r.bec)
?: =(p.r.bec %track)
[p.bec q.bec %trak]
bec
bec
::
++ de-beak
|= pax=path
^- beak
=/ bem=beam (need (de-beam pax))
?> =(s.bem /)
-.bem
::
++ path-to-fuse-source
|= pax=path
^- fuse-source
(parse-fuse-source (de-beak pax))
--
:- %say :- %say
|= [[now=@da eny=@uvJ bec=beak] [arg=[?(~ [des=desk bas=beak con=(list [beak germ]) ~])]] ~] |= [* [arg=[?(~ fuse-arg)]] [overwrite=$~(| flag) ~]]
:- %kiln-fuse :- %kiln-fuse
?~ arg ?~ arg
((slog (turn `wain`help-text |=(=@t leaf+(trip t)))) ~) ((slog (turn `wain`help-text |=(=@t leaf+(trip t)))) ~)
[des bas con]:arg :- des.arg
?: ?=([%cancel ~] res.arg)
~
:+ overwrite
(path-to-fuse-source bas.res.arg)
%+ turn
con.res.arg
|= [g=germ pax=path]
^- [fuse-source germ]
[(path-to-fuse-source pax) g]

View File

@ -1,8 +1,21 @@
Usage: Usage:
|fuse %destination-desk base-beak ~[[source-beak %some-germ] [another-beak %another-germ]] |fuse %dest /=kids= mate//~nel/home= meet//~zod/kids/track
|fuse %old-desk /=kids= only-that//~nus/test=, =overwrite &
|fuse %desk-to-cancel-fuse-into %cancel
A fuse replaces the contents of %destination-desk with the merge of the A %fuse request in clay replaces the contents of %destination-desk
specified beaks according to their merge strategies. This has no dependence with the merge of the specified beaks according to their merge
on the previous state of %destination-desk so any commits/work there will strategies. This has no dependence on the previous state of %dest
be overwritten. so any commits/work there will be overwritten.
|fuse extends this concept with the idea of a tracked source. When
specifying beaks to include in your fuse, specify %track instead of
a case. This will tell |fuse to retrieve the latest version of the
source beak AND to rerun the %fuse request whenever that tracked
source changes. A fuse can have many tracked sources, or none. The
base may be tracked as well.
The overwrite flag allows you to overwrite a currently ongoing fuse.
Without this flag, attempting a fuse into a desk that you already
|fuse'd into will error.

View File

@ -1,4 +1,4 @@
/- sur=graph-store, pos=post /- sur=graph-store, pos=post, pull-hook
/+ res=resource, migrate /+ res=resource, migrate
=< [sur .] =< [sur .]
=< [pos .] =< [pos .]
@ -872,6 +872,10 @@
^- card:agent:gall ^- card:agent:gall
=/ res-path (en-path:res rid) =/ res-path (en-path:res rid)
=/ wire [%try-rejoin (scot %ud nack-count) res-path] =/ wire [%try-rejoin (scot %ud nack-count) res-path]
[%pass wire %agent [entity.rid %graph-push-hook] %watch resource+res-path] =/ =cage
:- %pull-hook-action
!> ^- action:pull-hook
[%add [entity .]:rid]
[%pass wire %agent [our %graph-pull-hook] %poke cage]
-- --
-- --

View File

@ -96,22 +96,12 @@
?> ?=(%add-graph -.q.update) ?> ?=(%add-graph -.q.update)
graph.q.update graph.q.update
:: ::
++ gut-younger-node-siblings
|= [res=resource =index:store]
^- (map index:store node:store)
=+ %+ scry-for ,=update:store
%+ weld
/node-siblings/younger/(scot %p entity.res)/[name.res]/all
(turn index (cury scot %ud))
?> ?=(%add-nodes -.q.update)
nodes.q.update
::
++ got-node ++ got-node
|= [res=resource =index:store] |= [res=resource =index:store]
^- node:store ^- node:store
=+ %+ scry-for ,=update:store =+ %+ scry-for ,=update:store
%+ weld %+ weld
/node/(scot %p entity.res)/[name.res] /graph/(scot %p entity.res)/[name.res]/node/index/kith
(turn index (cury scot %ud)) (turn index (cury scot %ud))
?> ?=(%add-nodes -.q.update) ?> ?=(%add-nodes -.q.update)
?> ?=(^ nodes.q.update) ?> ?=(^ nodes.q.update)
@ -122,7 +112,7 @@
^- ? ^- ?
%+ scry-for ,? %+ scry-for ,?
%+ weld %+ weld
/node-exists/(scot %p entity.res)/[name.res] /graph/(scot %p entity.res)/[name.res]/node/exists
(turn index (cury scot %ud)) (turn index (cury scot %ud))
:: ::
++ get-update-log ++ get-update-log
@ -134,13 +124,13 @@
++ peek-update-log ++ peek-update-log
|= res=resource |= res=resource
^- (unit time) ^- (unit time)
(scry-for (unit time) /peek-update-log/(scot %p entity.res)/[name.res]) (scry-for (unit time) /update-log/(scot %p entity.res)/[name.res]/latest)
:: ::
++ get-update-log-subset ++ get-update-log-subset
|= [res=resource start=@da] |= [res=resource start=@da]
^- update-log:store ^- update-log:store
%+ scry-for update-log:store %+ scry-for update-log:store
/update-log-subset/(scot %p entity.res)/[name.res]/(scot %da start)/'~' /update-log/(scot %p entity.res)/[name.res]/subset/(scot %da start)/'~'
:: ::
++ get-keys ++ get-keys
^- resources ^- resources
@ -183,5 +173,5 @@
:: ::
++ get-mark ++ get-mark
|= res=resource |= res=resource
(scry-for ,(unit mark) /graph-mark/(scot %p entity.res)/[name.res]) (scry-for ,(unit mark) /graph/(scot %p entity.res)/[name.res]/mark)
-- --

View File

@ -3,11 +3,29 @@
=, space:userlib =, space:userlib
=, format =, format
|% |%
+$ state [%1 pith-1] +$ state state-2
+$ state-2 [%2 pith-2]
+$ state-1 [%1 pith-1]
+$ state-0 [%0 pith-0]
+$ any-state +$ any-state
$% state $% state-2
[%0 pith-0] state-1
state-0
== ==
+$ pith-2 ::
$: rem=(map desk per-desk) ::
syn=(map kiln-sync let=@ud) ::
ota=(unit [=ship =desk =aeon]) ::
commit-timer=[way=wire nex=@da tim=@dr mon=term] ::
:: map desk to the currently ongoing fuse request
:: and the latest version numbers for beaks to
fus=(map desk per-fuse)
:: used for fuses - every time we get a fuse we
:: bump this. used when calculating hashes to
:: ensure they're unique even when the same
:: request is made multiple times.
hxs=(map desk @ud)
== ::
+$ pith-1 :: +$ pith-1 ::
$: rem=(map desk per-desk) :: $: rem=(map desk per-desk) ::
syn=(map kiln-sync let=@ud) :: syn=(map kiln-sync let=@ud) ::
@ -31,6 +49,15 @@
sud=@tas :: from desk sud=@tas :: from desk
cas=case :: at case cas=case :: at case
== ==
+$ per-fuse :: per fuse state
:: map [ship desk] to latest version number we
:: have for them. used for things we're %trak-ing
:: our invariant here is to store the latest version
:: number we've heard of.
$: mox=(map [ship desk] let=@ud)
:: relevant parts of originating request
kf=kiln-fuse-data
==
+$ kiln-commit term :: +$ kiln-commit term ::
+$ kiln-mount :: +$ kiln-mount ::
$: pax=path :: $: pax=path ::
@ -55,12 +82,26 @@
cas=case :: cas=case ::
gim=?(%auto germ) :: gim=?(%auto germ) ::
== ==
+$ fuse-source [who=ship des=desk ver=$@(%trak case)]
:: actual poke
+$ kiln-fuse +$ kiln-fuse
$@ ~ $@ ~
$: syd=desk $: syd=desk
bas=beak $@ ~ :: signifies clearing the fuse
con=(list [beak germ]) $: overwrite=flag :: force overwrite previous fuse
bas=fuse-source
con=(list [fuse-source germ])
==
== ==
:: state tracked by kiln
+$ kiln-fuse-data
$: syd=desk
bas=fuse-source
con=(list [fuse-source germ])
==
:: Request to list current fuses. ~ means "list all"
::
+$ kiln-fuse-list (unit desk)
-- --
|= [bowl:gall state] |= [bowl:gall state]
?> =(src our) ?> =(src our)
@ -85,6 +126,15 @@
~[leaf+"from {<sud>}" leaf+"on {<who>}" leaf+"to {<syd>}"] ~[leaf+"from {<sud>}" leaf+"on {<who>}" leaf+"to {<syd>}"]
:: ::
++ on-load ++ on-load
=>
|%
++ state-1-to-2
|= s=state-1
^- state-2
=/ p=pith-1 +.s
:- %2
[rem.p syn.p ota.p commit-timer.p *(map desk per-fuse) *(map desk @ud)]
--
|= [hood-version=@ud old=any-state] |= [hood-version=@ud old=any-state]
=< abet =< abet
=? . ?=(%0 -.old) =? . ?=(%0 -.old)
@ -97,8 +147,8 @@
?: &(=(%base syd.i.syncs) !=(our her.i.syncs) =(%kids sud.i.syncs)) ?: &(=(%base syd.i.syncs) !=(our her.i.syncs) =(%kids sud.i.syncs))
`[syd her sud]:i.syncs `[syd her sud]:i.syncs
$(syncs t.syncs) $(syncs t.syncs)
::
=. +<+.$.abet =. +<+.$.abet
%- state-1-to-2
=- old(- %1, |3 [ota=~ commit-timer.old], syn -) =- old(- %1, |3 [ota=~ commit-timer.old], syn -)
?~ recognized-ota ?~ recognized-ota
syn syn
@ -108,7 +158,8 @@
(poke-internal:update `[her sud]:u.recognized-ota) (poke-internal:update `[her sud]:u.recognized-ota)
+(old +<+.$.abet) +(old +<+.$.abet)
:: ::
?> ?=(%1 -.old) =? old ?=(%1 -.old) (state-1-to-2 old)
?> ?=(%2 -.old)
=. +<+.$.abet old =. +<+.$.abet old
..abet ..abet
:: ::
@ -387,10 +438,76 @@
?~ +< abet ?~ +< abet
abet:abet:(merge:(work syd) ali sud cas gim) abet:abet:(merge:(work syd) ali sud cas gim)
:: ::
++ poke-fuse-list
=>
|%
++ format-fuse
|= [into=desk pf=per-fuse]
^- tank
=/ sources=tape
%+ reel
con.kf.pf
|= [[fs=fuse-source g=germ] acc=tape]
^- tape
;: weld
" ["
(format-fuse-source fs)
" "
<g>
"]"
acc
==
:- %leaf
;: weld
"|fuse {<into>} "
(format-fuse-source bas.kf.pf)
sources
==
:: +format-fuse-source: fuse source -> beak -> path
::
++ format-fuse-source
|= fs=fuse-source
^- tape
=/ bec=beak [who.fs des.fs ?:(?=([%trak] ver.fs) [%tas %track] ver.fs)]
<(en-beam [bec /])>
--
|= k=kiln-fuse-list
^+ abet
%. abet
?~ k
?~ fus
(slog [leaf+"no ongoing fuses" ~])
%- slog
%+ roll
~(tap by `(map desk per-fuse)`fus)
|= [[syd=desk pf=per-fuse] acc=tang]
^- tang
[(format-fuse syd pf) acc]
=/ pfu=(unit per-fuse) (~(get by fus) u.k)
?~ pfu
(slog [leaf+"no ongoing fuse for {<u.k>}" ~])
(slog [(format-fuse u.k u.pfu) ~])
::
++ poke-fuse ++ poke-fuse
|= k=kiln-fuse |= k=kiln-fuse
?~ k abet ?~ k abet
abet:(emit [%pass /kiln/fuse/[syd.k] %arvo %c [%fuse syd.k bas.k con.k]]) =/ payload +.k
?~ payload
:: cancelling an ongoing fuse
%- (slog [leaf+"cancelling fuse into {<syd.k>}" ~])
=/ f (fuzz syd.k now)
?~ f
abet
abet:abet:delete:u.f
?: &(!overwrite.payload (~(has by fus) syd.k))
((slog [leaf+"existing fuse into {<syd.k>} - need =overwrite &" ~]) abet)
=. fus (~(put by fus) syd.k [~ [syd.k bas.payload con.payload]])
=/ old-cnt=@ud (~(gut by hxs) syd.k 0)
=. hxs (~(put by hxs) syd.k +(old-cnt))
=/ f (fuzz syd.k now)
?~ f
abet
abet:abet:fuse:u.f
:: ::
++ poke-cancel ++ poke-cancel
|= a=@tas |= a=@tas
@ -442,6 +559,7 @@
%kiln-label =;(f (f !<(_+<.f vase)) poke-label) %kiln-label =;(f (f !<(_+<.f vase)) poke-label)
%kiln-merge =;(f (f !<(_+<.f vase)) poke-merge) %kiln-merge =;(f (f !<(_+<.f vase)) poke-merge)
%kiln-fuse =;(f (f !<(_+<.f vase)) poke-fuse) %kiln-fuse =;(f (f !<(_+<.f vase)) poke-fuse)
%kiln-fuse-list =;(f (f !<(_+<.f vase)) poke-fuse-list)
%kiln-mount =;(f (f !<(_+<.f vase)) poke-mount) %kiln-mount =;(f (f !<(_+<.f vase)) poke-mount)
%kiln-ota =;(f (f !<(_+<.f vase)) poke:update) %kiln-ota =;(f (f !<(_+<.f vase)) poke:update)
%kiln-ota-info =;(f (f !<(_+<.f vase)) poke-ota-info) %kiln-ota-info =;(f (f !<(_+<.f vase)) poke-ota-info)
@ -489,6 +607,21 @@
[%autocommit *] %+ take-wake-autocommit t.wire [%autocommit *] %+ take-wake-autocommit t.wire
?>(?=(%wake +<.sign-arvo) +>.sign-arvo) ?>(?=(%wake +<.sign-arvo) +>.sign-arvo)
[%ota *] abet:(take:update t.wire sign-arvo) [%ota *] abet:(take:update t.wire sign-arvo)
[%fuse-request @tas *]
=/ f (fuzz i.t.wire now)
?~ f
abet
abet:abet:(take:u.f t.t.wire sign-arvo)
[%fuse @tas *] ?> ?=(%mere +<.sign-arvo)
=/ syd=desk i.t.wire
?. ?=([%| *] +>.sign-arvo)
?~ p.p.sign-arvo
abet
=/ msg=tape "fuse merge conflict for {<syd>}"
%- (slog [leaf+msg >p.p.sign-arvo< ~])
abet
%- (slog leaf+"failed fuse for {<syd>}" p.p.sign-arvo)
abet
* *
?+ +<.sign-arvo ?+ +<.sign-arvo
((slog leaf+"kiln: strange card {<+<.sign-arvo wire>}" ~) abet) ((slog leaf+"kiln: strange card {<+<.sign-arvo wire>}" ~) abet)
@ -567,6 +700,122 @@
++ spam ++ spam
|= mes=(list tank) |= mes=(list tank)
((slog mes) ..spam) ((slog mes) ..spam)
:: state machine for fuses
::
++ fuzz
|= [syd=desk now=@da]
=/ pfu=(unit per-fuse) (~(get by fus) syd)
?~ pfu
~
=* kf kf.u.pfu
=* mox mox.u.pfu
=/ should-delete=flag |
%- some
|%
:: finalize
::
++ abet
?: should-delete
..fuzz(fus (~(del by fus) syd))
..fuzz(fus (~(put by fus) syd [mox kf]))
::
++ delete
^+ ..delete
=. should-delete &
..delete
:: queue moves
::
++ blab
|= new=(list card:agent:gall)
^+ +>
+>.$(moz (welp new moz))
:: +make-requests: send requests for each %trak source.
::
++ make-requests
^+ ..abet
=/ movs=(list card:agent:gall)
%+ murn
[[bas.kf *germ] con.kf]
|= [fs=fuse-source germ]
^- (unit card:agent:gall)
?^ ver.fs
:: static source, don't need to track
~
=/ bec=beak (realize-fuse-source fs &)
?> =(who.fs p.bec)
?> =(des.fs q.bec)
=/ hax=@ud (mug [kf (~(got by hxs) syd)])
=/ wir=wire
/kiln/fuse-request/[syd]/(scot %p p.bec)/[q.bec]/(scot %ud hax)
=/ rav=rave [%sing %w r.bec /]
=/ rif=riff [q.bec `rav]
`[%pass wir %arvo %c [%warp who.fs rif]]
:: No need to keep state if all the sources are static
?~ movs
delete
(blab movs)
::
++ send-fuse
^+ ..abet
=/ bas=beak (realize-fuse-source bas.kf |)
=/ con=(list [beak germ])
%+ turn
con.kf
|= [fs=fuse-source g=germ]
[(realize-fuse-source fs |) g]
%- blab
[%pass /kiln/fuse/[syd] %arvo %c [%fuse syd bas con]]~
::
++ fuse
^+ ..abet
send-fuse:make-requests
::
++ take
|= [wir=wire =sign-arvo]
^+ ..fuse
?> =((lent wir) 3)
=/ who=ship (slav %p (snag 0 wir))
=/ src=desk (snag 1 wir)
=/ hax=@ud (slav %ud (snag 2 wir))
?. =(hax (mug [kf (~(got by hxs) syd)]))
:: If the hash in the wire doesn't match the current request
:: this is a response for a previous fuse that we can ignore.
..take
?> ?=([?(%clay %behn) %writ *] sign-arvo)
=/ gif +.sign-arvo
?~ p.gif
%- (slog leaf+"|fuse request failed for {<src>} on <who> - cancelling")
delete
=/ cas=cass:clay !<(cass:clay +.r.u.p.gif)
=. mox (~(put by mox) [who src] ud.cas)
fuse
::
:: utility functions below
::
:: +realize-fuse-source: convert a fuse-source to a
:: fully realized beak.
::
++ realize-fuse-source
|= [fs=fuse-source incr=flag]
^- beak
:+ who.fs
des.fs
?@ ver.fs
(realize-case [who.fs des.fs incr])
`case`ver.fs
::
++ realize-case
|= [who=ship des=desk incr=flag]
^- case
=/ let=(unit @ud) (~(get by mox) [who des])
^- case
?~ let
da+now
:- %ud
?: incr
+(u.let)
u.let
--
:: ::
++ auto ++ auto
|= kiln-sync |= kiln-sync

View File

@ -37,6 +37,17 @@
[%metadata (^metadatum metadatum)] [%metadata (^metadatum metadatum)]
== ==
:: ::
++ edit-field
|= edt=^edit-field
^- json
%+ frond -.edt
^- json
?- -.edt
%color [%s `@t`(scot %ux color.edt)]
?(%title %description %picture %vip) [%s `@t`+.edt]
?(%preview %hidden) [%b `?`+.edt]
==
::
++ metadatum ++ metadatum
|= met=^metadatum |= met=^metadatum
^- json ^- json
@ -85,6 +96,16 @@
[%resource s+(enjs-path:resource resource.resource.upd)] [%resource s+(enjs-path:resource resource.resource.upd)]
[%metadata (metadatum metadatum.upd)] [%metadata (metadatum metadatum.upd)]
== ==
::
%edit
:- %edit
%- pairs
:~ [%group s+(enjs-path:resource group.upd)]
[%app-name s+app-name.resource.upd]
[%resource s+(enjs-path:resource resource.resource.upd)]
[%edit (edit-field edit-field.upd)]
==
::
%updated-metadata %updated-metadata
:- %add :- %add
%- pairs %- pairs
@ -136,6 +157,25 @@
:~ [%add add] :~ [%add add]
[%remove remove] [%remove remove]
[%initial-group initial-group] [%initial-group initial-group]
[%edit edit]
==
::
++ edit
%- ot
:~ [%group dejs-path:resource]
[%resource md-resource]
[%edit edit-field]
==
::
++ edit-field
%- of
:~ [%title so]
[%description so]
[%color nu]
[%picture so]
[%preview bo]
[%hidden bo]
[%vip vip]
== ==
:: ::
++ initial-group ++ initial-group

View File

@ -30,7 +30,7 @@
|= =vase |= =vase
^- (list resource) ^- (list resource)
=/ =update:store !<(update:store vase) =/ =update:store !<(update:store vase)
?. ?=(?(%add %remove %initial-group) -.update) ~ ?. ?=(?(%add %remove %initial-group %edit) -.update) ~
~[group.update] ~[group.update]
:: ::
++ app-paths-from-group ++ app-paths-from-group

View File

@ -232,58 +232,76 @@
:: +veri:dawn: validate keys, life, discontinuity, &c :: +veri:dawn: validate keys, life, discontinuity, &c
:: ::
++ veri ++ veri
|= [=seed:jael =point:azimuth =live] |= [=ship =feed:jael =point:azimuth =live]
^- (unit error=term) ^- (each seed:jael (lest error=term))
=/ rac (clan:title who.seed) |^ ?@ -.feed
=/ cub (nol:nu:crub:crypto key.seed) ?^ err=(test feed) |+[u.err ~]
?- rac &+feed
%pawn ?> ?=([%1 ~] -.feed)
:: a comet address is the fingerprint of the keypair =| errs=(list term)
:: |-
?. =(who.seed `@`fig:ex:cub) ?~ kyz.feed
`%key-mismatch |+?~(errs [%no-key ~] errs)
:: a comet can never be breached =/ =seed:jael [who [lyf key ~]:i.kyz]:feed
:: ?~ err=(test seed)
?^ live &+seed
`%already-booted =. errs (snoc errs u.err)
:: a comet can never be re-keyed $(kyz.feed t.kyz.feed)
::
?. ?=(%1 lyf.seed)
`%invalid-life
~
:: ::
%earl ++ test
~ |= =seed:jael
:: ^- (unit error=term)
* ?. =(ship who.seed) `%not-our-key
:: on-chain ships must be launched =/ rac (clan:title who.seed)
=/ cub (nol:nu:crub:crypto key.seed)
?- rac
%pawn
:: a comet address is the fingerprint of the keypair
::
?. =(who.seed `@`fig:ex:cub)
`%key-mismatch
:: a comet can never be breached
::
?^ live
`%already-booted
:: a comet can never be re-keyed
::
?. ?=(%1 lyf.seed)
`%invalid-life
~
:: ::
?~ net.point %earl
`%not-keyed ~
=* net u.net.point
:: boot keys must match the contract
:: ::
?. =(pub:ex:cub pass.net) *
~& [%key-mismatch pub:ex:cub pass.net] :: on-chain ships must be launched
`%key-mismatch ::
:: life must match the contract ?~ net.point
:: `%not-keyed
?. =(lyf.seed life.net) =* net u.net.point
`%life-mismatch :: boot keys must match the contract
:: the boot life must be greater than and discontinuous with ::
:: the last seen life (per the sponsor) ?. =(pub:ex:cub pass.net)
:: `%key-mismatch
?: ?& ?=(^ live) :: life must match the contract
?| ?=(%| breach.u.live) ::
(lte life.net life.u.live) ?. =(lyf.seed life.net)
== == `%life-mismatch
`%already-booted :: the boot life must be greater than and discontinuous with
:: produce the sponsor for vere :: the last seen life (per the sponsor)
:: ::
~? !has.sponsor.net ?: ?& ?=(^ live)
[%no-sponsorship-guarantees-from who.sponsor.net] ?| ?=(%| breach.u.live)
~ (lte life.net life.u.live)
== == ==
`%already-booted
:: produce the sponsor for vere
::
~? !has.sponsor.net
[%no-sponsorship-guarantees-from who.sponsor.net]
~
==
--
:: +sponsor:dawn: retreive sponsor from point :: +sponsor:dawn: retreive sponsor from point
:: ::
++ sponsor ++ sponsor

View File

@ -1,15 +1,22 @@
/+ store=metadata-store /+ store=metadata-store
|_ =update:store |_ =update:one:store
++ grad %noun ++ grad %noun
++ grow ++ grow
|% |%
++ noun update ++ noun update
++ json (update:enjs:store update) ++ metadata-update-2
^- update:store
update
-- --
:: ::
++ grab ++ grab
|% |%
++ noun update:store ++ noun update:one:store
++ json action:dejs:store :: This is ok, we don't send %edit over the wire yet.
++ metadata-update-2
|= upd=update:store
^- update:one:store
?< ?=(%edit -.upd)
upd
-- --
-- --

View File

@ -0,0 +1,15 @@
/+ store=metadata-store
|_ =update:store
++ grad %noun
++ grow
|%
++ noun update
++ json (update:enjs:store update)
--
::
++ grab
|%
++ noun update:store
++ json action:dejs:store
--
--

View File

@ -44,6 +44,16 @@
[%empty ~] [%empty ~]
== ==
:: ::
+$ edit-field
$% [%title title=cord]
[%description description=cord]
[%color color=@ux]
[%picture =url]
[%preview preview=?]
[%hidden hidden=?]
[%vip vip=vip-metadata]
==
::
+$ metadatum +$ metadatum
$: title=cord $: title=cord
description=cord description=cord
@ -60,6 +70,7 @@
+$ action +$ action
$% [%add group=resource resource=md-resource =metadatum] $% [%add group=resource resource=md-resource =metadatum]
[%remove group=resource resource=md-resource] [%remove group=resource resource=md-resource]
[%edit group=resource resource=md-resource =edit-field]
[%initial-group group=resource =associations] [%initial-group group=resource =associations]
== ==
:: ::
@ -79,6 +90,18 @@
== ==
== ==
:: historical :: historical
++ one
|%
::
+$ action
$~ [%remove *resource *md-resource]
$< %edit ^action
::
+$ update
$~ [%remove *resource *md-resource]
$< %edit ^update
::
--
++ zero ++ zero
|% |%
:: ::

View File

@ -1879,7 +1879,12 @@
[%public-keys =public-keys-result] :: ethereum changes [%public-keys =public-keys-result] :: ethereum changes
[%turf turf=(list turf)] :: domains [%turf turf=(list turf)] :: domains
== :: == ::
:: +seed: private boot parameters :: +feed: potential boot parameters
::
+$ feed
$^ [[%1 ~] who=ship kyz=(list [lyf=life key=ring])]
seed
:: +seed: individual boot parameters
:: ::
+$ seed [who=ship lyf=life key=ring sig=(unit oath:pki)] +$ seed [who=ship lyf=life key=ring sig=(unit oath:pki)]
:: ::

View File

@ -2096,7 +2096,7 @@
|= [k=beak v=(unit dome:clay)] |= [k=beak v=(unit dome:clay)]
^- tank ^- tank
=/ received=tape ?~(v "missing" "received") =/ received=tape ?~(v "missing" "received")
leaf+"{<k>} {received}" leaf+"{<(en-beam k ~)>} {received}"
:_ discarded :_ discarded
leaf+"fusing into {<syd>} from {<bas>} {<con>} - overwriting prior fuse" leaf+"fusing into {<syd>} from {<bas>} {<con>} - overwriting prior fuse"
=. fiz (make-melt bas con) =. fiz (make-melt bas con)
@ -2113,8 +2113,11 @@
:: responses we get for the merge will cause take-fuse to crash :: responses we get for the merge will cause take-fuse to crash
:: ::
=. fiz *melt =. fiz *melt
((slog [leaf+"clay: fuse failed, missing {<bec>}"]~) ..take-fuse) =/ msg=tape <(en-beam bec ~)>
?> (~(has by sto.fiz) bec) ((slog [leaf+"clay: fuse failed, missing {msg}"]~) ..take-fuse)
?. (~(has by sto.fiz) bec)
=/ msg=tape <(en-beam bec ~)>
((slog [leaf+"clay: got strange fuse response {<msg>}"]~) ..take-fuse)
=. fiz =. fiz
:+ bas.fiz con.fiz :+ bas.fiz con.fiz
(~(put by sto.fiz) bec `!<(dome:clay q.r.u.riot)) (~(put by sto.fiz) bec `!<(dome:clay q.r.u.riot))
@ -2135,7 +2138,6 @@
|- |-
^+ ..take-fuse ^+ ..take-fuse
?~ merges ?~ merges
=/ t=tang [leaf+"{<syd>} fused from {<bas.fiz>} {<con.fiz>}" ~]
=. ..take-fuse (done-fuse clean-state %& ~) =. ..take-fuse (done-fuse clean-state %& ~)
(park | [%| continuation-yaki(p (flop parents))] rag) (park | [%| continuation-yaki(p (flop parents))] rag)
=/ [bec=beak g=germ] i.merges =/ [bec=beak g=germ] i.merges
@ -2143,7 +2145,8 @@
=/ result (merge-helper p.bec q.bec g ali-dom `continuation-yaki) =/ result (merge-helper p.bec q.bec g ali-dom `continuation-yaki)
?- -.result ?- -.result
%| %|
(done-fuse clean-state %| %fuse-merge-failed p.result) =/ failing-merge=tape "{<bec>} {<g>}"
(done-fuse clean-state %| %fuse-merge-failed leaf+failing-merge p.result)
:: ::
%& %&
=/ merge-result=(unit merge-result) +.result =/ merge-result=(unit merge-result) +.result

View File

@ -554,6 +554,7 @@
:: +per-server-event: per-event server core :: +per-server-event: per-event server core
:: ::
++ per-server-event ++ per-server-event
~% %eyre-per-server-event ..part ~
:: gate that produces the +per-server-event core from event information :: gate that produces the +per-server-event core from event information
:: ::
|= [[eny=@ =duct now=@da rof=roof] state=server-state] |= [[eny=@ =duct now=@da rof=roof] state=server-state]
@ -781,7 +782,7 @@
:* duct %pass /run-app-request/[eyre-id] :* duct %pass /run-app-request/[eyre-id]
%g %deal [our our] app %g %deal [our our] app
%poke %handle-http-request %poke %handle-http-request
!>([eyre-id inbound-request]) !>(`[@ta inbound-request:eyre]`[eyre-id inbound-request])
== ==
== ==
:: +cancel-request: handles a request being externally aborted :: +cancel-request: handles a request being externally aborted
@ -1205,15 +1206,21 @@
?~ maybe-channel=(~(get by session.channel-state.state) channel-id) ?~ maybe-channel=(~(get by session.channel-state.state) channel-id)
%^ return-static-data-on-duct 404 'text/html' %^ return-static-data-on-duct 404 'text/html'
(error-page 404 %.y url.request ~) (error-page 404 %.y url.request ~)
:: if there's already a duct listening to this channel, we must 400
::
?: ?=([%| *] state.u.maybe-channel)
%^ return-static-data-on-duct 400 'text/html'
(error-page 400 %.y url.request "channel already bound")
:: when opening an event-stream, we must cancel our timeout timer :: when opening an event-stream, we must cancel our timeout timer
:: if there's no duct already bound. Else, kill the old request
:: and replace it
:: ::
=. moves =^ cancel-moves state
[(cancel-timeout-move channel-id p.state.u.maybe-channel) moves] ?. ?=([%| *] state.u.maybe-channel)
:_ state
(cancel-timeout-move channel-id p.state.u.maybe-channel)^~
=/ cancel-heartbeat
?~ heartbeat.u.maybe-channel ~
:_ ~
%+ cancel-heartbeat-move channel-id
[date duct]:u.heartbeat.u.maybe-channel
=- [(weld cancel-heartbeat -<) ->]
(handle-response(duct p.state.u.maybe-channel) [%cancel ~])
:: the request may include a 'Last-Event-Id' header :: the request may include a 'Last-Event-Id' header
:: ::
=/ maybe-last-event-id=(unit @ud) =/ maybe-last-event-id=(unit @ud)
@ -1282,7 +1289,7 @@
|= =channel |= =channel
channel(events ~, state [%| duct], heartbeat (some [heartbeat-time duct])) channel(events ~, state [%| duct], heartbeat (some [heartbeat-time duct]))
:: ::
[[heartbeat (weld http-moves moves)] state] [[heartbeat :(weld http-moves cancel-moves moves)] state]
:: +acknowledge-events: removes events before :last-event-id on :channel-id :: +acknowledge-events: removes events before :last-event-id on :channel-id
:: ::
++ acknowledge-events ++ acknowledge-events
@ -1710,7 +1717,7 @@
=/ res =/ res
%- handle-response %- handle-response
:* %continue :* %continue
data=(some (as-octs:mimes:html '\0a')) data=(some (as-octs:mimes:html ':\0a'))
complete=%.n complete=%.n
== ==
=/ http-moves -.res =/ http-moves -.res

View File

@ -760,12 +760,10 @@
mo-core mo-core
=^ [=duct =routes blocker=(each deal sign:agent)] blocked =^ [=duct =routes blocker=(each deal sign:agent)] blocked
~(get to blocked) ~(get to blocked)
?: ?=(%| -.blocker) $
=/ =move =/ =move
=/ =sock [attributing.routes our] =/ =sock [attributing.routes our]
=/ card =/ card [%slip %g %deal sock dap p.blocker]
?: ?=(%& -.blocker)
[%slip %g %deal sock dap p.blocker]
[%pass /clear-huck %b %huck `sign-arvo`[%gall %unto p.blocker]]
[duct card] [duct card]
$(moves [move moves]) $(moves [move moves])
:: +mo-filter-queue: remove all blocked tasks from ship. :: +mo-filter-queue: remove all blocked tasks from ship.

View File

@ -434,7 +434,7 @@
%- curd =< abet %- curd =< abet
(private-keys:~(feel su hen now pki etn) life.tac ring.tac) (private-keys:~(feel su hen now pki etn) life.tac ring.tac)
:: ::
:: update private keys :: register moon keys
:: ::
%moon %moon
?. =(%earl (clan:title ship.tac)) ?. =(%earl (clan:title ship.tac))
@ -717,6 +717,14 @@
=/ a-point=point (~(gut by pos.zim.pki) ship.i.udiffs *point) =/ a-point=point (~(gut by pos.zim.pki) ship.i.udiffs *point)
=/ a-diff=(unit diff:point) (udiff-to-diff:point udiff.i.udiffs a-point) =/ a-diff=(unit diff:point) (udiff-to-diff:point udiff.i.udiffs a-point)
=? this-su ?=(^ a-diff) =? this-su ?=(^ a-diff)
:: if this about our keys, and we already know these, start using them
::
=? lyf.own
?& =(our ship.i.udiffs)
?=(%keys -.u.a-diff)
(~(has by jaw.own) life.to.u.a-diff)
==
life.to.u.a-diff
(public-keys:feel original-pos %diff ship.i.udiffs u.a-diff) (public-keys:feel original-pos %diff ship.i.udiffs u.a-diff)
$(udiffs t.udiffs) $(udiffs t.udiffs)
:: ::
@ -926,7 +934,16 @@
^+ ..feel ^+ ..feel
?: &(=(lyf.own life) =((~(get by jaw.own) life) `ring)) ?: &(=(lyf.own life) =((~(get by jaw.own) life) `ring))
..feel ..feel
=. lyf.own life :: only eagerly update lyf if we were behind the chain life
::
=? lyf.own
?& (gth life lyf.own)
::
=+ pon=(~(get by pos.zim) our)
?~ pon |
(lth lyf.own life.u.pon)
==
life
=. jaw.own (~(put by jaw.own) life ring) =. jaw.own (~(put by jaw.own) life ring)
(exec yen.own [%give %private-keys lyf.own jaw.own]) (exec yen.own [%give %private-keys lyf.own jaw.own])
:: ::

View File

@ -46,7 +46,7 @@
(poke-our %graph-push-hook %push-hook-action !>([%add feed-rid])) (poke-our %graph-push-hook %push-hook-action !>([%add feed-rid]))
;< ~ bind:m ;< ~ bind:m
%+ poke-our %metadata-push-hook %+ poke-our %metadata-push-hook
:- %metadata-update-1 :- %metadata-update-2
!> ^- action:met !> ^- action:met
:^ %add :^ %add
group.action group.action
@ -54,7 +54,7 @@
metadatum(feed.config ``[%graph feed-rid]) metadatum(feed.config ``[%graph feed-rid])
;< ~ bind:m ;< ~ bind:m
%+ poke-our %metadata-push-hook %+ poke-our %metadata-push-hook
:- %metadata-update-1 :- %metadata-update-2
!> ^- action:met !> ^- action:met
:^ %add :^ %add
group.action group.action

View File

@ -78,7 +78,7 @@
=/ met-action=action:met =/ met-action=action:met
[%add group graph+rid.action metadatum] [%add group graph+rid.action metadatum]
;< ~ bind:m ;< ~ bind:m
(poke-our %metadata-push-hook metadata-update-1+!>(met-action)) (poke-our %metadata-push-hook metadata-update-2+!>(met-action))
:: ::
:: Send invites :: Send invites
:: ::

View File

@ -41,7 +41,7 @@
(poke-our %graph-push-hook %push-hook-action !>([%remove rid])) (poke-our %graph-push-hook %push-hook-action !>([%remove rid]))
;< ~ bind:m ;< ~ bind:m
%+ poke-our %metadata-push-hook %+ poke-our %metadata-push-hook
:- %metadata-update-1 :- %metadata-update-2
!> ^- action:metadata !> ^- action:metadata
[%remove group-rid [%graph rid]] [%remove group-rid [%graph rid]]
(pure:m ~) (pure:m ~)

View File

@ -31,7 +31,7 @@
== ==
;< ~ bind:m ;< ~ bind:m
%+ poke-our %metadata-push-hook %+ poke-our %metadata-push-hook
:- %metadata-update-1 :- %metadata-update-2
!> ^- action:met !> ^- action:met
:^ %add :^ %add
group.action group.action

View File

@ -11,7 +11,7 @@
|= arg=vase |= arg=vase
=/ m (strand ,vase) =/ m (strand ,vase)
^- form:m ^- form:m
=+ !<(desks=(list desk) arg) =+ !<([~ desks=(list desk)] arg)
=? desks =(~ desks) [%work]~ =? desks =(~ desks) [%work]~
|- ^- form:m |- ^- form:m
=* loop $ =* loop $

View File

@ -188,37 +188,53 @@
++ test-veri-good ++ test-veri-good
=/ sed [~zod 1 sec ~] =/ sed [~zod 1 sec ~]
%+ expect-eq %+ expect-eq
!> ~ !> &+sed
!> (veri:dawn sed pot ~) !> (veri:dawn ~zod sed pot ~)
:: ::
++ test-veri-not-spawned ++ test-veri-not-spawned
=/ sed [~zod 1 sec ~] =/ sed [~zod 1 sec ~]
%+ expect-eq %+ expect-eq
!> `%not-keyed !> |+[%not-keyed ~]
!> (veri:dawn sed =>(pot .(net ~)) ~) !> (veri:dawn ~zod sed =>(pot .(net ~)) ~)
:: ::
++ test-veri-wrong-key ++ test-veri-wrong-key
=/ sed [~zod 1 sec:ex:(pit:nu:crub:crypto 24 %foo) ~] =/ sed [~zod 1 sec:ex:(pit:nu:crub:crypto 24 %foo) ~]
%+ expect-eq %+ expect-eq
!> `%key-mismatch !> |+[%key-mismatch ~]
!> (veri:dawn sed pot ~) !> (veri:dawn ~zod sed pot ~)
:: ::
++ test-veri-life-mismatch ++ test-veri-life-mismatch
=/ sed [~zod 2 sec ~] =/ sed [~zod 2 sec ~]
%+ expect-eq %+ expect-eq
!> `%life-mismatch !> |+[%life-mismatch ~]
!> (veri:dawn sed pot ~) !> (veri:dawn ~zod sed pot ~)
::
++ test-veri-bad-multikey
=/ fed=feed:jael
:- [%1 ~]
:- ~zod
:~ [1 sec:ex:(pit:nu:crub:crypto 24 %foo)]
[2 sec]
==
%+ expect-eq
!> |+[%key-mismatch %life-mismatch ~]
!> (veri:dawn ~zod fed pot ~)
::
++ test-veri-none-multikey
%+ expect-eq
!> |+[%no-key ~]
!> (veri:dawn ~zod [[%1 ~] ~zod ~] pot ~)
:: ::
++ test-veri-already-booted ++ test-veri-already-booted
=/ sed [~zod 1 sec ~] =/ sed [~zod 1 sec ~]
;: weld ;: weld
%+ expect-eq %+ expect-eq
!> `%already-booted !> |+[%already-booted ~]
!> (veri:dawn sed pot `[1 |]) !> (veri:dawn ~zod sed pot `[1 |])
:: ::
%+ expect-eq %+ expect-eq
!> `%already-booted !> |+[%already-booted ~]
!> (veri:dawn sed pot `[2 &]) !> (veri:dawn ~zod sed pot `[2 &])
== ==
:: ::
++ test-veri-earl-good ++ test-veri-earl-good
@ -230,8 +246,8 @@
(shaf %earl (sham who 1 pub:ex:cub)) (shaf %earl (sham who 1 pub:ex:cub))
[who 1 sec:ex:cub `sig] [who 1 sec:ex:cub `sig]
%+ expect-eq %+ expect-eq
!> ~ !> &+sed
!> (veri:dawn sed pot ~) !> (veri:dawn who sed pot ~)
:: ::
++ test-veri-earl-parent-not-keyed ++ test-veri-earl-parent-not-keyed
=/ cub (pit:nu:crub:crypto 24 %foo) =/ cub (pit:nu:crub:crypto 24 %foo)
@ -242,38 +258,38 @@
(shaf %earl (sham who 1 pub:ex:cub)) (shaf %earl (sham who 1 pub:ex:cub))
[who 1 sec:ex:cub `sig] [who 1 sec:ex:cub `sig]
%+ expect-eq %+ expect-eq
!> ~ !> &+sed
!> (veri:dawn sed =>(pot .(net ~)) ~) !> (veri:dawn who sed =>(pot .(net ~)) ~)
:: ::
++ test-veri-pawn-good ++ test-veri-pawn-good
=/ cub (pit:nu:crub:crypto 24 %foo) =/ cub (pit:nu:crub:crypto 24 %foo)
=/ who=ship `@`fig:ex:cub =/ who=ship `@`fig:ex:cub
=/ sed [who 1 sec:ex:cub ~] =/ sed [who 1 sec:ex:cub ~]
%+ expect-eq %+ expect-eq
!> ~ !> &+sed
!> (veri:dawn sed *point:azimuth-types ~) !> (veri:dawn who sed *point:azimuth-types ~)
:: ::
++ test-veri-pawn-key-mismatch ++ test-veri-pawn-key-mismatch
=/ cub (pit:nu:crub:crypto 24 %foo) =/ cub (pit:nu:crub:crypto 24 %foo)
=/ who=ship `@`fig:ex:cub =/ who=ship `@`fig:ex:cub
=/ sed [who 1 sec:ex:(pit:nu:crub:crypto 24 %bar) ~] =/ sed [who 1 sec:ex:(pit:nu:crub:crypto 24 %bar) ~]
%+ expect-eq %+ expect-eq
!> `%key-mismatch !> |+[%key-mismatch ~]
!> (veri:dawn sed *point:azimuth-types ~) !> (veri:dawn who sed *point:azimuth-types ~)
:: ::
++ test-veri-pawn-invalid-life ++ test-veri-pawn-invalid-life
=/ cub (pit:nu:crub:crypto 24 %foo) =/ cub (pit:nu:crub:crypto 24 %foo)
=/ who=ship `@`fig:ex:cub =/ who=ship `@`fig:ex:cub
=/ sed [who 2 sec:ex:cub ~] =/ sed [who 2 sec:ex:cub ~]
%+ expect-eq %+ expect-eq
!> `%invalid-life !> |+[%invalid-life ~]
!> (veri:dawn sed *point:azimuth-types ~) !> (veri:dawn who sed *point:azimuth-types ~)
:: ::
++ test-veri-pawn-already-booted ++ test-veri-pawn-already-booted
=/ cub (pit:nu:crub:crypto 24 %foo) =/ cub (pit:nu:crub:crypto 24 %foo)
=/ who=ship `@`fig:ex:cub =/ who=ship `@`fig:ex:cub
=/ sed [who 1 sec:ex:cub ~] =/ sed [who 1 sec:ex:cub ~]
%+ expect-eq %+ expect-eq
!> `%already-booted !> |+[%already-booted ~]
!> (veri:dawn sed *point:azimuth-types `[1 |]) !> (veri:dawn who sed *point:azimuth-types `[1 |])
-- --

View File

@ -5,4 +5,4 @@ dojo:
it should return with the following hash: it should return with the following hash:
`0v7.v4dng.o33qi.kc497.5jc02.ke5es` `0v2.ifoe4.fbv35.aigir.66su4.fbspu`

View File

@ -241,11 +241,13 @@ export function uxToHex(ux: string) {
export const hexToUx = (hex) => { export const hexToUx = (hex) => {
const ux = f.flow( const ux = f.flow(
f.reverse,
f.chunk(4), f.chunk(4),
// eslint-disable-next-line prefer-arrow-callback // eslint-disable-next-line prefer-arrow-callback
f.map(x => _.dropWhile(x, function(y: unknown) { f.map(x => _.dropWhile(x, function(y: unknown) {
return y === 0; return y === '0';
}).join('')), }).reverse().join('')),
f.reverse,
f.join('.') f.join('.')
)(hex.split('')); )(hex.split(''));
return `0x${ux}`; return `0x${ux}`;

View File

@ -2,9 +2,9 @@ import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import { patp2dec } from 'urbit-ob'; import { patp2dec } from 'urbit-ob';
import shallow from 'zustand/shallow'; import shallow from 'zustand/shallow';
import { Association, deSig, GraphNode, Graphs, FlatGraphs, resourceFromPath, ThreadGraphs, getGraph, getShallowChildren } from '@urbit/api'; import { Association, deSig, GraphNode, Graphs, FlatGraphs, resourceFromPath, ThreadGraphs, getGraph, getShallowChildren, setScreen } from '@urbit/api';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { createState, createSubscription, reduceStateN } from './base'; import { createState, createSubscription, reduceStateN, pokeOptimisticallyN } from './base';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
import { addDmMessage, addPost, Content, getDeepOlderThan, getFirstborn, getNewest, getNode, getOlderSiblings, getYoungerSiblings, markPending, Post, addNode, GraphNodePoke } from '@urbit/api/graph'; import { addDmMessage, addPost, Content, getDeepOlderThan, getFirstborn, getNewest, getNode, getOlderSiblings, getYoungerSiblings, markPending, Post, addNode, GraphNodePoke } from '@urbit/api/graph';
import { GraphReducer, reduceDm } from '../reducers/graph-update'; import { GraphReducer, reduceDm } from '../reducers/graph-update';
@ -35,8 +35,8 @@ export interface GraphState {
getGraph: (ship: string, name: string) => Promise<void>; getGraph: (ship: string, name: string) => Promise<void>;
addDmMessage: (ship: string, contents: Content[]) => Promise<void>; addDmMessage: (ship: string, contents: Content[]) => Promise<void>;
addPost: (ship: string, name: string, post: Post) => Promise<void>; addPost: (ship: string, name: string, post: Post) => Promise<void>;
addNode: (ship: string, name: string, post: GraphNodePoke) => Promise<void>; addNode: (ship: string, name: string, post: GraphNodePoke) => Promise<void>;
setScreen: (screen: boolean) => void;
} }
// @ts-ignore investigate zustand types // @ts-ignore investigate zustand types
const useGraphState = createState<GraphState>('Graph', (set, get) => ({ const useGraphState = createState<GraphState>('Graph', (set, get) => ({
@ -145,6 +145,10 @@ const useGraphState = createState<GraphState>('Graph', (set, get) => ({
const data = await airlock.scry(getShallowChildren(ship, name, index)); const data = await airlock.scry(getShallowChildren(ship, name, index));
data['graph-update'].fetch = true; data['graph-update'].fetch = true;
GraphReducer(data); GraphReducer(data);
},
setScreen: (screen: boolean) => {
const poke = setScreen(screen);
pokeOptimisticallyN(useGraphState, poke, reduceDm);
} }
// getKeys: async () => { // getKeys: async () => {
// const api = useApi(); // const api = useApi();

View File

@ -7,6 +7,7 @@ import {
Timebox, Timebox,
Unreads Unreads
} from '@urbit/api'; } from '@urbit/api';
import { Poke } from '@urbit/http-api';
import { patp2dec } from 'urbit-ob'; import { patp2dec } from 'urbit-ob';
import _ from 'lodash'; import _ from 'lodash';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap'; import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
@ -22,6 +23,7 @@ export const HARK_FETCH_MORE_COUNT = 3;
export interface HarkState { export interface HarkState {
archivedNotifications: BigIntOrderedMap<Timebox>; archivedNotifications: BigIntOrderedMap<Timebox>;
doNotDisturb: boolean; doNotDisturb: boolean;
poke: (poke: Poke<any>) => Promise<void>;
getMore: () => Promise<boolean>; getMore: () => Promise<boolean>;
getSubset: (offset: number, count: number, isArchive: boolean) => Promise<void>; getSubset: (offset: number, count: number, isArchive: boolean) => Promise<void>;
// getTimeSubset: (start?: Date, end?: Date) => Promise<void>; // getTimeSubset: (start?: Date, end?: Date) => Promise<void>;
@ -42,6 +44,9 @@ const useHarkState = createState<HarkState>(
archivedNotifications: new BigIntOrderedMap<Timebox>(), archivedNotifications: new BigIntOrderedMap<Timebox>(),
doNotDisturb: false, doNotDisturb: false,
unreadNotes: [], unreadNotes: [],
poke: async (poke: Poke<any>) => {
await pokeOptimisticallyN(useHarkState, poke, [reduce]);
},
readCount: async (resource: string, index?: string) => { readCount: async (resource: string, index?: string) => {
const poke = markCountAsRead(resource, index); const poke = markCountAsRead(resource, index);
await pokeOptimisticallyN(useHarkState, poke, [reduce]); await pokeOptimisticallyN(useHarkState, poke, [reduce]);
@ -68,7 +73,6 @@ const useHarkState = createState<HarkState>(
}); });
reduceState(useHarkState, harkUpdate, [reduce]); reduceState(useHarkState, harkUpdate, [reduce]);
}, },
notifications: new BigIntOrderedMap<Timebox>(), notifications: new BigIntOrderedMap<Timebox>(),
notificationsCount: 0, notificationsCount: 0,
notificationsGraphConfig: { notificationsGraphConfig: {

View File

@ -10,12 +10,14 @@ import {
BaseState, BaseState,
createState, createState,
createSubscription, createSubscription,
reduceStateN reduceStateN,
pokeOptimisticallyN
} from '~/logic/state/base'; } from '~/logic/state/base';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { reduceUpdate } from '../reducers/settings-update'; import { reduceUpdate } from '../reducers/settings-update';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
import { getAll } from '@urbit/api'; import { getAll, Value } from '@urbit/api';
import { putEntry } from '@urbit/api/settings';
export interface ShortcutMapping { export interface ShortcutMapping {
cycleForward: string; cycleForward: string;
@ -43,6 +45,7 @@ export interface SettingsState {
keyboard: ShortcutMapping; keyboard: ShortcutMapping;
remoteContentPolicy: RemoteContentPolicy; remoteContentPolicy: RemoteContentPolicy;
getAll: () => Promise<void>; getAll: () => Promise<void>;
putEntry: (bucket: string, key: string, value: Value) => void;
leap: { leap: {
categories: LeapCategories[]; categories: LeapCategories[];
}; };
@ -102,6 +105,10 @@ const useSettingsState = createState<SettingsState>(
get().set((s) => { get().set((s) => {
Object.assign(s, all); Object.assign(s, all);
}); });
},
putEntry: (bucket: string, entry: string, value: Value) => {
const poke = putEntry(bucket, entry, value);
pokeOptimisticallyN(useSettingsState, poke, reduceUpdate);
} }
}), }),
[], [],

View File

@ -6,7 +6,7 @@ import { Formik } from 'formik';
import { ColorInput, ColorInputProps } from '~/views/components/ColorInput'; import { ColorInput, ColorInputProps } from '~/views/components/ColorInput';
const initialValues = { const initialValues = {
color: '#33FF22' color: '33FF22'
}; };
export default { export default {

View File

@ -136,7 +136,11 @@ const ChatEditor = React.forwardRef<CodeMirrorShim, ChatEditorProps>(({ inCodeMo
setMessage setMessage
} = useChatStore(); } = useChatStore();
function onKeyPress(e) { const onKeyPress = (e: KeyboardEvent, editor: CodeMirrorShim) => {
if (!editor) {
return;
}
const focusedTag = document.activeElement?.nodeName?.toLowerCase(); const focusedTag = document.activeElement?.nodeName?.toLowerCase();
const shouldCapture = !(focusedTag === 'textarea' || focusedTag === 'input' || e.metaKey || e.ctrlKey); const shouldCapture = !(focusedTag === 'textarea' || focusedTag === 'input' || e.metaKey || e.ctrlKey);
if(/^[a-z]|[A-Z]$/.test(e.key) && shouldCapture) { if(/^[a-z]|[A-Z]$/.test(e.key) && shouldCapture) {
@ -145,13 +149,14 @@ const ChatEditor = React.forwardRef<CodeMirrorShim, ChatEditorProps>(({ inCodeMo
if(e.key === 'Escape') { if(e.key === 'Escape') {
editor.getInputField().blur(); editor.getInputField().blur();
} }
} };
useEffect(() => { useEffect(() => {
document.addEventListener('keydown', onKeyPress); const focusListener = (e: KeyboardEvent) => onKeyPress(e, editorRef.current);
document.addEventListener('keydown', focusListener);
return () => { return () => {
document.removeEventListener('keydown', onKeyPress); document.removeEventListener('keydown', focusListener);
}; };
}, []); }, []);

View File

@ -3,14 +3,12 @@ import {
Text Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { putEntry } from '@urbit/api/settings';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { Form } from 'formik'; import { Form } from 'formik';
import useSettingsState, { SettingsState } from '~/logic/state/settings'; import useSettingsState, { SettingsState } from '~/logic/state/settings';
import { BackButton } from './BackButton'; import { BackButton } from './BackButton';
import _ from 'lodash'; import _ from 'lodash';
import { FormikOnBlur } from '~/views/components/FormikOnBlur'; import { FormikOnBlur } from '~/views/components/FormikOnBlur';
import airlock from '~/logic/api';
interface FormSchema { interface FormSchema {
hideAvatars: boolean; hideAvatars: boolean;
@ -25,14 +23,14 @@ interface FormSchema {
} }
const settingsSel = (s: SettingsState): FormSchema => ({ const settingsSel = (s: SettingsState): FormSchema => ({
hideAvatars: s.calm.hideAvatars, hideAvatars: s.calm.hideAvatars,
hideNicknames: s.calm.hideAvatars, hideNicknames: s.calm.hideNicknames,
hideUnreads: s.calm.hideUnreads, hideUnreads: s.calm.hideUnreads,
hideGroups: s.calm.hideGroups, hideGroups: s.calm.hideGroups,
hideUtilities: s.calm.hideUtilities, hideUtilities: s.calm.hideUtilities,
imageShown: !s.remoteContentPolicy.imageShown, imageShown: !s.remoteContentPolicy.imageShown,
videoShown: !s.remoteContentPolicy.videoShown, videoShown: !s.remoteContentPolicy.videoShown,
oembedShown: !s.remoteContentPolicy.oembedShown, oembedShown: !s.remoteContentPolicy.oembedShown,
audioShown: !s.remoteContentPolicy.audioShown audioShown: !s.remoteContentPolicy.audioShown
}); });
@ -40,10 +38,11 @@ export function CalmPrefs() {
const initialValues = useSettingsState(settingsSel); const initialValues = useSettingsState(settingsSel);
const onSubmit = useCallback(async (v: FormSchema) => { const onSubmit = useCallback(async (v: FormSchema) => {
const { putEntry } = useSettingsState.getState();
_.forEach(v, (bool, key) => { _.forEach(v, (bool, key) => {
const bucket = ['imageShown', 'videoShown', 'audioShown', 'oembedShown'].includes(key) ? 'remoteContentPolicy' : 'calm'; const bucket = ['imageShown', 'videoShown', 'audioShown', 'oembedShown'].includes(key) ? 'remoteContentPolicy' : 'calm';
if(initialValues[key] !== bool) { if(initialValues[key] !== bool) {
airlock.poke(putEntry(bucket, key, bool)); putEntry(bucket, key, bucket === 'calm' ? bool : !bool);
} }
}); });
}, [initialValues]); }, [initialValues]);

View File

@ -5,15 +5,14 @@ import {
Text Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { Form } from 'formik'; import { Form } from 'formik';
import { putEntry } from '@urbit/api/settings'; import React, { useCallback } from 'react';
import React, { useMemo } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { uxToHex } from '~/logic/lib/util'; import { uxToHex } from '~/logic/lib/util';
import useSettingsState, { selectSettingsState } from '~/logic/state/settings'; import useSettingsState, { SettingsState } from '~/logic/state/settings';
import { FormikOnBlur } from '~/views/components/FormikOnBlur'; import { FormikOnBlur } from '~/views/components/FormikOnBlur';
import { BackButton } from './BackButton'; import { BackButton } from './BackButton';
import airlock from '~/logic/api';
import { BackgroundPicker, BgType } from './BackgroundPicker'; import { BackgroundPicker, BgType } from './BackgroundPicker';
import shallow from 'zustand/shallow';
const formSchema = Yup.object().shape({ const formSchema = Yup.object().shape({
bgType: Yup.string() bgType: Yup.string()
@ -30,59 +29,54 @@ interface FormSchema {
bgUrl: string | undefined; bgUrl: string | undefined;
theme: string; theme: string;
} }
const emptyString = '';
const settingsSel = selectSettingsState(['display']); const settingsSel = (s: SettingsState): FormSchema => {
const { display } = s;
let bgColor = emptyString;
let bgUrl = emptyString;
if (display.backgroundType === 'url') {
bgUrl = display.background;
}
if (display.backgroundType === 'color') {
bgColor = display.background;
}
return {
bgType: display.backgroundType,
bgColor,
bgUrl,
theme: display.theme
};
};
export default function DisplayForm() { export default function DisplayForm() {
const { const initialValues = useSettingsState(settingsSel, shallow);
display: { background, backgroundType, theme }
} = useSettingsState(settingsSel);
const initialValues: FormSchema = useMemo(() => { const onSubmit = useCallback(async (values) => {
let bgColor, bgUrl; const { putEntry } = useSettingsState.getState();
if (backgroundType === 'url') { putEntry('display', 'backgroundType', values.bgType);
bgUrl = background; putEntry(
} 'display',
if (backgroundType === 'color') { 'background',
bgColor = background; values.bgType === 'color'
} ? `#${uxToHex(values.bgColor || '0x0')}`
return { : values.bgType === 'url'
bgType: backgroundType, ? values.bgUrl || ''
bgColor: bgColor || '', : false
bgUrl, );
theme putEntry('display', 'theme', values.theme);
}; }, []);
}, [backgroundType, background, theme]);
return ( return (
<FormikOnBlur <FormikOnBlur
validationSchema={formSchema} validationSchema={formSchema}
initialValues={initialValues} initialValues={initialValues}
onSubmit={async (values, actions) => { onSubmit={onSubmit}
const promises = [] as Promise<any>[];
promises.push(
airlock.poke(putEntry('display', 'backgroundType', values.bgType))
);
promises.push(
airlock.poke(
putEntry(
'display',
'background',
values.bgType === 'color'
? `#${uxToHex(values.bgColor || '0x0')}`
: values.bgType === 'url'
? values.bgUrl || ''
: false
)
)
);
promises.push(airlock.poke(putEntry('display', 'theme', values.theme)));
}}
> >
<Form> <Form>
<BackButton /> <BackButton />
<Col p={5} pt={4} gapY={5}> <Col p={5} pt={4} gapY={5}>
<Col gapY={1} mt={0}> <Col overflowY="auto" gapY={1} mt={0}>
<Text color="black" fontSize={2} fontWeight="medium"> <Text color="black" fontSize={2} fontWeight="medium">
Display Preferences Display Preferences
</Text> </Text>

View File

@ -4,22 +4,25 @@ import {
Box, Box,
ManagedToggleSwitchField ManagedToggleSwitchField
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { Form, Formik } from 'formik'; import { Form } from 'formik';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import useGraphState from '~/logic/state/graph'; import useGraphState, { GraphState } from '~/logic/state/graph';
import { AsyncButton } from '~/views/components/AsyncButton'; import { FormikOnBlur } from '~/views/components/FormikOnBlur';
import airlock from '~/logic/api'; import shallow from 'zustand/shallow';
import { setScreen } from '@urbit/api/graph';
const selInit = (s: GraphState) => ({
accept: !s.screening
});
export function DmSettings() { export function DmSettings() {
const screening = useGraphState(s => s.screening); const initialValues = useGraphState(selInit, shallow);
const initialValues = { accept: !screening };
const onSubmit = useCallback( const onSubmit = useCallback(
async (values, actions) => { async (values, actions) => {
await airlock.poke(setScreen(!values.accept)); const { setScreen } = useGraphState.getState();
setScreen(!values.accept);
actions.setStatus({ success: null }); actions.setStatus({ success: null });
}, },
[screening] []
); );
return ( return (
@ -38,7 +41,7 @@ export function DmSettings() {
<Box mb="4" fontWeight="medium" fontSize="1" color="gray"> <Box mb="4" fontWeight="medium" fontSize="1" color="gray">
Direct Messages Direct Messages
</Box> </Box>
<Formik initialValues={initialValues} onSubmit={onSubmit}> <FormikOnBlur initialValues={initialValues} onSubmit={onSubmit}>
<Form> <Form>
<Col gapY="4"> <Col gapY="4">
<ManagedToggleSwitchField <ManagedToggleSwitchField
@ -46,12 +49,9 @@ export function DmSettings() {
label="Auto-accept DM invites" label="Auto-accept DM invites"
caption="Direct messages will be automatically joined, and you wil see any messages sent in notifications" caption="Direct messages will be automatically joined, and you wil see any messages sent in notifications"
/> />
<AsyncButton width="fit-content" primary>
Save Changes
</AsyncButton>
</Col> </Col>
</Form> </Form>
</Formik> </FormikOnBlur>
</Col> </Col>
</Col> </Col>
); );

View File

@ -101,20 +101,19 @@ function Channel(props: { association: Association }) {
return isWatching(config, association.resource); return isWatching(config, association.resource);
}); });
const [, , { setValue, setTouched }] = useField( const [{ value }, , { setValue, setTouched }] = useField(
`graph["${association.resource}"]` `graph["${association.resource}"]`
); );
const [optValue, setOptValue] = useState(watching);
useEffect(() => {
setValue(optValue);
setTouched(true);
}, [watching]);
const onClick = () => { const onClick = () => {
setOptValue(v => !v); setValue(!value);
setTouched(true);
}; };
useEffect(() => {
setValue(watching);
}, [watching]);
const icon = getModuleIcon((metadata.config as GraphConfig)?.graph as GraphModule); const icon = getModuleIcon((metadata.config as GraphConfig)?.graph as GraphModule);
return ( return (
@ -126,7 +125,7 @@ function Channel(props: { association: Association }) {
<Text> {metadata.title}</Text> <Text> {metadata.title}</Text>
</Box> </Box>
<Box gridColumn={4}> <Box gridColumn={4}>
<StatelessToggleSwitchField selected={optValue} onClick={onClick} /> <StatelessToggleSwitchField selected={value} onClick={onClick} />
</Box> </Box>
</> </>
); );

View File

@ -11,9 +11,15 @@ import useHarkState from '~/logic/state/hark';
import { FormikOnBlur } from '~/views/components/FormikOnBlur'; import { FormikOnBlur } from '~/views/components/FormikOnBlur';
import { BackButton } from './BackButton'; import { BackButton } from './BackButton';
import { GroupChannelPicker } from './GroupChannelPicker'; import { GroupChannelPicker } from './GroupChannelPicker';
import airlock from '~/logic/api'; import {
import { ignoreGraph, ignoreGroup, listenGraph, listenGroup, setDoNotDisturb, setMentions } from '@urbit/api'; setMentions,
import { setWatchOnSelf } from '@urbit/api'; setWatchOnSelf,
setDoNotDisturb,
listenGraph,
listenGroup,
ignoreGraph,
ignoreGroup
} from '@urbit/api';
interface FormSchema { interface FormSchema {
mentions: boolean; mentions: boolean;
@ -39,28 +45,26 @@ export function NotificationPreferences() {
const onSubmit = useCallback(async (values: FormSchema, actions: FormikHelpers<FormSchema>) => { const onSubmit = useCallback(async (values: FormSchema, actions: FormikHelpers<FormSchema>) => {
try { try {
const promises: Promise<any>[] = []; const { poke } = useHarkState.getState();
if (values.mentions !== graphConfig.mentions) { if (values.mentions !== graphConfig.mentions) {
promises.push(airlock.poke(setMentions(values.mentions))); poke(setMentions(values.mentions));
} }
if (values.watchOnSelf !== graphConfig.watchOnSelf) { if (values.watchOnSelf !== graphConfig.watchOnSelf) {
promises.push(airlock.poke(setWatchOnSelf(values.watchOnSelf))); poke(setWatchOnSelf(values.watchOnSelf));
} }
if (values.dnd !== dnd && !_.isUndefined(values.dnd)) { if (values.dnd !== dnd && !_.isUndefined(values.dnd)) {
promises.push(airlock.poke(setDoNotDisturb(values.dnd))); poke(setDoNotDisturb(values.dnd));
} }
_.forEach(values.graph, (listen: boolean, graph: string) => { _.forEach(values.graph, (listen: boolean, graph: string) => {
if(listen !== isWatching(graphConfig, graph)) { if(listen !== isWatching(graphConfig, graph)) {
promises.push(airlock.poke((listen ? listenGraph : ignoreGraph)(graph, '/'))); poke((listen ? listenGraph : ignoreGraph)(graph, '/'));
} }
}); });
_.forEach(values.groups, (listen: boolean, group: string) => { _.forEach(values.groups, (listen: boolean, group: string) => {
if(listen !== groupConfig.includes(group)) { if(listen !== groupConfig.includes(group)) {
promises.push(airlock.poke((listen ? listenGroup : ignoreGroup)(group))); poke((listen ? listenGroup : ignoreGroup)(group));
} }
}); });
await Promise.all(promises);
actions.setStatus({ success: null }); actions.setStatus({ success: null });
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View File

@ -7,8 +7,9 @@ import {
StatelessTextInput as Input StatelessTextInput as Input
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { useField } from 'formik'; import { useField } from 'formik';
import React, { FormEvent } from 'react'; import React, { FormEvent, useState, useEffect } from 'react';
import { hexToUx } from '~/logic/lib/util'; import { hexToUx } from '~/logic/lib/util';
import { uxToHex } from '@urbit/api/dist';
export type ColorInputProps = Parameters<typeof Col>[0] & { export type ColorInputProps = Parameters<typeof Col>[0] & {
id: string; id: string;
@ -17,24 +18,39 @@ export type ColorInputProps = Parameters<typeof Col>[0] & {
disabled?: boolean; disabled?: boolean;
}; };
const COLOR_REGEX = /^(\d|[a-f]|[A-F]){6}$/;
function padHex(hex: string) {
if(hex.length === 0) {
return '000000';
}
const repeat = 6 / hex.length;
if(Math.floor(repeat) === repeat) {
return hex.repeat(repeat);
}
if(hex.length < 6) {
return hex.slice(0,3).repeat(2);
}
return hex.slice(0,6);
}
export function ColorInput(props: ColorInputProps) { export function ColorInput(props: ColorInputProps) {
const { id, placeholder, label, caption, disabled, ...rest } = props; const { id, placeholder, label, caption, disabled, ...rest } = props;
const [{ value, onBlur }, meta, { setValue }] = useField(id); const [{ value, onBlur }, meta, { setValue, setTouched }] = useField(id);
const [field, setField] = useState(uxToHex(value));
const hex = value.replace('#', '').replace('0x', '').replace('.', ''); useEffect(() => {
const padded = hex.padStart(6, '0'); const newValue = hexToUx(padHex(field));
setValue(newValue);
setTouched(true);
}, [field]);
const onChange = (e: FormEvent<HTMLInputElement>) => { const onChange = (e: FormEvent<HTMLInputElement>) => {
let { value: newValue } = e.target as HTMLInputElement; const { value: newValue } = e.target as HTMLInputElement;
newValue = newValue.replace('#', ''); setField(newValue.slice(1));
const valid = newValue.match(/^(\d|[a-f]|[A-F]){0,6}$/);
if (!valid) {
return;
}
const result = hexToUx(newValue);
setValue(result);
}; };
const hex = uxToHex(value);
const isValid = COLOR_REGEX.test(hex);
return ( return (
<Box display='flex' flexDirection='column' {...rest}> <Box display='flex' flexDirection='column' {...rest}>
@ -51,7 +67,7 @@ export function ColorInput(props: ColorInputProps) {
borderBottomRightRadius={0} borderBottomRightRadius={0}
onBlur={onBlur} onBlur={onBlur}
onChange={onChange} onChange={onChange}
value={hex} value={field}
disabled={disabled || false} disabled={disabled || false}
borderRight={0} borderRight={0}
placeholder={placeholder} placeholder={placeholder}
@ -64,18 +80,17 @@ export function ColorInput(props: ColorInputProps) {
borderColor='lightGray' borderColor='lightGray'
width='32px' width='32px'
alignSelf='stretch' alignSelf='stretch'
bg={`#${padded}`} bg={isValid ? `#${hex}` : 'transparent'}
> >
<Input <Input
width='100%' width='100%'
height='100%' height='100%'
alignSelf='stretch' alignSelf='stretch'
onChange={onChange}
value={padded}
disabled={disabled || false} disabled={disabled || false}
type='color' type='color'
opacity={0} opacity={0}
overflow='hidden' overflow='hidden'
onChange={onChange}
/> />
</Box> </Box>
</Row> </Row>

View File

@ -1,35 +1,28 @@
import { FormikConfig, FormikProvider, FormikValues, useFormik } from 'formik'; import { FormikConfig, FormikProvider, FormikValues, useFormik } from 'formik';
import React, { useEffect, useImperativeHandle, useState } from 'react'; import React, { useEffect, useImperativeHandle, useCallback } from 'react';
import _ from 'lodash';
export function FormikOnBlur< export function FormikOnBlur<
Values extends FormikValues = FormikValues, Values extends FormikValues = FormikValues,
ExtraProps = {} ExtraProps = {}
>(props: FormikConfig<Values> & ExtraProps) { >(props: FormikConfig<Values> & ExtraProps) {
const formikBag = useFormik<Values>({ ...props, validateOnBlur: true }); const formikBag = useFormik<Values>({ ...props, validateOnBlur: true });
const [submitting, setSubmitting] = useState(false);
useEffect(() => { const trySubmit = useCallback(_.debounce((formikBag) => {
if ( if (
Object.keys(formikBag.errors || {}).length === 0 && Object.keys(formikBag.errors || {}).length === 0 &&
formikBag.dirty && formikBag.dirty &&
!formikBag.isSubmitting && !formikBag.isSubmitting
!submitting
) { ) {
setSubmitting(true); formikBag.submitForm();
const { values } = formikBag;
formikBag.validateForm(values)
.then(valid => valid ?
formikBag.submitForm().then(() => {
formikBag.resetForm({ values });
setSubmitting(false);
}) : null
);
} }
}, 100), []);
useEffect(() => {
trySubmit(formikBag);
}, [ }, [
formikBag.errors, formikBag.values,
formikBag.dirty, formikBag.errors
submitting,
formikBag.isSubmitting
]); ]);
useEffect(() => { useEffect(() => {

View File

@ -1,8 +1,13 @@
import { import {
BaseInput, Box, BaseInput,
Box,
Button, Button,
Icon, Label, Row, StatelessTextInput as Input, Icon,
Text Label,
Row,
StatelessTextInput as Input,
Text,
ErrorLabel
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { useField } from 'formik'; import { useField } from 'formik';
import React, { ReactElement, useCallback, useRef, useState } from 'react'; import React, { ReactElement, useCallback, useRef, useState } from 'react';
@ -20,23 +25,19 @@ const prompt = (
uploading, uploading,
meta, meta,
clickUploadButton, clickUploadButton,
canUpload canUpload,
error
) => { ) => {
if ( if (!focus && !field.value && !uploading && error === undefined) {
!focus &&
!field.value &&
!uploading &&
meta.error === undefined
) {
return ( return (
<Text <Text
color='black' color="black"
fontWeight='500' fontWeight="500"
position='absolute' position="absolute"
left={2} left={2}
display='flex' display="flex"
height='100%' height="100%"
alignItems='center' alignItems="center"
lineHeight={1} lineHeight={1}
style={{ pointerEvents: 'none' }} style={{ pointerEvents: 'none' }}
onSelect={e => e.preventDefault} onSelect={e => e.preventDefault}
@ -50,7 +51,7 @@ const prompt = (
cursor="pointer" cursor="pointer"
color="blue" color="blue"
style={{ pointerEvents: 'all' }} style={{ pointerEvents: 'all' }}
mx='0.5ch' mx="0.5ch"
onClick={clickUploadButton} onClick={clickUploadButton}
> >
upload upload
@ -70,9 +71,9 @@ const uploadingStatus = (uploading, meta) => {
<Text <Text
position="absolute" position="absolute"
left={2} left={2}
display='flex' display="flex"
height='100%' height="100%"
alignItems='center' alignItems="center"
lineHeight={1} lineHeight={1}
gray gray
onSelect={e => e.preventDefault} onSelect={e => e.preventDefault}
@ -84,27 +85,27 @@ const uploadingStatus = (uploading, meta) => {
return null; return null;
}; };
const errorRetry = (meta, focus, uploading, clickUploadButton) => { const errorRetry = (meta, error, focus, uploading, clickUploadButton) => {
if (!focus && meta.error !== undefined) { if (!focus && error !== undefined && meta.touched) {
return ( return (
<Text <Text
position="absolute" position="absolute"
left={2} left={2}
display='flex' display="flex"
height='100%' height="100%"
alignItems='center' alignItems="center"
lineHeight={1} lineHeight={1}
color='red' color="red"
style={{ pointerEvents: 'none' }} style={{ pointerEvents: 'none' }}
onSelect={e => e.preventDefault} onSelect={e => e.preventDefault}
> >
{meta.error} {error}
{', '}please{' '} {', '}please{' '}
<Text <Text
fontWeight='500' fontWeight="500"
cursor='pointer' cursor="pointer"
color='blue' color="blue"
mx='0.5ch' mx="0.5ch"
style={{ pointerEvents: 'all' }} style={{ pointerEvents: 'all' }}
onClick={clickUploadButton} onClick={clickUploadButton}
> >
@ -143,7 +144,8 @@ export const clearButton = (field, uploading, clearEvt) => {
export function ImageInput(props: ImageInputProps): ReactElement { export function ImageInput(props: ImageInputProps): ReactElement {
const { id, label, caption } = props; const { id, label, caption } = props;
const { uploadDefault, canUpload, uploading } = useStorage(); const { uploadDefault, canUpload, uploading } = useStorage();
const [field, meta, { setValue, setError }] = useField(id); const [field, meta, { setValue, setTouched }] = useField(id);
const [uploadError, setUploadError] = useState();
const [focus, setFocus] = useState(false); const [focus, setFocus] = useState(false);
const ref = useRef<HTMLInputElement | null>(null); const ref = useRef<HTMLInputElement | null>(null);
@ -170,9 +172,10 @@ export function ImageInput(props: ImageInputProps): ReactElement {
const url = await uploadDefault(file); const url = await uploadDefault(file);
setFocus(false); setFocus(false);
setValue(url); setValue(url);
setTouched(true);
} catch (e) { } catch (e) {
setFocus(false); setFocus(false);
setError(e.message); setUploadError(e);
} }
}, [ref.current, uploadDefault, canUpload, setValue]); }, [ref.current, uploadDefault, canUpload, setValue]);
@ -184,19 +187,19 @@ export function ImageInput(props: ImageInputProps): ReactElement {
{caption} {caption}
</Label> </Label>
) : null} ) : null}
<Row mt={2} alignItems="flex-end" position='relative' width='100%'> <Row mt={2} alignItems="flex-end" position="relative" width="100%">
{prompt(field, focus, uploading, meta, clickUploadButton, canUpload)} {prompt(field, focus, uploading, meta, clickUploadButton, canUpload, uploadError)}
{clearButton(field, uploading, clearEvt)} {clearButton(field, uploading, clearEvt)}
{uploadingStatus(uploading, meta)} {uploadingStatus(uploading, meta)}
{errorRetry(meta, focus, uploading, clickUploadButton)} {errorRetry(meta, uploadError, focus, uploading, clickUploadButton)}
<Box background='white' borderRadius={2} width='100%'> <Box background="white" borderRadius={2} width="100%">
<Input <Input
{...field} {...field}
width='100%' width="100%"
type={'text'} type={'text'}
onFocus={() => setFocus(true)} onFocus={() => setFocus(true)}
onBlur={e => handleBlur(e)} onBlur={e => handleBlur(e)}
hasError={meta.touched && meta.error !== undefined} hasError={Boolean(uploadError)}
/> />
</Box> </Box>
{canUpload && ( {canUpload && (
@ -213,6 +216,9 @@ export function ImageInput(props: ImageInputProps): ReactElement {
</> </>
)} )}
</Row> </Row>
<ErrorLabel mt="2" hasError={Boolean(meta.touched && meta.error)}>
{meta.error}
</ErrorLabel>
</Box> </Box>
); );
} }

View File

@ -5,7 +5,7 @@ import {
Text Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { addTag, Association, Group, metadataUpdate, PermVariation, removeTag } from '@urbit/api'; import { addTag, Association, Group, PermVariation, removeTag, metadataEdit } from '@urbit/api';
import { Form, Formik } from 'formik'; import { Form, Formik } from 'formik';
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; import React from 'react';
@ -107,7 +107,7 @@ export function GraphPermissions(props: GraphPermissionsProps) {
}; };
const allWriters = Array.from(writers).map(w => `~${w}`); const allWriters = Array.from(writers).map(w => `~${w}`);
if (values.readerComments !== readerComments) { if (values.readerComments !== readerComments) {
await airlock.poke(metadataUpdate(association, { await airlock.poke(metadataEdit(association, {
vip: values.readerComments ? 'reader-comments' : '' vip: values.readerComments ? 'reader-comments' : ''
})); }));
} }

View File

@ -3,7 +3,8 @@ import {
Label, ManagedTextInputField as Input, Label, ManagedTextInputField as Input,
Text Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { Association, metadataUpdate } from '@urbit/api'; import _ from 'lodash';
import { Association, metadataEdit } from '@urbit/api';
import { Form, Formik } from 'formik'; import { Form, Formik } from 'formik';
import React from 'react'; import React from 'react';
import { uxToHex } from '~/logic/lib/util'; import { uxToHex } from '~/logic/lib/util';
@ -32,9 +33,13 @@ export function ChannelDetails(props: ChannelDetailsProps) {
}; };
const onSubmit = async (values: FormSchema, actions) => { const onSubmit = async (values: FormSchema, actions) => {
const { title, description } = values; values.color = uxToHex(values.color);
const color = uxToHex(values.color); const promises = _.compact(_.map(values, (value,k) => {
await airlock.poke(metadataUpdate(association, { title, color, description })); return value !== initialValues[k]
? airlock.poke(metadataEdit(association, { [k]: value }))
: null;
}));
await Promise.all(promises);
actions.setStatus({ success: null }); actions.setStatus({ success: null });
}; };

View File

@ -8,7 +8,7 @@ import {
leaveGraph, leaveGraph,
metadataRemove metadataRemove
} from '@urbit/api'; } from '@urbit/api';
import React, { useCallback, useRef } from 'react'; import React, { useCallback } from 'react';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { isChannelAdmin, isHost } from '~/logic/lib/group'; import { isChannelAdmin, isHost } from '~/logic/lib/group';
import { useHashLink } from '~/logic/lib/useHashLink'; import { useHashLink } from '~/logic/lib/useHashLink';
@ -31,7 +31,6 @@ interface ChannelPopoverRoutesProps {
export function ChannelPopoverRoutes(props: ChannelPopoverRoutesProps) { export function ChannelPopoverRoutes(props: ChannelPopoverRoutesProps) {
const { association, group } = props; const { association, group } = props;
useHashLink(); useHashLink();
const overlayRef = useRef<HTMLElement>();
const history = useHistory(); const history = useHistory();
const onDismiss = useCallback(() => { const onDismiss = useCallback(() => {
@ -63,7 +62,6 @@ export function ChannelPopoverRoutes(props: ChannelPopoverRoutesProps) {
height="100%" height="100%"
width="100%" width="100%"
spacing={[3, 5, 7]} spacing={[3, 5, 7]}
ref={overlayRef}
dismiss={onDismiss} dismiss={onDismiss}
> >
<Row <Row

View File

@ -4,9 +4,10 @@ import {
ManagedToggleSwitchField as Checkbox, ManagedToggleSwitchField as Checkbox,
Text Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { changePolicy, Enc, metadataUpdate } from '@urbit/api'; import _ from 'lodash';
import { changePolicy, Enc } from '@urbit/api';
import { Group, GroupPolicy } from '@urbit/api/groups'; import { Group, GroupPolicy } from '@urbit/api/groups';
import { Association } from '@urbit/api/metadata'; import { Association, metadataEdit, MetadataEditField } from '@urbit/api/metadata';
import { Form, Formik, FormikHelpers } from 'formik'; import { Form, Formik, FormikHelpers } from 'formik';
import React from 'react'; import React from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
@ -58,16 +59,26 @@ export function GroupAdminSettings(props: GroupAdminSettingsProps) {
actions: FormikHelpers<FormSchema> actions: FormikHelpers<FormSchema>
) => { ) => {
try { try {
const { title, description, picture, color, isPrivate, adminMetadata } = values; const { color, isPrivate, adminMetadata } = values;
const update = (upd: MetadataEditField) =>
airlock.poke(metadataEdit(association, upd));
const uxColor = uxToHex(color); const uxColor = uxToHex(color);
const vip = adminMetadata ? '' : 'member-metadata'; const vip = adminMetadata ? '' : 'member-metadata';
await airlock.poke(metadataUpdate(props.association, { const promises = _.compact(_.map(['title', 'description', 'picture'] as const,
title, (k) => {
description, const edit: MetadataEditField = { [k]: values[k] };
picture, return (values[k] !== initialValues[k])
color: uxColor, ? update(edit)
vip : null;
})); }));
if(vip !== metadata.vip) {
promises.push(update({ vip }));
}
if(uxColor !== metadata.color) {
promises.push(update({ color: uxColor }));
}
await Promise.all(promises);
if (isPrivate !== currentPrivate) { if (isPrivate !== currentPrivate) {
const resource = resourceFromPath(props.association.group); const resource = resourceFromPath(props.association.group);
const newPolicy: Enc<GroupPolicy> = isPrivate const newPolicy: Enc<GroupPolicy> = isPrivate

View File

@ -1,5 +1,5 @@
import { Col, Icon, Row, Text } from '@tlon/indigo-react'; import { Col, Icon, Row, Text } from '@tlon/indigo-react';
import { Association, Group, metadataRemove, metadataUpdate } from '@urbit/api'; import { Association, Group, metadataRemove, metadataEdit } from '@urbit/api';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { resourceFromPath, roleForShip } from '~/logic/lib/group'; import { resourceFromPath, roleForShip } from '~/logic/lib/group';
import { getModuleIcon, GraphModule } from '~/logic/lib/util'; import { getModuleIcon, GraphModule } from '~/logic/lib/util';
@ -22,7 +22,8 @@ export function GroupChannelSettings(props: GroupChannelSettingsProps) {
const onChange = useCallback( const onChange = useCallback(
async (resource: string, preview: boolean) => { async (resource: string, preview: boolean) => {
await airlock.poke(metadataUpdate(associations.graph[resource], { preview })); const association = associations.graph[resource];
await airlock.poke(metadataEdit(association, { preview }));
}, },
[associations.graph] [associations.graph]
); );

View File

@ -1,5 +1,5 @@
import { BaseLabel, Col, Label, Text } from '@tlon/indigo-react'; import { BaseLabel, Col, Label, Text } from '@tlon/indigo-react';
import { Association, createGroupFeed, disableGroupFeed, Group, metadataUpdate, PermVariation, resourceFromPath } from '@urbit/api'; import { Association, createGroupFeed, disableGroupFeed, Group, metadataEdit, PermVariation, resourceFromPath } from '@urbit/api';
import { Form, Formik, FormikHelpers } from 'formik'; import { Form, Formik, FormikHelpers } from 'formik';
import React from 'react'; import React from 'react';
import useMetadataState from '~/logic/state/metadata'; import useMetadataState from '~/logic/state/metadata';
@ -46,7 +46,11 @@ export function GroupFeedSettings(props: {
values: FormSchema, values: FormSchema,
actions: FormikHelpers<FormSchema> actions: FormikHelpers<FormSchema>
) => { ) => {
await airlock.poke(metadataUpdate(feedAssoc, { vip: values.permissions.trim() as PermVariation })); await airlock.poke(
metadataEdit(feedAssoc, {
vip: values.permissions.trim() as PermVariation
})
);
actions.setStatus({ success: null }); actions.setStatus({ success: null });
}; };

View File

@ -400,7 +400,8 @@ export const getNewest = (
index = '' index = ''
): Scry => ({ ): Scry => ({
app: 'graph-store', app: 'graph-store',
path: `/newest/${ship}/${name}/${count}${encodeIndex(index)}` path: `/graph/${ship}/${name}/node/siblings` +
`/newest/lone/${count}${encodeIndex(index)}`
}); });
/** /**
@ -419,7 +420,7 @@ export const getOlderSiblings = (
index: string index: string
): Scry => ({ ): Scry => ({
app: 'graph-store', app: 'graph-store',
path: `/node-siblings/older/${ship}/${name}/${count}${encodeIndex(index)}` path: `/graph/${ship}/${name}/node/siblings/older/lone/${count}${encodeIndex(index)}`
}); });
/** /**
@ -438,7 +439,7 @@ export const getYoungerSiblings = (
index: string index: string
): Scry => ({ ): Scry => ({
app: 'graph-store', app: 'graph-store',
path: `/node-siblings/younger/${ship}/${name}/${count}${encodeIndex(index)}` path: `/graph/${ship}/${name}/node/siblings/newer/lone/${count}${encodeIndex(index)}`
}); });
/** /**
@ -450,7 +451,7 @@ export const getYoungerSiblings = (
*/ */
export const getShallowChildren = (ship: string, name: string, index = '') => ({ export const getShallowChildren = (ship: string, name: string, index = '') => ({
app: 'graph-store', app: 'graph-store',
path: `/shallow-children/${ship}/${name}${encodeIndex(index)}` path: `/graph/${ship}/${name}/node/children/lone/~/~${encodeIndex(index)}`
}); });
/** /**
@ -467,10 +468,12 @@ export const getDeepOlderThan = (
ship: string, ship: string,
name: string, name: string,
count: number, count: number,
start?: string start = ''
) => ({ ) => ({
app: 'graph-store', app: 'graph-store',
path: `/deep-nodes-older-than/${ship}/${name}/${count}/${start ? decToUd(start) : 'null'}` path: `/graph/${ship}/${name}/node/siblings` +
`/${start.length > 0 ? 'older' : 'oldest'}` +
`/kith/${count}${encodeIndex(start)}`
}); });
/** /**
@ -487,7 +490,7 @@ export const getFirstborn = (
index: string index: string
): Scry => ({ ): Scry => ({
app: 'graph-store', app: 'graph-store',
path: `/firstborn/${ship}/${name}${encodeIndex(index)}` path: `/graph/${ship}/${name}/node/firstborn${encodeIndex(index)}`
}); });
/** /**
@ -504,7 +507,7 @@ export const getNode = (
index: string index: string
): Scry => ({ ): Scry => ({
app: 'graph-store', app: 'graph-store',
path: `/node/${ship}/${name}${encodeIndex(index)}` path: `/graph/${ship}/${name}/node/index/kith${encodeIndex(index)}`
}); });
/** /**

View File

@ -1,7 +1,7 @@
import { AppName, Path, Poke, uxToHex, PatpNoSig } from '../lib'; import { AppName, Path, Poke, uxToHex, PatpNoSig } from '../lib';
import { Association, Metadata, MetadataUpdate, MetadataUpdateAdd, MetadataUpdateRemove } from './types'; import { Association, Metadata, MetadataUpdate, MetadataUpdateAdd, MetadataUpdateRemove, MetadataEditField, MetadataUpdateEdit } from './types';
export const METADATA_UPDATE_VERSION = 1; export const METADATA_UPDATE_VERSION = 2;
export const metadataAction = <T extends MetadataUpdate>(data: T, version: number = METADATA_UPDATE_VERSION): Poke<T> => ({ export const metadataAction = <T extends MetadataUpdate>(data: T, version: number = METADATA_UPDATE_VERSION): Poke<T> => ({
app: 'metadata-push-hook', app: 'metadata-push-hook',
@ -59,6 +59,25 @@ export const remove = (
export { remove as metadataRemove }; export { remove as metadataRemove };
export const edit = (
association: Association,
edit: MetadataEditField
): Poke<MetadataUpdateEdit> => metadataAction({
edit: {
group: association.group,
resource: {
resource: association.resource,
'app-name': association['app-name']
},
edit
}
});
export { edit as metadataEdit };
/**
* @deprecated use {@link edit} instead
*/
export const update = ( export const update = (
association: Association, association: Association,
newMetadata: Partial<Metadata> newMetadata: Partial<Metadata>

View File

@ -4,7 +4,8 @@ export type MetadataUpdate =
MetadataUpdateInitial MetadataUpdateInitial
| MetadataUpdateAdd | MetadataUpdateAdd
| MetadataUpdateUpdate | MetadataUpdateUpdate
| MetadataUpdateRemove; | MetadataUpdateRemove
| MetadataUpdateEdit;
export interface MetadataUpdateInitial { export interface MetadataUpdateInitial {
associations: ResourceAssociations; associations: ResourceAssociations;
@ -22,6 +23,15 @@ export type MetadataUpdateUpdate = {
update: AssociationPoke; update: AssociationPoke;
} }
export interface MetadataUpdateEdit {
edit: {
resource: MdResource;
edit: MetadataEditField;
}
}
export type MetadataEditField = Partial<Omit<Metadata, 'config' | 'creator' | 'date-created'>>;
export type MetadataUpdateRemove = { export type MetadataUpdateRemove = {
remove: { remove: {
resource: MdResource; resource: MdResource;

View File

@ -1,6 +1,6 @@
{ {
"name": "@urbit/api", "name": "@urbit/api",
"version": "1.2.0", "version": "1.4.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@urbit/api", "name": "@urbit/api",
"version": "1.2.0", "version": "1.4.0",
"description": "", "description": "",
"repository": { "repository": {
"type": "git", "type": "git",
@ -21,7 +21,7 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@types/lodash": "^4.14.168", "@types/lodash": "^4.14.168",
"@urbit/eslint-config": "^1.0.1", "@urbit/eslint-config": "^1.0.3",
"big-integer": "^1.6.48", "big-integer": "^1.6.48",
"immer": "^9.0.1", "immer": "^9.0.1",
"lodash": "^4.17.20", "lodash": "^4.17.20",

View File

@ -1,6 +1,6 @@
{ {
"name": "@urbit/eslint-config", "name": "@urbit/eslint-config",
"version": "1.0.1", "version": "1.0.3",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@urbit/eslint-config", "name": "@urbit/eslint-config",
"version": "1.0.1", "version": "1.0.3",
"description": "", "description": "",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,6 +1,6 @@
{ {
"name": "@urbit/http-api", "name": "@urbit/http-api",
"version": "1.2.3", "version": "1.3.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@urbit/http-api", "name": "@urbit/http-api",
"version": "1.2.3", "version": "1.3.1",
"license": "MIT", "license": "MIT",
"description": "Library to interact with an Urbit ship over HTTP", "description": "Library to interact with an Urbit ship over HTTP",
"repository": { "repository": {