Merge remote-tracking branch 'origin/release/next-userspace' into lf/nu-collections

This commit is contained in:
Liam Fitzgerald 2021-06-15 09:53:25 +10:00
commit 71ac3b4053
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
65 changed files with 698 additions and 571 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:61e583dd7db795dac4a7c31bfd3ee8b240e679bb882e35d4e7d1acb5f9f2f3d6
size 8270131
oid sha256:e0af91e5c51359719aaa943f37a1e953989c786412616b18fbaa0addb2cf0740
size 10272514

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:185ea5e76dc48695e55efc543377e0682e485f81b16e3b443f9be881d026d4f2
size 2616564
oid sha256:23d8235b19a3404e0bfbed54aa56a018255beb1f33457e37f521bc0763b4d0eb
size 6245506

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e720a18aa0a0e1e4a0ae7fae537ce67f01e9501496dd45f5040826a26e1eda32
size 12272143
oid sha256:64d7cd93dbdf650e390b9c5780ee8bee22cf925fa31f47a0a1a3b17ce7443302
size 11566105

View File

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

View File

@ -918,7 +918,7 @@
^- (quip card _state)
:_ state
=- (turn - print:sh-out)
:~ ";view ~host/chat to print messages for a chat you've already jonied."
:~ ";view ~host/chat to print messages for a chat you've already joined."
";flee ~host/chat to stop printing messages for a chat."
"For more details:"
"https://urbit.org/using/operations/using-your-ship/#messaging"

View File

@ -233,7 +233,7 @@
|= =path
^- (unit (unit cage))
?+ path (on-peek:def path)
[%x %all ~] ``noun+!>(rolodex)
[%x %all ~] ``noun+!>(`rolodex:store`rolodex)
::
[%x %contact @ ~]
=/ =ship (slav %p i.t.t.path)
@ -245,14 +245,13 @@
::
[%x %allowed-ship @ ~]
=/ =ship (slav %p i.t.t.path)
``noun+!>((~(has in allowed-ships) ship))
``noun+!>(`?`(~(has in allowed-ships) ship))
::
[%x %is-public ~]
``noun+!>(is-public)
``noun+!>(`?`is-public)
::
[%x %allowed-groups ~]
``noun+!>(allowed-groups)
``noun+!>(`(set resource)`allowed-groups)
::
[%x %is-allowed @ @ @ @ ~]
=/ is-personal =(i.t.t.t.t.t.path 'true')

View File

@ -5,8 +5,8 @@
/- glob, *resource
/+ default-agent, verb, dbug
|%
++ landscape-hash 0v7.5i82f.ntu87.0gevr.qheif.0iut9
++ btc-wallet-hash 0v2.2k8eq.k1uhe.9acu7.emum0.s8gjd
++ landscape-hash 0v2.i41hn.un6g3.jucd7.rhrah.n0qmv
++ btc-wallet-hash 0v2.3qak4.al612.8m1ig.kg03r.mfide
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ state-1 [%1 =globs:glob]
+$ all-states

View File

