Merge branch 'master' into release/next-vere

* master: (151 commits)
  build: check subdirs on tsc
  meta: auto-deploy release/next-userspace
  glob: update to 0v4.vrvkt.4gcnm.dgg5o.e73d6.kqnaq
  OmniboxResult: fix arg order
  glob: update to 0v3.p4se6.4k5i0.8v0le.v5vn4.mdotq
  hooks: reset diplomacy
  interface: package audit
  interface: stringify number prop
  graph: added comments
  links: remove stray semicolon
  graph-push-hook: only cache marks, do not cache metadata for a graph
  graph-push-hook: code style cleaning
  push-hook: clean-up code style
  graph-store: internally cache all scried daises
  graph-push-hook: rename mark for cache action
  demo-push-hook: update for new push-hook interface
  hover
  permalinks: add group peer space
  notifications: key notifications correctly
  DeleteGroup: fix copy
  ...
This commit is contained in:
Joe Bryan 2021-05-18 12:27:42 -07:00
commit 68a19287ab
336 changed files with 9587 additions and 7354 deletions

View File

@ -2,7 +2,7 @@ name: glob
on: on:
push: push:
branches: branches:
- 'release/next-js' - 'release/next-userspace'
jobs: jobs:
glob: glob:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -6,13 +6,13 @@ on:
jobs: jobs:
merge-to-next-js: merge-to-next-js:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "Merge master to release/next-js" name: "Merge master to release/next-userspace"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: devmasx/merge-branch@v1.3.1 - uses: devmasx/merge-branch@v1.3.1
with: with:
type: now type: now
target_branch: release/next-js target_branch: release/next-userspace
github_token: ${{ secrets.JANEWAY_BOT_TOKEN }} github_token: ${{ secrets.JANEWAY_BOT_TOKEN }}
merge-to-group-timer: merge-to-group-timer:

14
.github/workflows/typescript-check.yml vendored Normal file
View File

@ -0,0 +1,14 @@
name: typescript-check
on:
pull_request:
paths:
- 'pkg/interface/**'
jobs:
typescript-check:
runs-on: ubuntu-latest
name: "Check pkg/interface types"
steps:
- uses: actions/checkout@v2
- run: cd 'pkg/interface' && npm i && npm run tsc

View File

@ -309,9 +309,9 @@ the new binary, and restarting the pier with it.
#### Continuous deployment #### Continuous deployment
A subset of release branches are deployed continuously to the network. Thus far A subset of release branches are deployed continuously to the network. Thus far
this only includes `release/next-js`, which deploys livenet-compatible this only includes `release/next-userspace`, which deploys livenet-compatible
JavaScript changes to select QA ships. Any push to master will automatically changes to select QA ships. Any push to master will automatically
merge master into `release/next-js` to keep the streams at parity. merge master into `release/next-userspace` to keep the streams at parity.
### Announce the update ### Announce the update

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e1656cbfd472430c463db4e968c1dd4c8894a5219a27cd301a70ad262f5999f1 oid sha256:d7b7cf24e56ab078cf1dcb82e4e7744f188c5221c08772d6cfb15f59ce81aaa5
size 10610939 size 11198219

View File

@ -1394,8 +1394,6 @@
^+ this ^+ this
?: =(~ dom) ?: =(~ dom)
~|(%acme-empty-certificate-order !!) ~|(%acme-empty-certificate-order !!)
?: ?=(?(%earl %pawn) (clan:title our.bow))
this
=. ..emit (queue-next-order 1 | dom) =. ..emit (queue-next-order 1 | dom)
=. ..emit cancel-current-order =. ..emit cancel-current-order
:: notify %dill :: notify %dill

View File

@ -738,6 +738,7 @@
:: ::
?. (is-chat-graph target) ?. (is-chat-graph target)
[[(note:sh-out "no such chat")]~ put-ses] [[(note:sh-out "no such chat")]~ put-ses]
=. audience target
=. viewing (~(put in viewing) target) =. viewing (~(put in viewing) target)
=^ cards state =^ cards state
?: (~(has by bound) target) ?: (~(has by bound) target)

View File

@ -70,10 +70,11 @@
:: ::
++ transform-proxy-update ++ transform-proxy-update
|= vas=vase |= vas=vase
^- (unit vase) ^- (quip card (unit vase))
:: TODO: should check if user is allowed to %add, %remove, %edit :: TODO: should check if user is allowed to %add, %remove, %edit
:: contact :: contact
=/ =update:store !<(update:store vas) =/ =update:store !<(update:store vas)
:- ~
?- -.update ?- -.update
%initial ~ %initial ~
%add `vas %add `vas

View File

