Merge pull request #16 from urbit/t/merge-agent-clay

grid: migrate upstream changes for compatibility with 417 clay + kiln
This commit is contained in:
Hunter Miller 2022-12-06 08:15:46 -06:00 committed by GitHub
commit d882b0dce0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 585 additions and 492 deletions

View File

@ -3,7 +3,7 @@
|% |%
+$ card card:agent:gall +$ card card:agent:gall
+$ app-state +$ app-state
$: %2 $: %3
:: local :: local
charges=(map desk charge) charges=(map desk charge)
== ==
@ -42,7 +42,7 @@
++ on-init ++ on-init
^- (quip card _this) ^- (quip card _this)
:_ this :_ this
:~ (~(watch-our pass /kiln) %hood /kiln/vats) :~ ~(tire pass /tire)
(~(connect pass /eyre) [~ /] %docket) (~(connect pass /eyre) [~ /] %docket)
(~(wait pass /init) (add 1 now.bowl)) (~(wait pass /init) (add 1 now.bowl))
(~(connect pass /eyre) [~ /apps] %docket) (~(connect pass /eyre) [~ /apps] %docket)
@ -54,20 +54,21 @@
|^ |^
=+ !<(old=app-states vase) =+ !<(old=app-states vase)
=? old ?=(?(~ ^) -.old) [%1 old] =? old ?=(?(~ ^) -.old) [%1 old]
=^ cards old =^ cards-1 old
?. ?=(%1 -.old) `old ?. ?=(%1 -.old) `old
=/ rein=cage kiln-rein+!>([%base %.y ~ ~]) `old(- %2)
=/ nuke=cage kiln-uninstall+!>(%hodl) =^ cards-2 old
:_ old(- %2) ?. ?=(%2 -.old) `old
:~ [%pass /rein %agent [our.bowl %hood] %poke rein] :_ old(- %3) :_ ~
[%pass /nuke %agent [our.bowl %hood] %poke nuke] ~(tire pass /tire)
== ?> ?=(%3 -.old)
?> ?=(%2 -.old) =/ cards-tire [~(tire pass /tire) ~]
=. -.state old =. -.state old
:: inflate-cache needs to be called after the state is set :: inflate-cache needs to be called after the state is set
:: ::
=. +.state inflate-cache =. +.state inflate-cache
[cards this] [:(weld cards-1 cards-2 cards-tire) this]
:: ::
++ inflate-cache ++ inflate-cache
^- cache ^- cache
@ -81,9 +82,11 @@
$^ state-0-ket $^ state-0-ket
$% state-0-sig $% state-0-sig
state-1 state-1
state-2
app-state app-state
== ==
:: ::
+$ state-2 [%2 (map desk charge)]
+$ state-1 [%1 (map desk charge)] +$ state-1 [%1 (map desk charge)]
+$ state-0-sig +$ state-0-sig
$: ~ $: ~
@ -94,6 +97,7 @@
== ==
-- --
:: ::
++ on-save !>(-.state) ++ on-save !>(-.state)
++ on-poke ++ on-poke
|= [=mark =vase] |= [=mark =vase]
@ -189,13 +193,21 @@
== ==
:: ::
[%x %charges ~] [%x %charges ~]
=/ tyr
.^(rock:tire:clay %cx /(scot %p our.bowl)//(scot %da now.bowl)/tire)
:- ~ :- ~ :- ~ :- ~
%- charge-update:cg %- charge-update:cg
:- %initial :- %initial
%- ~(gas by *(map desk charge)) %- ~(gas by *(map desk charge))
%+ turn ~(tap by charges) %+ murn ~(tap by charges)
|= [=desk =charge] |= [=desk =charge]
[desk (get-light-charge charge)] ?~ got=(~(get by tyr) desk)
~
?: ?& ?=(%dead zest.u.got)
?=(~ (get-apps-have:hood our.bowl desk now.bowl))
==
~
`u=[desk (get-light-charge charge)]
:: ::
[%x %charges @ %version ~] [%x %charges @ %version ~]
?~ charge=(~(get by charges) i.t.t.path) ?~ charge=(~(get by charges) i.t.t.path)
@ -212,114 +224,11 @@
~ `state ~ `state
[%rein ~] ~&(%reined `state) [%rein ~] ~&(%reined `state)
[%nuke ~] ~&(%nuked `state) [%nuke ~] ~&(%nuked `state)
[%kiln ~] take-kiln [%kiln ~] `state
[%charge @ *] (take-charge i.t.wire t.t.wire) [%charge @ *] (take-charge i.t.wire t.t.wire)
== ==
[cards this] [cards this]
:: ::
++ take-kiln
^- (quip card _state)
?+ -.sign (on-agent:def:cc wire sign)
%kick [(~(watch-our pass /kiln) %hood /kiln/vats)^~ state]
%fact
|^ ^- (quip card _state)
?+ p.cage.sign ~|(take-kiln-mark/p.cage.sign !!)
%kiln-vats-snap-0 (on-snap !<(snap:hood q.cage.sign))
%kiln-vats-diff-0 (on-diff !<(diff:hood q.cage.sign))
==
::
++ on-snap
|= =snap:hood
^- (quip card _state)
=| fex=(list card)
=/ ark ~(tap by snap)
|- ^- (quip card _state)
?~ ark [(flop fex) state]
=^ caz state (on-commit i.ark)
$(ark t.ark, fex (weld (flop caz) fex))
::
++ on-diff
|= =diff:hood
=+ !<(=diff:hood q.cage.sign)
?- -.diff
%commit (on-commit [desk arak]:diff)
%suspend (on-suspend [desk arak]:diff)
%revive (on-revive [desk arak]:diff)
?(%block %reset %merge-sunk %merge-fail)
`state
==
::
++ on-commit
|= [=desk =arak:hood]
^- (quip card _state)
=* cha ~(. ch desk)
?. docket-exists:cha
~? ?& !=(%base desk)
!=(%kids desk)
==
[dap.bowl %no-docket-file-for desk]
`state
:: always update the docket in state to match clay's
::
=/ =docket docket:cha
=/ pre=(unit charge) (~(get by charges) desk)
=. charges (new-docket:cha docket)
:: if the new chad is a site, we're instantly done
::
?: ?=(%site -.href.docket)
=. charges (new-chad:cha %site ~)
:- ~[add-fact:cha]
state
::
=. by-base (~(put by by-base) base.href.docket desk)
:: if the glob specification is unchanged, keep it
::
?: &(?=(^ pre) =(href.docket.u.pre href.docket) ?=(%glob -.chad.u.pre))
[~[add-fact:cha] state]
:: if the glob spec changed, but we already host it, keep it
:: (this is the "just locally uploaded" case)
::
?: ?& ?=(^ pre)
?=(%glob -.chad.u.pre)
::
.= [(sham glob.chad.u.pre) %ames our.bowl]
glob-reference.href.docket
==
[~[add-fact:cha] state]
:: if the glob changed, forget the old and fetch the new
::
=. charges (new-chad:cha %install ~)
[[add-fact:cha fetch-glob:cha] state]
::
++ on-suspend
|= [=desk =arak:hood]
^- (quip card _state)
=* cha ~(. ch desk)
?. (~(has by charges) desk) `state
=/ glob=(unit glob)
=/ =chad
chad:(~(got by charges) desk)
?:(?=(%glob -.chad) `glob.chad ~)
=. charges (new-chad:cha %suspend glob)
:_(state ~[add-fact:cha])
::
++ on-revive
|= [=desk =arak:hood]
^- (quip card _state)
=* cha ~(. ch desk)
?. (~(has by charges) desk) `state
=/ =charge (~(got by charges) desk)
?. ?=(%glob -.href.docket.charge)
=. charges (new-chad:cha %site ~)
:_(state ~[add-fact:cha])
=. charges
%- new-chad:cha
?. ?=([%suspend ~ *] chad.charge)
[%install ~]
[%glob u.glob.chad.charge]
:_(state [add-fact fetch-glob]:cha)
--
==
++ take-charge ++ take-charge
|= [=desk =^wire] |= [=desk =^wire]
^- (quip card _state) ^- (quip card _state)
@ -398,6 +307,7 @@
:: ::
++ on-arvo ++ on-arvo
|= [=wire sign=sign-arvo] |= [=wire sign=sign-arvo]
|^
=^ cards state =^ cards state
?+ wire (on-arvo:def wire sign) ?+ wire (on-arvo:def wire sign)
[%init ~] [%init ~]
@ -410,8 +320,114 @@
?: accepted.sign `state ?: accepted.sign `state
~& [dap.bowl %failed-to-bind path.binding.sign] ~& [dap.bowl %failed-to-bind path.binding.sign]
`state `state
::
[%tire ~]
?> ?=([%clay %tire *] sign)
?- -.p.sign
%& (on-rock p.p.sign)
%| (on-wave p.p.sign)
==
::
[%warp * ~]
?> ?=(%writ +<.sign)
(on-writ i.t.wire p.sign)
== ==
[cards this] [cards this]
::
++ on-rock
|= tyr=rock:tire:clay
^- (quip card _state)
=| fex=(list card)
=/ ark ~(tap by tyr)
|- ^- (quip card _state)
?~ ark [(flop fex) state]
=^ caz state (on-zest [p zest.q]:i.ark)
$(ark t.ark, fex (weld (flop caz) fex))
::
++ on-wave
|= =wave:tire:clay
^- (quip card _state)
?- -.wave
%wait `state
%warp `state
%zest (on-zest +.wave)
==
::
++ on-zest
|= [=desk =zest:clay]
^- (quip card _state)
=* cha ~(. ch desk)
=/ card-1
(~(warp-our pass /warp/[desk]) desk ~ %sing %z da+now.bowl /desk/docket-0)
=^ cards-2 state
?. (~(has by charges) desk)
`state
=/ =charge (~(got by charges) desk)
?- zest
%live
?. ?=(%glob -.href.docket.charge)
=. charges (new-chad:cha %site ~)
:_(state ~[add-fact:cha])
:_(state ~[add-fact:cha])
::
?(%held %dead)
=/ glob=(unit glob)
?:(?=(%glob -.chad.charge) `glob.chad.charge ~)
=. charges (new-chad:cha %suspend glob)
:_(state ~[add-fact:cha])
==
[[card-1 cards-2] state]
::
++ on-writ
|= [=desk =riot:clay]
^- (quip card _state)
=/ card-1
(~(warp-our pass /warp/[desk]) desk ~ %next %z da+now.bowl /desk/docket-0)
=^ cards-2 state
=* cha ~(. ch desk)
=/ tyr
.^(rock:tire:clay %cx /(scot %p our.bowl)//(scot %da now.bowl)/tire)
?. =(%live zest:(~(got by tyr) desk))
`state
?. docket-exists:cha
:: ~? ?& !=(%base desk)
:: !=(%kids desk)
:: ==
:: [dap.bowl %no-docket-file-for desk]
`state
:: always update the docket in state to match clay's
::
=/ =docket docket:cha
=/ pre=(unit charge) (~(get by charges) desk)
=. charges (new-docket:cha docket)
:: if the new chad is a site, we're instantly done
::
?: ?=(%site -.href.docket)
=. charges (new-chad:cha %site ~)
:- ~[add-fact:cha]
state
::
=. by-base (~(put by by-base) base.href.docket desk)
:: if the glob specification is unchanged, keep it
::
?: &(?=(^ pre) =(href.docket.u.pre href.docket) ?=(%glob -.chad.u.pre))
[~[add-fact:cha] state]
:: if the glob spec changed, but we already host it, keep it
:: (this is the "just locally uploaded" case)
::
?: ?& ?=(^ pre)
?=(%glob -.chad.u.pre)
::
.= [(sham glob.chad.u.pre) %ames our.bowl]
glob-reference.href.docket
==
[~[add-fact:cha] state]
:: if the glob changed, forget the old and fetch the new
::
=. charges (new-chad:cha %install ~)
[[add-fact:cha fetch-glob:cha] state]
[[card-1 cards-2] state]
--
:: ::
++ on-fail on-fail:def ++ on-fail on-fail:def
++ on-leave on-leave:def ++ on-leave on-leave:def
@ -728,4 +744,3 @@
++ docket .^(^docket %cx (scry:io desk docket-loc)) ++ docket .^(^docket %cx (scry:io desk docket-loc))
-- --
-- --

View File

@ -2,16 +2,16 @@
/+ verb, dbug, default-agent, agentio /+ verb, dbug, default-agent, agentio
|% |%
+$ card card:agent:gall +$ card card:agent:gall
+$ state-0 [%0 lagging=_|] +$ state-1 [%1 lagging=_|]
:: ::
++ lag-interval ~m10 ++ lag-interval ~m10
-- --
%+ verb | %+ verb |
%- agent:dbug %- agent:dbug
^- agent:gall ^- agent:gall
=| state-0 =| state-1
=* state - =* state -
=< =<
|_ =bowl:gall |_ =bowl:gall
+* this . +* this .
def ~(. (default-agent this %|) bowl) def ~(. (default-agent this %|) bowl)
@ -21,12 +21,23 @@
++ on-init ++ on-init
^- (quip card _this) ^- (quip card _this)
:_ this :_ this
[onboard watch:kiln check:lag ~]:cc [onboard tire:cy check:lag ~]:cc
:: ::
++ on-load ++ on-load
|= =vase |= =vase
=+ !<(old=state-0 vase) ^- (quip card _this)
`this(state old) |^
=+ !<(old=app-states vase)
=^ cards-1 old
?. ?=(%0 -.old) `old
[[tire:cy:cc]~ old(- %1)]
?> ?=(%1 -.old)
=/ cards-tire [tire:cy:cc ~]
[(weld cards-1 cards-tire) this(state old)]
::
+$ app-states $%(state-0 state-1)
+$ state-0 [%0 lagging=_|]
--
:: ::
++ on-save !>(state) ++ on-save !>(state)
++ on-poke on-poke:def ++ on-poke on-poke:def
@ -34,45 +45,57 @@
++ on-watch on-watch:def ++ on-watch on-watch:def
++ on-agent ++ on-agent
|= [=wire =sign:agent:gall] |= [=wire =sign:agent:gall]
|^ ^- (quip card _this)
?+ wire (on-agent:def wire sign) ?+ wire (on-agent:def wire sign)
[%kiln %vats ~] take-kiln-vats [%kiln %vats ~] `this
== ==
++ take-kiln-vats ::
?- -.sign ++ on-arvo
?(%poke-ack %watch-ack) (on-agent:def wire sign) |= [=wire sign=sign-arvo]
%kick :_(this (drop safe-watch:kiln:cc)) ^- (quip card _this)
:: |^
%fact ?+ wire (on-arvo:def wire sign)
?. ?=(%kiln-vats-diff-0 p.cage.sign) `this [%clay %tire ~] take-clay-tire
=+ !<(=diff:hood q.cage.sign) [%clay %warp * ~] (take-clay-warp i.t.t.wire)
?+ -.diff `this [%check-lag ~] take-check-lag
:: ==
%commit ::
?. |(=(desk.diff %base) ~(has-docket de:cc desk.diff)) `this ++ take-check-lag
=/ =action:hark ~(commit de:cc desk.diff) ^- (quip card _this)
?> ?=([%behn %wake *] sign)
=+ .^(lag=? %$ (scry:io %$ /zen/lag))
?: =(lagging lag) :_(this ~[check:lag:cc])
:_ this(lagging lag)
:_ ~[check:lag:cc]
?:(lagging start:lag:cc stop:lag:cc)
::
++ take-clay-tire
^- (quip card _this)
?> ?=(%tire +<.sign)
?- -.p.sign
%& [(turn ~(tap in ~(key by p.p.sign)) warp:cy:cc) this]
%|
?- -.p.p.sign
%zest `this
%warp `this
%wait
=/ =action:hark (~(blocked de:cc desk.p.p.sign) weft.p.p.sign)
:_ this :_ this
~[(poke:ha:cc action)] ~[(poke:ha:cc action)]
::
%block
=/ =action:hark (~(blocked de:cc desk.diff) blockers.diff)
:_ this
~[(poke:ha:cc action)]
== ==
== ==
::
++ take-clay-warp
|= =desk
^- (quip card _this)
?> ?=(%writ +<.sign)
=/ cards
?. |(=(desk %base) ~(has-docket de:cc desk)) ~
=/ =action:hark ~(commit de:cc desk)
~[(poke:ha:cc action)]
[[(warp:cy:cc desk) cards] this]
-- --
:: ::
++ on-arvo
|= [=wire sign=sign-arvo]
^- (quip card _this)
?. ?=([%check-lag ~] wire) (on-arvo:def wire sign)
?> ?=([%behn %wake *] sign)
=+ .^(lag=? %$ (scry:io %$ /zen/lag))
?: =(lagging lag) :_(this ~[check:lag:cc])
:_ this(lagging lag)
:_ ~[check:lag:cc]
?:(lagging start:lag:cc stop:lag:cc)
::
++ on-fail on-fail:def ++ on-fail on-fail:def
++ on-leave on-leave:def ++ on-leave on-leave:def
-- --
@ -89,7 +112,7 @@
[~[text+'Welcome to urbit'] ~ now.bowl / /] [~[text+'Welcome to urbit'] ~ now.bowl / /]
:: ::
++ lag ++ lag
|% |%
++ check (~(wait pass /check-lag) (add now.bowl lag-interval)) ++ check (~(wait pass /check-lag) (add now.bowl lag-interval))
++ place [q.byk.bowl /lag] ++ place [q.byk.bowl /lag]
++ body `body:hark`[~[text/'Runtime lagging'] ~ now.bowl / /] ++ body `body:hark`[~[text/'Runtime lagging'] ~ now.bowl / /]
@ -102,24 +125,23 @@
++ poke ++ poke
|=(=action:hark (poke-our:pass %hark-store hark-action+!>(action))) |=(=action:hark (poke-our:pass %hark-store hark-action+!>(action)))
-- --
++ kiln ::
++ cy
|% |%
++ path /kiln/vats ++ tire ~(tire pass /clay/tire)
++ pass ~(. ^pass path) ++ warp
++ watch (watch-our:pass %hood path) |= =desk
++ watching (~(has by wex.bowl) [path our.bowl %hood]) (~(warp-our pass /clay/warp/[desk]) desk ~ %next %z da+now.bowl /)
++ safe-watch `(unit card)`?:(watching ~ `watch)
-- --
:: ::
++ de ++ de
|_ =desk |_ =desk
++ scry-path (scry:io desk /desk/docket-0) ++ scry-path (scry:io desk /desk/docket-0)
++ has-docket .^(? %cu scry-path) ++ has-docket .^(? %cu scry-path)
++ docket .^(docket:^docket %cx scry-path) ++ docket .^(docket:^docket %cx scry-path)
++ hash .^(@uv %cz (scry:io desk ~)) ++ hash .^(@uv %cz (scry:io desk ~))
++ place `place:hark`[q.byk.bowl /desk/[desk]] ++ place `place:hark`[q.byk.bowl /desk/[desk]]
++ vat ++ version ud:.^(cass:clay %cw (scry:io desk /))
.^(vat:hood %gx (scry:io %hood /kiln/vat/[desk]/noun))
++ body ++ body
|= [=path title=cord content=(unit cord)] |= [=path title=cord content=(unit cord)]
^- body:hark ^- body:hark
@ -131,7 +153,7 @@
%+ rap 3 %+ rap 3
?: =(desk %base) ?: =(desk %base)
['System software' cord ~] ['System software' cord ~]
?: has-docket ?: has-docket
['App: "' title:docket '"' cord ~] ['App: "' title:docket '"' cord ~]
['Desk: ' desk cord ~] ['Desk: ' desk cord ~]
:: ::
@ -142,7 +164,7 @@
:: ::
++ commit ++ commit
^- action:hark ^- action:hark
?:(=(1 ud.cass:vat) created updated) ?:(=(1 version) created updated)
:: ::
++ created ++ created
^- action:hark ^- action:hark
@ -155,11 +177,11 @@
(body /desk/[desk] (title-prefix (rap 3 ' has been updated to ' get-version ~)) ~) (body /desk/[desk] (title-prefix (rap 3 ' has been updated to ' get-version ~)) ~)
:: ::
++ blocked ++ blocked
|= blockers=(set ^desk) |= =weft
^- action:hark ^- action:hark
:+ %add-note [/blocked place] :+ %add-note [/blocked place]
%^ body /blocked (title-prefix ' is blocked from upgrading') %^ body /blocked (title-prefix ' is blocked from upgrading')
`(rap 3 'Blocking desks: ' (join ', ' ~(tap in blockers))) `(rap 3 'Blocked waiting for system version: ' (scot %ud num.weft) 'K' ~)
:: ::
++ ver ++ ver
|= =version:^docket |= =version:^docket
@ -172,7 +194,7 @@
-- --
++ note ++ note
|% |%
++ merge ++ merge
|= [=desk hash=@uv] |= [=desk hash=@uv]
^- (list body:hark) ^- (list body:hark)
:_ ~ :_ ~