@ -131,10 +131,9 @@
=^ allowed cards (is-allowed-add:hc rid nodes.q.update)
?. allowed
[cards ~]
=/ mark-cached (~(has by graph-to-mark) rid)
=/ mark
?: mark-cached
(~(got by graph-to-mark) rid)
%+ fall
(~(get by graph-to-mark) rid)
(get-mark:gra rid)
?~ mark
[cards `vas]
@ -143,15 +142,12 @@
|%
++ $
^- (quip card (unit vase))
=/ transform-cached (~(has by transform-marks) u.mark)
=/ transform=cached-transform
?: transform-cached
(~(got by transform-marks) u.mark)
%+ fall
(~(get by transform-marks) u.mark)
=/ =tube:clay
.^(tube:clay (scry:hc %cc %home /[u.mark]/transform-add-nodes))
!< cached-transform
%. !>(*indexed-post:store)
tube
!<(cached-transform (tube !>(*indexed-post:store)))
=/ [* result=(list [index:store node:store])]
%+ roll
(flatten-node-map ~(tap by nodes.q.update))
@ -164,13 +160,15 @@
update
%+ weld cards
%- zing
:~ ?: mark-cached ~
:~ ?: (~(has by graph-to-mark) rid)
~
:_ ~
%+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action
[%graph-to-mark rid mark]
::
?: transform-cached ~
?: (~(has by transform-marks) u.mark)
~
:_ ~
%+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action
@ -316,28 +314,27 @@
|= [=resource:res perm=@t vip=vip-metadata:metadata =indexed-post:store]
^- [permissions:store (list card)]
|^
=/ mark-cached (~(has by graph-to-mark.cache) resource)
=/ mark
?: mark-cached
(~(got by graph-to-mark.cache) resource)
%+ fall
(~(get by graph-to-mark.cache) resource)
(get-mark:gra resource)
?~ mark
[[%no %no %no] ~]
=/ key [u.mark (perm-mark-name perm)]
=/ perms-cached (~(has by perm-marks.cache) key)
=/ convert
?: perms-cached
(~(got by perm-marks.cache) key)
%+ fall
(~(get by perm-marks.cache) key)
.^(cached-permission (scry %cf %home /[u.mark]/(perm-mark-name perm)))
:- ((convert indexed-post) vip)
%- zing
:~ ?: mark-cached ~
:~ ?: (~(has by graph-to-mark.cache) resource)
~
:_ ~
%+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action
[%graph-to-mark resource mark]
::
?: perms-cached ~
?: (~(has by perm-marks.cache) key) ~
:_ ~
%+ poke-self:pass:io %graph-cache-hook
!> ^- cache-action

View File

@ -5,8 +5,8 @@
|%
+$ card card:agent:gall
+$ versioned-state
$% [%0 network:zero:store]
[%1 network:zero:store]
$% [%0 *]
[%1 *]
[%2 network:zero:store]
[%3 network:one:store]
[%4 network:store]
@ -16,7 +16,6 @@
+$ state-5 [%5 network:store]
++ orm orm:store
++ orm-log orm-log:store
+$ debug-input [%validate-graph =resource:store]
::
+$ cache
$: validators=(map mark $-(indexed-post:store indexed-post:store))
@ -50,34 +49,8 @@
=| cards=(list card)
|-
?- -.old
%0
=* zro zero-load:upgrade:store
%_ $
-.old %1
::
graphs.old
%- ~(run by graphs.old)
|= [=graph:zero:store q=(unit mark)]
^- [graph:zero:store (unit mark)]
:- (convert-unix-timestamped-graph:zro graph)
?^ q q
`%graph-validator-link
::
update-logs.old
%- ~(run by update-logs.old)
|=(a=* *update-log:zero:store)
==
::
%1
=* zro zero-load:upgrade:store
%_ $
-.old %2
graphs.old (~(run by graphs.old) change-revision-graph:zro)
::
update-logs.old
%- ~(run by update-logs.old)
|=(a=* *update-log:zero:store)
==
%0 !!
%1 !!
::
%2
=* upg upgrade:store
@ -138,7 +111,7 @@
++ give
|= =action:store
^- (list card)
[%give %fact ~ [%graph-update-2 !>([now.bowl action])]]~
[%give %fact ~ [%graph-update-2 !>(`update:store`[now.bowl action])]]~
--
::
++ on-poke
@ -148,10 +121,9 @@
|^
?> (team:title our.bowl src.bowl)
=^ cards state
?+ mark (on-poke:def mark vase)
%graph-update-2 (graph-update !<(update:store vase))
%noun (debug !<(debug-input vase))
%import (poke-import q.vase)
?+ mark (on-poke:def mark vase)
%graph-update-2 (graph-update !<(update:store vase))
%import (poke-import q.vase)
==
[cards this]
::
@ -204,7 +176,7 @@
==
%- zing
:~ (give [/keys ~] %keys (~(put in ~(key by graphs)) resource))
(give [/updates ~] %add-graph resource *graph:store mark overwrite)
(give [/updates ~] %add-graph resource ~ mark overwrite)
==
::
++ remove-graph
@ -274,7 +246,7 @@
?~ index
?=(^ node)
?~ t.index
?=(^ (get:orm graph i.index))
(has:orm graph i.index)
=. node (get:orm graph i.index)
?~ node %.n
?- -.children.u.node
@ -330,7 +302,8 @@
~| "cannot add deleted post"
?> ?=(%& -.post.node)
=* p p.post.node
?~ hash.p node(signatures.p.post *signatures:store)
?~ hash.p
node(signatures.p.post ~)
=/ =validated-portion:store
[parent-hash author.p time-sent.p contents.p]
=/ =hash:store `@ux`(sham validated-portion)
@ -343,24 +316,23 @@
::
=/ parent=node:store
~| "index does not exist to add a node to!"
(need (get:orm graph atom))
(got:orm graph atom)
%_ parent
children
^- internal-graph:store
:- %graph
%_ $
index t.index
index t.index
::
parent-hash
?- -.post.parent
%| `p.post.parent
%& hash.p.post.parent
==
?: ?=(%| -.post.parent)
`p.post.parent
hash.p.post.parent
::
graph
?: ?=(%graph -.children.parent)
p.children.parent
(gas:orm ~ ~)
?. ?=(%graph -.children.parent)
~
p.children.parent
==
==
--
@ -416,7 +388,7 @@
?~ t.index
=/ =node:store
~| "cannot remove index that does not exist {<index>}"
(need (get:orm graph atom))
(got:orm graph atom)
%_ node
post
~| "cannot remove post that has already been removed"
@ -434,7 +406,7 @@
::
=/ parent=node:store
~| "parent index does not exist to remove a node from!"
(need (get:orm graph atom))
(got:orm graph atom)
~| "child index does not exist to remove a node from!"
?> ?=(%graph -.children.parent)
%_ parent
@ -444,10 +416,9 @@
graph p.children.parent
::
parent-hash
?- -.post.parent
%| `p.post.parent
%& hash.p.post.parent
==
?: ?=(%| -.post.parent)
`p.post.parent
hash.p.post.parent
==
==
--
@ -478,7 +449,7 @@
=* atom i.index
=/ =node:store
~| "node does not exist to add signatures to!"
(need (get:orm graph atom))
(got:orm graph atom)
:: last index in list
::
%^ put:orm
@ -490,7 +461,10 @@
~| "cannot add signatures to a node missing a hash"
?> ?=(^ hash.p.post.node)
~| "signatures did not match public keys!"
?> (are-signatures-valid:sigs our.bowl signatures u.hash.p.post.node now.bowl)
?> %: are-signatures-valid:sigs
our.bowl signatures
u.hash.p.post.node now.bowl
==
node(signatures.p.post (~(uni in signatures) signatures.p.post.node))
~| "child graph does not exist to add signatures to!"
?> ?=(%graph -.children.node)
@ -525,7 +499,7 @@
=* atom i.index
=/ =node:store
~| "node does not exist to add signatures to!"
(need (get:orm graph atom))
(got:orm graph atom)
:: last index in list
::
%^ put:orm
@ -578,7 +552,7 @@
%_ state
archive (~(del by archive) resource)
graphs (~(put by graphs) resource (~(got by archive) resource))
update-logs (~(put by update-logs) resource (gas:orm-log ~ ~))
update-logs (~(put by update-logs) resource ~)
==
::
++ run-updates
@ -599,38 +573,28 @@
%- graph-update
^- update:store
?- -.q.update
%add-graph update(resource.q resource)
%add-nodes update(resource.q resource)
%remove-posts update(resource.q resource)
%add-signatures update(resource.uid.q resource)
%remove-signatures update(resource.uid.q resource)
%add-graph update(resource.q resource)
%add-nodes update(resource.q resource)
%remove-posts update(resource.q resource)
%add-signatures update(resource.uid.q resource)
%remove-signatures update(resource.uid.q resource)
==
$(cards (weld cards crds), updates t.updates)
::
++ give
|= [paths=(list path) update=action:store]
^- (list card)
[%give %fact paths [%graph-update-2 !>([now.bowl update])]]~
[%give %fact paths [%graph-update-2 !>(`update:store`[now.bowl update])]]~
--
::
++ debug
|= =debug-input
^- (quip card _state)
=/ [=graph:store mark=(unit mark:store)]
(~(got by graphs) resource.debug-input)
=^ is-valid state
(validate-graph graph mark)
?> is-valid
[~ state]
::
++ validate-graph
|= [=graph:store mark=(unit mark:store)]
^- [? _state]
?~ mark [%.y state]
=/ has-validator (~(has by validators) u.mark)
?~ mark
[%.y state]
=/ validate=$-(indexed-post:store indexed-post:store)
?: has-validator
(~(got by validators) u.mark)
%+ fall
(~(get by validators) u.mark)
.^ $-(indexed-post:store indexed-post:store)
%cf
(scot %p our.bowl)
@ -640,11 +604,13 @@
%graph-indexed-post
~
==
:_ state(validators (~(put by validators) u.mark validate))
=? validators !(~(has by validators) u.mark)
(~(put by validators) u.mark validate)
:_ state
|- ^- ?
?~ graph %.y
%+ roll (tap:orm graph)
|= [[=atom =node:store] out=?]
%+ all:orm graph
|= [=atom =node:store]
^- ?
?& ?| ?=(%| -.post.node)
?=(^ (validate [atom p.post.node]))
@ -676,7 +642,7 @@
=/ result=(unit marked-graph:store)
(~(get by graphs) [ship term])
?~ result [~ ~]
``noun+!>(q.u.result)
``noun+!>(`(unit mark)`q.u.result)
::
[%x %keys ~]
:- ~ :- ~ :- %graph-update-2
@ -745,7 +711,7 @@
(turn t.t.t.t.path (cury slav %ud))
=/ node=(unit node:store)
(get-node ship term index)
``noun+!>(?=(^ node))
``noun+!>(`?`?=(^ node))
::
[%x %node @ @ @ *]
=/ =ship (slav %p i.t.t.path)
@ -762,6 +728,7 @@
(~(gas by *(map index:store node:store)) [index u.node] ~)
::
[%x %node-siblings ?(%older %younger) @ @ @ *]
|^
=/ older ?=(%older i.t.t.path)
=/ =ship (slav %p i.t.t.t.path)
=/ =term i.t.t.t.t.path
@ -779,21 +746,28 @@
:+ %add-nodes
[ship term]
%- ~(gas by *(map index:store node:store))
:: TODO time complexity not desirable
:: replace with custom ordered map functions
%+ turn
=- ?.(older (slag (safe-sub (lent -) count) -) (scag count -))
?: older
(tab:orm u.graph ~ count)
:: TODO time complexity not desirable for %younger case
::
%+ slag (safe-sub (lent -) count)
%- tap:orm
%+ lot:orm u.graph
=/ idx
(snag (dec (lent index)) index)
?:(older [`idx ~] [~ `idx])
[~ `(snag (dec (lent index)) index)]
|= [=atom =node:store]
^- [index:store node:store]
[(snoc parent atom) node]
::
++ safe-sub
|= [a=@ b=@]
^- @
?: (gte b a)
0
(sub a b)
--
::
[%x %shallow-children @ @ *]
=/ newest ?=(%newest i.t.path)
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ =index:store
@ -831,8 +805,9 @@
%- ~(gas by *(map index:store node:store))
%+ turn
%+ scag count
%- ?:(newest same flop)
(tap:orm u.children)
?: newest
(tap:orm u.children)
(bap:orm u.children)
|= [=atom =node:store]
^- [index:store node:store]
[(snoc index atom) node]
@ -923,7 +898,8 @@
::
++ collect-parents
|= [=graph:store =index:store =ship =term]
^- (unit [node:store index:store (map index:store node:store) ^ship ^term])
^- %- unit
[node:store index:store (map index:store node:store) ^ship ^term]
=| =(map index:store node:store)
=| =node:store
=| ind=index:store
@ -953,16 +929,16 @@
==
::
++ collect-firstborn
|= [=node:store =index:store map=(map index:store node:store) =ship =term]
|= [=node:store =index:store mp=(map index:store node:store) =ship =term]
^- (unit (unit cage))
?: ?=(%empty -.children.node)
:- ~ :- ~ :- %graph-update-2
!> ^- update:store
[now.bowl [%add-nodes [ship term] map]]
[now.bowl [%add-nodes [ship term] mp]]
=/ item=[k=atom v=node:store]
(need (ram:orm p.children.node))
=. index (snoc index k.item)
$(map (~(put by map) index v.item(children empty+~)), node v.item)
$(mp (~(put by mp) index v.item(children empty+~)), node v.item)
--
::
[%x %update-log-subset @ @ @ @ ~]
@ -973,35 +949,29 @@
=/ update-log=(unit update-log:store) (~(get by update-logs) [ship term])
?~ update-log [~ ~]
:: orm-log is ordered backwards, so swap start and end
``noun+!>((lot:orm-log u.update-log end start))
``noun+!>(`update-log:store`(lot:orm-log u.update-log end start))
::
[%x %update-log @ @ ~]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ update-log=(unit update-log:store) (~(get by update-logs) [ship term])
?~ update-log [~ ~]
``noun+!>(u.update-log)
``noun+!>(`update-log:store`u.update-log)
::
[%x %peek-update-log @ @ ~]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ m-update-log=(unit update-log:store) (~(get by update-logs) [ship term])
=/ m-update-log=(unit update-log:store)
(~(get by update-logs) [ship term])
:- ~ :- ~ :- %noun
!> ^- (unit time)
%+ biff m-update-log
|= =update-log:store
=/ result=(unit [=time =update:store])
(pry:orm-log:store update-log)
(bind result |=([=time update:store] time))
(bind result head)
==
::
++ safe-sub
|= [a=@ b=@]
^- @
?: (gte b a)
0
(sub a b)
::
++ get-node-children
|= [=ship =term =index:store]
^- (unit graph:store)
@ -1044,40 +1014,12 @@
?+ wire (on-arvo:def wire sign-arvo)
::
:: old wire, do nothing
[%graph *] [~ this]
[%validator @ ~] [~ this]
::
[%try-rejoin @ *]
=/ rid=resource:store (de-path:res t.t.wire)
=/ nack-count (slav %ud i.t.wire)
?> ?=([%behn %wake *] sign-arvo)
~? ?=(^ error.sign-arvo)
"behn errored in backoff timers, continuing anyway"
=/ new=^wire [%try-rejoin (scot %ud +(nack-count)) t.t.wire]
:_ this
[%pass new %agent [entity.rid %graph-push-hook] %watch resource+t.t.wire]~
[%graph *] [~ this]
[%validator @ ~] [~ this]
[%try-rejoin @ *] [~ this]
==
::
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
?. ?=([%try-rejoin @ *] wire)
(on-agent:def wire sign)
?. ?=(%watch-ack -.sign)
[~ this]
=/ rid=resource:store (de-path:res t.t.wire)
?~ p.sign
=/ =cage [%pull-hook-action !>([%add entity.rid rid])]
:_ this
:~ [%pass / %agent [our.bowl %graph-pull-hook] %poke cage]
[%pass wire %agent [entity.rid %graph-push-hook] %leave ~]
==
=/ nack-count=@ud (slav %ud i.t.wire)
=/ wakeup=@da
(add now.bowl (mul ~s1 (bex (min 19 nack-count))))
:_ this
[%pass wire %arvo %b %wait wakeup]~
::
++ on-agent on-agent:def
++ on-leave on-leave:def
++ on-fail on-fail:def
--

View File

@ -30,12 +30,12 @@
?> ?=(^ t.p)
.^(mold i.p (scot %p our) i.t.p (scot %da now) t.t.p)
::
++ scry-conversion
++ scry-notif-conversion
|= [[our=@p now=@da] desk=term =mark]
~+
^- $-(indexed-post:graph-store (unit notif-kind:hook))
%^ scry [our now]
tube:clay
/cc/[desk]/[mark]/notification-kind
$-(indexed-post:graph-store (unit notif-kind:hook))
/cf/[desk]/[mark]/notification-kind
--
::
=| state-1
@ -87,7 +87,7 @@
|= =mark
^- card
=/ =wire /validator/[mark]
=/ =rave:clay [%sing %c [%da now.bowl] /[mark]/notification-kind]
=/ =rave:clay [%sing %f [%da now.bowl] /[mark]/notification-kind]
[%pass wire %arvo %c %warp our.bowl [%home `rave]]
::
++ on-watch
@ -214,19 +214,18 @@
%- ~(gas by *(set [resource index:graph-store]))
(turn ~(tap in indices) (lead rid))
:_ state(watching (~(dif in watching) to-remove))
=/ =tube:clay
(get-conversion:ha rid)
=/ convert (get-conversion:ha rid)
%+ roll
~(tap in indices)
|= [=index:graph-store out=(list card)]
=| =indexed-post:graph-store
=. index.p.indexed-post index
=+ !<(u-notif-kind=(unit notif-kind:hook) (tube !>(indexed-post)))
?~ u-notif-kind out
=* notif-kind u.u-notif-kind
=/ notif-kind=(unit notif-kind:hook)
(convert indexed-post)
?~ notif-kind out
=/ =stats-index:store
[%graph rid (scag parent.index-len.notif-kind index)]
?. ?=(%each mode.notif-kind) out
[%graph rid (scag parent.index-len.u.notif-kind index)]
?. ?=(%each mode.u.notif-kind) out
:_ out
(poke-hark %read-each stats-index index)
::
@ -285,7 +284,7 @@
[%validator @ ~]
:_ this
=* validator i.t.wire
=/ =rave:clay [%next %c [%da now.bowl] /[validator]/notification-kind]
=/ =rave:clay [%next %f [%da now.bowl] /[validator]/notification-kind]
[%pass wire %arvo %c %warp our.bowl [%home `rave]]~
==
++ on-fail on-fail:def
@ -298,13 +297,13 @@
::
++ get-conversion
|= rid=resource
^- tube:clay
^- $-(indexed-post:graph-store (unit notif-kind:hook))
=+ %^ scry [our now]:bowl
,mark=(unit mark)
/gx/graph-store/graph-mark/(scot %p entity.rid)/[name.rid]/noun
?~ mark
|=(v=vase !>(~))
(scry-conversion [our now]:bowl q.byk.bowl u.mark)
|=(=indexed-post:graph-store ~)
(scry-notif-conversion [our now]:bowl q.byk.bowl u.mark)
::
++ give
|= [paths=(list path) =update:hook]
@ -355,8 +354,6 @@
update-core(rid r, updates upds, mark m)
::
++ get-conversion
:: 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
@ -410,9 +407,8 @@
?: ?=(%| -.post.node)
update-core
=* pos p.post.node
=+ !< notif-kind=(unit notif-kind:hook)
%- get-conversion
!>(`indexed-post:graph-store`[0 pos])
=/ notif-kind=(unit notif-kind:hook)
(get-conversion [0 pos])
?~ notif-kind
update-core
=/ desc=@t

View File

@ -422,7 +422,11 @@
%read-note (read-note +.in)
::
%seen-index (seen-index +.in)
::
%remove-graph (remove-graph +.in)
%read-graph (read-graph +.in)
%read-group (read-group +.in)
::
%set-dnd (set-dnd +.in)
%seen seen
%read-all read-all
@ -566,10 +570,53 @@
(~(put by last-seen) stats-index new-time)
(give %seen-index new-time stats-index)
::
++ get-stats-indices
|= rid=resource
%- ~(gas ^in *(set stats-index:store))
%+ skim
;: weld
~(tap ^in ~(key by unreads-count))
~(tap ^in ~(key by last-seen))
~(tap ^in ~(key by unreads-each))
==
|= =stats-index:store
?. ?=(%graph -.stats-index) %.n
=(graph.stats-index rid)
::
++ read-all-each
|= =stats-index:store
=/ refs=(list index:graph-store)
~(tap ^in (~(get ju unreads-each) stats-index))
|-
?~ refs poke-core
$(refs t.refs, poke-core (read-each stats-index i.refs))
::
++ read-graph
|= rid=resource
=/ indices=(list stats-index:store)
~(tap ^in (get-stats-indices rid))
|-
?~ indices poke-core
=* index i.indices
=? poke-core (~(has by unreads-count) index)
(read-count i.indices)
=? poke-core (~(has by unreads-each) index)
(read-all-each i.indices)
$(indices t.indices)
::
++ read-group
|= rid=resource
=/ graphs=(list resource)
(graphs-of-group:met rid)
|-
?~ graphs poke-core
=/ core=_poke-core (read-graph i.graphs)
$(graphs t.graphs, poke-core core)
::
++ remove-graph
|= rid=resource
|^
=/ indices get-stats-indices
=/ indices (get-stats-indices rid)
=. poke-core
(give %remove-graph rid)
=. poke-core
@ -583,18 +630,6 @@
((dif-map-by-key ,@da) last-seen indices)
poke-core
::
++ get-stats-indices
%- ~(gas ^in *(set stats-index:store))
%+ skim
;: weld
~(tap ^in ~(key by unreads-count))
~(tap ^in ~(key by last-seen))
~(tap ^in ~(key by unreads-each))
==
|= =stats-index:store
?. ?=(%graph -.stats-index) %.n
=(graph.stats-index rid)
::
++ dif-map-by-key
|* value=mold
|= [=(map stats-index:store value) =(set stats-index:store)]

View File

@ -24,6 +24,6 @@
<div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script>
<script src="/~landscape/js/bundle/index.00c3f83f1989fbd9455f.js"></script>
<script src="/~landscape/js/bundle/index.969caa5f68ba7bcf5762.js"></script>
</body>
</html>

View File

@ -194,10 +194,18 @@
|= =path
^- (unit (unit cage))
?+ path (on-peek:def path)
[%y %group-indices ~] ``noun+!>(group-indices)
[%y %app-indices ~] ``noun+!>(app-indices)
[%y %resource-indices ~] ``noun+!>(resource-indices)
[%x %associations ~] ``noun+!>(associations)
[%y %group-indices ~]
``noun+!>(`(jug resource md-resource:store)`group-indices)
::
[%y %app-indices ~]
``noun+!>(`(jug app-name:store [group=resource =resource])`app-indices)
::
[%y %resource-indices ~]
``noun+!>(`(map md-resource:store resource)`resource-indices)
::
[%x %associations ~]
``noun+!>(`associations:store`associations)
::
[%x %app-name @ ~]
=/ =app-name:store i.t.t.path
``noun+!>(`associations:store`(metadata-for-app:mc app-name))

View File

@ -83,13 +83,13 @@
^- (unit (unit cage))
?+ pax (on-peek:def pax)
[%x %all ~]
``settings-data+!>(all+settings)
``settings-data+!>(`data`all+settings)
::
[%x %bucket @ ~]
=* buc i.t.t.pax
=/ bucket=(unit bucket) (~(get by settings) buc)
?~ bucket [~ ~]
``settings-data+!>(bucket+u.bucket)
``settings-data+!>(`data`bucket+u.bucket)
::
[%x %entry @ @ ~]
=* buc i.t.t.pax
@ -97,19 +97,19 @@
=/ =bucket (fall (~(get by settings) buc) ~)
=/ entry=(unit val) (~(get by bucket) key)
?~ entry [~ ~]
``settings-data+!>(entry+u.entry)
``settings-data+!>(`data`entry+u.entry)
::
[%x %has-bucket @ ~]
=* buc i.t.t.pax
=/ has-bucket=? (~(has by settings) buc)
``noun+!>(has-bucket)
``noun+!>(`?`has-bucket)
::
[%x %has-entry @ @ ~]
=* buc i.t.t.pax
=* key i.t.t.t.pax
=/ =bucket (fall (~(get by settings) buc) ~)
=/ has-entry=? (~(has by bucket) key)
``noun+!>(has-entry)
``noun+!>(`?`has-entry)
==
::
++ on-agent on-agent:def

View File

@ -1,6 +1,7 @@
/- spider
/+ libstrand=strand, default-agent, verb, server
=, strand=strand:libstrand
~% %spider-top ..part ~
|%
+$ card card:agent:gall
+$ thread thread:spider
@ -60,6 +61,7 @@
::
:: Trie operations
::
~% %spider ..card ~
|%
++ get-yarn
|= [=trie =yarn]
@ -137,6 +139,7 @@
=| =state
=<
%+ verb |
~% %spider-agent ..bind-eyre ~
|_ =bowl:gall
+* this .
spider-core +>
@ -192,6 +195,7 @@
--
::
++ on-poke
~/ %on-poke
|= [=mark =vase]
^- (quip card _this)
?: ?=(%spider-kill mark)
@ -208,6 +212,7 @@
[cards this]
::
++ on-watch
~/ %on-watch
|= =path
^- (quip card _this)
=^ cards state
@ -220,6 +225,7 @@
::
++ on-leave on-leave:def
++ on-peek
~/ %on-peek
|= =path
^- (unit (unit cage))
?+ path (on-peek:def path)
@ -234,6 +240,7 @@
==
::
++ on-agent
~/ %on-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
=^ cards state
@ -243,6 +250,7 @@
[cards this]
::
++ on-arvo
~/ %on-arvo
|= [=wire =sign-arvo]
^- (quip card _this)
=^ cards state
@ -261,6 +269,7 @@
(on-load on-save)
--
::
~% %spider-helper ..get-yarn ~
|_ =bowl:gall
::
++ bind-eyre
@ -272,6 +281,7 @@
:((cury cat 3) file '--' (scot %uv (sham eny.bowl)))
::
++ handle-http-request
~/ %handle-http-request
|= [eyre-id=@ta =inbound-request:eyre]
^- (quip card _state)
?> authenticated.inbound-request
@ -284,6 +294,8 @@
=/ =tid (new-thread-id thread)
=. serving.state
(~(put by serving.state) tid [eyre-id output-mark])
:: TODO: speed this up somehow. we spend about 15ms in this arm alone
::
=+ .^
=tube:clay
%cc
@ -315,6 +327,7 @@
`state
::
++ handle-sign
~/ %handle-sign
|= [=tid =wire =sign-arvo]
=/ yarn (~(get by tid.state) tid)
?~ yarn
@ -331,6 +344,7 @@
(take-input u.yarn ~ %agent wire sign)
::
++ handle-start-thread
~/ %handle-start-thread
|= [parent-tid=(unit tid) use=(unit tid) file=term =vase]
^- (quip card ^state)
=/ parent-yarn=yarn
@ -353,12 +367,13 @@
=/ pax=path
~| no-file-for-thread+file
(need (get-fit:clay [our q.byk da+now]:bowl %ted file))
=/ =card
:+ %pass /build/[new-tid]
[%arvo %c %warp our.bowl %home ~ %sing %a da+now.bowl pax]
[[card ~] state]
:_ state
:_ ~
:+ %pass /build/[new-tid]
[%arvo %c %warp our.bowl %home ~ %sing %a da+now.bowl pax]
::
++ handle-build
~/ %handle-build
|= [=tid =sign-arvo]
^- (quip card ^state)
=/ =yarn (~(got by tid.state) tid)
@ -377,6 +392,7 @@
(start-thread yarn p.maybe-thread)
::
++ start-thread
~/ %start-thread
|= [=yarn =thread]
^- (quip card ^state)
=/ =vase vase:(~(got by starting.state) yarn)
@ -411,6 +427,7 @@
(thread-fail u.yarn %cancelled ~)
::
++ take-input
~/ %take-input
|= [=yarn input=(unit input:strand)]
^- (quip card ^state)
=/ m (strand ,vase)

View File

@ -42,6 +42,7 @@
(snoc rids [our.bowl %''])
--
++ scry-sharing
^- (set resource)
.^ (set resource)
%gx
(scot %p our.bowl)
@ -58,6 +59,7 @@
(~(get by rolodex) ship)
::
++ scry-is-public
^- ?
.^ ?
%gx
(scot %p our.bowl)

View File

@ -28,8 +28,8 @@
rose+(ot style+(ot mid+sa open+sa close+sa ~) lines+(ar dank) ~)
==
::
++ orm ((ordered-map atom node) gth)
++ orm-log ((ordered-map time logged-update) gth)
++ orm ((on atom node) gth)
++ orm-log ((on time logged-update) gth)
::
++ enjs
=, enjs:format
@ -311,7 +311,7 @@
++ graph
|= a=json
^- ^graph
=/ or-mp ((ordered-map atom ^node) gth)
=/ or-mp ((on atom ^node) gth)
%+ gas:or-mp ~
%+ turn ~(tap by ((om node) a))
|* [b=cord c=*]
@ -559,7 +559,7 @@
=>
|%
++ in-orm
((ordered-map atom in-node) gth)
((on atom in-node) gth)
+$ in-node
[post=in-pst children=in-internal-graph]
+$ in-graph
@ -571,7 +571,7 @@
==
::
++ out-orm
((ordered-map atom out-node) gth)
((on atom out-node) gth)
+$ out-node
[post=out-pst children=out-internal-graph]
+$ out-graph
@ -823,7 +823,7 @@
++ remake-update-log
|= t=tree-update-log
^- update-log
=/ ulm ((ordered-map time logged-update) gth)
=/ ulm ((on time logged-update) gth)
%+ gas:ulm *update-log
%+ turn ~(tap by t)
|= [=time tlu=tree-logged-update]

View File

@ -314,6 +314,8 @@
add-note+add
set-dnd+bo
read-count+stats-index
read-graph+dejs-path:resource
read-group+dejs-path:resource
read-each+read-graph-index
read-all+ul
==

View File

@ -100,4 +100,13 @@
^- (unit resource)
%+ bind (peek-association md-resource)
|=(association:store group)
::
++ graphs-of-group
|= group=resource
=/ =associations:store
(metadata-for-group group)
%+ murn ~(tap in ~(key by associations))
|= [=app-name:store rid=resource]
?.(=(%graph app-name) ~ `rid)
--

View File

@ -73,8 +73,8 @@
::
++ one
|%
++ orm ((ordered-map atom node) gth)
++ orm-log ((ordered-map time logged-update) gth)
++ orm ((on atom node) gth)
++ orm-log ((on time logged-update) gth)
::
+$ graph ((mop atom node) gth)
+$ marked-graph [p=graph q=(unit mark)]

View File

@ -44,6 +44,9 @@
[%read-note =index]
::
[%seen-index time=@da =stats-index]
::
[%read-graph =resource]
[%read-group =resource]
[%remove-graph =resource]
::
[%read-all ~]
@ -281,6 +284,7 @@
[%unread-note time=@da index]
::
[%seen-index time=@da =stats-index]
::
[%remove-graph =resource]
::
[%read-all ~]

View File

@ -252,11 +252,13 @@
:: %what: update from files
:: %whey: produce $mass :: XX remove, scry
:: %verb: toggle laconicity
:: %whiz: prime vane caches
::
$% [%trim p=@ud]
[%what p=(list (pair path (cask)))]
[%whey ~]
[%verb p=(unit ?)]
[%whiz ~]
==
+$ wasp
:: %crud: reroute $ovum with $goof
@ -291,14 +293,23 @@
|=(b=beam =*(s scot `path`[(s %p p.b) q.b (s r.b) s.b]))
::
++ de-beam
~/ %de-beam
|= p=path
^- (unit beam)
?. ?=([@ @ @ *] p) ~
?~ who=(slaw %p i.p) ~
?~ des=?~(i.t.p (some %$) (slaw %tas i.t.p)) ~ :: XX +sym ;~(pose low (easy %$))
?~ ved=(slay i.t.t.p) ~
?. ?=([%$ case] u.ved) ~
`(unit beam)`[~ [`ship`u.who `desk`u.des `case`p.u.ved] t.t.t.p]
?~ ved=(de-case i.t.t.p) ~
`[[`ship`u.who `desk`u.des u.ved] t.t.t.p]
::
++ de-case
~/ %de-case
|= =knot
^- (unit case)
?^ num=(slaw %ud knot) `[%ud u.num]
?^ wen=(slaw %da knot) `[%da u.wen]
?~ lab=(slaw %tas knot) ~
`[%tas u.lab]
::
++ en-omen
|= [vis=view bem=beam]
@ -308,6 +319,7 @@
~(rent co [%many $/tas/way.vis $/tas/car.vis ~])
::
++ de-omen
~/ %de-omen
|= pax=path
^- (unit [vis=view bem=beam])
?~ pax ~
@ -1000,8 +1012,11 @@
++ settle
|= van=vase
^- (pair vase worm)
=/ [rig=vase wor=worm] (~(slym wa *worm) van *vane-sample)
[van +:(~(slap wa wor) rig [%limb %scry])]
=| sac=worm
=^ rig=vase sac (~(slym wa sac) van *vane-sample)
=^ gat=vase sac (~(slap wa sac) rig [%limb %scry])
=^ pro=vase sac (~(slap wa sac) gat [%limb %$])
[van +:(~(mint wa sac) p.pro [%$ 7])]
::
:: XX pass identity to preserve behavior?
::
@ -1470,6 +1485,9 @@
%verb ..pith(lac.fad ?~(p.waif !lac.fad u.p.waif))
%what ~(kel what p.waif)
%whey ..pith(out [[//arvo mass/whey] out])
::
%whiz
..pith(van.mod (~(run by van.mod) |=(vane (settle:va:part vase))))
==
::
++ peek

View File

@ -259,6 +259,8 @@
++ head |*(^ ,:+<-) :: get head
++ same |*(* +<) :: identity
::
++ succ |=(@ +(+<)) :: successor
::
++ tail |*(^ ,:+<+) :: get tail
++ test |=(^ =(+<- +<+)) :: equality
::
@ -8764,6 +8766,7 @@
%peek peek
%repo repo
%rest rest
%sink sink
%tack tack
%toss toss
%wrap wrap
@ -10838,7 +10841,7 @@
|- ^- type
?~ lov sut
$(lov t.lov, sut (face i.lov sut))
:: ::
::
++ sint :: reduce by reference
|= $: :: hod: expand holds
::
@ -10911,6 +10914,39 @@
%- ~(gas in *(set type))
(turn leg |=([p=type q=hoon] (play(sut p) q)))
::
++ sink
~/ %sink
|^ ^- cord
?- sut
%void 'void'
%noun 'noun'
[%atom *] (rap 3 'atom ' p.sut ' ' ?~(q.sut '~' u.q.sut) ~)
[%cell *] (rap 3 'cell ' (mup p.sut) ' ' (mup q.sut) ~)
[%face *] (rap 3 'face ' ?@(p.sut p.sut (mup p.sut)) ' ' (mup q.sut) ~)
[%fork *] (rap 3 'fork ' (mup p.sut) ~)
[%hint *] (rap 3 'hint ' (mup p.sut) ' ' (mup q.sut) ~)
[%hold *] (rap 3 'hold ' (mup p.sut) ' ' (mup q.sut) ~)
::
[%core *]
%+ rap 3
:~ 'core '
(mup p.sut)
' '
?~(p.p.q.sut '~' u.p.p.q.sut)
' '
q.p.q.sut
' '
r.p.q.sut
' '
(mup q.q.sut)
' '
(mup p.r.q.sut)
==
==
::
++ mup |=(* (scot %p (mug +<)))
--
::
++ take
|= [vit=vein duz=$-(type type)]
^- (pair axis type)

View File

@ -1069,14 +1069,23 @@
:::: :: (1d2)
::
+$ blew [p=@ud q=@ud] :: columns rows
+$ belt :: old belt
+$ belt :: client input
$? bolt :: simple input
$% [%mod mod=?(%ctl %met %hyp) key=bolt] :: w/ modifier
[%txt p=(list @c)] :: utf32 text
::TODO consider moving %hey, %rez, %yow here ::
::TMP forward backwards-compatibility ::
:: ::
[%ctl p=@c] ::
[%met p=@c] ::
== == ::
+$ bolt :: simple input
$@ @c :: simple keystroke
$% [%aro p=?(%d %l %r %u)] :: arrow key
[%bac ~] :: true backspace
[%ctl p=@c] :: control-key
[%del ~] :: true delete
[%met p=@c] :: meta-key
[%hit r=@ud c=@ud] :: mouse click
[%ret ~] :: return
[%txt p=(list @c)] :: utf32 text
== ::
+$ blit :: old blit
$% [%bel ~] :: make a noise
@ -2102,6 +2111,7 @@
[%g task:gall]
[%i task:iris]
[%j task:jael]
[%$ %whiz ~]
[@tas %meta vase]
==
:: full vane names are required in vanes

View File

@ -3660,7 +3660,7 @@
=/ lower=@ud 1
|-
:: a should be excluded, so wait until we're past it
?: =(lower +(a))
?: (gte lower +(a))
acc
=/ res=(set tako) (reachable-takos (~(got by hit.dom) lower))
$(acc (~(uni in acc) res), lower +(lower))
@ -3668,7 +3668,7 @@
=| acc=(set tako)
=/ upper=@ud b
|-
?: =(upper a)
?: (lte upper a)
acc
=/ res=(set tako) (reachable-takos (~(got by hit.dom) upper))
$(acc (~(uni in acc) res), upper (dec upper))

View File

@ -106,7 +106,6 @@
%flow +>
%harm +>
%hail (send %hey ~)
%belt (send `dill-belt`p.kyz)
%text (from %out (tuba p.kyz))
%crud :: (send `dill-belt`[%cru p.kyz q.kyz])
(crud p.kyz q.kyz)
@ -116,6 +115,18 @@
%pack (dump kyz)
%crop (dump trim+p.kyz)
%verb (pass /verb %$ kyz)
::
%belt
%- send
::TMP forwards compatibility with next-dill
::
?@ p.kyz [%txt p.kyz ~]
?: ?=(%hit -.p.kyz) [%txt ~]
?. ?=(%mod -.p.kyz) p.kyz
=/ =@c
?@ key.p.kyz key.p.kyz
?:(?=(?(%bac %del %ret) -.key.p.kyz) `@`-.key.p.kyz ~-)
?:(?=(%met mod.p.kyz) [%met c] [%ctl c])
==
::
++ crud

View File

@ -30,6 +30,9 @@
$% [%rest p=@da]
[%wait p=@da]
== ==
$: %c
$>(%warp task:clay)
==
:: %d: to dill
::
$: %d
@ -53,6 +56,12 @@
$: %gall
gift:gall
:: $>(%unto gift:gall)
::
==
$: %clay
gift:clay
:: $>(%writ gift:clay)
::
== ==
--
:: more structures
@ -1231,9 +1240,9 @@
::NOTE these will only fail if the mark and/or json types changed,
:: since conversion failure also gets caught during first receive.
:: we can't do anything about this, so consider it unsupported.
?~ sign=(channel-event-to-sign channel-event) $
?~ json=(sign-to-json request-id u.sign) $
$(events [(event-json-to-wall id u.json) events])
?~ sign=(channel-event-to-sign channel-event) $
?~ jive=(sign-to-json request-id u.sign) $
$(events [(event-json-to-wall id +.u.jive) events])
:: send the start event to the client
::
=^ http-moves state
@ -1499,8 +1508,12 @@
:: if conversion succeeds, we *can* send it. if the client is actually
:: connected, we *will* send it immediately.
::
=/ json=(unit json)
=/ jive=(unit (quip move json))
(sign-to-json request-id sign)
=/ json=(unit json)
?~(jive ~ `+.u.jive)
=? moves ?=(^ jive)
(weld moves -.u.jive)
=* sending &(?=([%| *] state.u.channel) ?=(^ json))
::
=/ next-id next-id.u.channel
@ -1578,7 +1591,7 @@
^= data
%- wall-to-octs
%+ event-json-to-wall next-id
(need (sign-to-json request-id %kick ~))
+:(need (sign-to-json request-id %kick ~))
::
complete=%.n
==
@ -1619,32 +1632,33 @@
:: +sign-to-json: render sign from request-id as json channel event
::
++ sign-to-json
~% %sign-to-json ..part ~
|= [request-id=@ud =sign:agent:gall]
^- (unit json)
^- (unit (quip move json))
:: for facts, we try to convert the result to json
::
=/ jsyn=(unit sign:agent:gall)
?. ?=(%fact -.sign) `sign
?: ?=(%json p.cage.sign) `sign
=/ [from=(unit mark) jsyn=(unit sign:agent:gall)]
?. ?=(%fact -.sign) [~ `sign]
?: ?=(%json p.cage.sign) [~ `sign]
:: find and use tube from fact mark to json
::
=* have=mark p.cage.sign
=* desc=tape "from {(trip have)} to json"
=/ tube=(unit tube:clay)
=/ tuc=(unit (unit cage))
(rof ~ %cc [our %home da+now] /[have]/json)
?. ?=([~ ~ *] tuc) ~
`!<(tube:clay q.u.u.tuc)
?~ tube
((slog leaf+"eyre: no tube {desc}" ~) ~)
::
=/ res (mule |.((u.tube q.cage.sign)))
?: ?=(%& -.res)
`[%fact %json p.res]
((slog leaf+"eyre: failed tube {desc}" ~) ~)
::
=/ convert=(unit vase)
=/ cag=(unit (unit cage))
(rof ~ %cf [our %home da+now] /[have]/json)
?. ?=([~ ~ *] cag) ~
`q.u.u.cag
?~ convert
((slog leaf+"eyre: no convert {desc}" ~) [~ ~])
~| "conversion failed {desc}"
[`have `[%fact %json (slym u.convert q.q.cage.sign)]]
?~ jsyn ~
%- some
:- ?~ from ~
:_ ~
:^ duct %pass /conversion-cache/[u.from]
[%c %warp our %home `[%sing %f da+now /[u.from]/json]]
=* sign u.jsyn
=, enjs:format
%- pairs
@ -1665,7 +1679,7 @@
:- 'json'
~| [%unexpected-fact-mark p.cage.sign]
?> =(%json p.cage.sign)
;;(json q.q.cage.sign)
!<(json q.cage.sign)
==
::
%kick
@ -2320,14 +2334,15 @@
::
|^ ^- [(list move) _http-server-gate]
::
?+ i.wire
~|([%bad-take-wire wire] !!)
?+ i.wire
~|([%bad-take-wire wire] !!)
::
%run-app-request run-app-request
%watch-response watch-response
%sessions sessions
%channel channel
%acme acme-ack
%run-app-request run-app-request
%watch-response watch-response
%sessions sessions
%channel channel
%acme acme-ack
%conversion-cache `http-server-gate
==
::
++ run-app-request

View File

@ -159,7 +159,10 @@
~< %slog.[0 leaf+"gall: molted"]
:: +molt should never notify its client about agent changes
::
=- [(skip -< |=(move ?=([* %give %onto *] +<))) ->]
=- :_ ->
%+ welp
(skip -< |=(move ?=([* %give %onto *] +<)))
[^duct %pass /whiz/gall %$ %whiz ~]~
=/ adult adult-core
=. state.adult
[%7 system-duct outstanding contacts yokes=~ blocked]:spore

View File

@ -3391,6 +3391,11 @@
:: :: ++no:dejs:format
++ no :: number as cord
|=(jon=json ?>(?=([%n *] jon) p.jon))
:: :: ++nu:dejs:format
++ nu :: parse number as hex
|= jon=json
?> ?=([%s *] jon)
(rash p.jon hex)
:: :: ++of:dejs:format
++ of :: object as frond
|* wer=(pole [cord fist])
@ -3440,6 +3445,11 @@
=/ ten ~|(key+key.wer (wit.wer (~(get by jom) key.wer)))
?~(t.wer ten [ten ((ou-raw t.wer) jom)])
==
:: :: ++oj:dejs:format
++ oj :: object as jug
|* =fist
^- $-(json (jug cord _(fist *json)))
(om (as fist))
:: :: ++om:dejs:format
++ om :: object as map
|* wit=fist
@ -3466,6 +3476,12 @@
:: :: ++sa:dejs:format
++ sa :: string as tape
|=(jon=json ?>(?=([%s *] jon) (trip p.jon)))
:: :: ++sd:dejs:format
++ sd :: string @ud as date
|= jon=json
^- @da
?> ?=(%s -.jon)
`@da`(rash p.jon dem:ag)
:: :: ++se:dejs:format
++ se :: string as aura
|= aur=@tas
@ -3580,6 +3596,15 @@
?. ?=([%s *] jon) ~
(bind (stud:chrono:userlib p.jon) |=(a=date (year a)))
::
++ dank :: tank
^- $-(json (unit tank))
%+ re *tank |. ~+
%- of :~
leaf+sa
palm+(ot style+(ot mid+sa cap+sa open+sa close+sa ~) lines+(ar dank) ~)
rose+(ot style+(ot mid+sa open+sa close+sa ~) lines+(ar dank) ~)
==
::
++ di :: millisecond date
(cu from-unix-ms:chrono:userlib ni)
::
@ -3653,6 +3678,12 @@
|* [pre=* wit=fist]
(cu |*(* [pre +<]) wit)
::
++ re :: recursive reparsers
|* [gar=* sef=_|.(fist)]
|= jon=json
^- (unit _gar)
((sef) jon)
::
++ sa :: string as tape
|= jon=json
?.(?=([%s *] jon) ~ (some (trip p.jon)))

View File

@ -49,7 +49,7 @@
^- hexb:bc
%- flip:byt:bc
%- sha256:bc
(script-pubkey:bc a)
(to-script-pubkey:adr:bc a)
::
++ parse-json-rpc
|= =json

View File

@ -5,4 +5,4 @@ dojo:
it should return with the following hash:
`0v2.2k8eq.k1uhe.9acu7.emum0.s8gjd`
`0v2.3qak4.al612.8m1ig.kg03r.mfide`

View File

@ -48,7 +48,7 @@ export default class Send extends Component {
showModal: false,
note: '',
choosingSignMethod: false,
signMethod: 'Sign Transaction',
signMethod: 'bridge',
};
this.initPayment = this.initPayment.bind(this);
@ -86,7 +86,7 @@ export default class Send extends Component {
}
setSignMethod(signMethod) {
this.setState({signMethod});
this.setState({signMethod, choosingSignMethod: false});
}
checkPayee(e){
@ -204,7 +204,7 @@ export default class Send extends Component {
const signReady = (this.state.ready && (parseInt(this.state.satsAmount) > 0)) && !signing;
let invoice = null;
if (signMethod === 'Sign Transaction') {
if (signMethod === 'masterTicket') {
invoice =
<Invoice
network={network}
@ -218,7 +218,7 @@ export default class Send extends Component {
satsAmount={satsAmount}
state={this.props.state}
/>
} else if (signMethod === 'Sign with Bridge') {
} else if (signMethod === 'bridge') {
invoice =
<BridgeInvoice
state={this.props.state}
@ -439,6 +439,17 @@ export default class Send extends Component {
border='none'
style={{cursor: signReady ? 'pointer' : 'default'}} />
</Row>
{signMethod === 'masterTicket' &&
<Row
mt={4}
alignItems='center'
>
<Icon icon='Info' color='yellow' height={4} width={4}/>
<Text fontSize="14px" fontWeight="regular" color="gray" ml={2}>
We recommend that you sign transactions using Bridge to protect your master ticket.
</Text>
</Row>
}
</Col>
}
</>

View File

@ -19,26 +19,26 @@ export default function Signer(props) {
backgroundColor='transparent'
fontWeight='bold'
cursor='pointer'
color={(signMethod === 'Sign Transaction') ? 'blue' : 'lightBlue'}
color={(signMethod === 'masterTicket') ? 'blue' : 'lightBlue'}
py='24px'
px='24px'
onClick={() => setSignMethod('Sign Transaction')}
children='Sign Transaction' />
onClick={() => setSignMethod('masterTicket')}
children='Sign with Master Ticket' />
<Button
border='none'
backgroundColor='transparent'
fontWeight='bold'
cursor='pointer'
color={(signMethod === 'Sign with Bridge') ? 'blue' : 'lightBlue'}
color={(signMethod === 'bridge') ? 'blue' : 'lightBlue'}
py='24px'
px='24px'
onClick={() => setSignMethod('Sign with Bridge')}
onClick={() => setSignMethod('bridge')}
children='Sign with Bridge' />
</Box>
:
<Button
primary
children={signMethod}
children={signMethod === 'bridge' ? 'Sign with Bridge' : 'Sign with Master Ticket'}
fontSize={1}
fontWeight='bold'
borderRadius='24px'

View File

@ -22,7 +22,7 @@ export default class WalletModal extends Component {
super(props);
this.state = {
mode: 'masterTicket',
mode: 'xpub',
masterTicket: '',
confirmedMasterTicket: '',
xpub: '',
@ -103,13 +103,15 @@ export default class WalletModal extends Component {
Step 2 of 2: Import your extended public key
</Text>
</Row>
<Box mt={3}>
<Text fontSize="14px" fontWeight="regular" color="gray">
To begin, you'll need to derive an XPub Bitcoin address using your
master ticket.
<a fontSize="14px" target="_blank" href="https://urbit.org/bitcoin-wallet"> Learn More</a>
<Row
mt={3}
alignItems='center'
>
<Icon icon='Info' color='yellow' height={4} width={4}/>
<Text fontSize="14px" fontWeight="regular" color="gray" ml={2}>
We recommend that you import your wallet using Bridge to protect your master ticket.
</Text>
</Box>
</Row>
<Box
display='flex'
alignItems='center'
@ -126,7 +128,7 @@ export default class WalletModal extends Component {
error: false
})}/>}
<Text fontSize="14px" fontWeight="500">
{this.state.confirmingMasterTicket ? 'Confirm Master Key' : 'Master Key'}
{this.state.confirmingMasterTicket ? 'Confirm Master Ticket' : 'Master Ticket'}
</Text>
</Box>
<Row alignItems="center">
@ -155,37 +157,38 @@ export default class WalletModal extends Component {
</Text>
</Row>
}
<Box mt={3} mb={3}>
<Text fontSize="14px" fontWeight="regular"
color={(inputDisabled) ? "lighterGray" : "gray"}
style={{cursor: (inputDisabled) ? "default" : "pointer"}}
<Row mt={3}>
<Button
primary
color="black"
backgroundColor="veryLightGray"
borderColor="veryLightGray"
children="Cancel"
fontSize="14px"
mr={2}
style={{cursor: "pointer"}}
onClick={() => {this.setState({mode: 'xpub', masterTicket: '', xpub: '', readyToSubmit: false})}}
/>
<Button
primary
disabled={buttonDisabled}
children="Next Step"
fontSize="14px"
style={{cursor: buttonDisabled ? "default" : "pointer"}}
onClick={() => {
if (inputDisabled) return;
this.setState({mode: 'xpub', xpub: '', masterTicket: '', readyToSubmit: false})
}}
>
Manually import your extended public key ->
</Text>
</Box>
<Button
primary
disabled={buttonDisabled}
children="Next Step"
fontSize="14px"
style={{cursor: buttonDisabled ? "default" : "pointer"}}
onClick={() => {
if (!this.state.confirmingMasterTicket) {
this.setState({confirmingMasterTicket: true});
} else {
if (this.state.masterTicket === this.state.confirmedMasterTicket) {
this.setState({error: false});
this.submitMasterTicket(this.state.masterTicket);
if (!this.state.confirmingMasterTicket) {
this.setState({confirmingMasterTicket: true});
} else {
this.setState({error: true});
if (this.state.masterTicket === this.state.confirmedMasterTicket) {
this.setState({error: false});
this.submitMasterTicket(this.state.masterTicket);
} else {
this.setState({error: true});
}
}
}
}}
/>
}}
/>
</Row>
</Box>
);
} else if (this.state.mode === 'xpub') {
@ -203,7 +206,7 @@ export default class WalletModal extends Component {
</Row>
<Box mt={3}>
<Text fontSize="14px" fontWeight="regular" color="gray">
Visit bridge.urbit.org to obtain your key
Visit <a href='https://bridge.urbit.org/?kind=xpub' target='_blank' style={{color: 'black'}} >bridge.urbit.org</a> to obtain your key
</Text>
</Box>
<Box mt={3} mb={2}>
@ -221,27 +224,27 @@ export default class WalletModal extends Component {
autoCorrect="off"
onChange={this.checkXPub}
/>
<Row mt={3}>
<Button
primary
color="black"
backgroundColor="veryLightGray"
borderColor="veryLightGray"
children="Cancel"
fontSize="14px"
mr={2}
style={{cursor: "pointer"}}
onClick={() => {this.setState({mode: 'masterTicket', masterTicket: '', xpub: '', readyToSubmit: false})}}
/>
<Button
primary
disabled={buttonDisabled}
children="Next Step"
fontSize="14px"
style={{cursor: this.state.ready ? "pointer" : "default"}}
onClick={() => { this.submitXPub(this.state.xpub) }}
/>
</Row>
<Box mt={3} mb={3}>
<Text fontSize="14px" fontWeight="regular"
color={(inputDisabled) ? "lighterGray" : "gray"}
style={{cursor: (inputDisabled) ? "default" : "pointer"}}
onClick={() => {
if (inputDisabled) return;
this.setState({mode: 'masterTicket', xpub: '', masterTicket: '', readyToSubmit: false})
}}
>
Import using master ticket ->
</Text>
</Box>
<Button
primary
mt={3}
disabled={buttonDisabled}
children="Next Step"
fontSize="14px"
style={{cursor: this.state.ready ? "pointer" : "default"}}
onClick={() => { this.submitXPub(this.state.xpub) }}
/>
</Box>
);
}

View File

@ -1,10 +1,9 @@
import { Association, GraphNotifDescription, IndexedNotification, NotifIndex } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import { BigInteger } from 'big-integer';
import { getParentIndex } from '../lib/notification';
import { dateToDa, decToUd } from '../lib/util';
import {reduce} from '../reducers/hark-update';
import {doOptimistically, optReduceState} from '../state/base';
import { reduce } from '../reducers/hark-update';
import { doOptimistically } from '../state/base';
import useHarkState from '../state/hark';
import { StoreState } from '../store/type';
import BaseApi from './base';
@ -62,7 +61,7 @@ export class HarkApi extends BaseApi<StoreState> {
index
}
};
await doOptimistically(useHarkState, action, this.harkAction.bind(this), [reduce])
await doOptimistically(useHarkState, action, this.harkAction.bind(this), [reduce]);
}
read(time: BigInteger, index: NotifIndex) {
@ -81,6 +80,18 @@ export class HarkApi extends BaseApi<StoreState> {
return this.actOnNotification('unread-note', time, index);
}
readGroup(group: string) {
return this.harkAction({
'read-group': group
});
}
readGraph(graph: string) {
return this.harkAction({
'read-graph': graph
});
}
dismissReadCount(graph: string, index: string) {
return this.harkAction({
'read-count': {

View File

@ -5,7 +5,7 @@ import React, {
useEffect,
useMemo,
useRef,
useState,
useState
} from 'react';
import _ from 'lodash';
import { getChord } from '~/logic/lib/util';
@ -13,10 +13,9 @@ import { getChord } from '~/logic/lib/util';
type Handler = (e: KeyboardEvent) => void;
const fallback: ShortcutContextProps = {
add: () => {},
remove: () => {},
remove: () => {}
};
export const ShortcutContext = createContext(fallback);
export interface ShortcutContextProps {
add: (cb: (e: KeyboardEvent) => void, key: string) => void;
@ -27,19 +26,19 @@ export function ShortcutContextProvider({ children }) {
const handlerRef = useRef<Handler>(() => {});
const add = useCallback((cb: Handler, key: string) => {
setShortcuts((s) => ({ ...s, [key]: cb }));
setShortcuts(s => ({ ...s, [key]: cb }));
}, []);
const remove = useCallback((cb: Handler, key: string) => {
setShortcuts((s) => (key in s ? _.omit(s, key) : s));
setShortcuts(s => (key in s ? _.omit(s, key) : s));
}, []);
useEffect(() => {
function onKeypress(e: KeyboardEvent) {
handlerRef.current(e);
}
document.addEventListener('keypress', onKeypress);
document.addEventListener('keydown', onKeypress);
return () => {
document.removeEventListener('keypress', onKeypress);
document.removeEventListener('keydown', onKeypress);
};
}, []);
@ -50,7 +49,7 @@ export function ShortcutContextProvider({ children }) {
};
}, [shortcuts]);
const value = useMemo(() => ({ add, remove }), [add, remove])
const value = useMemo(() => ({ add, remove }), [add, remove]);
return (
<ShortcutContext.Provider value={value}>

View File

@ -1,7 +1,7 @@
import urbitOb from 'urbit-ob';
import { parsePermalink, permalinkToReference } from '~/logic/lib/permalinks';
const URL_REGEX = new RegExp(String(/^([^[\]]*?)(([\w\-\+]+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+[\w/])([\s\S]*)/.source));
const URL_REGEX = new RegExp(String(/^([^[\]]*?)(([\w\-\+]+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+[-a-zA-Z0-9:@;?&=\/%\+\*!'\(\)\$_\{\}\^~\[\]`#|])([\s\S]*)/.source));
const PATP_REGEX = /^([\s\S]*?)(~[a-z_-]+)([\s\S]*)/;

View File

@ -93,4 +93,24 @@ describe('tokenizeMessage', () => {
expect(three).toEqual(' test ');
expect(hastuc).toEqual('~hastuc-dibtux');
});
it('should tokenize a url with a par', () => {
const example = 'test https://en.wikipedia.org/wiki/Turbo_(gastropod)';
const [{ text }, { url }] = tokenizeMessage(example);
expect(text).toBe('test ');
expect(url).toBe('https://en.wikipedia.org/wiki/Turbo_(gastropod)');
});
it('should ignore ending commas', () => {
const example = 'https://tlon.io/test, foo';
const [{ url }, { text }] = tokenizeMessage(example);
expect(text).toBe(', foo');
expect(url).toBe('https://tlon.io/test');
});
it('should ignore ending dots', () => {
const example = 'https://tlon.io/test. foo';
const [{ url }, { text }] = tokenizeMessage(example);
expect(text).toBe('. foo');
expect(url).toBe('https://tlon.io/test');
});
});

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react';
import { DragEvent, useCallback, useEffect, useState } from 'react';
function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
const files: File[] = [];
@ -37,7 +37,7 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
const [dragging, setDragging] = useState(false);
const onDragEnter = useCallback(
(e: DragEvent) => {
(e: DragEvent<HTMLDivElement>) => {
if (!validateDragEvent(e)) {
return;
}
@ -47,7 +47,7 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
);
const onDrop = useCallback(
(e: DragEvent) => {
(e: DragEvent<HTMLDivElement>) => {
setDragging(false);
const files = validateDragEvent(e);
if (!files || files === true) {
@ -60,7 +60,7 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
);
const onDragOver = useCallback(
(e: DragEvent) => {
(e: DragEvent<HTMLDivElement>) => {
if (!validateDragEvent(e)) {
return;
}

View File

@ -1,9 +1,9 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import _ from 'lodash';
import { patp2dec } from 'urbit-ob';
import f, { compose, memoize } from 'lodash/fp';
import f from 'lodash/fp';
import { Association, Contact, Patp } from '@urbit/api';
import produce, { enableMapSet } from 'immer';
import { enableMapSet } from 'immer';
import useSettingsState from '../state/settings';
/* eslint-disable max-lines */
import anyAscii from 'any-ascii';
@ -50,14 +50,14 @@ export function parentPath(path: string) {
return _.dropRight(path.split('/'), 1).join('/');
}
/**
/*
* undefined -> initial
* null -> disabled feed
* string -> enabled feed
*/
export function getFeedPath(association: Association): string | null | undefined {
const { metadata } = association;
if(metadata.config && 'group' in metadata?.config && metadata.config?.group) {
const { metadata = { config: {} } } = association;
if (metadata.config && 'group' in metadata?.config && metadata.config?.group) {
if ('resource' in metadata.config.group) {
return metadata.config.group.resource;
}
@ -67,23 +67,26 @@ export function getFeedPath(association: Association): string | null | undefined
}
export const getChord = (e: KeyboardEvent) => {
let chord = [e.key];
const chord = [e.key];
if(e.metaKey) {
chord.unshift('meta');
}
if(e.ctrlKey) {
chord.unshift('ctrl');
}
if(e.shiftKey) {
chord.unshift('shift');
}
return chord.join('+');
}
};
export function getResourcePath(workspace: Workspace, path: string, joined: boolean, mod: string) {
const base = workspace.type === 'group'
? `/~landscape${workspace.group}`
: workspace.type === 'home'
? `/~landscape/home`
: `/~landscape/messages`;
return `${base}/${joined ? 'resource' : 'join'}/${mod}${path}`
? '/~landscape/home'
: '/~landscape/messages';
return `${base}/${joined ? 'resource' : 'join'}/${mod}${path}`;
}
const DA_UNIX_EPOCH = bigInt('170141184475152167957503069145530368000'); // `@ud` ~1970.1.1
@ -135,14 +138,14 @@ export function decToUd(str: string): string {
);
}
/**
/*
* Clamp a number between a min and max
*/
export function clamp(x: number, min: number, max: number) {
return Math.max(min, Math.min(max, x));
}
/**
/*
* Euclidean modulo
*/
export function modulo(x: number, mod: number) {
@ -355,6 +358,7 @@ export function stringToTa(str: string) {
add = '~~';
break;
default:
// eslint-disable-next-line
const charCode = str.charCodeAt(i);
if (
(charCode >= 97 && charCode <= 122) || // a-z
@ -413,7 +417,7 @@ export function stringToSymbol(str: string) {
}
return result;
}
/**
/*
* Formats a numbers as a `@ud` inserting dot where needed
*/
export function numToUd(num: number) {
@ -428,7 +432,7 @@ export function numToUd(num: number) {
}
export function patpToUd(patp: Patp) {
return numToUd(patp2dec(patp))
return numToUd(patp2dec(patp));
}
export function usePreventWindowUnload(shouldPreventDefault: boolean, message = 'You have unsaved changes. Are you sure you want to exit?') {
@ -443,7 +447,7 @@ export function usePreventWindowUnload(shouldPreventDefault: boolean, message =
window.onbeforeunload = handleBeforeUnload;
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
// @ts-ignore
// @ts-ignore need better window typings
window.onbeforeunload = undefined;
};
}, [shouldPreventDefault]);
@ -484,8 +488,8 @@ export function withHovering<T>(Component: React.ComponentType<T>) {
return React.forwardRef((props, ref) => {
const { hovering, bind } = useHovering();
// @ts-ignore needs type signature on return?
return <Component ref={ref} hovering={hovering} bind={bind} {...props} />
})
return <Component ref={ref} hovering={hovering} bind={bind} {...props} />;
});
}
const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/;
@ -500,14 +504,14 @@ export function getItemTitle(association: Association): string {
return association.metadata.title ?? association.resource ?? '';
}
export const svgDataURL = (svg) => 'data:image/svg+xml;base64,' + btoa(svg);
export const svgDataURL = svg => 'data:image/svg+xml;base64,' + btoa(svg);
export const svgBlobURL = (svg) => URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }));
export const svgBlobURL = svg => URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }));
export const favicon = () => {
let background = '#ffffff';
const contacts = useContactState.getState().contacts;
if (contacts.hasOwnProperty(`~${window.ship}`)) {
if (Object.prototype.hasOwnProperty.call(contacts, `~${window.ship}`)) {
background = `#${uxToHex(contacts[`~${window.ship}`].color)}`;
}
const foreground = foregroundFromBackground(background);
@ -518,4 +522,4 @@ export const favicon = () => {
colors: [background, foreground]
});
return svg;
}
};

View File

@ -1,7 +1,7 @@
import { NotificationGraphConfig, Timebox, Unreads, dateToDa } from "@urbit/api";
import { NotificationGraphConfig, Timebox, Unreads } from '@urbit/api';
import { patp2dec } from 'urbit-ob';
import BigIntOrderedMap from "@urbit/api/lib/BigIntOrderedMap";
import {useCallback} from "react";
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import { useCallback } from 'react';
// import { harkGraphHookReducer, harkGroupHookReducer, harkReducer } from "~/logic/subscription/hark";
import { createState } from './base';
@ -70,7 +70,7 @@ const useHarkState = createState<HarkState>('Hark', {
}, ['unreadNotes', 'notifications', 'archivedNotifications', 'unreads', 'notificationsCount']);
export function useHarkDm(ship: string) {
return useHarkState(useCallback(s => {
return useHarkState(useCallback((s) => {
return s.unreads.graph[`/ship/~${window.ship}/dm-inbox`]?.[`/${patp2dec(ship)}`];
}, [ship]));
}

View File

@ -10,6 +10,7 @@ export interface ShortcutMapping {
navForward: string;
navBack: string;
hideSidebar: string;
readGroup: string;
}
export interface SettingsState extends BaseState<SettingsState> {
@ -77,7 +78,8 @@ const useSettingsState = createState<SettingsState>('Settings', {
cycleBack: 'ctrl+;',
navForward: 'ctrl+]',
navBack: 'ctrl+[',
hideSidebar: 'ctrl+\\'
hideSidebar: 'ctrl+\\',
readGroup: 'shift+Escape'
}
});

View File

@ -78,6 +78,7 @@ class App extends React.Component {
this.store.setStateHandler(this.setState.bind(this));
this.state = this.store.state;
// eslint-disable-next-line
this.appChannel = new window.channel();
this.api = new GlobalApi(this.ship, this.appChannel, this.store);
gcpManager.configure(this.api);
@ -103,7 +104,7 @@ class App extends React.Component {
this.updateTheme(this.themeWatcher);
}, 500);
this.api.local.getBaseHash();
this.api.local.getRuntimeLag(); //TODO consider polling periodically
this.api.local.getRuntimeLag(); // TODO consider polling periodically
this.api.settings.getAll();
gcpManager.start();
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {

View File

@ -159,16 +159,17 @@ export const MessageAuthor = ({
fontWeight={nameMono ? '400' : '500'}
cursor='pointer'
onClick={doCopy}
title={`~${msg.author}`}
title={showNickname ? `~${msg.author}` : contact?.nickname}
>
{copyDisplay}
</Text>
<Text flexShrink={0} fontSize={0} gray>
<Text whiteSpace='nowrap' flexShrink={0} fontSize={0} gray>
{timestamp}
</Text>
<Text
flexShrink={0}
fontSize={0}
whiteSpace='nowrap'
gray
ml={2}
display={['none', hovering ? 'block' : 'none']}
@ -205,6 +206,7 @@ export const Message = React.memo(({
top='2px'
lineHeight="tall"
fontSize={0}
whiteSpace='nowrap'
gray
>
{timestamp}

View File

@ -136,7 +136,6 @@ export function ChatPane(props: ChatPaneProps): ReactElement {
}
return (
// @ts-ignore
<Col {...bind} height="100%" overflow="hidden" position="relative">
<ShareProfile
our={ourContact}

View File

@ -1,12 +1,12 @@
import { BaseInput, Box, Button, LoadingSpinner, Text } from '@tlon/indigo-react';
import { BaseInput, Box, Button, LoadingSpinner } from '@tlon/indigo-react';
import { hasProvider } from 'oembed-parser';
import React, { useCallback, useState } from 'react';
import React, { useCallback, useState, DragEvent, useEffect } from 'react';
import GlobalApi from '~/logic/api/global';
import { createPost } from '~/logic/api/graph';
import { parsePermalink, permalinkToReference } from '~/logic/lib/permalinks';
import { useFileDrag } from '~/logic/lib/useDrag';
import useStorage from '~/logic/lib/useStorage';
import {StatelessUrlInput} from '~/views/components/StatelessUrlInput';
import { StatelessUrlInput } from '~/views/components/StatelessUrlInput';
import SubmitDragger from '~/views/components/SubmitDragger';
interface LinkSubmitProps {
@ -21,35 +21,11 @@ const LinkSubmit = (props: LinkSubmitProps) => {
useStorage();
const [submitFocused, setSubmitFocused] = useState(false);
const [urlFocused, setUrlFocused] = useState(false);
const [linkValue, setLinkValueHook] = useState('');
const [linkValue, setLinkValue] = useState('');
const [linkTitle, setLinkTitle] = useState('');
const [disabled, setDisabled] = useState(false);
const [linkValid, setLinkValid] = useState(false);
const doPost = () => {
const url = linkValue;
const text = linkTitle ? linkTitle : linkValue;
const contents = url.startsWith('web+urbitgraph:/')
? [{ text }, permalinkToReference(parsePermalink(url)!)]
: [{ text }, { url }];
setDisabled(true);
const parentIndex = props.parentIndex || '';
const post = createPost(contents, parentIndex);
props.api.graph.addPost(
`~${props.ship}`,
props.name,
post
).then(() => {
setDisabled(false);
setLinkValue('');
setLinkTitle('');
setLinkValid(false);
});
};
const validateLink = (link) => {
const URLparser = new RegExp(
/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/
@ -96,6 +72,33 @@ const LinkSubmit = (props: LinkSubmitProps) => {
return link;
};
useEffect(() => {
setLinkValid(validateLink(linkValue));
}, [linkValue]);
const doPost = () => {
const url = linkValue;
const text = linkTitle ? linkTitle : linkValue;
const contents = url.startsWith('web+urbitgraph:/')
? [{ text }, permalinkToReference(parsePermalink(url)!)]
: [{ text }, { url }];
setDisabled(true);
const parentIndex = props.parentIndex || '';
const post = createPost(contents, parentIndex);
props.api.graph.addPost(
`~${props.ship}`,
props.name,
post
).then(() => {
setDisabled(false);
setLinkValue('');
setLinkTitle('');
setLinkValid(false);
});
};
const onFileDrag = useCallback(
(files: FileList | File[], e: DragEvent): void => {
if (!canUpload) {
@ -108,17 +111,6 @@ const LinkSubmit = (props: LinkSubmitProps) => {
const { bind, dragging } = useFileDrag(onFileDrag);
const onLinkChange = (linkValue: string) => {
setLinkValueHook(linkValue);
const link = validateLink(linkValue);
setLinkValid(link);
};
const setLinkValue = (linkValue: string) => {
onLinkChange(linkValue);
setLinkValueHook(linkValue);
};
const onPaste = useCallback(
(event: ClipboardEvent) => {
if (!event.clipboardData || !event.clipboardData.files.length) {
@ -137,8 +129,6 @@ const LinkSubmit = (props: LinkSubmitProps) => {
}
};
return (
<>
{/* @ts-ignore archaic event type mismatch */}
@ -165,7 +155,7 @@ const LinkSubmit = (props: LinkSubmitProps) => {
>
<LoadingSpinner />
</Box>}
{dragging && <SubmitDragger />}
{dragging && <SubmitDragger />}
<StatelessUrlInput
value={linkValue}
promptUpload={promptUpload}

View File

@ -16,7 +16,7 @@ import GlobalApi from '~/logic/api/global';
import { getNotificationKey } from '~/logic/lib/hark';
import { useLazyScroll } from '~/logic/lib/useLazyScroll';
import useLaunchState from '~/logic/state/launch';
import { daToUnix, MOMENT_CALENDAR_DATE } from '~/logic/lib/util';
import { daToUnix } from '~/logic/lib/util';
import useHarkState from '~/logic/state/hark';
import { Invites } from './invites';
import { Notification } from './notification';
@ -72,16 +72,6 @@ export default function Inbox(props: {
const notifications =
Array.from(props.showArchive ? archivedNotifications : notificationState) || [];
const calendar = {
...MOMENT_CALENDAR_DATE, sameDay: function (now) {
if (this.subtract(6, 'hours').isBefore(now)) {
return '[Earlier Today]';
} else {
return MOMENT_CALENDAR_DATE.sameDay;
}
}
};
const notificationsByDay = f.flow(
f.map<DatedTimebox, DatedTimebox>(([date, nots]) => [
date,

View File

@ -1,16 +1,16 @@
import React, { ReactElement, ReactNode } from 'react';
import React, { ReactElement } from 'react';
import _ from 'lodash';
import {
Invite,
AppInvites,
JoinRequest,
JoinRequest
} from '@urbit/api';
import GlobalApi from '~/logic/api/global';
import { alphabeticalOrder, resourceAsPath } from '~/logic/lib/util';
import useInviteState from '~/logic/state/invite';
import useGraphState from '~/logic/state/graph';
import {PendingDm} from './PendingDm';
import { PendingDm } from './PendingDm';
import InviteItem from '~/views/components/Invite';
interface InvitesProps {
@ -26,9 +26,9 @@ interface InviteRef {
export function Invites(props: InvitesProps): ReactElement {
const { api } = props;
const invites = useInviteState((state) => state.invites);
const invites = useInviteState(state => state.invites);
const pendingDms = useGraphState((s) => s.pendingDms) ?? [];
const pendingDms = useGraphState(s => s.pendingDms) ?? [];
const inviteArr: InviteRef[] = _.reduce(
invites,
@ -49,13 +49,12 @@ export function Invites(props: InvitesProps): ReactElement {
const invitesAndStatus: { [rid: string]: JoinRequest | InviteRef } = {
..._.keyBy(inviteArr, ({ invite }) => resourceAsPath(invite.resource)),
...pendingJoin,
...pendingJoin
};
return (
<>
{[...pendingDms].map((ship) => (
{[...pendingDms].map(ship => (
<PendingDm key={ship} api={api} ship={`~${ship}`} />
))}
{Object.keys(invitesAndStatus)

View File

@ -4,18 +4,14 @@ import {
GroupNotificationContents,
GroupNotificationsConfig, IndexedNotification,
IndexedNotification
NotificationGraphConfig
} from '@urbit/api';
import { BigInteger } from 'big-integer';
import _ from 'lodash';
import React, { ReactNode, useCallback } from 'react';
import GlobalApi from '~/logic/api/global';
import { getNotificationKey } from '~/logic/lib/hark';
import { getParentIndex } from '~/logic/lib/notification';
import { useHovering } from '~/logic/lib/util';
import useHarkState from '~/logic/state/hark';
import useLocalState from '~/logic/state/local';
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
import { SwipeMenu } from '~/views/components/SwipeMenu';
@ -29,32 +25,6 @@ export interface NotificationProps {
unread: boolean;
}
function getMuted(
idxNotif: IndexedNotification,
groups: GroupNotificationsConfig,
graphs: NotificationGraphConfig
) {
const { index, notification } = idxNotif;
if ('graph' in idxNotif.index) {
const { graph } = idxNotif.index.graph;
if (!('graph' in notification.contents)) {
throw new Error();
}
const parent = getParentIndex(idxNotif.index.graph, notification.contents.graph);
return (
_.findIndex(
graphs?.watching || [],
g => g.graph === graph && g.index === parent
) === -1
);
}
if ('group' in index) {
return _.findIndex(groups || [], g => g === index.group.group) === -1;
}
return false;
}
export function NotificationWrapper(props: {
api: GlobalApi;
time?: BigInteger;
@ -74,12 +44,6 @@ export function NotificationWrapper(props: {
return api.hark.archive(time, notification.index);
}, [time, notification]);
const groupConfig = useHarkState(state => state.notificationsGroupConfig);
const graphConfig = useHarkState(state => state.notificationsGraphConfig);
const isMuted =
time && notification && getMuted(notification, groupConfig, graphConfig);
const onClick = (e: any) => {
if (!notification || read) {
return;

View File

@ -169,13 +169,10 @@ export function ProfileActions(props: any): ReactElement {
}
export function Profile(props: any): ReactElement | null {
const { hideAvatars } = useSettingsState(selectCalmState);
const history = useHistory();
const nackedContacts = useContactState(state => state.nackedContacts);
const { contact, hasLoaded, isEdit, ship } = props;
const nacked = nackedContacts.has(ship);
const formRef = useRef(null);
useEffect(() => {
if (hasLoaded && !contact && !nacked) {
@ -183,8 +180,6 @@ export function Profile(props: any): ReactElement | null {
}
}, [hasLoaded, contact]);
const anchorRef = useRef<HTMLElement | null>(null);
if (!props.ship) {
return null;
}

View File

@ -5,7 +5,7 @@ import 'codemirror/addon/edit/continuelist';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/markdown/markdown';
import { useFormikContext } from 'formik';
import React, { useCallback, useRef } from 'react';
import React, { useCallback, useRef, DragEvent } from 'react';
import { UnControlled as CodeEditor } from 'react-codemirror2';
import { Prompt } from 'react-router-dom';
import { useFileDrag } from '~/logic/lib/useDrag';
@ -61,13 +61,6 @@ export function MarkdownEditor(
[onChange]
);
const handleBlur = useCallback(
(_i, e: any) => {
onBlur && onBlur(e);
},
[onBlur]
);
const { uploadDefault, canUpload } = useStorage();
const onFileDrag = useCallback(
@ -110,10 +103,10 @@ export function MarkdownEditor(
value={value}
options={options}
onChange={handleChange}
onDragLeave={(editor, e: DragEvent) => bind.onDragLeave(e)}
onDragOver={(editor, e: DragEvent) => bind.onDragOver(e)}
onDrop={(editor, e: DragEvent) => bind.onDrop(e)}
onDragEnter={(editor, e: DragEvent) => bind.onDragEnter(e)}
onDragLeave={(editor, e: any) => bind.onDragLeave(e)}
onDragOver={(editor, e: any) => bind.onDragOver(e)}
onDrop={(editor, e: any) => bind.onDrop(e)}
onDragEnter={(editor, e: any) => bind.onDragEnter(e)}
/>
{dragging && <SubmitDragger />}
</Box>

View File

@ -1,7 +1,8 @@
import { Box, Col, Row, Text } from '@tlon/indigo-react';
import { Box, Button, Col, Row, Text } from '@tlon/indigo-react';
import { Association, Graph } from '@urbit/api';
import React, { ReactElement } from 'react';
import React, { ReactElement, useCallback } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import GlobalApi from '~/logic/api/global';
import { useShowNickname } from '~/logic/lib/util';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
@ -14,6 +15,7 @@ interface NotebookProps {
association: Association;
baseUrl: string;
rootUrl: string;
api: GlobalApi;
}
export function Notebook(props: NotebookProps & RouteComponentProps): ReactElement | null {
@ -21,19 +23,23 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
ship,
book,
association,
graph
graph,
api
} = props;
const groups = useGroupState(state => state.groups);
const contacts = useContactState(state => state.contacts);
const group = groups[association?.group];
const relativePath = (p: string) => props.baseUrl + p;
const contact = contacts?.[`~${ship}`];
const showNickname = useShowNickname(contact);
const readBook = useCallback(() => {
api.hark.readGraph(association.resource);
}, [association.resource]);
if (!group) {
return null; // Waiting on groups to populate
}
@ -48,6 +54,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
{showNickname ? contact?.nickname : ship}
</Text>
</Box>
<Button onClick={readBook}>Mark all as Read</Button>
</Row>
<Box borderBottom={1} borderBottomColor="lightGray" />
<NotebookPosts

View File

@ -32,7 +32,9 @@ const StoreDebugger = (props: StoreDebuggerProps) => {
let output: any = false;
try {
output = _.get(state, filterToTry, undefined);
} catch (e) { }
} catch (e) {
console.log('filter failed');
}
if (output) {
console.log(output);
setText(objectToString(output));

View File

@ -8,7 +8,7 @@ import GlobalApi from '~/logic/api/global';
import { getChord } from '~/logic/lib/util';
import useSettingsState, {
selectSettingsState,
ShortcutMapping,
ShortcutMapping
} from '~/logic/state/settings';
import { AsyncButton } from '~/views/components/AsyncButton';
import { BackButton } from './BackButton';
@ -108,6 +108,7 @@ export default function ShortcutSettings(props: ShortcutSettingsProps) {
label="Cycle backward through channel list"
/>
<ChordInput id="hideSidebar" label="Show/hide group sidebar" />
<ChordInput id="readGroup" label="Read all in a group" />
</Box>
<AsyncButton primary width="fit-content">Save Changes</AsyncButton>
</Col>

View File

@ -12,7 +12,7 @@ import { LeapSettings } from './components/lib/LeapSettings';
import { NotificationPreferences } from './components/lib/NotificationPref';
import S3Form from './components/lib/S3Form';
import SecuritySettings from './components/lib/Security';
import {DmSettings} from './components/lib/DmSettings';
import { DmSettings } from './components/lib/DmSettings';
import ShortcutSettings from './components/lib/ShortcutSettings';
export const Skeleton = (props: { children: ReactNode }) => (

View File

@ -1,4 +1,4 @@
import { BaseImage, Box, Row } from '@tlon/indigo-react';
import { BaseImage, Box, Row, Text } from '@tlon/indigo-react';
import moment from 'moment';
import React, { ReactElement, ReactNode } from 'react';
import GlobalApi from '~/logic/api/global';
@ -94,7 +94,7 @@ export default function Author(props: AuthorProps & PropFunc<typeof Box>): React
)}
</Box>
<Box display='flex' alignItems='baseline'>
<Box
<Text
ml={showImage ? 2 : 0}
color='black'
fontSize='1'
@ -107,10 +107,11 @@ export default function Author(props: AuthorProps & PropFunc<typeof Box>): React
overflow="hidden"
textOverflow="ellipsis"
whiteSpace="nowrap"
title={showNickname ? cite(ship) : contact?.nickname}
onClick={doCopy}
>
{copyDisplay}
</Box>
</Text>
{ !dontShowTime && time && (
<Timestamp
height="fit-content"
@ -118,6 +119,7 @@ export default function Author(props: AuthorProps & PropFunc<typeof Box>): React
stamp={stamp}
fontSize={0}
time={time}
whiteSpace='nowrap'
ml={2}
color={unread ? 'blue' : 'gray'}
/>

View File

@ -46,7 +46,6 @@ export function Mention(props: {
const contact = useContact(`~${deSig(ship)}`);
const showNickname = useShowNickname(contact);
const name = showNickname ? contact?.nickname : cite(ship);
return (
<ProfileOverlay ship={ship} api={api} display="inline">
<Text
@ -57,6 +56,8 @@ export function Mention(props: {
color='blue'
fontSize={showNickname ? 1 : 0}
mono={!showNickname}
title={showNickname ? cite(ship) : contact?.nickname}
{...rest}
>
{name}
</Text>

View File

@ -1,4 +1,4 @@
import { Anchor, Box, Text } from '@tlon/indigo-react';
import { Anchor, Text } from '@tlon/indigo-react';
import { Contact, Group } from '@urbit/api';
import React from 'react';
import ReactMarkdown, { ReactMarkdownProps } from 'react-markdown';
@ -6,7 +6,6 @@ import RemarkDisableTokenizers from 'remark-disable-tokenizers';
import { isValidPatp } from 'urbit-ob';
import GlobalApi from '~/logic/api/global';
import { deSig } from '~/logic/lib/util';
import {PropFunc} from '~/types';
import { PermalinkEmbed } from '~/views/apps/permalinks/embed';
import { Mention } from '~/views/components/MentionText';
import RemoteContent from '~/views/components/RemoteContent';
@ -49,7 +48,7 @@ type RichTextProps = ReactMarkdownProps & {
py?: number;
overflowX?: any;
verticalAlign?: any;
};
};
const RichText = React.memo(({ disableRemoteContent = false, api, ...props }: RichTextProps) => (
<ReactMarkdown

View File

@ -245,7 +245,7 @@ const renderers = {
return (
<Text
mono
fontWeight='inherit'
fontWeight="inherit"
p={1}
backgroundColor="washedGray"
fontSize={0}
@ -285,7 +285,11 @@ const renderers = {
paragraph: ({ children }) => {
return (
<Box display="block">
<Text fontSize={1} lineHeight="tall" style={{ 'overflowWrap': 'break-word' }}>
<Text
fontSize={1}
lineHeight="tall"
style={{ overflowWrap: 'break-word' }}
>
{children}
</Text>
</Box>
@ -310,7 +314,7 @@ const renderers = {
className="clamp-message"
display="block"
borderRadius={1}
fontWeight='inherit'
fontWeight="inherit"
mono
fontSize={0}
backgroundColor="washedGray"
@ -340,23 +344,15 @@ const renderers = {
list: ({ depth, ordered, children }) => {
return ordered ? <Ol>{children}</Ol> : <Ul>{children}</Ul>;
},
'graph-mention': ({ ship }) => <Mention api={{} as any} ship={ship} />,
'graph-mention': ({ api, ship }) => <Mention api={api} ship={ship} />,
image: ({ url, tall }) => (
<Box mt="1" mb="2" flexShrink={0}>
<RemoteContent
key={url}
url={url}
tall={tall}
/>
<RemoteContent key={url} url={url} tall={tall} />
</Box>
),
'graph-url': ({ url, tall }) => (
<Box mt={1} mb={2} flexShrink={0}>
<RemoteContent
key={url}
url={url}
tall={tall}
/>
<RemoteContent key={url} url={url} tall={tall} />
</Box>
),
'graph-reference': ({ api, reference, transcluded }) => {
@ -437,16 +433,8 @@ export type GraphContentProps = PropFunc<typeof Box> & {
showOurContact: boolean;
};
export const GraphContent = React.memo((
props: GraphContentProps
) => {
const {
contents,
tall = false,
transcluded = 0,
api,
...rest
} = props;
export const GraphContent = React.memo((props: GraphContentProps) => {
const { contents, tall = false, transcluded = 0, api, ...rest } = props;
const [, ast] = stitchAsts(contents.map(contentToMdAst(tall)));
return (
<Box {...rest}>

View File

@ -1,18 +1,19 @@
import { AppName } from '@urbit/api';
import _ from 'lodash';
import React, { useEffect } from 'react';
import React, { useCallback, useEffect } from 'react';
import Helmet from 'react-helmet';
import {
Route,
RouteComponentProps, Switch
} from 'react-router-dom';
import GlobalApi from '~/logic/api/global';
import { useShortcut } from '~/logic/state/settings';
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
import { getGroupFromWorkspace } from '~/logic/lib/workspace';
import useGroupState from '~/logic/state/group';
import useHarkState from '~/logic/state/hark';
import useMetadataState from '~/logic/state/metadata';
import {DmResource} from '~/views/apps/chat/DmResource';
import { DmResource } from '~/views/apps/chat/DmResource';
import { StoreState } from '~/logic/store/type';
import { Workspace } from '~/types/workspace';
import '~/views/apps/links/css/custom.css';
@ -42,6 +43,12 @@ export function GroupsPane(props: GroupsPaneProps) {
const groupPath = getGroupFromWorkspace(workspace);
const groups = useGroupState(state => state.groups);
useShortcut('readGroup', useCallback(() => {
if(groupPath) {
api.hark.readGroup(groupPath);
}
}, [groupPath, api]));
const groupAssociation =
(groupPath && associations.groups[groupPath]) || undefined;
const group = (groupPath && groups[groupPath]) || undefined;
@ -56,7 +63,7 @@ export function GroupsPane(props: GroupsPaneProps) {
}
return () => {
setRecentGroups(gs => _.uniq([workspace.group, ...gs]));
}
};
}, [workspace]);
if (!(associations && (groupPath ? groupPath in groups : true))) {
@ -180,7 +187,6 @@ export function GroupsPane(props: GroupsPaneProps) {
<Route
path={relativePath('/new')}
render={(routeProps) => {
const newUrl = `${baseUrl}/new`;
return (
<Skeleton mobileHide recentGroups={recentGroups} {...props} baseUrl={baseUrl}>
<NewChannel

View File

@ -31,7 +31,7 @@ export function PopoverRoutes(
const groupSize = props.group.members.size;
const owner = resourceFromPath(props.association.group).ship.slice(1) === window.ship;
const owner = resourceFromPath(props.association?.group ?? '~zod/group').ship.slice(1) === window.ship;
const admin = props.group?.tags?.role?.admin.has(window.ship) || false;

View File

@ -102,8 +102,10 @@ export function SidebarDmItem(props: {
}) {
const { ship, selected = false } = props;
const contact = useContact(ship);
const title = contact?.nickname || (cite(ship) ?? ship);
const hideAvatars = false;
const { hideAvatars, hideNicknames } = useSettingsState(s => s.calm);
const title = (!hideNicknames && contact?.nickname)
? contact?.nickname
: (cite(ship) ?? ship);
const { unreads } = useHarkDm(ship) || { unreads: 0 };
const img =
contact?.avatar && !hideAvatars ? (
@ -131,7 +133,7 @@ export function SidebarDmItem(props: {
hasUnread={(unreads as number) > 0}
to={`/~landscape/messages/dm/${ship}`}
title={title}
mono={!contact?.nickname}
mono={hideAvatars || !contact?.nickname}
isSynced
>
{img}

View File

@ -20,7 +20,7 @@ function sidebarSort(
const alphabetical = (a: string, b: string) => {
const aAssoc = associations[a];
const bAssoc = associations[b];
const aTitle = aAssoc?.metadata?.title || b;
const aTitle = aAssoc?.metadata?.title || a;
const bTitle = bAssoc?.metadata?.title || b;
return alphabeticalOrder(aTitle, bTitle);
@ -33,8 +33,9 @@ function sidebarSort(
const bAppName = bAssoc?.['app-name'];
const aUpdated = a.startsWith('~')
? (inboxUnreads?.[`/${patp2dec(a)}`]?.last)
? (inboxUnreads?.[`/${patp2dec(a)}`]?.last || 0)
: (apps[aAppName]?.lastUpdated(a) || 0);
const bUpdated = b.startsWith('~')
? (inboxUnreads?.[`/${patp2dec(b)}`]?.last || 0)
: (apps[bAppName]?.lastUpdated(b) || 0);
@ -108,14 +109,19 @@ export function SidebarList(props: {
const offset = backward ? -1 : 1;
const newIdx = modulo(idx+offset, ordered.length - 1);
const { metadata, resource } = associations[ordered[newIdx]];
const joined = graphKeys.has(resource.slice(7));
let path = '/~landscape/home';
if ('graph' in metadata.config) {
path = getResourcePath(workspace, resource, joined, metadata.config.graph);
const newChannel = ordered[newIdx];
let path = '';
if(newChannel.startsWith('~')) {
path = `/~landscape/messages/dm/${newChannel}`;
} else {
const { metadata, resource } = associations.graph[ordered[newIdx]];
const joined = graphKeys.has(resource.slice(7));
if ('graph' in metadata.config) {
path = getResourcePath(workspace, resource, joined, metadata.config.graph);
}
}
history.push(path);
}, [selected, history.push]);
}, [ordered, selected, history.push]);
useShortcut('cycleForward', useCallback((e: KeyboardEvent) => {
cycleChannels(false);

View File

@ -21,7 +21,7 @@ import { Dropdown } from '~/views/components/Dropdown';
import { FormikOnBlur } from '~/views/components/FormikOnBlur';
import { NewChannel } from '~/views/landscape/components/NewChannel';
import { SidebarListConfig } from './types';
import {getFeedPath} from '~/logic/lib/util';
import { getFeedPath } from '~/logic/lib/util';
export function SidebarListHeader(props: {
api: GlobalApi;
@ -53,7 +53,7 @@ export function SidebarListHeader(props: {
const noun = (props.workspace?.type === 'messages') ? 'Messages' : 'Channels';
let feedPath = groupPath ? getFeedPath(associations.groups[groupPath]) : undefined;
const feedPath = groupPath ? getFeedPath(associations.groups[groupPath]) : undefined;
const unreadCount = useHarkState(
s => s.unreads?.graph?.[feedPath ?? '']?.['/']?.unreads as number ?? 0
@ -61,7 +61,7 @@ export function SidebarListHeader(props: {
return (
<Box>
{( !!feedPath) ? (
{( feedPath) ? (
<Row
flexShrink={0}
alignItems="center"

View File

@ -1,18 +1,14 @@
import { Box } from '@tlon/indigo-react';
import { PatpNoSig } from '@urbit/api';
import moment from 'moment';
import React, { ReactElement, useCallback, useEffect } from 'react';
import React from 'react';
import Helmet from 'react-helmet';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
import { Route, Switch } from 'react-router-dom';
import GlobalApi from '~/logic/api/global';
import { cite } from '~/logic/lib/util';
import useGraphState from '~/logic/state/graph';
import useHarkState from '~/logic/state/hark';
import { StoreState } from '~/logic/store/type';
import GlobalSubscription from '~/logic/subscription/global';
import { Workspace } from '~/types/workspace';
import { Body } from '../components/Body';
import { Loading } from '../components/Loading';
import { GroupsPane } from './components/GroupsPane';
import { JoinGroup } from './components/JoinGroup';
import { NewGroup } from './components/NewGroup';
@ -43,11 +39,10 @@ type LandscapeProps = StoreState & {
ship: PatpNoSig;
api: GlobalApi;
subscription: GlobalSubscription;
notificationsCount: number;
}
export default function Landscape(props) {
const notificationsCount = useHarkState(s => s.notificationsCount);
export default function Landscape(props: LandscapeProps) {
return (
<>
<Helmet defer={false}>