@ -43,8 +43,8 @@
:: ::
++ transform-proxy-update ++ transform-proxy-update
|= vas=vase |= vas=vase
^- (unit vase) ^- (quip card (unit vase))
`vas ``vas
:: ::
++ resource-for-update ++ resource-for-update
|= =vase |= =vase

View File

@ -188,8 +188,11 @@
?: ?=([%'~landscape' %js %session ~] site.req-line) ?: ?=([%'~landscape' %js %session ~] site.req-line)
%+ require-authorization-simple:app %+ require-authorization-simple:app
inbound-request inbound-request
%- js-response:gen %. %- as-octs:mimes:html
(as-octt:mimes:html "window.ship = '{+:(scow %p our.bowl)}';") (rap 3 'window.ship = "' (rsh 3 (scot %p our.bowl)) '";' ~)
%* . js-response:gen
cache %.n
==
:: ::
=/ [payload=simple-payload:http public=?] (get-file req-line is-file) =/ [payload=simple-payload:http public=?] (get-file req-line is-file)
?: public payload ?: public payload
@ -222,6 +225,7 @@
[~ %js] (js-response:gen file) [~ %js] (js-response:gen file)
[~ %css] (css-response:gen file) [~ %css] (css-response:gen file)
[~ %png] (png-response:gen file) [~ %png] (png-response:gen file)
[~ %ico] (ico-response:gen file)
:: ::
[~ %html] [~ %html]
%. file %. file
@ -238,11 +242,9 @@
[not-found:gen %.n] [not-found:gen %.n]
:_ public.u.content :_ public.u.content
=/ mime-type=@t (rsh 3 (crip <p.u.data>)) =/ mime-type=@t (rsh 3 (crip <p.u.data>))
:: Should maybe inspect to see how long cache should hold
::
=/ headers =/ headers
:~ content-type+mime-type :~ content-type+mime-type
max-1-da:gen max-1-wk:gen
'service-worker-allowed'^'/' 'service-worker-allowed'^'/'
== ==
[[200 headers] `q.u.data] [[200 headers] `q.u.data]

View File

@ -5,7 +5,7 @@
/- glob /- glob
/+ default-agent, verb, dbug /+ default-agent, verb, dbug
|% |%
++ hash 0v7.dmf2h.884m6.b2p1b.l1and.uv1lv ++ hash 0v4.vrvkt.4gcnm.dgg5o.e73d6.kqnaq
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))] +$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states +$ all-states
$% state-0 $% state-0

View File

@ -1,6 +1,6 @@
/- *group, metadata=metadata-store /- *group, metadata=metadata-store
/+ store=graph-store, mdl=metadata, res=resource, graph, group, default-agent, /+ store=graph-store, mdl=metadata, res=resource, graph, group, default-agent,
dbug, verb, push-hook dbug, verb, push-hook, agentio
:: ::
~% %graph-push-hook-top ..part ~ ~% %graph-push-hook-top ..part ~
|% |%
@ -25,6 +25,25 @@
$% state-zero $% state-zero
state-one state-one
== ==
::
:: TODO: come back to this and potentially use send a %t
:: to be notified of validator changes
+$ cache
$: graph-to-mark=(map resource:res (unit mark))
perm-marks=(map [mark @tas] tube:clay)
transform-marks=(map mark tube:clay)
==
::
+$ inflated-state
$: state-one
cache
==
::
+$ cache-action
$% [%graph-to-mark (pair resource:res (unit mark))]
[%perm-marks (pair (pair mark @tas) tube:clay)]
[%transform-marks (pair mark tube:clay)]
==
-- --
:: ::
%- agent:dbug %- agent:dbug
@ -33,7 +52,8 @@
%- (agent:push-hook config) %- (agent:push-hook config)
^- agent ^- agent
=- =-
=| state-one ~% %graph-push-hook-agent ..scry.hook-core ~
=| inflated-state
=* state - =* state -
|_ =bowl:gall |_ =bowl:gall
+* this . +* this .
@ -41,10 +61,11 @@
grp ~(. group bowl) grp ~(. group bowl)
gra ~(. graph bowl) gra ~(. graph bowl)
met ~(. mdl bowl) met ~(. mdl bowl)
hc ~(. hook-core bowl) hc ~(. hook-core bowl +.state)
io ~(. agentio bowl)
:: ::
++ on-init on-init:def ++ on-init on-init:def
++ on-save !>(state) ++ on-save !>(-.state)
++ on-load ++ on-load
|= =vase |= =vase
=+ !<(old=versioned-state vase) =+ !<(old=versioned-state vase)
@ -53,9 +74,26 @@
=? old ?=(%0 -.old) =? old ?=(%0 -.old)
[%1 ~] [%1 ~]
?> ?=(%1 -.old) ?> ?=(%1 -.old)
`this(state old) `this(-.state old, +.state *cache)
::
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?. =(mark %graph-cache-hook)
[~ this]
=/ a=cache-action !<(cache-action vase)
=* c +.state
=* graph-to-mark graph-to-mark.c
=* perm-marks perm-marks.c
=* transform-marks transform-marks.c
=. c
?- -.a
%graph-to-mark c(graph-to-mark (~(put by graph-to-mark) p.a q.a))
%perm-marks c(perm-marks (~(put by perm-marks) p.a q.a))
%transform-marks c(transform-marks (~(put by transform-marks) p.a q.a))
==
[~ this(+.state c)]
:: ::
++ on-poke on-poke:def
++ on-agent on-agent:def ++ on-agent on-agent:def
++ on-watch on-watch:def ++ on-watch on-watch:def
++ on-leave on-leave:def ++ on-leave on-leave:def
@ -72,22 +110,39 @@
:: ::
++ on-fail on-fail:def ++ on-fail on-fail:def
++ transform-proxy-update ++ transform-proxy-update
~/ %transform-proxy-update
|= vas=vase |= vas=vase
^- (unit vase) ^- (quip card (unit vase))
=/ =update:store !<(update:store vas) =/ =update:store !<(update:store vas)
=* rid resource.q.update =* rid resource.q.update
=. p.update now.bowl =. p.update now.bowl
?- -.q.update ?- -.q.update
%add-nodes %add-nodes
?. (is-allowed-add:hc rid nodes.q.update) =| cards=(list card)
~ =^ allowed cards (is-allowed-add:hc rid nodes.q.update)
=/ mark (get-mark:gra rid) ?. allowed
?~ mark `vas [cards ~]
|^ =/ mark-cached (~(has by graph-to-mark) rid)
=/ mark
?: mark-cached
(~(got by graph-to-mark) rid)
(get-mark:gra rid)
?~ mark
[cards `vas]
=< $
~% %transform-add-nodes ..transform-proxy-update ~
|%
++ $
^- (quip card (unit vase))
=/ transform-cached (~(has by transform-marks) u.mark)
=/ =tube:clay
?: transform-cached
(~(got by transform-marks) u.mark)
.^(tube:clay (scry:hc %cc %home /[u.mark]/transform-add-nodes))
=/ transform =/ transform
!< $-([index:store post:store atom ?] [index:store post:store]) !< $-([index:store post:store atom ?] [index:store post:store])
%. !>(*indexed-post:store) %. !>(*indexed-post:store)
.^(tube:clay (scry:hc %cc %home /[u.mark]/transform-add-nodes)) tube
=/ [* result=(list [index:store node:store])] =/ [* result=(list [index:store node:store])]
%+ roll %+ roll
(flatten-node-map ~(tap by nodes.q.update)) (flatten-node-map ~(tap by nodes.q.update))
@ -95,9 +150,24 @@
=. nodes.q.update =. nodes.q.update
%- ~(gas by *(map index:store node:store)) %- ~(gas by *(map index:store node:store))
result result
[~ !>(update)] :_ [~ !>(update)]
%+ weld cards
%- zing
:~ ?: mark-cached ~
:_ ~
%+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action
[%graph-to-mark rid mark]
::
?: transform-cached ~
:_ ~
%+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action
[%transform-marks u.mark tube]
==
:: ::
++ flatten-node-map ++ flatten-node-map
~/ %flatten-node-map
|= lis=(list [index:store node:store]) |= lis=(list [index:store node:store])
^- (list [index:store node:store]) ^- (list [index:store node:store])
|^ |^
@ -129,6 +199,7 @@
-- --
:: ::
++ transform-list ++ transform-list
~/ %transform-list
|= transform=$-([index:store post:store atom ?] [index:store post:store]) |= transform=$-([index:store post:store atom ?] [index:store post:store])
|= $: [=index:store =node:store] |= $: [=index:store =node:store]
[indices=(set index:store) lis=(list [index:store node:store])] [indices=(set index:store) lis=(list [index:store node:store])]
@ -151,27 +222,32 @@
-- --
:: ::
%remove-posts %remove-posts
?. (is-allowed-remove:hc resource.q.update indices.q.update) =| cards=(list card)
=^ allowed cards
(is-allowed-remove:hc rid indices.q.update)
:- cards
?. allowed
~ ~
`vas `vas
:: ::
%add-graph ~ %add-graph [~ ~]
%remove-graph ~ %remove-graph [~ ~]
%add-signatures ~ %add-signatures [~ ~]
%remove-signatures ~ %remove-signatures [~ ~]
%archive-graph ~ %archive-graph [~ ~]
%unarchive-graph ~ %unarchive-graph [~ ~]
%add-tag ~ %add-tag [~ ~]
%remove-tag ~ %remove-tag [~ ~]
%keys ~ %keys [~ ~]
%tags ~ %tags [~ ~]
%tag-queries ~ %tag-queries [~ ~]
%run-updates ~ %run-updates [~ ~]
== ==
:: ::
++ resource-for-update resource-for-update:gra ++ resource-for-update resource-for-update:gra
:: ::
++ initial-watch ++ initial-watch
~/ %initial-watch
|= [=path =resource:res] |= [=path =resource:res]
^- vase ^- vase
|^ |^
@ -211,11 +287,13 @@
== ==
-- --
:: ::
^| ^= hook-core ~% %graph-push-hook-helper ..card.hook-core ~
|_ =bowl:gall ^= hook-core
|_ [=bowl:gall =cache]
+* grp ~(. group bowl) +* grp ~(. group bowl)
met ~(. mdl bowl) met ~(. mdl bowl)
gra ~(. graph bowl) gra ~(. graph bowl)
io ~(. agentio bowl)
:: ::
++ scry ++ scry
|= [care=@t desk=@t =path] |= [care=@t desk=@t =path]
@ -225,14 +303,38 @@
:: ::
++ perm-mark ++ perm-mark
|= [=resource:res perm=@t vip=vip-metadata:metadata =indexed-post:store] |= [=resource:res perm=@t vip=vip-metadata:metadata =indexed-post:store]
^- permissions:store ^- [permissions:store (list card)]
|^ |^
=- (check vip) =/ mark-cached (~(has by graph-to-mark.cache) resource)
!< check=$-(vip-metadata:metadata permissions:store) =/ mark
%. !>(indexed-post) ?: mark-cached
=/ mark (get-mark:gra resource) (~(got by graph-to-mark.cache) resource)
?~ mark |=(=vase !>([%no %no %no])) (get-mark:gra resource)
?~ mark
[[%no %no %no] ~]
=/ key [u.mark (perm-mark-name perm)]
=/ perms-cached (~(has by perm-marks.cache) key)
=/ =tube:clay
?: perms-cached
(~(got by perm-marks.cache) key)
.^(tube:clay (scry %cc %home /[u.mark]/(perm-mark-name perm))) .^(tube:clay (scry %cc %home /[u.mark]/(perm-mark-name perm)))
=/ check
!< $-(vip-metadata:metadata permissions:store)
(tube !>(indexed-post))
:- (check vip)
%- zing
:~ ?: mark-cached ~
:_ ~
%+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action
[%graph-to-mark resource mark]
::
?: perms-cached ~
:_ ~
%+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action
[%perm-marks [u.mark (perm-mark-name perm)] tube]
==
:: ::
++ perm-mark-name ++ perm-mark-name
|= perm=@t |= perm=@t
@ -252,15 +354,22 @@
reader.permissions reader.permissions
:: ::
++ get-roles-writers-variation ++ get-roles-writers-variation
~/ %get-roles-writers-variation
|= =resource:res |= =resource:res
^- (unit [is-admin=? writers=(set ship) vip=vip-metadata:metadata]) ^- (unit [is-admin=? writers=(set ship) vip=vip-metadata:metadata])
=/ assoc=(unit association:metadata) =/ assoc=(unit association:metadata)
(peek-association:met %graph resource) (peek-association:met %graph resource)
?~ assoc ~ ?~ assoc ~
=/ group=(unit group:grp)
(scry-group:grp group.u.assoc)
?~ group ~
=/ role=(unit (unit role-tag)) =/ role=(unit (unit role-tag))
(role-for-ship:grp group.u.assoc src.bowl) (role-for-ship-with-group:grp u.group group.u.assoc src.bowl)
=/ writers=(set ship) =/ writers=(set ship)
(get-tagged-ships:grp group.u.assoc [%graph resource %writers]) %^ get-tagged-ships-with-group:grp
u.group
group.u.assoc
[%graph resource %writers]
?~ role ~ ?~ role ~
=/ is-admin=? =/ is-admin=?
?=(?([~ %admin] [~ %moderator]) u.role) ?=(?([~ %admin] [~ %moderator]) u.role)
@ -274,28 +383,47 @@
[(snag (dec (lent index)) index) p.post.node] [(snag (dec (lent index)) index) p.post.node]
:: ::
++ is-allowed-add ++ is-allowed-add
~/ %is-allowed-add
|= [=resource:res nodes=(map index:store node:store)] |= [=resource:res nodes=(map index:store node:store)]
^- ? ^- [? (list card)]
|^ |^
%- (bond |.(%.n)) %- (bond |.([%.n ~]))
%+ biff (get-roles-writers-variation resource) %+ biff (get-roles-writers-variation resource)
|= [is-admin=? writers=(set ship) vip=vip-metadata:metadata] |= [is-admin=? writers=(set ship) vip=vip-metadata:metadata]
^- (unit ?) ^- (unit [? (list card)])
%- some %- some
%+ levy ~(tap by nodes) =/ a ~(tap by nodes)
|= [=index:store =node:store] =| cards=(list card)
|- ^- [? (list card)]
?~ a [& cards]
=/ c (check i.a is-admin writers vip)
?. -.c
[| (weld cards +.c)]
$(a t.a, cards (weld cards +.c))
::
++ check
|= $: [=index:store =node:store]
is-admin=?
writers=(set ship)
vip=vip-metadata:metadata
==
^- [? (list card)]
=/ parent-index=index:store =/ parent-index=index:store
(scag (dec (lent index)) index) (scag (dec (lent index)) index)
?: (~(has by nodes) parent-index) %.y ?: (~(has by nodes) parent-index)
[%.y ~]
?: ?=(%| -.post.node) ?: ?=(%| -.post.node)
%.n [%.n ~]
?. =(author.p.post.node src.bowl) ?. =(author.p.post.node src.bowl)
%.n [%.n ~]
=/ =permissions:store =/ added
%^ add-mark resource vip %^ add-mark resource vip
(node-to-indexed-post node) (node-to-indexed-post node)
=* permissions -.added
=* cards +.added
=/ =permission-level:store =/ =permission-level:store
(get-permission permissions is-admin writers) (get-permission permissions is-admin writers)
:_ cards
?- permission-level ?- permission-level
%yes %.y %yes %.y
%no %.n %no %.n
@ -314,24 +442,38 @@
-- --
:: ::
++ is-allowed-remove ++ is-allowed-remove
~/ %is-allowed-remove
|= [=resource:res indices=(set index:store)] |= [=resource:res indices=(set index:store)]
^- ? ^- [? (list card)]
|^ |^
%- (bond |.(%.n)) %- (bond |.([%.n ~]))
%+ biff (get-roles-writers-variation resource) %+ biff (get-roles-writers-variation resource)
|= [is-admin=? writers=(set ship) vip=vip-metadata:metadata] |= [is-admin=? writers=(set ship) vip=vip-metadata:metadata]
%- some %- some
%+ levy ~(tap by indices) =/ a ~(tap by indices)
|= =index:store =| cards=(list card)
^- ? |- ^- [? (list card)]
?~ a [& cards]
=/ c (check i.a is-admin writers vip)
?. -.c
[| (weld cards +.c)]
$(a t.a, cards (weld cards +.c))
::
++ check
|= [=index:store is-admin=? writers=(set ship) vip=vip-metadata:metadata]
^- [? (list card)]
=/ =node:store =/ =node:store
(got-node:gra resource index) (got-node:gra resource index)
?: ?=(%| -.post.node) %.n ?: ?=(%| -.post.node)
=/ =permissions:store [%.n ~]
=/ removed
%^ remove-mark resource vip %^ remove-mark resource vip
(node-to-indexed-post node) (node-to-indexed-post node)
=* permissions -.removed
=* cards +.removed
=/ =permission-level:store =/ =permission-level:store
(get-permission permissions is-admin writers) (get-permission permissions is-admin writers)
:_ cards
?- permission-level ?- permission-level
%yes %.y %yes %.y
%no %.n %no %.n

View File

@ -10,16 +10,28 @@
[%1 network:zero:store] [%1 network:zero:store]
[%2 network:zero:store] [%2 network:zero:store]
[%3 network:one:store] [%3 network:one:store]
state-4 [%4 network:store]
state-5
== ==
:: ::
+$ state-4 [%4 network:store] +$ state-5 [%5 network:store]
++ orm orm:store ++ orm orm:store
++ orm-log orm-log:store ++ orm-log orm-log:store
+$ debug-input [%validate-graph =resource:store] +$ debug-input [%validate-graph =resource:store]
::
+$ cache
$: validators=(map mark dais:clay)
==
::
:: TODO: come back to this and potentially use ford runes or otherwise
:: send a %t to be notified of validator changes
+$ inflated-state
$: state-5
cache
==
-- --
:: ::
=| state-4 =| inflated-state
=* state - =* state -
:: ::
%- agent:dbug %- agent:dbug
@ -31,7 +43,7 @@
def ~(. (default-agent this %|) bowl) def ~(. (default-agent this %|) bowl)
:: ::
++ on-init [~ this] ++ on-init [~ this]
++ on-save !>(state) ++ on-save !>(-.state)
++ on-load ++ on-load
|= =old=vase |= =old=vase
^- (quip card _this) ^- (quip card _this)
@ -91,7 +103,23 @@
|=(a=* *update-log:store) |=(a=* *update-log:store)
== ==
:: ::
%4 [cards this(state old)] %4
%_ $
-.old %5
::
update-logs.old
%- ~(gas by *update-logs:store)
%+ turn ~(tap by graphs.old)
|= [=resource:store =graph:store mar=(unit mark)]
:- resource
=/ log (~(got by update-logs.old) resource)
?. =(~ log) log
=/ =logged-update:store
[now.bowl %add-graph resource graph mar %.y]
(gas:orm-log ~ [now.bowl logged-update] ~)
==
::
%5 [cards this(-.state old, +.state *cache)]
== ==
:: ::
++ on-watch ++ on-watch
@ -163,7 +191,9 @@
!(~(has by graphs) resource) !(~(has by graphs) resource)
== == == ==
~| "validation of graph {<resource>} failed using mark {<mark>}" ~| "validation of graph {<resource>} failed using mark {<mark>}"
?> (validate-graph graph mark) =^ is-valid state
(validate-graph graph mark)
?> is-valid
=/ =logged-update:store =/ =logged-update:store
[time %add-graph resource graph mark overwrite] [time %add-graph resource graph mark overwrite]
=/ =update-log:store =/ =update-log:store
@ -200,6 +230,10 @@
(~(got by graphs) resource) (~(got by graphs) resource)
~| "cannot add duplicate nodes to {<resource>}" ~| "cannot add duplicate nodes to {<resource>}"
?< (check-for-duplicates graph ~(key by nodes)) ?< (check-for-duplicates graph ~(key by nodes))
~| "validation of nodes failed using mark {<mark>}"
=^ is-valid state
(check-validity ~(tap by nodes) mark)
?> is-valid
=/ =update-log:store (~(got by update-logs) resource) =/ =update-log:store (~(got by update-logs) resource)
=. update-log =. update-log
(put:orm-log update-log time [time [%add-nodes resource nodes]]) (put:orm-log update-log time [time [%add-nodes resource nodes]])
@ -214,6 +248,17 @@
(add-node-list resource graph mark (sort-nodes nodes)) (add-node-list resource graph mark (sort-nodes nodes))
== ==
:: ::
++ check-validity
|= [lis=(list (pair index:store node:store)) mark=(unit ^mark)]
^- [? _state]
|-
?~ lis [& state]
=^ is-valid state
(validate-graph (gas:orm ~ [(rear p.i.lis) q.i.lis]~) mark)
?. is-valid
[| state]
$(lis t.lis)
::
++ check-for-duplicates ++ check-for-duplicates
|= [=graph:store nodes=(set index:store)] |= [=graph:store nodes=(set index:store)]
^- ? ^- ?
@ -271,8 +316,6 @@
== ==
^- graph:store ^- graph:store
?< ?=(~ index) ?< ?=(~ index)
~| "validation of node failed using mark {<mark>}"
?> (validate-graph (gas:orm ~ [i.index node]~) mark)
=* atom i.index =* atom i.index
%^ put:orm %^ put:orm
graph graph
@ -571,18 +614,24 @@
^- (quip card _state) ^- (quip card _state)
=/ [=graph:store mark=(unit mark:store)] =/ [=graph:store mark=(unit mark:store)]
(~(got by graphs) resource.debug-input) (~(got by graphs) resource.debug-input)
?> (validate-graph graph mark) =^ is-valid state
(validate-graph graph mark)
?> is-valid
[~ state] [~ state]
:: ::
++ validate-graph ++ validate-graph
|= [=graph:store mark=(unit mark:store)] |= [=graph:store mark=(unit mark:store)]
^- ? ^- [? _state]
?~ mark %.y ?~ mark [%.y state]
=/ has-dais (~(has by validators) u.mark)
=/ =dais:clay =/ =dais:clay
?: has-dais
(~(got by validators) u.mark)
.^ =dais:clay .^ =dais:clay
%cb %cb
/(scot %p our.bowl)/[q.byk.bowl]/(scot %da now.bowl)/[u.mark] /(scot %p our.bowl)/[q.byk.bowl]/(scot %da now.bowl)/[u.mark]
== ==
:_ state(validators (~(put by validators) u.mark dais))
|- ^- ? |- ^- ?
?~ graph %.y ?~ graph %.y
%+ roll (tap:orm graph) %+ roll (tap:orm graph)
@ -600,7 +649,9 @@
++ poke-import ++ poke-import
|= arc=* |= arc=*
^- (quip card _state) ^- (quip card _state)
=^ cards -.state
(import:store arc our.bowl) (import:store arc our.bowl)
[cards state]
-- --
:: ::
++ on-peek ++ on-peek

View File

@ -47,8 +47,9 @@
:: ::
++ transform-proxy-update ++ transform-proxy-update
|= vas=vase |= vas=vase
^- (unit vase) ^- (quip card (unit vase))
=/ =update:store !<(update:store vas) =/ =update:store !<(update:store vas)
:- ~
?: ?=(%initial -.update) ?: ?=(%initial -.update)
~ ~
|^ |^

View File

@ -124,15 +124,15 @@
:: ::
++ poke-noun ++ poke-noun
|= non=* |= non=*
?> ?=(%rewatch-dms non) [~ state]
=/ graphs=(list resource) :: ?> ?=(%rewatch-dms non)
~(tap in get-keys:gra) :: =/ graphs=(list resource)
:- ~ :: ~(tap in get-keys:gra)
%_ state :: %_ state
watching :: watching
%- ~(gas in watching) :: %- ~(gas in watching)
(murn graphs |=(rid=resource ?:((should-watch:ha rid) `[rid ~] ~))) :: (murn graphs |=(rid=resource ?:((should-watch:ha rid) `[rid ~] ~)))
== :: ==
:: ::
++ hark-graph-hook-action ++ hark-graph-hook-action
|= =action:hook |= =action:hook
@ -201,7 +201,9 @@
:: ::
%add-nodes %add-nodes
=* rid resource.q.update =* rid resource.q.update
(check-nodes ~(val by nodes.q.update) rid) =/ assoc=(unit association:metadata)
(peek-association:met %graph rid)
(check-nodes ~(val by nodes.q.update) rid assoc)
== ==
:: this is awful, but notification kind should always switch :: this is awful, but notification kind should always switch
:: on the index, so hopefully doesn't matter :: on the index, so hopefully doesn't matter
@ -255,9 +257,11 @@
(get-graph-mop:gra rid) (get-graph-mop:gra rid)
=/ node=(unit node:graph-store) =/ node=(unit node:graph-store)
(bind (peek:orm:graph-store graph) |=([@ =node:graph-store] node)) (bind (peek:orm:graph-store graph) |=([@ =node:graph-store] node))
=/ assoc=(unit association:metadata)
(peek-association:met %graph rid)
=^ cards state =^ cards state
(check-nodes (drop node) rid) (check-nodes (drop node) rid assoc)
?. (should-watch:ha rid) ?. (should-watch:ha rid assoc)
[cards state] [cards state]
:_ state(watching (~(put in watching) [rid ~])) :_ state(watching (~(put in watching) [rid ~]))
(weld cards (give:ha ~[/updates] %listen [rid ~])) (weld cards (give:ha ~[/updates] %listen [rid ~]))
@ -265,20 +269,18 @@
++ check-nodes ++ check-nodes
|= $: nodes=(list node:graph-store) |= $: nodes=(list node:graph-store)
rid=resource rid=resource
assoc=(unit association:metadata)
== ==
=/ group=(unit resource) ?~ assoc
(peek-group:met %graph rid) ~& no-assoc+rid
?~ group
~& no-group+rid
`state `state
=/ metadatum=(unit metadatum:metadata) =* group group.u.assoc
(peek-metadatum:met %graph rid) =* metadatum metadatum.u.assoc
?~ metadatum `state
=/ module=term =/ module=term
?: ?=(%empty -.config.u.metadatum) %$ ?: ?=(%empty -.config.metadatum) %$
?: ?=(%group -.config.u.metadatum) %$ ?: ?=(%group -.config.metadatum) %$
module.config.u.metadatum module.config.metadatum
abet:check:(abed:handle-update:ha rid nodes u.group module) abet:check:(abed:handle-update:ha rid nodes group module)
-- --
:: ::
++ on-peek on-peek:def ++ on-peek on-peek:def
@ -340,12 +342,11 @@
$(contents t.contents) $(contents t.contents)
:: ::
++ should-watch ++ should-watch
|= rid=resource |= [rid=resource assoc=(unit association:metadata)]
^- ? ^- ?
=/ group-rid=(unit resource) ?~ assoc
(peek-group:met %graph rid) %.n
?~ group-rid %.n ?| !(is-managed:grp group.u.assoc)
?| !(is-managed:grp u.group-rid)
&(watch-on-self =(our.bowl entity.rid)) &(watch-on-self =(our.bowl entity.rid))
== ==
:: ::
@ -364,7 +365,9 @@
update-core(rid r, updates upds, group grp, module mod) update-core(rid r, updates upds, group grp, module mod)
:: ::
++ get-conversion ++ get-conversion
(^get-conversion rid) :: LA: this tube should be cached in %hark-graph-hook state
:: instead of just trying to keep it warm, as the scry overhead is large
~+ (^get-conversion rid)
:: ::
++ abet ++ abet
^- (quip card _state) ^- (quip card _state)
@ -418,7 +421,8 @@
update-core update-core
=* pos p.post.node =* pos p.post.node
=+ !< notif-kind=(unit notif-kind:hook) =+ !< notif-kind=(unit notif-kind:hook)
(get-conversion !>([0 pos])) %- get-conversion
!>(`indexed-post:graph-store`[0 pos])
?~ notif-kind ?~ notif-kind
update-core update-core
=/ desc=@t =/ desc=@t

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.eb051d816171fb1d0caf.js"></script> <script src="/~landscape/js/bundle/index.f252a9afb6e952de19c9.js"></script>
</body> </body>
</html> </html>

View File

@ -194,6 +194,11 @@
[%x %tiles ~] ``noun+!>([tiles tile-ordering]) [%x %tiles ~] ``noun+!>([tiles tile-ordering])
[%x %first-time ~] ``noun+!>(first-time) [%x %first-time ~] ``noun+!>(first-time)
[%x %keys ~] ``noun+!>(~(key by tiles)) [%x %keys ~] ``noun+!>(~(key by tiles))
::
[%x %runtime-lag ~]
:^ ~ ~ %json
!> ^- json
b+.^(? //(scot %p our.bowl)//(scot %da now.bowl)/zen/lag)
== ==
:: ::
++ on-arvo ++ on-arvo

View File

@ -59,8 +59,9 @@
:: ::
++ transform-proxy-update ++ transform-proxy-update
|= vas=vase |= vas=vase
^- (unit vase) ^- (quip card (unit vase))
=/ =update:store !<(update:store vas) =/ =update:store !<(update:store vas)
:- ~
?. ?=(?(%add %remove) -.update) ?. ?=(?(%add %remove) -.update)
~ ~
=/ role=(unit (unit role-tag)) =/ role=(unit (unit role-tag))

View File

@ -3,15 +3,14 @@
:::: /hoon/code/hood/gen :::: /hoon/code/hood/gen
:: ::
/? 310 /? 310
:: /- *sole
:::: /+ *generators
:: :- %ask
:- %say
|= $: [now=@da eny=@uvJ bec=beak] |= $: [now=@da eny=@uvJ bec=beak]
[arg=?(~ [%reset ~]) ~] [arg=?(~ [%reset ~]) ~]
== ==
=* our p.bec =* our p.bec
:- %helm-code ^- (sole-result [%helm-code ?(~ %reset)])
?~ arg ?~ arg
=/ code=tape =/ code=tape
%+ slag 1 %+ slag 1
@ -20,11 +19,23 @@
=/ step=tape =/ step=tape
%+ scow %ud %+ scow %ud
.^(@ud %j /(scot %p our)/step/(scot %da now)/(scot %p our)) .^(@ud %j /(scot %p our)/step/(scot %da now)/(scot %p our))
%- %- slog ::
:~ [%leaf code] %+ print 'use |code %reset to invalidate this and generate a new code'
[%leaf (weld "current step=" step)] %+ print leaf+(weld "current step=" step)
[%leaf "use |code %reset to invalidate this and generate a new code"] %+ print leaf+code
== (produce [%helm-code ~])
~ ::
?> =(%reset -.arg) ?> =(%reset -.arg)
%reset %+ print 'continue?'
%+ print 'warning: resetting your code closes all web sessions'
%+ prompt
[%& %project "y/n: "]
%+ parse
;~ pose
(cold %.y (mask "yY"))
(cold %.n (mask "nN"))
==
|= reset=?
?. reset
no-product
(produce [%helm-code %reset])

View File

@ -50,18 +50,17 @@
== ==
:: ::
++ index ++ index
|= i=^index |= ind=^index
^- json ^- json
?: =(~ i) s+'/' :- %s
=/ j=^tape "" ?: =(~ ind)
|- '/'
?~ i [%s (crip j)] %+ roll ind
=/ k=json (numb i.i) |= [cur=@ acc=@t]
?> ?=(%n -.k) ^- @t
%_ $ =/ num (numb cur)
i t.i ?> ?=(%n -.num)
j (weld j (weld "/" (trip +.k))) (rap 3 acc '/' p.num ~)
==
:: ::
++ uid ++ uid
|= u=^uid |= u=^uid
@ -721,9 +720,9 @@
-- --
++ import ++ import
|= [arc=* our=ship] |= [arc=* our=ship]
^- (quip card:agent:gall [%4 network]) ^- (quip card:agent:gall [%5 network])
|^ |^
=/ sty [%4 (remake-network ;;(tree-network +.arc))] =/ sty [%5 (remake-network ;;(tree-network +.arc))]
:_ sty :_ sty
%+ turn ~(tap by graphs.sty) %+ turn ~(tap by graphs.sty)
|= [rid=resource =marked-graph] |= [rid=resource =marked-graph]

View File

@ -76,6 +76,7 @@
++ get-graph ++ get-graph
|= res=resource |= res=resource
^- update:store ^- update:store
=- -(p *time)
%+ scry-for update:store %+ scry-for update:store
/graph/(scot %p entity.res)/[name.res] /graph/(scot %p entity.res)/[name.res]
:: ::

View File

@ -75,7 +75,12 @@
=/ grp=(unit group) =/ grp=(unit group)
(scry-group rid) (scry-group rid)
?~ grp ~ ?~ grp ~
=* group u.grp (role-for-ship-with-group u.grp rid ship)
::
++ role-for-ship-with-group
|= [grp=group rid=resource =ship]
^- (unit (unit role-tag))
=* group grp
=* policy policy.group =* policy policy.group
=* tags tags.group =* tags tags.group
=/ admins=(set ^ship) =/ admins=(set ^ship)
@ -107,7 +112,12 @@
=/ grp=(unit group) =/ grp=(unit group)
(scry-group rid) (scry-group rid)
?~ grp ~ ?~ grp ~
(~(get ju tags.u.grp) tag) (get-tagged-ships-with-group u.grp rid tag)
::
++ get-tagged-ships-with-group
|= [grp=group rid=resource =tag]
^- (set ship)
(~(get ju tags.grp) tag)
:: ::
++ is-managed ++ is-managed
|= rid=resource |= rid=resource

View File

@ -23,12 +23,12 @@
%+ turn ~(tap by associations) %+ turn ~(tap by associations)
|= [=md-resource [group=resource =^metadatum]] |= [=md-resource [group=resource =^metadatum]]
^- [cord json] ^- [cord json]
:- :- %: rap 3
%- crip (spat (en-path:resource group))
;: weld '/'
(trip (spat (en-path:resource group))) app-name.md-resource
(weld "/" (trip app-name.md-resource)) (spat (en-path:resource resource.md-resource))
(trip (spat (en-path:resource resource.md-resource))) ~
== ==
%- pairs %- pairs
:~ [%group s+(enjs-path:resource group)] :~ [%group s+(enjs-path:resource group)]

View File

@ -114,6 +114,14 @@
state-3 state-3
state-4 state-4
== ==
:: +diplomatic: only renegotiate if versions changed
::
:: If %.n please leave note as to why renegotiation necessary
::
::
++ diplomatic
^- ?
%.y
:: ::
++ default ++ default
|* [pull-hook=* =config] |* [pull-hook=* =config]
@ -239,6 +247,7 @@
=/ kick=(list card) =/ kick=(list card)
?: ?& =(min-version.config prev-min-version.old) ?: ?& =(min-version.config prev-min-version.old)
=(version.config prev-version.old) =(version.config prev-version.old)
diplomatic
== ==
~ ~
(poke-self:pass kick+!>(%kick))^~ (poke-self:pass kick+!>(%kick))^~
@ -439,6 +448,7 @@
?~ tan tr-core ?~ tan tr-core
?. versioned ?. versioned
(tr-ap-og:tr-cleanup |.((on-pull-nack:og rid u.tan))) (tr-ap-og:tr-cleanup |.((on-pull-nack:og rid u.tan)))
%- (slog leaf+"versioned nack for {<rid>} in {<dap.bowl>}" u.tan)
=/ pax =/ pax
(kick-mule:virt rid |.((on-pull-kick:og rid))) (kick-mule:virt rid |.((on-pull-kick:og rid)))
?~ pax tr-failed-kick ?~ pax tr-failed-kick
@ -463,18 +473,18 @@
:: subscription :: subscription
tr-core tr-core
(tr-suspend-pub-ver min-version.config) (tr-suspend-pub-ver min-version.config)
=/ =vase =/ =^cage
(convert-to:ver cage) (convert-to:ver cage)
=/ =wire =/ =wire
(make-wire /store) (make-wire /store)
=+ resources=(~(gas in *(set resource)) (resource-for-update:og vase)) =+ resources=(~(gas in *(set resource)) (resource-for-update:og q.cage))
?> ?| no-validate.config ?> ?| no-validate.config
?& (check-src resources) ?& (check-src resources)
(~(has in resources) rid) (~(has in resources) rid)
== == == ==
=/ =mark =/ =mark
(append-version:ver version.config) (append-version:ver version.config)
(tr-emit (~(poke-our pass wire) store-name.config mark vase)) (tr-emit (~(poke-our pass wire) store-name.config cage))
-- --
:: ::
++ tr-kick ++ tr-kick

View File

@ -26,6 +26,7 @@
:: ::
/- *push-hook /- *push-hook
/+ default-agent, resource, verb, versioning, agentio /+ default-agent, resource, verb, versioning, agentio
~% %push-hook-top ..part ~
|% |%
+$ card card:agent:gall +$ card card:agent:gall
:: ::
@ -73,7 +74,16 @@
state-1 state-1
state-2 state-2
== ==
:: +diplomatic: only renegotiate if versions changed
::
:: If %.n please leave note as to why renegotiation necessary
::
++ diplomatic
^- ?
%.y
::
++ push-hook ++ push-hook
~/ %push-hook
|* =config |* =config
$_ ^| $_ ^|
|_ bowl:gall |_ bowl:gall
@ -103,7 +113,7 @@
:: ::
++ transform-proxy-update ++ transform-proxy-update
|~ vase |~ vase
*(unit vase) *[(list card) (unit vase)]
:: +initial-watch: produce initial state for a subscription :: +initial-watch: produce initial state for a subscription
:: ::
:: .resource is the resource being subscribed to. :: .resource is the resource being subscribed to.
@ -165,6 +175,7 @@
=* state - =* state -
^- agent:gall ^- agent:gall
=< =<
~% %push-agent-lib ..poke-hook-action ~
|_ =bowl:gall |_ =bowl:gall
+* this . +* this .
og ~(. push-hook bowl) og ~(. push-hook bowl)
@ -221,6 +232,7 @@
|= [prev-min-version=@ud prev-version=@ud] |= [prev-min-version=@ud prev-version=@ud]
?: ?& =(min-version.config prev-min-version) ?: ?& =(min-version.config prev-min-version)
=(prev-version version.config) =(prev-version version.config)
diplomatic
== ==
:: bail on kick if we didn't change versions :: bail on kick if we didn't change versions
~ ~
@ -256,6 +268,7 @@
!>(state) !>(state)
:: ::
++ on-poke ++ on-poke
~/ %on-poke
|= [=mark =vase] |= [=mark =vase]
^- (quip card:agent:gall agent:gall) ^- (quip card:agent:gall agent:gall)
?: =(mark %push-hook-action) ?: =(mark %push-hook-action)
@ -272,6 +285,7 @@
[cards this] [cards this]
:: ::
++ on-watch ++ on-watch
~/ %on-watch
|= =path |= =path
^- (quip card:agent:gall agent:gall) ^- (quip card:agent:gall agent:gall)
?: ?=([%version ~] path) ?: ?=([%version ~] path)
@ -291,26 +305,25 @@
?. (supported:ver mark) ?. (supported:ver mark)
:_ this :_ this
(fact-init-kick:io version+!>(min-version.config)) (fact-init-kick:io version+!>(min-version.config))
=/ =vase
(convert-to:ver mark (initial-watch:og t.t.t.t.t.t.path resource))
:_ this :_ this
[%give %fact ~ mark vase]~ =- [%give %fact ~ -]~
(convert-to:ver mark (initial-watch:og t.t.t.t.t.t.path resource))
:: ::
++ unversioned ++ unversioned
?> ?=([%ship @ @ *] t.path) ?> ?=([%ship @ @ *] t.path)
?. =(min-version.config 0)
~& >>> "unversioned req from: {<src.bowl>}, nooping"
`this
=/ =resource =/ =resource
(de-path:resource t.path) (de-path:resource t.path)
=/ =vase =/ =vase
%+ convert-to:ver update-mark.config
(initial-watch:og t.t.t.t.path resource) (initial-watch:og t.t.t.t.path resource)
:_ this :_ this
[%give %fact ~ update-mark.config vase]~ ?. =(min-version.config 0)
~& >>> "unversioned req from: {<src.bowl>}, nooping"
~
[%give %fact ~ (convert-to:ver update-mark.config vase)]~
-- --
:: ::
++ on-agent ++ on-agent
~/ %on-agent
|= [=wire =sign:agent:gall] |= [=wire =sign:agent:gall]
^- (quip card:agent:gall agent:gall) ^- (quip card:agent:gall agent:gall)
?. ?=([%helper %push-hook @ *] wire) ?. ?=([%helper %push-hook @ *] wire)
@ -364,6 +377,7 @@
[%x %min-version ~] ``version+!>(version.config) [%x %min-version ~] ``version+!>(version.config)
== ==
-- --
~% %push-helper-lib ..card ~
|_ =bowl:gall |_ =bowl:gall
+* og ~(. push-hook bowl) +* og ~(. push-hook bowl)
ver ~(. versioning [bowl [update-mark version min-version]:config]) ver ~(. versioning [bowl [update-mark version min-version]:config])
@ -371,6 +385,7 @@
pass pass:io pass pass:io
:: ::
++ poke-hook-action ++ poke-hook-action
~/ %poke-hook-action
|= =action |= =action
^- (quip card:agent:gall _state) ^- (quip card:agent:gall _state)
|^ |^
@ -439,6 +454,7 @@
[%pass wire %agent [our.bowl store-name.config] %watch store-path.config] [%pass wire %agent [our.bowl store-name.config] %watch store-path.config]
:: ::
++ push-updates ++ push-updates
~/ %push-updates
|= =cage |= =cage
^- (list card:agent:gall) ^- (list card:agent:gall)
%+ roll (resource-for-update q.cage) %+ roll (resource-for-update q.cage)
@ -461,10 +477,7 @@
|= [fact-ver=@ud paths=(set path)] |= [fact-ver=@ud paths=(set path)]
=/ =mark =/ =mark
(append-version:ver fact-ver) (append-version:ver fact-ver)
=/ =^cage (fact:io (convert-from:ver mark q.cage) ~(tap in paths))
:- mark
(convert-from:ver mark q.cage)
(fact:io cage ~(tap in paths))
:: TODO: deprecate :: TODO: deprecate
++ unversioned ++ unversioned
?. =(min-version.config 0) ~ ?. =(min-version.config 0) ~
@ -474,39 +487,39 @@
%- ~(gas in *(set path)) %- ~(gas in *(set path))
(turn (incoming-subscriptions prefix) tail) (turn (incoming-subscriptions prefix) tail)
?: =(0 ~(wyt in unversioned)) ~ ?: =(0 ~(wyt in unversioned)) ~
=/ =^cage (fact:io (convert-from:ver update-mark.config q.cage) ~(tap in unversioned))^~
:- update-mark.config
(convert-from:ver update-mark.config q.cage)
(fact:io cage ~(tap in unversioned))^~
-- --
:: ::
++ forward-update ++ forward-update
~/ %forward-update
|= =cage |= =cage
^- (list card:agent:gall) ^- (list card:agent:gall)
=- lis =- lis
=/ vas =/ vas=vase
(convert-to:ver cage) q:(convert-to:ver cage)
%+ roll (resource-for-update q.cage) %+ roll (resource-for-update q.cage)
|= [rid=resource [lis=(list card:agent:gall) tf-vas=(unit vase)]] |= [rid=resource [lis=(list card:agent:gall) tf-vas=(unit vase)]]
^- [(list card:agent:gall) (unit vase)] ^- [(list card:agent:gall) (unit vase)]
=/ =path =/ =path
resource+(en-path:resource rid) resource+(en-path:resource rid)
=/ =wire (make-wire path)
=* ship entity.rid =* ship entity.rid
=. tf-vas =/ out=(pair (list card:agent:gall) (unit vase))
?. =(our.bowl ship) ?. =(our.bowl ship)
:: do not transform before forwarding :: do not transform before forwarding
:: ::
`vas ``vas
:: use cached transform :: use cached transform
:: ::
?^ tf-vas tf-vas ?^ tf-vas `tf-vas
:: transform before poking store :: transform before poking store
:: ::
(transform-proxy-update:og vas) (transform-proxy-update:og vas)
~| "forwarding failed during transform. mark: {<p.cage>} resource: {<rid>}" ~| "forwarding failed during transform. mark: {<p.cage>} rid: {<rid>}"
?> ?=(^ tf-vas) ?> ?=(^ q.out)
=/ =dock :_ q.out
:_ (weld lis p.out)
=/ =wire (make-wire path)
=- [%pass wire %agent - %poke [current-version:ver u.q.out]]
:- ship :- ship
?. =(our.bowl ship) ?. =(our.bowl ship)
:: forward to host :: forward to host
@ -515,11 +528,6 @@
:: poke our store :: poke our store
:: ::
store-name.config store-name.config
=/ cag=^cage
:- current-version:ver
u.tf-vas
:_ tf-vas
[[%pass wire %agent dock %poke cag] lis]
:: ::
++ ver-from-path ++ ver-from-path
|= =path |= =path
@ -529,6 +537,7 @@
(slav %ud i.extra) (slav %ud i.extra)
:: ::
++ resource-for-update ++ resource-for-update
~/ %resource-for-update
|= =vase |= =vase
^- (list resource) ^- (list resource)
%~ tap in %~ tap in

View File

@ -39,10 +39,10 @@
~! +:*handler ~! +:*handler
(handler inbound-request) (handler inbound-request)
:: ::
=/ redirect=cord =- [[307 ['location' -]~] ~]
%- crip %^ cat 3
"/~/login?redirect={(trip url.request.inbound-request)}" '/~/login?redirect='
[[307 ['location' redirect]~] ~] url.request.inbound-request
:: ::
:: +require-authorization-simple: :: +require-authorization-simple:
:: redirect to the login page when unauthenticated :: redirect to the login page when unauthenticated
@ -56,10 +56,10 @@
~! this ~! this
simple-payload simple-payload
:: ::
=/ redirect=cord =- [[307 ['location' -]~] ~]
%- crip %^ cat 3
"/~/login?redirect={(trip url.request.inbound-request)}" '/~/login?redirect='
[[307 ['location' redirect]~] ~] url.request.inbound-request
:: ::
++ give-simple-payload ++ give-simple-payload
|= [eyre-id=@ta =simple-payload:http] |= [eyre-id=@ta =simple-payload:http]
@ -86,36 +86,52 @@
:_ `octs :_ `octs
[200 [['content-type' 'text/html'] ?:(cache [max-1-wk ~] ~)]] [200 [['content-type' 'text/html'] ?:(cache [max-1-wk ~] ~)]]
:: ::
++ js-response
|= =octs
^- simple-payload:http
[[200 [['content-type' 'text/javascript'] max-1-da ~]] `octs]
::
++ json-response
|= =json
^- simple-payload:http
[[200 ['content-type' 'application/json']~] `(json-to-octs json)]
::
++ css-response ++ css-response
=| cache=?
|= =octs |= =octs
^- simple-payload:http ^- simple-payload:http
[[200 [['content-type' 'text/css'] max-1-da ~]] `octs] :_ `octs
[200 [['content-type' 'text/css'] ?:(cache [max-1-wk ~] ~)]]
:: ::
++ manx-response ++ js-response
|= man=manx =| cache=?
|= =octs
^- simple-payload:http ^- simple-payload:http
[[200 ['content-type' 'text/html']~] `(manx-to-octs man)] :_ `octs
[200 [['content-type' 'text/javascript'] ?:(cache [max-1-wk ~] ~)]]
:: ::
++ png-response ++ png-response
=| cache=?
|= =octs |= =octs
^- simple-payload:http ^- simple-payload:http
[[200 [['content-type' 'image/png'] max-1-wk ~]] `octs] :_ `octs
[200 [['content-type' 'image/png'] ?:(cache [max-1-wk ~] ~)]]
::
++ ico-response
|= =octs
^- simple-payload:http
[[200 [['content-type' 'image/x-icon'] max-1-wk ~]] `octs]
:: ::
++ woff2-response ++ woff2-response
=| cache=?
|= =octs |= =octs
^- simple-payload:http ^- simple-payload:http
[[200 [['content-type' 'font/woff2'] max-1-wk ~]] `octs] [[200 [['content-type' 'font/woff2'] max-1-wk ~]] `octs]
:: ::
++ json-response
=| cache=_|
|= =json
^- simple-payload:http
:_ `(json-to-octs json)
[200 [['content-type' 'application/json'] ?:(cache [max-1-da ~] ~)]]
::
++ manx-response
=| cache=_|
|= man=manx
^- simple-payload:http
:_ `(manx-to-octs man)
[200 [['content-type' 'text/html'] ?:(cache [max-1-da ~] ~)]]
::
++ not-found ++ not-found
^- simple-payload:http ^- simple-payload:http
[[404 ~] ~] [[404 ~] ~]
@ -123,10 +139,10 @@
++ login-redirect ++ login-redirect
|= =request:http |= =request:http
^- simple-payload:http ^- simple-payload:http
=/ redirect=cord =- [[307 ['location' -]~] ~]
%- crip %^ cat 3
"/~/login?redirect={(trip url.request)}" '/~/login?redirect='
[[307 ['location' redirect]~] ~] url.request
:: ::
++ redirect ++ redirect
|= redirect=cord |= redirect=cord

