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