mirror of
https://github.com/urbit/shrub.git
synced 2024-12-19 00:13:12 +03:00
Merge branch 'release/next-userspace' into la/group-feed
This commit is contained in:
commit
59da9e1f49
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ec80a42446a1d80974f32bf87283435547441cb7ea3fcd340711df2ce6cbec81
|
||||
size 9146390
|
||||
oid sha256:d279b349fb7825ce1dfd79e98a647a397cdd9db9bb7b4f68636b02b33ae5d578
|
||||
size 9219596
|
||||
|
@ -67,18 +67,20 @@
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
::
|
||||
++ should-proxy-update
|
||||
|= =vase
|
||||
^- ?
|
||||
=/ =update:store !<(update:store vase)
|
||||
++ transform-proxy-update
|
||||
|= vas=vase
|
||||
^- (unit vase)
|
||||
:: TODO: should check if user is allowed to %add, %remove, %edit
|
||||
:: contact
|
||||
=/ =update:store !<(update:store vas)
|
||||
?- -.update
|
||||
%initial %.n
|
||||
%add %.y
|
||||
%remove %.y
|
||||
%edit %.y
|
||||
%allow %.n
|
||||
%disallow %.n
|
||||
%set-public %.n
|
||||
%initial ~
|
||||
%add `vas
|
||||
%remove `vas
|
||||
%edit `vas
|
||||
%allow ~
|
||||
%disallow ~
|
||||
%set-public ~
|
||||
==
|
||||
::
|
||||
++ resource-for-update resource-for-update:con
|
||||
|
@ -5,7 +5,7 @@
|
||||
/- glob
|
||||
/+ default-agent, verb, dbug
|
||||
|%
|
||||
++ hash 0v8buag.lcoib.dupqj.iprqk.spvqf
|
||||
++ hash 0v3.i5rmt.7fid4.sr9bh.0pcpi.cmc0v
|
||||
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
|
||||
+$ all-states
|
||||
$% state-0
|
||||
|
@ -63,31 +63,110 @@
|
||||
=* mark i.t.wire
|
||||
:_ this
|
||||
(build-permissions mark i.t.t.wire %next)^~
|
||||
::
|
||||
[%transform-add @ ~]
|
||||
=* mark i.t.wire
|
||||
:_ this
|
||||
(build-transform-add mark %next)^~
|
||||
==
|
||||
::
|
||||
++ on-fail on-fail:def
|
||||
::
|
||||
++ should-proxy-update
|
||||
|= =vase
|
||||
^- ?
|
||||
=/ =update:store !<(update:store vase)
|
||||
++ transform-proxy-update
|
||||
|= vas=vase
|
||||
^- (unit vase)
|
||||
=/ =update:store !<(update:store vas)
|
||||
=* rid resource.q.update
|
||||
=. p.update now.bowl
|
||||
?- -.q.update
|
||||
%add-graph %.n
|
||||
%remove-graph %.n
|
||||
%add-nodes (is-allowed-add:hc resource.q.update nodes.q.update)
|
||||
%remove-nodes (is-allowed-remove:hc resource.q.update indices.q.update)
|
||||
%add-signatures %.n
|
||||
%remove-signatures %.n
|
||||
%archive-graph %.n
|
||||
%unarchive-graph %.n
|
||||
%add-tag %.n
|
||||
%remove-tag %.n
|
||||
%keys %.n
|
||||
%tags %.n
|
||||
%tag-queries %.n
|
||||
%run-updates %.n
|
||||
%add-nodes
|
||||
?. (is-allowed-add:hc rid nodes.q.update)
|
||||
~
|
||||
=/ mark (get-mark:gra rid)
|
||||
?~ mark `vas
|
||||
|^
|
||||
=/ transform
|
||||
!< $-([index:store post:store atom ?] [index:store post:store])
|
||||
%. !>(*indexed-post:store)
|
||||
.^(tube:clay (scry:hc %cc %home /[u.mark]/transform-add-nodes))
|
||||
=/ [* result=(list [index:store node:store])]
|
||||
%+ roll
|
||||
(flatten-node-map ~(tap by nodes.q.update))
|
||||
(transform-list transform)
|
||||
=. nodes.q.update
|
||||
%- ~(gas by *(map index:store node:store))
|
||||
result
|
||||
[~ !>(update)]
|
||||
::
|
||||
++ flatten-node-map
|
||||
|= lis=(list [index:store node:store])
|
||||
^- (list [index:store node:store])
|
||||
|^
|
||||
%- sort-nodes
|
||||
%+ welp
|
||||
(turn lis empty-children)
|
||||
%- zing
|
||||
%+ turn lis
|
||||
|= [=index:store =node:store]
|
||||
^- (list [index:store node:store])
|
||||
?: ?=(%empty -.children.node)
|
||||
~
|
||||
%+ turn
|
||||
(tap-deep:gra index p.children.node)
|
||||
empty-children
|
||||
::
|
||||
++ empty-children
|
||||
|= [=index:store =node:store]
|
||||
^- [index:store node:store]
|
||||
[index node(children [%empty ~])]
|
||||
::
|
||||
++ sort-nodes
|
||||
|= unsorted=(list [index:store node:store])
|
||||
^- (list [index:store node:store])
|
||||
%+ sort unsorted
|
||||
|= [p=[=index:store *] q=[=index:store *]]
|
||||
^- ?
|
||||
(lth (lent index.p) (lent index.q))
|
||||
--
|
||||
::
|
||||
++ transform-list
|
||||
|= transform=$-([index:store post:store atom ?] [index:store post:store])
|
||||
|= $: [=index:store =node:store]
|
||||
[indices=(set index:store) lis=(list [index:store node:store])]
|
||||
==
|
||||
=/ l (lent index)
|
||||
=/ parent-modified=?
|
||||
%- ~(rep in indices)
|
||||
|= [i=index:store out=_|]
|
||||
?: out out
|
||||
=/ k (lent i)
|
||||
?: (lte l k)
|
||||
%.n
|
||||
=((swag [0 k] index) i)
|
||||
=/ [ind=index:store =post:store]
|
||||
(transform index post.node now.bowl parent-modified)
|
||||
:- (~(put in indices) index)
|
||||
(snoc lis [ind node(post post)])
|
||||
--
|
||||
::
|
||||
%remove-nodes
|
||||
?. (is-allowed-remove:hc resource.q.update indices.q.update)
|
||||
~
|
||||
`vas
|
||||
::
|
||||
%add-graph ~
|
||||
%remove-graph ~
|
||||
%add-signatures ~
|
||||
%remove-signatures ~
|
||||
%archive-graph ~
|
||||
%unarchive-graph ~
|
||||
%add-tag ~
|
||||
%remove-tag ~
|
||||
%keys ~
|
||||
%tags ~
|
||||
%tag-queries ~
|
||||
%run-updates ~
|
||||
==
|
||||
::
|
||||
++ resource-for-update resource-for-update:gra
|
||||
::
|
||||
++ initial-watch
|
||||
@ -111,7 +190,7 @@
|
||||
|= =vase
|
||||
^- [(list card) agent]
|
||||
=/ =update:store !<(update:store vase)
|
||||
?+ -.q.update [~ this]
|
||||
?+ -.q.update [~ this]
|
||||
%add-graph
|
||||
?~ mark.q.update `this
|
||||
=* mark u.mark.q.update
|
||||
@ -119,6 +198,7 @@
|
||||
:_ this(marks (~(put in marks) mark))
|
||||
:~ (build-permissions:hc mark %add %sing)
|
||||
(build-permissions:hc mark %remove %sing)
|
||||
(build-transform-add:hc mark %sing)
|
||||
==
|
||||
::
|
||||
%remove-graph
|
||||
@ -133,19 +213,14 @@
|
||||
|_ =bowl:gall
|
||||
+* grp ~(. group bowl)
|
||||
met ~(. mdl bowl)
|
||||
gra ~(. graph bowl)
|
||||
gra ~(. graph bowl)
|
||||
::
|
||||
++ scry
|
||||
|= [care=@t desk=@t =path]
|
||||
%+ weld
|
||||
/[care]/(scot %p our.bowl)/[desk]/(scot %da now.bowl)
|
||||
path
|
||||
::
|
||||
++ scry-mark
|
||||
|= =resource:res
|
||||
.^ (unit mark)
|
||||
(scry %gx %graph-store /graph-mark/(scot %p entity.resource)/[name.resource]/noun)
|
||||
==
|
||||
::
|
||||
++ perm-mark-name
|
||||
|= perm=@t
|
||||
^- @t
|
||||
@ -264,5 +339,13 @@
|
||||
=/ =mood:clay [%c da+now.bowl /[mark]/(perm-mark-name kind)]
|
||||
=/ =rave:clay ?:(?=(%sing mode) [mode mood] [mode mood])
|
||||
[%pass wire %arvo %c %warp our.bowl %home `rave]
|
||||
::
|
||||
++ build-transform-add
|
||||
|= [=mark mode=?(%sing %next)]
|
||||
^- card
|
||||
=/ =wire /transform-add/[mark]
|
||||
=/ =mood:clay [%c da+now.bowl /[mark]/transform-add-nodes]
|
||||
=/ =rave:clay ?:(?=(%sing mode) [mode mood] [mode mood])
|
||||
[%pass wire %arvo %c %warp our.bowl %home `rave]
|
||||
--
|
||||
|
||||
|
@ -386,14 +386,14 @@
|
||||
::
|
||||
?~ t.index
|
||||
=* p post.node
|
||||
?~ hash.p node(signatures.post *signatures:store)
|
||||
=/ =validated-portion:store
|
||||
[parent-hash author.p time-sent.p contents.p]
|
||||
=/ =hash:store `@ux`(sham validated-portion)
|
||||
?~ hash.p node(signatures.post *signatures:store)
|
||||
~| "signatures do not match the calculated hash"
|
||||
?> (are-signatures-valid:sigs our.bowl signatures.p hash now.bowl)
|
||||
~| "hash of post does not match calculated hash"
|
||||
?> =(hash u.hash.p)
|
||||
~| "signatures do not match the calculated hash"
|
||||
?> (are-signatures-valid:sigs our.bowl signatures.p hash now.bowl)
|
||||
node
|
||||
:: recurse children
|
||||
::
|
||||
|
@ -110,12 +110,12 @@
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
::
|
||||
++ should-proxy-update
|
||||
|= =vase
|
||||
=/ =update:store
|
||||
!<(update:store vase)
|
||||
++ transform-proxy-update
|
||||
|= vas=vase
|
||||
^- (unit vase)
|
||||
=/ =update:store !<(update:store vas)
|
||||
?: ?=(%initial -.update)
|
||||
%.n
|
||||
~
|
||||
|^
|
||||
=/ role=(unit (unit role-tag))
|
||||
(role-for-ship:grp resource.update src.bowl)
|
||||
@ -128,24 +128,36 @@
|
||||
%moderator moderator
|
||||
%janitor member
|
||||
==
|
||||
::
|
||||
++ member
|
||||
?: ?=(%add-members -.update)
|
||||
=(~(tap in ships.update) ~[src.bowl])
|
||||
?: ?=(%remove-members -.update)
|
||||
=(~(tap in ships.update) ~[src.bowl])
|
||||
%.n
|
||||
?: ?| ?& ?=(%add-members -.update)
|
||||
=(~(tap in ships.update) ~[src.bowl])
|
||||
==
|
||||
?& ?=(%remove-members -.update)
|
||||
=(~(tap in ships.update) ~[src.bowl])
|
||||
== ==
|
||||
`vas
|
||||
~
|
||||
::
|
||||
++ admin
|
||||
!?=(?(%remove-group %add-group) -.update)
|
||||
?. ?=(?(%remove-group %add-group) -.update)
|
||||
`vas
|
||||
~
|
||||
::
|
||||
++ moderator
|
||||
?= $? %add-members %remove-members
|
||||
%add-tag %remove-tag ==
|
||||
-.update
|
||||
?: ?=(?(%add-members %remove-members %add-tag %remove-tag) -.update)
|
||||
`vas
|
||||
~
|
||||
::
|
||||
++ non-member
|
||||
?& ?=(%add-members -.update)
|
||||
(can-join:grp resource.update src.bowl)
|
||||
=(~(tap in ships.update) ~[src.bowl])
|
||||
==
|
||||
?: ?& ?=(%add-members -.update)
|
||||
(can-join:grp resource.update src.bowl)
|
||||
=(~(tap in ships.update) ~[src.bowl])
|
||||
==
|
||||
`vas
|
||||
~
|
||||
--
|
||||
::
|
||||
++ resource-for-update resource-for-update:grp
|
||||
::
|
||||
++ take-update
|
||||
|
@ -24,8 +24,6 @@
|
||||
watch-on-self=_&
|
||||
==
|
||||
::
|
||||
+$ notif-kind
|
||||
[name=@t parent-lent=@ud mode=?(%each %count %none) watch=?]
|
||||
::
|
||||
++ scry
|
||||
|* [[our=@p now=@da] =mold p=path]
|
||||
@ -223,11 +221,11 @@
|
||||
|= [=index:graph-store out=(list card)]
|
||||
=| =indexed-post:graph-store
|
||||
=. index.p.indexed-post index
|
||||
=+ !<(u-notif-kind=(unit notif-kind) (tube !>(indexed-post)))
|
||||
=+ !<(u-notif-kind=(unit notif-kind:hook) (tube !>(indexed-post)))
|
||||
?~ u-notif-kind out
|
||||
=* notif-kind u.u-notif-kind
|
||||
=/ =stats-index:store
|
||||
[%graph rid (scag parent-lent.notif-kind index)]
|
||||
[%graph rid (scag parent.index-len.notif-kind index)]
|
||||
?. ?=(%each mode.notif-kind) out
|
||||
:_ out
|
||||
(poke-hark %read-each stats-index index)
|
||||
@ -386,8 +384,12 @@
|
||||
update-core(hark-pokes [action hark-pokes])
|
||||
::
|
||||
++ new-watch
|
||||
|= =index:graph-store
|
||||
update-core(new-watches [index new-watches])
|
||||
|= [=index:graph-store =watch-for:hook =index-len:hook]
|
||||
=? new-watches =(%siblings watch-for)
|
||||
[(scag parent.index-len index) new-watches]
|
||||
=? new-watches =(%children watch-for)
|
||||
[(scag self.index-len index) new-watches]
|
||||
update-core
|
||||
::
|
||||
++ check
|
||||
|- ^+ update-core
|
||||
@ -415,7 +417,7 @@
|
||||
|= =node:graph-store
|
||||
^+ update-core
|
||||
=. update-core (check-node-children node)
|
||||
=+ !< notif-kind=(unit notif-kind)
|
||||
=+ !< notif-kind=(unit notif-kind:hook)
|
||||
(get-conversion !>([0 post.node]))
|
||||
?~ notif-kind
|
||||
update-core
|
||||
@ -425,11 +427,11 @@
|
||||
name.u.notif-kind
|
||||
=* not-kind u.notif-kind
|
||||
=/ parent=index:post
|
||||
(scag parent-lent.not-kind index.post.node)
|
||||
(scag parent.index-len.not-kind index.post.node)
|
||||
=/ notif-index=index:store
|
||||
[%graph group rid module desc parent]
|
||||
?: =(our.bowl author.post.node)
|
||||
(self-post node notif-index [mode watch]:not-kind)
|
||||
(self-post node notif-index not-kind)
|
||||
=. update-core
|
||||
(update-unread-count not-kind notif-index [time-sent index]:post.node)
|
||||
=? update-core
|
||||
@ -442,7 +444,7 @@
|
||||
update-core
|
||||
::
|
||||
++ update-unread-count
|
||||
|= [=notif-kind =index:store time=@da ref=index:graph-store]
|
||||
|= [=notif-kind:hook =index:store time=@da ref=index:graph-store]
|
||||
=/ =stats-index:store
|
||||
(to-stats-index:store index)
|
||||
?- mode.notif-kind
|
||||
@ -454,19 +456,18 @@
|
||||
++ self-post
|
||||
|= $: =node:graph-store
|
||||
=index:store
|
||||
mode=?(%count %each %none)
|
||||
watch=?
|
||||
=notif-kind:hook
|
||||
==
|
||||
^+ update-core
|
||||
?: ?=(%none mode) update-core
|
||||
?: ?=(%none mode.notif-kind) update-core
|
||||
=/ =stats-index:store
|
||||
(to-stats-index:store index)
|
||||
=. update-core
|
||||
(hark %seen-index time-sent.post.node stats-index)
|
||||
=? update-core ?=(%count mode)
|
||||
=? update-core ?=(%count mode.notif-kind)
|
||||
(hark %read-count stats-index)
|
||||
=? update-core &(watch watch-on-self)
|
||||
(new-watch index.post.node)
|
||||
=? update-core watch-on-self
|
||||
(new-watch index.post.node [watch-for index-len]:notif-kind)
|
||||
update-core
|
||||
::
|
||||
++ add-unread
|
||||
|
@ -23,6 +23,7 @@
|
||||
state-2
|
||||
state-3
|
||||
state-4
|
||||
state-5
|
||||
==
|
||||
+$ unread-stats
|
||||
[indices=(set index:graph-store) last=@da]
|
||||
@ -46,8 +47,11 @@
|
||||
+$ state-4
|
||||
[%4 base-state]
|
||||
::
|
||||
+$ state-5
|
||||
[%5 base-state]
|
||||
::
|
||||
+$ inflated-state
|
||||
$: state-4
|
||||
$: state-5
|
||||
cache
|
||||
==
|
||||
:: $cache: useful to have precalculated, but can be derived from state
|
||||
@ -88,9 +92,18 @@
|
||||
=| cards=(list card)
|
||||
|^
|
||||
?- -.old
|
||||
%4
|
||||
%5
|
||||
:- (flop cards)
|
||||
this(-.state old, +.state (inflate-cache:ha old))
|
||||
::
|
||||
%4
|
||||
%_ $
|
||||
-.old %5
|
||||
::
|
||||
last-seen.old
|
||||
%- ~(run by last-seen.old)
|
||||
|=(old=@da (min old now.bowl))
|
||||
==
|
||||
::
|
||||
%3
|
||||
%_ $
|
||||
@ -765,7 +778,7 @@
|
||||
==
|
||||
::
|
||||
++ inflate-cache
|
||||
|= state-4
|
||||
|= state-5
|
||||
^+ +.state
|
||||
=. +.state
|
||||
*cache
|
||||
|
@ -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.579404e0378c0c8cd2fe.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.5e5637d7960360d37d6e.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -56,22 +56,27 @@
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
::
|
||||
++ should-proxy-update
|
||||
|= =vase
|
||||
=+ !<(=update:store vase)
|
||||
++ transform-proxy-update
|
||||
|= vas=vase
|
||||
^- (unit vase)
|
||||
=/ =update:store !<(update:store vas)
|
||||
?. ?=(?(%add %remove) -.update)
|
||||
%.n
|
||||
~
|
||||
=/ role=(unit (unit role-tag))
|
||||
(role-for-ship:grp group.update src.bowl)
|
||||
=/ =metadatum:store
|
||||
(need (peek-metadatum:met %groups group.update))
|
||||
?~ role %.n
|
||||
?~ role ~
|
||||
?^ u.role
|
||||
?=(?(%admin %moderator) u.u.role)
|
||||
?. ?=(%add -.update) %.n
|
||||
?& =(src.bowl entity.resource.resource.update)
|
||||
?=(%member-metadata vip.metadatum)
|
||||
==
|
||||
?: ?=(?(%admin %moderator) u.u.role)
|
||||
`vas
|
||||
~
|
||||
?. ?=(%add -.update) ~
|
||||
?: ?& =(src.bowl entity.resource.resource.update)
|
||||
?=(%member-metadata vip.metadatum)
|
||||
==
|
||||
`vas
|
||||
~
|
||||
::
|
||||
++ resource-for-update resource-for-update:met
|
||||
++ take-update
|
||||
|
@ -1,4 +1,4 @@
|
||||
/- sur=graph-view
|
||||
/- sur=graph-view, store=graph-store
|
||||
/+ resource, group-store
|
||||
^?
|
||||
=< [sur .]
|
||||
@ -17,6 +17,7 @@
|
||||
leave+leave
|
||||
groupify+groupify
|
||||
eval+so
|
||||
pending-indices+pending-indices
|
||||
::invite+invite
|
||||
==
|
||||
::
|
||||
@ -51,6 +52,9 @@
|
||||
:~ resource+(un dejs:resource)
|
||||
to+(uf ~ (mu dejs:resource))
|
||||
==
|
||||
::
|
||||
++ pending-indices (op hex (su ;~(pfix fas (more fas dem))))
|
||||
::
|
||||
++ invite !!
|
||||
::
|
||||
++ associated
|
||||
@ -60,4 +64,35 @@
|
||||
==
|
||||
--
|
||||
--
|
||||
::
|
||||
++ enjs
|
||||
=, enjs:format
|
||||
|%
|
||||
++ action
|
||||
|= act=^action
|
||||
^- json
|
||||
?> ?=(%pending-indices -.act)
|
||||
%+ frond %pending-indices
|
||||
%- pairs
|
||||
%+ turn ~(tap by pending.act)
|
||||
|= [h=hash:store i=index:store]
|
||||
^- [@t json]
|
||||
=/ idx (index i)
|
||||
?> ?=(%s -.idx)
|
||||
[p.idx s+(scot %ux h)]
|
||||
::
|
||||
++ index
|
||||
|= i=index:store
|
||||
^- json
|
||||
?: =(~ i) s+'/'
|
||||
=/ j=^tape ""
|
||||
|-
|
||||
?~ i [%s (crip j)]
|
||||
=/ k=json (numb i.i)
|
||||
?> ?=(%n -.k)
|
||||
%_ $
|
||||
i t.i
|
||||
j (weld j (weld "/" (trip +.k)))
|
||||
==
|
||||
--
|
||||
--
|
||||
|
@ -104,26 +104,35 @@
|
||||
resources.q.update
|
||||
::
|
||||
++ tap-deep
|
||||
|= =graph:store
|
||||
|= [=index:store =graph:store]
|
||||
^- (list [index:store node:store])
|
||||
=| =index:store
|
||||
=/ nodes=(list [atom node:store])
|
||||
(tap:orm:store graph)
|
||||
|- =* tap-nodes $
|
||||
^- (list [index:store node:store])
|
||||
%- zing
|
||||
%+ turn
|
||||
nodes
|
||||
|= [=atom =node:store]
|
||||
^- (list [index:store node:store])
|
||||
%+ welp
|
||||
^- (list [index:store node:store])
|
||||
[(snoc index atom) node]~
|
||||
?. ?=(%graph -.children.node)
|
||||
~
|
||||
%_ tap-nodes
|
||||
index (snoc index atom)
|
||||
nodes (tap:orm:store p.children.node)
|
||||
%+ roll (tap:orm:store graph)
|
||||
|= $: [=atom =node:store]
|
||||
lis=(list [index:store node:store])
|
||||
==
|
||||
=/ child-index (snoc index atom)
|
||||
=/ childless-node node(children [%empty ~])
|
||||
?: ?=(%empty -.children.node)
|
||||
(snoc lis [child-index childless-node])
|
||||
%+ weld
|
||||
(snoc lis [child-index childless-node])
|
||||
(tap-deep child-index p.children.node)
|
||||
::
|
||||
++ got-deep
|
||||
|= [=graph:store =index:store]
|
||||
^- node:store
|
||||
=/ ind index
|
||||
?> ?=(^ index)
|
||||
=/ =node:store (need (get:orm:store graph `atom`i.index))
|
||||
=. ind t.index
|
||||
|- ^- node:store
|
||||
?~ ind
|
||||
node
|
||||
?: ?=(%empty -.children.node)
|
||||
!!
|
||||
%_ $
|
||||
ind t.ind
|
||||
node (need (get:orm:store p.children.node i.ind))
|
||||
==
|
||||
::
|
||||
++ get-mark
|
||||
|
@ -85,15 +85,15 @@
|
||||
++ take-update
|
||||
|~ vase
|
||||
*[(list card) _^|(..on-init)]
|
||||
:: +should-proxy-update: should forward update to store
|
||||
:: +transform-proxy-update: optionally transform update
|
||||
::
|
||||
:: If %.y is produced, then the update is forwarded to the local
|
||||
:: store. If %.n is produced then the update is not forwarded and
|
||||
:: the poke fails.
|
||||
:: If ^ is produced, then the update is forwarded to the local
|
||||
:: store. If ~ is produced, the update is not forwarded and the
|
||||
:: poke fails.
|
||||
::
|
||||
++ should-proxy-update
|
||||
++ transform-proxy-update
|
||||
|~ vase
|
||||
*?
|
||||
*(unit vase)
|
||||
:: +initial-watch: produce initial state for a subscription
|
||||
::
|
||||
:: .resource is the resource being subscribed to.
|
||||
@ -301,20 +301,20 @@
|
||||
+* og ~(. push-hook bowl)
|
||||
::
|
||||
++ poke-update
|
||||
|= =vase
|
||||
|= vas=vase
|
||||
^- (quip card:agent:gall _state)
|
||||
?> (should-proxy-update:og vase)
|
||||
=/ wire
|
||||
(make-wire /store)
|
||||
=/ vax=(unit vase) (transform-proxy-update:og vas)
|
||||
?> ?=(^ vax)
|
||||
=/ wire (make-wire /store)
|
||||
:_ state
|
||||
[%pass wire %agent [our.bowl store-name.config] %poke update-mark.config vase]~
|
||||
[%pass wire %agent [our.bowl store-name.config] %poke update-mark.config u.vax]~
|
||||
::
|
||||
++ poke-hook-action
|
||||
|= =action
|
||||
^- (quip card:agent:gall _state)
|
||||
|^
|
||||
?- -.action
|
||||
%add (add +.action)
|
||||
%add (add +.action)
|
||||
%remove (remove +.action)
|
||||
%revoke (revoke +.action)
|
||||
==
|
||||
|
@ -18,8 +18,14 @@
|
||||
::
|
||||
++ notification-kind
|
||||
?+ index.p.i ~
|
||||
[@ ~] `[%message 0 %count %.n]
|
||||
[@ ~] `[%message [0 1] %count %none]
|
||||
==
|
||||
::
|
||||
++ transform-add-nodes
|
||||
|= [=index =post =atom was-parent-modified=?]
|
||||
^- [^index ^post]
|
||||
=- [- post(index -)]
|
||||
[atom ~]
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
|
@ -26,9 +26,22 @@
|
||||
::
|
||||
++ notification-kind
|
||||
?+ index.p.i ~
|
||||
[@ ~] `[%link 0 %each %.y]
|
||||
[@ @ %1 ~] `[%comment 1 %count %.n]
|
||||
[@ @ @ ~] `[%edit-comment 1 %none %.n]
|
||||
[@ ~] `[%link [0 1] %each %children]
|
||||
[@ @ %1 ~] `[%comment [1 2] %count %siblings]
|
||||
[@ @ @ ~] `[%edit-comment [1 2] %none %none]
|
||||
==
|
||||
::
|
||||
++ transform-add-nodes
|
||||
|= [=index =post =atom was-parent-modified=?]
|
||||
^- [^index ^post]
|
||||
=- [- post(index -)]
|
||||
?+ index ~|(transform+[index post] !!)
|
||||
[@ ~] [atom ~]
|
||||
[@ @ ~] [i.index atom ~]
|
||||
[@ @ @ ~]
|
||||
?: was-parent-modified
|
||||
[i.index atom i.t.t.index ~]
|
||||
index
|
||||
==
|
||||
--
|
||||
++ grab
|
||||
|
@ -25,10 +25,31 @@
|
||||
::
|
||||
++ notification-kind
|
||||
?+ index.p.i ~
|
||||
[@ %1 %1 ~] `[%note 0 %each %.n]
|
||||
[@ %1 @ ~] `[%edit-note 0 %none %.n]
|
||||
[@ %2 @ %1 ~] `[%comment 1 %count %.n]
|
||||
[@ %2 @ @ ~] `[%edit-comment 1 %none %.n]
|
||||
[@ %1 %1 ~] `[%note [0 1] %each %children]
|
||||
[@ %1 @ ~] `[%edit-note [0 1] %none %none]
|
||||
[@ %2 @ %1 ~] `[%comment [1 3] %count %siblings]
|
||||
[@ %2 @ @ ~] `[%edit-comment [1 3] %none %none]
|
||||
==
|
||||
::
|
||||
++ transform-add-nodes
|
||||
|= [=index =post =atom was-parent-modified=?]
|
||||
^- [^index ^post]
|
||||
=- [- post(index -)]
|
||||
?+ index ~|(transform+[index post] !!)
|
||||
[@ ~] [atom ~]
|
||||
[@ %1 ~] [atom %1 ~]
|
||||
::
|
||||
[@ %1 @ ~]
|
||||
?: was-parent-modified
|
||||
[atom %1 i.t.t.index ~]
|
||||
index
|
||||
::
|
||||
[@ %2 ~] [atom %2 ~]
|
||||
[@ %2 @ ~] [i.index %2 atom ~]
|
||||
[@ %2 @ @ ~]
|
||||
?: was-parent-modified
|
||||
[i.index %2 atom i.t.t.t.index ~]
|
||||
index
|
||||
==
|
||||
--
|
||||
++ grab
|
||||
|
@ -4,6 +4,7 @@
|
||||
++ grow
|
||||
|%
|
||||
++ noun act
|
||||
++ json (action:enjs act)
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
|
@ -42,6 +42,7 @@
|
||||
[%groupify rid=resource to=(unit resource)]
|
||||
[%forward rid=resource =update:store]
|
||||
[%eval =cord]
|
||||
[%pending-indices pending=(map hash:store index:store)]
|
||||
==
|
||||
--
|
||||
|
||||
|
@ -1,6 +1,17 @@
|
||||
/- *resource, graph-store, post
|
||||
^?
|
||||
|%
|
||||
::
|
||||
+$ mode ?(%each %count %none)
|
||||
::
|
||||
+$ watch-for ?(%siblings %children %none)
|
||||
::
|
||||
+$ index-len
|
||||
[parent=@ud self=@ud]
|
||||
::
|
||||
+$ notif-kind
|
||||
[name=@t =index-len =mode =watch-for]
|
||||
::
|
||||
+$ action
|
||||
$%
|
||||
[?(%listen %ignore) graph=resource =index:post]
|
||||
|
131
pkg/arvo/ted/graph/add-nodes.hoon
Normal file
131
pkg/arvo/ted/graph/add-nodes.hoon
Normal file
@ -0,0 +1,131 @@
|
||||
/- spider
|
||||
/+ strandio, store=graph-store, gra=graph, graph-view, sig=signatures
|
||||
=, strand=strand:spider
|
||||
=>
|
||||
|%
|
||||
++ scry-graph
|
||||
|= rid=resource:store
|
||||
=/ m (strand ,graph:store)
|
||||
^- form:m
|
||||
;< =update:store bind:m
|
||||
%+ scry:strandio update:store
|
||||
/gx/graph-store/graph/(scot %p entity.rid)/[name.rid]/noun
|
||||
?> ?=(%0 -.update)
|
||||
?> ?=(%add-graph -.q.update)
|
||||
(pure:m graph.q.update)
|
||||
--
|
||||
::
|
||||
^- thread:spider
|
||||
|= arg=vase
|
||||
=/ m (strand:spider ,vase)
|
||||
^- form:m
|
||||
=+ !<([~ =update:store] arg)
|
||||
?> ?=(%add-nodes -.q.update)
|
||||
=* poke-our poke-our:strandio
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
;< =graph:store bind:m (scry-graph resource.q.update)
|
||||
|^
|
||||
=. nodes.q.update
|
||||
%- ~(gas by *(map index:store node:store))
|
||||
%+ turn
|
||||
(concat-by-parent (sort-nodes nodes.q.update))
|
||||
add-hash-to-node
|
||||
=/ hashes (nodes-to-pending-indices nodes.q.update)
|
||||
;< ~ bind:m
|
||||
%^ poke-our %graph-push-hook
|
||||
%graph-update
|
||||
!>(update)
|
||||
(pure:m !>(`action:graph-view`[%pending-indices hashes]))
|
||||
::
|
||||
++ sort-nodes
|
||||
|= nodes=(map index:store node:store)
|
||||
^- (list [index:store node:store])
|
||||
%+ sort ~(tap by nodes)
|
||||
|= [p=[=index:store *] q=[=index:store *]]
|
||||
^- ?
|
||||
(lth (lent index.p) (lent index.q))
|
||||
::
|
||||
++ concat-by-parent
|
||||
|= lis=(list [index:store node:store])
|
||||
^- (list [index:store node:store])
|
||||
%~ tap by
|
||||
%+ roll lis
|
||||
|= $: [=index:store =node:store]
|
||||
nds=(map index:store node:store)
|
||||
==
|
||||
?: ?=(~ index) !!
|
||||
?: ?=([@ ~] index)
|
||||
(~(put by nds) index node)
|
||||
=/ ind (snip `(list atom)`index)
|
||||
=/ nod (~(get by nds) ind)
|
||||
?~ nod
|
||||
(~(put by nds) index node)
|
||||
=. children.u.nod
|
||||
:- %graph
|
||||
?: ?=(%empty -.children.u.nod)
|
||||
%+ gas:orm:store *graph:store
|
||||
[(rear index) node]~
|
||||
%^ put:orm:store p.children.u.nod
|
||||
(rear index)
|
||||
node
|
||||
(~(put by nds) ind u.nod)
|
||||
::
|
||||
++ add-hash-to-node
|
||||
=| parent-hash=(unit hash:store)
|
||||
|= [=index:store =node:store]
|
||||
^- [index:store node:store]
|
||||
=* loop $
|
||||
:- index
|
||||
=* p post.node
|
||||
=/ =hash:store
|
||||
=- `@ux`(sham -)
|
||||
:^ ?^ parent-hash
|
||||
parent-hash
|
||||
(index-to-parent-hash index)
|
||||
author.p
|
||||
time-sent.p
|
||||
contents.p
|
||||
%_ node
|
||||
hash.post `hash
|
||||
::
|
||||
:: TODO: enable signing our own post as soon as we're ready
|
||||
:: signatures.post
|
||||
:: %- ~(gas in *signatures:store)
|
||||
:: [(sign:sig our.bowl now.bowl hash)]~
|
||||
::
|
||||
children
|
||||
?: ?=(%empty -.children.node)
|
||||
children.node
|
||||
:- %graph
|
||||
%+ gas:orm:store *graph:store
|
||||
%+ turn (tap:orm:store p.children.node)
|
||||
|= [=atom =node:store]
|
||||
=/ [* nod=node:store]
|
||||
%_ loop
|
||||
parent-hash `hash
|
||||
index (snoc index atom)
|
||||
node node
|
||||
==
|
||||
[atom nod]
|
||||
==
|
||||
::
|
||||
++ index-to-parent-hash
|
||||
|= =index:store
|
||||
^- (unit hash:store)
|
||||
?: ?=(~ index)
|
||||
!!
|
||||
?: ?=([@ ~] index)
|
||||
~
|
||||
=/ node (got-deep:gra graph (snip `(list atom)`index))
|
||||
hash.post.node
|
||||
::
|
||||
++ nodes-to-pending-indices
|
||||
|= nodes=(map index:store node:store)
|
||||
^- (map hash:store index:store)
|
||||
%- ~(gas by *(map hash:store index:store))
|
||||
%+ turn ~(tap by nodes)
|
||||
|= [=index:store =node:store]
|
||||
^- [hash:store index:store]
|
||||
?> ?=(^ hash.post.node)
|
||||
[u.hash.post.node index]
|
||||
--
|
@ -191,6 +191,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
addGraph(ship: Patp, name: string, graph: any, mark: any) {
|
||||
return this.storeAction({
|
||||
'add-graph': {
|
||||
@ -225,12 +226,30 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
}
|
||||
};
|
||||
|
||||
const promise = this.hookAction(ship, action);
|
||||
const pendingPromise = this.spider(
|
||||
'graph-update',
|
||||
'graph-view-action',
|
||||
'graph-add-nodes',
|
||||
action
|
||||
);
|
||||
|
||||
markPending(action['add-nodes'].nodes);
|
||||
action['add-nodes'].resource.ship = action['add-nodes'].resource.ship.slice(1);
|
||||
console.log(action);
|
||||
this.store.handleEvent({ data: { 'graph-update': action } });
|
||||
return promise;
|
||||
action['add-nodes'].resource.ship =
|
||||
action['add-nodes'].resource.ship.slice(1);
|
||||
|
||||
return pendingPromise.then((pendingHashes) => {
|
||||
for (let index in action['add-nodes'].nodes) {
|
||||
action['add-nodes'].nodes[index].post.hash =
|
||||
pendingHashes['pending-indices'][index] || null;
|
||||
}
|
||||
|
||||
this.store.handleEvent({ data: {
|
||||
'graph-update': {
|
||||
'pending-indices': pendingHashes['pending-indices'],
|
||||
...action
|
||||
}
|
||||
} });
|
||||
});
|
||||
}
|
||||
|
||||
removeNodes(ship: Patp, name: string, indices: string[]) {
|
||||
|
@ -5,10 +5,11 @@
|
||||
// 1. call configure with a GlobalApi and GlobalStore.
|
||||
// 2. call start() to start the token refresh loop.
|
||||
//
|
||||
// If the ship has S3 credentials set, we don't try to get a token, but we keep
|
||||
// checking at regular intervals to see if they get unset. Otherwise, we try to
|
||||
// invoke the GCP token thread on the ship until it gives us an access token.
|
||||
// Once we have a token, we refresh it every hour or so, since it has an
|
||||
// If the ship does not have GCP storage configured, we don't try to get
|
||||
// a token, but we keep checking at regular intervals to see if it gets
|
||||
// configured. If GCP storage is configured, we try to invoke the GCP
|
||||
// get-token thread on the ship until it gives us an access token. Once
|
||||
// we have a token, we refresh it every hour or so according to its
|
||||
// intrinsic expiry.
|
||||
//
|
||||
//
|
||||
|
@ -107,7 +107,7 @@ export const progressDetails: Record<TutorialProgress, StepDetail> = {
|
||||
chat: {
|
||||
title: 'Chat',
|
||||
description:
|
||||
'Chat channels are for messaging within your group. Direct Messages are also supported, and are accessible from the “DMs” tile on the homescreen',
|
||||
'Chat channels are for messaging within your group. Direct Messages can be accessed from Messages in the top right',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/chat/ship/${TUTORIAL_HOST}/${TUTORIAL_CHAT}`,
|
||||
alignY: 'top',
|
||||
arrow: 'North',
|
||||
@ -156,7 +156,7 @@ export const progressDetails: Record<TutorialProgress, StepDetail> = {
|
||||
alignX: 'right',
|
||||
arrow: 'South',
|
||||
offsetX: -300 + MODAL_WIDTH / 2,
|
||||
offsetY: -60,
|
||||
offsetY: -4,
|
||||
},
|
||||
leap: {
|
||||
title: 'Leap',
|
||||
|
@ -4,12 +4,15 @@ import bigInt, { BigInteger } from "big-integer";
|
||||
|
||||
export const GraphReducer = (json, state) => {
|
||||
const data = _.get(json, 'graph-update', false);
|
||||
|
||||
if (data) {
|
||||
keys(data, state);
|
||||
addGraph(data, state);
|
||||
removeGraph(data, state);
|
||||
addNodes(data, state);
|
||||
removeNodes(data, state);
|
||||
|
||||
pendingIndices(data, state);
|
||||
}
|
||||
};
|
||||
|
||||
@ -94,8 +97,17 @@ const mapifyChildren = (children) => {
|
||||
}));
|
||||
};
|
||||
|
||||
const pendingIndices = (json, state) => {
|
||||
const data = _.get(json, 'pending-indices', false);
|
||||
if (data) {
|
||||
Object.keys(data).forEach((key) => {
|
||||
state.pendingIndices[data[key]] = key;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const addNodes = (json, state) => {
|
||||
const _addNode = (graph, index, node) => {
|
||||
const _addNode = (graph, index, node, resource) => {
|
||||
// set child of graph
|
||||
if (index.length === 1) {
|
||||
graph.set(index[0], node);
|
||||
@ -113,6 +125,36 @@ const addNodes = (json, state) => {
|
||||
return graph;
|
||||
};
|
||||
|
||||
const _remove = (graph, index) => {
|
||||
if (index.length === 1) {
|
||||
graph.delete(index[0]);
|
||||
} else {
|
||||
const child = graph.get(index[0]);
|
||||
if (child) {
|
||||
child.children = _remove(child.children, index.slice(1));
|
||||
graph.set(index[0], child);
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
||||
const _removePending = (graph, post) => {
|
||||
if (post.hash && state.pendingIndices[post.hash]) {
|
||||
let index = state.pendingIndices[post.hash];
|
||||
|
||||
if (index.split('/').length === 0) { return; }
|
||||
let indexArr = index.split('/').slice(1).map((ind) => {
|
||||
return bigInt(ind);
|
||||
});
|
||||
|
||||
graph = _remove(graph, indexArr);
|
||||
delete state.pendingIndices[post.hash];
|
||||
}
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
||||
const data = _.get(json, 'add-nodes', false);
|
||||
if (data) {
|
||||
if (!('graphs' in state)) { return; }
|
||||
@ -122,11 +164,22 @@ const addNodes = (json, state) => {
|
||||
state.graphs[resource] = new BigIntOrderedMap();
|
||||
}
|
||||
state.graphKeys.add(resource);
|
||||
|
||||
let indices = Array.from(Object.keys(data.nodes));
|
||||
|
||||
for (let index in data.nodes) {
|
||||
indices.sort((a, b) => {
|
||||
let aArr = a.split('/');
|
||||
let bArr = b.split('/');
|
||||
return bArr.length < aArr.length;
|
||||
});
|
||||
|
||||
let graph = state.graphs[resource];
|
||||
|
||||
indices.forEach((index) => {
|
||||
let node = data.nodes[index];
|
||||
if (index.split('/').length === 0) { return; }
|
||||
graph = _removePending(graph, node.post);
|
||||
|
||||
if (index.split('/').length === 0) { return; }
|
||||
index = index.split('/').slice(1).map((ind) => {
|
||||
return bigInt(ind);
|
||||
});
|
||||
@ -134,27 +187,32 @@ const addNodes = (json, state) => {
|
||||
if (index.length === 0) { return; }
|
||||
|
||||
node.children = mapifyChildren(node?.children || {});
|
||||
|
||||
|
||||
state.graphs[resource] = _addNode(
|
||||
state.graphs[resource],
|
||||
|
||||
graph = _addNode(
|
||||
graph,
|
||||
index,
|
||||
node
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
state.graphs[resource] = graph;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const removeNodes = (json, state) => {
|
||||
const _remove = (graph, index) => {
|
||||
if (index.length === 1) {
|
||||
graph.delete(index[0]);
|
||||
} else {
|
||||
const child = graph.get(index[0]);
|
||||
_remove(child.children, index.slice(1));
|
||||
graph.set(index[0], child);
|
||||
if (child) {
|
||||
_remove(child.children, index.slice(1));
|
||||
graph.set(index[0], child);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const data = _.get(json, 'remove-nodes', false);
|
||||
if (data) {
|
||||
const { ship, name } = data.resource;
|
||||
|
@ -60,7 +60,7 @@ export default class SettingsStateZusettingsReducer{
|
||||
getAll(json: any, state: SettingsStateZus) {
|
||||
const data = _.get(json, 'all');
|
||||
if(data) {
|
||||
_.merge(state, data);
|
||||
_.mergeWith(state, data, (obj, src) => _.isArray(src) ? src : undefined)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,8 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
},
|
||||
notificationsCount: 0,
|
||||
settings: {},
|
||||
pendingJoin: {}
|
||||
pendingJoin: {},
|
||||
pendingIndices: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -119,8 +119,8 @@ class App extends React.Component {
|
||||
|
||||
faviconString() {
|
||||
let background = '#ffffff';
|
||||
if (this.state.contacts.hasOwnProperty('/~/default')) {
|
||||
background = `#${uxToHex(this.state.contacts['/~/default'][window.ship].color)}`;
|
||||
if (this.state.contacts.hasOwnProperty(`~${window.ship}`)) {
|
||||
background = `#${uxToHex(this.state.contacts[`~${window.ship}`].color)}`;
|
||||
}
|
||||
const foreground = foregroundFromBackground(background);
|
||||
const svg = sigiljs({
|
||||
|
@ -159,6 +159,7 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
association={props.association}
|
||||
associations={props.associations}
|
||||
groups={props.groups}
|
||||
pendingSize={Object.keys(props.pendingIndices || {}).length}
|
||||
group={group}
|
||||
ship={owner}
|
||||
station={station}
|
||||
|
@ -79,7 +79,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
|
||||
props.deleteMessage();
|
||||
|
||||
props.api.graph.addPost(ship,name, post);
|
||||
props.api.graph.addPost(ship, name, post);
|
||||
}
|
||||
|
||||
uploadSuccess(url) {
|
||||
|
@ -246,7 +246,10 @@ export const MessageAuthor = ({
|
||||
scrollWindow,
|
||||
...rest
|
||||
}) => {
|
||||
const dark = useLocalState((state) => state.dark);
|
||||
const osDark = useLocalState((state) => state.dark);
|
||||
|
||||
const theme = useSettingsState(s => s.display.theme);
|
||||
const dark = theme === 'dark' || (theme === 'auto' && osDark)
|
||||
|
||||
const datestamp = moment
|
||||
.unix(msg['time-sent'] / 1000)
|
||||
|
@ -278,7 +278,8 @@ export default class ChatWindow extends Component<
|
||||
graph,
|
||||
history,
|
||||
groups,
|
||||
associations
|
||||
associations,
|
||||
pendingSize
|
||||
} = this.props;
|
||||
|
||||
const unreadMarkerRef = this.unreadMarkerRef;
|
||||
@ -320,6 +321,7 @@ export default class ChatWindow extends Component<
|
||||
onScroll={this.onScroll}
|
||||
data={graph}
|
||||
size={graph.size}
|
||||
pendingSize={pendingSize}
|
||||
id={association.resource}
|
||||
averageHeight={22}
|
||||
renderer={this.renderer}
|
||||
|
@ -156,7 +156,8 @@ h2 {
|
||||
blockquote {
|
||||
padding: 0 0 0 16px;
|
||||
margin: 0;
|
||||
border-left: 1px solid black;
|
||||
color: inherit;
|
||||
border-left: 1px solid;
|
||||
}
|
||||
|
||||
:root {
|
||||
@ -173,6 +174,7 @@ blockquote {
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
cursor: text;
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@ -242,7 +244,7 @@ pre.CodeMirror-placeholder.CodeMirror-line-like {
|
||||
}
|
||||
.chat .cm-s-tlon span.cm-comment {
|
||||
font-family: 'Source Code Pro';
|
||||
color: black;
|
||||
color: inherit;
|
||||
background-color: var(--light-gray);
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
@ -308,9 +310,6 @@ pre.CodeMirror-placeholder.CodeMirror-line-like {
|
||||
/* dark */
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
blockquote {
|
||||
border-left: 1px solid inherit;
|
||||
}
|
||||
|
||||
/* codemirror */
|
||||
.chat .cm-s-tlon.CodeMirror {
|
||||
@ -367,12 +366,4 @@ pre.CodeMirror-placeholder.CodeMirror-line-like {
|
||||
background: var(--medium-gray) !important;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.chat .cm-s-tlon span.cm-comment {
|
||||
color: black;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
associations,
|
||||
graphKeys,
|
||||
unreads,
|
||||
pendingIndices,
|
||||
storage,
|
||||
history
|
||||
} = props;
|
||||
@ -78,6 +79,7 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
baseUrl={resourceUrl}
|
||||
group={group}
|
||||
path={resource.group}
|
||||
pendingSize={Object.keys(props.pendingIndices || {}).length}
|
||||
api={api}
|
||||
mb={3}
|
||||
/>
|
||||
|
@ -1,21 +1,21 @@
|
||||
import React, { useRef, useCallback, useEffect, useMemo } from 'react';
|
||||
import React, {
|
||||
useRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
Component,
|
||||
} from "react";
|
||||
|
||||
import { Col, Text } from '@tlon/indigo-react';
|
||||
import bigInt from 'big-integer';
|
||||
import {
|
||||
Association,
|
||||
Graph,
|
||||
Unreads,
|
||||
Group,
|
||||
Rolodex,
|
||||
} from '@urbit/api';
|
||||
import { Col, Text } from "@tlon/indigo-react";
|
||||
import bigInt from "big-integer";
|
||||
import { Association, Graph, Unreads, Group, Rolodex } from "@urbit/api";
|
||||
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import VirtualScroller from '~/views/components/VirtualScroller';
|
||||
import { LinkItem } from './components/LinkItem';
|
||||
import LinkSubmit from './components/LinkSubmit';
|
||||
import { isWriter } from '~/logic/lib/group';
|
||||
import { StorageState } from '~/types';
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import VirtualScroller from "~/views/components/VirtualScroller";
|
||||
import { LinkItem } from "./components/LinkItem";
|
||||
import LinkSubmit from "./components/LinkSubmit";
|
||||
import { isWriter } from "~/logic/lib/group";
|
||||
import { StorageState } from "~/types";
|
||||
|
||||
interface LinkWindowProps {
|
||||
association: Association;
|
||||
@ -31,74 +31,109 @@ interface LinkWindowProps {
|
||||
api: GlobalApi;
|
||||
storage: StorageState;
|
||||
}
|
||||
export function LinkWindow(props: LinkWindowProps) {
|
||||
const { graph, api, association } = props;
|
||||
const fetchLinks = useCallback(
|
||||
async (newer: boolean) => {
|
||||
return true;
|
||||
/* stubbed, should we generalize the display of graphs in virtualscroller? */
|
||||
}, []
|
||||
);
|
||||
|
||||
const first = graph.peekLargest()?.[0];
|
||||
const [,,ship, name] = association.resource.split('/');
|
||||
const canWrite = isWriter(props.group, association.resource);
|
||||
const style = {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
};
|
||||
|
||||
const style = useMemo(() =>
|
||||
({
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
}), []);
|
||||
export class LinkWindow extends Component<LinkWindowProps, {}> {
|
||||
fetchLinks = async () => true;
|
||||
|
||||
if (!first) {
|
||||
return (
|
||||
<Col key={0} mx="auto" mt="4" maxWidth="768px" width="100%" flexShrink={0} px={3}>
|
||||
{ canWrite ? (
|
||||
<LinkSubmit storage={props.storage} name={name} ship={ship.slice(1)} api={api} />
|
||||
canWrite() {
|
||||
const { group, association } = this.props;
|
||||
return isWriter(group, association.resource);
|
||||
}
|
||||
|
||||
renderItem = ({ index, scrollWindow }) => {
|
||||
const { props } = this;
|
||||
const { association, graph, api } = props;
|
||||
const [, , ship, name] = association.resource.split("/");
|
||||
const node = graph.get(index);
|
||||
const first = graph.peekLargest()?.[0];
|
||||
const post = node?.post;
|
||||
if (!node || !post) {
|
||||
return null;
|
||||
}
|
||||
const linkProps = {
|
||||
...props,
|
||||
node,
|
||||
};
|
||||
if (this.canWrite() && index.eq(first ?? bigInt.zero)) {
|
||||
return (
|
||||
<React.Fragment key={index.toString()}>
|
||||
<Col
|
||||
key={index.toString()}
|
||||
mx="auto"
|
||||
mt="4"
|
||||
maxWidth="768px"
|
||||
width="100%"
|
||||
flexShrink={0}
|
||||
px={3}
|
||||
>
|
||||
<LinkSubmit
|
||||
storage={props.storage}
|
||||
name={name}
|
||||
ship={ship.slice(1)}
|
||||
api={api}
|
||||
/>
|
||||
</Col>
|
||||
<LinkItem {...linkProps} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
return <LinkItem key={index.toString()} {...linkProps} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { graph, api, association, storage, pendingSize } = this.props;
|
||||
const first = graph.peekLargest()?.[0];
|
||||
const [, , ship, name] = association.resource.split("/");
|
||||
if (!first) {
|
||||
return (
|
||||
<Col
|
||||
key={0}
|
||||
mx="auto"
|
||||
mt="4"
|
||||
maxWidth="768px"
|
||||
width="100%"
|
||||
flexShrink={0}
|
||||
px={3}
|
||||
>
|
||||
{this.canWrite() ? (
|
||||
<LinkSubmit
|
||||
storage={storage}
|
||||
name={name}
|
||||
ship={ship.slice(1)}
|
||||
api={api}
|
||||
/>
|
||||
) : (
|
||||
<Text>There are no links here yet. You do not have permission to post to this collection.</Text>
|
||||
)
|
||||
}
|
||||
<Text>
|
||||
There are no links here yet. You do not have permission to post to
|
||||
this collection.
|
||||
</Text>
|
||||
)}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Col width="100%" height="100%" position="relative">
|
||||
<VirtualScroller
|
||||
origin="top"
|
||||
offset={0}
|
||||
style={style}
|
||||
data={graph}
|
||||
averageHeight={100}
|
||||
size={graph.size}
|
||||
pendingSize={pendingSize}
|
||||
renderer={this.renderItem}
|
||||
loadRows={this.fetchLinks}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Col width="100%" height="100%" position="relative">
|
||||
<VirtualScroller
|
||||
origin="top"
|
||||
style={style}
|
||||
onStartReached={() => {}}
|
||||
onScroll={() => {}}
|
||||
data={graph}
|
||||
averageHeight={100}
|
||||
size={graph.size}
|
||||
renderer={({ index, scrollWindow }) => {
|
||||
const node = graph.get(index);
|
||||
const post = node?.post;
|
||||
if (!node || !post)
|
||||
return null;
|
||||
const linkProps = {
|
||||
...props,
|
||||
node,
|
||||
};
|
||||
if(canWrite && index.eq(first ?? bigInt.zero)) {
|
||||
return (
|
||||
<React.Fragment key={index.toString()}>
|
||||
<Col key={index.toString()} mx="auto" mt="4" maxWidth="768px" width="100%" flexShrink={0} px={3}>
|
||||
<LinkSubmit storage={props.storage} name={name} ship={ship.slice(1)} api={api} />
|
||||
</Col>
|
||||
<LinkItem {...linkProps} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
return <LinkItem key={index.toString()} {...linkProps} />;
|
||||
}}
|
||||
loadRows={fetchLinks}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
@ -29,6 +29,10 @@ export function ProfileImages(props: any): ReactElement {
|
||||
const { contact, hideCover, ship } = { ...props };
|
||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : '#000000';
|
||||
|
||||
const anchorRef = useRef<HTMLElement | null>(null)
|
||||
|
||||
useTutorialModal('profile', ship === `~${window.ship}`, anchorRef);
|
||||
|
||||
const cover =
|
||||
contact?.cover && !hideCover ? (
|
||||
<BaseImage
|
||||
@ -60,7 +64,7 @@ export function ProfileImages(props: any): ReactElement {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row width='100%' height='300px' position='relative'>
|
||||
<Row ref={anchorRef} width='100%' height='300px' position='relative'>
|
||||
{cover}
|
||||
<Center position='absolute' width='100%' height='100%'>
|
||||
{props.children}
|
||||
@ -111,7 +115,7 @@ export function ProfileStatus(props: any): ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
export function ProfileOwnControls(props: any): ReactElement {
|
||||
export function ProfileActions(props: any): ReactElement {
|
||||
const { ship, isPublic, contact, api } = { ...props };
|
||||
const history = useHistory();
|
||||
return (
|
||||
@ -137,7 +141,18 @@ export function ProfileOwnControls(props: any): ReactElement {
|
||||
contact={contact}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
) : (
|
||||
<>
|
||||
<Text
|
||||
py='2'
|
||||
cursor='pointer'
|
||||
fontWeight='500'
|
||||
onClick={() => history.push(`/~landscape/dm/${ship.substring(1)}`)}
|
||||
>
|
||||
Message
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
@ -145,9 +160,6 @@ export function ProfileOwnControls(props: any): ReactElement {
|
||||
export function Profile(props: any): ReactElement {
|
||||
const history = useHistory();
|
||||
|
||||
if (!props.ship) {
|
||||
return null;
|
||||
}
|
||||
const { contact, nackedContacts, hasLoaded, isPublic, isEdit, ship } = props;
|
||||
const nacked = nackedContacts.has(ship);
|
||||
const formRef = useRef(null);
|
||||
@ -160,30 +172,14 @@ export function Profile(props: any): ReactElement {
|
||||
|
||||
const anchorRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
useTutorialModal('profile', ship === `~${window.ship}`, anchorRef);
|
||||
if (!props.ship) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ViewInterface = () => {
|
||||
return (
|
||||
<Center p={[0, 4]} height='100%' width='100%'>
|
||||
<Box ref={anchorRef} maxWidth='600px' width='100%' position='relative'>
|
||||
<ViewProfile
|
||||
nacked={nacked}
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
isPublic={isPublic}
|
||||
api={props.api}
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
/>
|
||||
</Box>
|
||||
</Center>
|
||||
);
|
||||
};
|
||||
|
||||
const EditInterface = () => {
|
||||
return (
|
||||
<Center p={[0, 4]} height='100%' width='100%'>
|
||||
<Box ref={anchorRef} maxWidth='600px' width='100%' position='relative'>
|
||||
return (
|
||||
<Center p={[0, 4]} height='100%' width='100%'>
|
||||
<Box maxWidth='600px' width='100%' position='relative'>
|
||||
{ isEdit ? (
|
||||
<EditProfile
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
@ -193,10 +189,18 @@ export function Profile(props: any): ReactElement {
|
||||
associations={props.associations}
|
||||
isPublic={isPublic}
|
||||
/>
|
||||
</Box>
|
||||
</Center>
|
||||
);
|
||||
};
|
||||
|
||||
return isEdit ? <EditInterface /> : <ViewInterface />;
|
||||
) : (
|
||||
<ViewProfile
|
||||
nacked={nacked}
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
isPublic={isPublic}
|
||||
api={props.api}
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import useLocalState from '~/logic/state/local';
|
||||
import {
|
||||
ProfileHeader,
|
||||
ProfileControls,
|
||||
ProfileOwnControls,
|
||||
ProfileActions,
|
||||
ProfileStatus,
|
||||
ProfileImages
|
||||
} from './Profile';
|
||||
@ -25,7 +25,7 @@ export function ViewProfile(props: any) {
|
||||
<>
|
||||
<ProfileHeader>
|
||||
<ProfileControls>
|
||||
<ProfileOwnControls
|
||||
<ProfileActions
|
||||
ship={ship}
|
||||
isPublic={isPublic}
|
||||
contact={contact}
|
||||
@ -33,7 +33,7 @@ export function ViewProfile(props: any) {
|
||||
/>
|
||||
<ProfileStatus contact={contact} />
|
||||
</ProfileControls>
|
||||
<ProfileImages contact={contact} ship={ship} />
|
||||
<ProfileImages key={ship} contact={contact} ship={ship} />
|
||||
</ProfileHeader>
|
||||
<Row pb={2} alignItems='center' width='100%'>
|
||||
<Center width='100%'>
|
||||
|
@ -28,6 +28,7 @@ export const MarkdownField = ({
|
||||
width="100%"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
color="black"
|
||||
{...rest}
|
||||
>
|
||||
<MarkdownEditor
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Text, Col } from '@tlon/indigo-react';
|
||||
import { Box, Text, Col, Anchor } from '@tlon/indigo-react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import bigInt from 'big-integer';
|
||||
|
||||
@ -32,6 +32,14 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
const { notebook, note, contacts, ship, book, api, rootUrl, baseUrl, group } = props;
|
||||
const editCommentId = props.match.params.commentId;
|
||||
|
||||
const renderers = {
|
||||
link: ({ href, children }) => {
|
||||
return (
|
||||
<Anchor display="inline" target="_blank" href={href}>{children}</Anchor>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
const deletePost = async () => {
|
||||
setDeleting(true);
|
||||
const indices = [note.post.index];
|
||||
@ -107,7 +115,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
</Box>
|
||||
</Col>
|
||||
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
|
||||
<ReactMarkdown source={body} linkTarget={'_blank'} />
|
||||
<ReactMarkdown source={body} linkTarget={'_blank'} renderers={renderers} />
|
||||
</Box>
|
||||
<NoteNavigation
|
||||
notebook={notebook}
|
||||
|
@ -48,9 +48,14 @@ export function NotePreview(props: NotePreviewProps) {
|
||||
const snippet = getSnippet(body);
|
||||
|
||||
const commColor = (props.unreads.graph?.[appPath]?.[`/${noteId}`]?.unreads ?? 0) > 0 ? 'blue' : 'gray';
|
||||
|
||||
const cursorStyle = post.pending ? 'default' : 'pointer';
|
||||
|
||||
return (
|
||||
<Box width='100%'>
|
||||
<Link to={url}>
|
||||
<Box width='100%' opacity={post.pending ? '0.5' : '1'}>
|
||||
<Link
|
||||
to={post.pending ? '#' : url}
|
||||
style={ { cursor: cursorStyle } }>
|
||||
<Col
|
||||
lineHeight='tall'
|
||||
width='100%'
|
||||
|
@ -33,10 +33,7 @@ export default function NewPost(props: NewPostProps & RouteComponentProps) {
|
||||
try {
|
||||
const [noteId, nodes] = newPost(title, body);
|
||||
await api.graph.addNodes(ship, book, nodes);
|
||||
await waiter(p =>
|
||||
p.graph.has(noteId) && !p.graph.get(noteId)?.post?.pending
|
||||
);
|
||||
history.push(`${props.baseUrl}/note/${noteId}`);
|
||||
history.push(`${props.baseUrl}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: 'Posting note failed' });
|
||||
|
@ -41,6 +41,8 @@
|
||||
cursor: text;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.publish .CodeMirror * {
|
||||
|
@ -67,6 +67,7 @@ export default class TermApp extends Component {
|
||||
backgroundColor='white'
|
||||
width='100%'
|
||||
minHeight='0'
|
||||
minWidth='0'
|
||||
color='washedGray'
|
||||
borderRadius='2'
|
||||
mx={['0','3']}
|
||||
|
@ -13,6 +13,7 @@ export class History extends Component {
|
||||
<Box
|
||||
height='100%'
|
||||
minHeight='0'
|
||||
minWidth='0'
|
||||
display='flex'
|
||||
flexDirection='column-reverse'
|
||||
overflowY='scroll'
|
||||
|
@ -8,6 +8,7 @@ import { Group } from '@urbit/api';
|
||||
|
||||
import { uxToHex, cite, useShowNickname, deSig } from '~/logic/lib/util';
|
||||
import useSettingsState, {selectCalmState} from "~/logic/state/settings";
|
||||
import useLocalState from "~/logic/state/local";
|
||||
import OverlaySigil from './OverlaySigil';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
@ -28,11 +29,16 @@ interface AuthorProps {
|
||||
export default function Author(props: AuthorProps): ReactElement {
|
||||
const { contacts, ship = '', date, showImage, group } = props;
|
||||
const history = useHistory();
|
||||
const osDark = useLocalState((state) => state.dark);
|
||||
|
||||
const theme = useSettingsState(s => s.display.theme);
|
||||
const dark = theme === 'dark' || (theme === 'auto' && osDark)
|
||||
|
||||
let contact;
|
||||
if (contacts) {
|
||||
contact = `~${deSig(ship)}` in contacts ? contacts[`~${deSig(ship)}`] : null;
|
||||
}
|
||||
const color = contact?.color ? `#${uxToHex(contact?.color)}` : '#000000';
|
||||
const color = contact?.color ? `#${uxToHex(contact?.color)}` : dark ? '#000000' : '#FFFFFF';
|
||||
const showNickname = useShowNickname(contact);
|
||||
const { hideAvatars } = useSettingsState(selectCalmState);
|
||||
const name = showNickname ? contact.nickname : cite(ship);
|
||||
|
@ -42,6 +42,8 @@ export default function CommentInput(props: CommentInputProps) {
|
||||
validationSchema={formSchema}
|
||||
onSubmit={props.onSubmit}
|
||||
initialValues={initialValues}
|
||||
validateOnBlur={false}
|
||||
validateOnChange={false}
|
||||
>
|
||||
<Form>
|
||||
<SubmitTextArea
|
||||
|
@ -21,6 +21,8 @@ interface DropdownProps {
|
||||
options: ReactNode;
|
||||
alignY: AlignY | AlignY[];
|
||||
alignX: AlignX | AlignX[];
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
width?: string;
|
||||
dropWidth?: string;
|
||||
}
|
||||
@ -37,7 +39,7 @@ const DropdownOptions = styled(Box)`
|
||||
`;
|
||||
|
||||
export function Dropdown(props: DropdownProps): ReactElement {
|
||||
const { children, options } = props;
|
||||
const { children, options, offsetX = 0, offsetY = 0 } = props;
|
||||
const dropdownRef = useRef<HTMLElement>(null);
|
||||
const anchorRef = useRef<HTMLElement>(null);
|
||||
const { pathname } = useLocation();
|
||||
@ -45,7 +47,7 @@ export function Dropdown(props: DropdownProps): ReactElement {
|
||||
const [coords, setCoords] = useState({});
|
||||
|
||||
const updatePos = useCallback(() => {
|
||||
const newCoords = getRelativePosition(anchorRef.current, props.alignX, props.alignY);
|
||||
const newCoords = getRelativePosition(anchorRef.current, props.alignX, props.alignY, offsetX, offsetY);
|
||||
if(newCoords) {
|
||||
setCoords(newCoords);
|
||||
}
|
||||
|
@ -44,6 +44,10 @@ export function InviteItem(props: InviteItemProps) {
|
||||
if (!(app && invite && uid)) {
|
||||
return;
|
||||
}
|
||||
if(resource in props.groups) {
|
||||
await api.invite.decline(app, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
api.groups.join(ship, name);
|
||||
await waiter(p => resource in p.pendingJoin);
|
||||
|
@ -109,9 +109,9 @@ const StatusBar = (props) => {
|
||||
alignY="top"
|
||||
alignX="right"
|
||||
flexShrink={'0'}
|
||||
offsetY={-48}
|
||||
options={
|
||||
<Col
|
||||
mt='6'
|
||||
p='1'
|
||||
backgroundColor="white"
|
||||
color="washedGray"
|
||||
|
@ -24,19 +24,50 @@ interface RendererProps {
|
||||
}
|
||||
|
||||
interface VirtualScrollerProps<T> {
|
||||
/**
|
||||
* Start scroll from
|
||||
*/
|
||||
origin: 'top' | 'bottom';
|
||||
/**
|
||||
* Load more of the graph
|
||||
*
|
||||
* @returns boolean whether or not the graph is now fully loaded
|
||||
*/
|
||||
loadRows(newer: boolean): Promise<boolean>;
|
||||
/**
|
||||
* The data to iterate over
|
||||
*/
|
||||
data: BigIntOrderedMap<T>;
|
||||
id: string;
|
||||
/**
|
||||
* The component to render the items
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This component must be referentially stable, so either use `useCallback` or
|
||||
* a instance method. It must also forward the DOM ref from its root DOM node
|
||||
*/
|
||||
renderer: (props: RendererProps) => JSX.Element | null;
|
||||
onStartReached?(): void;
|
||||
onEndReached?(): void;
|
||||
size: number;
|
||||
pendingSize: number;
|
||||
totalSize: number;
|
||||
/**
|
||||
* Average height of a single rendered item
|
||||
*
|
||||
* @remarks
|
||||
* This is used primarily to calculate how many items should be onscreen. If
|
||||
* size is variable, err on the lower side.
|
||||
*/
|
||||
averageHeight: number;
|
||||
/**
|
||||
* The offset to begin rendering at, on load.
|
||||
*
|
||||
* @remarks
|
||||
* This is only looked up once, on component creation. Subsequent changes to
|
||||
* this prop will have no effect
|
||||
*/
|
||||
offset: number;
|
||||
onCalculateVisibleItems?(visibleItems: BigIntOrderedMap<T>): void;
|
||||
onScroll?({ scrollTop, scrollHeight, windowHeight }): void;
|
||||
style?: any;
|
||||
}
|
||||
|
||||
@ -61,6 +92,12 @@ const ZONE_SIZE = IS_IOS ? 10 : 40;
|
||||
// nb: in this file, an index refers to a BigInteger and an offset refers to a
|
||||
// number used to index a listified BigIntOrderedMap
|
||||
|
||||
/**
|
||||
* A virtualscroller for a `BigIntOrderedMap`.
|
||||
*
|
||||
* VirtualScroller does not clean up or reset itself, so please use `key`
|
||||
* to ensure a new instance is created for each BigIntOrderedMap
|
||||
*/
|
||||
export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T>, VirtualScrollerState<T>> {
|
||||
/**
|
||||
* A reference to our scroll container
|
||||
@ -87,8 +124,6 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
|
||||
*/
|
||||
private saveDepth = 0;
|
||||
|
||||
private isUpdating = false;
|
||||
|
||||
private scrollLocked = true;
|
||||
|
||||
private pageSize = 50;
|
||||
@ -97,7 +132,6 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
|
||||
|
||||
private scrollRef: HTMLElement | null = null;
|
||||
|
||||
|
||||
private loaded = {
|
||||
top: false,
|
||||
bottom: false
|
||||
@ -119,12 +153,14 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if(true) {
|
||||
this.updateVisible(0);
|
||||
this.resetScroll();
|
||||
this.loadRows(false);
|
||||
return;
|
||||
if(this.props.size < 100) {
|
||||
this.loaded.top = true;
|
||||
this.loaded.bottom = true;
|
||||
}
|
||||
|
||||
this.updateVisible(0);
|
||||
this.resetScroll();
|
||||
this.loadRows(false);
|
||||
}
|
||||
|
||||
// manipulate scrollbar manually, to dodge change detection
|
||||
@ -146,9 +182,10 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
|
||||
|
||||
|
||||
componentDidUpdate(prevProps: VirtualScrollerProps<T>, _prevState: VirtualScrollerState<T>) {
|
||||
const { id, size, data, offset } = this.props;
|
||||
const { id, size, data, offset, pendingSize } = this.props;
|
||||
const { visibleItems } = this.state;
|
||||
if(size !== prevProps.size) {
|
||||
|
||||
if(size !== prevProps.size || pendingSize !== prevProps.pendingSize) {
|
||||
if(this.scrollLocked) {
|
||||
this.updateVisible(0);
|
||||
this.resetScroll();
|
||||
@ -168,7 +205,9 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
|
||||
}
|
||||
const offset = [...this.props.data].findIndex(([i]) => i.eq(startIndex))
|
||||
if(offset === -1) {
|
||||
throw new Error("a");
|
||||
// TODO: revisit when we remove nodes for any other reason than
|
||||
// pending indices being removed
|
||||
return 0;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
@ -182,7 +221,6 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
|
||||
return;
|
||||
}
|
||||
log('reflow', `from: ${this.startOffset()} to: ${newOffset}`);
|
||||
this.isUpdating = true;
|
||||
|
||||
const { data, onCalculateVisibleItems } = this.props;
|
||||
const visibleItems = new BigIntOrderedMap<any>(
|
||||
@ -191,14 +229,12 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
|
||||
|
||||
this.save();
|
||||
|
||||
onCalculateVisibleItems ? onCalculateVisibleItems(visibleItems) : null;
|
||||
this.setState({
|
||||
visibleItems,
|
||||
}, () => {
|
||||
requestAnimationFrame(() => {
|
||||
this.restore();
|
||||
requestAnimationFrame(() => {
|
||||
this.isUpdating = false;
|
||||
|
||||
});
|
||||
});
|
||||
@ -449,9 +485,8 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
|
||||
|
||||
const transform = isTop ? 'scale3d(1, 1, 1)' : 'scale3d(1, -1, 1)';
|
||||
|
||||
const loaded = this.props.data.size > 0;
|
||||
|
||||
const atStart = loaded && this.props.data.peekLargest()?.[0].eq(visibleItems.peekLargest()?.[0] || bigInt.zero);
|
||||
const atStart = (this.props.data.peekLargest()?.[0] ?? bigInt.zero).eq(visibleItems.peekLargest()?.[0] || bigInt.zero);
|
||||
const atEnd = this.loaded.top;
|
||||
|
||||
return (
|
||||
|
@ -286,8 +286,15 @@ function Participant(props: {
|
||||
|
||||
const onKick = useCallback(async () => {
|
||||
const resource = resourceFromPath(association.group);
|
||||
await api.groups.remove(resource, [`~${contact.patp}`]);
|
||||
}, [api, association]);
|
||||
if(contact.pending) {
|
||||
await api.groups.changePolicy(
|
||||
resource,
|
||||
{ invite: { removeInvites: [`~${contact.patp}`] } }
|
||||
);
|
||||
} else {
|
||||
await api.groups.remove(resource, [`~${contact.patp}`]);
|
||||
}
|
||||
}, [api, contact, association]);
|
||||
|
||||
const avatar =
|
||||
contact?.avatar !== null && !hideAvatars ? (
|
||||
|
@ -98,6 +98,7 @@ export function TutorialModal(props: { api: GlobalApi }) {
|
||||
}, [tutorialRef]);
|
||||
|
||||
const dismiss = useCallback(async () => {
|
||||
setPaused(false);
|
||||
hideTutorial();
|
||||
await props.api.settings.putEntry('tutorial', 'seen', true);
|
||||
}, [hideTutorial, props.api]);
|
||||
@ -228,6 +229,7 @@ export function TutorialModal(props: { api: GlobalApi }) {
|
||||
direction={arrow}
|
||||
height="0px"
|
||||
width="0px"
|
||||
display={["none", "block"]}
|
||||
/>
|
||||
|
||||
<Box
|
||||
|
Loading…
Reference in New Issue
Block a user