View File

@ -29,11 +29,12 @@
&((gte ver min) (lte ver version)) &((gte ver min) (lte ver version))
:: ::
++ convert-to ++ convert-to
|= =cage |= [=mark =vase]
^- vase ^- cage
?: =(p.cage current-version) :- current-version
q.cage ?: =(mark current-version)
((tube-to p.cage) q.cage) vase
((tube-to mark) vase)
:: ::
++ tube-to ++ tube-to
|= =mark |= =mark
@ -44,10 +45,11 @@
.^(tube:clay %cc (scry:io %home /[current-version]/[mark])) .^(tube:clay %cc (scry:io %home /[current-version]/[mark]))
:: ::
++ convert-from ++ convert-from
|= =cage |= [=mark =vase]
^- vase ^- cage
?: =(p.cage current-version) :- mark
q.cage ?: =(mark current-version)
((tube-from p.cage) q.cage) vase
((tube-from mark) vase)
-- --

20
pkg/arvo/mar/graph/cache/hook.hoon vendored Normal file
View File

@ -0,0 +1,20 @@
/- metadata=metadata-store, res=resource
|%
+$ cache-action
$% [%graph-to-mark (pair resource:res (unit mark))]
[%perm-marks (pair (pair mark @tas) tube:clay)]
[%transform-marks (pair mark tube:clay)]
==
--
::
|_ act=cache-action
++ grad %noun
++ grow
|%
++ noun act
--
++ grab
|%
++ noun cache-action
--
--

12
pkg/arvo/mar/ico.hoon Normal file
View File

@ -0,0 +1,12 @@
|_ dat=@
++ grow
|%
++ mime [/image/x-icon (as-octs:mimes:html dat)]
--
++ grab
|%
++ mime |=([p=mite q=octs] q.q)
++ noun @
--
++ grad %mime
--

View File

@ -1,3 +1,3 @@
module.exports = { module.exports = {
extends: "@urbit" extends: '@urbit'
}; };

File diff suppressed because it is too large Load Diff

View File

@ -8,9 +8,10 @@
"@reach/disclosure": "^0.10.5", "@reach/disclosure": "^0.10.5",
"@reach/menu-button": "^0.10.5", "@reach/menu-button": "^0.10.5",
"@reach/tabs": "^0.10.5", "@reach/tabs": "^0.10.5",
"@react-spring/web": "^9.1.1",
"@tlon/indigo-dark": "^1.0.6", "@tlon/indigo-dark": "^1.0.6",
"@tlon/indigo-light": "^1.0.7", "@tlon/indigo-light": "^1.0.7",
"@tlon/indigo-react": "^1.2.21", "@tlon/indigo-react": "^1.2.22",
"@tlon/sigil-js": "^1.4.3", "@tlon/sigil-js": "^1.4.3",
"@urbit/api": "file:../npm/api", "@urbit/api": "file:../npm/api",
"any-ascii": "^0.1.7", "any-ascii": "^0.1.7",
@ -22,8 +23,7 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"formik": "^2.1.5", "formik": "^2.1.5",
"immer": "^8.0.1", "immer": "^8.0.1",
"lodash": "^4.17.20", "lodash": "^4.17.21",
"markdown-to-jsx": "^6.11.4",
"moment": "^2.29.1", "moment": "^2.29.1",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"mousetrap-global-bind": "^1.1.0", "mousetrap-global-bind": "^1.1.0",
@ -38,15 +38,18 @@
"react-markdown": "^4.3.1", "react-markdown": "^4.3.1",
"react-oembed-container": "^1.0.0", "react-oembed-container": "^1.0.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-use-gesture": "^9.1.3",
"react-virtuoso": "^0.20.3", "react-virtuoso": "^0.20.3",
"react-visibility-sensor": "^5.1.1", "react-visibility-sensor": "^5.1.1",
"remark-breaks": "^2.0.1", "remark": "^12.0.0",
"remark-disable-tokenizers": "^1.0.24", "remark-breaks": "^2.0.2",
"remark-disable-tokenizers": "1.1.0",
"stacktrace-js": "^2.0.2", "stacktrace-js": "^2.0.2",
"style-loader": "^1.3.0", "style-loader": "^1.3.0",
"styled-components": "^5.1.1", "styled-components": "^5.1.1",
"styled-system": "^5.1.5", "styled-system": "^5.1.5",
"suncalc": "^1.8.0", "suncalc": "^1.8.0",
"unist-util-visit": "^3.0.0",
"urbit-ob": "^5.0.1", "urbit-ob": "^5.0.1",
"workbox-core": "^6.0.2", "workbox-core": "^6.0.2",
"workbox-precaching": "^6.0.2", "workbox-precaching": "^6.0.2",

View File

@ -1,9 +1,7 @@
import './wdyr';
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom';
import './register-sw'; import './register-sw';
import App from './views/App'; import App from './views/App';
import './wdyr';
ReactDOM.render(<App />, document.getElementById('root')); ReactDOM.render(<App />, document.getElementById('root'));

View File

@ -1,5 +1,5 @@
import { Path, Patp } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import { Patp, Path } from '@urbit/api';
import BaseStore from '../store/base'; import BaseStore from '../store/base';
export default class BaseApi<S extends object = {}> { export default class BaseApi<S extends object = {}> {

View File

@ -1,8 +1,8 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import { Patp } from '@urbit/api'; import { Patp } from '@urbit/api';
import { ContactEdit } from '@urbit/api/contacts'; import { ContactEditField } from '@urbit/api/contacts';
import _ from 'lodash'; import _ from 'lodash';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class ContactsApi extends BaseApi<StoreState> { export default class ContactsApi extends BaseApi<StoreState> {
add(ship: Patp, contact: any) { add(ship: Patp, contact: any) {
@ -14,7 +14,7 @@ export default class ContactsApi extends BaseApi<StoreState> {
return this.storeAction({ remove: { ship } }); return this.storeAction({ remove: { ship } });
} }
edit(ship: Patp, editField: ContactEdit) { edit(ship: Patp, editField: ContactEditField) {
/* editField can be... /* editField can be...
{nickname: ''} {nickname: ''}
{email: ''} {email: ''}
@ -78,17 +78,17 @@ export default class ContactsApi extends BaseApi<StoreState> {
return _.compact( return _.compact(
await Promise.all( await Promise.all(
ships.map( ships.map(
async s => { async (s) => {
const ship = `~${s}`; const ship = `~${s}`;
if(s === window.ship) { if(s === window.ship) {
return null return null;
} }
const allowed = await this.fetchIsAllowed( const allowed = await this.fetchIsAllowed(
`~${window.ship}`, `~${window.ship}`,
'personal', 'personal',
ship, ship,
true true
) );
return allowed ? null : ship; return allowed ? null : ship;
} }
) )

View File

@ -1,7 +1,5 @@
import BaseApi from './base';
import type { StoreState } from '../store/type'; import type { StoreState } from '../store/type';
import type {GcpToken} from '../../types/gcp-state'; import BaseApi from './base';
export default class GcpApi extends BaseApi<StoreState> { export default class GcpApi extends BaseApi<StoreState> {
// Does not touch the store; use the value manually. // Does not touch the store; use the value manually.
@ -18,4 +16,4 @@ export default class GcpApi extends BaseApi<StoreState> {
}); });
}); });
} }
}; }

View File

@ -1,17 +1,17 @@
import { Patp } from '@urbit/api'; import { Patp } from '@urbit/api';
import BaseApi from './base';
import { StoreState } from '../store/type';
import GlobalStore from '../store/store'; import GlobalStore from '../store/store';
import LocalApi from './local'; import { StoreState } from '../store/type';
import InviteApi from './invite'; import BaseApi from './base';
import MetadataApi from './metadata';
import ContactsApi from './contacts'; import ContactsApi from './contacts';
import GroupsApi from './groups';
import LaunchApi from './launch';
import GraphApi from './graph';
import S3Api from './s3';
import GcpApi from './gcp'; import GcpApi from './gcp';
import GraphApi from './graph';
import GroupsApi from './groups';
import { HarkApi } from './hark'; import { HarkApi } from './hark';
import InviteApi from './invite';
import LaunchApi from './launch';
import LocalApi from './local';
import MetadataApi from './metadata';
import S3Api from './s3';
import SettingsApi from './settings'; import SettingsApi from './settings';
export default class GlobalApi extends BaseApi<StoreState> { export default class GlobalApi extends BaseApi<StoreState> {

View File

@ -1,16 +1,16 @@
import BaseApi from './base'; import { Content, Enc, GraphNode, GroupPolicy, Path, Patp, Post, Resource } from '@urbit/api';
import { StoreState } from '../store/type'; import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import { Patp, Path, Resource } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import { decToUd, deSig, resourceAsPath, unixToDa } from '~/logic/lib/util';
import { makeResource, resourceFromPath } from '../lib/group'; import { makeResource, resourceFromPath } from '../lib/group';
import { GroupPolicy, Enc, Post, Content } from '@urbit/api'; import { StoreState } from '../store/type';
import { numToUd, unixToDa, decToUd, deSig, resourceAsPath } from '~/logic/lib/util'; import BaseApi from './base';
export const createBlankNodeWithChildPost = ( export const createBlankNodeWithChildPost = (
parentIndex = '', parentIndex = '',
childIndex = '', childIndex = '',
contents: Content[] contents: Content[]
) => { ): GraphNode => {
const date = unixToDa(Date.now()).toString(); const date = unixToDa(Date.now()).toString();
const nodeIndex = parentIndex + '/' + date; const nodeIndex = parentIndex + '/' + date;
@ -36,7 +36,7 @@ export const createBlankNodeWithChildPost = (
hash: null, hash: null,
signatures: [] signatures: []
}, },
children: childGraph children: childGraph as BigIntOrderedMap<GraphNode>
}; };
}; };
@ -185,13 +185,12 @@ export default class GraphApi extends BaseApi<StoreState> {
}); });
} }
eval(cord: string) { eval(cord: string): Promise<string[] | undefined> {
return this.spider('graph-view-action', 'tang', 'graph-eval', { return this.spider('graph-view-action', 'tang', 'graph-eval', {
eval: cord eval: cord
}); });
} }
addGraph(ship: Patp, name: string, graph: any, mark: any) { addGraph(ship: Patp, name: string, graph: any, mark: any) {
return this.storeAction({ return this.storeAction({
'add-graph': { 'add-graph': {
@ -211,7 +210,7 @@ export default class GraphApi extends BaseApi<StoreState> {
return this.addNodes(ship, name, nodes); return this.addNodes(ship, name, nodes);
} }
addNode(ship: Patp, name: string, node: Object) { addNode(ship: Patp, name: string, node: GraphNode) {
const nodes = {}; const nodes = {};
nodes[node.post.index] = node; nodes[node.post.index] = node;
@ -265,7 +264,7 @@ export default class GraphApi extends BaseApi<StoreState> {
'resource', 'resource',
'graph-create-group-feed', 'graph-create-group-feed',
{ {
"create-group-feed": { resource: group, vip } 'create-group-feed': { resource: group, vip }
} }
); );
return resource; return resource;
@ -277,12 +276,11 @@ export default class GraphApi extends BaseApi<StoreState> {
'json', 'json',
'graph-disable-group-feed', 'graph-disable-group-feed',
{ {
"disable-group-feed": { resource: group } 'disable-group-feed': { resource: group }
} }
); );
} }
removePosts(ship: Patp, name: string, indices: string[]) { removePosts(ship: Patp, name: string, indices: string[]) {
return this.hookAction(ship, { return this.hookAction(ship, {
'remove-posts': { 'remove-posts': {
@ -369,7 +367,7 @@ export default class GraphApi extends BaseApi<StoreState> {
const node = data['graph-update']; const node = data['graph-update'];
this.store.handleEvent({ this.store.handleEvent({
data: { data: {
"graph-update-loose": node 'graph-update-loose': node
} }
}); });
} }

View File

@ -1,14 +1,14 @@
import BaseApi from './base'; import { Enc, Patp } from '@urbit/api';
import { StoreState } from '../store/type';
import { Path, Patp, Enc } from '@urbit/api';
import { import {
GroupAction, GroupAction,
GroupPolicy, GroupPolicy,
Resource,
Tag, GroupPolicyDiff, Resource,
GroupPolicyDiff Tag
} from '@urbit/api/groups'; } from '@urbit/api/groups';
import { makeResource } from '../lib/group'; import { makeResource } from '../lib/group';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class GroupsApi extends BaseApi<StoreState> { export default class GroupsApi extends BaseApi<StoreState> {
remove(resource: Resource, ships: Patp[]) { remove(resource: Resource, ships: Patp[]) {

View File

@ -1,10 +1,10 @@
import BaseApi from './base'; import { Association, GraphNotifDescription, IndexedNotification, NotifIndex } from '@urbit/api';
import { StoreState } from '../store/type';
import { dateToDa, decToUd } from '../lib/util';
import { NotifIndex, IndexedNotification, Association, GraphNotifDescription } from '@urbit/api';
import { BigInteger } from 'big-integer'; import { BigInteger } from 'big-integer';
import { getParentIndex } from '../lib/notification'; import { getParentIndex } from '../lib/notification';
import { dateToDa, decToUd } from '../lib/util';
import useHarkState from '../state/hark'; import useHarkState from '../state/hark';
import { StoreState } from '../store/type';
import BaseApi from './base';
function getHarkSize() { function getHarkSize() {
return useHarkState.getState().notifications.size ?? 0; return useHarkState.getState().notifications.size ?? 0;
@ -75,7 +75,6 @@ export class HarkApi extends BaseApi<StoreState> {
graph: { graph: {
graph: association.resource, graph: association.resource,
group: association.group, group: association.group,
module: association.metadata.module,
description, description,
index: parent index: parent
} } } }

View File

@ -1,6 +1,6 @@
import BaseApi from './base'; import { Serial } from '@urbit/api';
import { StoreState } from '../store/type'; import { StoreState } from '../store/type';
import { Serial, Path } from '@urbit/api'; import BaseApi from './base';
export default class InviteApi extends BaseApi<StoreState> { export default class InviteApi extends BaseApi<StoreState> {
accept(app: string, uid: Serial) { accept(app: string, uid: Serial) {

View File

@ -1,5 +1,5 @@
import BaseApi from './base';
import { StoreState } from '../store/type'; import { StoreState } from '../store/type';
import BaseApi from './base';
export default class LaunchApi extends BaseApi<StoreState> { export default class LaunchApi extends BaseApi<StoreState> {
add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' } }) { add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' } }) {

View File

@ -1,5 +1,5 @@
import BaseApi from './base';
import { StoreState } from '../store/type'; import { StoreState } from '../store/type';
import BaseApi from './base';
export default class LocalApi extends BaseApi<StoreState> { export default class LocalApi extends BaseApi<StoreState> {
getBaseHash() { getBaseHash() {
@ -8,7 +8,9 @@ export default class LocalApi extends BaseApi<StoreState> {
}); });
} }
dehydrate() { getRuntimeLag() {
this.store.dehydrate(); return this.scry<boolean>('launch', '/runtime-lag').then((runtimeLag) => {
this.store.handleEvent({ data: { runtimeLag } });
});
} }
} }

View File

@ -1,8 +1,8 @@
import BaseApi from './base'; import { Association, Metadata, MetadataUpdatePreview, Path } from '@urbit/api';
import { StoreState } from '../store/type';
import { Path, Patp, Association, Metadata, MetadataUpdatePreview } from '@urbit/api';
import { uxToHex } from '../lib/util'; import { uxToHex } from '../lib/util';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class MetadataApi extends BaseApi<StoreState> { export default class MetadataApi extends BaseApi<StoreState> {
metadataAdd(appName: string, resource: Path, group: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) { metadataAdd(appName: string, resource: Path, group: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) {

View File

@ -1,6 +1,5 @@
import BaseApi from './base';
import { StoreState } from '../store/type'; import { StoreState } from '../store/type';
import { S3Update } from '../../types/s3-update'; import BaseApi from './base';
export default class S3Api extends BaseApi<StoreState> { export default class S3Api extends BaseApi<StoreState> {
setCurrentBucket(bucket: string) { setCurrentBucket(bucket: string) {

View File

@ -1,12 +1,13 @@
import BaseApi from './base'; import {
import { StoreState } from '../store/type'; Bucket, Key,
import { Key,
Value, SettingsUpdate, Value
Bucket
} from '@urbit/api/settings'; } from '@urbit/api/settings';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class SettingsApi extends BaseApi<StoreState> { export default class SettingsApi extends BaseApi<StoreState> {
private storeAction(action: SettingsEvent): Promise<any> { private storeAction(action: SettingsUpdate): Promise<any> {
return this.action('settings-store', 'settings-event', action); return this.action('settings-store', 'settings-event', action);
} }
@ -47,14 +48,14 @@ export default class SettingsApi extends BaseApi<StoreState> {
} }
async getAll() { async getAll() {
const { all } = await this.scry("settings-store", "/all"); const { all } = await this.scry('settings-store', '/all');
this.store.handleEvent({ data: this.store.handleEvent({ data:
{"settings-data": { all } } { 'settings-data': { all } }
}); });
} }
async getBucket(bucket: Key) { async getBucket(bucket: Key) {
const data = await this.scry('settings-store', `/bucket/${bucket}`); const data: Record<string, unknown> = await this.scry('settings-store', `/bucket/${bucket}`);
this.store.handleEvent({ data: { 'settings-data': { this.store.handleEvent({ data: { 'settings-data': {
'bucket-key': bucket, 'bucket-key': bucket,
'bucket': data.bucket 'bucket': data.bucket
@ -62,7 +63,7 @@ export default class SettingsApi extends BaseApi<StoreState> {
} }
async getEntry(bucket: Key, entry: Key) { async getEntry(bucket: Key, entry: Key) {
const data = await this.scry('settings-store', `/entry/${bucket}/${entry}`); const data: Record<string, unknown> = await this.scry('settings-store', `/entry/${bucket}/${entry}`);
this.store.handleEvent({ data: { 'settings-data': { this.store.handleEvent({ data: { 'settings-data': {
'bucket-key': bucket, 'bucket-key': bucket,
'entry-key': entry, 'entry-key': entry,

View File

@ -7,14 +7,12 @@
// //
import querystring from 'querystring'; import querystring from 'querystring';
import { import {
StorageAcl,
StorageClient, StorageClient,
StorageUpload, StorageUpload,
UploadParams, UploadParams,
UploadResult UploadResult
} from './StorageClient'; } from './StorageClient';
const ENDPOINT = 'storage.googleapis.com'; const ENDPOINT = 'storage.googleapis.com';
class GcpUpload implements StorageUpload { class GcpUpload implements StorageUpload {

View File

@ -1,14 +1,13 @@
// Defines a StorageClient interface interoperable between S3 and GCP Storage. // Defines a StorageClient interface interoperable between S3 and GCP Storage.
// //
// XX kind of gross. S3 needs 'public-read', GCP needs 'publicRead'. // XX kind of gross. S3 needs 'public-read', GCP needs 'publicRead'.
// Rather than write a wrapper around S3, we offer this field here, which // Rather than write a wrapper around S3, we offer this field here, which
// should always be passed, and will be replaced by 'publicRead' in the // should always be passed, and will be replaced by 'publicRead' in the
// GCP client. // GCP client.
export enum StorageAcl { export enum StorageAcl {
PublicRead = 'public-read' PublicRead = 'public-read'
}; }
export interface UploadParams { export interface UploadParams {
Bucket: string; // the bucket to upload the object to Bucket: string; // the bucket to upload the object to
@ -16,17 +15,17 @@ export interface UploadParams {
ContentType: string; // the object's mime-type ContentType: string; // the object's mime-type
ACL: StorageAcl; // ACL, always 'public-read' ACL: StorageAcl; // ACL, always 'public-read'
Body: File; // the object itself Body: File; // the object itself
}; }
export interface UploadResult { export interface UploadResult {
Location: string; Location: string;
}; }
// Extra layer of indirection used by S3 client. // Extra layer of indirection used by S3 client.
export interface StorageUpload { export interface StorageUpload {
promise(): Promise<UploadResult>; promise(): Promise<UploadResult>;
}; }
export interface StorageClient { export interface StorageClient {
upload(params: UploadParams): StorageUpload; upload(params: UploadParams): StorageUpload;
}; }

View File

@ -1,4 +1,4 @@
import bigInt, { BigInteger } from 'big-integer'; import { BigInteger } from 'big-integer';
export function max(a: BigInteger, b: BigInteger) { export function max(a: BigInteger, b: BigInteger) {
return a.gt(b) ? a : b; return a.gt(b) ? a : b;

View File

@ -1,4 +1,4 @@
import React from "react"; import React from 'react';
export type SubmitHandler = () => Promise<any>; export type SubmitHandler = () => Promise<any>;
interface IFormGroupContext { interface IFormGroupContext {
@ -12,7 +12,7 @@ const fallback: IFormGroupContext = {
addSubmit: () => {}, addSubmit: () => {},
onDirty: () => {}, onDirty: () => {},
onErrors: () => {}, onErrors: () => {},
submitAll: () => Promise.resolve(), submitAll: () => Promise.resolve()
}; };
export const FormGroupContext = React.createContext(fallback); export const FormGroupContext = React.createContext(fallback);

View File

@ -15,7 +15,6 @@
import GlobalApi from '../api/global'; import GlobalApi from '../api/global';
import useStorageState from '../state/storage'; import useStorageState from '../state/storage';
class GcpManager { class GcpManager {
#api: GlobalApi | null = null; #api: GlobalApi | null = null;
@ -59,15 +58,15 @@ class GcpManager {
this.start(); this.start();
} }
#consecutiveFailures: number = 0; #consecutiveFailures = 0;
#configured: boolean = false; #configured = false;
private refreshLoop() { private refreshLoop() {
if (!this.#configured) { if (!this.#configured) {
this.#api!.gcp.isConfigured() this.#api!.gcp.isConfigured()
.then((configured) => { .then((configured) => {
if (configured === undefined) { if (configured === undefined) {
throw new Error("can't check whether GCP is configured?"); throw new Error('can\'t check whether GCP is configured?');
} }
this.#configured = configured; this.#configured = configured;
if (this.#configured) { if (this.#configured) {

View File

@ -1,7 +1,6 @@
import { Path, PatpNoSig } from '@urbit/api';
import { Group, Resource, roleTags, RoleTags } from '@urbit/api/groups';
import _ from 'lodash'; import _ from 'lodash';
import { roleTags, RoleTags, Group, Resource } from '@urbit/api/groups';
import { PatpNoSig, Path } from '@urbit/api';
import { deSig } from './util';
export function roleForShip( export function roleForShip(
group: Group, group: Group,

View File

@ -1,6 +1,7 @@
import { IndexedNotification, NotificationGraphConfig, Unreads } from '@urbit/api';
import bigInt, { BigInteger } from 'big-integer'; import bigInt, { BigInteger } from 'big-integer';
import _ from 'lodash';
import f from 'lodash/fp'; import f from 'lodash/fp';
import { Unreads, NotificationGraphConfig } from '@urbit/api';
export function getLastSeen( export function getLastSeen(
unreads: Unreads, unreads: Unreads,
@ -31,16 +32,29 @@ export function getNotificationCount(
): number { ): number {
const unread = unreads.graph?.[path] || {}; const unread = unreads.graph?.[path] || {};
return Object.keys(unread) return Object.keys(unread)
.map(index => unread[index]?.notifications?.length || 0) .map(index => _.get(unread[index], 'notifications.length', 0))
.reduce(f.add, 0); .reduce(f.add, 0);
} }
export function isWatching( export function isWatching(
config: NotificationGraphConfig, config: NotificationGraphConfig,
graph: string, graph: string,
index = "/" index = '/'
) { ) {
return !!config.watching.find( return Boolean(config.watching.find(
watch => watch.graph === graph && watch.index === index watch => watch.graph === graph && watch.index === index
); ));
} }
export function getNotificationKey(time: BigInteger, notification: IndexedNotification): string {
const base = time.toString();
if('graph' in notification.index) {
const { graph, index } = notification.index.graph;
return `${base}-${graph}-${index}`;
} else if('group' in notification.index) {
const { group } = notification.index.group;
return `${base}-${group}`;
}
return `${base}-unknown`;
}

View File

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'; import { useEffect, useState } from 'react';
export function useIdlingState() { export function useIdlingState() {
const [idling, setIdling] = useState(false); const [idling, setIdling] = useState(false);
@ -16,7 +16,7 @@ export function useIdlingState() {
return () => { return () => {
window.removeEventListener('blur', blur); window.removeEventListener('blur', blur);
window.removeEventListener('focus', focus); window.removeEventListener('focus', focus);
} };
}, []); }, []);
return idling; return idling;

View File

@ -1,15 +1,15 @@
import useLocalState, { LocalState } from "~/logic/state/local"; import useLocalState from '~/logic/state/local';
import useSettingsState from "~/logic/state/settings"; import useSettingsState from '~/logic/state/settings';
import GlobalApi from "../api/global"; import { BackgroundConfig, RemoteContentPolicy } from '~/types';
import { BackgroundConfig, RemoteContentPolicy } from "~/types"; import GlobalApi from '../api/global';
const getBackgroundString = (bg: BackgroundConfig) => { const getBackgroundString = (bg: BackgroundConfig) => {
if (bg?.type === "url") { if (bg?.type === 'url') {
return bg.url; return bg.url;
} else if (bg?.type === "color") { } else if (bg?.type === 'color') {
return bg.color; return bg.color;
} else { } else {
return ""; return '';
} }
}; };
@ -18,17 +18,17 @@ export function useMigrateSettings(api: GlobalApi) {
const { display, remoteContentPolicy, calm } = useSettingsState(); const { display, remoteContentPolicy, calm } = useSettingsState();
return async () => { return async () => {
let promises: Promise<any>[] = []; const promises: Promise<any>[] = [];
if (local.hideAvatars !== calm.hideAvatars) { if (local.hideAvatars !== calm.hideAvatars) {
promises.push( promises.push(
api.settings.putEntry("calm", "hideAvatars", local.hideAvatars) api.settings.putEntry('calm', 'hideAvatars', local.hideAvatars)
); );
} }
if (local.hideNicknames !== calm.hideNicknames) { if (local.hideNicknames !== calm.hideNicknames) {
promises.push( promises.push(
api.settings.putEntry("calm", "hideNicknames", local.hideNicknames) api.settings.putEntry('calm', 'hideNicknames', local.hideNicknames)
); );
} }
@ -38,15 +38,15 @@ export function useMigrateSettings(api: GlobalApi) {
) { ) {
promises.push( promises.push(
api.settings.putEntry( api.settings.putEntry(
"display", 'display',
"background", 'background',
getBackgroundString(local.background) getBackgroundString(local.background)
) )
); );
promises.push( promises.push(
api.settings.putEntry( api.settings.putEntry(
"display", 'display',
"backgroundType", 'backgroundType',
local.background?.type local.background?.type
) )
); );
@ -57,12 +57,12 @@ export function useMigrateSettings(api: GlobalApi) {
const localVal = local.remoteContentPolicy[key]; const localVal = local.remoteContentPolicy[key];
if (localVal !== remoteContentPolicy[key]) { if (localVal !== remoteContentPolicy[key]) {
promises.push( promises.push(
api.settings.putEntry("remoteContentPolicy", key, localVal) api.settings.putEntry('remoteContentPolicy', key, localVal)
); );
} }
}); });
await Promise.all(promises); await Promise.all(promises);
localStorage.removeItem("localReducer"); localStorage.removeItem('localReducer');
}; };
} }

View File

@ -1,4 +1,4 @@
import { GraphNotifIndex, GraphNotificationContents } from '@urbit/api'; import { GraphNotificationContents, GraphNotifIndex } from '@urbit/api';
export function getParentIndex( export function getParentIndex(
idx: GraphNotifIndex, idx: GraphNotifIndex,

View File

@ -1,5 +1,5 @@
import { cite } from '~/logic/lib/util';
import { isChannelAdmin } from '~/logic/lib/group'; import { isChannelAdmin } from '~/logic/lib/group';
import { cite } from '~/logic/lib/util';
const makeIndexes = () => new Map([ const makeIndexes = () => new Map([
['ships', []], ['ships', []],
@ -23,7 +23,7 @@ const result = function(title, link, app, host) {
const shipIndex = function(contacts) { const shipIndex = function(contacts) {
const ships = []; const ships = [];
Object.keys(contacts).map((e) => { Object.keys(contacts).map((e) => {
return ships.push(result(e, `/~profile/${e}`, 'profile', contacts[e]?.status || "")); return ships.push(result(e, `/~profile/${e}`, 'profile', contacts[e]?.status || ''));
}); });
return ships; return ships;
}; };
@ -38,11 +38,11 @@ const commandIndex = function (currentGroup, groups, associations) {
? (association.metadata.vip === 'member-metadata' || isChannelAdmin(group, currentGroup)) ? (association.metadata.vip === 'member-metadata' || isChannelAdmin(group, currentGroup))
: !currentGroup; // home workspace or hasn't loaded : !currentGroup; // home workspace or hasn't loaded
const workspace = currentGroup || '/home'; const workspace = currentGroup || '/home';
commands.push(result(`Groups: Create`, `/~landscape/new`, 'Groups', null)); commands.push(result('Groups: Create', '/~landscape/new', 'Groups', null));
if (canAdd) { if (canAdd) {
commands.push(result(`Channel: Create`, `/~landscape${workspace}/new`, 'Groups', null)); commands.push(result('Channel: Create', `/~landscape${workspace}/new`, 'Groups', null));
} }
commands.push(result(`Groups: Join`, `/~landscape/join`, 'Groups', null)); commands.push(result('Groups: Join', '/~landscape/join', 'Groups', null));
return commands; return commands;
}; };
@ -80,7 +80,7 @@ const otherIndex = function(config) {
logout: result('Log Out', '/~/logout', 'logout', null) logout: result('Log Out', '/~/logout', 'logout', null)
}; };
other.push(result('Tutorial', '/?tutorial=true', 'tutorial', null)); other.push(result('Tutorial', '/?tutorial=true', 'tutorial', null));
for(let cat of config.categories) { for(const cat of config.categories) {
if(idx[cat]) { if(idx[cat]) {
other.push(idx[cat]); other.push(idx[cat]);
} }
@ -102,7 +102,7 @@ export default function index(contacts, associations, apps, currentGroup, groups
}).map((e) => { }).map((e) => {
// iterate through each app's metadata object // iterate through each app's metadata object
Object.keys(associations[e]) Object.keys(associations[e])
.filter((association) => !associations?.[e]?.[association]?.metadata?.hidden) .filter(association => !associations?.[e]?.[association]?.metadata?.hidden)
.map((association) => { .map((association) => {
const each = associations[e][association]; const each = associations[e][association];
let title = each.resource; let title = each.resource;

View File

@ -1,15 +1,12 @@
import { import {
Association, ReferenceContent, resourceFromPath
resourceFromPath, } from '@urbit/api';
Group,
ReferenceContent,
} from "@urbit/api";
export function getPermalinkForGraph( export function getPermalinkForGraph(
group: string, group: string,
graph: string, graph: string,
index = "" index = ''
) { ) {
const groupLink = getPermalinkForAssociatedGroup(group); const groupLink = getPermalinkForAssociatedGroup(group);
const { ship, name } = resourceFromPath(graph); const { ship, name } = resourceFromPath(graph);
@ -21,16 +18,16 @@ function getPermalinkForAssociatedGroup(group: string) {
return `web+urbitgraph://group/${ship}/${name}`; return `web+urbitgraph://group/${ship}/${name}`;
} }
type Permalink = GraphPermalink | GroupPermalink; type Permalink = GraphPermalink | GroupPermalink;
interface GroupPermalink { export interface GroupPermalink {
type: "group"; type: 'group';
group: string; group: string;
link: string; link: string;
} }
interface GraphPermalink {
type: "graph"; export interface GraphPermalink {
type: 'graph';
link: string; link: string;
graph: string; graph: string;
group: string; group: string;
@ -43,16 +40,16 @@ function parseGraphPermalink(
segments: string[] segments: string[]
): GraphPermalink | null { ): GraphPermalink | null {
const [kind, ship, name, ...index] = segments; const [kind, ship, name, ...index] = segments;
if (kind !== "graph") { if (kind !== 'graph') {
return null; return null;
} }
const graph = `/ship/${ship}/${name}`; const graph = `/ship/${ship}/${name}`;
return { return {
type: "graph", type: 'graph',
link: link.slice(16), link: link.slice(16),
graph, graph,
group, group,
index: `/${index.join("/")}`, index: `/${index.join('/')}`
}; };
} }
@ -64,7 +61,7 @@ export function permalinkToReference(link: Permalink): ReferenceContent {
group: link.group, group: link.group,
index: link.index index: link.index
} }
} };
return { reference }; return { reference };
} else { } else {
const reference = { const reference = {
@ -89,22 +86,22 @@ export function referenceToPermalink({ reference }: ReferenceContent): Permalink
type: 'group', type: 'group',
link, link,
...reference ...reference
} };
} }
} }
export function parsePermalink(url: string): Permalink | null { export function parsePermalink(url: string): Permalink | null {
const [kind, ...rest] = url.slice(17).split("/"); const [kind, ...rest] = url.slice(17).split('/');
if (kind === "group") { if (kind === 'group') {
const [ship, name, ...graph] = rest; const [ship, name, ...graph] = rest;
const group = `/ship/${ship}/${name}`; const group = `/ship/${ship}/${name}`;
if (graph.length > 0) { if (graph.length > 0) {
return parseGraphPermalink(url, group, graph); return parseGraphPermalink(url, group, graph);
} }
return { return {
type: "group", type: 'group',
group, group,
link: url.slice(11), link: url.slice(11)
}; };
} }
return null; return null;

View File

@ -1,4 +1,4 @@
import { Post, GraphNode } from '@urbit/api'; import { GraphNode, Post } from '@urbit/api';
export const buntPost = (): Post => ({ export const buntPost = (): Post => ({
author: '', author: '',

View File

@ -1,8 +1,9 @@
import { Post, GraphNode, TextContent } from '@urbit/api'; import { Content, GraphNode, Post, TextContent } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import bigInt, { BigInteger } from 'big-integer';
import { buntPost } from '~/logic/lib/post'; import { buntPost } from '~/logic/lib/post';
import { unixToDa } from '~/logic/lib/util'; import { unixToDa } from '~/logic/lib/util';
import BigIntOrderedMap from "@urbit/api/lib/BigIntOrderedMap"; import tokenizeMessage from './tokenizeMessage';
import bigInt, { BigInteger } from 'big-integer';
export function newPost( export function newPost(
title: string, title: string,
@ -19,13 +20,15 @@ export function newPost(
signatures: [] signatures: []
}; };
const tokenisedBody = tokenizeMessage(body);
const revContainer: Post = { ...root, index: root.index + '/1' }; const revContainer: Post = { ...root, index: root.index + '/1' };
const commentsContainer = { ...root, index: root.index + '/2' }; const commentsContainer = { ...root, index: root.index + '/2' };
const firstRevision: Post = { const firstRevision: Post = {
...revContainer, ...revContainer,
index: revContainer.index + '/1', index: revContainer.index + '/1',
contents: [{ text: title }, { text: body }] contents: [{ text: title }, ...tokenisedBody]
}; };
const nodes = { const nodes = {
@ -54,11 +57,12 @@ export function newPost(
export function editPost(rev: number, noteId: BigInteger, title: string, body: string) { export function editPost(rev: number, noteId: BigInteger, title: string, body: string) {
const now = Date.now(); const now = Date.now();
const tokenisedBody = tokenizeMessage(body);
const newRev: Post = { const newRev: Post = {
author: `~${window.ship}`, author: `~${window.ship}`,
index: `/${noteId.toString()}/1/${rev}`, index: `/${noteId.toString()}/1/${rev}`,
'time-sent': now, 'time-sent': now,
contents: [{ text: title }, { text: body }], contents: [{ text: title }, ...tokenisedBody],
hash: null, hash: null,
signatures: [] signatures: []
}; };
@ -85,8 +89,9 @@ export function getLatestRevision(node: GraphNode): [number, string, string, Pos
if (!rev) { if (!rev) {
return empty; return empty;
} }
const [title, body] = rev.post.contents as TextContent[]; const title = rev.post.contents[0];
return [revNum.toJSNumber(), title.text, body.text, rev.post]; const body = rev.post.contents.slice(1);
return [revNum.toJSNumber(), title.text, body, rev.post];
} }
export function getLatestCommentRevision(node: GraphNode): [number, Post] { export function getLatestCommentRevision(node: GraphNode): [number, Post] {
@ -113,10 +118,12 @@ export function getComments(node: GraphNode): GraphNode {
return comments; return comments;
} }
export function getSnippet(body: string) { export function getSnippet(body: Content[]) {
const newlineIdx = body.indexOf('\n', 2); const firstContent = body
const end = newlineIdx > -1 ? newlineIdx : body.length; .filter((c: Content): c is TextContent => 'text' in c).map(c => c.text)[0] ?? '';
const start = body.substr(0, end); const newlineIdx = firstContent.indexOf('\n', 2);
const end = newlineIdx > -1 ? newlineIdx : firstContent.length;
const start = firstContent.substr(0, end);
return (start === body || start.startsWith('![')) ? start : `${start}...`; return (start === firstContent || firstContent.startsWith('![')) ? start : `${start}...`;
} }

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react';
import { sigil, reactRenderer } from '@tlon/sigil-js';
import { Box } from '@tlon/indigo-react'; import { Box } from '@tlon/indigo-react';
import { reactRenderer, sigil } from '@tlon/sigil-js';
import React, { memo } from 'react';
export const foregroundFromBackground = (background) => { export const foregroundFromBackground = (background) => {
const rgb = { const rgb = {

View File

@ -1,31 +1,43 @@
import urbitOb from 'urbit-ob'; import urbitOb from 'urbit-ob';
import { parsePermalink, permalinkToReference } from "~/logic/lib/permalinks"; import { parsePermalink, permalinkToReference } from '~/logic/lib/permalinks';
const URL_REGEX = new RegExp(String(/^(([\w\-\+]+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+\w)/.source)); const URL_REGEX = new RegExp(String(/^(([\w\-\+]+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+\w)/.source));
const GROUP_REGEX = new RegExp(String(/^~[-a-z_]+\/[-a-z]+/.source));
const isUrl = (string) => { const isUrl = (string) => {
try { try {
return URL_REGEX.test(string); return URL_REGEX.test(string);
} catch (e) { } catch (e) {
return false; return false;
} }
} };
const isRef = (str) => { const isRef = (str) => {
return isUrl(str) && str.startsWith("web+urbitgraph://"); return isUrl(str) && str.startsWith('web+urbitgraph://');
};
const isGroup = str => {
try {
return GROUP_REGEX.test(str);
} catch (e) {
return false;
} }
}
const convertToGroupRef = (group) => `web+urbitgraph://group/${group}`;
const tokenizeMessage = (text) => { const tokenizeMessage = (text) => {
let messages = []; let messages = [];
let message = []; // by line
let currTextBlock = [];
let isInCodeBlock = false; let isInCodeBlock = false;
let endOfCodeBlock = false; let endOfCodeBlock = false;
text.split(/\r?\n/).forEach((line, index) => { text.split(/\r?\n/).forEach((line, index) => {
if (index !== 0) { // by space
message.push('\n'); let currTextLine = [];
}
// A line of backticks enters and exits a codeblock // A line of backticks enters and exits a codeblock
if (line.startsWith('```')) { if (line.trim().startsWith('```')) {
// But we need to check if we've ended a codeblock // But we need to check if we've ended a codeblock
endOfCodeBlock = isInCodeBlock; endOfCodeBlock = isInCodeBlock;
isInCodeBlock = (!isInCodeBlock); isInCodeBlock = (!isInCodeBlock);
@ -34,25 +46,26 @@ const tokenizeMessage = (text) => {
} }
if (isInCodeBlock || endOfCodeBlock) { if (isInCodeBlock || endOfCodeBlock) {
message.push(line); currTextLine = [line];
} else { } else {
line.split(/\s/).forEach((str) => { const words = line.split(/\s/);
words.forEach((word, idx) => {
const str = isGroup(word) ? convertToGroupRef(word) : word;
const last = words.length - 1 === idx;
if ( if (
(str.startsWith('`') && str !== '`') (str.startsWith('`') && str !== '`')
|| (str === '`' && !isInCodeBlock) || (str === '`' && !isInCodeBlock)
) { ) {
isInCodeBlock = true; isInCodeBlock = true;
} else if (
(str.endsWith('`') && str !== '`')
|| (str === '`' && isInCodeBlock)
) {
isInCodeBlock = false;
} }
if(isRef(str) && !isInCodeBlock) { if(isRef(str) && !isInCodeBlock) {
if (message.length > 0) { if (currTextLine.length > 0 || currTextBlock.length > 0) {
// If we're in the middle of a message, add it to the stack and reset // If we're in the middle of a message, add it to the stack and reset
messages.push({ text: message.join(' ') }); currTextLine.push('');
messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
currTextBlock = last ? [''] : [];
currTextLine = [];
} }
const link = parsePermalink(str); const link = parsePermalink(str);
if(!link) { if(!link) {
@ -61,34 +74,46 @@ const tokenizeMessage = (text) => {
const reference = permalinkToReference(link); const reference = permalinkToReference(link);
messages.push(reference); messages.push(reference);
} }
message = []; currTextLine = [];
} else if (isUrl(str) && !isInCodeBlock) { } else if (isUrl(str) && !isInCodeBlock) {
if (message.length > 0) { if (currTextLine.length > 0 || currTextBlock.length > 0) {
// If we're in the middle of a message, add it to the stack and reset // If we're in the middle of a message, add it to the stack and reset
messages.push({ text: message.join(' ') }); currTextLine.push('');
message = []; messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
currTextBlock = last ? [''] : [];
currTextLine = [];
} }
messages.push({ url: str }); messages.push({ url: str });
message = []; currTextLine = [];
} else if(urbitOb.isValidPatp(str) && !isInCodeBlock) { } else if(urbitOb.isValidPatp(str) && !isInCodeBlock) {
if (message.length > 0) { if (currTextLine.length > 0 || currTextBlock.length > 0) {
// If we're in the middle of a message, add it to the stack and reset // If we're in the middle of a message, add it to the stack and reset
messages.push({ text: message.join(' ') }); currTextLine.push('');
message = []; messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
currTextBlock = last ? [''] : [];
currTextLine = [];
} }
messages.push({ mention: str }); messages.push({ mention: str });
message = []; currTextLine = [];
} else { } else {
message.push(str); currTextLine.push(str);
} }
if (
(str.endsWith('`') && str !== '`')
|| (str === '`' && isInCodeBlock)
) {
isInCodeBlock = false;
}
}); });
} }
currTextBlock.push(currTextLine.join(' '))
}); });
if (message.length) { if (currTextBlock.length) {
// Add any remaining message // Add any remaining message
messages.push({ text: message.join(' ') }); messages.push({ text: currTextBlock.join('\n') });
} }
return messages; return messages;
}; };

View File

@ -1,6 +1,6 @@
import { Associations } from '@urbit/api'; import { Associations } from '@urbit/api';
import { TutorialProgress } from '~/types';
import { AlignX, AlignY } from '~/logic/lib/relativePosition'; import { AlignX, AlignY } from '~/logic/lib/relativePosition';
import { TutorialProgress } from '~/types';
import { Direction } from '~/views/components/Triangle'; import { Direction } from '~/views/components/Triangle';
export const MODAL_WIDTH = 256; export const MODAL_WIDTH = 256;
@ -92,7 +92,7 @@ export const progressDetails: Record<TutorialProgress, StepDetail> = {
alignY: 'top', alignY: 'top',
arrow: 'East', arrow: 'East',
offsetX: MODAL_WIDTH + 24, offsetX: MODAL_WIDTH + 24,
offsetY: 80, offsetY: 80
}, },
channels: { channels: {
title: 'Channels', title: 'Channels',
@ -157,17 +157,17 @@ export const progressDetails: Record<TutorialProgress, StepDetail> = {
alignX: 'right', alignX: 'right',
arrow: 'South', arrow: 'South',
offsetX: -300 + MODAL_WIDTH / 2, offsetX: -300 + MODAL_WIDTH / 2,
offsetY: -4, offsetY: -4
}, },
leap: { leap: {
title: 'Leap', title: 'Leap',
description: description:
'Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.', 'Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.',
url: `/~profile/~${window.ship}`, url: `/~profile/~${window.ship}`,
alignY: "top", alignY: 'top',
alignX: "left", alignX: 'left',
arrow: "North", arrow: 'North',
offsetX: 76, offsetX: 76,
offsetY: -48, offsetY: -48
}, }
}; };

View File

@ -1,7 +1,7 @@
import { writeText } from "./util"; import { useCallback, useMemo, useState } from 'react';
import { useCallback, useState, useMemo } from "react"; import { writeText } from './util';
export function useCopy(copied: string, display: string) { export function useCopy(copied: string, display?: string) {
const [didCopy, setDidCopy] = useState(false); const [didCopy, setDidCopy] = useState(false);
const doCopy = useCallback(() => { const doCopy = useCallback(() => {
writeText(copied); writeText(copied);
@ -11,9 +11,9 @@ export function useCopy(copied: string, display: string) {
}, 2000); }, 2000);
}, [copied]); }, [copied]);
const copyDisplay = useMemo(() => (didCopy ? "Copied" : display), [ const copyDisplay = useMemo(() => (didCopy ? 'Copied' : display), [
didCopy, didCopy,
display, display
]); ]);
return { copyDisplay, doCopy, didCopy }; return { copyDisplay, doCopy, didCopy };

View File

@ -1,4 +1,4 @@
import { useState, useCallback, useMemo, useEffect } from 'react'; import { useCallback, useEffect, useState } from 'react';
function validateDragEvent(e: DragEvent): FileList | File[] | true | null { function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
const files: File[] = []; const files: File[] = [];

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useMemo, useCallback } from 'react'; import { useCallback, useState } from 'react';
export function useDropdown<C>( export function useDropdown<C>(
candidates: C[], candidates: C[],

View File

@ -1,5 +1,5 @@
import { useEffect, RefObject, useRef, useState } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import { RefObject, useEffect, useState } from 'react';
import usePreviousValue from './usePreviousValue'; import usePreviousValue from './usePreviousValue';
export function distanceToBottom(el: HTMLElement) { export function distanceToBottom(el: HTMLElement) {

View File

@ -1,4 +1,4 @@
import { useState, useCallback, useEffect } from 'react'; import { useCallback, useEffect, useState } from 'react';
function retrieve<T>(key: string, initial: T): T { function retrieve<T>(key: string, initial: T): T {
const s = localStorage.getItem(key); const s = localStorage.getItem(key);

View File

@ -1,15 +1,13 @@
import { Box } from '@tlon/indigo-react';
import React, { import React, {
useState,
ReactNode, ReactNode,
useCallback, useCallback,
useMemo, useMemo,
useRef useRef, useState
} from 'react'; } from 'react';
import { PropFunc } from '~/types';
import { Box } from '@tlon/indigo-react';
import { ModalOverlay } from '~/views/components/ModalOverlay'; import { ModalOverlay } from '~/views/components/ModalOverlay';
import { Portal } from '~/views/components/Portal'; import { Portal } from '~/views/components/Portal';
import { PropFunc } from '~/types';
type ModalFunc = (dismiss: () => void) => JSX.Element; type ModalFunc = (dismiss: () => void) => JSX.Element;
interface UseModalProps { interface UseModalProps {
@ -59,7 +57,7 @@ export function useModal(props: UseModalProps & PropFunc<typeof Box>): UseModalR
display="flex" display="flex"
alignItems="stretch" alignItems="stretch"
flexDirection="column" flexDirection="column"
spacing="2" spacing={2}
dismiss={dismiss} dismiss={dismiss}
{...rest} {...rest}
> >

View File

@ -1,4 +1,4 @@
import { useEffect, RefObject } from 'react'; import { RefObject, useEffect } from 'react';
export function useOutsideClick( export function useOutsideClick(
ref: RefObject<HTMLElement | null | undefined>, ref: RefObject<HTMLElement | null | undefined>,

View File

@ -1,6 +1,6 @@
import { useMemo, useCallback } from "react"; import _ from 'lodash';
import { useLocation } from "react-router-dom"; import { useCallback, useMemo } from 'react';
import _ from "lodash"; import { useLocation } from 'react-router-dom';
function mergeQuery(search: URLSearchParams, added: Record<string, string>) { function mergeQuery(search: URLSearchParams, added: Record<string, string>) {
_.forIn(added, (v, k) => { _.forIn(added, (v, k) => {
@ -32,7 +32,7 @@ export function useQuery() {
mergeQuery(q, params); mergeQuery(q, params);
return { return {
pathname: path, pathname: path,
search: q.toString(), search: q.toString()
}; };
}, },
[search, pathname] [search, pathname]
@ -41,6 +41,6 @@ export function useQuery() {
return { return {
query, query,
appendQuery, appendQuery,
toQuery, toQuery
}; };
} }

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useEffect, useState } from 'react';
import {unstable_batchedUpdates} from "react-dom"; import { unstable_batchedUpdates } from 'react-dom';
export type IOInstance<I, P, O> = ( export type IOInstance<I, P, O> = (
input: I input: I
@ -29,7 +29,7 @@ export function useRunIO<I, O>(
}); });
useEffect(() => { useEffect(() => {
reject(new Error("useRunIO: key changed")); reject(new Error('useRunIO: key changed'));
setDone(false); setDone(false);
setOutput(null); setOutput(null);
}, [key]); }, [key]);

View File

@ -1,4 +1,4 @@
import { MouseEvent, useCallback, useState, useEffect } from 'react'; import { MouseEvent, useCallback, useEffect, useState } from 'react';
export type AsyncClickableState = 'waiting' | 'error' | 'loading' | 'success'; export type AsyncClickableState = 'waiting' | 'error' | 'loading' | 'success';
export function useStatelessAsyncClickable( export function useStatelessAsyncClickable(

View File

@ -1,22 +1,16 @@
import { useCallback, useMemo, useEffect, useRef, useState } from 'react';
import {
GcpState,
S3State,
StorageState
} from '../../types';
import S3 from 'aws-sdk/clients/s3'; import S3 from 'aws-sdk/clients/s3';
import GcpClient from './GcpClient'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { StorageClient, StorageAcl } from './StorageClient';
import { dateToDa, deSig } from './util';
import useStorageState from '../state/storage'; import useStorageState from '../state/storage';
import GcpClient from './GcpClient';
import { StorageAcl, StorageClient } from './StorageClient';
import { dateToDa, deSig } from './util';
export interface IuseStorage { export interface IuseStorage {
canUpload: boolean; canUpload: boolean;
upload: (file: File, bucket: string) => Promise<string>; upload: (file: File, bucket: string) => Promise<string>;
uploadDefault: (file: File) => Promise<string>; uploadDefault: (file: File) => Promise<string>;
uploading: boolean; uploading: boolean;
promptUpload: () => Promise<unknown>; promptUpload: () => Promise<string>;
} }
const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => { const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
@ -54,7 +48,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
); );
const upload = useCallback( const upload = useCallback(
async (file: File, bucket: string) => { async (file: File, bucket: string): Promise<string> => {
if (client.current === null) { if (client.current === null) {
throw new Error('Storage not ready'); throw new Error('Storage not ready');
} }
@ -83,7 +77,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
[client, setUploading] [client, setUploading]
); );
const uploadDefault = useCallback(async (file: File) => { const uploadDefault = useCallback(async (file: File): Promise<string> => {
if (s3.configuration.currentBucket === '') { if (s3.configuration.currentBucket === '') {
throw new Error('current bucket not set'); throw new Error('current bucket not set');
} }
@ -91,7 +85,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
}, [s3, upload]); }, [s3, upload]);
const promptUpload = useCallback( const promptUpload = useCallback(
() => { (): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const fileSelector = document.createElement('input'); const fileSelector = document.createElement('input');
fileSelector.setAttribute('type', 'file'); fileSelector.setAttribute('type', 'file');
@ -101,10 +95,10 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
const files = fileSelector.files; const files = fileSelector.files;
if (!files || files.length <= 0) { if (!files || files.length <= 0) {
reject(); reject();
return; } else {
}
uploadDefault(files[0]).then(resolve); uploadDefault(files[0]).then(resolve);
document.body.removeChild(fileSelector); document.body.removeChild(fileSelector);
}
}); });
document.body.appendChild(fileSelector); document.body.appendChild(fileSelector);
fileSelector.click(); fileSelector.click();

View File

@ -1,10 +1,10 @@
import { useState, useCallback } from "react"; import { useCallback, useState } from 'react';
export function useToggleState(initial: boolean) { export function useToggleState(initial: boolean) {
const [state, setState] = useState(initial); const [state, setState] = useState(initial);
const toggle = useCallback(() => { const toggle = useCallback(() => {
setState((s) => !s); setState(s => !s);
}, [setState]); }, [setState]);
return [state, toggle] as const; return [state, toggle] as const;

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react'; import { useCallback, useEffect, useState } from 'react';
export function useWaitForProps<P>(props: P, timeout = 0) { export function useWaitForProps<P>(props: P, timeout = 0) {
const [resolve, setResolve] = useState<() => void>(() => () => {}); const [resolve, setResolve] = useState<() => void>(() => () => {});

View File

@ -1,15 +1,13 @@
import { useEffect, useState, useCallback, useMemo } from 'react'; /* eslint-disable max-lines */
import _ from 'lodash';
import f, { compose, memoize } from 'lodash/fp';
import bigInt, { BigInteger } from 'big-integer';
import { Association, Contact } from '@urbit/api'; import { Association, Contact } from '@urbit/api';
import useLocalState from '../state/local';
import produce, { enableMapSet } from 'immer';
import useSettingsState from '../state/settings';
import { State, UseStore } from 'zustand';
import { Cage } from '~/types/cage';
import { BaseState } from '../state/base';
import anyAscii from 'any-ascii'; import anyAscii from 'any-ascii';
import bigInt, { BigInteger } from 'big-integer';
import { enableMapSet } from 'immer';
import _ from 'lodash';
import f from 'lodash/fp';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { IconRef } from '~/types';
import useSettingsState from '../state/settings';
enableMapSet(); enableMapSet();
@ -24,7 +22,9 @@ export const MOMENT_CALENDAR_DATE = {
sameElse: '~YYYY.M.D' sameElse: '~YYYY.M.D'
}; };
export const getModuleIcon = (mod: string) => { export type GraphModule = 'link' | 'post' | 'chat' | 'publish';
export const getModuleIcon = (mod: GraphModule): IconRef => {
if (mod === 'link') { if (mod === 'link') {
return 'Collection'; return 'Collection';
} }
@ -33,7 +33,7 @@ export const getModuleIcon = (mod: string) => {
return 'Dashboard'; return 'Dashboard';
} }
return _.capitalize(mod); return _.capitalize(mod) as IconRef;
}; };
export function wait(ms: number) { export function wait(ms: number) {
@ -172,9 +172,9 @@ export function dateToDa(d: Date, mil = false) {
); );
} }
export function deSig(ship: string) { export function deSig(ship: string): string {
if (!ship) { if (!ship) {
return null; return '';
} }
return ship.replace('~', ''); return ship.replace('~', '');
} }
@ -201,7 +201,7 @@ export const hexToUx = (hex) => {
return `0x${ux}`; return `0x${ux}`;
}; };
export function writeText(str: string) { export function writeText(str: string | null): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(document.body); range.selectNodeContents(document.body);
@ -226,11 +226,11 @@ export function writeText(str: string) {
} }
// trim patps to match dojo, chat-cli // trim patps to match dojo, chat-cli
export function cite(ship: string) { export function cite(ship: string): string {
let patp = ship, let patp = ship,
shortened = ''; shortened = '';
if (patp === null || patp === '') { if (patp === null || patp === '') {
return null; return '';
} }
if (patp.startsWith('~')) { if (patp.startsWith('~')) {
patp = patp.substr(1); patp = patp.substr(1);
@ -400,7 +400,7 @@ export function pluralize(text: string, isPlural = false, vowel = false) {
export function useShowNickname(contact: Contact | null, hide?: boolean): boolean { export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
const hideState = useSettingsState(state => state.calm.hideNicknames); const hideState = useSettingsState(state => state.calm.hideNicknames);
const hideNicknames = typeof hide !== 'undefined' ? hide : hideState; const hideNicknames = typeof hide !== 'undefined' ? hide : hideState;
return !!(contact && contact.nickname && !hideNicknames); return Boolean(contact && contact.nickname && !hideNicknames);
} }
interface useHoveringInterface { interface useHoveringInterface {
@ -413,19 +413,25 @@ interface useHoveringInterface {
export const useHovering = (): useHoveringInterface => { export const useHovering = (): useHoveringInterface => {
const [hovering, setHovering] = useState(false); const [hovering, setHovering] = useState(false);
const onMouseOver = useCallback(() => setHovering(true), []) const onMouseOver = useCallback(() => setHovering(true), []);
const onMouseLeave = useCallback(() => setHovering(false), []) const onMouseLeave = useCallback(() => setHovering(false), []);
const bind = useMemo(() => ({ const bind = useMemo(() => ({
onMouseOver, onMouseOver,
onMouseLeave, onMouseLeave
}), [onMouseLeave, onMouseOver]); }), [onMouseLeave, onMouseOver]);
return useMemo(() => ({ hovering, bind }), [hovering, bind]); return useMemo(() => ({ hovering, bind }), [hovering, bind]);
}; };
export function withHovering<T>(Component: React.ComponentType<T>) {
return React.forwardRef((props, ref) => {
const { hovering, bind } = useHovering();
return <Component ref={ref} hovering={hovering} bind={bind} {...props} />
})
}
const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/; const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/;
export function getItemTitle(association: Association) { export function getItemTitle(association: Association): string {
if (DM_REGEX.test(association.resource)) { if (DM_REGEX.test(association.resource)) {
const [, , ship, name] = association.resource.split('/'); const [, , ship, name] = association.resource.split('/');
if (ship.slice(1) === window.ship) { if (ship.slice(1) === window.ship) {
@ -433,6 +439,6 @@ export function getItemTitle(association: Association) {
} }
return cite(ship); return cite(ship);
} }
return association.metadata.title || association.resource; return association.metadata.title ?? association.resource ?? '';
} }

View File

@ -1,13 +1,10 @@
import React, { import React, {
useContext, useCallback, useContext,
useState,
useCallback, useEffect, useState
useLayoutEffect, } from 'react';
useRef, import { Primitive } from '~/types';
useEffect, import usePreviousValue from './usePreviousValue';
} from "react";
import usePreviousValue from "./usePreviousValue";
import {Primitive} from "~/types";
export interface VirtualContextProps { export interface VirtualContextProps {
save: () => void; save: () => void;
@ -15,7 +12,7 @@ export interface VirtualContextProps {
} }
const fallback: VirtualContextProps = { const fallback: VirtualContextProps = {
save: () => {}, save: () => {},
restore: () => {}, restore: () => {}
}; };
export const VirtualContext = React.createContext(fallback); export const VirtualContext = React.createContext(fallback);
@ -27,7 +24,7 @@ export function useVirtual() {
export const withVirtual = <P extends {}>(Component: React.ComponentType<P>) => export const withVirtual = <P extends {}>(Component: React.ComponentType<P>) =>
React.forwardRef((props: P, ref) => ( React.forwardRef((props: P, ref) => (
<VirtualContext.Consumer> <VirtualContext.Consumer>
{(context) => <Component ref={ref} {...props} {...context} />} {context => <Component ref={ref} {...props} {...context} />}
</VirtualContext.Consumer> </VirtualContext.Consumer>
)); ));
@ -52,7 +49,7 @@ export function useVirtualResizeState(s: boolean) {
export function useVirtualResizeProp(prop: Primitive) { export function useVirtualResizeProp(prop: Primitive) {
const { save, restore } = useVirtual(); const { save, restore } = useVirtual();
const oldProp = usePreviousValue(prop) const oldProp = usePreviousValue(prop);
if(prop !== oldProp) { if(prop !== oldProp) {
save(); save();
@ -61,6 +58,4 @@ export function useVirtualResizeProp(prop: Primitive) {
useEffect(() => { useEffect(() => {
requestAnimationFrame(restore); requestAnimationFrame(restore);
}, [prop]); }, [prop]);
} }

View File

@ -1,7 +1,6 @@
import React from "react"; import React from 'react';
import { ReactElement } from "react"; import { UseStore } from 'zustand';
import { UseStore } from "zustand"; import { BaseState } from '../state/base';
import { BaseState } from "../state/base";
const withStateo = < const withStateo = <
StateType extends BaseState<any> StateType extends BaseState<any>
@ -16,19 +15,21 @@ const withStateo = <
(object, key) => ({ ...object, [key]: state[key] }), {} (object, key) => ({ ...object, [key]: state[key] }), {}
) )
) : useState(); ) : useState();
return <Component ref={ref} {...state} {...props} /> return <Component ref={ref} {...state} {...props} />;
}) });
}; };
const withState = < interface StatePicker extends Array<any> {
StateType extends BaseState<StateType>, 0: UseStore<any>;
stateKey extends keyof StateType 1?: string[];
>( }
const withState = (
Component: any, Component: any,
stores: ([UseStore<StateType>, stateKey[]])[], stores: StatePicker[]
) => { ) => {
return React.forwardRef((props, ref) => { return React.forwardRef((props, ref) => {
let stateProps: unknown = {}; const stateProps: unknown = {};
stores.forEach(([store, keys]) => { stores.forEach(([store, keys]) => {
const storeProps = Array.isArray(keys) const storeProps = Array.isArray(keys)
? store(state => keys.reduce( ? store(state => keys.reduce(
@ -37,8 +38,8 @@ const withState = <
: store(); : store();
Object.assign(stateProps, storeProps); Object.assign(stateProps, storeProps);
}); });
return <Component ref={ref} {...stateProps} {...props} /> return <Component ref={ref} {...stateProps} {...props} />;
}); });
} };
export default withState; export default withState;

View File

@ -18,10 +18,10 @@ export function getTitleFromWorkspace(
export function getGroupFromWorkspace( export function getGroupFromWorkspace(
workspace: Workspace workspace: Workspace
): string | undefined { ): string {
if (workspace.type === 'group') { if (workspace.type === 'group') {
return workspace.group; return workspace.group;
} }
return undefined; return '';
} }

View File

@ -1,5 +1,5 @@
import { StoreState } from '../store/type';
import { Cage } from '~/types/cage'; import { Cage } from '~/types/cage';
import { StoreState } from '../store/type';
type LocalState = Pick<StoreState, 'connection'>; type LocalState = Pick<StoreState, 'connection'>;

View File

@ -1,11 +1,7 @@
import _ from 'lodash';
import { compose } from 'lodash/fp';
import { ContactUpdate } from '@urbit/api'; import { ContactUpdate } from '@urbit/api';
import _ from 'lodash';
import useContactState, { ContactState } from '../state/contact';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useContactState, { ContactState } from '../state/contact';
export const ContactReducer = (json) => { export const ContactReducer = (json) => {
const data: ContactUpdate = _.get(json, 'contact-update', false); const data: ContactUpdate = _.get(json, 'contact-update', false);

View File

@ -1,8 +1,7 @@
import {StoreState} from '../store/type';
import type {GcpToken} from '../../types/gcp-state';
import type { Cage } from '~/types/cage'; import type { Cage } from '~/types/cage';
import useStorageState, { StorageState } from '../state/storage'; import type { GcpToken } from '../../types/gcp-state';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useStorageState, { StorageState } from '../state/storage';
export default class GcpReducer { export default class GcpReducer {
reduce(json: Cage) { reduce(json: Cage) {
@ -13,21 +12,21 @@ export default class GcpReducer {
} }
const reduceToken = (json: Cage, state: StorageState): StorageState => { const reduceToken = (json: Cage, state: StorageState): StorageState => {
let data = json['gcp-token']; const data = json['gcp-token'];
if (data) { if (data) {
setToken(data, state); setToken(data, state);
} }
return state; return state;
} };
const setToken = (data: any, state: StorageState): StorageState => { const setToken = (data: any, state: StorageState): StorageState => {
if (isToken(data)) { if (isToken(data)) {
state.gcp.token = data; state.gcp.token = data;
} }
return state; return state;
} };
const isToken = (token: any): token is GcpToken => { const isToken = (token: any): token is GcpToken => {
return (typeof(token.accessKey) === 'string' && return (typeof(token.accessKey) === 'string' &&
typeof(token.expiresIn) === 'number'); typeof(token.expiresIn) === 'number');
} };

View File

@ -1,9 +1,10 @@
import _ from 'lodash'; import { GraphNode } from '@urbit/api';
import BigIntOrderedMap from "@urbit/api/lib/BigIntOrderedMap"; import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import bigInt, { BigInteger } from 'big-integer';
import produce from 'immer'; import produce from 'immer';
import bigInt, { BigInteger } from "big-integer"; import _ from 'lodash';
import useGraphState, { GraphState } from '../state/graph';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useGraphState, { GraphState } from '../state/graph';
export const GraphReducer = (json) => { export const GraphReducer = (json) => {
const data = _.get(json, 'graph-update', false); const data = _.get(json, 'graph-update', false);
@ -36,13 +37,13 @@ const addNodesLoose = (json: any, state: GraphState): GraphState => {
_.set(state.looseNodes, [resource], indices); _.set(state.looseNodes, [resource], indices);
} }
return state; return state;
} };
const keys = (json, state: GraphState): GraphState => { const keys = (json, state: GraphState): GraphState => {
const data = _.get(json, 'keys', false); const data = _.get(json, 'keys', false);
if (data) { if (data) {
state.graphKeys = new Set(data.map((res) => { state.graphKeys = new Set(data.map((res) => {
let resource = res.ship + '/' + res.name; const resource = res.ship + '/' + res.name;
return resource; return resource;
})); }));
} }
@ -52,36 +53,32 @@ const keys = (json, state: GraphState): GraphState => {
const processNode = (node) => { const processNode = (node) => {
// is empty // is empty
if (!node.children) { if (!node.children) {
return produce(node, draft => { return produce<GraphNode>(node, (draft: GraphNode) => {
draft.children = new BigIntOrderedMap(); draft.children = new BigIntOrderedMap();
}); });
} }
// is graph // is graph
return produce(node, draft => { return produce<GraphNode>(node, (draft: GraphNode) => {
draft.children = new BigIntOrderedMap() draft.children = new BigIntOrderedMap<GraphNode>()
.gas(_.map(draft.children, (item, idx) => .gas(_.map(draft.children, (item, idx) =>
[bigInt(idx), processNode(item)] as [BigInteger, any] [bigInt(idx), processNode(item)] as [BigInteger, any]
)); ));
}); });
}; };
const addGraph = (json, state: GraphState): GraphState => { const addGraph = (json, state: GraphState): GraphState => {
const data = _.get(json, 'add-graph', false); const data = _.get(json, 'add-graph', false);
if (data) { if (data) {
if (!('graphs' in state)) { if (!('graphs' in state)) {
state.graphs = {}; state.graphs = {};
} }
let resource = data.resource.ship + '/' + data.resource.name; const resource = data.resource.ship + '/' + data.resource.name;
state.graphs[resource] = new BigIntOrderedMap(); state.graphs[resource] = new BigIntOrderedMap();
state.graphTimesentMap[resource] = {}; state.graphTimesentMap[resource] = {};
state.graphs[resource] = state.graphs[resource].gas(Object.keys(data.graph).map((idx) => {
state.graphs[resource] = state.graphs[resource].gas(Object.keys(data.graph).map(idx => {
return [bigInt(idx), processNode(data.graph[idx])]; return [bigInt(idx), processNode(data.graph[idx])];
})); }));
@ -93,11 +90,10 @@ const addGraph = (json, state: GraphState): GraphState => {
const removeGraph = (json, state: GraphState): GraphState => { const removeGraph = (json, state: GraphState): GraphState => {
const data = _.get(json, 'remove-graph', false); const data = _.get(json, 'remove-graph', false);
if (data) { if (data) {
if (!('graphs' in state)) { if (!('graphs' in state)) {
state.graphs = {}; state.graphs = {};
} }
let resource = data.ship + '/' + data.name; const resource = data.ship + '/' + data.name;
state.graphKeys.delete(resource); state.graphKeys.delete(resource);
delete state.graphs[resource]; delete state.graphs[resource];
} }
@ -121,12 +117,12 @@ const addNodes = (json, state) => {
} }
// set parent of graph // set parent of graph
let parNode = graph.get(index[0]); const parNode = graph.get(index[0]);
if (!parNode) { if (!parNode) {
console.error('parent node does not exist, cannot add child'); console.error('parent node does not exist, cannot add child');
return graph; return graph;
} }
return graph.set(index[0], produce(parNode, draft => { return graph.set(index[0], produce(parNode, (draft) => {
draft.children = _addNode(draft.children, index.slice(1), node); draft.children = _addNode(draft.children, index.slice(1), node);
})); }));
}; };
@ -137,7 +133,7 @@ const addNodes = (json, state) => {
} else { } else {
const child = graph.get(index[0]); const child = graph.get(index[0]);
if (child) { if (child) {
return graph.set(index[0], produce(child, draft => { return graph.set(index[0], produce(child, (draft) => {
draft.children = _remove(draft.children, index.slice(1)); draft.children = _remove(draft.children, index.slice(1));
})); }));
} }
@ -148,10 +144,12 @@ const addNodes = (json, state) => {
const _killByFuzzyTimestamp = (graph, resource, timestamp) => { const _killByFuzzyTimestamp = (graph, resource, timestamp) => {
if (state.graphTimesentMap[resource][timestamp]) { if (state.graphTimesentMap[resource][timestamp]) {
let index = state.graphTimesentMap[resource][timestamp]; const index = state.graphTimesentMap[resource][timestamp];
if (index.split('/').length === 0) { return graph; } if (index.split('/').length === 0) {
let indexArr = index.split('/').slice(1).map((ind) => { return graph;
}
const indexArr = index.split('/').slice(1).map((ind) => {
return bigInt(ind); return bigInt(ind);
}); });
@ -174,9 +172,11 @@ const addNodes = (json, state) => {
const data = _.get(json, 'add-nodes', false); const data = _.get(json, 'add-nodes', false);
if (data) { if (data) {
if (!('graphs' in state)) { return state; } if (!('graphs' in state)) {
return state;
}
let resource = data.resource.ship + '/' + data.resource.name; const resource = data.resource.ship + '/' + data.resource.name;
if (!(resource in state.graphs)) { if (!(resource in state.graphs)) {
state.graphs[resource] = new BigIntOrderedMap(); state.graphs[resource] = new BigIntOrderedMap();
} }
@ -187,47 +187,46 @@ const addNodes = (json, state) => {
state.graphKeys.add(resource); state.graphKeys.add(resource);
let indices = Array.from(Object.keys(data.nodes)); const indices = Array.from(Object.keys(data.nodes));
indices.sort((a, b) => { indices.sort((a, b) => {
let aArr = a.split('/'); const aArr = a.split('/');
let bArr = b.split('/'); const bArr = b.split('/');
return aArr.length - bArr.length; return aArr.length - bArr.length;
}); });
indices.forEach((index) => { indices.forEach((index) => {
let node = data.nodes[index]; const node = data.nodes[index];
const old = state.graphs[resource].size; const old = state.graphs[resource].size;
state.graphs[resource] = _removePending(state.graphs[resource], node.post, resource); state.graphs[resource] = _removePending(state.graphs[resource], node.post, resource);
const newSize = state.graphs[resource].size; const newSize = state.graphs[resource].size;
if (index.split('/').length === 0) {
if (index.split('/').length === 0) { return; } return;
let indexArr = index.split('/').slice(1).map((ind) => { }
const indexArr = index.split('/').slice(1).map((ind) => {
return bigInt(ind); return bigInt(ind);
}); });
if (indexArr.length === 0) { return state; } if (indexArr.length === 0) {
return state;
}
if (node.post.pending) { if (node.post.pending) {
state.graphTimesentMap[resource][node.post['time-sent']] = index; state.graphTimesentMap[resource][node.post['time-sent']] = index;
} }
state.graphs[resource] = _addNode( state.graphs[resource] = _addNode(
state.graphs[resource], state.graphs[resource],
indexArr, indexArr,
produce(node, draft => { produce(node, (draft) => {
draft.children = mapifyChildren(draft?.children || {}); draft.children = mapifyChildren(draft?.children || {});
}) })
); );
if(newSize !== old) { if(newSize !== old) {
console.log(`${resource}, (${old}, ${newSize}, ${state.graphs[resource].size})`); console.log(`${resource}, (${old}, ${newSize}, ${state.graphs[resource].size})`);
} }
}); });
} }
return state; return state;
}; };
@ -249,8 +248,8 @@ const removePosts = (json, state: GraphState): GraphState => {
} else { } else {
const child = graph.get(index[0]); const child = graph.get(index[0]);
if (child) { if (child) {
return graph.set(index[0], produce(draft => { return graph.set(index[0], produce((draft) => {
draft.children = _remove(draft.children, index.slice(1)) draft.children = _remove(draft.children, index.slice(1));
})); }));
} }
return graph; return graph;
@ -262,11 +261,15 @@ const removePosts = (json, state: GraphState): GraphState => {
if (data) { if (data) {
const { ship, name } = data.resource; const { ship, name } = data.resource;
const res = `${ship}/${name}`; const res = `${ship}/${name}`;
if (!(res in state.graphs)) { return state; } if (!(res in state.graphs)) {
return state;
}
data.indices.forEach((index) => { data.indices.forEach((index) => {
if (index.split('/').length === 0) { return; } if (index.split('/').length === 0) {
let indexArr = index.split('/').slice(1).map((ind) => { return;
}
const indexArr = index.split('/').slice(1).map((ind) => {
return bigInt(ind); return bigInt(ind);
}); });
state.graphs[res] = _remove(state.graphs[res], indexArr); state.graphs[res] = _remove(state.graphs[res], indexArr);

View File

@ -1,19 +1,16 @@
import { Enc } from '@urbit/api';
import {
Group,
GroupPolicy, GroupUpdate,
InvitePolicy, InvitePolicyDiff, OpenPolicy, OpenPolicyDiff, Tags
} from '@urbit/api/groups';
import _ from 'lodash'; import _ from 'lodash';
import { Cage } from '~/types/cage'; import { Cage } from '~/types/cage';
import {
GroupUpdate,
Group,
Tags,
GroupPolicy,
OpenPolicyDiff,
OpenPolicy,
InvitePolicyDiff,
InvitePolicy
} from '@urbit/api/groups';
import { Enc } from '@urbit/api';
import { resourceAsPath } from '../lib/util'; import { resourceAsPath } from '../lib/util';
import useGroupState, { GroupState } from '../state/group';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useGroupState, { GroupState } from '../state/group';
function decodeGroup(group: Enc<Group>): Group { function decodeGroup(group: Enc<Group>): Group {
const members = new Set(group.members); const members = new Set(group.members);
@ -41,7 +38,7 @@ function decodePolicy(policy: Enc<GroupPolicy>): GroupPolicy {
function decodeTags(tags: Enc<Tags>): Tags { function decodeTags(tags: Enc<Tags>): Tags {
return _.reduce( return _.reduce(
tags, tags,
(acc, ships, key): Tags => { (acc, ships: any, key): Tags => {
if (key.search(/\\/) === -1) { if (key.search(/\\/) === -1) {
acc.role[key] = new Set(ships); acc.role[key] = new Set(ships);
return acc; return acc;
@ -69,11 +66,10 @@ export default class GroupReducer {
addGroup, addGroup,
removeGroup, removeGroup,
changePolicy, changePolicy,
expose, expose
]); ]);
} }
} }
} }
const initial = (json: GroupUpdate, state: GroupState): GroupState => { const initial = (json: GroupUpdate, state: GroupState): GroupState => {
const data = json['initial']; const data = json['initial'];
@ -81,7 +77,7 @@ const initial = (json: GroupUpdate, state: GroupState): GroupState => {
state.groups = _.mapValues(data, decodeGroup); state.groups = _.mapValues(data, decodeGroup);
} }
return state; return state;
} };
const initialGroup = (json: GroupUpdate, state: GroupState): GroupState => { const initialGroup = (json: GroupUpdate, state: GroupState): GroupState => {
if ('initialGroup' in json) { if ('initialGroup' in json) {
@ -90,7 +86,7 @@ const initialGroup = (json: GroupUpdate, state: GroupState): GroupState => {
state.groups[path] = decodeGroup(group); state.groups[path] = decodeGroup(group);
} }
return state; return state;
} };
const addGroup = (json: GroupUpdate, state: GroupState): GroupState => { const addGroup = (json: GroupUpdate, state: GroupState): GroupState => {
if ('addGroup' in json) { if ('addGroup' in json) {
@ -104,7 +100,7 @@ const addGroup = (json: GroupUpdate, state: GroupState): GroupState => {
}; };
} }
return state; return state;
} };
const removeGroup = (json: GroupUpdate, state: GroupState): GroupState => { const removeGroup = (json: GroupUpdate, state: GroupState): GroupState => {
if('removeGroup' in json) { if('removeGroup' in json) {
@ -113,7 +109,7 @@ const removeGroup = (json: GroupUpdate, state: GroupState): GroupState => {
delete state.groups[resourcePath]; delete state.groups[resourcePath];
} }
return state; return state;
} };
const addMembers = (json: GroupUpdate, state: GroupState): GroupState => { const addMembers = (json: GroupUpdate, state: GroupState): GroupState => {
if ('addMembers' in json) { if ('addMembers' in json) {
@ -130,7 +126,7 @@ const addMembers = (json: GroupUpdate, state: GroupState): GroupState => {
} }
} }
return state; return state;
} };
const removeMembers = (json: GroupUpdate, state: GroupState): GroupState => { const removeMembers = (json: GroupUpdate, state: GroupState): GroupState => {
if ('removeMembers' in json) { if ('removeMembers' in json) {
@ -141,7 +137,7 @@ const removeMembers = (json: GroupUpdate, state: GroupState): GroupState => {
} }
} }
return state; return state;
} };
const addTag = (json: GroupUpdate, state: GroupState): GroupState => { const addTag = (json: GroupUpdate, state: GroupState): GroupState => {
if ('addTag' in json) { if ('addTag' in json) {
@ -177,7 +173,7 @@ const removeTag = (json: GroupUpdate, state: GroupState): GroupState => {
_.set(tags, tagAccessors, tagged); _.set(tags, tagAccessors, tagged);
} }
return state; return state;
} };
const changePolicy = (json: GroupUpdate, state: GroupState): GroupState => { const changePolicy = (json: GroupUpdate, state: GroupState): GroupState => {
if ('changePolicy' in json && state) { if ('changePolicy' in json && state) {
@ -195,7 +191,7 @@ const changePolicy = (json: GroupUpdate, state: GroupState): GroupState => {
} }
} }
return state; return state;
} };
const expose = (json: GroupUpdate, state: GroupState): GroupState => { const expose = (json: GroupUpdate, state: GroupState): GroupState => {
if( 'expose' in json && state) { if( 'expose' in json && state) {
@ -204,7 +200,7 @@ const expose = (json: GroupUpdate, state: GroupState): GroupState => {
state.groups[resourcePath].hidden = false; state.groups[resourcePath].hidden = false;
} }
return state; return state;
} };
const inviteChangePolicy = (diff: InvitePolicyDiff, policy: InvitePolicy) => { const inviteChangePolicy = (diff: InvitePolicyDiff, policy: InvitePolicy) => {
if ('addInvites' in diff) { if ('addInvites' in diff) {
@ -220,7 +216,7 @@ const inviteChangePolicy = (diff: InvitePolicyDiff, policy: InvitePolicy) => {
} else { } else {
console.log('bad policy change'); console.log('bad policy change');
} }
} };
const openChangePolicy = (diff: OpenPolicyDiff, policy: OpenPolicy) => { const openChangePolicy = (diff: OpenPolicyDiff, policy: OpenPolicy) => {
if ('allowRanks' in diff) { if ('allowRanks' in diff) {
@ -246,4 +242,4 @@ const openChangePolicy = (diff: OpenPolicyDiff, policy: OpenPolicy) => {
} else { } else {
console.log('bad policy change'); console.log('bad policy change');
} }
} };

View File

@ -1,5 +1,4 @@
import { GroupUpdate } from '@urbit/api/groups'; import { GroupUpdate } from '@urbit/api/groups';
import { resourceAsPath } from '~/logic/lib/util';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useGroupState, { GroupState } from '../state/group'; import useGroupState, { GroupState } from '../state/group';
@ -18,7 +17,7 @@ const started = (json: any, state: GroupState): GroupState => {
state.pendingJoin[resource] = request; state.pendingJoin[resource] = request;
} }
return state; return state;
} };
const progress = (json: any, state: GroupState): GroupState => { const progress = (json: any, state: GroupState): GroupState => {
const data = json.progress; const data = json.progress;
@ -26,7 +25,6 @@ const progress = (json: any, state: GroupState): GroupState => {
const { progress, resource } = data; const { progress, resource } = data;
state.pendingJoin[resource].progress = progress; state.pendingJoin[resource].progress = progress;
if(progress === 'done') { if(progress === 'done') {
setTimeout(() => { setTimeout(() => {
delete state.pendingJoin[resource]; delete state.pendingJoin[resource];
}, 10000); }, 10000);
@ -41,8 +39,7 @@ const hide = (json: any, state: GroupState) => {
state.pendingJoin[data].hidden = true; state.pendingJoin[data].hidden = true;
} }
return state; return state;
};
}
export const GroupViewReducer = (json: any) => { export const GroupViewReducer = (json: any) => {
const data = json['group-view-update']; const data = json['group-view-update'];

View File

@ -2,13 +2,13 @@ import {
NotifIndex, NotifIndex,
Timebox Timebox
} from '@urbit/api'; } from '@urbit/api';
import { makePatDa } from '~/logic/lib/util';
import _ from 'lodash';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap'; import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import useHarkState, { HarkState } from '../state/hark';
import { compose } from 'lodash/fp';
import { reduceState } from '../state/base';
import { BigInteger } from 'big-integer'; import { BigInteger } from 'big-integer';
import _ from 'lodash';
import { compose } from 'lodash/fp';
import { makePatDa } from '~/logic/lib/util';
import { reduceState } from '../state/base';
import useHarkState, { HarkState } from '../state/hark';
export const HarkReducer = (json: any) => { export const HarkReducer = (json: any) => {
const data = _.get(json, 'harkUpdate', false); const data = _.get(json, 'harkUpdate', false);
@ -22,7 +22,7 @@ export const HarkReducer = (json: any) => {
graphIgnore, graphIgnore,
graphListen, graphListen,
graphWatchSelf, graphWatchSelf,
graphMentions, graphMentions
]); ]);
} }
const groupHookData = _.get(json, 'hark-group-hook-update', false); const groupHookData = _.get(json, 'hark-group-hook-update', false);
@ -30,7 +30,7 @@ export const HarkReducer = (json: any) => {
reduceState<HarkState, any>(useHarkState, groupHookData, [ reduceState<HarkState, any>(useHarkState, groupHookData, [
groupInitial, groupInitial,
groupListen, groupListen,
groupIgnore, groupIgnore
]); ]);
} }
}; };
@ -52,9 +52,9 @@ function reduce(data, state) {
unreadEach, unreadEach,
seenIndex, seenIndex,
removeGraph, removeGraph,
readAll, readAll
]; ];
const reducer = compose(reducers.map(r => s => { const reducer = compose(reducers.map(r => (s) => {
return r(data, s); return r(data, s);
})); }));
return reducer(state); return reducer(state);
@ -63,13 +63,21 @@ function reduce(data, state) {
function calculateCount(json: any, state: HarkState) { function calculateCount(json: any, state: HarkState) {
let count = 0; let count = 0;
_.forEach(state.unreads.graph, (graphs) => { _.forEach(state.unreads.graph, (graphs) => {
_.forEach(graphs, graph => { _.forEach(graphs, (graph) => {
count += (graph?.notifications || []).length; if (typeof graph?.notifications === 'object') {
count += graph?.notifications.length;
} else {
count += 0;
}
}); });
}); });
_.forEach(state.unreads.group, group => { _.forEach(state.unreads.group, (group) => {
count += (group?.notifications || []).length; if (typeof group?.notifications === 'object') {
}) count += group?.notifications.length;
} else {
count += 0;
}
});
state.notificationsCount = count; state.notificationsCount = count;
return state; return state;
} }
@ -260,7 +268,7 @@ function updateUnreads(state: HarkState, index: NotifIndex, f: (us: Set<string>)
if(!('graph' in index)) { if(!('graph' in index)) {
return state; return state;
} }
let unreads: any = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>()); const unreads: any = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>());
f(unreads); f(unreads);
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads); _.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads);
@ -294,19 +302,18 @@ function removeNotificationFromUnread(state: HarkState, index: NotifIndex, time:
const path = [index.graph.graph, index.graph.index, 'notifications']; const path = [index.graph.graph, index.graph.index, 'notifications'];
const curr = _.get(state.unreads.graph, path, []); const curr = _.get(state.unreads.graph, path, []);
_.set(state.unreads.graph, path, _.set(state.unreads.graph, path,
curr.filter(c => !(c.time.eq(time) && notifIdxEqual(c.index, index))), curr.filter(c => !(c.time.eq(time) && notifIdxEqual(c.index, index)))
); );
} else if ('group' in index) { } else if ('group' in index) {
const path = [index.group.group, 'notifications']; const path = [index.group.group, 'notifications'];
const curr = _.get(state.unreads.group, path, []); const curr = _.get(state.unreads.group, path, []);
_.set(state.unreads.group, path, _.set(state.unreads.group, path,
curr.filter(c => !(c.time.eq(time) && notifIdxEqual(c.index, index))), curr.filter(c => !(c.time.eq(time) && notifIdxEqual(c.index, index)))
); );
} }
} }
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'unreads' | 'last', f: (x: number) => number) { function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'unreads' | 'last', f: (x: number) => number) {
if('graph' in index) { if('graph' in index) {
const curr: any = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, statField], 0); const curr: any = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, statField], 0);
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, statField], f(curr)); _.set(state.unreads.graph, [index.graph.graph, index.graph.index, statField], f(curr));
@ -359,7 +366,7 @@ const timebox = (json: any, state: HarkState): HarkState => {
function more(json: any, state: HarkState): HarkState { function more(json: any, state: HarkState): HarkState {
const data = _.get(json, 'more', false); const data = _.get(json, 'more', false);
if (data) { if (data) {
_.forEach(data, d => { _.forEach(data, (d) => {
reduce(d, state); reduce(d, state);
}); });
} }
@ -431,7 +438,7 @@ function archive(json: any, state: HarkState): HarkState {
const data = _.get(json, 'archive', false); const data = _.get(json, 'archive', false);
if (data) { if (data) {
const { index } = data; const { index } = data;
removeNotificationFromUnread(state, index, makePatDa(data.time)) removeNotificationFromUnread(state, index, makePatDa(data.time));
const time = makePatDa(data.time); const time = makePatDa(data.time);
const timebox = state.notifications.get(time); const timebox = state.notifications.get(time);
if (!timebox) { if (!timebox) {

View File

@ -1,11 +1,8 @@
import _ from 'lodash';
import { compose } from 'lodash/fp';
import { InviteUpdate } from '@urbit/api/invite'; import { InviteUpdate } from '@urbit/api/invite';
import _ from 'lodash';
import { Cage } from '~/types/cage'; import { Cage } from '~/types/cage';
import useInviteState, { InviteState } from '../state/invite';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useInviteState, { InviteState } from '../state/invite';
export default class InviteReducer { export default class InviteReducer {
reduce(json: Cage) { reduce(json: Cage) {
@ -17,7 +14,7 @@ export default class InviteReducer {
deleteInvite, deleteInvite,
invite, invite,
accepted, accepted,
decline, decline
]); ]);
} }
} }
@ -29,7 +26,7 @@ const initial = (json: InviteUpdate, state: InviteState): InviteState => {
state.invites = data; state.invites = data;
} }
return state; return state;
} };
const create = (json: InviteUpdate, state: InviteState): InviteState => { const create = (json: InviteUpdate, state: InviteState): InviteState => {
const data = _.get(json, 'create', false); const data = _.get(json, 'create', false);
@ -37,7 +34,7 @@ const create = (json: InviteUpdate, state: InviteState): InviteState => {
state.invites[data] = {}; state.invites[data] = {};
} }
return state; return state;
} };
const deleteInvite = (json: InviteUpdate, state: InviteState): InviteState => { const deleteInvite = (json: InviteUpdate, state: InviteState): InviteState => {
const data = _.get(json, 'delete', false); const data = _.get(json, 'delete', false);
@ -45,7 +42,7 @@ const deleteInvite = (json: InviteUpdate, state: InviteState): InviteState => {
delete state.invites[data]; delete state.invites[data];
} }
return state; return state;
} };
const invite = (json: InviteUpdate, state: InviteState): InviteState => { const invite = (json: InviteUpdate, state: InviteState): InviteState => {
const data = _.get(json, 'invite', false); const data = _.get(json, 'invite', false);
@ -53,7 +50,7 @@ const invite = (json: InviteUpdate, state: InviteState): InviteState => {
state.invites[data.term][data.uid] = data.invite; state.invites[data.term][data.uid] = data.invite;
} }
return state; return state;
} };
const accepted = (json: InviteUpdate, state: InviteState): InviteState => { const accepted = (json: InviteUpdate, state: InviteState): InviteState => {
const data = _.get(json, 'accepted', false); const data = _.get(json, 'accepted', false);
@ -61,7 +58,7 @@ const accepted = (json: InviteUpdate, state: InviteState): InviteState => {
delete state.invites[data.term][data.uid]; delete state.invites[data.term][data.uid];
} }
return state; return state;
} };
const decline = (json: InviteUpdate, state: InviteState): InviteState => { const decline = (json: InviteUpdate, state: InviteState): InviteState => {
const data = _.get(json, 'decline', false); const data = _.get(json, 'decline', false);
@ -69,4 +66,4 @@ const decline = (json: InviteUpdate, state: InviteState): InviteState => {
delete state.invites[data.term][data.uid]; delete state.invites[data.term][data.uid];
} }
return state; return state;
} };

View File

@ -1,9 +1,8 @@
import _ from 'lodash'; import _ from 'lodash';
import { LaunchUpdate, WeatherState } from '~/types/launch-update';
import { Cage } from '~/types/cage'; import { Cage } from '~/types/cage';
import useLaunchState, { LaunchState } from '../state/launch'; import { LaunchUpdate, WeatherState } from '~/types/launch-update';
import { compose } from 'lodash/fp';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useLaunchState, { LaunchState } from '../state/launch';
export default class LaunchReducer { export default class LaunchReducer {
reduce(json: Cage) { reduce(json: Cage) {
@ -14,29 +13,36 @@ export default class LaunchReducer {
changeFirstTime, changeFirstTime,
changeOrder, changeOrder,
changeFirstTime, changeFirstTime,
changeIsShown, changeIsShown
]); ]);
} }
const weatherData: WeatherState | boolean | Record<string, never> = _.get(json, 'weather', false); const weatherData: WeatherState | boolean | Record<string, never> = _.get(json, 'weather', false);
if (weatherData) { if (weatherData) {
useLaunchState.getState().set(state => { useLaunchState.getState().set((state) => {
state.weather = weatherData; state.weather = weatherData;
}); });
} }
const locationData = _.get(json, 'location', false); const locationData = _.get(json, 'location', false);
if (locationData) { if (locationData) {
useLaunchState.getState().set(state => { useLaunchState.getState().set((state) => {
state.userLocation = locationData; state.userLocation = locationData;
}); });
} }
const baseHash = _.get(json, 'baseHash', false); const baseHash = _.get(json, 'baseHash', false);
if (baseHash) { if (baseHash) {
useLaunchState.getState().set(state => { useLaunchState.getState().set((state) => {
state.baseHash = baseHash; state.baseHash = baseHash;
}) });
}
const runtimeLag = _.get(json, 'runtimeLag', null);
if (runtimeLag !== null) {
useLaunchState.getState().set(state => {
state.runtimeLag = runtimeLag;
});
} }
} }
} }
@ -44,12 +50,12 @@ export default class LaunchReducer {
export const initial = (json: LaunchUpdate, state: LaunchState): LaunchState => { export const initial = (json: LaunchUpdate, state: LaunchState): LaunchState => {
const data = _.get(json, 'initial', false); const data = _.get(json, 'initial', false);
if (data) { if (data) {
Object.keys(data).forEach(key => { Object.keys(data).forEach((key) => {
state[key] = data[key]; state[key] = data[key];
}); });
} }
return state; return state;
} };
export const changeFirstTime = (json: LaunchUpdate, state: LaunchState): LaunchState => { export const changeFirstTime = (json: LaunchUpdate, state: LaunchState): LaunchState => {
const data = _.get(json, 'changeFirstTime', false); const data = _.get(json, 'changeFirstTime', false);
@ -57,7 +63,7 @@ export const changeFirstTime = (json: LaunchUpdate, state: LaunchState): LaunchS
state.firstTime = data; state.firstTime = data;
} }
return state; return state;
} };
export const changeOrder = (json: LaunchUpdate, state: LaunchState): LaunchState => { export const changeOrder = (json: LaunchUpdate, state: LaunchState): LaunchState => {
const data = _.get(json, 'changeOrder', false); const data = _.get(json, 'changeOrder', false);
@ -65,7 +71,7 @@ export const changeOrder = (json: LaunchUpdate, state: LaunchState): LaunchState
state.tileOrdering = data; state.tileOrdering = data;
} }
return state; return state;
} };
export const changeIsShown = (json: LaunchUpdate, state: LaunchState): LaunchState => { export const changeIsShown = (json: LaunchUpdate, state: LaunchState): LaunchState => {
const data = _.get(json, 'changeIsShown', false); const data = _.get(json, 'changeIsShown', false);
@ -76,4 +82,4 @@ export const changeIsShown = (json: LaunchUpdate, state: LaunchState): LaunchSta
} }
} }
return state; return state;
} };

View File

@ -1,11 +1,8 @@
import _ from 'lodash';
import { compose } from 'lodash/fp';
import { MetadataUpdate } from '@urbit/api/metadata'; import { MetadataUpdate } from '@urbit/api/metadata';
import _ from 'lodash';
import { Cage } from '~/types/cage'; import { Cage } from '~/types/cage';
import useMetadataState, { MetadataState } from '../state/metadata';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useMetadataState, { MetadataState } from '../state/metadata';
export default class MetadataReducer { export default class MetadataReducer {
reduce(json: Cage) { reduce(json: Cage) {
@ -16,7 +13,7 @@ export default class MetadataReducer {
add, add,
update, update,
remove, remove,
groupInitial, groupInitial
]); ]);
} }
} }
@ -28,7 +25,7 @@ const groupInitial = (json: MetadataUpdate, state: MetadataState): MetadataState
associations(data, state); associations(data, state);
} }
return state; return state;
} };
const associations = (json: MetadataUpdate, state: MetadataState): MetadataState => { const associations = (json: MetadataUpdate, state: MetadataState): MetadataState => {
const data = _.get(json, 'associations', false); const data = _.get(json, 'associations', false);
@ -50,7 +47,7 @@ const associations = (json: MetadataUpdate, state: MetadataState): MetadataState
state.associations = metadata; state.associations = metadata;
} }
return state; return state;
} };
const add = (json: MetadataUpdate, state: MetadataState): MetadataState => { const add = (json: MetadataUpdate, state: MetadataState): MetadataState => {
const data = _.get(json, 'add', false); const data = _.get(json, 'add', false);
@ -70,7 +67,7 @@ const add = (json: MetadataUpdate, state: MetadataState): MetadataState => {
state.associations = metadata; state.associations = metadata;
} }
return state; return state;
} };
const update = (json: MetadataUpdate, state: MetadataState): MetadataState => { const update = (json: MetadataUpdate, state: MetadataState): MetadataState => {
const data = _.get(json, 'update-metadata', false); const data = _.get(json, 'update-metadata', false);
@ -90,7 +87,7 @@ const update = (json: MetadataUpdate, state: MetadataState): MetadataState => {
state.associations = metadata; state.associations = metadata;
} }
return state; return state;
} };
const remove = (json: MetadataUpdate, state: MetadataState): MetadataState => { const remove = (json: MetadataUpdate, state: MetadataState): MetadataState => {
const data = _.get(json, 'remove', false); const data = _.get(json, 'remove', false);
@ -105,4 +102,4 @@ const remove = (json: MetadataUpdate, state: MetadataState): MetadataState => {
state.associations = metadata; state.associations = metadata;
} }
return state; return state;
} };

View File

@ -1,11 +1,9 @@
import _ from 'lodash'; import _ from 'lodash';
import { compose } from 'lodash/fp';
import { Cage } from '~/types/cage'; import { Cage } from '~/types/cage';
import { S3Update } from '~/types/s3-update'; import { S3Update } from '~/types/s3-update';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useStorageState, { StorageState } from '../state/storage'; import useStorageState, { StorageState } from '../state/storage';
export default class S3Reducer { export default class S3Reducer {
reduce(json: Cage) { reduce(json: Cage) {
const data = _.get(json, 's3-update', false); const data = _.get(json, 's3-update', false);
@ -18,7 +16,7 @@ export default class S3Reducer {
removeBucket, removeBucket,
endpoint, endpoint,
accessKeyId, accessKeyId,
secretAccessKey, secretAccessKey
]); ]);
} }
} }
@ -30,7 +28,7 @@ const credentials = (json: S3Update, state: StorageState): StorageState => {
state.s3.credentials = data; state.s3.credentials = data;
} }
return state; return state;
} };
const configuration = (json: S3Update, state: StorageState): StorageState => { const configuration = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'configuration', false); const data = _.get(json, 'configuration', false);
@ -41,7 +39,7 @@ const configuration = (json: S3Update, state: StorageState): StorageState => {
}; };
} }
return state; return state;
} };
const currentBucket = (json: S3Update, state: StorageState): StorageState => { const currentBucket = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'setCurrentBucket', false); const data = _.get(json, 'setCurrentBucket', false);
@ -49,7 +47,7 @@ const currentBucket = (json: S3Update, state: StorageState): StorageState => {
state.s3.configuration.currentBucket = data; state.s3.configuration.currentBucket = data;
} }
return state; return state;
} };
const addBucket = (json: S3Update, state: StorageState): StorageState => { const addBucket = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'addBucket', false); const data = _.get(json, 'addBucket', false);
@ -58,7 +56,7 @@ const addBucket = (json: S3Update, state: StorageState): StorageState => {
state.s3.configuration.buckets.add(data); state.s3.configuration.buckets.add(data);
} }
return state; return state;
} };
const removeBucket = (json: S3Update, state: StorageState): StorageState => { const removeBucket = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'removeBucket', false); const data = _.get(json, 'removeBucket', false);
@ -66,7 +64,7 @@ const removeBucket = (json: S3Update, state: StorageState): StorageState => {
state.s3.configuration.buckets.delete(data); state.s3.configuration.buckets.delete(data);
} }
return state; return state;
} };
const endpoint = (json: S3Update, state: StorageState): StorageState => { const endpoint = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'setEndpoint', false); const data = _.get(json, 'setEndpoint', false);
@ -74,7 +72,7 @@ const endpoint = (json: S3Update, state: StorageState): StorageState => {
state.s3.credentials.endpoint = data; state.s3.credentials.endpoint = data;
} }
return state; return state;
} };
const accessKeyId = (json: S3Update , state: StorageState): StorageState => { const accessKeyId = (json: S3Update , state: StorageState): StorageState => {
const data = _.get(json, 'setAccessKeyId', false); const data = _.get(json, 'setAccessKeyId', false);
@ -82,7 +80,7 @@ const accessKeyId = (json: S3Update , state: StorageState): StorageState => {
state.s3.credentials.accessKeyId = data; state.s3.credentials.accessKeyId = data;
} }
return state; return state;
} };
const secretAccessKey = (json: S3Update, state: StorageState): StorageState => { const secretAccessKey = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'setSecretAccessKey', false); const data = _.get(json, 'setSecretAccessKey', false);
@ -90,4 +88,4 @@ const secretAccessKey = (json: S3Update, state: StorageState): StorageState => {
state.s3.credentials.secretAccessKey = data; state.s3.credentials.secretAccessKey = data;
} }
return state; return state;
} };

View File

@ -1,26 +1,25 @@
import { SettingsUpdate } from '@urbit/api/settings';
import _ from 'lodash'; import _ from 'lodash';
import useSettingsState, { SettingsState } from '~/logic/state/settings'; import useSettingsState, { SettingsState } from '~/logic/state/settings';
import { SettingsUpdate } from '@urbit/api/settings';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import { string } from 'prop-types';
export default class SettingsReducer { export default class SettingsReducer {
reduce(json: any) { reduce(json: any) {
let data = json["settings-event"]; let data = json['settings-event'];
if (data) { if (data) {
reduceState<SettingsState, SettingsUpdate>(useSettingsState, data, [ reduceState<SettingsState, SettingsUpdate>(useSettingsState, data, [
this.putBucket, this.putBucket,
this.delBucket, this.delBucket,
this.putEntry, this.putEntry,
this.delEntry, this.delEntry
]); ]);
} }
data = json["settings-data"]; data = json['settings-data'];
if (data) { if (data) {
reduceState<SettingsState, SettingsUpdate>(useSettingsState, data, [ reduceState<SettingsState, SettingsUpdate>(useSettingsState, data, [
this.getAll, this.getAll,
this.getBucket, this.getBucket,
this.getEntry, this.getEntry
]); ]);
} }
} }
@ -28,7 +27,7 @@ export default class SettingsReducer {
putBucket(json: SettingsUpdate, state: SettingsState): SettingsState { putBucket(json: SettingsUpdate, state: SettingsState): SettingsState {
const data = _.get(json, 'put-bucket', false); const data = _.get(json, 'put-bucket', false);
if (data) { if (data) {
state[data["bucket-key"]] = data.bucket; state[data['bucket-key']] = data.bucket;
} }
return state; return state;
} }
@ -63,7 +62,7 @@ export default class SettingsReducer {
getAll(json: any, state: SettingsState): SettingsState { getAll(json: any, state: SettingsState): SettingsState {
const data = _.get(json, 'all'); const data = _.get(json, 'all');
if(data) { if(data) {
_.mergeWith(state, data, (obj, src) => _.isArray(src) ? src : undefined) _.mergeWith(state, data, (obj, src) => _.isArray(src) ? src : undefined);
} }
return state; return state;
} }

View File

@ -1,11 +1,10 @@
import produce, { setAutoFreeze } from "immer"; import produce, { setAutoFreeze } from 'immer';
import { compose } from "lodash/fp"; import { compose } from 'lodash/fp';
import create, { State, UseStore } from "zustand"; import create, { State, UseStore } from 'zustand';
import { persist, devtools } from "zustand/middleware"; import { persist } from 'zustand/middleware';
setAutoFreeze(false); setAutoFreeze(false);
export const stateSetter = <StateType>( export const stateSetter = <StateType>(
fn: (state: StateType) => void, fn: (state: StateType) => void,
set set
@ -22,7 +21,7 @@ export const reduceState = <
reducers: ((data: UpdateType, state: StateType) => StateType)[] reducers: ((data: UpdateType, state: StateType) => StateType)[]
): void => { ): void => {
const reducer = compose(reducers.map(r => sta => r(data, sta))); const reducer = compose(reducers.map(r => sta => r(data, sta)));
state.getState().set(state => { state.getState().set((state) => {
reducer(state); reducer(state);
}); });
}; };
@ -36,25 +35,24 @@ export const stateStorageKey = (stateName: string) => {
}; };
(window as any).clearStates = () => { (window as any).clearStates = () => {
stateStorageKeys.forEach(key => { stateStorageKeys.forEach((key) => {
localStorage.removeItem(key); localStorage.removeItem(key);
}); });
} };
export interface BaseState<StateType> extends State { export interface BaseState<StateType> extends State {
set: (fn: (state: StateType) => void) => void; set: (fn: (state: StateType) => void) => void;
} }
export const createState = <StateType extends BaseState<any>>( export const createState = <T extends BaseState<T>>(
name: string, name: string,
properties: Omit<StateType, 'set'>, properties: { [K in keyof Omit<T, 'set'>]: T[K] },
blacklist: string[] = [] blacklist: string[] = []
): UseStore<StateType> => create(persist((set, get) => ({ ): UseStore<T> => create(persist((set, get) => ({
// TODO why does this typing break?
set: fn => stateSetter(fn, set), set: fn => stateSetter(fn, set),
...properties ...properties as any
}), { }), {
blacklist, blacklist,
name: stateStorageKey(name), name: stateStorageKey(name),
version: process.env.LANDSCAPE_SHORTHASH version: process.env.LANDSCAPE_SHORTHASH as any
})); }));

View File

@ -1,7 +1,6 @@
import { Patp, Rolodex, Scry, Contact } from "@urbit/api"; import { Contact, Patp, Rolodex } from '@urbit/api';
import { useCallback } from 'react';
import { BaseState, createState } from "./base"; import { BaseState, createState } from './base';
import {useCallback} from "react";
export interface ContactState extends BaseState<ContactState> { export interface ContactState extends BaseState<ContactState> {
contacts: Rolodex; contacts: Rolodex;
@ -13,7 +12,7 @@ export interface ContactState extends BaseState<ContactState> {
const useContactState = createState<ContactState>('Contact', { const useContactState = createState<ContactState>('Contact', {
contacts: {}, contacts: {},
nackedContacts: new Set(), nackedContacts: new Set(),
isContactPublic: false, isContactPublic: false
// fetchIsAllowed: async ( // fetchIsAllowed: async (
// entity, // entity,
// name, // name,
@ -36,7 +35,7 @@ export function useContact(ship: string) {
} }
export function useOurContact() { export function useOurContact() {
return useContact(`~${window.ship}`) return useContact(`~${window.ship}`);
} }
export default useContactState; export default useContactState;

View File

@ -1,7 +1,6 @@
import { Graphs, decToUd, numToUd, GraphNode, deSig, Association, resourceFromPath } from "@urbit/api"; import { Association, deSig, GraphNode, Graphs, resourceFromPath } from '@urbit/api';
import {useCallback} from "react"; import { useCallback } from 'react';
import { BaseState, createState } from './base';
import { BaseState, createState } from "./base";
export interface GraphState extends BaseState<GraphState> { export interface GraphState extends BaseState<GraphState> {
graphs: Graphs; graphs: Graphs;
@ -22,14 +21,14 @@ export interface GraphState extends BaseState<GraphState> {
// getYoungerSiblings: (ship: string, resource: string, count: number, index?: string) => Promise<void>; // getYoungerSiblings: (ship: string, resource: string, count: number, index?: string) => Promise<void>;
// getGraphSubset: (ship: string, resource: string, start: string, end: string) => Promise<void>; // getGraphSubset: (ship: string, resource: string, start: string, end: string) => Promise<void>;
// getNode: (ship: string, resource: string, index: string) => Promise<void>; // getNode: (ship: string, resource: string, index: string) => Promise<void>;
}; }
const useGraphState = createState<GraphState>('Graph', { const useGraphState = createState<GraphState>('Graph', {
graphs: {}, graphs: {},
graphKeys: new Set(), graphKeys: new Set(),
looseNodes: {}, looseNodes: {},
pendingIndices: {}, pendingIndices: {},
graphTimesentMap: {}, graphTimesentMap: {}
// getKeys: async () => { // getKeys: async () => {
// const api = useApi(); // const api = useApi();
// const keys = await api.scry({ // const keys = await api.scry({

View File

@ -1,18 +1,17 @@
import { Path, JoinRequests, Association, Group } from "@urbit/api"; import { Association, Group, JoinRequests } from '@urbit/api';
import { useCallback } from 'react';
import { BaseState, createState } from "./base"; import { BaseState, createState } from './base';
import {useCallback} from "react";
export interface GroupState extends BaseState<GroupState> { export interface GroupState extends BaseState<GroupState> {
groups: { groups: {
[group: string]: Group; [group: string]: Group;
} }
pendingJoin: JoinRequests; pendingJoin: JoinRequests;
}; }
const useGroupState = createState<GroupState>('Group', { const useGroupState = createState<GroupState>('Group', {
groups: {}, groups: {},
pendingJoin: {}, pendingJoin: {}
}, ['groups']); }, ['groups']);
export function useGroup(group: string) { export function useGroup(group: string) {

View File

@ -1,8 +1,7 @@
import { NotificationGraphConfig, Timebox, Unreads, dateToDa } from "@urbit/api"; import { NotificationGraphConfig, Timebox, Unreads } from '@urbit/api';
import BigIntOrderedMap from "@urbit/api/lib/BigIntOrderedMap"; import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
// import { harkGraphHookReducer, harkGroupHookReducer, harkReducer } from "~/logic/subscription/hark"; // import { harkGraphHookReducer, harkGroupHookReducer, harkReducer } from "~/logic/subscription/hark";
import { BaseState, createState } from "./base"; import { BaseState, createState } from './base';
export const HARK_FETCH_MORE_COUNT = 3; export const HARK_FETCH_MORE_COUNT = 3;
@ -17,7 +16,7 @@ export interface HarkState extends BaseState<HarkState> {
notificationsGraphConfig: NotificationGraphConfig; // TODO unthread this everywhere notificationsGraphConfig: NotificationGraphConfig; // TODO unthread this everywhere
notificationsGroupConfig: string[]; notificationsGroupConfig: string[];
unreads: Unreads; unreads: Unreads;
}; }
const useHarkState = createState<HarkState>('Hark', { const useHarkState = createState<HarkState>('Hark', {
archivedNotifications: new BigIntOrderedMap<Timebox>(), archivedNotifications: new BigIntOrderedMap<Timebox>(),
@ -62,8 +61,7 @@ const useHarkState = createState<HarkState>('Hark', {
unreads: { unreads: {
graph: {}, graph: {},
group: {} group: {}
}, }
}, ['notifications', 'archivedNotifications', 'unreads', 'notificationsCount']); }, ['notifications', 'archivedNotifications', 'unreads', 'notificationsCount']);
export default useHarkState; export default useHarkState;

View File

@ -3,10 +3,10 @@ import { BaseState, createState } from './base';
export interface InviteState extends BaseState<InviteState> { export interface InviteState extends BaseState<InviteState> {
invites: Invites; invites: Invites;
}; }
const useInviteState = createState<InviteState>('Invite', { const useInviteState = createState<InviteState>('Invite', {
invites: {}, invites: {}
}); });
export default useInviteState; export default useInviteState;

View File

@ -1,7 +1,5 @@
import { Tile, WeatherState } from "~/types/launch-update"; import { Tile, WeatherState } from '~/types/launch-update';
import { BaseState, createState } from './base';
import { BaseState, createState } from "./base";
export interface LaunchState extends BaseState<LaunchState> { export interface LaunchState extends BaseState<LaunchState> {
firstTime: boolean; firstTime: boolean;
@ -12,6 +10,7 @@ export interface LaunchState extends BaseState<LaunchState> {
weather: WeatherState | null | Record<string, never> | boolean, weather: WeatherState | null | Record<string, never> | boolean,
userLocation: string | null; userLocation: string | null;
baseHash: string | null; baseHash: string | null;
runtimeLag: boolean;
}; };
const useLaunchState = createState<LaunchState>('Launch', { const useLaunchState = createState<LaunchState>('Launch', {
@ -20,8 +19,8 @@ const useLaunchState = createState<LaunchState>('Launch', {
tiles: {}, tiles: {},
weather: null, weather: null,
userLocation: null, userLocation: null,
baseHash: null baseHash: null,
runtimeLag: false,
}); });
export default useLaunchState; export default useLaunchState;

View File

@ -1,13 +1,12 @@
import React, { ReactNode } from 'react'; import produce from 'immer';
import f from 'lodash/fp'; import f from 'lodash/fp';
import React from 'react';
import create, { State } from 'zustand'; import create, { State } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import produce from 'immer'; import { BackgroundConfig, LeapCategories, RemoteContentPolicy, TutorialProgress, tutorialProgress } from '~/types/local-update';
import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress, LeapCategories } from "~/types/local-update";
export interface LocalState { export interface LocalState {
theme: "light" | "dark" | "auto"; theme: 'light' | 'dark' | 'auto';
hideAvatars: boolean; hideAvatars: boolean;
hideNicknames: boolean; hideNicknames: boolean;
remoteContentPolicy: RemoteContentPolicy; remoteContentPolicy: RemoteContentPolicy;
@ -21,12 +20,13 @@ export interface LocalState {
hideLeapCats: LeapCategories[]; hideLeapCats: LeapCategories[];
setTutorialRef: (el: HTMLElement | null) => void; setTutorialRef: (el: HTMLElement | null) => void;
dark: boolean; dark: boolean;
mobile: boolean;
background: BackgroundConfig; background: BackgroundConfig;
omniboxShown: boolean; omniboxShown: boolean;
suspendedFocus?: HTMLElement; suspendedFocus?: HTMLElement;
toggleOmnibox: () => void; toggleOmnibox: () => void;
set: (fn: (state: LocalState) => void) => void set: (fn: (state: LocalState) => void) => void
}; }
type LocalStateZus = LocalState & State; type LocalStateZus = LocalState & State;
@ -35,8 +35,9 @@ export const selectLocalState =
const useLocalState = create<LocalStateZus>(persist((set, get) => ({ const useLocalState = create<LocalStateZus>(persist((set, get) => ({
dark: false, dark: false,
mobile: false,
background: undefined, background: undefined,
theme: "auto", theme: 'auto',
hideAvatars: false, hideAvatars: false,
hideNicknames: false, hideNicknames: false,
hideLeapCats: [], hideLeapCats: [],

Some files were not shown because too many files have changed in this diff Show More