View File

@ -28,6 +28,7 @@
%tas s+(scot %tas p.c) %tas s+(scot %tas p.c)
%ud (numb p.c) %ud (numb p.c)
== ==
::
++ foreign-desk ++ foreign-desk
|= [s=^ship =desk] |= [s=^ship =desk]
^- cord ^- cord

View File

@ -1 +1 @@
[%zuse 418] [%zuse 417]

53
desk/ted/get-dudes.hoon Normal file
View File

@ -0,0 +1,53 @@
/- spider
/+ *strandio
::
=, strand=strand:spider
::
:: send on /spider/garden/json/get-dudes/json
::
|%
++ buds :: get agents currently running
|= p=desk
=/ m (strand ,(list dude:gall))
^- form:m
?. =(%$ p)
;< q=(list dude:gall) bind:m (suds p)
(pure:m q)
;< q=(list desk) bind:m duds
=| r=(list (list dude:gall))
|- ^- form:m
=* s $
?~ q (pure:m (zing r))
;< t=(list dude:gall) bind:m (suds i.q)
s(q t.q, r [t r])
::
++ suds :: clean %ge scry
|= p=desk
=/ m (strand ,(list dude:gall))
^- form:m
;< q=(set [dude:gall ?]) bind:m
(scry (set ,[dude:gall ?]) /ge/(scot %tas p))
%- pure:m
(murn ~(tap in q) |=([dude:gall ?] ?.(+.+< ~ `-.+<)))
::
++ duds :: get desks
=/ m (strand ,(list desk))
^- form:m
;< p=(set desk) bind:m (scry (set ,desk) /cd/base)
(pure:m ~(tap in p))
--
::
^- thread:spider
|= jon=vase
=/ m (strand ,vase)
^- form:m
;< =bowl:spider bind:m get-bowl
=, bowl
?~ know=!<((unit json) jon)
(pure:m !>(`json`[%s 'invalid-request']))
?. ?=([%s @] u.know)
(pure:m !>(`json`[%s 'invalid-request']))
=, format
;< breh=(list @tas) bind:m (buds (so:dejs u.know))
%- pure:m
!>(`json`(frond:enjs 'buds' a+(turn breh |=(@tas s+[+<]))))

16
ui/package-lock.json generated
View File

@ -20,8 +20,8 @@
"@tlon/sigil-js": "^1.4.4", "@tlon/sigil-js": "^1.4.4",
"@tloncorp/mock-http-api": "^1.2.0", "@tloncorp/mock-http-api": "^1.2.0",
"@types/lodash": "^4.14.172", "@types/lodash": "^4.14.172",
"@urbit/api": "^2.1.1", "@urbit/api": "^2.2.0",
"@urbit/http-api": "^2.2.0", "@urbit/http-api": "^2.3.0",
"big-integer": "^1.6.48", "big-integer": "^1.6.48",
"browser-cookies": "^1.2.0", "browser-cookies": "^1.2.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
@ -1695,9 +1695,9 @@
} }
}, },
"node_modules/@urbit/api": { "node_modules/@urbit/api": {
"version": "2.1.1", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.1.1.tgz", "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.2.0.tgz",
"integrity": "sha512-QRlqhtJ73q+pgMdSwuOO62HlxA7/2c5ylCcOUT01LXkJ2LTVCl5u+QnejdDvUmqjOuN2PyZk7df30xJVg6rC2A==", "integrity": "sha512-W8kP9OT6yOK62n+4yCPO3i9QqU5xriLvZQ9WYW4SAV7ktbSrGuf2kmYbnoqfA/NybIs9Q/MbFkPewrz4XJ96Ag==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.16.0", "@babel/runtime": "^7.16.0",
"big-integer": "^1.6.48", "big-integer": "^1.6.48",
@ -8888,9 +8888,9 @@
} }
}, },
"@urbit/api": { "@urbit/api": {
"version": "2.1.1", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.1.1.tgz", "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.2.0.tgz",
"integrity": "sha512-QRlqhtJ73q+pgMdSwuOO62HlxA7/2c5ylCcOUT01LXkJ2LTVCl5u+QnejdDvUmqjOuN2PyZk7df30xJVg6rC2A==", "integrity": "sha512-W8kP9OT6yOK62n+4yCPO3i9QqU5xriLvZQ9WYW4SAV7ktbSrGuf2kmYbnoqfA/NybIs9Q/MbFkPewrz4XJ96Ag==",
"requires": { "requires": {
"@babel/runtime": "^7.16.0", "@babel/runtime": "^7.16.0",
"big-integer": "^1.6.48", "big-integer": "^1.6.48",

View File

@ -27,8 +27,8 @@
"@tlon/sigil-js": "^1.4.4", "@tlon/sigil-js": "^1.4.4",
"@tloncorp/mock-http-api": "^1.2.0", "@tloncorp/mock-http-api": "^1.2.0",
"@types/lodash": "^4.14.172", "@types/lodash": "^4.14.172",
"@urbit/api": "^2.1.1", "@urbit/api": "^2.2.0",
"@urbit/http-api": "^2.2.0", "@urbit/http-api": "^2.3.0",
"big-integer": "^1.6.48", "big-integer": "^1.6.48",
"browser-cookies": "^1.2.0", "browser-cookies": "^1.2.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",

View File

@ -86,9 +86,8 @@ const AppRoutes = () => {
fetchCharges(); fetchCharges();
fetchAllies(); fetchAllies();
const { fetchVats, fetchLag } = useKilnState.getState(); const { initializeKiln } = useKilnState.getState();
fetchVats(); initializeKiln();
fetchLag();
useContactState.getState().initialize(api); useContactState.getState().initialize(api);
useHarkState.getState().start(); useHarkState.getState().start();

View File

@ -1,4 +1,4 @@
import { chadIsRunning, Treaty, Vat } from '@urbit/api'; import { chadIsRunning, Pike, Treaty } from '@urbit/api';
import clipboardCopy from 'clipboard-copy'; import clipboardCopy from 'clipboard-copy';
import React, { FC, useCallback, useState } from 'react'; import React, { FC, useCallback, useState } from 'react';
import cn from 'classnames'; import cn from 'classnames';
@ -6,7 +6,7 @@ import { Button, PillButton } from './Button';
import { Dialog, DialogClose, DialogContent, DialogTrigger } from './Dialog'; import { Dialog, DialogClose, DialogContent, DialogTrigger } from './Dialog';
import { DocketHeader } from './DocketHeader'; import { DocketHeader } from './DocketHeader';
import { Spinner } from './Spinner'; import { Spinner } from './Spinner';
import { VatMeta } from './VatMeta'; import { PikeMeta } from './PikeMeta';
import useDocketState, { ChargeWithDesk, useTreaty } from '../state/docket'; import useDocketState, { ChargeWithDesk, useTreaty } from '../state/docket';
import { getAppHref, getAppName } from '../state/util'; import { getAppHref, getAppName } from '../state/util';
import { addRecentApp } from '../nav/search/Home'; import { addRecentApp } from '../nav/search/Home';
@ -17,7 +17,7 @@ type InstallStatus = 'uninstalled' | 'installing' | 'installed';
type App = ChargeWithDesk | Treaty; type App = ChargeWithDesk | Treaty;
interface AppInfoProps { interface AppInfoProps {
docket: App; docket: App;
vat?: Vat; pike?: Pike;
className?: string; className?: string;
} }
@ -34,10 +34,9 @@ function getInstallStatus(docket: App): InstallStatus {
return 'uninstalled'; return 'uninstalled';
} }
function getRemoteDesk(docket: App, vat?: Vat) { function getRemoteDesk(docket: App, pike?: Pike) {
if (vat && vat.arak.rail) { if (pike && pike.sync) {
const { ship, desk } = vat.arak.rail; return [pike.sync.ship, pike.sync.desk];
return [ship, desk];
} }
if ('chad' in docket) { if ('chad' in docket) {
return ['', docket.desk]; return ['', docket.desk];
@ -46,10 +45,10 @@ function getRemoteDesk(docket: App, vat?: Vat) {
return [ship, desk]; return [ship, desk];
} }
export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => { export const AppInfo: FC<AppInfoProps> = ({ docket, pike, className }) => {
const installStatus = getInstallStatus(docket); const installStatus = getInstallStatus(docket);
const [ship, desk] = getRemoteDesk(docket, vat); const [ship, desk] = getRemoteDesk(docket, pike);
const publisher = vat?.arak?.rail?.publisher ?? ship; const publisher = pike?.sync?.ship ?? ship;
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const treaty = useTreaty(ship, desk); const treaty = useTreaty(ship, desk);
@ -136,10 +135,10 @@ export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => {
</div> </div>
</DocketHeader> </DocketHeader>
<div className="space-y-6"> <div className="space-y-6">
{vat ? ( {pike ? (
<> <>
<hr className="-mx-5 sm:-mx-8 border-gray-50" /> <hr className="-mx-5 sm:-mx-8 border-gray-50" />
<VatMeta vat={vat} /> <PikeMeta pike={pike} />
</> </>
) : null} ) : null}
{!treaty ? null : ( {!treaty ? null : (

View File

@ -0,0 +1,25 @@
import React from 'react';
import { Pike } from '@urbit/api';
import { Attribute } from './Attribute';
export function PikeMeta(props: { pike: Pike }) {
const { pike } = props;
const pluralUpdates = pike.wefts?.length !== 1;
return (
<div className="mt-5 sm:mt-8 space-y-5 sm:space-y-8">
<Attribute title="Desk Hash" attr="hash">
{pike.hash}
</Attribute>
<Attribute title="Installed into" attr="local-desk">
%{pike.sync?.desk}
</Attribute>
{pike.wefts && pike.wefts.length > 0 ? (
<Attribute attr="next" title="Pending Updates">
{pike.wefts.length} update{pluralUpdates ? 's are' : ' is'} pending a System Update
</Attribute>
) : null}
</div>
);
}

View File

@ -3,11 +3,12 @@ import React, { HTMLAttributes } from 'react';
type ShipNameProps = { type ShipNameProps = {
name: string; name: string;
truncate?: boolean;
} & HTMLAttributes<HTMLSpanElement>; } & HTMLAttributes<HTMLSpanElement>;
export const ShipName = ({ name, ...props }: ShipNameProps) => { export const ShipName = ({ name, truncate = true, ...props }: ShipNameProps) => {
const separator = /([_^-])/; const separator = /([_^-])/;
const citedName = cite(name); const citedName = truncate ? cite(name) : name;
if (!citedName) { if (!citedName) {
return null; return null;

View File

@ -0,0 +1,103 @@
import React, { useCallback, useState } from 'react';
import { useAsyncCall } from '../logic/useAsyncCall';
import { Button } from './Button';
import { ShipName } from './ShipName';
import { Spinner } from './Spinner';
interface SourceSetterProps {
appName: string;
srcDesk: string;
srcShip?: string;
title: string;
toggleSrc: (desk: string, ship: string) => Promise<void>;
}
export default function SourceSetter({
appName,
srcDesk,
srcShip,
title,
toggleSrc
}: SourceSetterProps) {
const [newSyncShip, setNewSyncShip] = useState(srcShip ?? '');
const { status: requestStatus, call: handleSubmit } = useAsyncCall(toggleSrc);
const syncDirty = newSyncShip !== srcShip;
const onUnset = useCallback(() => {
if (!srcShip) {
return;
}
if (
// eslint-disable-next-line no-alert, no-restricted-globals
confirm(`Are you sure you want to unsync ${appName}? You will no longer receive updates.`)
) {
toggleSrc(srcDesk, srcShip);
}
}, [srcShip, srcDesk]);
const handleSourceChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const { target } = e;
const value = target.value.trim();
setNewSyncShip(value.startsWith('~') ? value : `~${value}`);
}, []);
const onSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
await handleSubmit(srcDesk, newSyncShip);
},
[srcDesk, newSyncShip]
);
return (
<>
<h2 className="h3 mb-7">{title}</h2>
<div className="space-y-3">
{srcShip ? (
<>
<h3 className="flex items-center h4 mb-2">Automatic Updates</h3>
<p>Automatically download and apply updates to keep {appName} up to date.</p>
<div className="flex-1 flex flex-col justify-center space-y-6">
<p>
OTA Source:{' '}
<ShipName name={srcShip} truncate={false} className="font-semibold font-mono" />
</p>
</div>
<div className="flex space-x-2">
<Button onClick={onUnset} variant="destructive">
Unsync Updates for {appName}...
</Button>
</div>
</>
) : (
<form className="inner-section relative" onSubmit={onSubmit}>
<label htmlFor="ota-source" className="h4 mb-3">
Set Update Source
</label>
<p className="mb-2">Enter a valid urbit name to receive updates for {appName}.</p>
<div className="relative">
<input
id="ota-source"
type="text"
value={newSyncShip}
onChange={handleSourceChange}
className="input font-semibold default-ring"
/>
{syncDirty && (
<Button type="submit" className="absolute top-1 right-1 py-1 px-3 text-sm">
{requestStatus !== 'loading' && 'Save'}
{requestStatus === 'loading' && (
<>
<span className="sr-only">Saving...</span>
<Spinner className="w-5 h-5" />
</>
)}
</Button>
)}
</div>
</form>
)}
</div>
</>
);
}

View File

@ -1,27 +0,0 @@
import React from 'react';
import { Vat } from '@urbit/api';
import { Attribute } from './Attribute';
export function VatMeta(props: { vat: Vat }) {
const { vat } = props;
const { desk, arak, cass, hash } = vat;
const { desk: foreignDesk, ship, next } = arak.rail || {};
const pluralUpdates = next?.length !== 1;
return (
<div className="mt-5 sm:mt-8 space-y-5 sm:space-y-8">
<Attribute title="Desk Hash" attr="hash">
{hash}
</Attribute>
<Attribute title="Installed into" attr="local-desk">
%{desk}
</Attribute>
{next && next.length > 0 ? (
<Attribute attr="next" title="Pending Updates">
{next.length} update{pluralUpdates ? 's are' : ' is'} pending a System Update
</Attribute>
) : null}
</div>
);
}

View File

@ -1,42 +1,37 @@
import { kilnBump, Vat } from '@urbit/api'; import { kilnBump, Pike } from '@urbit/api';
import { partition, pick } from 'lodash'; import { partition, pick } from 'lodash';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import api from '../state/api'; import api from '../state/api';
import { useCharges } from '../state/docket'; import { useCharges } from '../state/docket';
import useKilnState, { useVat } from '../state/kiln'; import useKilnState, { usePike } from '../state/kiln';
export function vatIsBlocked(newKelvin: number | undefined, vat: Vat) { function pikeIsBlocked(newKelvin: number, pike: Pike) {
if (!newKelvin) { return !pike.wefts?.find(({ kelvin }) => kelvin === newKelvin);
return false;
}
return !(vat.arak?.rail?.next || []).find(({ weft }) => weft.kelvin === newKelvin);
} }
export function useSystemUpdate() { export function useSystemUpdate() {
const { push } = useHistory(); const { push } = useHistory();
const base = useVat('base'); const base = usePike('base');
const update = base?.arak?.rail?.next?.[0]; const nextUpdate = base?.wefts[0];
const newKelvin = update?.weft?.kelvin; const newKelvin = base?.wefts[0]?.kelvin ?? 417;
const charges = useCharges(); const charges = useCharges();
const [blocked] = useKilnState((s) => { const [blocked] = useKilnState((s) => {
const [b, u] = partition(Object.entries(s.vats), ([, vat]) => vatIsBlocked(newKelvin, vat)); const [b, u] = partition(Object.entries(s.pikes), ([, pike]) => pikeIsBlocked(newKelvin, pike));
return [b.map(([d]) => d), u.map(([d]) => d)] as const; return [b.map(([d]) => d), u.map(([d]) => d)] as const;
}); });
const systemBlocked = update && blocked; const systemBlocked = nextUpdate && blocked;
const blockedCharges = Object.values(pick(charges, blocked)); const blockedCharges = Object.values(pick(charges, blocked));
const blockedCount = blockedCharges.length; const blockedCount = blockedCharges.length;
const freezeApps = useCallback(async () => { const freezeApps = useCallback(async () => {
api.poke(kilnBump(true)); await api.poke(kilnBump());
push('/leap/upgrading'); push('/leap/upgrading');
}, []); }, []);
return { return {
base, nextUpdate,
update,
systemBlocked, systemBlocked,
blockedCharges, blockedCharges,
blockedCount, blockedCount,

View File

@ -1,6 +1,6 @@
import { Handler, SubscriptionHandler, createResponse } from '@tloncorp/mock-http-api'; import { Handler, SubscriptionHandler, createResponse } from '@tloncorp/mock-http-api';
import mockContacts from './mockContacts'; import mockContacts from './mockContacts';
import { mockAllies, mockCharges, mockTreaties, mockVats } from '../state/mock-data'; import { mockAllies, mockCharges, mockTreaties } from '../state/mock-data';
const settingsSub = { const settingsSub = {
action: 'subscribe', action: 'subscribe',
@ -45,14 +45,6 @@ const mockHandlers: Handler[] = [
} }
}) })
}, },
{
action: 'scry',
app: 'hood',
path: '/kiln/vats',
func: () => ({
mockVats
})
},
{ {
action: 'scry', action: 'scry',
app: 'hood', app: 'hood',

View File

@ -3,9 +3,9 @@ import classNames from 'classnames';
import clipboardCopy from 'clipboard-copy'; import clipboardCopy from 'clipboard-copy';
import React, { HTMLAttributes, useCallback, useState } from 'react'; import React, { HTMLAttributes, useCallback, useState } from 'react';
import { Link, Route, useHistory } from 'react-router-dom'; import { Link, Route, useHistory } from 'react-router-dom';
import { Vat } from '@urbit/api'; import { Pike } from '@urbit/api';
import { Adjust } from '../components/icons/Adjust'; import { Adjust } from '../components/icons/Adjust';
import { useVat } from '../state/kiln'; import { usePike } from '../state/kiln';
import { disableDefault, handleDropdownLink } from '../state/util'; import { disableDefault, handleDropdownLink } from '../state/util';
import { useMedia } from '../logic/useMedia'; import { useMedia } from '../logic/useMedia';
import { Cross } from '../components/icons/Cross'; import { Cross } from '../components/icons/Cross';
@ -17,15 +17,15 @@ type SystemMenuProps = HTMLAttributes<HTMLButtonElement> & {
shouldDim: boolean; shouldDim: boolean;
}; };
function getHash(vat: Vat): string { function getHash(pike: Pike): string {
const parts = vat.hash.split('.'); const parts = pike.hash.split('.');
return parts[parts.length - 1]; return parts[parts.length - 1];
} }
export const SystemMenu = ({ className, open, subMenuOpen, shouldDim }: SystemMenuProps) => { export const SystemMenu = ({ className, open, subMenuOpen, shouldDim }: SystemMenuProps) => {
const { push } = useHistory(); const { push } = useHistory();
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const garden = useVat(window.desk); const garden = usePike(window.desk);
const hash = garden ? getHash(garden) : null; const hash = garden ? getHash(garden) : null;
const isMobile = useMedia('(max-width: 639px)'); const isMobile = useMedia('(max-width: 639px)');
const select = useLeapStore((s) => s.select); const select = useLeapStore((s) => s.select);

View File

@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom';
import { AppInfo } from '../../components/AppInfo'; import { AppInfo } from '../../components/AppInfo';
import { Spinner } from '../../components/Spinner'; import { Spinner } from '../../components/Spinner';
import useDocketState, { useCharge, useTreaty } from '../../state/docket'; import useDocketState, { useCharge, useTreaty } from '../../state/docket';
import { useVat } from '../../state/kiln'; import { usePike } from '../../state/kiln';
import { getAppName } from '../../state/util'; import { getAppName } from '../../state/util';
import { useLeapStore } from '../Nav'; import { useLeapStore } from '../Nav';
@ -11,7 +11,7 @@ export const TreatyInfo = () => {
const select = useLeapStore((state) => state.select); const select = useLeapStore((state) => state.select);
const { host, desk } = useParams<{ host: string; desk: string }>(); const { host, desk } = useParams<{ host: string; desk: string }>();
const treaty = useTreaty(host, desk); const treaty = useTreaty(host, desk);
const vat = useVat(desk); const pike = usePike(desk);
const charge = useCharge(desk); const charge = useCharge(desk);
const name = getAppName(treaty); const name = getAppName(treaty);
@ -34,5 +34,5 @@ export const TreatyInfo = () => {
</div> </div>
); );
} }
return <AppInfo className="dialog-inner-container" docket={charge || treaty} vat={vat} />; return <AppInfo className="dialog-inner-container" docket={charge || treaty} pike={pike} />;
}; };

View File

@ -26,8 +26,8 @@ export const Grid: FunctionComponent = () => {
window.location.reload(); window.location.reload();
} }
const start = performance.now(); const start = performance.now();
await useKilnState.getState().fetchVats(); await useKilnState.getState().fetchPikes();
await useKilnState.getState().fetchVats(); await useKilnState.getState().fetchPikes();
if (performance.now() - start > 5000) { if (performance.now() - start > 5000) {
attempt(count + 1); attempt(count + 1);
} else { } else {

View File

@ -1,15 +1,15 @@
import { Pikes } from '@urbit/api';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom'; import { Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom';
import { Spinner } from '../components/Spinner'; import { Spinner } from '../components/Spinner';
import { useQuery } from '../logic/useQuery'; import { useQuery } from '../logic/useQuery';
import { useCharge } from '../state/docket'; import { useCharge } from '../state/docket';
import useKilnState, { useKilnLoaded } from '../state/kiln'; import { useKilnLoaded, usePikes } from '../state/kiln';
import { getAppHref } from '../state/util'; import { getAppHref } from '../state/util';
function getDeskByForeignRef(ship: string, desk: string): string | undefined { function getDeskByForeignRef(pikes: Pikes, ship: string, desk: string): string | undefined {
const { vats } = useKilnState.getState(); const found = Object.entries(pikes).find(
const found = Object.entries(vats).find( ([, pike]) => pike.sync?.ship === ship && pike.sync?.desk === desk
([, vat]) => vat.arak.rail?.ship === ship && vat.arak.rail?.desk === desk
); );
return found ? found[0] : undefined; return found ? found[0] : undefined;
} }
@ -22,8 +22,8 @@ type AppLinkProps = RouteComponentProps<{
function AppLink({ match, history, location }: AppLinkProps) { function AppLink({ match, history, location }: AppLinkProps) {
const { ship, desk, link = '' } = match.params; const { ship, desk, link = '' } = match.params;
const ourDesk = getDeskByForeignRef(ship, desk); const pikes = usePikes();
console.log(ourDesk); const ourDesk = getDeskByForeignRef(pikes, ship, desk);
if (ourDesk) { if (ourDesk) {
return <AppLinkRedirect desk={ourDesk} link={link} />; return <AppLinkRedirect desk={ourDesk} link={link} />;

View File

@ -1,50 +1,27 @@
import React, { useCallback } from 'react'; import React from 'react';
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom';
import { Setting } from '../components/Setting';
import { ShipName } from '../components/ShipName';
import { useCharge } from '../state/docket'; import { useCharge } from '../state/docket';
import useKilnState, { useVat } from '../state/kiln'; import useKilnState, { usePike } from '../state/kiln';
import { getAppName } from '../state/util'; import { getAppName } from '../state/util';
import SourceSetter from '../components/SourceSetter';
export const AppPrefs = ({ match }: RouteComponentProps<{ desk: string }>) => { export const AppPrefs = ({ match }: RouteComponentProps<{ desk: string }>) => {
const { desk } = match.params; const { desk } = match.params;
const charge = useCharge(desk); const charge = useCharge(desk);
const vat = useVat(desk); const appName = getAppName(charge);
const tracking = !!vat?.arak.rail; const pike = usePike(desk);
const otasEnabled = !vat?.arak.rail?.paused; const srcShip = pike?.sync?.ship;
const otaSource = vat?.arak.rail?.ship; const { toggleSync } = useKilnState();
const toggleOTAs = useKilnState((s) => s.toggleOTAs);
const toggleUpdates = useCallback((on: boolean) => toggleOTAs(desk, on), [desk, toggleOTAs]);
return ( return (
<> <div className="inner-section space-y-8 relative">
<h2 className="h3 mb-7">{getAppName(charge)} Settings</h2> <SourceSetter
<div className="space-y-8"> appName={appName}
{tracking ? ( title={`${appName} Settings`}
<Setting toggleSrc={toggleSync}
on={otasEnabled} srcDesk={desk}
toggle={toggleUpdates} srcShip={srcShip}
name="Automatic Updates" />
> </div>
<p className="mb-1 leading-5">
Automatically download and apply updates to keep{' '}
{getAppName(charge)} up to date.
</p>
{otaSource && (
<p>
OTA Source:{' '}
<ShipName
name={otaSource}
className="font-mono font-semibold"
/>
</p>
)}
</Setting>
) : (
<h4 className="text-gray-500">No settings</h4>
)}
</div>
</>
); );
}; };

View File

@ -1,4 +1,4 @@
import { Vat } from '@urbit/api'; import { Pike } from '@urbit/api';
import React from 'react'; import React from 'react';
import { AppList } from '../../components/AppList'; import { AppList } from '../../components/AppList';
import { Button } from '../../components/Button'; import { Button } from '../../components/Button';
@ -6,24 +6,22 @@ import { Dialog, DialogClose, DialogContent, DialogTrigger } from '../../compone
import { FullTlon16Icon } from '../../components/icons/FullTlon16Icon'; import { FullTlon16Icon } from '../../components/icons/FullTlon16Icon';
import { useSystemUpdate } from '../../logic/useSystemUpdate'; import { useSystemUpdate } from '../../logic/useSystemUpdate';
import { useCharge } from '../../state/docket'; import { useCharge } from '../../state/docket';
import { useVat } from '../../state/kiln'; import { usePike } from '../../state/kiln';
import { disableDefault, pluralize } from '../../state/util'; import { disableDefault, pluralize } from '../../state/util';
import { UpdatePreferences } from './UpdatePreferences'; import { UpdatePreferences } from './UpdatePreferences';
function getHash(vat: Vat): string { function getHash(pike: Pike): string {
const parts = vat.hash.split('.'); const parts = pike.hash.split('.');
return parts[parts.length - 1]; return parts[parts.length - 1];
} }
export const AboutSystem = () => { export const AboutSystem = () => {
const garden = useVat('garden');
const gardenCharge = useCharge('garden'); const gardenCharge = useCharge('garden');
const { base, update, systemBlocked, blockedCharges, blockedCount, freezeApps } = const gardenPike = usePike(window.desk);
const { systemBlocked, blockedCharges, blockedCount, freezeApps } =
useSystemUpdate(); useSystemUpdate();
const hash = base && getHash(base); const hash = gardenPike && getHash(gardenPike);
const aeon = base ? base.arak.rail?.aeon : '';
const nextAeon = update?.aeon;
return ( return (
<> <>
<div className="inner-section space-y-8 relative mb-4"> <div className="inner-section space-y-8 relative mb-4">
@ -45,15 +43,6 @@ export const AboutSystem = () => {
<p> <p>
Version {gardenCharge?.version} ({hash}) Version {gardenCharge?.version} ({hash})
</p> </p>
{systemBlocked && (
<p>
Aeon {aeon}{' '}
<span className="text-orange-500 mx-4 space-x-2">
<span>&mdash;&gt;</span> <span>/</span> <span>&mdash;&gt;</span>
</span>{' '}
Aeon {nextAeon}
</p>
)}
</div> </div>
{systemBlocked ? ( {systemBlocked ? (
<> <>
@ -98,7 +87,7 @@ export const AboutSystem = () => {
)} )}
</div> </div>
</div> </div>
<UpdatePreferences base={base} /> <UpdatePreferences />
</> </>
); );
}; };

View File

@ -1,81 +1,25 @@
import { Vat } from '@urbit/api'; import { Vat } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import React, { ChangeEvent, FormEvent, useCallback, useEffect, useState } from 'react'; import React from 'react';
import { Button } from '../../components/Button'; import SourceSetter from '../../components/SourceSetter';
import { Setting } from '../../components/Setting'; import useKilnState, { usePike } from '../../state/kiln';
import { Spinner } from '../../components/Spinner';
import { useAsyncCall } from '../../logic/useAsyncCall';
import useKilnState from '../../state/kiln';
interface UpdatePreferencesProps {
base: Vat | undefined;
}
export const UpdatePreferences = ({ base }: UpdatePreferencesProps) => {
const { changeOTASource, toggleOTAs } = useKilnState((s) =>
_.pick(s, ['toggleOTAs', 'changeOTASource'])
);
const otasEnabled = base && !(base.arak?.rail?.paused ?? true);
const otaSource = base && base.arak.rail?.ship;
const toggleBase = useCallback((on: boolean) => toggleOTAs('base', on), [toggleOTAs]);
const [source, setSource] = useState('');
const sourceDirty = source !== otaSource;
const { status: sourceStatus, call: setOTA } = useAsyncCall(changeOTASource);
useEffect(() => {
if (otaSource) {
setSource(otaSource);
}
}, [otaSource]);
const handleSourceChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const { target } = e;
const value = target.value.trim();
setSource(value.startsWith('~') ? value : `~${value}`);
}, []);
const onSubmit = useCallback(
(e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setOTA(source);
},
[source]
);
export const UpdatePreferences = () => {
const desk = 'base';
const appName = 'your Urbit';
const pike = usePike(desk);
const srcShip = pike?.sync?.ship;
const { toggleInstall } = useKilnState();
return ( return (
<div className="inner-section space-y-8 relative"> <div className="inner-section space-y-8 relative">
<h2 className="h4">Update Preferences</h2> <SourceSetter
<form onSubmit={onSubmit}> appName={appName}
<label htmlFor="ota-source" className="block font-semibold mb-1.5"> toggleSrc={toggleInstall}
System Update Provider srcDesk={desk}
</label> srcShip={srcShip}
<div className="flex items-center space-x-2"> title="System Updates"
<input />
id="ota-source"
type="text"
value={source}
onChange={handleSourceChange}
className="input bg-gray-50 font-semibold default-ring"
/>
<Button type="submit" disabled={!sourceDirty}>
{sourceStatus !== 'loading' && 'Update'}
{sourceStatus === 'loading' && (
<>
<span className="sr-only">Saving...</span>
<Spinner className="w-5 h-5" />
</>
)}
</Button>
</div>
</form>
<Setting on={!!otasEnabled} toggle={toggleBase} name="Automatically Update My Urbit">
<p className="text-gray-600 leading-5">
Ensure that system updates are downloaded and applied as soon as my update provider has an
update readied
</p>
</Setting>
</div> </div>
); );
}; };

View File

@ -1,79 +1,76 @@
import { import {
getVats,
Vats,
scryLag, scryLag,
getBlockers, getPikes,
Vat, Pikes,
kilnInstall, Pike,
kilnPause, kilnUnsync,
kilnResume kilnSync,
kilnUninstall,
kilnInstall
} from '@urbit/api'; } from '@urbit/api';
import create from 'zustand'; import create from 'zustand';
import produce from 'immer'; import produce from 'immer';
import { useCallback } from 'react'; import { useCallback } from 'react';
import api from './api'; import api from './api';
import { fakeRequest, useMockData } from './util';
import { mockPikes } from './mock-data';
interface KilnState { interface KilnState {
vats: Vats; pikes: Pikes;
loaded: boolean; loaded: boolean;
fetchVats: () => Promise<void>;
lag: boolean; lag: boolean;
fetchLag: () => Promise<void>; fetchLag: () => Promise<void>;
changeOTASource: (ship: string) => Promise<void>; fetchPikes: () => Promise<void>;
toggleOTAs: (desk: string, on: boolean) => Promise<void>; toggleInstall: (desk: string, ship: string) => Promise<void>;
toggleSync: (desk: string, ship: string) => Promise<void>;
set: (s: KilnState) => void; set: (s: KilnState) => void;
initializeKiln: () => Promise<void>;
} }
const useKilnState = create<KilnState>((set, get) => ({ const useKilnState = create<KilnState>((set, get) => ({
vats: {}, pikes: useMockData ? mockPikes : {},
lag: false, lag: false,
loaded: false, loaded: false,
fetchVats: async () => { fetchPikes: async () => {
const vats = await api.scry<Vats>(getVats); if (useMockData) {
set({ vats, loaded: true }); await fakeRequest({}, 500);
set({ loaded: true });
return;
}
const pikes = await api.scry<Pikes>(getPikes);
set({ pikes, loaded: true });
}, },
fetchLag: async () => { fetchLag: async () => {
const lag = await api.scry<boolean>(scryLag); const lag = await api.scry<boolean>(scryLag);
set({ lag }); set({ lag });
}, },
changeOTASource: async (ship: string) => { toggleInstall: async (desk: string, ship: string) => {
await api.poke(kilnInstall(ship, 'kids', 'base')); const synced = !!get().pikes[desk].sync;
await (useMockData
? fakeRequest('')
: api.poke(synced ? kilnUninstall(desk) : kilnInstall(ship, 'kids', desk)));
await get().fetchPikes();
}, },
toggleOTAs: async (desk: string, on: boolean) => { toggleSync: async (desk: string, ship: string) => {
set( const synced = !!get().pikes[desk].sync;
produce((draft: KilnState) => { await (useMockData
const { arak } = draft.vats[desk]; ? fakeRequest('')
if (!arak.rail) { : api.poke(synced ? kilnUnsync(ship, desk) : kilnSync(ship, desk)));
return; await get().fetchPikes();
} },
if (on) { set: produce(set),
arak.rail.paused = false; initializeKiln: async () => {
} else { await get().fetchLag();
arak.rail.paused = true; await get().fetchPikes();
} }
})
);
await api.poke(on ? kilnResume(desk) : kilnPause(desk));
await get().fetchVats(); // refresh vat state
},
set: produce(set)
})); }));
api.subscribe({ const selPikes = (s: KilnState) => s.pikes;
app: 'hood', export function usePikes(): Pikes {
path: '/kiln/vats', return useKilnState(selPikes);
event: () => {
useKilnState.getState().fetchVats();
}
});
const selBlockers = (s: KilnState) => getBlockers(s.vats);
export function useBlockers() {
return useKilnState(selBlockers);
} }
export function useVat(desk: string): Vat | undefined { export function usePike(desk: string): Pike | undefined {
return useKilnState(useCallback((s) => s.vats[desk], [desk])); return useKilnState(useCallback((s) => s.pikes[desk], [desk]));
} }
const selLag = (s: KilnState) => s.lag; const selLag = (s: KilnState) => s.lag;

View File

@ -1,6 +1,4 @@
import { import {
Vat,
Vats,
Allies, Allies,
Charges, Charges,
DocketHrefGlob, DocketHrefGlob,
@ -13,7 +11,8 @@ import {
Contact, Contact,
Contacts, Contacts,
Timebox, Timebox,
harkBinToId harkBinToId,
Pikes
} from '@urbit/api'; } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import systemUrl from '../assets/system.png'; import systemUrl from '../assets/system.png';
@ -341,37 +340,35 @@ export const mockContacts: Contacts = {
} }
}; };
export const mockVat = (desk: string, blockers?: boolean): Vat => ({ export const mockPikes: Pikes = {
cass: { kids: {
da: '~2021.9.13..05.41.04..ae65', sync: null,
ud: 1 zest: 'dead',
wefts: [],
hash: '0v19.q7u27.omps3.fbhf4.53rai.co157.pben7.pu94n.63v4p.3kcb7.iafj0'
}, },
desk, garden: {
arak: { sync: {
rein: { desk: 'garden',
sub: [], ship: '~mister-dister-dozzod-dozzod'
add: []
}, },
rail: zest: 'live',
desk === 'uniswap' wefts: [],
? null hash: '0v18.hbbs6.onu15.skjkv.qrfgl.vf4oo.0igo5.2q0d3.6r3r8.2dkmo.oa04m'
: {
aeon: 3,
desk,
publisher: '~zod',
next: blockers ? [{ aeon: 3, weft: { name: 'zuse', kelvin: 419 } }] : [],
ship: '~zod',
paused: desk === 'groups'
}
}, },
hash: '0vh.lhfn6.julg1.fs52d.g2lqj.q5kp0.2o7j3.2bljl.jdm34.hd46v.9uv5v' landscape: {
}); sync: {
desk: 'landscape',
const badVats = ['inbox', 'system', 'terminal', 'base']; ship: '~lander-dister-dozzod-dozzod'
export const mockVats = _.reduce( },
mockCharges, zest: 'live',
(vats, charge, desk) => { wefts: [],
return { ...vats, [desk]: mockVat(desk, !badVats.includes(desk)) }; hash: '0v1t.qln8k.cskmt.cn6lv.gu335.jfba6.kte90.iqqn3.aj67b.t389a.8imuo'
}, },
{ base: mockVat('base', true) } as Vats base: {
); sync: null,
zest: 'live',
wefts: [],
hash: '0v1e.b5auh.6u82i.hqk1r.22kli.4ubef.a1cbo.3g532.6l49k.g0i8e.t6eid'
}
};

View File

@ -2,6 +2,16 @@ import { Docket, DocketHref, Treaty } from '@urbit/api';
import { hsla, parseToHsla } from 'color2k'; import { hsla, parseToHsla } from 'color2k';
import _ from 'lodash'; import _ from 'lodash';
export const useMockData = import.meta.env.MODE === 'mock';
export async function fakeRequest<T>(data: T, time = 300): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(data);
}, time);
});
}
export function getAppHref(href: DocketHref) { export function getAppHref(href: DocketHref) {
return 'site' in href ? href.site : `/apps/${href.glob.base}/`; return 'site' in href ? href.site : `/apps/${href.glob.base}/`;
} }

View File

@ -8,7 +8,7 @@ import { getAppHref } from '../state/util';
import { useRecentsStore } from '../nav/search/Home'; import { useRecentsStore } from '../nav/search/Home';
import { ChargeWithDesk } from '../state/docket'; import { ChargeWithDesk } from '../state/docket';
import { useTileColor } from './useTileColor'; import { useTileColor } from './useTileColor';
import { useVat } from '../state/kiln'; import { usePike } from '../state/kiln';
import { Bullet } from '../components/icons/Bullet'; import { Bullet } from '../components/icons/Bullet';
import { dragTypes } from './TileGrid'; import { dragTypes } from './TileGrid';
@ -21,11 +21,12 @@ type TileProps = {
export const Tile: FunctionComponent<TileProps> = ({ charge, desk, disabled = false }) => { export const Tile: FunctionComponent<TileProps> = ({ charge, desk, disabled = false }) => {
const addRecentApp = useRecentsStore((state) => state.addRecentApp); const addRecentApp = useRecentsStore((state) => state.addRecentApp);
const { title, image, color, chad, href } = charge; const { title, image, color, chad, href } = charge;
const vat = useVat(desk); const pike = usePike(desk);
const { lightText, tileColor, menuColor, suspendColor, suspendMenuColor } = useTileColor(color); const { lightText, tileColor, menuColor, suspendColor, suspendMenuColor } = useTileColor(color);
const loading = !disabled && 'install' in chad; const loading = !disabled && 'install' in chad;
const suspended = disabled || 'suspend' in chad; const suspended = disabled || 'suspend' in chad;
const hung = 'hung' in chad; const hung = 'hung' in chad;
// TODO should held zest be considered inactive? suspended? also, null sync?
const active = !disabled && chadIsRunning(chad); const active = !disabled && chadIsRunning(chad);
const link = getAppHref(href); const link = getAppHref(href);
const backgroundColor = suspended ? suspendColor : active ? tileColor || 'purple' : suspendColor; const backgroundColor = suspended ? suspendColor : active ? tileColor || 'purple' : suspendColor;
@ -56,6 +57,9 @@ export const Tile: FunctionComponent<TileProps> = ({ charge, desk, disabled = fa
> >
<div> <div>
<div className="absolute z-10 top-4 left-4 sm:top-6 sm:left-6 flex items-center"> <div className="absolute z-10 top-4 left-4 sm:top-6 sm:left-6 flex items-center">
{pike?.zest === 'held' && !disabled && (
<Bullet className="w-4 h-4 text-orange-500 dark:text-black" />
)}
{!active && ( {!active && (
<> <>
{loading && <Spinner className="h-6 w-6 mr-2" />} {loading && <Spinner className="h-6 w-6 mr-2" />}
@ -65,9 +69,6 @@ export const Tile: FunctionComponent<TileProps> = ({ charge, desk, disabled = fa
</> </>
)} )}
</div> </div>
{vat?.arak.rail?.paused && !disabled && (
<Bullet className="absolute z-10 top-5 left-5 sm:top-7 sm:left-7 w-4 h-4 text-orange-500 dark:text-black" />
)}
<TileMenu <TileMenu
desk={desk} desk={desk}
chad={chad} chad={chad}

View File

@ -3,13 +3,13 @@ import { useHistory, useParams } from 'react-router-dom';
import { Dialog, DialogContent } from '../components/Dialog'; import { Dialog, DialogContent } from '../components/Dialog';
import { AppInfo } from '../components/AppInfo'; import { AppInfo } from '../components/AppInfo';
import { useCharge } from '../state/docket'; import { useCharge } from '../state/docket';
import { useVat } from '../state/kiln'; import { usePike } from '../state/kiln';
export const TileInfo = () => { export const TileInfo = () => {
const { desk } = useParams<{ desk: string }>(); const { desk } = useParams<{ desk: string }>();
const { push } = useHistory(); const { push } = useHistory();
const charge = useCharge(desk); const charge = useCharge(desk);
const vat = useVat(desk); const pike = usePike(desk);
if (!charge) { if (!charge) {
return null; return null;
@ -18,7 +18,7 @@ export const TileInfo = () => {
return ( return (
<Dialog open onOpenChange={(open) => !open && push('/')}> <Dialog open onOpenChange={(open) => !open && push('/')}>
<DialogContent> <DialogContent>
<AppInfo vat={vat} docket={charge} /> <AppInfo pike={pike} docket={charge} />
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );