mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-28 03:00:15 +03:00
Merge branch 'release/next-userspace' into la/contact-store
This commit is contained in:
commit
72e036bae3
@ -278,7 +278,7 @@
|
|||||||
=/ app-rid=resource
|
=/ app-rid=resource
|
||||||
(path-to-resource path)
|
(path-to-resource path)
|
||||||
=/ group-rid=resource
|
=/ group-rid=resource
|
||||||
(fall (group-from-app-resource:met %graph app-rid) [nobody %bad-group])
|
(fall (peek-group:met %graph app-rid) [nobody %bad-group])
|
||||||
=/ group=(unit group)
|
=/ group=(unit group)
|
||||||
(scry-group:grp group-rid)
|
(scry-group:grp group-rid)
|
||||||
:- (add-graph app-rid mailbox)
|
:- (add-graph app-rid mailbox)
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
++ on-pull-nack
|
++ on-pull-nack
|
||||||
|= [=resource =tang]
|
|= [=resource =tang]
|
||||||
^- (quip card _this)
|
^- (quip card _this)
|
||||||
|
~& nacked+resource
|
||||||
:_ this
|
:_ this
|
||||||
?. (~(has in get-keys:gra) resource) ~
|
?. (~(has in get-keys:gra) resource) ~
|
||||||
=- [%pass /pull-nack %agent [our.bowl %graph-store] %poke %graph-update -]~
|
=- [%pass /pull-nack %agent [our.bowl %graph-store] %poke %graph-update -]~
|
||||||
|
@ -25,12 +25,13 @@
|
|||||||
^- ?
|
^- ?
|
||||||
=/ grp ~(. group bowl)
|
=/ grp ~(. group bowl)
|
||||||
=/ met ~(. metadata bowl)
|
=/ met ~(. metadata bowl)
|
||||||
=/ group-paths (groups-from-resource:met [%graph (en-path:res resource)])
|
=/ group=(unit resource:res)
|
||||||
?~ group-paths %.n
|
(peek-group:met %graph resource)
|
||||||
|
?~ group %.n
|
||||||
?: requires-admin
|
?: requires-admin
|
||||||
(is-admin:grp src.bowl i.group-paths)
|
(is-admin:grp src.bowl u.group)
|
||||||
?| (is-member:grp src.bowl i.group-paths)
|
?| (is-member:grp src.bowl u.group)
|
||||||
(is-admin:grp src.bowl i.group-paths)
|
(is-admin:grp src.bowl u.group)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ is-allowed-remove
|
++ is-allowed-remove
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
:: hark-graph-hook: notifications for graph-store [landscape]
|
:: hark-graph-hook: notifications for graph-store [landscape]
|
||||||
::
|
::
|
||||||
/- post, group-store, metadata-store, hook=hark-graph-hook, store=hark-store
|
/- post, group-store, metadata=metadata-store, hook=hark-graph-hook, store=hark-store
|
||||||
/+ resource, metadata, default-agent, dbug, graph-store, graph, grouplib=group, store=hark-store
|
/+ resource, mdl=metadata, default-agent, dbug, graph-store, graph, grouplib=group, store=hark-store
|
||||||
::
|
::
|
||||||
::
|
::
|
||||||
~% %hark-graph-hook-top ..part ~
|
~% %hark-graph-hook-top ..part ~
|
||||||
@ -53,7 +53,7 @@
|
|||||||
+* this .
|
+* this .
|
||||||
ha ~(. +> bowl)
|
ha ~(. +> bowl)
|
||||||
def ~(. (default-agent this %|) bowl)
|
def ~(. (default-agent this %|) bowl)
|
||||||
met ~(. metadata bowl)
|
met ~(. mdl bowl)
|
||||||
grp ~(. grouplib bowl)
|
grp ~(. grouplib bowl)
|
||||||
gra ~(. graph bowl)
|
gra ~(. graph bowl)
|
||||||
::
|
::
|
||||||
@ -272,14 +272,14 @@
|
|||||||
rid=resource
|
rid=resource
|
||||||
==
|
==
|
||||||
=/ group=(unit resource)
|
=/ group=(unit resource)
|
||||||
(group-from-app-resource:met %graph rid)
|
(peek-group:met %graph rid)
|
||||||
?~ group
|
?~ group
|
||||||
~& no-group+rid
|
~& no-group+rid
|
||||||
`state
|
`state
|
||||||
=/ metadata=(unit metadata:metadata-store)
|
=/ metadatum=(unit metadatum:metadata)
|
||||||
(peek-metadata:met %graph u.group rid)
|
(peek-metadatum:met %graph rid)
|
||||||
?~ metadata `state
|
?~ metadatum `state
|
||||||
abet:check:(abed:handle-update:ha rid nodes u.group module.u.metadata)
|
abet:check:(abed:handle-update:ha rid nodes u.group module.u.metadatum)
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
++ on-peek on-peek:def
|
++ on-peek on-peek:def
|
||||||
@ -300,7 +300,7 @@
|
|||||||
--
|
--
|
||||||
::
|
::
|
||||||
|_ =bowl:gall
|
|_ =bowl:gall
|
||||||
+* met ~(. metadata bowl)
|
+* met ~(. mdl bowl)
|
||||||
grp ~(. grouplib bowl)
|
grp ~(. grouplib bowl)
|
||||||
gra ~(. graph bowl)
|
gra ~(. graph bowl)
|
||||||
::
|
::
|
||||||
@ -344,7 +344,7 @@
|
|||||||
|= rid=resource
|
|= rid=resource
|
||||||
^- ?
|
^- ?
|
||||||
=/ group-rid=(unit resource)
|
=/ group-rid=(unit resource)
|
||||||
(group-from-app-resource:met %graph rid)
|
(peek-group:met %graph rid)
|
||||||
?~ group-rid %.n
|
?~ group-rid %.n
|
||||||
?| !(is-managed:grp u.group-rid)
|
?| !(is-managed:grp u.group-rid)
|
||||||
&(watch-on-self =(our.bowl entity.rid))
|
&(watch-on-self =(our.bowl entity.rid))
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
:: hark-group-hook: notifications for groups [landscape]
|
:: hark-group-hook: notifications for groups [landscape]
|
||||||
::
|
::
|
||||||
/- store=hark-store, post, group-store, metadata-store, hook=hark-group-hook
|
/- store=hark-store, post, group-store, metadata=metadata-store, hook=hark-group-hook
|
||||||
/+ resource, metadata, default-agent, dbug, graph-store
|
/+ resource, mdl=metadata, default-agent, dbug, graph-store
|
||||||
::
|
::
|
||||||
~% %hark-group-hook-top ..part ~
|
~% %hark-group-hook-top ..part ~
|
||||||
|%
|
|%
|
||||||
@ -28,7 +28,7 @@
|
|||||||
+* this .
|
+* this .
|
||||||
ha ~(. +> bowl)
|
ha ~(. +> bowl)
|
||||||
def ~(. (default-agent this %|) bowl)
|
def ~(. (default-agent this %|) bowl)
|
||||||
met ~(. metadata bowl)
|
met ~(. mdl bowl)
|
||||||
::
|
::
|
||||||
++ on-init
|
++ on-init
|
||||||
:_ this
|
:_ this
|
||||||
@ -115,7 +115,7 @@
|
|||||||
::
|
::
|
||||||
%metadata-update
|
%metadata-update
|
||||||
=^ cards state
|
=^ cards state
|
||||||
(metadata-update !<(metadata-update:metadata-store q.cage.sign))
|
(metadata-update !<(update:metadata q.cage.sign))
|
||||||
[cards this]
|
[cards this]
|
||||||
==
|
==
|
||||||
==
|
==
|
||||||
@ -140,7 +140,7 @@
|
|||||||
:: - We have no way of retrieving old metadata to e.g. get a
|
:: - We have no way of retrieving old metadata to e.g. get a
|
||||||
:: channel's old name when it is renamed
|
:: channel's old name when it is renamed
|
||||||
++ metadata-update
|
++ metadata-update
|
||||||
|= update=metadata-update:metadata-store
|
|= =update:metadata
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
[~ state]
|
[~ state]
|
||||||
::
|
::
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
^- (list @tas)
|
^- (list @tas)
|
||||||
:~ %group-store
|
:~ %group-store
|
||||||
%metadata-store
|
%metadata-store
|
||||||
%metadata-hook
|
|
||||||
%contact-store
|
%contact-store
|
||||||
%contact-hook
|
%contact-hook
|
||||||
%invite-store
|
%invite-store
|
||||||
|
@ -6,13 +6,14 @@
|
|||||||
:: /group/%group-path all updates related to this group
|
:: /group/%group-path all updates related to this group
|
||||||
::
|
::
|
||||||
/- *metadata-store, *metadata-hook
|
/- *metadata-store, *metadata-hook
|
||||||
/+ default-agent, dbug, verb, grpl=group, *migrate
|
/+ default-agent, dbug, verb, grpl=group, *migrate, resource
|
||||||
~% %metadata-hook-top ..part ~
|
~% %metadata-hook-top ..part ~
|
||||||
|%
|
|%
|
||||||
+$ card card:agent:gall
|
+$ card card:agent:gall
|
||||||
+$ versioned-state
|
+$ versioned-state
|
||||||
$% state-zero
|
$% state-zero
|
||||||
state-one
|
state-one
|
||||||
|
state-two
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ state-zero
|
+$ state-zero
|
||||||
@ -23,300 +24,57 @@
|
|||||||
$: %1
|
$: %1
|
||||||
synced=(map group-path ship)
|
synced=(map group-path ship)
|
||||||
==
|
==
|
||||||
|
+$ state-two
|
||||||
|
[%2 ~]
|
||||||
--
|
--
|
||||||
=| state-one
|
=| state-two
|
||||||
=* state -
|
=* state -
|
||||||
%- agent:dbug
|
%- agent:dbug
|
||||||
%+ verb |
|
%+ verb |
|
||||||
^- agent:gall
|
^- agent:gall
|
||||||
=<
|
|
||||||
|_ =bowl:gall
|
|
||||||
+* this .
|
|
||||||
hc ~(. +> bowl)
|
|
||||||
def ~(. (default-agent this %|) bowl)
|
|
||||||
::
|
|
||||||
++ on-init
|
|
||||||
[[%pass /updates %agent [our.bowl %metadata-store] %watch /updates]~ this]
|
|
||||||
::
|
|
||||||
++ on-save !>(state)
|
|
||||||
++ on-load
|
|
||||||
|= =vase
|
|
||||||
=/ old
|
|
||||||
!<(versioned-state vase)
|
|
||||||
?: ?=(%1 -.old)
|
|
||||||
`this(state old)
|
|
||||||
:: groups OTA did not migrate metadata syncs
|
|
||||||
:: we clear our syncs, and wait for metadata-store
|
|
||||||
:: to poke us with the syncs
|
|
||||||
`this
|
|
||||||
::
|
|
||||||
++ on-leave on-leave:def
|
|
||||||
++ on-peek
|
|
||||||
|= =path
|
|
||||||
^- (unit (unit cage))
|
|
||||||
?+ path (on-peek:def path)
|
|
||||||
[%x %synced ~]
|
|
||||||
``noun+!>(~(key by synced))
|
|
||||||
[%x %export ~]
|
|
||||||
``noun+!>(state)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ on-arvo
|
|
||||||
|= [=wire =sign-arvo]
|
|
||||||
^- (quip card _this)
|
|
||||||
?. ?=([%try-rejoin @ @ *] wire)
|
|
||||||
(on-arvo:def wire sign-arvo)
|
|
||||||
=/ nack-count=@ud (slav %ud i.t.wire)
|
|
||||||
=/ who=@p (slav %p i.t.t.wire)
|
|
||||||
=/ pax t.t.t.wire
|
|
||||||
?> ?=([%behn %wake *] sign-arvo)
|
|
||||||
~? ?=(^ error.sign-arvo)
|
|
||||||
"behn errored in backoff timers, continuing anyway"
|
|
||||||
:_ this
|
|
||||||
[(try-rejoin:hc who pax +(nack-count))]~
|
|
||||||
::
|
|
||||||
++ on-fail on-fail:def
|
|
||||||
::
|
|
||||||
++ on-poke
|
|
||||||
|= [=mark =vase]
|
|
||||||
^- (quip card _this)
|
|
||||||
?+ mark (on-poke:def mark vase)
|
|
||||||
%metadata-hook-action
|
|
||||||
=^ cards state
|
|
||||||
(poke-hook-action:hc !<(metadata-hook-action vase))
|
|
||||||
[cards this]
|
|
||||||
::
|
|
||||||
%metadata-action
|
|
||||||
[(poke-action:hc !<(metadata-action vase)) this]
|
|
||||||
::
|
|
||||||
%import
|
|
||||||
?> (team:title our.bowl src.bowl)
|
|
||||||
=^ cards state
|
|
||||||
(poke-import:hc q.vase)
|
|
||||||
[cards this]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ on-watch
|
|
||||||
|= =path
|
|
||||||
^- (quip card _this)
|
|
||||||
?+ path (on-watch:def path)
|
|
||||||
[%group *] [(watch-group:hc t.path) this]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ on-agent
|
|
||||||
|= [=wire =sign:agent:gall]
|
|
||||||
^- (quip card _this)
|
|
||||||
?+ -.sign (on-agent:def wire sign)
|
|
||||||
%kick =^(cards state (kick:hc wire) [cards this])
|
|
||||||
%watch-ack =^(cards state (watch-ack:hc wire p.sign) [cards this])
|
|
||||||
%fact
|
|
||||||
?+ p.cage.sign (on-agent:def wire sign)
|
|
||||||
%metadata-update
|
|
||||||
=^ cards state
|
|
||||||
(fact-metadata-update:hc wire !<(metadata-update q.cage.sign))
|
|
||||||
[cards this]
|
|
||||||
==
|
|
||||||
==
|
|
||||||
--
|
|
||||||
::
|
|
||||||
|_ =bowl:gall
|
|_ =bowl:gall
|
||||||
+* grp ~(. grpl bowl)
|
+* this .
|
||||||
++ poke-hook-action
|
def ~(. (default-agent *agent:gall %|) bowl)
|
||||||
|= act=metadata-hook-action
|
++ on-init on-init:def
|
||||||
^- (quip card _state)
|
++ on-save !>(state)
|
||||||
|
++ on-load
|
||||||
|
|= =vase
|
||||||
|
=+ !<(old=versioned-state vase)
|
||||||
|^
|
|^
|
||||||
?- -.act
|
?: ?=(%2 -.old)
|
||||||
%add-owned
|
`this
|
||||||
?> (team:title our.bowl src.bowl)
|
:_ this
|
||||||
:- ~
|
%+ murn
|
||||||
?: (~(has by synced) path.act) state
|
~(tap by synced.old)
|
||||||
state(synced (~(put by synced) path.act our.bowl))
|
|= [group=path =ship]
|
||||||
|
%+ bind
|
||||||
|
(de-path-soft:resource group)
|
||||||
|
|= rid=resource
|
||||||
|
?: =(our.bowl ship)
|
||||||
|
(push-metadata rid)
|
||||||
|
(pull-metadata rid ship)
|
||||||
::
|
::
|
||||||
%add-synced
|
++ poke-our
|
||||||
?> (team:title our.bowl src.bowl)
|
|= [app=term =cage]
|
||||||
=/ =path [%group path.act]
|
^- card
|
||||||
?: (~(has by synced) path.act) [~ state]
|
[%pass / %agent [our.bowl app] %poke cage]
|
||||||
:_ state(synced (~(put by synced) path.act ship.act))
|
|
||||||
[%pass path %agent [ship.act %metadata-hook] %watch path]~
|
|
||||||
::
|
::
|
||||||
%remove
|
++ push-metadata
|
||||||
=/ ship (~(get by synced) path.act)
|
|= rid=resource
|
||||||
?~ ship [~ state]
|
^- card
|
||||||
?: &(!=(u.ship src.bowl) ?!((team:title our.bowl src.bowl)))
|
(poke-our %metadata-push-hook push-hook-action+!>([%add rid]))
|
||||||
[~ state]
|
|
||||||
:_ state(synced (~(del by synced) path.act))
|
|
||||||
%- zing
|
|
||||||
:~ (unsubscribe [%group path.act] u.ship)
|
|
||||||
[%give %kick ~[[%group path.act]] ~]~
|
|
||||||
==
|
|
||||||
==
|
|
||||||
::
|
::
|
||||||
++ unsubscribe
|
++ pull-metadata
|
||||||
|= [=path =ship]
|
|= [rid=resource =ship]
|
||||||
^- (list card)
|
^- card
|
||||||
?: =(ship our.bowl)
|
(poke-our %metadata-pull-hook pull-hook-action+!>([%add ship rid]))
|
||||||
[%pass path %agent [our.bowl %metadata-store] %leave ~]~
|
|
||||||
[%pass path %agent [ship %metadata-hook] %leave ~]~
|
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
++ poke-action
|
++ on-poke on-poke:def
|
||||||
|= act=metadata-action
|
++ on-watch on-watch:def
|
||||||
^- (list card)
|
++ on-peek on-peek:def
|
||||||
|^
|
++ on-leave on-leave:def
|
||||||
?: (team:title our.bowl src.bowl)
|
++ on-agent on-agent:def
|
||||||
?- -.act
|
++ on-arvo on-arvo:def
|
||||||
%add (send group-path.act)
|
++ on-fail on-fail:def
|
||||||
%remove (send group-path.act)
|
|
||||||
==
|
|
||||||
?> (is-member:grp src.bowl group-path.act)
|
|
||||||
?- -.act
|
|
||||||
%add (metadata-poke our.bowl %metadata-store)
|
|
||||||
%remove (metadata-poke our.bowl %metadata-store)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ send
|
|
||||||
|= =group-path
|
|
||||||
^- (list card)
|
|
||||||
=/ =ship
|
|
||||||
%+ slav %p
|
|
||||||
(snag 1 group-path)
|
|
||||||
=/ app ?:(=(ship our.bowl) %metadata-store %metadata-hook)
|
|
||||||
(metadata-poke ship app)
|
|
||||||
::
|
|
||||||
++ metadata-poke
|
|
||||||
|= [=ship app=@tas]
|
|
||||||
^- (list card)
|
|
||||||
[%pass / %agent [ship app] %poke %metadata-action !>(act)]~
|
|
||||||
::
|
|
||||||
++ is-managed
|
|
||||||
|= =path
|
|
||||||
^- ?
|
|
||||||
?> ?=(^ path)
|
|
||||||
!=(i.path '~')
|
|
||||||
--
|
|
||||||
::
|
|
||||||
++ poke-import
|
|
||||||
|= arc=*
|
|
||||||
^- (quip card _state)
|
|
||||||
=/ sty=state-one
|
|
||||||
[%1 (remake-map ;;((tree [group-path ship]) +.arc))]
|
|
||||||
:_ sty
|
|
||||||
%+ murn ~(tap by synced.sty)
|
|
||||||
|= [=group-path =ship]
|
|
||||||
?: =(ship our.bowl)
|
|
||||||
~
|
|
||||||
=/ =path [%group group-path]
|
|
||||||
`(try-rejoin ship path 0)
|
|
||||||
::
|
|
||||||
++ try-rejoin
|
|
||||||
|= [who=@p pax=path nack-count=@ud]
|
|
||||||
^- card
|
|
||||||
=/ =wire
|
|
||||||
[%try-rejoin (scot %ud nack-count) (scot %p who) pax]
|
|
||||||
[%pass wire %agent [who %metadata-hook] %watch pax]
|
|
||||||
::
|
|
||||||
++ watch-group
|
|
||||||
|= =path
|
|
||||||
^- (list card)
|
|
||||||
|^
|
|
||||||
?> =(our.bowl (~(got by synced) path))
|
|
||||||
?> (is-member:grp src.bowl path)
|
|
||||||
%+ turn ~(tap by (metadata-scry path))
|
|
||||||
|= [[=group-path =md-resource] =metadata]
|
|
||||||
^- card
|
|
||||||
[%give %fact ~ %metadata-update !>([%add group-path md-resource metadata])]
|
|
||||||
::
|
|
||||||
++ metadata-scry
|
|
||||||
|= pax=^path
|
|
||||||
^- associations
|
|
||||||
=. pax
|
|
||||||
;: weld
|
|
||||||
/(scot %p our.bowl)/metadata-store/(scot %da now.bowl)/group
|
|
||||||
pax
|
|
||||||
/noun
|
|
||||||
==
|
|
||||||
.^(associations %gx pax)
|
|
||||||
--
|
|
||||||
::
|
|
||||||
++ fact-metadata-update
|
|
||||||
|= [wir=wire fact=metadata-update]
|
|
||||||
^- (quip card _state)
|
|
||||||
|^
|
|
||||||
[?:((team:title our.bowl src.bowl) handle-local handle-foreign) state]
|
|
||||||
::
|
|
||||||
++ handle-local
|
|
||||||
?+ -.fact ~
|
|
||||||
%add
|
|
||||||
?. (~(has by synced) group-path.fact) ~
|
|
||||||
(give group-path.fact fact)
|
|
||||||
::
|
|
||||||
%update-metadata
|
|
||||||
?. (~(has by synced) group-path.fact) ~
|
|
||||||
(give group-path.fact fact)
|
|
||||||
::
|
|
||||||
%remove
|
|
||||||
?. (~(has by synced) group-path.fact) ~
|
|
||||||
(give group-path.fact fact)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ handle-foreign
|
|
||||||
?+ -.fact ~
|
|
||||||
%add
|
|
||||||
?. =(src.bowl (~(got by synced) group-path.fact)) ~
|
|
||||||
(poke fact)
|
|
||||||
::
|
|
||||||
%update-metadata
|
|
||||||
?. =(src.bowl (~(got by synced) group-path.fact)) ~
|
|
||||||
(poke [%add +.fact])
|
|
||||||
::
|
|
||||||
%remove
|
|
||||||
?. =(src.bowl (~(got by synced) group-path.fact)) ~
|
|
||||||
(poke fact)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ give
|
|
||||||
|= [=path upd=metadata-update]
|
|
||||||
^- (list card)
|
|
||||||
[%give %fact ~[[%group path]] %metadata-update !>(upd)]~
|
|
||||||
::
|
|
||||||
++ poke
|
|
||||||
|= act=metadata-action
|
|
||||||
^- (list card)
|
|
||||||
[%pass / %agent [our.bowl %metadata-store] %poke %metadata-action !>(act)]~
|
|
||||||
--
|
|
||||||
::
|
|
||||||
++ kick
|
|
||||||
|= wir=wire
|
|
||||||
^- (quip card _state)
|
|
||||||
:_ state
|
|
||||||
|-
|
|
||||||
?+ wir !!
|
|
||||||
[%try-rejoin @ @ *]
|
|
||||||
$(wir t.t.t.wir)
|
|
||||||
::
|
|
||||||
[%updates ~]
|
|
||||||
[%pass /updates %agent [our.bowl %metadata-store] %watch /updates]~
|
|
||||||
::
|
|
||||||
[%group @ *]
|
|
||||||
?. (~(has by synced) t.wir) ~
|
|
||||||
=/ =ship (~(got by synced) t.wir)
|
|
||||||
?: =(ship our.bowl)
|
|
||||||
[%pass wir %agent [our.bowl %metadata-store] %watch wir]~
|
|
||||||
[%pass wir %agent [ship %metadata-hook] %watch wir]~
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ watch-ack
|
|
||||||
|= [wir=wire saw=(unit tang)]
|
|
||||||
^- (quip card _state)
|
|
||||||
?: ?=([%try-rejoin @ *] wir)
|
|
||||||
?~ saw
|
|
||||||
[~ state]
|
|
||||||
=/ nack-count=@ud (slav %ud i.t.wir)
|
|
||||||
=/ wakeup=@da
|
|
||||||
(add now.bowl (mul ~s1 (bex (min 19 nack-count))))
|
|
||||||
:_ state
|
|
||||||
[%pass wir %arvo %b %wait wakeup]~
|
|
||||||
?> ?=(^ wir)
|
|
||||||
[~ ?~(saw state state(synced (~(del by synced) t.wir)))]
|
|
||||||
::
|
|
||||||
--
|
--
|
||||||
|
147
pkg/arvo/app/metadata-pull-hook.hoon
Normal file
147
pkg/arvo/app/metadata-pull-hook.hoon
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
:: metadata-pull-hook [landscape]:
|
||||||
|
::
|
||||||
|
:: allow syncing group data from foreign paths to local paths
|
||||||
|
::
|
||||||
|
/- *group, invite-store, metadata=metadata-store
|
||||||
|
/+ default-agent, verb, dbug, store=group-store, grpl=group, pull-hook
|
||||||
|
/+ resource, mdl=metadata
|
||||||
|
~% %group-hook-top ..part ~
|
||||||
|
|%
|
||||||
|
+$ card card:agent:gall
|
||||||
|
::
|
||||||
|
++ config
|
||||||
|
^- config:pull-hook
|
||||||
|
:* %metadata-store
|
||||||
|
update:metadata
|
||||||
|
%metadata-update
|
||||||
|
%metadata-push-hook
|
||||||
|
==
|
||||||
|
+$ state-zero
|
||||||
|
[%0 previews=(map resource group-preview:metadata)]
|
||||||
|
::
|
||||||
|
--
|
||||||
|
::
|
||||||
|
::
|
||||||
|
%- agent:dbug
|
||||||
|
%+ verb |
|
||||||
|
^- agent:gall
|
||||||
|
%- (agent:pull-hook config)
|
||||||
|
^- (pull-hook:pull-hook config)
|
||||||
|
=| state-zero
|
||||||
|
=* state -
|
||||||
|
=> |_ =bowl:gall
|
||||||
|
++ def ~(. (default-agent state %|) bowl)
|
||||||
|
++ get-preview
|
||||||
|
|= rid=resource
|
||||||
|
^- card
|
||||||
|
=/ =path
|
||||||
|
preview+(en-path:resource rid)
|
||||||
|
=/ =dock
|
||||||
|
[entity.rid %metadata-push-hook]
|
||||||
|
=/ =cage
|
||||||
|
metadata-hook-update+!>([%req-preview rid])
|
||||||
|
[%pass path %agent dock %poke cage]
|
||||||
|
::
|
||||||
|
++ watch-invites
|
||||||
|
^- card
|
||||||
|
[%pass /invites %agent [our.bowl %invite-store] %watch /updates]
|
||||||
|
::
|
||||||
|
++ take-invites
|
||||||
|
|= =sign:agent:gall
|
||||||
|
^- (quip card _state)
|
||||||
|
?+ -.sign (on-agent:def /invites sign)
|
||||||
|
%fact
|
||||||
|
?> ?=(%invite-update p.cage.sign)
|
||||||
|
=+ !<(=update:invite-store q.cage.sign)
|
||||||
|
:_ state
|
||||||
|
?. ?=(%invite -.update) ~
|
||||||
|
?. =(%contacts term.update) ~
|
||||||
|
(get-preview resource.invite.update)^~
|
||||||
|
::
|
||||||
|
%kick [watch-invites^~ state]
|
||||||
|
==
|
||||||
|
--
|
||||||
|
|_ =bowl:gall
|
||||||
|
+* this .
|
||||||
|
def ~(. (default-agent this %|) bowl)
|
||||||
|
dep ~(. (default:pull-hook this config) bowl)
|
||||||
|
met ~(. mdl bowl)
|
||||||
|
hc ~(. +> bowl)
|
||||||
|
::
|
||||||
|
++ on-init on-init:def
|
||||||
|
++ on-save !>(state)
|
||||||
|
++ on-load
|
||||||
|
|= =vase
|
||||||
|
=+ !<(old=state-zero vase)
|
||||||
|
:_ this(state old)
|
||||||
|
?: (~(has by wex.bowl) [/invites our.bowl %invite-store]) ~
|
||||||
|
~[watch-invites:hc]
|
||||||
|
::
|
||||||
|
++ on-poke
|
||||||
|
|= [=mark =vase]
|
||||||
|
?. ?=(%metadata-hook-update mark)
|
||||||
|
(on-poke:def mark vase)
|
||||||
|
=+ !<(=hook-update:metadata vase)
|
||||||
|
?. ?=(%preview -.hook-update)
|
||||||
|
(on-poke:def mark vase)
|
||||||
|
:_ this(previews (~(put by previews) group.hook-update +.hook-update))
|
||||||
|
=/ paths=(list path)
|
||||||
|
~[preview+(en-path:resource group.hook-update)]
|
||||||
|
:~ [%give %fact paths mark^vase]
|
||||||
|
[%give %kick paths ~]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ on-agent
|
||||||
|
|= [=wire =sign:agent:gall]
|
||||||
|
=^ cards state
|
||||||
|
?+ wire (on-agent:def:hc wire sign)
|
||||||
|
[%invites ~] (take-invites:hc sign)
|
||||||
|
::
|
||||||
|
[%preview @ @ @ ~]
|
||||||
|
?. ?=(%poke-ack -.sign)
|
||||||
|
(on-agent:def:hc wire sign)
|
||||||
|
:_ state
|
||||||
|
?~ p.sign ~
|
||||||
|
:~ [%give %fact ~[wire] tang+!>(u.p.sign)]
|
||||||
|
[%give %kick ~[wire] ~]
|
||||||
|
==
|
||||||
|
==
|
||||||
|
[cards this]
|
||||||
|
::
|
||||||
|
++ on-watch
|
||||||
|
|= =path
|
||||||
|
?> (team:title [our src]:bowl)
|
||||||
|
?. ?=([%preview @ @ @ ~] path)
|
||||||
|
(on-watch:def path)
|
||||||
|
=/ rid=resource
|
||||||
|
(de-path:resource t.path)
|
||||||
|
=/ prev=(unit group-preview:metadata)
|
||||||
|
(~(get by previews) rid)
|
||||||
|
:_ this
|
||||||
|
?~ prev
|
||||||
|
(get-preview rid)^~
|
||||||
|
[%give %fact ~ metadata-hook-update+!>([%preview u.prev])]~
|
||||||
|
::
|
||||||
|
++ on-leave on-leave:def
|
||||||
|
++ on-peek on-peek:def
|
||||||
|
++ on-arvo on-arvo:def
|
||||||
|
::
|
||||||
|
++ on-fail on-fail:def
|
||||||
|
++ on-pull-nack
|
||||||
|
|= [=resource =tang]
|
||||||
|
^- (quip card _this)
|
||||||
|
=/ =associations:metadata
|
||||||
|
(metadata-for-group:met resource)
|
||||||
|
:_ this
|
||||||
|
%+ turn ~(tap by associations)
|
||||||
|
|= [=md-resource:metadata =association:metadata]
|
||||||
|
=- [%pass / %agent [our.bowl %metadata-store] %poke -]
|
||||||
|
:- %metadata-update
|
||||||
|
!> ^- update:metadata
|
||||||
|
[%remove resource md-resource]
|
||||||
|
::
|
||||||
|
++ on-pull-kick
|
||||||
|
|= =resource
|
||||||
|
^- (unit path)
|
||||||
|
`/
|
||||||
|
--
|
104
pkg/arvo/app/metadata-push-hook.hoon
Normal file
104
pkg/arvo/app/metadata-push-hook.hoon
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
:: metadata-push-hook [landscape]:
|
||||||
|
::
|
||||||
|
/- *group, *invite-store, store=metadata-store
|
||||||
|
/+ default-agent, verb, dbug, grpl=group, push-hook,
|
||||||
|
resource, mdl=metadata, gral=graph
|
||||||
|
~% %group-hook-top ..part ~
|
||||||
|
|%
|
||||||
|
+$ card card:agent:gall
|
||||||
|
::
|
||||||
|
++ config
|
||||||
|
^- config:push-hook
|
||||||
|
:* %metadata-store
|
||||||
|
/all
|
||||||
|
update:store
|
||||||
|
%metadata-update
|
||||||
|
%metadata-pull-hook
|
||||||
|
==
|
||||||
|
::
|
||||||
|
+$ agent (push-hook:push-hook config)
|
||||||
|
--
|
||||||
|
::
|
||||||
|
::
|
||||||
|
%- agent:dbug
|
||||||
|
%+ verb |
|
||||||
|
^- agent:gall
|
||||||
|
%- (agent:push-hook config)
|
||||||
|
^- agent
|
||||||
|
|_ =bowl:gall
|
||||||
|
+* this .
|
||||||
|
def ~(. (default-agent this %|) bowl)
|
||||||
|
grp ~(. grpl bowl)
|
||||||
|
met ~(. mdl bowl)
|
||||||
|
gra ~(. gral bowl)
|
||||||
|
::
|
||||||
|
++ on-init on-init:def
|
||||||
|
++ on-save !>(~)
|
||||||
|
++ on-load on-load:def
|
||||||
|
++ on-poke
|
||||||
|
|= [=mark =vase]
|
||||||
|
?. ?=(%metadata-hook-update mark)
|
||||||
|
(on-poke:def mark vase)
|
||||||
|
=+ !<(=hook-update:store vase)
|
||||||
|
?. ?=(%req-preview -.hook-update)
|
||||||
|
(on-poke:def mark vase)
|
||||||
|
=* rid group.hook-update
|
||||||
|
|^
|
||||||
|
?> =(entity.rid our.bowl)
|
||||||
|
?> (can-join:grp rid src.bowl)
|
||||||
|
=/ members
|
||||||
|
~(wyt in (members:grp rid))
|
||||||
|
=/ =metadatum:store
|
||||||
|
(need (peek-metadatum:met %contacts rid))
|
||||||
|
:_ this
|
||||||
|
=; =cage
|
||||||
|
[%pass / %agent [src.bowl %metadata-pull-hook] %poke cage]~
|
||||||
|
:- %metadata-hook-update
|
||||||
|
!> ^- hook-update:store
|
||||||
|
[%preview rid channels members channel-count metadatum]
|
||||||
|
::
|
||||||
|
++ channels
|
||||||
|
%- ~(gas by *associations:store)
|
||||||
|
%+ skim ~(tap by (app-metadata-for-group:met rid %graph))
|
||||||
|
|=([=md-resource:store group=resource =metadatum:store] preview.metadatum)
|
||||||
|
::
|
||||||
|
++ channel-count
|
||||||
|
~(wyt by (app-metadata-for-group:met rid %graph))
|
||||||
|
--
|
||||||
|
|
||||||
|
++ on-agent on-agent:def
|
||||||
|
++ on-watch on-watch:def
|
||||||
|
++ on-leave on-leave:def
|
||||||
|
++ on-peek on-peek:def
|
||||||
|
++ on-arvo on-arvo:def
|
||||||
|
++ on-fail on-fail:def
|
||||||
|
::
|
||||||
|
++ should-proxy-update
|
||||||
|
|= =vase
|
||||||
|
=+ !<(=update:store vase)
|
||||||
|
?. ?=(?(%add %remove %update) -.update)
|
||||||
|
%.n
|
||||||
|
=/ role=(unit (unit role-tag))
|
||||||
|
(role-for-ship:grp group.update src.bowl)
|
||||||
|
?~ role %.n
|
||||||
|
?~ u.role %.n
|
||||||
|
?=(?(%admin %moderator) u.u.role)
|
||||||
|
::
|
||||||
|
++ take-update
|
||||||
|
|= =vase
|
||||||
|
^- [(list card) agent]
|
||||||
|
`this
|
||||||
|
::
|
||||||
|
++ initial-watch
|
||||||
|
|= [=path rid=resource]
|
||||||
|
^- vase
|
||||||
|
=/ group
|
||||||
|
(scry-group:grp rid)
|
||||||
|
=/ =associations:store
|
||||||
|
(metadata-for-group:met rid)
|
||||||
|
?> ?=(^ group)
|
||||||
|
?> (~(has in members.u.group) src.bowl)
|
||||||
|
!> ^- update:store
|
||||||
|
[%initial-group rid associations]
|
||||||
|
::
|
||||||
|
--
|
@ -3,11 +3,11 @@
|
|||||||
:: data store for application metadata and mappings
|
:: data store for application metadata and mappings
|
||||||
:: between groups and resources within applications
|
:: between groups and resources within applications
|
||||||
::
|
::
|
||||||
:: group-paths are expected to be an existing group path
|
:: paths are expected to be an existing group path
|
||||||
:: resources are expected to correspond to existing app paths
|
:: resources are expected to correspond to existing app paths
|
||||||
::
|
::
|
||||||
:: note: when scrying for metadata, to make the arguments safe in paths,
|
:: note: when scrying for metadata, to make the arguments safe in paths,
|
||||||
:: encode group-path and app-path using (scot %t (spat group-path))
|
:: encode path and path using (scot %t (spat path))
|
||||||
::
|
::
|
||||||
:: +watch paths:
|
:: +watch paths:
|
||||||
:: /all associations + updates
|
:: /all associations + updates
|
||||||
@ -19,22 +19,22 @@
|
|||||||
:: /group-indices all group indices
|
:: /group-indices all group indices
|
||||||
:: /app-indices all app indices
|
:: /app-indices all app indices
|
||||||
:: /resource-indices all resource indices
|
:: /resource-indices all resource indices
|
||||||
:: /metadata/%group-path/%app-name/%app-path specific metadatum
|
:: /metadata/%path/%app-name/%path specific metadatum
|
||||||
:: /app-name/%app-name associations for app
|
:: /app-name/%app-name associations for app
|
||||||
:: /group/%group-path associations for group
|
:: /group/%path associations for group
|
||||||
::
|
::
|
||||||
/- *metadata-store, *metadata-hook
|
/- store=metadata-store
|
||||||
/+ *metadata-json, default-agent, verb, dbug, resource, *migrate
|
/+ *metadata-json, default-agent, verb, dbug, resource, *migrate
|
||||||
|%
|
|%
|
||||||
+$ card card:agent:gall
|
+$ card card:agent:gall
|
||||||
+$ base-state-0
|
+$ base-state-0
|
||||||
$: associations=associations-0
|
$: associations=associations-0
|
||||||
group-indices=(jug group-path md-resource)
|
group-indices=(jug path md-resource:store)
|
||||||
app-indices=(jug app-name [group-path app-path])
|
app-indices=(jug app-name:store [path path])
|
||||||
resource-indices=(jug md-resource group-path)
|
resource-indices=(jug md-resource:store path)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ associations-0 (map [group-path md-resource] metadata-0)
|
+$ associations-0 (map [path md-resource:store] metadata-0)
|
||||||
::
|
::
|
||||||
+$ metadata-0
|
+$ metadata-0
|
||||||
$: title=@t
|
$: title=@t
|
||||||
@ -44,11 +44,35 @@
|
|||||||
creator=@p
|
creator=@p
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
+$ metadata-1
|
||||||
|
$: title=@t
|
||||||
|
description=@t
|
||||||
|
color=@ux
|
||||||
|
date-created=@da
|
||||||
|
creator=@p
|
||||||
|
module=term
|
||||||
|
==
|
||||||
|
::
|
||||||
|
+$ md-resource-1 [=app-name:store =path]
|
||||||
|
::
|
||||||
|
+$ associations-1 (map [path md-resource-1] metadata-1)
|
||||||
|
::
|
||||||
+$ base-state-1
|
+$ base-state-1
|
||||||
$: associations=associations
|
$: associations=associations-1
|
||||||
group-indices=(jug group-path md-resource)
|
group-indices=(jug path md-resource-1)
|
||||||
app-indices=(jug app-name [group-path app-path])
|
app-indices=(jug app-name:store [path path])
|
||||||
resource-indices=(jug md-resource group-path)
|
resource-indices=(jug md-resource-1 path)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
+$ cached-indices
|
||||||
|
$: group-indices=(jug resource md-resource:store)
|
||||||
|
app-indices=(jug app-name:store [group=resource =resource])
|
||||||
|
resource-indices=(map md-resource:store resource)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
+$ base-state-2
|
||||||
|
$: =associations:store
|
||||||
|
~
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ state-0 [%0 base-state-0]
|
+$ state-0 [%0 base-state-0]
|
||||||
@ -58,6 +82,7 @@
|
|||||||
+$ state-4 [%4 base-state-1]
|
+$ state-4 [%4 base-state-1]
|
||||||
+$ state-5 [%5 base-state-1]
|
+$ state-5 [%5 base-state-1]
|
||||||
+$ state-6 [%6 base-state-1]
|
+$ state-6 [%6 base-state-1]
|
||||||
|
+$ state-7 [%7 base-state-2]
|
||||||
+$ versioned-state
|
+$ versioned-state
|
||||||
$% state-0
|
$% state-0
|
||||||
state-1
|
state-1
|
||||||
@ -66,10 +91,16 @@
|
|||||||
state-4
|
state-4
|
||||||
state-5
|
state-5
|
||||||
state-6
|
state-6
|
||||||
|
state-7
|
||||||
|
==
|
||||||
|
::
|
||||||
|
+$ inflated-state
|
||||||
|
$: state-7
|
||||||
|
cached-indices
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
=| state-6
|
=| inflated-state
|
||||||
=* state -
|
=* state -
|
||||||
%+ verb |
|
%+ verb |
|
||||||
%- agent:dbug
|
%- agent:dbug
|
||||||
@ -81,7 +112,7 @@
|
|||||||
def ~(. (default-agent this %|) bowl)
|
def ~(. (default-agent this %|) bowl)
|
||||||
::
|
::
|
||||||
++ on-init on-init:def
|
++ on-init on-init:def
|
||||||
++ on-save !>(state)
|
++ on-save !>(-.state)
|
||||||
++ on-load
|
++ on-load
|
||||||
|= =vase
|
|= =vase
|
||||||
^- (quip card _this)
|
^- (quip card _this)
|
||||||
@ -95,30 +126,13 @@
|
|||||||
?> (team:title our.bowl src.bowl)
|
?> (team:title our.bowl src.bowl)
|
||||||
=^ cards state
|
=^ cards state
|
||||||
?+ mark (on-poke:def mark vase)
|
?+ mark (on-poke:def mark vase)
|
||||||
%metadata-action
|
?(%metadata-action %metadata-update)
|
||||||
(poke-metadata-action:mc !<(metadata-action vase))
|
(poke-metadata-update:mc !<(update:store vase))
|
||||||
::
|
|
||||||
%noun
|
|
||||||
=/ val=(each [%cleanup path] tang)
|
|
||||||
(mule |.(!<([%cleanup path] vase)))
|
|
||||||
?. ?=(%& -.val)
|
|
||||||
(on-poke:def mark vase)
|
|
||||||
=/ group=path +.p.val
|
|
||||||
=/ res=(set md-resource) (~(get ju group-indices) group)
|
|
||||||
=. group-indices (~(del by group-indices) group)
|
|
||||||
:- ~
|
|
||||||
%+ roll ~(tap in res)
|
|
||||||
|= [r=md-resource out=_state]
|
|
||||||
=: resource-indices.out (~(del by resource-indices.out) r)
|
|
||||||
associations.out (~(del by associations.out) group r)
|
|
||||||
app-indices.out
|
|
||||||
%- ~(del ju app-indices.out)
|
|
||||||
[app-name.r group app-path.r]
|
|
||||||
==
|
|
||||||
out
|
|
||||||
::
|
::
|
||||||
%import
|
%import
|
||||||
(poke-import:mc q.vase)
|
(poke-import:mc q.vase)
|
||||||
|
::
|
||||||
|
%noun ~& +.state `state
|
||||||
==
|
==
|
||||||
[cards this]
|
[cards this]
|
||||||
::
|
::
|
||||||
@ -136,7 +150,7 @@
|
|||||||
~
|
~
|
||||||
::
|
::
|
||||||
[%app-name @ ~]
|
[%app-name @ ~]
|
||||||
=/ =app-name i.t.path
|
=/ =app-name:store i.t.path
|
||||||
=/ app-indices (metadata-for-app:mc app-name)
|
=/ app-indices (metadata-for-app:mc app-name)
|
||||||
(give %metadata-update !>([%associations app-indices]))
|
(give %metadata-update !>([%associations app-indices]))
|
||||||
==
|
==
|
||||||
@ -157,25 +171,26 @@
|
|||||||
[%y %resource-indices ~] ``noun+!>(resource-indices)
|
[%y %resource-indices ~] ``noun+!>(resource-indices)
|
||||||
[%x %associations ~] ``noun+!>(associations)
|
[%x %associations ~] ``noun+!>(associations)
|
||||||
[%x %app-name @ ~]
|
[%x %app-name @ ~]
|
||||||
=/ =app-name i.t.t.path
|
=/ =app-name:store i.t.t.path
|
||||||
``noun+!>((metadata-for-app:mc app-name))
|
``noun+!>((metadata-for-app:mc app-name))
|
||||||
::
|
::
|
||||||
[%x %group *]
|
[%x %group *]
|
||||||
=/ =group-path t.t.path
|
=/ group=resource (de-path:resource t.t.path)
|
||||||
``noun+!>((metadata-for-group:mc group-path))
|
``noun+!>((metadata-for-group:mc group))
|
||||||
::
|
::
|
||||||
[%x %metadata @ @ @ ~]
|
[%x %metadata @ @ @ @ ~]
|
||||||
=/ =group-path (stab (slav %t i.t.t.path))
|
=/ =md-resource:store
|
||||||
=/ =md-resource [`term`i.t.t.t.path (stab (slav %t i.t.t.t.t.path))]
|
[i.t.t.path (de-path:resource t.t.t.path)]
|
||||||
``noun+!>((~(get by associations) [group-path md-resource]))
|
``noun+!>((~(get by associations) md-resource))
|
||||||
::
|
::
|
||||||
[%x %resource @ *]
|
[%x %resource @ *]
|
||||||
=/ app=term i.t.t.path
|
=/ app=term i.t.t.path
|
||||||
=/ app-path=^path t.t.t.path
|
=/ rid=resource (de-path:resource t.t.t.path)
|
||||||
``noun+!>((~(get by resource-indices) app app-path))
|
``noun+!>((~(get by resource-indices) [app rid]))
|
||||||
|
|
||||||
::
|
::
|
||||||
[%x %export ~]
|
[%x %export ~]
|
||||||
``noun+!>(state)
|
``noun+!>(-.state)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ on-leave on-leave:def
|
++ on-leave on-leave:def
|
||||||
@ -192,307 +207,239 @@
|
|||||||
=/ old !<(versioned-state vase)
|
=/ old !<(versioned-state vase)
|
||||||
=| cards=(list card)
|
=| cards=(list card)
|
||||||
|^
|
|^
|
||||||
?: ?=(%6 -.old)
|
=* loop $
|
||||||
=/ =^associations
|
?: ?=(%7 -.old)
|
||||||
(migrate-app-to-graph-store %chat associations.old)
|
|
||||||
:- cards
|
:- cards
|
||||||
%_ state
|
%_ state
|
||||||
associations associations
|
associations
|
||||||
::
|
associations.old
|
||||||
resource-indices
|
::
|
||||||
(rebuild-resource-indices associations)
|
resource-indices
|
||||||
|
(rebuild-resource-indices associations.old)
|
||||||
|
::
|
||||||
|
group-indices
|
||||||
|
(rebuild-group-indices associations.old)
|
||||||
::
|
::
|
||||||
app-indices
|
app-indices
|
||||||
(rebuild-app-indices associations)
|
(rebuild-app-indices associations.old)
|
||||||
::
|
|
||||||
group-indices
|
|
||||||
(rebuild-group-indices associations)
|
|
||||||
==
|
==
|
||||||
|
?: ?=(%6 -.old)
|
||||||
|
=/ old-assoc=associations-1
|
||||||
|
(migrate-app-to-graph-store %chat associations.old)
|
||||||
|
$(old [%7 (associations-1-to-2 old-assoc) ~])
|
||||||
|
::
|
||||||
?: ?=(%5 -.old)
|
?: ?=(%5 -.old)
|
||||||
=/ =^associations
|
=/ associations=associations-1
|
||||||
(migrate-app-to-graph-store %publish associations.old)
|
(migrate-app-to-graph-store %publish associations.old)
|
||||||
%_ $
|
%_ $
|
||||||
-.old %6
|
-.old %6
|
||||||
associations.old associations
|
associations.old associations
|
||||||
::
|
|
||||||
resource-indices.old
|
|
||||||
(rebuild-resource-indices associations)
|
|
||||||
::
|
|
||||||
app-indices.old
|
|
||||||
(rebuild-app-indices associations)
|
|
||||||
::
|
|
||||||
group-indices.old
|
|
||||||
(rebuild-group-indices associations)
|
|
||||||
==
|
==
|
||||||
|
:: pre-breach, can safely throw away
|
||||||
?: ?=(%4 -.old)
|
loop(old *state-7)
|
||||||
%_ $
|
::
|
||||||
-.old %5
|
++ associations-1-to-2
|
||||||
::
|
|= assoc=associations-1
|
||||||
resource-indices.old
|
^- associations:store
|
||||||
(rebuild-resource-indices associations.old)
|
%- ~(gas by *associations:store)
|
||||||
::
|
%+ murn
|
||||||
app-indices.old
|
~(tap by assoc)
|
||||||
(rebuild-app-indices associations.old)
|
|= [[group=path m=md-resource-1] met=metadata-1]
|
||||||
::
|
%+ biff (de-path-soft:resource group)
|
||||||
group-indices.old
|
|= g=resource
|
||||||
(rebuild-group-indices associations.old)
|
%+ bind (md-resource-1-to-2 m)
|
||||||
|
|= =md-resource:store
|
||||||
|
[md-resource g (metadata-1-to-2 met)]
|
||||||
|
::
|
||||||
|
++ md-resource-1-to-2
|
||||||
|
|= m=md-resource-1
|
||||||
|
^- (unit md-resource:store)
|
||||||
|
%+ bind (de-path-soft:resource path.m)
|
||||||
|
|=(rid=resource [app-name.m rid])
|
||||||
|
::
|
||||||
|
++ metadata-1-to-2
|
||||||
|
|= m=metadata-1
|
||||||
|
%* . *metadatum:store
|
||||||
|
title title.m
|
||||||
|
description description.m
|
||||||
|
color color.m
|
||||||
|
date-created date-created.m
|
||||||
|
creator creator.m
|
||||||
|
module module.m
|
||||||
==
|
==
|
||||||
?: ?=(%3 -.old)
|
|
||||||
$(old [%4 +.old])
|
|
||||||
?: ?=(%2 -.old)
|
|
||||||
=/ new-state=state-3
|
|
||||||
%* . *state-3
|
|
||||||
associations
|
|
||||||
%- malt
|
|
||||||
%+ murn ~(tap by associations.old)
|
|
||||||
|= [[=group-path =md-resource] m=metadata-0]
|
|
||||||
^- (unit [[^group-path ^md-resource] metadata])
|
|
||||||
?: =(app-name.md-resource %link) ~
|
|
||||||
`[[group-path md-resource] (old-md-to-new m)]
|
|
||||||
==
|
|
||||||
$(old new-state)
|
|
||||||
?: ?=(%1 -.old)
|
|
||||||
%_ $
|
|
||||||
old [%2 +.old]
|
|
||||||
::
|
|
||||||
cards
|
|
||||||
%+ murn ~(tap in ~(key by group-indices.old))
|
|
||||||
|= =group-path
|
|
||||||
^- (unit card)
|
|
||||||
=/ rid (de-path-soft:resource group-path)
|
|
||||||
?~ rid ~
|
|
||||||
?: =(our.bowl entity.u.rid)
|
|
||||||
`(poke-md-hook %add-owned group-path)
|
|
||||||
`(poke-md-hook %add-synced entity.u.rid group-path)
|
|
||||||
==
|
|
||||||
=/ new-state-1=state-1
|
|
||||||
%* . *state-1
|
|
||||||
associations (migrate-associations associations.old)
|
|
||||||
group-indices (migrate-group-indices group-indices.old)
|
|
||||||
app-indices (migrate-app-indices app-indices.old)
|
|
||||||
resource-indices (migrate-resource-indices resource-indices.old)
|
|
||||||
==
|
|
||||||
$(old new-state-1)
|
|
||||||
::
|
::
|
||||||
++ rebuild-resource-indices
|
++ rebuild-resource-indices
|
||||||
|= =^associations
|
|= =associations:store
|
||||||
%- ~(gas ju *(jug md-resource group-path))
|
%- ~(gas by *(map md-resource:store resource))
|
||||||
%+ turn ~(tap in ~(key by associations))
|
%+ turn ~(tap by associations)
|
||||||
|= [g=group-path r=md-resource]
|
|= [r=md-resource:store g=resource =metadatum:store]
|
||||||
^- [md-resource group-path]
|
|
||||||
[r g]
|
[r g]
|
||||||
::
|
::
|
||||||
++ rebuild-group-indices
|
++ rebuild-group-indices
|
||||||
|= =^associations
|
|= =associations:store
|
||||||
%- ~(gas ju *(jug group-path md-resource))
|
%- ~(gas ju *(jug resource md-resource:store))
|
||||||
~(tap in ~(key by associations))
|
%+ turn
|
||||||
|
~(tap by associations)
|
||||||
|
|= [r=md-resource:store g=resource =metadatum:store]
|
||||||
|
[g r]
|
||||||
::
|
::
|
||||||
++ rebuild-app-indices
|
++ rebuild-app-indices
|
||||||
|= =^associations
|
|= =associations:store
|
||||||
%- ~(gas ju *(jug app-name [group-path app-path]))
|
%- ~(gas ju *(jug app-name:store [group=resource resource]))
|
||||||
%+ turn ~(tap in ~(key by associations))
|
%+ turn ~(tap by associations)
|
||||||
|= [g=group-path r=md-resource]
|
|= [r=md-resource:store g=resource =metadatum:store]
|
||||||
^- [app-name [group-path app-path]]
|
[app-name.r g resource.r]
|
||||||
[app-name.r [g app-path.r]]
|
|
||||||
|
|
||||||
::
|
::
|
||||||
++ migrate-app-to-graph-store
|
++ migrate-app-to-graph-store
|
||||||
|= [app=@tas =^associations]
|
|= [app=@tas associations=associations-1]
|
||||||
^+ associations
|
^- associations-1
|
||||||
%- malt
|
%- malt
|
||||||
%+ turn ~(tap by associations)
|
%+ turn ~(tap by associations)
|
||||||
|= [[=group-path =md-resource] m=metadata]
|
|= [[=path md-resource=md-resource-1] m=metadata-1]
|
||||||
^- [[^group-path ^md-resource] metadata]
|
^- [[^path md-resource-1] metadata-1]
|
||||||
?. =(app-name.md-resource app)
|
?. =(app-name.md-resource app)
|
||||||
[[group-path md-resource] m]
|
[[path md-resource] m]
|
||||||
=/ new-app-path=path
|
=/ new-path=^path
|
||||||
?. ?=([@ @ ~] app-path.md-resource)
|
?. ?=([@ @ ~] path.md-resource)
|
||||||
app-path.md-resource
|
path.md-resource
|
||||||
ship+app-path.md-resource
|
ship+path.md-resource
|
||||||
[[group-path [%graph new-app-path]] m(module app)]
|
[[path [%graph new-path]] m(module app)]
|
||||||
::
|
|
||||||
++ poke-md-hook
|
|
||||||
|= act=metadata-hook-action
|
|
||||||
^- card
|
|
||||||
=/ =cage metadata-hook-action+!>(act)
|
|
||||||
[%pass / %agent [our.bowl %metadata-hook] %poke cage]
|
|
||||||
::
|
|
||||||
++ new-group-path
|
|
||||||
|= =group-path
|
|
||||||
ship+(new-app-path group-path)
|
|
||||||
::
|
|
||||||
++ new-app-path
|
|
||||||
|= =app-path
|
|
||||||
^- path
|
|
||||||
?> ?=(^ app-path)
|
|
||||||
?:(=('~' i.app-path) t.app-path app-path)
|
|
||||||
::
|
|
||||||
++ old-md-to-new
|
|
||||||
|= m=metadata-0
|
|
||||||
^- metadata
|
|
||||||
%* . *metadata
|
|
||||||
title title.m
|
|
||||||
description description.m
|
|
||||||
color color.m
|
|
||||||
date-created date-created.m
|
|
||||||
creator creator.m
|
|
||||||
module *term
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ migrate-md-resource
|
|
||||||
|= md-resource
|
|
||||||
^- md-resource
|
|
||||||
?: =(%chat app-name) [%chat (new-app-path app-path)]
|
|
||||||
?: =(%contacts app-name) [%contacts ship+app-path]
|
|
||||||
[app-name app-path]
|
|
||||||
::
|
|
||||||
++ migrate-resource-indices
|
|
||||||
|= resource-indices=(jug md-resource group-path)
|
|
||||||
^- (jug md-resource group-path)
|
|
||||||
%- malt
|
|
||||||
%+ turn ~(tap by resource-indices)
|
|
||||||
|= [=md-resource paths=(set group-path)]
|
|
||||||
:- (migrate-md-resource md-resource)
|
|
||||||
(~(run in paths) new-group-path)
|
|
||||||
::
|
|
||||||
++ migrate-app-indices
|
|
||||||
|= app-indices=(jug app-name [group-path app-path])
|
|
||||||
%- malt
|
|
||||||
%+ turn ~(tap by app-indices)
|
|
||||||
|= [app=term indices=(set [=group-path =app-path])]
|
|
||||||
:- app
|
|
||||||
%- ~(run in indices)
|
|
||||||
|= [=group-path =app-path]
|
|
||||||
:- (new-group-path group-path)
|
|
||||||
?: =(%chat app) (new-app-path app-path)
|
|
||||||
?: =(%contacts app) ship+app-path
|
|
||||||
app-path
|
|
||||||
::
|
|
||||||
++ migrate-group-indices
|
|
||||||
|= group-indices=(jug group-path md-resource)
|
|
||||||
%- malt
|
|
||||||
%+ turn ~(tap by group-indices)
|
|
||||||
|= [=group-path resources=(set md-resource)]
|
|
||||||
:- (new-group-path group-path)
|
|
||||||
%- sy
|
|
||||||
%+ turn ~(tap in resources)
|
|
||||||
migrate-md-resource
|
|
||||||
::
|
|
||||||
++ migrate-associations
|
|
||||||
|= associations=associations-0
|
|
||||||
%- malt
|
|
||||||
%+ turn ~(tap by associations)
|
|
||||||
|= [[g=group-path r=md-resource] m=metadata-0]
|
|
||||||
:_ m
|
|
||||||
[(new-group-path g) (migrate-md-resource r)]
|
|
||||||
--
|
--
|
||||||
++ poke-metadata-action
|
++ poke-metadata-update
|
||||||
|= act=metadata-action
|
|= upd=update:store
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
?> (team:title our.bowl src.bowl)
|
?> (team:title [our src]:bowl)
|
||||||
?- -.act
|
?+ -.upd !!
|
||||||
%add (handle-add group-path.act resource.act metadata.act)
|
%add (handle-add +.upd)
|
||||||
%remove (handle-remove group-path.act resource.act)
|
%remove (handle-remove +.upd)
|
||||||
|
%initial-group (handle-initial-group +.upd)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ poke-import
|
++ poke-import
|
||||||
|= arc=*
|
|= arc=*
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
|^
|
|^
|
||||||
(on-load !>([%5 (remake-metadata ;;(tree-metadata +.arc))]))
|
=^ cards state
|
||||||
|
(on-load !>([%7 (remake-metadata ;;(tree-metadata +.arc))]))
|
||||||
|
:_ state
|
||||||
|
%+ weld cards
|
||||||
|
%+ turn ~(tap in ~(key by group-indices))
|
||||||
|
|= rid=resource
|
||||||
|
%- poke-our
|
||||||
|
?: =(entity.rid our.bowl)
|
||||||
|
:- %metadata-push-hook
|
||||||
|
push-hook-action+!>([%add rid])
|
||||||
|
:- %metadata-pull-hook
|
||||||
|
pull-hook-action+!>([%add [entity .]:rid])
|
||||||
|
::
|
||||||
|
++ poke-our
|
||||||
|
|= [app=term =cage]
|
||||||
|
^- card
|
||||||
|
[%pass / %agent [our.bowl app] %poke cage]
|
||||||
::
|
::
|
||||||
+$ tree-metadata
|
+$ tree-metadata
|
||||||
$: associations=(tree [[group-path md-resource] metadata])
|
$: associations=(tree [md-resource:store [resource metadatum:store]])
|
||||||
group-indices=(tree [group-path (tree md-resource)])
|
~
|
||||||
app-indices=(tree [app-name (tree [group-path app-path])])
|
|
||||||
resource-indices=(tree [md-resource (tree group-path)])
|
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ remake-metadata
|
++ remake-metadata
|
||||||
|= tm=tree-metadata
|
|= tm=tree-metadata
|
||||||
^- base-state-1
|
^- base-state-2
|
||||||
:* (remake-map associations.tm)
|
:* (remake-map associations.tm)
|
||||||
(remake-jug group-indices.tm)
|
~
|
||||||
(remake-jug app-indices.tm)
|
|
||||||
(remake-jug resource-indices.tm)
|
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
++ handle-add
|
++ handle-add
|
||||||
|= [=group-path =md-resource =metadata]
|
|= [group=resource =md-resource:store =metadatum:store]
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
:- %+ send-diff app-name.md-resource
|
:- %+ send-diff app-name.md-resource
|
||||||
?: (~(has by resource-indices) md-resource)
|
[%add group md-resource metadatum]
|
||||||
[%update-metadata group-path md-resource metadata]
|
|
||||||
[%add group-path md-resource metadata]
|
|
||||||
%= state
|
%= state
|
||||||
associations
|
associations
|
||||||
(~(put by associations) [group-path md-resource] metadata)
|
(~(put by associations) md-resource [group metadatum])
|
||||||
::
|
|
||||||
group-indices
|
|
||||||
(~(put ju group-indices) group-path md-resource)
|
|
||||||
::
|
::
|
||||||
app-indices
|
app-indices
|
||||||
%+ ~(put ju app-indices)
|
%+ ~(put ju app-indices)
|
||||||
app-name.md-resource
|
app-name.md-resource
|
||||||
[group-path app-path.md-resource]
|
[group resource.md-resource]
|
||||||
::
|
::
|
||||||
resource-indices
|
resource-indices
|
||||||
(~(put ju resource-indices) md-resource group-path)
|
(~(put by resource-indices) md-resource group)
|
||||||
|
::
|
||||||
|
group-indices
|
||||||
|
(~(put ju group-indices) group md-resource)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ handle-remove
|
++ handle-remove
|
||||||
|= [=group-path =md-resource]
|
|= [group=resource =md-resource:store]
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
:- (send-diff app-name.md-resource [%remove group-path md-resource])
|
:- (send-diff app-name.md-resource [%remove group md-resource])
|
||||||
%= state
|
%= state
|
||||||
associations
|
associations
|
||||||
(~(del by associations) [group-path md-resource])
|
(~(del by associations) md-resource)
|
||||||
::
|
|
||||||
group-indices
|
|
||||||
(~(del ju group-indices) group-path md-resource)
|
|
||||||
::
|
::
|
||||||
app-indices
|
app-indices
|
||||||
%+ ~(del ju app-indices)
|
%+ ~(del ju app-indices)
|
||||||
app-name.md-resource
|
app-name.md-resource
|
||||||
[group-path app-path.md-resource]
|
[group resource.md-resource]
|
||||||
::
|
::
|
||||||
resource-indices
|
resource-indices
|
||||||
(~(del ju resource-indices) md-resource group-path)
|
(~(del by resource-indices) md-resource)
|
||||||
|
::
|
||||||
|
group-indices
|
||||||
|
(~(del ju group-indices) group md-resource)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
++ handle-initial-group
|
||||||
|
|= [group=resource =associations:store]
|
||||||
|
=/ assocs=(list [=md-resource:store grp=resource =metadatum:store])
|
||||||
|
~(tap by associations)
|
||||||
|
=| cards=(list card)
|
||||||
|
|-
|
||||||
|
?~ assocs
|
||||||
|
[cards state]
|
||||||
|
=, assocs
|
||||||
|
?> =(group grp.i)
|
||||||
|
=^ new-cards state
|
||||||
|
(handle-add group [md-resource metadatum]:i)
|
||||||
|
$(cards (weld cards new-cards), assocs t)
|
||||||
|
::
|
||||||
++ metadata-for-app
|
++ metadata-for-app
|
||||||
|= =app-name
|
|= =app-name:store
|
||||||
^- ^associations
|
^+ associations
|
||||||
%- ~(gas by *^associations)
|
%+ roll ~(tap in (~(gut by app-indices) app-name ~))
|
||||||
%+ turn ~(tap in (~(gut by app-indices) app-name ~))
|
|= [[group=resource rid=resource] out=associations:store]
|
||||||
|= [=group-path =app-path]
|
=/ =md-resource:store
|
||||||
:- [group-path [app-name app-path]]
|
[app-name rid]
|
||||||
(~(got by associations) [group-path [app-name app-path]])
|
=/ [resource =metadatum:store]
|
||||||
|
(~(got by associations) md-resource)
|
||||||
|
(~(put by out) md-resource [group metadatum])
|
||||||
::
|
::
|
||||||
++ metadata-for-group
|
++ metadata-for-group
|
||||||
|= =group-path
|
|= group=resource
|
||||||
^- ^associations
|
=/ resources=(set md-resource:store)
|
||||||
%- ~(gas by *^associations)
|
(~(get ju group-indices) group)
|
||||||
%+ turn ~(tap in (~(gut by group-indices) group-path ~))
|
%+ roll
|
||||||
|= =md-resource
|
~(tap in resources)
|
||||||
:- [group-path md-resource]
|
|= [=md-resource:store out=associations:store]
|
||||||
(~(got by associations) [group-path md-resource])
|
=/ [resource =metadatum:store]
|
||||||
|
(~(got by associations) md-resource)
|
||||||
|
(~(put by out) md-resource [group metadatum])
|
||||||
::
|
::
|
||||||
++ send-diff
|
++ send-diff
|
||||||
|= [=app-name upd=metadata-update]
|
|= [=app-name:store =update:store]
|
||||||
^- (list card)
|
^- (list card)
|
||||||
|^
|
|^
|
||||||
%- zing
|
%- zing
|
||||||
:~ (update-subscribers /all upd)
|
:~ (update-subscribers /all update)
|
||||||
(update-subscribers /updates upd)
|
(update-subscribers /updates update)
|
||||||
(update-subscribers [%app-name app-name ~] upd)
|
(update-subscribers [%app-name app-name ~] update)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ update-subscribers
|
++ update-subscribers
|
||||||
|= [pax=path upd=metadata-update]
|
|= [pax=path =update:store]
|
||||||
^- (list card)
|
^- (list card)
|
||||||
[%give %fact ~[pax] %metadata-update !>(upd)]~
|
[%give %fact ~[pax] %metadata-update !>(update)]~
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
|
@ -33,9 +33,8 @@
|
|||||||
+$ issue
|
+$ issue
|
||||||
$% [%lib-pull-hook-desync app=term =resource]
|
$% [%lib-pull-hook-desync app=term =resource]
|
||||||
[%lib-push-hook-desync app=term =resource]
|
[%lib-push-hook-desync app=term =resource]
|
||||||
[%md-hook-desync =path]
|
|
||||||
[%contact-hook-desync =path]
|
[%contact-hook-desync =path]
|
||||||
[%dangling-md =path]
|
[%dangling-md =resource]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ issues
|
+$ issues
|
||||||
@ -125,6 +124,7 @@
|
|||||||
++ check-all
|
++ check-all
|
||||||
=> (lib-hooks-desync %group scry-groups)
|
=> (lib-hooks-desync %group scry-groups)
|
||||||
=> (lib-hooks-desync %graph get-keys:gra)
|
=> (lib-hooks-desync %graph get-keys:gra)
|
||||||
|
=> (lib-hooks-desync %metadata scry-groups)
|
||||||
=> groups
|
=> groups
|
||||||
metadata
|
metadata
|
||||||
::
|
::
|
||||||
@ -136,22 +136,18 @@
|
|||||||
?~ groups
|
?~ groups
|
||||||
fk-core
|
fk-core
|
||||||
=* group i.groups
|
=* group i.groups
|
||||||
=? fk-core !(~(has in scry-md-syncs) group)
|
|
||||||
(report %md-hook-desync (en-path:resource group))
|
|
||||||
=? fk-core &((is-managed:grp group) !(~(has in scry-contact-syncs) group))
|
=? fk-core &((is-managed:grp group) !(~(has in scry-contact-syncs) group))
|
||||||
(report %contact-hook-desync (en-path:resource group))
|
(report %contact-hook-desync (en-path:resource group))
|
||||||
$(groups t.groups)
|
$(groups t.groups)
|
||||||
::
|
::
|
||||||
++ metadata
|
++ metadata
|
||||||
^+ fk-core
|
^+ fk-core
|
||||||
=/ md-groups=(list path)
|
=/ md-groups=(list resource)
|
||||||
~(tap in ~(key by md-group-indices))
|
~(tap in ~(key by md-group-indices))
|
||||||
|-
|
|-
|
||||||
?~ md-groups
|
?~ md-groups
|
||||||
fk-core
|
fk-core
|
||||||
=/ rid=resource
|
=? fk-core !(~(has in scry-groups) i.md-groups)
|
||||||
(de-path:resource i.md-groups)
|
|
||||||
=? fk-core !(~(has in scry-groups) rid)
|
|
||||||
(report %dangling-md i.md-groups)
|
(report %dangling-md i.md-groups)
|
||||||
$(md-groups t.md-groups)
|
$(md-groups t.md-groups)
|
||||||
::
|
::
|
||||||
@ -212,15 +208,6 @@
|
|||||||
::
|
::
|
||||||
%lib-push-hook-desync
|
%lib-push-hook-desync
|
||||||
(poke-our app.issue push-hook-action+!>([%add resource.issue]))^~
|
(poke-our app.issue push-hook-action+!>([%add resource.issue]))^~
|
||||||
::
|
|
||||||
%md-hook-desync
|
|
||||||
=/ rid=resource
|
|
||||||
(de-path:resource path.issue)
|
|
||||||
=/ act
|
|
||||||
?: =(entity.rid our.bowl)
|
|
||||||
[%add-owned path.issue]
|
|
||||||
[%add-synced entity.rid path.issue]
|
|
||||||
(poke-our %metadata-hook metadata-hook-action+!>(act))^~
|
|
||||||
::
|
::
|
||||||
%contact-hook-desync
|
%contact-hook-desync
|
||||||
=/ rid=resource
|
=/ rid=resource
|
||||||
@ -233,12 +220,12 @@
|
|||||||
::
|
::
|
||||||
%dangling-md
|
%dangling-md
|
||||||
=/ app-indices
|
=/ app-indices
|
||||||
(~(get ju md-group-indices) path.issue)
|
(~(get ju md-group-indices) resource.issue)
|
||||||
%+ turn
|
%+ turn
|
||||||
~(tap in app-indices)
|
~(tap in app-indices)
|
||||||
|= =md-resource
|
|= =md-resource
|
||||||
^- card
|
^- card
|
||||||
(poke-our %metadata-store metadata-action+!>([%remove path.issue md-resource]))
|
(poke-our %metadata-store metadata-action+!>([%remove resource.issue md-resource]))
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ poke-our
|
++ poke-our
|
||||||
@ -265,13 +252,6 @@
|
|||||||
,(set resource)
|
,(set resource)
|
||||||
/x/[hook]/sharing/noun
|
/x/[hook]/sharing/noun
|
||||||
::
|
::
|
||||||
++ scry-md-syncs
|
|
||||||
^- (set resource)
|
|
||||||
=- (~(run in -) de-path:resource)
|
|
||||||
%+ scry
|
|
||||||
,(set path)
|
|
||||||
/x/metadata-hook/synced/noun
|
|
||||||
::
|
|
||||||
++ scry-contact-syncs
|
++ scry-contact-syncs
|
||||||
^- (set resource)
|
^- (set resource)
|
||||||
=- (~(run in -) de-path:resource)
|
=- (~(run in -) de-path:resource)
|
||||||
@ -291,8 +271,9 @@
|
|||||||
,(set path)
|
,(set path)
|
||||||
/x/chat-store/keys/noun
|
/x/chat-store/keys/noun
|
||||||
::
|
::
|
||||||
|
::
|
||||||
++ md-group-indices
|
++ md-group-indices
|
||||||
(scry (jug group-path md-resource) /y/metadata-store/group-indices)
|
(scry (jug resource md-resource) /y/metadata-store/group-indices)
|
||||||
::
|
::
|
||||||
++ scry
|
++ scry
|
||||||
|* [=mold =path]
|
|* [=mold =path]
|
||||||
|
167
pkg/arvo/app/settings-store.hoon
Normal file
167
pkg/arvo/app/settings-store.hoon
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/- *settings
|
||||||
|
/+ verb, dbug, default-agent
|
||||||
|
|%
|
||||||
|
+$ card card:agent:gall
|
||||||
|
+$ versioned-state
|
||||||
|
$% state-0
|
||||||
|
==
|
||||||
|
+$ state-0
|
||||||
|
$: %0
|
||||||
|
=settings
|
||||||
|
==
|
||||||
|
--
|
||||||
|
=| state-0
|
||||||
|
=* state -
|
||||||
|
::
|
||||||
|
%- agent:dbug
|
||||||
|
%+ verb |
|
||||||
|
^- agent:gall
|
||||||
|
=<
|
||||||
|
|_ bol=bowl:gall
|
||||||
|
+* this .
|
||||||
|
do ~(. +> bol)
|
||||||
|
def ~(. (default-agent this %|) bol)
|
||||||
|
::
|
||||||
|
++ on-init on-init:def
|
||||||
|
::
|
||||||
|
++ on-save !>(state)
|
||||||
|
::
|
||||||
|
++ on-load
|
||||||
|
|= =old=vase
|
||||||
|
^- (quip card _this)
|
||||||
|
=/ old !<(versioned-state old-vase)
|
||||||
|
?- -.old
|
||||||
|
%0 [~ this(state old)]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ on-poke
|
||||||
|
|= [mar=mark vas=vase]
|
||||||
|
^- (quip card _this)
|
||||||
|
?> (team:title our.bol src.bol)
|
||||||
|
?. ?=(%settings-event mar)
|
||||||
|
(on-poke:def mar vas)
|
||||||
|
=/ evt=event !<(event vas)
|
||||||
|
=^ cards state
|
||||||
|
?- -.evt
|
||||||
|
%put-bucket (put-bucket:do key.evt bucket.evt)
|
||||||
|
%del-bucket (del-bucket:do key.evt)
|
||||||
|
%put-entry (put-entry:do buc.evt key.evt val.evt)
|
||||||
|
%del-entry (del-entry:do buc.evt key.evt)
|
||||||
|
==
|
||||||
|
[cards this]
|
||||||
|
::
|
||||||
|
++ on-watch
|
||||||
|
|= pax=path
|
||||||
|
^- (quip card _this)
|
||||||
|
?> (team:title our.bol src.bol)
|
||||||
|
?+ pax (on-watch:def pax)
|
||||||
|
[%all ~]
|
||||||
|
[~ this]
|
||||||
|
::
|
||||||
|
[%bucket @ ~]
|
||||||
|
=* bucket-key i.t.pax
|
||||||
|
?> (~(has by settings) bucket-key)
|
||||||
|
[~ this]
|
||||||
|
::
|
||||||
|
[%entry @ @ ~]
|
||||||
|
=* bucket-key i.t.pax
|
||||||
|
=* entry-key i.t.t.pax
|
||||||
|
=/ bucket (~(got by settings) bucket-key)
|
||||||
|
?> (~(has by bucket) entry-key)
|
||||||
|
[~ this]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ on-peek
|
||||||
|
|= pax=path
|
||||||
|
^- (unit (unit cage))
|
||||||
|
?+ pax (on-peek:def pax)
|
||||||
|
[%x %all ~]
|
||||||
|
``settings-data+!>(all+settings)
|
||||||
|
::
|
||||||
|
[%x %bucket @ ~]
|
||||||
|
=* buc i.t.t.pax
|
||||||
|
=/ bucket=(unit bucket) (~(get by settings) buc)
|
||||||
|
?~ bucket [~ ~]
|
||||||
|
``settings-data+!>(bucket+u.bucket)
|
||||||
|
::
|
||||||
|
[%x %entry @ @ ~]
|
||||||
|
=* buc i.t.t.pax
|
||||||
|
=* key i.t.t.t.pax
|
||||||
|
=/ =bucket (fall (~(get by settings) buc) ~)
|
||||||
|
=/ entry=(unit val) (~(get by bucket) key)
|
||||||
|
?~ entry [~ ~]
|
||||||
|
``settings-data+!>(entry+u.entry)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ on-agent on-agent:def
|
||||||
|
++ on-leave on-leave:def
|
||||||
|
++ on-arvo on-arvo:def
|
||||||
|
++ on-fail on-fail:def
|
||||||
|
--
|
||||||
|
::
|
||||||
|
|_ bol=bowl:gall
|
||||||
|
::
|
||||||
|
:: +put-bucket: put a bucket in the top level settings map, overwriting if it
|
||||||
|
:: already exists
|
||||||
|
::
|
||||||
|
++ put-bucket
|
||||||
|
|= [=key =bucket]
|
||||||
|
^- (quip card _state)
|
||||||
|
=/ pas=(list path)
|
||||||
|
:~ /all
|
||||||
|
/bucket/[key]
|
||||||
|
==
|
||||||
|
:- [(give-event pas %put-bucket key bucket)]~
|
||||||
|
state(settings (~(put by settings) key bucket))
|
||||||
|
::
|
||||||
|
:: +del-bucket: delete a bucket from the top level settings map
|
||||||
|
::
|
||||||
|
++ del-bucket
|
||||||
|
|= =key
|
||||||
|
^- (quip card _state)
|
||||||
|
=/ pas=(list path)
|
||||||
|
:~ /all
|
||||||
|
/bucket/[key]
|
||||||
|
==
|
||||||
|
:- [(give-event pas %del-bucket key)]~
|
||||||
|
state(settings (~(del by settings) key))
|
||||||
|
::
|
||||||
|
:: +put-entry: put an entry in a bucket, overwriting if it already exists
|
||||||
|
:: if bucket does not yet exist, create it
|
||||||
|
::
|
||||||
|
++ put-entry
|
||||||
|
|= [buc=key =key =val]
|
||||||
|
^- (quip card _state)
|
||||||
|
=/ pas=(list path)
|
||||||
|
:~ /all
|
||||||
|
/bucket/[buc]
|
||||||
|
/entry/[buc]/[key]
|
||||||
|
==
|
||||||
|
=/ =bucket (fall (~(get by settings) buc) ~)
|
||||||
|
=. bucket (~(put by bucket) key val)
|
||||||
|
:- [(give-event pas %put-entry buc key val)]~
|
||||||
|
state(settings (~(put by settings) buc bucket))
|
||||||
|
::
|
||||||
|
:: +del-entry: delete an entry from a bucket, fail quietly if bucket does not
|
||||||
|
:: exist
|
||||||
|
::
|
||||||
|
++ del-entry
|
||||||
|
|= [buc=key =key]
|
||||||
|
^- (quip card _state)
|
||||||
|
=/ pas=(list path)
|
||||||
|
:~ /all
|
||||||
|
/bucket/[buc]
|
||||||
|
/entry/[buc]/[key]
|
||||||
|
==
|
||||||
|
=/ bucket=(unit bucket) (~(get by settings) buc)
|
||||||
|
?~ bucket
|
||||||
|
[~ state]
|
||||||
|
=. u.bucket (~(del by u.bucket) key)
|
||||||
|
:- [(give-event pas %del-entry buc key)]~
|
||||||
|
state(settings (~(put by settings) buc u.bucket))
|
||||||
|
::
|
||||||
|
++ give-event
|
||||||
|
|= [pas=(list path) evt=event]
|
||||||
|
^- card
|
||||||
|
[%give %fact pas %settings-event !>(evt)]
|
||||||
|
--
|
@ -1,16 +1,18 @@
|
|||||||
/- *group, *metadata-store
|
/- *group
|
||||||
/+ store=group-store, resource
|
/+ store=group-store, resource
|
||||||
::
|
::
|
||||||
|_ =bowl:gall
|
|_ =bowl:gall
|
||||||
+$ card card:agent:gall
|
+$ card card:agent:gall
|
||||||
++ scry-for
|
++ scry-for
|
||||||
|* [=mold =path]
|
|* [=mold =path]
|
||||||
|
=. path
|
||||||
|
(snoc path %noun)
|
||||||
.^ mold
|
.^ mold
|
||||||
%gx
|
%gx
|
||||||
(scot %p our.bowl)
|
(scot %p our.bowl)
|
||||||
%group-store
|
%group-store
|
||||||
(scot %da now.bowl)
|
(scot %da now.bowl)
|
||||||
(snoc `^path`path %noun)
|
path
|
||||||
==
|
==
|
||||||
++ scry-tag
|
++ scry-tag
|
||||||
|= [rid=resource =tag]
|
|= [rid=resource =tag]
|
||||||
@ -21,38 +23,27 @@
|
|||||||
~
|
~
|
||||||
`(~(gut by tags.u.group) tag ~)
|
`(~(gut by tags.u.group) tag ~)
|
||||||
::
|
::
|
||||||
++ scry-group-path
|
|
||||||
|= =path
|
|
||||||
%+ scry-for
|
|
||||||
(unit group)
|
|
||||||
[%groups path]
|
|
||||||
::
|
|
||||||
++ scry-group
|
++ scry-group
|
||||||
|= rid=resource
|
|= rid=resource
|
||||||
%- scry-group-path
|
%+ scry-for ,(unit group)
|
||||||
(en-path:resource rid)
|
`path`groups+(en-path:resource rid)
|
||||||
::
|
::
|
||||||
++ members
|
++ members
|
||||||
|= rid=resource
|
|= rid=resource
|
||||||
%- members-from-path
|
=; =group
|
||||||
(en-path:resource rid)
|
members.group
|
||||||
::
|
(fall (scry-group rid) *group)
|
||||||
++ members-from-path
|
|
||||||
|= =group-path
|
|
||||||
^- (set ship)
|
|
||||||
=- members:(fall - *group)
|
|
||||||
(scry-group-path group-path)
|
|
||||||
::
|
::
|
||||||
++ is-member
|
++ is-member
|
||||||
|= [=ship =group-path]
|
|= [=ship group=resource]
|
||||||
^- ?
|
^- ?
|
||||||
=- (~(has in -) ship)
|
=- (~(has in -) ship)
|
||||||
(members-from-path group-path)
|
(members group)
|
||||||
::
|
::
|
||||||
++ is-admin
|
++ is-admin
|
||||||
|= [=ship =group-path]
|
|= [=ship group=resource]
|
||||||
^- ?
|
^- ?
|
||||||
=/ tags tags:(fall (scry-group-path group-path) *group)
|
=/ tags tags:(fall (scry-group group) *^group)
|
||||||
=/ admins=(set ^ship) (~(gut by tags) %admin ~)
|
=/ admins=(set ^ship) (~(gut by tags) %admin ~)
|
||||||
(~(has in admins) ship)
|
(~(has in admins) ship)
|
||||||
:: +role-for-ship: get role for user
|
:: +role-for-ship: get role for user
|
||||||
@ -85,31 +76,18 @@
|
|||||||
[~ ~]
|
[~ ~]
|
||||||
~
|
~
|
||||||
::
|
::
|
||||||
++ can-join-from-path
|
|
||||||
|= [=path =ship]
|
|
||||||
%+ scry-for
|
|
||||||
?
|
|
||||||
%+ welp
|
|
||||||
[%groups path]
|
|
||||||
/join/[(scot %p ship)]
|
|
||||||
::
|
|
||||||
++ can-join
|
++ can-join
|
||||||
|= [rid=resource =ship]
|
|= [rid=resource =ship]
|
||||||
%+ can-join-from-path
|
%+ scry-for ,?
|
||||||
(en-path:resource rid)
|
^- path
|
||||||
ship
|
:- %groups
|
||||||
::
|
(weld (en-path:resource rid) /join/(scot %p ship))
|
||||||
++ is-managed-path
|
|
||||||
|= =path
|
|
||||||
^- ?
|
|
||||||
=/ group=(unit group)
|
|
||||||
(scry-group-path path)
|
|
||||||
?~ group %.n
|
|
||||||
!hidden.u.group
|
|
||||||
::
|
::
|
||||||
++ is-managed
|
++ is-managed
|
||||||
|= rid=resource
|
|= rid=resource
|
||||||
%- is-managed-path
|
=/ group=(unit group)
|
||||||
(en-path:resource rid)
|
(scry-group rid)
|
||||||
|
?~ group %.n
|
||||||
|
!hidden.u.group
|
||||||
::
|
::
|
||||||
--
|
--
|
||||||
|
@ -245,11 +245,9 @@
|
|||||||
|= =(list ^group-contents)
|
|= =(list ^group-contents)
|
||||||
^- json
|
^- json
|
||||||
:- %a
|
:- %a
|
||||||
%+ murn list
|
%+ turn list
|
||||||
|= =^group-contents
|
|= =^group-contents
|
||||||
?. ?=(?(%add-members %remove-members) -.group-contents)
|
(update:enjs:group-store group-contents)
|
||||||
~
|
|
||||||
`(update:enjs:group-store group-contents)
|
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
++ indexed-notification
|
++ indexed-notification
|
||||||
|
@ -95,7 +95,6 @@
|
|||||||
%contact-pull-hook
|
%contact-pull-hook
|
||||||
%contact-view
|
%contact-view
|
||||||
%metadata-store
|
%metadata-store
|
||||||
%metadata-hook
|
|
||||||
%s3-store
|
%s3-store
|
||||||
%file-server
|
%file-server
|
||||||
%glob
|
%glob
|
||||||
@ -107,6 +106,8 @@
|
|||||||
%hark-group-hook
|
%hark-group-hook
|
||||||
%hark-chat-hook
|
%hark-chat-hook
|
||||||
%observe-hook
|
%observe-hook
|
||||||
|
%metadata-push-hook
|
||||||
|
%metadata-pull-hook
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ deft-fish :: default connects
|
++ deft-fish :: default connects
|
||||||
@ -249,6 +250,8 @@
|
|||||||
=> (se-born | %home %hark-chat-hook)
|
=> (se-born | %home %hark-chat-hook)
|
||||||
=> (se-born | %home %hark-store)
|
=> (se-born | %home %hark-store)
|
||||||
=> (se-born | %home %observe-hook)
|
=> (se-born | %home %observe-hook)
|
||||||
|
=> (se-born | %home %metadata-pull-hook)
|
||||||
|
=> (se-born | %home %metadata-push-hook)
|
||||||
(se-born | %home %herm)
|
(se-born | %home %herm)
|
||||||
=? ..on-load (lte hood-version %12)
|
=? ..on-load (lte hood-version %12)
|
||||||
=> (se-born | %home %contact-push-hook)
|
=> (se-born | %home %contact-push-hook)
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
/- *metadata-store
|
|
||||||
|%
|
|
||||||
++ associations-to-json
|
|
||||||
|= =associations
|
|
||||||
=, enjs:format
|
|
||||||
^- json
|
|
||||||
%- pairs
|
|
||||||
%+ turn ~(tap by associations)
|
|
||||||
|= [[=group-path =md-resource] =metadata]
|
|
||||||
^- [cord json]
|
|
||||||
:-
|
|
||||||
%- crip
|
|
||||||
;: weld
|
|
||||||
(trip (spat group-path))
|
|
||||||
(weld "/" (trip app-name.md-resource))
|
|
||||||
(trip (spat app-path.md-resource))
|
|
||||||
==
|
|
||||||
%- pairs
|
|
||||||
:~ [%group-path (path group-path)]
|
|
||||||
[%app-name s+app-name.md-resource]
|
|
||||||
[%app-path (path app-path.md-resource)]
|
|
||||||
[%metadata (metadata-to-json metadata)]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ json-to-action
|
|
||||||
|= jon=json
|
|
||||||
^- metadata-action
|
|
||||||
=, dejs:format
|
|
||||||
=< (parse-json jon)
|
|
||||||
|%
|
|
||||||
++ parse-json
|
|
||||||
%- of
|
|
||||||
:~ [%add add]
|
|
||||||
[%remove remove]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ add
|
|
||||||
%- ot
|
|
||||||
:~ [%group-path pa]
|
|
||||||
[%resource md-resource]
|
|
||||||
[%metadata metadata]
|
|
||||||
==
|
|
||||||
++ remove
|
|
||||||
%- ot
|
|
||||||
:~ [%group-path pa]
|
|
||||||
[%resource md-resource]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ nu
|
|
||||||
|= jon=json
|
|
||||||
?> ?=([%s *] jon)
|
|
||||||
(rash p.jon hex)
|
|
||||||
::
|
|
||||||
++ metadata
|
|
||||||
%- ot
|
|
||||||
:~ [%title so]
|
|
||||||
[%description so]
|
|
||||||
[%color nu]
|
|
||||||
[%date-created (se %da)]
|
|
||||||
[%creator (su ;~(pfix sig fed:ag))]
|
|
||||||
[%module so]
|
|
||||||
==
|
|
||||||
++ md-resource
|
|
||||||
%- ot
|
|
||||||
:~ [%app-name so]
|
|
||||||
[%app-path pa]
|
|
||||||
==
|
|
||||||
--
|
|
||||||
::
|
|
||||||
++ metadata-to-json
|
|
||||||
|= met=metadata
|
|
||||||
^- json
|
|
||||||
=, enjs:format
|
|
||||||
%- pairs
|
|
||||||
:~ [%title s+title.met]
|
|
||||||
[%description s+description.met]
|
|
||||||
[%color s+(scot %ux color.met)]
|
|
||||||
[%date-created s+(scot %da date-created.met)]
|
|
||||||
[%creator s+(scot %p creator.met)]
|
|
||||||
[%module s+module.met]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ update-to-json
|
|
||||||
|= upd=metadata-update
|
|
||||||
^- json
|
|
||||||
=, enjs:format
|
|
||||||
%+ frond %metadata-update
|
|
||||||
%- pairs
|
|
||||||
:~ ?- -.upd
|
|
||||||
%add
|
|
||||||
:- %add
|
|
||||||
%- pairs
|
|
||||||
:~ [%group-path (path group-path.upd)]
|
|
||||||
[%app-name s+app-name.resource.upd]
|
|
||||||
[%app-path (path app-path.resource.upd)]
|
|
||||||
[%metadata (metadata-to-json metadata.upd)]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
%update-metadata
|
|
||||||
:- %update-metadata
|
|
||||||
%- pairs
|
|
||||||
:~ [%group-path (path group-path.upd)]
|
|
||||||
[%app-name s+app-name.resource.upd]
|
|
||||||
[%app-path (path app-path.resource.upd)]
|
|
||||||
[%metadata (metadata-to-json metadata.upd)]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
%remove
|
|
||||||
:- %remove
|
|
||||||
%- pairs
|
|
||||||
:~ [%group-path (path group-path.upd)]
|
|
||||||
[%app-name s+app-name.resource.upd]
|
|
||||||
[%app-path (path app-path.resource.upd)]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
%associations
|
|
||||||
[%associations (associations-to-json associations.upd)]
|
|
||||||
== ==
|
|
||||||
--
|
|
163
pkg/arvo/lib/metadata-store.hoon
Normal file
163
pkg/arvo/lib/metadata-store.hoon
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
/- sur=metadata-store
|
||||||
|
/+ resource
|
||||||
|
^?
|
||||||
|
=< [. sur]
|
||||||
|
=, sur
|
||||||
|
|%
|
||||||
|
++ enjs
|
||||||
|
=, enjs:format
|
||||||
|
|%
|
||||||
|
++ associations
|
||||||
|
|= =^associations
|
||||||
|
=, enjs:format
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
%+ turn ~(tap by associations)
|
||||||
|
|= [=md-resource [group=resource =^metadatum]]
|
||||||
|
^- [cord json]
|
||||||
|
:-
|
||||||
|
%- crip
|
||||||
|
;: weld
|
||||||
|
(trip (spat (en-path:resource group)))
|
||||||
|
(weld "/" (trip app-name.md-resource))
|
||||||
|
(trip (spat (en-path:resource resource.md-resource)))
|
||||||
|
==
|
||||||
|
%- pairs
|
||||||
|
:~ [%group s+(enjs-path:resource group)]
|
||||||
|
[%app-name s+app-name.md-resource]
|
||||||
|
[%resource s+(enjs-path:resource resource.md-resource)]
|
||||||
|
[%metadata (^metadatum metadatum)]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ metadatum
|
||||||
|
|= met=^metadatum
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
:~ [%title s+title.met]
|
||||||
|
[%description s+description.met]
|
||||||
|
[%color s+(scot %ux color.met)]
|
||||||
|
[%date-created s+(scot %da date-created.met)]
|
||||||
|
[%creator s+(scot %p creator.met)]
|
||||||
|
[%module s+module.met]
|
||||||
|
[%picture s+picture.met]
|
||||||
|
[%preview b+preview.met]
|
||||||
|
[%vip s+`@t`vip.met]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ update
|
||||||
|
|= upd=^update
|
||||||
|
^- json
|
||||||
|
%+ frond %metadata-update
|
||||||
|
%- pairs
|
||||||
|
:~ ?+ -.upd *[cord json]
|
||||||
|
%add
|
||||||
|
:- %add
|
||||||
|
%- pairs
|
||||||
|
:~ [%group s+(enjs-path:resource group.upd)]
|
||||||
|
[%app-name s+app-name.resource.upd]
|
||||||
|
[%resource s+(enjs-path:resource resource.resource.upd)]
|
||||||
|
[%metadata (metadatum metadatum.upd)]
|
||||||
|
==
|
||||||
|
%updated-metadata
|
||||||
|
:- %add
|
||||||
|
%- pairs
|
||||||
|
:~ [%group s+(enjs-path:resource group.upd)]
|
||||||
|
[%app-name s+app-name.resource.upd]
|
||||||
|
[%resource s+(enjs-path:resource resource.resource.upd)]
|
||||||
|
[%metadata (metadatum metadatum.upd)]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
%remove
|
||||||
|
:- %remove
|
||||||
|
%- pairs
|
||||||
|
:~ [%group s+(enjs-path:resource group.upd)]
|
||||||
|
[%app-name s+app-name.resource.upd]
|
||||||
|
[%resource s+(enjs-path:resource resource.resource.upd)]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
%associations
|
||||||
|
[%associations (associations associations.upd)]
|
||||||
|
::
|
||||||
|
== ==
|
||||||
|
::
|
||||||
|
++ hook-update
|
||||||
|
|= upd=^hook-update
|
||||||
|
%+ frond %metadata-hook-update
|
||||||
|
%+ frond -.upd
|
||||||
|
%- pairs
|
||||||
|
?- -.upd
|
||||||
|
%preview
|
||||||
|
:~ [%group s+(enjs-path:resource group.upd)]
|
||||||
|
[%channels (associations channels.upd)]
|
||||||
|
[%members (numb members.upd)]
|
||||||
|
[%channel-count (numb channel-count.upd)]
|
||||||
|
[%metadata (metadatum metadatum.upd)]
|
||||||
|
==
|
||||||
|
%req-preview
|
||||||
|
~[group+s+(enjs-path:resource group.upd)]
|
||||||
|
==
|
||||||
|
--
|
||||||
|
::
|
||||||
|
++ dejs
|
||||||
|
=, dejs:format
|
||||||
|
|%
|
||||||
|
++ action
|
||||||
|
%- of
|
||||||
|
:~ [%add add]
|
||||||
|
[%remove remove]
|
||||||
|
[%initial-group initial-group]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ initial-group
|
||||||
|
|= json
|
||||||
|
[%initial-group *resource *associations]
|
||||||
|
::
|
||||||
|
++ add
|
||||||
|
%- ot
|
||||||
|
:~ [%group dejs-path:resource]
|
||||||
|
[%resource md-resource]
|
||||||
|
[%metadata metadatum]
|
||||||
|
==
|
||||||
|
++ remove
|
||||||
|
%- ot
|
||||||
|
:~ [%group dejs-path:resource]
|
||||||
|
[%resource md-resource]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ nu
|
||||||
|
|= jon=json
|
||||||
|
?> ?=([%s *] jon)
|
||||||
|
(rash p.jon hex)
|
||||||
|
::
|
||||||
|
++ vip
|
||||||
|
%- su
|
||||||
|
;~ pose
|
||||||
|
(tag %$)
|
||||||
|
(tag %reader-comments)
|
||||||
|
(tag %member-metadata)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ metadatum
|
||||||
|
^- $-(json ^metadatum)
|
||||||
|
%- ot
|
||||||
|
:~ [%title so]
|
||||||
|
[%description so]
|
||||||
|
[%color nu]
|
||||||
|
[%date-created (se %da)]
|
||||||
|
[%creator (su ;~(pfix sig fed:ag))]
|
||||||
|
[%module so]
|
||||||
|
[%picture so]
|
||||||
|
[%preview bo]
|
||||||
|
[%vip vip]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ tag |*(a=@tas (cold a (jest a)))
|
||||||
|
::
|
||||||
|
++ md-resource
|
||||||
|
^- $-(json ^md-resource)
|
||||||
|
%- ot
|
||||||
|
:~ [%app-name so]
|
||||||
|
[%resource dejs-path:resource]
|
||||||
|
==
|
||||||
|
--
|
||||||
|
--
|
@ -1,61 +1,69 @@
|
|||||||
:: metadata: helpers for getting data from the metadata-store
|
:: metadata: helpers for getting data from the metadata-store
|
||||||
::
|
::
|
||||||
/- *metadata-store
|
/- store=metadata-store
|
||||||
/+ res=resource
|
/+ resource
|
||||||
::
|
::
|
||||||
|_ =bowl:gall
|
|_ =bowl:gall
|
||||||
++ app-paths-from-group
|
++ app-paths-from-group
|
||||||
|= [=app-name =group-path]
|
|= [=app-name:store group=resource]
|
||||||
^- (list app-path)
|
^- (list resource)
|
||||||
%+ murn
|
%+ murn
|
||||||
%~ tap in
|
%~ tap in
|
||||||
=- (~(gut by -) group-path ~)
|
=- (~(gut by -) group ~)
|
||||||
.^ (jug ^group-path md-resource)
|
.^ (jug resource md-resource:store)
|
||||||
%gy
|
%gy
|
||||||
(scot %p our.bowl)
|
(scot %p our.bowl)
|
||||||
%metadata-store
|
%metadata-store
|
||||||
(scot %da now.bowl)
|
(scot %da now.bowl)
|
||||||
/group-indices
|
/group-indices
|
||||||
==
|
==
|
||||||
|= =md-resource
|
|= =md-resource:store
|
||||||
^- (unit app-path)
|
^- (unit resource)
|
||||||
?. =(app-name.md-resource app-name) ~
|
?. =(app-name.md-resource app-name) ~
|
||||||
`app-path.md-resource
|
`resource.md-resource
|
||||||
::
|
::
|
||||||
++ peek-metadata
|
++ app-metadata-for-group
|
||||||
|= [app-name=term =group=resource:res =app=resource:res]
|
|= [group=resource =app-name:store]
|
||||||
^- (unit metadata)
|
=/ =associations:store
|
||||||
=/ group-cord=cord (scot %t (spat (en-path:res group-resource)))
|
(metadata-for-group group)
|
||||||
=/ app-cord=cord (scot %t (spat (en-path:res app-resource)))
|
%- ~(gas by *associations:store)
|
||||||
=/ our=cord (scot %p our.bowl)
|
%+ skim ~(tap by associations)
|
||||||
=/ now=cord (scot %da now.bowl)
|
|= [=md-resource:store association:store]
|
||||||
.^ (unit metadata)
|
=(app-name app-name.md-resource)
|
||||||
|
::
|
||||||
|
++ metadata-for-group
|
||||||
|
|= group=resource
|
||||||
|
.^ associations:store
|
||||||
%gx (scot %p our.bowl) %metadata-store (scot %da now.bowl)
|
%gx (scot %p our.bowl) %metadata-store (scot %da now.bowl)
|
||||||
%metadata group-cord app-name app-cord /noun
|
%group (snoc (en-path:resource group) %noun)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ group-from-app-resource
|
++ md-resources-from-group
|
||||||
|= [app=term =app=resource:res]
|
|= group=resource
|
||||||
^- (unit resource:res)
|
=- (~(get ju -) group)
|
||||||
=/ app-path (en-path:res app-resource)
|
.^ (jug resource md-resource:store)
|
||||||
=/ group-paths (groups-from-resource app app-path)
|
|
||||||
?~ group-paths
|
|
||||||
~
|
|
||||||
`(de-path:res i.group-paths)
|
|
||||||
::
|
|
||||||
++ groups-from-resource
|
|
||||||
|= =md-resource
|
|
||||||
^- (list group-path)
|
|
||||||
=; resources
|
|
||||||
%~ tap in
|
|
||||||
%+ ~(gut by resources)
|
|
||||||
md-resource
|
|
||||||
*(set group-path)
|
|
||||||
.^ (jug ^md-resource group-path)
|
|
||||||
%gy
|
%gy
|
||||||
(scot %p our.bowl)
|
(scot %p our.bowl)
|
||||||
%metadata-store
|
%metadata-store
|
||||||
(scot %da now.bowl)
|
(scot %da now.bowl)
|
||||||
/resource-indices
|
/group-indices
|
||||||
==
|
==
|
||||||
|
::
|
||||||
|
++ peek-association
|
||||||
|
|= [app-name=term rid=resource]
|
||||||
|
.^ (unit association:store)
|
||||||
|
%gx (scot %p our.bowl) %metadata-store (scot %da now.bowl)
|
||||||
|
%metadata app-name (snoc (en-path:resource rid) %noun)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ peek-metadatum
|
||||||
|
|= =md-resource:store
|
||||||
|
%+ bind (peek-association md-resource)
|
||||||
|
|=(association:store metadatum)
|
||||||
|
::
|
||||||
|
++ peek-group
|
||||||
|
|= =md-resource:store
|
||||||
|
^- (unit resource)
|
||||||
|
%+ bind (peek-association md-resource)
|
||||||
|
|=(association:store group)
|
||||||
--
|
--
|
||||||
|
@ -232,22 +232,23 @@
|
|||||||
++ on-poke
|
++ on-poke
|
||||||
|= [=mark =vase]
|
|= [=mark =vase]
|
||||||
^- [(list card:agent:gall) agent:gall]
|
^- [(list card:agent:gall) agent:gall]
|
||||||
?> (team:title our.bowl src.bowl)
|
?+ mark
|
||||||
?+ mark
|
=^ cards pull-hook
|
||||||
=^ cards pull-hook
|
(on-poke:og mark vase)
|
||||||
(on-poke:og mark vase)
|
[cards this]
|
||||||
[cards this]
|
::
|
||||||
::
|
%sane
|
||||||
%sane
|
?> (team:title [our src]:bowl)
|
||||||
=^ cards state
|
=^ cards state
|
||||||
poke-sane:hc
|
poke-sane:hc
|
||||||
[cards this]
|
[cards this]
|
||||||
::
|
::
|
||||||
%pull-hook-action
|
%pull-hook-action
|
||||||
=^ cards state
|
?> (team:title [our src]:bowl)
|
||||||
(poke-hook-action:hc !<(action vase))
|
=^ cards state
|
||||||
[cards this]
|
(poke-hook-action:hc !<(action vase))
|
||||||
==
|
[cards this]
|
||||||
|
==
|
||||||
::
|
::
|
||||||
++ on-watch
|
++ on-watch
|
||||||
|= =path
|
|= =path
|
||||||
|
132
pkg/arvo/lib/settings.hoon
Normal file
132
pkg/arvo/lib/settings.hoon
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/- *settings
|
||||||
|
|%
|
||||||
|
++ enjs
|
||||||
|
=, enjs:format
|
||||||
|
|%
|
||||||
|
++ data
|
||||||
|
|= dat=^data
|
||||||
|
^- json
|
||||||
|
%+ frond -.dat
|
||||||
|
?- -.dat
|
||||||
|
%all (settings +.dat)
|
||||||
|
%bucket (bucket +.dat)
|
||||||
|
%entry (value +.dat)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ settings
|
||||||
|
|= s=^settings
|
||||||
|
^- json
|
||||||
|
[%o (~(run by s) bucket)]
|
||||||
|
::
|
||||||
|
++ event
|
||||||
|
|= evt=^event
|
||||||
|
^- json
|
||||||
|
%+ frond -.evt
|
||||||
|
?- -.evt
|
||||||
|
%put-bucket (put-bucket +.evt)
|
||||||
|
%del-bucket (del-bucket +.evt)
|
||||||
|
%put-entry (put-entry +.evt)
|
||||||
|
%del-entry (del-entry +.evt)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ put-bucket
|
||||||
|
|= [k=key b=^bucket]
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
:~ bucket-key+s+k
|
||||||
|
bucket+(bucket b)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ del-bucket
|
||||||
|
|= k=key
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
:~ bucket-key+s+k
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ put-entry
|
||||||
|
|= [b=key k=key v=val]
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
:~ bucket-key+s+b
|
||||||
|
entry-key+s+k
|
||||||
|
value+(val v)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ del-entry
|
||||||
|
|= [buc=key =key]
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
:~ bucket-key+s+buc
|
||||||
|
entry-key+s+key
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ value
|
||||||
|
|= =val
|
||||||
|
^- json
|
||||||
|
?- -.val
|
||||||
|
%s val
|
||||||
|
%b val
|
||||||
|
%n (numb p.val)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ bucket
|
||||||
|
|= b=^bucket
|
||||||
|
^- json
|
||||||
|
[%o (~(run by b) value)]
|
||||||
|
--
|
||||||
|
::
|
||||||
|
++ dejs
|
||||||
|
=, dejs:format
|
||||||
|
|%
|
||||||
|
++ event
|
||||||
|
|= jon=json
|
||||||
|
^- ^event
|
||||||
|
%. jon
|
||||||
|
%- of
|
||||||
|
:~ put-bucket+put-bucket
|
||||||
|
del-bucket+del-bucket
|
||||||
|
put-entry+put-entry
|
||||||
|
del-entry+del-entry
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ put-bucket
|
||||||
|
%- ot
|
||||||
|
:~ bucket-key+so
|
||||||
|
bucket+bucket
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ del-bucket
|
||||||
|
%- ot
|
||||||
|
:~ bucket-key+so
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ put-entry
|
||||||
|
%- ot
|
||||||
|
:~ bucket-key+so
|
||||||
|
entry-key+so
|
||||||
|
value+val
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ del-entry
|
||||||
|
%- ot
|
||||||
|
:~ bucket-key+so
|
||||||
|
entry-key+so
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ value
|
||||||
|
|= jon=json
|
||||||
|
^- val
|
||||||
|
?+ -.jon !!
|
||||||
|
%s jon
|
||||||
|
%b jon
|
||||||
|
%n [%n (rash p.jon dem)]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ bucket
|
||||||
|
|= jon=json
|
||||||
|
^- ^bucket
|
||||||
|
?> ?=([%o *] jon)
|
||||||
|
(~(run by p.jon) value)
|
||||||
|
--
|
||||||
|
--
|
@ -238,6 +238,13 @@
|
|||||||
`[%done ~]
|
`[%done ~]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
++ raw-poke-our
|
||||||
|
|= [app=term =cage]
|
||||||
|
=/ m (strand ,~)
|
||||||
|
^- form:m
|
||||||
|
;< =bowl:spider bind:m get-bowl
|
||||||
|
(raw-poke [our.bowl app] cage)
|
||||||
|
::
|
||||||
++ poke-our
|
++ poke-our
|
||||||
|= [=term =cage]
|
|= [=term =cage]
|
||||||
=/ m (strand ,~)
|
=/ m (strand ,~)
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
/+ *metadata-json
|
/+ store=metadata-store
|
||||||
=, dejs:format
|
|_ =action:store
|
||||||
|_ act=metadata-action
|
|
||||||
++ grad %noun
|
++ grad %noun
|
||||||
++ grow
|
++ grow
|
||||||
|%
|
|%
|
||||||
++ noun act
|
++ noun action
|
||||||
|
++ json update:enjs:store
|
||||||
--
|
--
|
||||||
++ grab
|
++ grab
|
||||||
|%
|
|%
|
||||||
++ noun metadata-action
|
++ noun action:store
|
||||||
++ json
|
++ json action:dejs:store
|
||||||
|= jon=^json
|
|
||||||
(json-to-action jon)
|
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
|
15
pkg/arvo/mar/metadata/hook-update.hoon
Normal file
15
pkg/arvo/mar/metadata/hook-update.hoon
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/+ store=metadata-store
|
||||||
|
|_ =hook-update:store
|
||||||
|
++ grad %noun
|
||||||
|
++ grow
|
||||||
|
|%
|
||||||
|
++ noun hook-update
|
||||||
|
++ json (hook-update:enjs:store hook-update)
|
||||||
|
--
|
||||||
|
::
|
||||||
|
++ grab
|
||||||
|
|%
|
||||||
|
++ noun hook-update:store
|
||||||
|
--
|
||||||
|
--
|
||||||
|
|
@ -1,15 +1,19 @@
|
|||||||
/+ *metadata-json
|
/+ store=metadata-store
|
||||||
|_ upd=metadata-update
|
|_ =update:store
|
||||||
++ grad %noun
|
++ grad %noun
|
||||||
++ grow
|
++ grow
|
||||||
|%
|
|%
|
||||||
++ noun upd
|
++ noun update
|
||||||
++ json (update-to-json upd)
|
++ resource
|
||||||
|
?> ?=(?(%add %remove %initial-group) -.update)
|
||||||
|
group.update
|
||||||
|
++ json (update:enjs:store update)
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
++ grab
|
++ grab
|
||||||
|%
|
|%
|
||||||
++ noun metadata-update
|
++ noun update:store
|
||||||
|
++ json action:dejs:store
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
--
|
--
|
||||||
|
13
pkg/arvo/mar/settings/data.hoon
Normal file
13
pkg/arvo/mar/settings/data.hoon
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/+ *settings
|
||||||
|
|_ dat=data
|
||||||
|
++ grad %noun
|
||||||
|
++ grow
|
||||||
|
|%
|
||||||
|
++ noun dat
|
||||||
|
++ json (data:enjs dat)
|
||||||
|
--
|
||||||
|
++ grab
|
||||||
|
|%
|
||||||
|
++ noun data
|
||||||
|
--
|
||||||
|
--
|
16
pkg/arvo/mar/settings/event.hoon
Normal file
16
pkg/arvo/mar/settings/event.hoon
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/+ *settings
|
||||||
|
|_ evt=event
|
||||||
|
++ grad %noun
|
||||||
|
++ grow
|
||||||
|
|%
|
||||||
|
++ noun evt
|
||||||
|
++ json
|
||||||
|
%+ frond:enjs:format %settings-event
|
||||||
|
(event:enjs evt)
|
||||||
|
--
|
||||||
|
++ grab
|
||||||
|
|%
|
||||||
|
++ noun event
|
||||||
|
++ json event:dejs
|
||||||
|
--
|
||||||
|
--
|
@ -62,9 +62,7 @@
|
|||||||
::
|
::
|
||||||
+$ group-contents
|
+$ group-contents
|
||||||
$~ [%add-members *resource ~]
|
$~ [%add-members *resource ~]
|
||||||
$% $>(?(%add-members %remove-members) update:group-store)
|
$>(?(%add-members %remove-members) update:group-store)
|
||||||
metadata-action:metadata-store
|
|
||||||
==
|
|
||||||
::
|
::
|
||||||
+$ notification
|
+$ notification
|
||||||
[date=@da read=? =contents]
|
[date=@da read=? =contents]
|
||||||
|
@ -1,28 +1,64 @@
|
|||||||
|
/- *resource
|
||||||
|
^?
|
||||||
|%
|
|%
|
||||||
+$ group-path path
|
::
|
||||||
+$ app-name term
|
+$ app-name term
|
||||||
+$ app-path path
|
+$ md-resource [=app-name =resource]
|
||||||
+$ md-resource [=app-name =app-path]
|
+$ association [group=resource =metadatum]
|
||||||
+$ associations (map [group-path md-resource] metadata)
|
+$ associations (map md-resource association)
|
||||||
|
+$ group-preview
|
||||||
|
$: group=resource
|
||||||
|
channels=associations
|
||||||
|
members=@ud
|
||||||
|
channel-count=@ud
|
||||||
|
=metadatum
|
||||||
|
==
|
||||||
::
|
::
|
||||||
+$ color @ux
|
+$ color @ux
|
||||||
+$ metadata
|
+$ url @t
|
||||||
|
::
|
||||||
|
:: $vip-metadata: variation in permissions
|
||||||
|
::
|
||||||
|
:: This will be passed to the graph-permissions mark
|
||||||
|
:: conversion to allow for custom permissions.
|
||||||
|
::
|
||||||
|
:: %reader-comments: Allow readers to comment, regardless
|
||||||
|
:: of whether they can write. (notebook, collections)
|
||||||
|
:: %member-metadata: Allow members to add channels (groups)
|
||||||
|
:: %$: No variation
|
||||||
|
::
|
||||||
|
+$ vip-metadata ?(%reader-comments %member-metadata %$)
|
||||||
|
+$ metadatum
|
||||||
$: title=cord
|
$: title=cord
|
||||||
description=cord
|
description=cord
|
||||||
=color
|
=color
|
||||||
date-created=time
|
date-created=time
|
||||||
creator=ship
|
creator=ship
|
||||||
module=term
|
module=term
|
||||||
|
picture=url
|
||||||
|
preview=?
|
||||||
|
vip=vip-metadata
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ metadata-action
|
+$ action
|
||||||
$% [%add =group-path resource=md-resource =metadata]
|
$% [%add group=resource resource=md-resource =metadatum]
|
||||||
[%remove =group-path resource=md-resource]
|
[%remove group=resource resource=md-resource]
|
||||||
|
[%initial-group group=resource =associations]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ metadata-update
|
+$ hook-update
|
||||||
$% metadata-action
|
$% [%req-preview group=resource]
|
||||||
|
[%preview group-preview]
|
||||||
|
==
|
||||||
|
::
|
||||||
|
+$ update
|
||||||
|
$% action
|
||||||
[%associations =associations]
|
[%associations =associations]
|
||||||
[%update-metadata =group-path resource=md-resource =metadata]
|
$: %updated-metadata
|
||||||
|
group=resource
|
||||||
|
resource=md-resource
|
||||||
|
before=metadatum
|
||||||
|
=metadatum
|
||||||
|
==
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
|
21
pkg/arvo/sur/settings.hoon
Normal file
21
pkg/arvo/sur/settings.hoon
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|%
|
||||||
|
+$ settings (map key bucket)
|
||||||
|
+$ bucket (map key val)
|
||||||
|
+$ key term
|
||||||
|
+$ val
|
||||||
|
$% [%s p=@t]
|
||||||
|
[%b p=?]
|
||||||
|
[%n p=@]
|
||||||
|
==
|
||||||
|
+$ event
|
||||||
|
$% [%put-bucket =key =bucket]
|
||||||
|
[%del-bucket =key]
|
||||||
|
[%put-entry buc=key =key =val]
|
||||||
|
[%del-entry buc=key =key]
|
||||||
|
==
|
||||||
|
+$ data
|
||||||
|
$% [%all =settings]
|
||||||
|
[%bucket =bucket]
|
||||||
|
[%entry =val]
|
||||||
|
==
|
||||||
|
--
|
@ -66,11 +66,11 @@
|
|||||||
module module.action
|
module module.action
|
||||||
==
|
==
|
||||||
=/ =metadata-action
|
=/ =metadata-action
|
||||||
[%add group-path graph+(en-path:resource rid.action) metadata]
|
[%add group graph+rid.action metadata]
|
||||||
;< ~ bind:m
|
;< ~ bind:m
|
||||||
(poke-our %metadata-hook %metadata-action !>(metadata-action))
|
(poke-our %metadata-store %metadata-action !>(metadata-action))
|
||||||
;< ~ bind:m
|
;< ~ bind:m
|
||||||
(poke-our %metadata-hook %metadata-hook-action !>([%add-owned group-path]))
|
(poke-our %metadata-push-hook %push-hook-action !>([%add group]))
|
||||||
::
|
::
|
||||||
:: Send invites
|
:: Send invites
|
||||||
::
|
::
|
||||||
|
@ -11,18 +11,15 @@
|
|||||||
|= rid=resource
|
|= rid=resource
|
||||||
=/ m (strand ,(unit resource))
|
=/ m (strand ,(unit resource))
|
||||||
^- form:m
|
^- form:m
|
||||||
;< pax=(unit (set path)) bind:m
|
;< res=(unit resource) bind:m
|
||||||
%+ scry:strandio ,(unit (set path))
|
%+ scry:strandio ,(unit resource)
|
||||||
;: weld
|
;: weld
|
||||||
/gx/metadata-store/resource/graph
|
/gx/metadata-store/resource/graph
|
||||||
(en-path:resource rid)
|
(en-path:resource rid)
|
||||||
/noun
|
/noun
|
||||||
==
|
==
|
||||||
%- pure:m
|
(pure:m res)
|
||||||
?~ pax ~
|
::
|
||||||
?~ u.pax ~
|
|
||||||
`(de-path:resource n.u.pax)
|
|
||||||
::
|
|
||||||
++ wait-for-group-join
|
++ wait-for-group-join
|
||||||
|= rid=resource
|
|= rid=resource
|
||||||
=/ m (strand ,~)
|
=/ m (strand ,~)
|
||||||
@ -89,9 +86,8 @@
|
|||||||
;< ~ bind:m (wait-for-group-join rid.action)
|
;< ~ bind:m (wait-for-group-join rid.action)
|
||||||
::
|
::
|
||||||
;< ~ bind:m
|
;< ~ bind:m
|
||||||
%+ poke-our %metadata-hook
|
%+ poke-our %metadata-pull-hook
|
||||||
metadata-hook-action+!>([%add-synced ship.action (en-path:resource rid.action)])
|
pull-hook-action+!>([%add ship.action rid.action])::
|
||||||
::
|
|
||||||
;< ~ bind:m (wait-for-md rid.action)
|
;< ~ bind:m (wait-for-md rid.action)
|
||||||
::
|
::
|
||||||
;< ~ bind:m
|
;< ~ bind:m
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
::
|
::
|
||||||
=* strand strand:spider
|
=* strand strand:spider
|
||||||
=* raw-poke raw-poke:strandio
|
=* raw-poke raw-poke:strandio
|
||||||
|
=* raw-poke-our raw-poke-our:strandio
|
||||||
=* scry scry:strandio
|
=* scry scry:strandio
|
||||||
::
|
::
|
||||||
^- thread:spider
|
^- thread:spider
|
||||||
@ -36,12 +37,12 @@
|
|||||||
:: stop serving or syncing metadata associated with group
|
:: stop serving or syncing metadata associated with group
|
||||||
::
|
::
|
||||||
;< ~ bind:m
|
;< ~ bind:m
|
||||||
%+ raw-poke
|
%- raw-poke-our
|
||||||
[our.bowl %metadata-hook]
|
?: =(our.bowl entity.resource.update)
|
||||||
:- %metadata-hook-action
|
:- %metadata-push-hook
|
||||||
!>([%remove (en-path:res resource.update)])
|
push-hook-action+!>([%remove resource.update])
|
||||||
:: get metadata associated with group
|
:- %metadata-pull-hook
|
||||||
::
|
pull-hook-action+!>([%remove resource.update])
|
||||||
;< =associations:met bind:m
|
;< =associations:met bind:m
|
||||||
%+ scry associations:met
|
%+ scry associations:met
|
||||||
;: weld
|
;: weld
|
||||||
@ -49,8 +50,8 @@
|
|||||||
(en-path:res resource.update)
|
(en-path:res resource.update)
|
||||||
/noun
|
/noun
|
||||||
==
|
==
|
||||||
=/ entries=(list [g=group-path:met m=md-resource:met])
|
=/ entries=(list [m=md-resource:met g=resource:res =metadata:met])
|
||||||
~(tap in ~(key by associations))
|
~(tap by associations)
|
||||||
|- ^- form:m
|
|- ^- form:m
|
||||||
=* loop $
|
=* loop $
|
||||||
?~ entries
|
?~ entries
|
||||||
@ -65,18 +66,16 @@
|
|||||||
[%remove g.i.entries m.i.entries]
|
[%remove g.i.entries m.i.entries]
|
||||||
:: archive graph associated with group
|
:: archive graph associated with group
|
||||||
::
|
::
|
||||||
=/ app-resource (de-path-soft:res app-path.m.i.entries)
|
=* app-resource resource.m.i.entries
|
||||||
?~ app-resource
|
|
||||||
loop(entries t.entries)
|
|
||||||
;< ~ bind:m
|
;< ~ bind:m
|
||||||
%+ raw-poke
|
%+ raw-poke
|
||||||
[our.bowl %graph-store]
|
[our.bowl %graph-store]
|
||||||
:- %graph-update
|
:- %graph-update
|
||||||
!> ^- update:gra
|
!> ^- update:gra
|
||||||
[%0 now.bowl [%archive-graph u.app-resource]]
|
[%0 now.bowl [%archive-graph app-resource]]
|
||||||
;< ~ bind:m
|
;< ~ bind:m
|
||||||
%+ raw-poke
|
%+ raw-poke
|
||||||
[our.bowl %graph-pull-hook]
|
[our.bowl %graph-pull-hook]
|
||||||
:- %pull-hook-action
|
:- %pull-hook-action
|
||||||
!>([%remove u.app-resource])
|
!>([%remove app-resource])
|
||||||
loop(entries t.entries)
|
loop(entries t.entries)
|
||||||
|
@ -11,6 +11,7 @@ import LaunchApi from './launch';
|
|||||||
import GraphApi from './graph';
|
import GraphApi from './graph';
|
||||||
import S3Api from './s3';
|
import S3Api from './s3';
|
||||||
import {HarkApi} from './hark';
|
import {HarkApi} from './hark';
|
||||||
|
import SettingsApi from './settings';
|
||||||
|
|
||||||
export default class GlobalApi extends BaseApi<StoreState> {
|
export default class GlobalApi extends BaseApi<StoreState> {
|
||||||
local = new LocalApi(this.ship, this.channel, this.store);
|
local = new LocalApi(this.ship, this.channel, this.store);
|
||||||
@ -22,6 +23,7 @@ export default class GlobalApi extends BaseApi<StoreState> {
|
|||||||
s3 = new S3Api(this.ship, this.channel, this.store);
|
s3 = new S3Api(this.ship, this.channel, this.store);
|
||||||
graph = new GraphApi(this.ship, this.channel, this.store);
|
graph = new GraphApi(this.ship, this.channel, this.store);
|
||||||
hark = new HarkApi(this.ship, this.channel, this.store);
|
hark = new HarkApi(this.ship, this.channel, this.store);
|
||||||
|
settings = new SettingsApi(this.ship, this.channel, this.store);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public ship: Patp,
|
public ship: Patp,
|
||||||
|
@ -75,8 +75,8 @@ export class HarkApi extends BaseApi<StoreState> {
|
|||||||
return this.harkAction(
|
return this.harkAction(
|
||||||
{ 'read-count': {
|
{ 'read-count': {
|
||||||
graph: {
|
graph: {
|
||||||
graph: association['app-path'],
|
graph: association.resource,
|
||||||
group: association['group-path'],
|
group: association.group,
|
||||||
module: association.metadata.module,
|
module: association.metadata.module,
|
||||||
description,
|
description,
|
||||||
index: parent
|
index: parent
|
||||||
@ -91,8 +91,8 @@ export class HarkApi extends BaseApi<StoreState> {
|
|||||||
'read-each': {
|
'read-each': {
|
||||||
index:
|
index:
|
||||||
{ graph:
|
{ graph:
|
||||||
{ graph: association['app-path'],
|
{ graph: association.resource,
|
||||||
group: association['group-path'],
|
group: association.group,
|
||||||
description,
|
description,
|
||||||
module: mod,
|
module: mod,
|
||||||
index: parent
|
index: parent
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
|
|
||||||
import BaseApi from './base';
|
import BaseApi from './base';
|
||||||
import { StoreState } from '../store/type';
|
import { StoreState } from '../store/type';
|
||||||
import { Path, Patp, Association, Metadata } from '~/types';
|
import { Path, Patp, Association, Metadata, MetadataUpdatePreview } from '~/types';
|
||||||
|
import {uxToHex} from '../lib/util';
|
||||||
|
|
||||||
export default class MetadataApi extends BaseApi<StoreState> {
|
export default class MetadataApi extends BaseApi<StoreState> {
|
||||||
|
|
||||||
|
|
||||||
metadataAdd(appName: string, appPath: Path, groupPath: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) {
|
metadataAdd(appName: string, resource: Path, group: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) {
|
||||||
const creator = `~${this.ship}`;
|
const creator = `~${this.ship}`;
|
||||||
return this.metadataAction({
|
return this.metadataAction({
|
||||||
add: {
|
add: {
|
||||||
'group-path': groupPath,
|
group,
|
||||||
resource: {
|
resource: {
|
||||||
'app-path': appPath,
|
resource,
|
||||||
'app-name': appName
|
'app-name': appName
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -21,7 +22,22 @@ export default class MetadataApi extends BaseApi<StoreState> {
|
|||||||
color,
|
color,
|
||||||
'date-created': dateCreated,
|
'date-created': dateCreated,
|
||||||
creator,
|
creator,
|
||||||
'module': moduleName
|
'module': moduleName,
|
||||||
|
preview: false,
|
||||||
|
picture: '',
|
||||||
|
permissions: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(appName: string, resource: string, group: string) {
|
||||||
|
return this.metadataAction({
|
||||||
|
remove: {
|
||||||
|
group,
|
||||||
|
resource: {
|
||||||
|
resource,
|
||||||
|
'app-name': appName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -29,19 +45,67 @@ export default class MetadataApi extends BaseApi<StoreState> {
|
|||||||
|
|
||||||
update(association: Association, newMetadata: Partial<Metadata>) {
|
update(association: Association, newMetadata: Partial<Metadata>) {
|
||||||
const metadata = {...association.metadata, ...newMetadata };
|
const metadata = {...association.metadata, ...newMetadata };
|
||||||
|
metadata.color = uxToHex(metadata.color);
|
||||||
return this.metadataAction({
|
return this.metadataAction({
|
||||||
add: {
|
add: {
|
||||||
'group-path': association['group-path'],
|
group: association.group,
|
||||||
resource: {
|
resource: {
|
||||||
'app-path': association['app-path'],
|
resource: association.resource,
|
||||||
'app-name': association['app-name'],
|
'app-name': association['app-name']
|
||||||
},
|
},
|
||||||
metadata
|
metadata
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preview(group: string) {
|
||||||
|
return new Promise<MetadataUpdatePreview>((resolve, reject) => {
|
||||||
|
const tempChannel: any = new (window as any).channel();
|
||||||
|
let done = false;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if(done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
done = true;
|
||||||
|
tempChannel.delete();
|
||||||
|
reject(new Error("offline"))
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
tempChannel.subscribe(window.ship, "metadata-pull-hook", `/preview${group}`,
|
||||||
|
(err) => {
|
||||||
|
reject(err);
|
||||||
|
tempChannel.delete();
|
||||||
|
},
|
||||||
|
(ev: any) => {
|
||||||
|
console.log(ev);
|
||||||
|
if ('metadata-hook-update' in ev) {
|
||||||
|
done = true;
|
||||||
|
tempChannel.delete();
|
||||||
|
const upd = ev['metadata-hook-update'].preview as MetadataUpdatePreview;
|
||||||
|
resolve(upd);
|
||||||
|
} else {
|
||||||
|
done = true;
|
||||||
|
tempChannel.delete();
|
||||||
|
reject(new Error("no-permissions"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(quit) => {
|
||||||
|
tempChannel.delete();
|
||||||
|
if(!done) {
|
||||||
|
reject(new Error("offline"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(a) => {
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private metadataAction(data) {
|
private metadataAction(data) {
|
||||||
return this.action('metadata-hook', 'metadata-action', data);
|
return this.action('metadata-push-hook', 'metadata-update', data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
74
pkg/interface/src/logic/api/settings.ts
Normal file
74
pkg/interface/src/logic/api/settings.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import BaseApi from './base';
|
||||||
|
import { StoreState } from '../store/type';
|
||||||
|
import {
|
||||||
|
SettingsUpdate,
|
||||||
|
SettingsData,
|
||||||
|
Key,
|
||||||
|
Value,
|
||||||
|
Bucket,
|
||||||
|
} from '~/types/settings';
|
||||||
|
|
||||||
|
|
||||||
|
export default class SettingsApi extends BaseApi<StoreState> {
|
||||||
|
private storeAction(action: SettingsEvent): Promise<any> {
|
||||||
|
return this.action('settings-store', 'settings-event', action);
|
||||||
|
}
|
||||||
|
|
||||||
|
putBucket(key: Key, bucket: Bucket) {
|
||||||
|
this.storeAction({
|
||||||
|
"put-bucket": {
|
||||||
|
"bucket-key": key,
|
||||||
|
"bucket": bucket,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delBucket(key: Key) {
|
||||||
|
this.storeAction({
|
||||||
|
"del-bucket": {
|
||||||
|
"bucket-key": key,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
putEntry(buc: Key, key: Key, val: Value) {
|
||||||
|
this.storeAction({
|
||||||
|
"put-entry": {
|
||||||
|
"bucket-key": buc,
|
||||||
|
"entry-key": key,
|
||||||
|
"value": val,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delEntry(buc: Key, key: Key) {
|
||||||
|
this.storeAction({
|
||||||
|
"put-entry": {
|
||||||
|
"bucket-key": buc,
|
||||||
|
"entry-key": key,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll() {
|
||||||
|
const data = await this.scry("settings-store", "/all");
|
||||||
|
this.store.handleEvent({data: {"settings-data": data.all}});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBucket(bucket: Key) {
|
||||||
|
const data = await this.scry('settings-store', `/bucket/${bucket}`);
|
||||||
|
this.store.handleEvent({data: {"settings-data": {
|
||||||
|
"bucket-key": bucket,
|
||||||
|
"bucket": data.bucket,
|
||||||
|
}}});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEntry(bucket: Key, entry: Key) {
|
||||||
|
const data = await this.scry('settings-store', `/entry/${bucket}/${entry}`);
|
||||||
|
this.store.handleEvent({data: {"settings-data": {
|
||||||
|
"bucket-key": bucket,
|
||||||
|
"entry-key": entry,
|
||||||
|
"entry": data.entry,
|
||||||
|
}}});
|
||||||
|
}
|
||||||
|
}
|
@ -84,7 +84,7 @@ export default function index(contacts, associations, apps, currentGroup, groups
|
|||||||
// iterate through each app's metadata object
|
// iterate through each app's metadata object
|
||||||
Object.keys(associations[e]).map((association) => {
|
Object.keys(associations[e]).map((association) => {
|
||||||
const each = associations[e][association];
|
const each = associations[e][association];
|
||||||
let title = each['app-path'];
|
let title = each.resource;
|
||||||
if (each.metadata.title !== '') {
|
if (each.metadata.title !== '') {
|
||||||
title = each.metadata.title;
|
title = each.metadata.title;
|
||||||
}
|
}
|
||||||
@ -98,25 +98,25 @@ export default function index(contacts, associations, apps, currentGroup, groups
|
|||||||
app = each.metadata.module;
|
app = each.metadata.module;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shipStart = each['app-path'].substr(each['app-path'].indexOf('~'));
|
const shipStart = each.resource.substr(each.resource.indexOf('~'));
|
||||||
|
|
||||||
if (app === 'groups') {
|
if (app === 'groups') {
|
||||||
const obj = result(
|
const obj = result(
|
||||||
title,
|
title,
|
||||||
`/~landscape${each['app-path']}`,
|
`/~landscape${each.resource}`,
|
||||||
app.charAt(0).toUpperCase() + app.slice(1),
|
app.charAt(0).toUpperCase() + app.slice(1),
|
||||||
cite(shipStart.slice(0, shipStart.indexOf('/')))
|
cite(shipStart.slice(0, shipStart.indexOf('/')))
|
||||||
);
|
);
|
||||||
landscape.push(obj);
|
landscape.push(obj);
|
||||||
} else {
|
} else {
|
||||||
const app = each.metadata.module || each['app-name'];
|
const app = each.metadata.module || each['app-name'];
|
||||||
const group = (groups[each['group-path']]?.hidden)
|
const group = (groups[each.group]?.hidden)
|
||||||
? '/home' : each['group-path'];
|
? '/home' : each.group;
|
||||||
const obj = result(
|
const obj = result(
|
||||||
title,
|
title,
|
||||||
`/~landscape${group}/join/${app}${each['app-path']}`,
|
`/~landscape${group}/join/${app}${each.resource}`,
|
||||||
app.charAt(0).toUpperCase() + app.slice(1),
|
app.charAt(0).toUpperCase() + app.slice(1),
|
||||||
(associations?.contacts?.[each['group-path']]?.metadata?.title || null)
|
(associations?.contacts?.[each.group]?.metadata?.title || null)
|
||||||
);
|
);
|
||||||
subscriptions.push(obj);
|
subscriptions.push(obj);
|
||||||
}
|
}
|
||||||
|
17
pkg/interface/src/logic/lib/useHashLink.ts
Normal file
17
pkg/interface/src/logic/lib/useHashLink.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import {useLocation} from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
|
export function useHashLink() {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(!location.hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.querySelector(location.hash)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
|
||||||
|
}, [location.hash]);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
103
pkg/interface/src/logic/lib/useModal.tsx
Normal file
103
pkg/interface/src/logic/lib/useModal.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import React, {
|
||||||
|
useState,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
SyntheticEvent,
|
||||||
|
useMemo,
|
||||||
|
useEffect,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
|
import { Box } from "@tlon/indigo-react";
|
||||||
|
|
||||||
|
type ModalFunc = (dismiss: () => void) => JSX.Element;
|
||||||
|
interface UseModalProps {
|
||||||
|
modal: JSX.Element | ModalFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseModalResult {
|
||||||
|
modal: ReactNode;
|
||||||
|
showModal: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopPropagation = (e: SyntheticEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useModal(props: UseModalProps): UseModalResult {
|
||||||
|
const [modalShown, setModalShown] = useState(false);
|
||||||
|
|
||||||
|
const dismiss = useCallback(() => {
|
||||||
|
setModalShown(false);
|
||||||
|
}, [setModalShown]);
|
||||||
|
|
||||||
|
const showModal = useCallback(() => {
|
||||||
|
setModalShown(true);
|
||||||
|
}, [setModalShown]);
|
||||||
|
|
||||||
|
const inner = useMemo(
|
||||||
|
() =>
|
||||||
|
!modalShown
|
||||||
|
? null
|
||||||
|
: typeof props.modal === "function"
|
||||||
|
? props.modal(dismiss)
|
||||||
|
: props.modal,
|
||||||
|
[modalShown, props.modal, dismiss]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(event) => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dismiss]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [modalShown]);
|
||||||
|
|
||||||
|
const modal = useMemo(
|
||||||
|
() =>
|
||||||
|
!inner ? null : (
|
||||||
|
<Box
|
||||||
|
backgroundColor="scales.black30"
|
||||||
|
left="0px"
|
||||||
|
top="0px"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
zIndex={10}
|
||||||
|
position="fixed"
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
onClick={dismiss}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
maxWidth="500px"
|
||||||
|
width="100%"
|
||||||
|
bg="white"
|
||||||
|
borderRadius={2}
|
||||||
|
border={[0, 1]}
|
||||||
|
borderColor={["washedGray", "washedGray"]}
|
||||||
|
onClick={stopPropagation}
|
||||||
|
display="flex"
|
||||||
|
alignItems="stretch"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
|
{inner}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
[inner, dismiss]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
showModal,
|
||||||
|
modal,
|
||||||
|
};
|
||||||
|
}
|
@ -16,6 +16,13 @@ export const MOMENT_CALENDAR_DATE = {
|
|||||||
sameElse: "~YYYY.M.D",
|
sameElse: "~YYYY.M.D",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getModuleIcon = (mod: string) => {
|
||||||
|
if (mod === "link") {
|
||||||
|
return "Collection";
|
||||||
|
}
|
||||||
|
return _.capitalize(mod);
|
||||||
|
}
|
||||||
|
|
||||||
export function appIsGraph(app: string) {
|
export function appIsGraph(app: string) {
|
||||||
return app === 'publish' || app == 'link';
|
return app === 'publish' || app == 'link';
|
||||||
}
|
}
|
||||||
@ -370,4 +377,4 @@ export function useHovering() {
|
|||||||
onMouseLeave: () => setHovering(false)
|
onMouseLeave: () => setHovering(false)
|
||||||
};
|
};
|
||||||
return { hovering, bind };
|
return { hovering, bind };
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ export default class MetadataReducer<S extends MetadataState> {
|
|||||||
reduce(json: Cage, state: S) {
|
reduce(json: Cage, state: S) {
|
||||||
let data = json['metadata-update']
|
let data = json['metadata-update']
|
||||||
if (data) {
|
if (data) {
|
||||||
|
console.log(data);
|
||||||
this.associations(data, state);
|
this.associations(data, state);
|
||||||
this.add(data, state);
|
this.add(data, state);
|
||||||
this.update(data, state);
|
this.update(data, state);
|
||||||
@ -25,14 +26,14 @@ export default class MetadataReducer<S extends MetadataState> {
|
|||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
let val = data[key];
|
let val = data[key];
|
||||||
let appName = val['app-name'];
|
let appName = val['app-name'];
|
||||||
let appPath = val['app-path'];
|
let rid = val.resource;
|
||||||
if (!(appName in metadata)) {
|
if (!(appName in metadata)) {
|
||||||
metadata[appName] = {};
|
metadata[appName] = {};
|
||||||
}
|
}
|
||||||
if (!(appPath in metadata[appName])) {
|
if (!(rid in metadata[appName])) {
|
||||||
metadata[appName][appPath] = {};
|
metadata[appName][rid] = {};
|
||||||
}
|
}
|
||||||
metadata[appName][appPath] = val;
|
metadata[appName][rid] = val;
|
||||||
});
|
});
|
||||||
|
|
||||||
state.associations = metadata;
|
state.associations = metadata;
|
||||||
@ -44,7 +45,7 @@ export default class MetadataReducer<S extends MetadataState> {
|
|||||||
if (data) {
|
if (data) {
|
||||||
let metadata = state.associations;
|
let metadata = state.associations;
|
||||||
let appName = data['app-name'];
|
let appName = data['app-name'];
|
||||||
let appPath = data['app-path'];
|
let appPath = data.resource;
|
||||||
|
|
||||||
if (!(appName in metadata)) {
|
if (!(appName in metadata)) {
|
||||||
metadata[appName] = {};
|
metadata[appName] = {};
|
||||||
@ -63,15 +64,15 @@ export default class MetadataReducer<S extends MetadataState> {
|
|||||||
if (data) {
|
if (data) {
|
||||||
let metadata = state.associations;
|
let metadata = state.associations;
|
||||||
let appName = data['app-name'];
|
let appName = data['app-name'];
|
||||||
let appPath = data['app-path'];
|
let rid = data.resource;
|
||||||
|
|
||||||
if (!(appName in metadata)) {
|
if (!(appName in metadata)) {
|
||||||
metadata[appName] = {};
|
metadata[appName] = {};
|
||||||
}
|
}
|
||||||
if (!(appPath in metadata[appName])) {
|
if (!(rid in metadata[appName])) {
|
||||||
metadata[appName][appPath] = {};
|
metadata[appName][rid] = {};
|
||||||
}
|
}
|
||||||
metadata[appName][appPath] = data;
|
metadata[appName][rid] = data;
|
||||||
|
|
||||||
state.associations = metadata;
|
state.associations = metadata;
|
||||||
}
|
}
|
||||||
@ -82,10 +83,10 @@ export default class MetadataReducer<S extends MetadataState> {
|
|||||||
if (data) {
|
if (data) {
|
||||||
let metadata = state.associations;
|
let metadata = state.associations;
|
||||||
let appName = data['app-name'];
|
let appName = data['app-name'];
|
||||||
let appPath = data['app-path'];
|
let rid = data.resource;
|
||||||
|
|
||||||
if (appName in metadata && appPath in metadata[appName]) {
|
if (appName in metadata && rid in metadata[appName]) {
|
||||||
delete metadata[appName][appPath];
|
delete metadata[appName][rid];
|
||||||
}
|
}
|
||||||
state.associations = metadata;
|
state.associations = metadata;
|
||||||
}
|
}
|
||||||
|
77
pkg/interface/src/logic/reducers/settings-update.ts
Normal file
77
pkg/interface/src/logic/reducers/settings-update.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { StoreState } from '../../store/type';
|
||||||
|
import {
|
||||||
|
SettingsUpdate,
|
||||||
|
} from '~/types/settings';
|
||||||
|
|
||||||
|
type SettingsState = Pick<StoreState, 'settings'>;
|
||||||
|
|
||||||
|
export default class SettingsReducer<S extends SettingsState>{
|
||||||
|
reduce(json: Cage, state: S) {
|
||||||
|
let data = json["settings-event"];
|
||||||
|
if (data) {
|
||||||
|
this.putBucket(data, state);
|
||||||
|
this.delBucket(data, state);
|
||||||
|
this.putEntry(data, state);
|
||||||
|
this.delEntry(data, state);
|
||||||
|
}
|
||||||
|
data = json["settings-data"];
|
||||||
|
if (data) {
|
||||||
|
this.getAll(data, state);
|
||||||
|
this.getBucket(data, state);
|
||||||
|
this.getEntry(data, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
putBucket(json: SettingsUpdate, state: S) {
|
||||||
|
const data = _.get(json, 'put-bucket', false);
|
||||||
|
if (data) {
|
||||||
|
state.settings[data["bucket-key"]] = data.bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delBucket(json: SettingsUpdate, state: S) {
|
||||||
|
const data = _.get(json, 'del-bucket', false);
|
||||||
|
if (data) {
|
||||||
|
delete state.settings[data["bucket-key"]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
putEntry(json: SettingsUpdate, state: S) {
|
||||||
|
const data = _.get(json, 'put-entry', false);
|
||||||
|
if (data) {
|
||||||
|
if (!state.settings[data["bucket-key"]]) {
|
||||||
|
state.settings[data["bucket-key"]] = {};
|
||||||
|
}
|
||||||
|
state.settings[data["bucket-key"]][data["entry-key"]] = data.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delEntry(json: SettingsUpdate, state: S) {
|
||||||
|
const data = _.get(json, 'del-entry', false);
|
||||||
|
if (data) {
|
||||||
|
delete state.settings[data["bucket-key"]][data["entry-key"]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll(json: any, state: S) {
|
||||||
|
state.settings = json;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBucket(json: any, state: S) {
|
||||||
|
const key = _.get(json, 'bucket-key', false);
|
||||||
|
const bucket = _.get(json, 'bucket', false);
|
||||||
|
if (key && bucket) {
|
||||||
|
state.settings[key] = bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntry(json: any, state: S) {
|
||||||
|
const bucketKey = _.get(json, 'bucket-key', false);
|
||||||
|
const entryKey = _.get(json, 'entry-key', false);
|
||||||
|
const entry = _.get(json, 'entry', false);
|
||||||
|
if (bucketKey && entryKey && entry) {
|
||||||
|
state.settings[bucketKey][entryKey] = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,22 +13,10 @@ import { ContactReducer } from '../reducers/contact-update';
|
|||||||
import GroupReducer from '../reducers/group-update';
|
import GroupReducer from '../reducers/group-update';
|
||||||
import LaunchReducer from '../reducers/launch-update';
|
import LaunchReducer from '../reducers/launch-update';
|
||||||
import ConnectionReducer from '../reducers/connection';
|
import ConnectionReducer from '../reducers/connection';
|
||||||
|
import SettingsReducer from '../reducers/settings-update';
|
||||||
import {OrderedMap} from '../lib/OrderedMap';
|
import {OrderedMap} from '../lib/OrderedMap';
|
||||||
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
|
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
|
||||||
|
|
||||||
export const homeAssociation = {
|
|
||||||
"app-path": "/home",
|
|
||||||
"app-name": "contact",
|
|
||||||
"group-path": "/home",
|
|
||||||
metadata: {
|
|
||||||
color: "0x0",
|
|
||||||
title: "DMs + Drafts",
|
|
||||||
description: "",
|
|
||||||
"date-created": "",
|
|
||||||
module: "",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default class GlobalStore extends BaseStore<StoreState> {
|
export default class GlobalStore extends BaseStore<StoreState> {
|
||||||
inviteReducer = new InviteReducer();
|
inviteReducer = new InviteReducer();
|
||||||
@ -38,6 +26,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
|||||||
groupReducer = new GroupReducer();
|
groupReducer = new GroupReducer();
|
||||||
launchReducer = new LaunchReducer();
|
launchReducer = new LaunchReducer();
|
||||||
connReducer = new ConnectionReducer();
|
connReducer = new ConnectionReducer();
|
||||||
|
settingsReducer = new SettingsReducer();
|
||||||
|
|
||||||
rehydrate() {
|
rehydrate() {
|
||||||
this.localReducer.rehydrate(this.state);
|
this.localReducer.rehydrate(this.state);
|
||||||
@ -89,7 +78,8 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
|||||||
graph: {},
|
graph: {},
|
||||||
group: {}
|
group: {}
|
||||||
},
|
},
|
||||||
notificationsCount: 0
|
notificationsCount: 0,
|
||||||
|
settings: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,5 +94,6 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
|||||||
GraphReducer(data, this.state);
|
GraphReducer(data, this.state);
|
||||||
HarkReducer(data, this.state);
|
HarkReducer(data, this.state);
|
||||||
ContactReducer(data, this.state);
|
ContactReducer(data, this.state);
|
||||||
|
this.settingsReducer.reduce(data, this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
|
|||||||
this.subscribe('/updates', 'hark-store');
|
this.subscribe('/updates', 'hark-store');
|
||||||
this.subscribe('/updates', 'hark-graph-hook');
|
this.subscribe('/updates', 'hark-graph-hook');
|
||||||
this.subscribe('/updates', 'hark-group-hook');
|
this.subscribe('/updates', 'hark-group-hook');
|
||||||
|
this.subscribe('/all', 'settings-store');
|
||||||
}
|
}
|
||||||
|
|
||||||
restart() {
|
restart() {
|
||||||
|
@ -5,6 +5,7 @@ import { MetadataUpdate } from "./metadata-update";
|
|||||||
import { GroupUpdate } from "./group-update";
|
import { GroupUpdate } from "./group-update";
|
||||||
import { LaunchUpdate, WeatherState } from "./launch-update";
|
import { LaunchUpdate, WeatherState } from "./launch-update";
|
||||||
import { ConnectionStatus } from "./connection";
|
import { ConnectionStatus } from "./connection";
|
||||||
|
import { SettingsUpdate } from "./settings";
|
||||||
|
|
||||||
interface MarksToTypes {
|
interface MarksToTypes {
|
||||||
readonly json: any;
|
readonly json: any;
|
||||||
@ -14,6 +15,7 @@ interface MarksToTypes {
|
|||||||
readonly groupUpdate: GroupUpdate;
|
readonly groupUpdate: GroupUpdate;
|
||||||
readonly "launch-update": LaunchUpdate;
|
readonly "launch-update": LaunchUpdate;
|
||||||
readonly "link-listen-update": LinkListenUpdate;
|
readonly "link-listen-update": LinkListenUpdate;
|
||||||
|
readonly "settings-event": SettingsUpdate;
|
||||||
// not really marks but w/e
|
// not really marks but w/e
|
||||||
readonly 'local': LocalUpdate;
|
readonly 'local': LocalUpdate;
|
||||||
readonly 'weather': WeatherState | {};
|
readonly 'weather': WeatherState | {};
|
||||||
|
@ -25,10 +25,18 @@ type MetadataUpdateUpdate = {
|
|||||||
|
|
||||||
type MetadataUpdateRemove = {
|
type MetadataUpdateRemove = {
|
||||||
remove: Resource & {
|
remove: Resource & {
|
||||||
'group-path': Path;
|
group: Path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MetadataUpdatePreview {
|
||||||
|
group: string;
|
||||||
|
channels: Associations;
|
||||||
|
"channel-count": number;
|
||||||
|
members: number;
|
||||||
|
metadata: Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
export type Associations = Record<AppName, AppAssociations>;
|
export type Associations = Record<AppName, AppAssociations>;
|
||||||
|
|
||||||
export type AppAssociations = {
|
export type AppAssociations = {
|
||||||
@ -36,12 +44,12 @@ export type AppAssociations = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Resource {
|
interface Resource {
|
||||||
'app-path': Path;
|
resource: Path;
|
||||||
'app-name': AppName;
|
'app-name': AppName;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Association = Resource & {
|
export type Association = Resource & {
|
||||||
'group-path': Path;
|
group: Path;
|
||||||
metadata: Metadata;
|
metadata: Metadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,4 +60,9 @@ export interface Metadata {
|
|||||||
description: string;
|
description: string;
|
||||||
title: string;
|
title: string;
|
||||||
module: string;
|
module: string;
|
||||||
|
picture: string;
|
||||||
|
preview: boolean;
|
||||||
|
permissions: Permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Permissions = '' | 'reader-comments';
|
||||||
|
55
pkg/interface/src/types/settings.ts
Normal file
55
pkg/interface/src/types/settings.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
export type Key = string;
|
||||||
|
export type Value = string | boolean | number;
|
||||||
|
export type Bucket = Map<string, Value>;
|
||||||
|
export type Settings = Map<string, Bucket>;
|
||||||
|
|
||||||
|
interface PutBucket {
|
||||||
|
"put-bucket": {
|
||||||
|
"bucket-key": Key;
|
||||||
|
"bucket": Bucket;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DelBucket {
|
||||||
|
"del-bucket": {
|
||||||
|
"bucket-key": Key;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PutEntry {
|
||||||
|
"put-entry": {
|
||||||
|
"bucket-key": Key;
|
||||||
|
"entry-key": Key;
|
||||||
|
"value": Value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DelEntry {
|
||||||
|
"del-entry": {
|
||||||
|
"bucket-key": Key;
|
||||||
|
"entry-key": Key;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AllData {
|
||||||
|
"all": Settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BucketData {
|
||||||
|
"bucket": Bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EntryData {
|
||||||
|
"entry": Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SettingsUpdate =
|
||||||
|
| PutBucket
|
||||||
|
| DelBucket
|
||||||
|
| PutEntry
|
||||||
|
| DelEntry;
|
||||||
|
|
||||||
|
export type SettingsData =
|
||||||
|
| AllData
|
||||||
|
| BucketData
|
||||||
|
| EntryData;
|
@ -94,6 +94,7 @@ class App extends React.Component {
|
|||||||
this.updateTheme(this.themeWatcher);
|
this.updateTheme(this.themeWatcher);
|
||||||
}, 500);
|
}, 500);
|
||||||
this.api.local.getBaseHash();
|
this.api.local.getBaseHash();
|
||||||
|
this.api.settings.getAll();
|
||||||
this.store.rehydrate();
|
this.store.rehydrate();
|
||||||
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {
|
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -21,8 +21,8 @@ type ChatResourceProps = StoreState & {
|
|||||||
} & RouteComponentProps;
|
} & RouteComponentProps;
|
||||||
|
|
||||||
export function ChatResource(props: ChatResourceProps) {
|
export function ChatResource(props: ChatResourceProps) {
|
||||||
const station = props.association['app-path'];
|
const station = props.association.resource;
|
||||||
const groupPath = props.association['group-path'];
|
const groupPath = props.association.group;
|
||||||
const group = props.groups[groupPath];
|
const group = props.groups[groupPath];
|
||||||
const contacts = props.contacts;
|
const contacts = props.contacts;
|
||||||
|
|
||||||
|
@ -18,17 +18,17 @@ const sortGroupsAlph = (a: Association, b: Association) =>
|
|||||||
|
|
||||||
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string) =>
|
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string) =>
|
||||||
f.flow(
|
f.flow(
|
||||||
f.pickBy((a: Association) => a['group-path'] === path),
|
f.pickBy((a: Association) => a.group === path),
|
||||||
f.map('app-path'),
|
f.map('resource'),
|
||||||
f.map(appPath => getUnreadCount(unreads, appPath, '/')),
|
f.map(rid => getUnreadCount(unreads, rid, '/')),
|
||||||
f.reduce(f.add, 0)
|
f.reduce(f.add, 0)
|
||||||
)(associations.graph);
|
)(associations.graph);
|
||||||
|
|
||||||
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
|
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
|
||||||
f.flow(
|
f.flow(
|
||||||
f.pickBy((a: Association) => a['group-path'] === path),
|
f.pickBy((a: Association) => a.group === path),
|
||||||
f.map('app-path'),
|
f.map('resource'),
|
||||||
f.map(appPath => getNotificationCount(unreads, appPath)),
|
f.map(rid => getNotificationCount(unreads, rid)),
|
||||||
f.reduce(f.add, 0)
|
f.reduce(f.add, 0)
|
||||||
)(associations.graph);
|
)(associations.graph);
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
|||||||
const { associations, unreads, inbox, ...boxProps } = props;
|
const { associations, unreads, inbox, ...boxProps } = props;
|
||||||
|
|
||||||
const groups = Object.values(associations?.contacts || {})
|
const groups = Object.values(associations?.contacts || {})
|
||||||
.filter((e) => e?.["group-path"] in props.groups)
|
.filter((e) => e?.group in props.groups)
|
||||||
.sort(sortGroupsAlph);
|
.sort(sortGroupsAlph);
|
||||||
const graphUnreads = getGraphUnreads(associations || {}, unreads);
|
const graphUnreads = getGraphUnreads(associations || {}, unreads);
|
||||||
const graphNotifications = getGraphNotifications(associations || {}, unreads);
|
const graphNotifications = getGraphNotifications(associations || {}, unreads);
|
||||||
@ -45,7 +45,7 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{groups.map((group, index) => {
|
{groups.map((group, index) => {
|
||||||
const path = group?.["group-path"];
|
const path = group?.group;
|
||||||
const unreadCount = graphUnreads(path)
|
const unreadCount = graphUnreads(path)
|
||||||
const notCount = graphNotifications(path);
|
const notCount = graphNotifications(path);
|
||||||
|
|
||||||
@ -54,8 +54,9 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
|||||||
updates={notCount}
|
updates={notCount}
|
||||||
first={index === 0}
|
first={index === 0}
|
||||||
unreads={unreadCount}
|
unreads={unreadCount}
|
||||||
path={group?.["group-path"]}
|
path={group?.group}
|
||||||
title={group.metadata.title}
|
title={group.metadata.title}
|
||||||
|
picture={group.metadata.picture}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -1,67 +1,24 @@
|
|||||||
import React, { useState, useEffect } from "react"
|
import React from "react"
|
||||||
import { Box, Button, Icon, Text } from "@tlon/indigo-react"
|
import { Box, Button, Icon, Text } from "@tlon/indigo-react"
|
||||||
import { NewGroup } from "~/views/landscape/components/NewGroup";
|
import {useModal} from "~/logic/lib/useModal";
|
||||||
import { JoinGroup } from "~/views/landscape/components/JoinGroup";
|
|
||||||
|
|
||||||
const ModalButton = (props) => {
|
const ModalButton = (props) => {
|
||||||
const {
|
const {
|
||||||
childen,
|
children,
|
||||||
icon,
|
icon,
|
||||||
text,
|
text,
|
||||||
bg,
|
bg,
|
||||||
color,
|
color,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
const [modalShown, setModalShown] = useState(false);
|
const { modal, showModal } = useModal({ modal: props.children });
|
||||||
|
|
||||||
const handleKeyDown = (event) => {
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
setModalShown(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
};
|
|
||||||
}, [modalShown]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{modalShown && (
|
{modal}
|
||||||
<Box
|
|
||||||
backgroundColor='scales.black30'
|
|
||||||
left="0px"
|
|
||||||
top="0px"
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
zIndex={4}
|
|
||||||
position="fixed"
|
|
||||||
display="flex"
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="center"
|
|
||||||
onClick={() => setModalShown(false)}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
maxWidth="500px"
|
|
||||||
width="100%"
|
|
||||||
bg="white"
|
|
||||||
borderRadius={2}
|
|
||||||
border={[0, 1]}
|
|
||||||
borderColor={["washedGray", "washedGray"]}
|
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
display="flex"
|
|
||||||
alignItems="stretch"
|
|
||||||
flexDirection="column"
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<Box
|
<Box
|
||||||
onClick={() => setModalShown(true)}
|
onClick={showModal}
|
||||||
display="flex"
|
display="flex"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
@ -78,4 +35,4 @@ const ModalButton = (props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ModalButton;
|
export default ModalButton;
|
||||||
|
@ -39,24 +39,24 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
history
|
history
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const appPath = association["app-path"];
|
const rid = association.resource;
|
||||||
|
|
||||||
const relativePath = (p: string) => `${baseUrl}/resource/link${appPath}${p}`;
|
const relativePath = (p: string) => `${baseUrl}/resource/link${rid}${p}`;
|
||||||
|
|
||||||
const [, , ship, name] = appPath.split("/");
|
const [, , ship, name] = rid.split("/");
|
||||||
const resourcePath = `${ship.slice(1)}/${name}`;
|
const resourcePath = `${ship.slice(1)}/${name}`;
|
||||||
const resource = associations.graph[appPath]
|
const resource = associations.graph[rid]
|
||||||
? associations.graph[appPath]
|
? associations.graph[rid]
|
||||||
: { metadata: {} };
|
: { metadata: {} };
|
||||||
const contactDetails = contacts[resource["group-path"]] || {};
|
const contactDetails = contacts[resource?.group] || {};
|
||||||
const group = groups[resource["group-path"]] || {};
|
const group = groups[resource?.group] || {};
|
||||||
const graph = graphs[resourcePath] || null;
|
const graph = graphs[resourcePath] || null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.graph.getGraph(ship, name);
|
api.graph.getGraph(ship, name);
|
||||||
}, [association]);
|
}, [association]);
|
||||||
|
|
||||||
const resourceUrl = `${baseUrl}/resource/link${appPath}`;
|
const resourceUrl = `${baseUrl}/resource/link${rid}`;
|
||||||
if (!graph) {
|
if (!graph) {
|
||||||
return <Center width='100%' height='100%'><LoadingSpinner/></Center>;
|
return <Center width='100%' height='100%'><LoadingSpinner/></Center>;
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
unreads={unreads}
|
unreads={unreads}
|
||||||
baseUrl={resourceUrl}
|
baseUrl={resourceUrl}
|
||||||
group={group}
|
group={group}
|
||||||
path={resource["group-path"]}
|
path={resource.group}
|
||||||
api={api}
|
api={api}
|
||||||
mb={3}
|
mb={3}
|
||||||
/>
|
/>
|
||||||
@ -116,7 +116,7 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
baseUrl={resourceUrl}
|
baseUrl={resourceUrl}
|
||||||
unreads={unreads}
|
unreads={unreads}
|
||||||
group={group}
|
group={group}
|
||||||
path={resource["group-path"]}
|
path={resource?.group}
|
||||||
api={api}
|
api={api}
|
||||||
mt={3}
|
mt={3}
|
||||||
measure={emptyMeasure}
|
measure={emptyMeasure}
|
||||||
|
@ -48,7 +48,7 @@ export function LinkWindow(props: LinkWindowProps) {
|
|||||||
}, [graph.size]);
|
}, [graph.size]);
|
||||||
|
|
||||||
const first = graph.peekLargest()?.[0];
|
const first = graph.peekLargest()?.[0];
|
||||||
const [,,ship, name] = association['app-path'].split('/');
|
const [,,ship, name] = association.resource.split('/');
|
||||||
|
|
||||||
const style = useMemo(() =>
|
const style = useMemo(() =>
|
||||||
({
|
({
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useCallback, useRef, useState } from "react";
|
import React, { useEffect, useCallback, useState, useMemo, useRef } from "react";
|
||||||
import f from "lodash/fp";
|
import f from "lodash/fp";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { Icon, Col, Row, Box, Text, Anchor, Rule, Center } from "@tlon/indigo-react";
|
import { Icon, Col, Row, Box, Text, Anchor, Rule, Center } from "@tlon/indigo-react";
|
||||||
@ -9,9 +9,17 @@ import { BigInteger } from "big-integer";
|
|||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { Notification } from "./notification";
|
import { Notification } from "./notification";
|
||||||
import { Associations } from "~/types";
|
import { Associations } from "~/types";
|
||||||
|
|
||||||
import {Invites} from "./invites";
|
import {Invites} from "./invites";
|
||||||
import {useLazyScroll} from "~/logic/lib/useLazyScroll";
|
import {useLazyScroll} from "~/logic/lib/useLazyScroll";
|
||||||
|
|
||||||
|
import { cite } from '~/logic/lib/util';
|
||||||
|
import { InviteItem } from '~/views/components/Invite';
|
||||||
|
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
import {useModal} from "~/logic/lib/useModal";
|
||||||
|
import {JoinGroup} from "~/views/landscape/components/JoinGroup";
|
||||||
|
|
||||||
type DatedTimebox = [BigInteger, Timebox];
|
type DatedTimebox = [BigInteger, Timebox];
|
||||||
|
|
||||||
function filterNotification(associations: Associations, groups: string[]) {
|
function filterNotification(associations: Associations, groups: string[]) {
|
||||||
@ -25,10 +33,7 @@ function filterNotification(associations: Associations, groups: string[]) {
|
|||||||
} else if ("group" in n.index) {
|
} else if ("group" in n.index) {
|
||||||
const { group } = n.index.group;
|
const { group } = n.index.group;
|
||||||
return groups.findIndex((g) => group === g) !== -1;
|
return groups.findIndex((g) => group === g) !== -1;
|
||||||
} else if ("chat" in n.index) {
|
}
|
||||||
const group = associations.chat[n.index.chat.chat]?.["group-path"];
|
|
||||||
return groups.findIndex((g) => group === g) !== -1;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -94,7 +99,6 @@ export default function Inbox(props: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const scrollRef = useRef(null);
|
const scrollRef = useRef(null);
|
||||||
|
|
||||||
const loadMore = useCallback(async () => {
|
const loadMore = useCallback(async () => {
|
||||||
return api.hark.getMore();
|
return api.hark.getMore();
|
||||||
}, [api]);
|
}, [api]);
|
||||||
|
@ -124,7 +124,6 @@ export function Notification(props: NotificationProps) {
|
|||||||
api={props.api}
|
api={props.api}
|
||||||
graphConfig={props.graphConfig}
|
graphConfig={props.graphConfig}
|
||||||
groupConfig={props.groupConfig}
|
groupConfig={props.groupConfig}
|
||||||
chatConfig={props.chatConfig}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</NotificationWrapper>
|
</NotificationWrapper>
|
||||||
|
@ -15,9 +15,9 @@ type PublishResourceProps = StoreState & {
|
|||||||
|
|
||||||
export function PublishResource(props: PublishResourceProps) {
|
export function PublishResource(props: PublishResourceProps) {
|
||||||
const { association, api, baseUrl, notebooks } = props;
|
const { association, api, baseUrl, notebooks } = props;
|
||||||
const appPath = association["app-path"];
|
const rid = association.resource;
|
||||||
const [, , ship, book] = appPath.split("/");
|
const [, , ship, book] = rid.split("/");
|
||||||
const notebookContacts = props.contacts[association["group-path"]];
|
const notebookContacts = props.contacts[association.group];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box height="100%" width="100%" overflowY="auto">
|
<Box height="100%" width="100%" overflowY="auto">
|
||||||
|
@ -64,11 +64,11 @@ export function MetadataForm(props: MetadataFormProps) {
|
|||||||
const { name, description } = values;
|
const { name, description } = values;
|
||||||
await api.metadata.metadataAdd(
|
await api.metadata.metadataAdd(
|
||||||
"publish",
|
"publish",
|
||||||
props.association["app-path"],
|
props.association.resource,
|
||||||
props.association["group-path"],
|
props.association.group,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
props.association.metadata["date-created"],
|
props.association.metadata["date-created"],,
|
||||||
uxToHex(props.association.metadata.color)
|
uxToHex(props.association.metadata.color)
|
||||||
);
|
);
|
||||||
actions.setStatus({ success: null });
|
actions.setStatus({ success: null });
|
||||||
|
@ -30,7 +30,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
|
|||||||
graph
|
graph
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const group = groups[association?.['group-path']];
|
const group = groups[association?.group];
|
||||||
if (!group) {
|
if (!group) {
|
||||||
return null; // Waiting on groups to populate
|
return null; // Waiting on groups to populate
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ export function NotebookRoutes(
|
|||||||
|
|
||||||
const graph = props.graphs[`${ship.slice(1)}/${book}`];
|
const graph = props.graphs[`${ship.slice(1)}/${book}`];
|
||||||
|
|
||||||
const group = groups?.[props.association?.['group-path']];
|
const group = groups?.[props.association?.group];
|
||||||
|
|
||||||
|
|
||||||
const relativePath = (path: string) => `${baseUrl}${path}`;
|
const relativePath = (path: string) => `${baseUrl}${path}`;
|
||||||
|
@ -10,8 +10,8 @@ export class Writers extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const { association, groups, contacts, api } = this.props;
|
const { association, groups, contacts, api } = this.props;
|
||||||
|
|
||||||
const [,,,name] = association?.['app-path'].split('/');
|
const [,,,name] = association?.resource.split('/');
|
||||||
const resource = resourceFromPath(association?.['group-path']);
|
const resource = resourceFromPath(association?.group);
|
||||||
|
|
||||||
const onSubmit = async (values, actions) => {
|
const onSubmit = async (values, actions) => {
|
||||||
try {
|
try {
|
||||||
@ -28,7 +28,7 @@ export class Writers extends Component {
|
|||||||
actions.setStatus({ error: e.message });
|
actions.setStatus({ error: e.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const writers = Array.from(groups?.[association?.['group-path']]?.tags.publish?.[`writers-${name}`] || new Set()).map(e => cite(`~${e}`)).join(', ');
|
const writers = Array.from(groups?.[association?.group]?.tags.publish?.[`writers-${name}`] || new Set()).map(e => cite(`~${e}`)).join(', ');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box maxWidth='512px'>
|
<Box maxWidth='512px'>
|
||||||
|
@ -92,14 +92,14 @@ export function Comments(props: CommentsProps) {
|
|||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(`dismissing ${association?.['app-path']}`);
|
console.log(`dismissing ${association?.resource}`);
|
||||||
return () => {
|
return () => {
|
||||||
api.hark.markCountAsRead(association, parentIndex, 'comment')
|
api.hark.markCountAsRead(association, parentIndex, 'comment')
|
||||||
};
|
};
|
||||||
}, [comments.post.index])
|
}, [comments.post.index])
|
||||||
|
|
||||||
|
|
||||||
const readCount = children.length - getUnreadCount(props?.unreads, association['app-path'], parentIndex)
|
const readCount = children.length - getUnreadCount(props?.unreads, association.resource, parentIndex)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col>
|
<Col>
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useFormikContext } from "formik";
|
import { useFormikContext } from "formik";
|
||||||
import { ErrorLabel } from "@tlon/indigo-react";
|
import { ErrorLabel } from "@tlon/indigo-react";
|
||||||
|
import {PropFunc} from "~/types/util";
|
||||||
|
|
||||||
export function FormError(props: { message: string }) {
|
export function FormError(props: { message?: string } & PropFunc<typeof ErrorLabel>) {
|
||||||
const { status } = useFormikContext();
|
const { status } = useFormikContext();
|
||||||
|
const { message, ...rest } = props;
|
||||||
|
|
||||||
let s = status || {};
|
let s = status || {};
|
||||||
|
const contents = message || s?.error;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorLabel>{"error" in s ? props.message : null}</ErrorLabel>
|
<ErrorLabel {...rest} hasError={"error" in s}>{contents}</ErrorLabel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ export function GroupSearch(props: InviteSearchProps) {
|
|||||||
|
|
||||||
const onSelect = useCallback(
|
const onSelect = useCallback(
|
||||||
(a: Association) => {
|
(a: Association) => {
|
||||||
setValue(a["group-path"]);
|
setValue(a.group);
|
||||||
setTouched(true);
|
setTouched(true);
|
||||||
},
|
},
|
||||||
[setValue]
|
[setValue]
|
||||||
@ -128,7 +128,7 @@ export function GroupSearch(props: InviteSearchProps) {
|
|||||||
search={(s: string, a: Association) =>
|
search={(s: string, a: Association) =>
|
||||||
a.metadata.title.toLowerCase().startsWith(s.toLowerCase())
|
a.metadata.title.toLowerCase().startsWith(s.toLowerCase())
|
||||||
}
|
}
|
||||||
getKey={(a: Association) => a["group-path"]}
|
getKey={(a: Association) => a.group}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import React, { ReactNode, useState, useEffect, useCallback } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { useStatelessAsyncClickable } from '~/logic/lib/useStatelessAsyncClickable';
|
import { useStatelessAsyncClickable } from '~/logic/lib/useStatelessAsyncClickable';
|
||||||
|
|
||||||
import { Button, LoadingSpinner, Action } from "@tlon/indigo-react";
|
import { LoadingSpinner, Action } from "@tlon/indigo-react";
|
||||||
|
|
||||||
import { useFormikContext } from "formik";
|
|
||||||
|
|
||||||
interface AsyncActionProps {
|
interface AsyncActionProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -26,6 +24,7 @@ export function StatelessAsyncAction({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Action
|
<Action
|
||||||
|
height="18px"
|
||||||
hideDisabled={!disabled}
|
hideDisabled={!disabled}
|
||||||
disabled={disabled || state === 'loading'}
|
disabled={disabled || state === 'loading'}
|
||||||
onClick={handleClick} {...rest}>
|
onClick={handleClick} {...rest}>
|
||||||
|
@ -32,22 +32,22 @@ function isJoined(path: string) {
|
|||||||
export function UnjoinedResource(props: UnjoinedResourceProps) {
|
export function UnjoinedResource(props: UnjoinedResourceProps) {
|
||||||
const { api, notebooks, graphKeys, inbox } = props;
|
const { api, notebooks, graphKeys, inbox } = props;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const appPath = props.association["app-path"];
|
const rid = props.association.resource;
|
||||||
const appName = props.association["app-name"];
|
const appName = props.association["app-name"];
|
||||||
const { title, description, module } = props.association.metadata;
|
const { title, description, module } = props.association.metadata;
|
||||||
const waiter = useWaitForProps(props);
|
const waiter = useWaitForProps(props);
|
||||||
const app = useMemo(() => module || appName, [props.association]);
|
const app = useMemo(() => module || appName, [props.association]);
|
||||||
|
|
||||||
const onJoin = async () => {
|
const onJoin = async () => {
|
||||||
const [, , ship, name] = appPath.split("/");
|
const [, , ship, name] = rid.split("/");
|
||||||
await api.graph.joinGraph(ship, name);
|
await api.graph.joinGraph(ship, name);
|
||||||
await waiter(isJoined(appPath));
|
await waiter(isJoined(rid));
|
||||||
history.push(`${props.baseUrl}/resource/${app}${appPath}`);
|
history.push(`${props.baseUrl}/resource/${app}${rid}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isJoined(appPath)({ graphKeys })) {
|
if (isJoined(rid)({ graphKeys })) {
|
||||||
history.push(`${props.baseUrl}/resource/${app}${appPath}`);
|
history.push(`${props.baseUrl}/resource/${app}${rid}`);
|
||||||
}
|
}
|
||||||
}, [props.association, inbox, graphKeys, notebooks]);
|
}, [props.association, inbox, graphKeys, notebooks]);
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ export function UnjoinedResource(props: UnjoinedResourceProps) {
|
|||||||
<RichText color="gray">{description}</RichText>
|
<RichText color="gray">{description}</RichText>
|
||||||
</Box>
|
</Box>
|
||||||
<StatelessAsyncButton
|
<StatelessAsyncButton
|
||||||
name={appPath}
|
name={rid}
|
||||||
primary
|
primary
|
||||||
width="fit-content"
|
width="fit-content"
|
||||||
onClick={onJoin}
|
onClick={onJoin}
|
||||||
|
@ -40,22 +40,22 @@ export function ChannelMenu(props: ChannelMenuProps) {
|
|||||||
const app = metadata.module || association["app-name"];
|
const app = metadata.module || association["app-name"];
|
||||||
const workspace = history.location.pathname.startsWith("/~landscape/home")
|
const workspace = history.location.pathname.startsWith("/~landscape/home")
|
||||||
? "/home"
|
? "/home"
|
||||||
: association?.["group-path"];
|
: association?.group;
|
||||||
const baseUrl = `/~landscape${workspace}/resource/${app}${association["app-path"]}`;
|
const baseUrl = `/~landscape${workspace}/resource/${app}${association.resource}`;
|
||||||
const appPath = association["app-path"];
|
const rid = association.resource;
|
||||||
|
|
||||||
const [,, ship, name] = appPath.split("/");
|
const [,, ship, name] = rid.split("/");
|
||||||
|
|
||||||
const isOurs = ship.slice(1) === window.ship;
|
const isOurs = ship.slice(1) === window.ship;
|
||||||
|
|
||||||
const isMuted =
|
const isMuted =
|
||||||
props.graphNotificationConfig.watching.findIndex(
|
props.graphNotificationConfig.watching.findIndex(
|
||||||
(a) => a.graph === appPath && a.index === "/"
|
(a) => a.graph === rid && a.index === "/"
|
||||||
) === -1;
|
) === -1;
|
||||||
|
|
||||||
const onChangeMute = async () => {
|
const onChangeMute = async () => {
|
||||||
const func = isMuted ? "listenGraph" : "ignoreGraph";
|
const func = isMuted ? "listenGraph" : "ignoreGraph";
|
||||||
await api.hark[func](appPath, "/");
|
await api.hark[func](rid, "/");
|
||||||
};
|
};
|
||||||
const onUnsubscribe = useCallback(async () => {
|
const onUnsubscribe = useCallback(async () => {
|
||||||
await api.graph.leaveGraph(ship, name);
|
await api.graph.leaveGraph(ship, name);
|
||||||
|
@ -45,8 +45,8 @@ export function ChannelSettings(props: ChannelSettingsProps) {
|
|||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const app = association["app-name"];
|
const app = association["app-name"];
|
||||||
const resource = association["app-path"];
|
const resource = association.resource;
|
||||||
const group = association["group-path"];
|
const group = association.group;
|
||||||
const date = metadata["date-created"];
|
const date = metadata["date-created"];
|
||||||
const { title, description, color } = values;
|
const { title, description, color } = values;
|
||||||
await api.metadata.metadataAdd(
|
await api.metadata.metadataAdd(
|
||||||
|
65
pkg/interface/src/views/landscape/components/DeleteGroup.tsx
Normal file
65
pkg/interface/src/views/landscape/components/DeleteGroup.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Col, Label, Row, Button } from "@tlon/indigo-react";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
|
import GlobalApi from "~/logic/api/global";
|
||||||
|
import { Association } from "~/types";
|
||||||
|
import { resourceFromPath } from "~/logic/lib/group";
|
||||||
|
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
|
||||||
|
import ModalButton from "~/views/apps/launch/components/ModalButton";
|
||||||
|
|
||||||
|
export function DeleteGroup(props: {
|
||||||
|
owner: boolean;
|
||||||
|
api: GlobalApi;
|
||||||
|
association: Association;
|
||||||
|
}) {
|
||||||
|
const history = useHistory();
|
||||||
|
const onDelete = async () => {
|
||||||
|
const name = props.association.group.split("/").pop();
|
||||||
|
if (props.owner) {
|
||||||
|
const shouldDelete =
|
||||||
|
prompt(`To confirm deleting this group, type ${name}`) === name;
|
||||||
|
if (!shouldDelete) return;
|
||||||
|
}
|
||||||
|
const resource = resourceFromPath(props.association.group);
|
||||||
|
await props.api.groups.removeGroup(resource);
|
||||||
|
history.push("/");
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = props.owner ? "Archive" : "Leave";
|
||||||
|
const description = props.owner
|
||||||
|
? "Permanently delete this group. (All current members will no longer see this group.)"
|
||||||
|
: "You can rejoin if it is an open group, or if you are reinvited";
|
||||||
|
|
||||||
|
const icon = props.owner ? "X" : "SignOut";
|
||||||
|
return (
|
||||||
|
<ModalButton
|
||||||
|
ml="2"
|
||||||
|
color="red"
|
||||||
|
boxShadow="none"
|
||||||
|
icon={icon}
|
||||||
|
text={`${action} group`}
|
||||||
|
>
|
||||||
|
{(dismiss: () => void) => (
|
||||||
|
<Col p="4">
|
||||||
|
<Label>{action} Group</Label>
|
||||||
|
<Label gray mt="2">
|
||||||
|
{description}
|
||||||
|
</Label>
|
||||||
|
<Row mt="2" justifyContent="flex-end">
|
||||||
|
<Button onClick={dismiss}>Cancel</Button>
|
||||||
|
<StatelessAsyncButton
|
||||||
|
name={`delete-${props.association.group}`}
|
||||||
|
onClick={onDelete}
|
||||||
|
ml="2"
|
||||||
|
destructive
|
||||||
|
primary
|
||||||
|
>
|
||||||
|
{action} {`"${props.association.metadata.title}"`}
|
||||||
|
</StatelessAsyncButton>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</ModalButton>
|
||||||
|
);
|
||||||
|
}
|
@ -8,6 +8,7 @@ import {
|
|||||||
Col,
|
Col,
|
||||||
Label,
|
Label,
|
||||||
Button,
|
Button,
|
||||||
|
Text,
|
||||||
} from "@tlon/indigo-react";
|
} from "@tlon/indigo-react";
|
||||||
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
|
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
|
||||||
import { FormError } from "~/views/components/FormError";
|
import { FormError } from "~/views/components/FormError";
|
||||||
@ -21,12 +22,15 @@ import { ColorInput } from "~/views/components/ColorInput";
|
|||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
import { uxToHex } from "~/logic/lib/util";
|
import { uxToHex } from "~/logic/lib/util";
|
||||||
|
import {S3State} from "~/types";
|
||||||
|
import {ImageInput} from "~/views/components/ImageInput";
|
||||||
|
|
||||||
interface FormSchema {
|
interface FormSchema {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
color: string;
|
color: string;
|
||||||
isPrivate: boolean;
|
isPrivate: boolean;
|
||||||
|
picture: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formSchema = Yup.object({
|
const formSchema = Yup.object({
|
||||||
@ -40,10 +44,11 @@ interface GroupAdminSettingsProps {
|
|||||||
group: Group;
|
group: Group;
|
||||||
association: Association;
|
association: Association;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
|
s3: S3State;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GroupAdminSettings(props: GroupAdminSettingsProps) {
|
export function GroupAdminSettings(props: GroupAdminSettingsProps) {
|
||||||
const { group, association } = props;
|
const { group, association, s3 } = props;
|
||||||
const { metadata } = association;
|
const { metadata } = association;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const currentPrivate = "invite" in props.group.policy;
|
const currentPrivate = "invite" in props.group.policy;
|
||||||
@ -51,6 +56,7 @@ export function GroupAdminSettings(props: GroupAdminSettingsProps) {
|
|||||||
title: metadata?.title,
|
title: metadata?.title,
|
||||||
description: metadata?.description,
|
description: metadata?.description,
|
||||||
color: metadata?.color,
|
color: metadata?.color,
|
||||||
|
picture: metadata?.picture,
|
||||||
isPrivate: currentPrivate,
|
isPrivate: currentPrivate,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,15 +65,16 @@ export function GroupAdminSettings(props: GroupAdminSettingsProps) {
|
|||||||
actions: FormikHelpers<FormSchema>
|
actions: FormikHelpers<FormSchema>
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const { title, description, color, isPrivate } = values;
|
const { title, description, picture, color, isPrivate } = values;
|
||||||
const uxColor = uxToHex(color);
|
const uxColor = uxToHex(color);
|
||||||
await props.api.metadata.update(props.association, {
|
await props.api.metadata.update(props.association, {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
|
picture,
|
||||||
color: uxColor,
|
color: uxColor,
|
||||||
});
|
});
|
||||||
if (isPrivate !== currentPrivate) {
|
if (isPrivate !== currentPrivate) {
|
||||||
const resource = resourceFromPath(props.association["group-path"]);
|
const resource = resourceFromPath(props.association.group);
|
||||||
const newPolicy: Enc<GroupPolicy> = isPrivate
|
const newPolicy: Enc<GroupPolicy> = isPrivate
|
||||||
? { invite: { pending: [] } }
|
? { invite: { pending: [] } }
|
||||||
: { open: { banRanks: [], banned: [] } };
|
: { open: { banRanks: [], banned: [] } };
|
||||||
@ -83,8 +90,9 @@ export function GroupAdminSettings(props: GroupAdminSettingsProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const disabled =
|
const disabled =
|
||||||
resourceFromPath(association["group-path"]).ship.slice(1) !== window.ship &&
|
resourceFromPath(association.group).ship.slice(1) !== window.ship &&
|
||||||
roleForShip(group, window.ship) !== "admin";
|
roleForShip(group, window.ship) !== "admin";
|
||||||
|
if(disabled) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
@ -93,7 +101,8 @@ export function GroupAdminSettings(props: GroupAdminSettingsProps) {
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<Col gapY={4}>
|
<Box p="4" fontWeight="600" fontSize="2" id="group-details">Group Details</Box>
|
||||||
|
<Col pb="4" px="4" maxWidth="384px" gapY={4}>
|
||||||
<Input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
label="Group Name"
|
label="Group Name"
|
||||||
@ -112,6 +121,14 @@ export function GroupAdminSettings(props: GroupAdminSettingsProps) {
|
|||||||
caption="A color to represent your group"
|
caption="A color to represent your group"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
|
<ImageInput
|
||||||
|
id="picture"
|
||||||
|
label="Group picture"
|
||||||
|
caption="A picture for your group"
|
||||||
|
placeholder="Enter URL"
|
||||||
|
disabled={disabled}
|
||||||
|
s3={s3}
|
||||||
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="isPrivate"
|
id="isPrivate"
|
||||||
label="Private group"
|
label="Private group"
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { Icon, Text, Row, Col } from "@tlon/indigo-react";
|
||||||
|
import { Formik } from "formik";
|
||||||
|
import { Association, Associations, Group } from "~/types";
|
||||||
|
import GlobalApi from "~/logic/api/global";
|
||||||
|
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
|
||||||
|
import { getModuleIcon } from "~/logic/lib/util";
|
||||||
|
import { Dropdown } from "~/views/components/Dropdown";
|
||||||
|
import { resourceFromPath, roleForShip } from "~/logic/lib/group";
|
||||||
|
|
||||||
|
interface GroupChannelSettingsProps {
|
||||||
|
group: Group;
|
||||||
|
association: Association;
|
||||||
|
associations: Associations;
|
||||||
|
api: GlobalApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GroupChannelSettings(props: GroupChannelSettingsProps) {
|
||||||
|
const { api, associations, association, group } = props;
|
||||||
|
const channels = Object.values(associations.graph).filter(
|
||||||
|
({ group }) => association.group === group
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChange = useCallback(
|
||||||
|
async (resource: string, preview: boolean) => {
|
||||||
|
return api.metadata.update(associations.graph[resource], { preview });
|
||||||
|
},
|
||||||
|
[associations, api]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onRemove = useCallback(
|
||||||
|
async (resource: string) => {
|
||||||
|
return api.metadata.remove("graph", resource, association.group);
|
||||||
|
},
|
||||||
|
[api, association]
|
||||||
|
);
|
||||||
|
|
||||||
|
const disabled =
|
||||||
|
resourceFromPath(association.group).ship.slice(1) !== window.ship &&
|
||||||
|
roleForShip(group, window.ship) !== "admin";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col maxWidth="384px" width="100%">
|
||||||
|
<Text p="4" id="channels" fontWeight="600" fontSize="2">
|
||||||
|
Channels
|
||||||
|
</Text>
|
||||||
|
<Col p="4" width="100%" gapY="3">
|
||||||
|
{channels.map(({ resource, metadata }) => (
|
||||||
|
<Row justifyContent="space-between" width="100%" key={resource}>
|
||||||
|
<Row gapX="2">
|
||||||
|
<Icon icon={getModuleIcon(metadata.module)} />
|
||||||
|
<Text>{metadata.title}</Text>
|
||||||
|
{metadata.preview && <Text gray>Pinned</Text>}
|
||||||
|
</Row>
|
||||||
|
{!disabled && (
|
||||||
|
<Dropdown
|
||||||
|
options={
|
||||||
|
<Col
|
||||||
|
bg="white"
|
||||||
|
border="1"
|
||||||
|
borderRadius="1"
|
||||||
|
borderColor="lightGray"
|
||||||
|
p="1"
|
||||||
|
gapY="1"
|
||||||
|
>
|
||||||
|
<StatelessAsyncAction
|
||||||
|
bg="transparent"
|
||||||
|
name={`pin-${resource}`}
|
||||||
|
onClick={() => onChange(resource, !metadata.preview)}
|
||||||
|
>
|
||||||
|
{metadata.preview ? "Unpin" : "Pin"}
|
||||||
|
</StatelessAsyncAction>
|
||||||
|
<StatelessAsyncAction
|
||||||
|
bg="transparent"
|
||||||
|
name={`remove-${resource}`}
|
||||||
|
onClick={() => onRemove(resource)}
|
||||||
|
>
|
||||||
|
<Text color="red">Remove from group</Text>
|
||||||
|
</StatelessAsyncAction>
|
||||||
|
</Col>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon icon="Ellipsis" />
|
||||||
|
</Dropdown>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
@ -1,43 +1,66 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect, useCallback } from "react";
|
||||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
import { Box, Col, Button, Text } from "@tlon/indigo-react";
|
||||||
import * as Yup from "yup";
|
import { Group } from "~/types/group-update";
|
||||||
import {
|
import { Association, Associations } from "~/types/metadata-update";
|
||||||
Box,
|
|
||||||
ManagedTextInputField as Input,
|
|
||||||
ManagedToggleSwitchField as Checkbox,
|
|
||||||
Col,
|
|
||||||
Label,
|
|
||||||
Button,
|
|
||||||
} from "@tlon/indigo-react";
|
|
||||||
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
|
|
||||||
import { FormError } from "~/views/components/FormError";
|
|
||||||
import { Group, GroupPolicy } from "~/types/group-update";
|
|
||||||
import { Enc } from "~/types/noun";
|
|
||||||
import { Association } from "~/types/metadata-update";
|
|
||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { resourceFromPath, roleForShip } from "~/logic/lib/group";
|
|
||||||
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
|
|
||||||
import { ColorInput } from "~/views/components/ColorInput";
|
|
||||||
import { useHistory } from "react-router-dom";
|
|
||||||
|
|
||||||
import { uxToHex } from "~/logic/lib/util";
|
|
||||||
import { GroupAdminSettings } from "./Admin";
|
import { GroupAdminSettings } from "./Admin";
|
||||||
import { GroupPersonalSettings } from "./Personal";
|
import { GroupPersonalSettings } from "./Personal";
|
||||||
import {GroupNotificationsConfig} from "~/types";
|
import { GroupNotificationsConfig, S3State } from "~/types";
|
||||||
|
import { GroupChannelSettings } from "./Channels";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
import {resourceFromPath, roleForShip} from "~/logic/lib/group";
|
||||||
|
|
||||||
|
const Section = ({ children }) => (
|
||||||
|
<Box boxShadow="inset 0px 1px 0px rgba(0, 0, 0, 0.2)">{children}</Box>
|
||||||
|
);
|
||||||
|
|
||||||
interface GroupSettingsProps {
|
interface GroupSettingsProps {
|
||||||
group: Group;
|
group: Group;
|
||||||
association: Association;
|
association: Association;
|
||||||
|
associations: Associations;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
notificationsGroupConfig: GroupNotificationsConfig;
|
notificationsGroupConfig: GroupNotificationsConfig;
|
||||||
|
s3: S3State;
|
||||||
|
baseUrl: string;
|
||||||
}
|
}
|
||||||
export function GroupSettings(props: GroupSettingsProps) {
|
export function GroupSettings(props: GroupSettingsProps) {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const linkRelative = useCallback(
|
||||||
|
(url: string) =>
|
||||||
|
useCallback(() => history.push(`${props.baseUrl}${url}`), [url]),
|
||||||
|
[history, props.baseUrl]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isAdmin =
|
||||||
|
resourceFromPath(props.association.group).ship.slice(1) === window.ship ||
|
||||||
|
roleForShip(props.group, window.ship) === "admin";
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box height="100%" overflowY="auto">
|
<Box height="100%" overflowY="auto">
|
||||||
<Col maxWidth="384px" p="4" gapY="4">
|
<Col>
|
||||||
<GroupPersonalSettings {...props} />
|
<GroupPersonalSettings {...props} />
|
||||||
<Box borderBottom="1" borderBottomColor="washedGray" />
|
<Section>
|
||||||
<GroupAdminSettings {...props} />
|
<Col p="4" maxWidth="384px">
|
||||||
|
<Text fontSize="2" fontWeight="600">
|
||||||
|
Participants
|
||||||
|
</Text>
|
||||||
|
<Text gray>View list of all group participants and statuses</Text>
|
||||||
|
<Button primary mt="4" onClick={linkRelative("/participants")}>View List</Button>
|
||||||
|
</Col>
|
||||||
|
</Section>
|
||||||
|
{ isAdmin && (
|
||||||
|
<>
|
||||||
|
<Section>
|
||||||
|
<GroupAdminSettings {...props} />
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<GroupChannelSettings {...props} />
|
||||||
|
</Section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,9 @@ import {
|
|||||||
Label,
|
Label,
|
||||||
Button,
|
Button,
|
||||||
LoadingSpinner,
|
LoadingSpinner,
|
||||||
BaseLabel
|
BaseLabel,
|
||||||
|
Anchor,
|
||||||
|
BaseAnchor
|
||||||
} from "@tlon/indigo-react";
|
} from "@tlon/indigo-react";
|
||||||
import { Group, GroupPolicy } from "~/types/group-update";
|
import { Group, GroupPolicy } from "~/types/group-update";
|
||||||
import { Enc } from "~/types/noun";
|
import { Enc } from "~/types/noun";
|
||||||
@ -26,44 +28,7 @@ import { FormikOnBlur } from "~/views/components/FormikOnBlur";
|
|||||||
import {GroupNotificationsConfig} from "~/types";
|
import {GroupNotificationsConfig} from "~/types";
|
||||||
import {StatelessAsyncToggle} from "~/views/components/StatelessAsyncToggle";
|
import {StatelessAsyncToggle} from "~/views/components/StatelessAsyncToggle";
|
||||||
|
|
||||||
function DeleteGroup(props: {
|
|
||||||
owner: boolean;
|
|
||||||
api: GlobalApi;
|
|
||||||
association: Association;
|
|
||||||
}) {
|
|
||||||
const history = useHistory();
|
|
||||||
const onDelete = async () => {
|
|
||||||
const name = props.association['group-path'].split('/').pop();
|
|
||||||
if (props.owner) {
|
|
||||||
const shouldDelete = (prompt(`To confirm deleting this group, type ${name}`) === name);
|
|
||||||
if (!shouldDelete) return;
|
|
||||||
}
|
|
||||||
const resource = resourceFromPath(props.association["group-path"])
|
|
||||||
await props.api.groups.removeGroup(resource);
|
|
||||||
history.push("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
const action = props.owner ? "Delete" : "Leave";
|
|
||||||
const description = props.owner
|
|
||||||
? "Permanently delete this group. (All current members will no longer see this group.)"
|
|
||||||
: "You can rejoin if it is an open group, or if you are reinvited";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col>
|
|
||||||
<Label>{action} Group</Label>
|
|
||||||
<Label gray mt="2">
|
|
||||||
{description}
|
|
||||||
</Label>
|
|
||||||
<StatelessAsyncButton onClick={onDelete} mt={2} destructive={props.owner}>
|
|
||||||
{action} this group
|
|
||||||
</StatelessAsyncButton>
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FormSchema {
|
|
||||||
watching: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GroupPersonalSettings(props: {
|
export function GroupPersonalSettings(props: {
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
@ -71,7 +36,7 @@ export function GroupPersonalSettings(props: {
|
|||||||
notificationsGroupConfig: GroupNotificationsConfig;
|
notificationsGroupConfig: GroupNotificationsConfig;
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
const groupPath = props.association['group-path'];
|
const groupPath = props.association.group;
|
||||||
|
|
||||||
const watching = props.notificationsGroupConfig.findIndex(g => g === groupPath) !== -1;
|
const watching = props.notificationsGroupConfig.findIndex(g => g === groupPath) !== -1;
|
||||||
|
|
||||||
@ -80,10 +45,9 @@ export function GroupPersonalSettings(props: {
|
|||||||
await props.api.hark[func](groupPath);
|
await props.api.hark[func](groupPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
const owner = (props.group?.tags?.role?.admin.has(window.ship) || false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col gapY="4">
|
<Col px="4" pb="4" gapY="4">
|
||||||
|
<BaseAnchor pt="4" fontWeight="600" id="notifications" fontSize="2">Group Notifications</BaseAnchor>
|
||||||
<BaseLabel
|
<BaseLabel
|
||||||
htmlFor="asyncToggle"
|
htmlFor="asyncToggle"
|
||||||
display="flex"
|
display="flex"
|
||||||
@ -95,7 +59,6 @@ export function GroupPersonalSettings(props: {
|
|||||||
<Label mt="2" gray>Send me notifications when this group changes</Label>
|
<Label mt="2" gray>Send me notifications when this group changes</Label>
|
||||||
</Col>
|
</Col>
|
||||||
</BaseLabel>
|
</BaseLabel>
|
||||||
<DeleteGroup association={props.association} owner={owner} api={props.api} />
|
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import { Metadata } from "~/types";
|
||||||
|
import { Col, Row, Text } from "@tlon/indigo-react";
|
||||||
|
import { MetadataIcon } from "./MetadataIcon";
|
||||||
|
|
||||||
|
interface GroupSummaryProps {
|
||||||
|
metadata: Metadata;
|
||||||
|
memberCount: number;
|
||||||
|
channelCount: number;
|
||||||
|
children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GroupSummary(props: GroupSummaryProps) {
|
||||||
|
const { channelCount, memberCount, metadata, children } = props;
|
||||||
|
return (
|
||||||
|
<Col maxWidth="300px" gapY="4">
|
||||||
|
<Row gapX="2">
|
||||||
|
<MetadataIcon
|
||||||
|
borderRadius="1"
|
||||||
|
border="1"
|
||||||
|
borderColor="lightGray"
|
||||||
|
width="40px"
|
||||||
|
height="40px"
|
||||||
|
metadata={metadata}
|
||||||
|
/>
|
||||||
|
<Col justifyContent="space-between">
|
||||||
|
<Text fontSize="1">{metadata.title}</Text>
|
||||||
|
<Row gapX="2" justifyContent="space-between">
|
||||||
|
<Text fontSize="1" gray>
|
||||||
|
{memberCount} participants
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="1" gray>
|
||||||
|
{channelCount} channels
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{metadata.description && <Text fontSize="1">{metadata.description}</Text>}
|
||||||
|
{children}
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
@ -13,6 +13,7 @@ import { Associations } from '~/types/metadata-update';
|
|||||||
import { Dropdown } from '~/views/components/Dropdown';
|
import { Dropdown } from '~/views/components/Dropdown';
|
||||||
import { Workspace } from '~/types';
|
import { Workspace } from '~/types';
|
||||||
import { getTitleFromWorkspace } from '~/logic/lib/workspace';
|
import { getTitleFromWorkspace } from '~/logic/lib/workspace';
|
||||||
|
import {MetadataIcon} from './MetadataIcon';
|
||||||
|
|
||||||
const GroupSwitcherItem = ({ to, children, bottom = false, ...rest }) => (
|
const GroupSwitcherItem = ({ to, children, bottom = false, ...rest }) => (
|
||||||
<Link to={to}>
|
<Link to={to}>
|
||||||
@ -77,15 +78,18 @@ export function GroupSwitcher(props: {
|
|||||||
}) {
|
}) {
|
||||||
const { associations, workspace, isAdmin } = props;
|
const { associations, workspace, isAdmin } = props;
|
||||||
const title = getTitleFromWorkspace(associations, workspace);
|
const title = getTitleFromWorkspace(associations, workspace);
|
||||||
|
const metadata = workspace.type === 'home' ? undefined : associations.contacts[workspace.group].metadata;
|
||||||
const navTo = (to: string) => `${props.baseUrl}${to}`;
|
const navTo = (to: string) => `${props.baseUrl}${to}`;
|
||||||
return (
|
return (
|
||||||
<Box height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" py={3} pl='3' borderBottom='1px solid' borderColor='washedGray'>
|
<Row width="100%" alignItems="center" height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" pl='3' borderBottom='1px solid' borderColor='washedGray'>
|
||||||
<Col
|
<Col
|
||||||
bg="white"
|
bg="white"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
>
|
>
|
||||||
<Row justifyContent="space-between">
|
<Row flexGrow={1} alignItems="center" justifyContent="space-between">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
width="100%"
|
width="auto"
|
||||||
dropWidth="231px"
|
dropWidth="231px"
|
||||||
alignY="top"
|
alignY="top"
|
||||||
options={
|
options={
|
||||||
@ -160,8 +164,9 @@ export function GroupSwitcher(props: {
|
|||||||
</Col>
|
</Col>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Row width='100%' minWidth='0' flexShrink={0}>
|
<Row flexGrow={1} alignItems="center" width='100%' minWidth='0' flexShrink={0}>
|
||||||
<Row justifyContent="space-between" mr={1} flexShrink={0} width='100%' minWidth='0'>
|
{ metadata && <MetadataIcon mr="2" border="1" borderColor="lightGray" borderRadius="1" metadata={metadata} height="24px" width="24px" /> }
|
||||||
|
<Row justifyContent="space-between" mr={1} flexShrink={0} flexGrow={1} minWidth='0'>
|
||||||
<Text lineHeight="1.1" fontSize='2' fontWeight="700" overflow='hidden' display='inline-block' flexShrink='1' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{title}</Text>
|
<Text lineHeight="1.1" fontSize='2' fontWeight="700" overflow='hidden' display='inline-block' flexShrink='1' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{title}</Text>
|
||||||
</Row>
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
@ -185,6 +190,6 @@ export function GroupSwitcher(props: {
|
|||||||
</Row>
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Box>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -32,23 +32,16 @@ export function GroupifyForm(props: GroupifyFormProps) {
|
|||||||
actions: FormikHelpers<FormSchema>
|
actions: FormikHelpers<FormSchema>
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
if (association["app-name"] === "chat") {
|
const rid = association.resource;
|
||||||
await props.api.chat.groupify(
|
const [, , ship, name] = rid;
|
||||||
association["app-path"],
|
await props.api.graph.groupifyGraph(
|
||||||
values.group,
|
ship,
|
||||||
true
|
name,
|
||||||
);
|
values.group || undefined
|
||||||
} else {
|
);
|
||||||
const [, , ship, name] = association["app-path"].split("/");
|
|
||||||
await props.api.graph.groupifyGraph(
|
|
||||||
ship,
|
|
||||||
name,
|
|
||||||
values.group || undefined
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const mod = association.metadata.module || association['app-name'];
|
const mod = association.metadata.module || association['app-name'];
|
||||||
const newGroup = values.group || association['group-path'];
|
const newGroup = values.group || association.group;
|
||||||
history.push(`/~landscape${newGroup}/resource/${mod}${association['app-path']}`);
|
history.push(`/~landscape${newGroup}/resource/${mod}${rid}`);
|
||||||
actions.setStatus({ success: null });
|
actions.setStatus({ success: null });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@ -56,7 +49,7 @@ export function GroupifyForm(props: GroupifyFormProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupPath = props.association?.["group-path"];
|
const groupPath = props.association?.group;
|
||||||
|
|
||||||
const isUnmanaged = props.groups?.[groupPath]?.hidden || false;
|
const isUnmanaged = props.groups?.[groupPath]?.hidden || false;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, ReactNode } from "react";
|
||||||
import {
|
import {
|
||||||
Switch,
|
Switch,
|
||||||
Route,
|
Route,
|
||||||
@ -27,6 +27,7 @@ import "~/views/apps/links/css/custom.css";
|
|||||||
import "~/views/apps/publish/css/custom.css";
|
import "~/views/apps/publish/css/custom.css";
|
||||||
import { Workspace } from "~/types";
|
import { Workspace } from "~/types";
|
||||||
import { getGroupFromWorkspace } from "~/logic/lib/workspace";
|
import { getGroupFromWorkspace } from "~/logic/lib/workspace";
|
||||||
|
import {GroupSummary} from "./GroupSummary";
|
||||||
|
|
||||||
type GroupsPaneProps = StoreState & {
|
type GroupsPaneProps = StoreState & {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@ -70,6 +71,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
api={api}
|
api={api}
|
||||||
s3={props.s3}
|
s3={props.s3}
|
||||||
notificationsGroupConfig={props.notificationsGroupConfig}
|
notificationsGroupConfig={props.notificationsGroupConfig}
|
||||||
|
associations={associations}
|
||||||
|
|
||||||
{...routeProps}
|
{...routeProps}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
@ -191,8 +193,21 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
path={relativePath("")}
|
path={relativePath("")}
|
||||||
render={(routeProps) => {
|
render={(routeProps) => {
|
||||||
const hasDescription = groupAssociation?.metadata?.description;
|
const hasDescription = groupAssociation?.metadata?.description;
|
||||||
const description = (hasDescription && hasDescription !== "")
|
let summary: ReactNode;
|
||||||
? hasDescription : "Create or select a channel to get started"
|
if(groupAssociation?.group) {
|
||||||
|
const memberCount = props.groups[groupAssociation.group].members.size;
|
||||||
|
summary = <GroupSummary
|
||||||
|
memberCount={memberCount}
|
||||||
|
channelCount={0}
|
||||||
|
metadata={groupAssociation.metadata}
|
||||||
|
/>
|
||||||
|
} else {
|
||||||
|
summary = (<Box p="4"><Text fontSize="0" color='gray'>
|
||||||
|
Create or select a channel to get started
|
||||||
|
</Text></Box>);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
const title = groupAssociation?.metadata?.title ?? 'Landscape';
|
const title = groupAssociation?.metadata?.title ?? 'Landscape';
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -206,9 +221,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
display={["none", "flex"]}
|
display={["none", "flex"]}
|
||||||
p='4'
|
p='4'
|
||||||
>
|
>
|
||||||
<Box p="4"><Text fontSize="0" color='gray'>
|
{summary}
|
||||||
{description}
|
|
||||||
</Text></Box>
|
|
||||||
</Col>
|
</Col>
|
||||||
{popovers(routeProps, baseUrl)}
|
{popovers(routeProps, baseUrl)}
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
|
@ -53,7 +53,7 @@ export function InvitePopover(props: InvitePopoverProps) {
|
|||||||
}
|
}
|
||||||
// TODO: how to invite via email?
|
// TODO: how to invite via email?
|
||||||
try {
|
try {
|
||||||
const resource = resourceFromPath(association["group-path"]);
|
const resource = resourceFromPath(association.group);
|
||||||
await ships.reduce(
|
await ships.reduce(
|
||||||
(acc, s) => acc.then(() => api.contacts.invite(resource, `~${deSig(s)}`)),
|
(acc, s) => acc.then(() => api.contacts.invite(resource, `~${deSig(s)}`)),
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
|
@ -2,9 +2,11 @@ import React, { useState, useCallback, useEffect } from "react";
|
|||||||
import { Body } from "~/views/components/Body";
|
import { Body } from "~/views/components/Body";
|
||||||
import {
|
import {
|
||||||
Col,
|
Col,
|
||||||
|
Row,
|
||||||
|
Icon,
|
||||||
Box,
|
Box,
|
||||||
Text,
|
Text,
|
||||||
ManagedTextInputField as Input
|
ManagedTextInputField as Input,
|
||||||
} from "@tlon/indigo-react";
|
} from "@tlon/indigo-react";
|
||||||
import { Formik, Form, FormikHelpers, useFormikContext } from "formik";
|
import { Formik, Form, FormikHelpers, useFormikContext } from "formik";
|
||||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||||
@ -12,8 +14,14 @@ import * as Yup from "yup";
|
|||||||
import { Groups, Rolodex } from "~/types";
|
import { Groups, Rolodex } from "~/types";
|
||||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
import { RouteComponentProps, useHistory } from "react-router-dom";
|
||||||
import urbitOb from "urbit-ob";
|
import urbitOb from "urbit-ob";
|
||||||
|
import { resourceFromPath } from "~/logic/lib/group";
|
||||||
|
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
|
||||||
|
import { uxToHex, getModuleIcon } from "~/logic/lib/util";
|
||||||
|
import { FormError } from "~/views/components/FormError";
|
||||||
|
import { MetadataIcon } from "./MetadataIcon";
|
||||||
|
import { GroupSummary } from "./GroupSummary";
|
||||||
|
|
||||||
const formSchema = Yup.object({
|
const formSchema = Yup.object({
|
||||||
group: Yup.string()
|
group: Yup.string()
|
||||||
@ -35,44 +43,67 @@ interface JoinGroupProps {
|
|||||||
groups: Groups;
|
groups: Groups;
|
||||||
contacts: Rolodex;
|
contacts: Rolodex;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
autojoin: string | null;
|
autojoin?: string;
|
||||||
|
inviteUid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Autojoin(props: { autojoin: string | null; }) {
|
function Autojoin(props: { autojoin: string | null }) {
|
||||||
const { submitForm } = useFormikContext();
|
const { submitForm } = useFormikContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(props.autojoin) {
|
if (props.autojoin) {
|
||||||
submitForm();
|
submitForm();
|
||||||
}
|
}
|
||||||
},[]);
|
}, []);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function JoinGroup(props: JoinGroupProps & RouteComponentProps) {
|
export function JoinGroup(props: JoinGroupProps) {
|
||||||
const { api, history, autojoin } = props;
|
const { api, autojoin } = props;
|
||||||
|
const history = useHistory();
|
||||||
const initialValues: FormSchema = {
|
const initialValues: FormSchema = {
|
||||||
group: autojoin || "",
|
group: autojoin || "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const waiter = useWaitForProps(props);
|
const waiter = useWaitForProps(props);
|
||||||
|
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
||||||
|
|
||||||
|
const onConfirm = useCallback(async () => {
|
||||||
|
const { group } = preview;
|
||||||
|
await api.contacts.join(resourceFromPath(group));
|
||||||
|
if (props.inviteUid) {
|
||||||
|
api.invite.accept("contacts", props.inviteUid);
|
||||||
|
}
|
||||||
|
await waiter(({ contacts, groups }) => {
|
||||||
|
return group in contacts && group in groups;
|
||||||
|
});
|
||||||
|
history.push(`/~landscape${group}`);
|
||||||
|
}, [api, preview, waiter]);
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async (values: FormSchema, actions: FormikHelpers<FormSchema>) => {
|
async (values: FormSchema, actions: FormikHelpers<FormSchema>) => {
|
||||||
try {
|
try {
|
||||||
const [ship, name] = values.group.split("/");
|
const [ship, name] = values.group.split("/");
|
||||||
await api.contacts.join({ ship, name });
|
|
||||||
const path = `/ship/${ship}/${name}`;
|
const path = `/ship/${ship}/${name}`;
|
||||||
await waiter(({ contacts, groups }) => {
|
|
||||||
return path in contacts && path in groups;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const prev = await api.metadata.preview(path);
|
||||||
actions.setStatus({ success: null });
|
actions.setStatus({ success: null });
|
||||||
history.push(`/~landscape${path}`);
|
setPreview(prev);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.log(e);
|
||||||
actions.setStatus({ error: e.message });
|
if (!(e instanceof Error)) {
|
||||||
|
actions.setStatus({ error: "Unknown error" });
|
||||||
|
} else if (e.message === "no-permissions") {
|
||||||
|
actions.setStatus({
|
||||||
|
error:
|
||||||
|
"Unable to join group, you do not have the correct permissions",
|
||||||
|
});
|
||||||
|
} else if (e.message === "offline") {
|
||||||
|
actions.setStatus({
|
||||||
|
error: "Group host is offline, please try again later",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, waiter, history]
|
[api, waiter, history]
|
||||||
@ -80,28 +111,65 @@ export function JoinGroup(props: JoinGroupProps & RouteComponentProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Col overflowY="auto" p="3">
|
<Col width="100%" alignItems="center" overflowY="auto" p="4">
|
||||||
<Box mb={3}>
|
<Box mb={3}>
|
||||||
<Text fontWeight="bold">Join Group</Text>
|
<Text fontSize="2" fontWeight="bold">
|
||||||
|
Join a Group
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Formik
|
{preview ? (
|
||||||
validationSchema={formSchema}
|
<GroupSummary
|
||||||
initialValues={initialValues}
|
metadata={preview.metadata}
|
||||||
onSubmit={onSubmit}
|
memberCount={preview?.members}
|
||||||
>
|
channelCount={preview?.["channel-count"]}
|
||||||
<Form>
|
>
|
||||||
<Autojoin autojoin={autojoin} />
|
<Col
|
||||||
<Col gapY="4">
|
gapY="2"
|
||||||
<Input
|
p="2"
|
||||||
id="group"
|
borderRadius="2"
|
||||||
label="Group"
|
border="1"
|
||||||
caption="What group are you joining?"
|
borderColor="washedGray"
|
||||||
placeholder="~sampel-palnet/test-group"
|
bg="washedBlue"
|
||||||
/>
|
>
|
||||||
<AsyncButton>Join Group</AsyncButton>
|
<Text gray fontSize="1">
|
||||||
|
Channels
|
||||||
|
</Text>
|
||||||
|
{Object.values(preview.channels).map(({ metadata }: any) => (
|
||||||
|
<Row>
|
||||||
|
<Icon
|
||||||
|
mr="2"
|
||||||
|
color="blue"
|
||||||
|
icon={getModuleIcon(metadata.module) as any}
|
||||||
|
/>
|
||||||
|
<Text color="blue">{metadata.title} </Text>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
</Col>
|
</Col>
|
||||||
</Form>
|
<StatelessAsyncButton primary name="join" onClick={onConfirm}>
|
||||||
</Formik>
|
Join {preview.metadata.title}
|
||||||
|
</StatelessAsyncButton>
|
||||||
|
</GroupSummary>
|
||||||
|
) : (
|
||||||
|
<Col width="100%" maxWidth="300px" gapY="4">
|
||||||
|
<Formik
|
||||||
|
validationSchema={formSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
>
|
||||||
|
<Form style={{ display: "contents" }}>
|
||||||
|
<Autojoin autojoin={autojoin} />
|
||||||
|
<Input
|
||||||
|
id="group"
|
||||||
|
label="Group"
|
||||||
|
caption="What group are you joining?"
|
||||||
|
placeholder="~sampel-palnet/test-group"
|
||||||
|
/>
|
||||||
|
<AsyncButton mt="4">Join Group</AsyncButton>
|
||||||
|
<FormError mt="4" />
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Box, Image } from "@tlon/indigo-react";
|
||||||
|
|
||||||
|
import { uxToHex } from "~/logic/lib/util";
|
||||||
|
import { Metadata } from "~/types";
|
||||||
|
import { PropFunc } from "~/types/util";
|
||||||
|
|
||||||
|
type MetadataIconProps = PropFunc<typeof Box> & {
|
||||||
|
metadata: Metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function MetadataIcon(props: MetadataIconProps) {
|
||||||
|
const { metadata, ...rest } = props;
|
||||||
|
|
||||||
|
const bgColor = metadata.picture ? {} : { bg: `#${uxToHex(metadata.color)}` };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box {...bgColor} {...rest}>
|
||||||
|
{metadata.picture && <Image height="100%" src={metadata.picture} />}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -10,7 +10,7 @@ import {
|
|||||||
import { Formik, Form, FormikHelpers } from "formik";
|
import { Formik, Form, FormikHelpers } from "formik";
|
||||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import { Groups, Rolodex, GroupPolicy, Enc } from "~/types";
|
import { Groups, Rolodex, GroupPolicy, Enc, Associations } from "~/types";
|
||||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { stringToSymbol } from "~/logic/lib/util";
|
import { stringToSymbol } from "~/logic/lib/util";
|
||||||
@ -31,6 +31,7 @@ interface FormSchema {
|
|||||||
interface NewGroupProps {
|
interface NewGroupProps {
|
||||||
groups: Groups;
|
groups: Groups;
|
||||||
contacts: Rolodex;
|
contacts: Rolodex;
|
||||||
|
associations: Associations;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +64,8 @@ export function NewGroup(props: NewGroupProps & RouteComponentProps) {
|
|||||||
};
|
};
|
||||||
await api.contacts.create(name, policy, title, description);
|
await api.contacts.create(name, policy, title, description);
|
||||||
const path = `/ship/~${window.ship}/${name}`;
|
const path = `/ship/~${window.ship}/${name}`;
|
||||||
await waiter(({ contacts, groups }) => {
|
await waiter(({ contacts, groups, associations }) => {
|
||||||
return path in contacts && path in groups;
|
return path in contacts && path in groups && path in associations.contacts;
|
||||||
});
|
});
|
||||||
|
|
||||||
actions.setStatus({ success: null });
|
actions.setStatus({ success: null });
|
||||||
|
@ -270,26 +270,26 @@ function Participant(props: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onPromote = useCallback(async () => {
|
const onPromote = useCallback(async () => {
|
||||||
const resource = resourceFromPath(association['group-path']);
|
const resource = resourceFromPath(association.group);
|
||||||
await api.groups.addTag(resource, { tag: 'admin' }, [`~${contact.patp}`]);
|
await api.groups.addTag(resource, { tag: 'admin' }, [`~${contact.patp}`]);
|
||||||
}, [api, association]);
|
}, [api, association]);
|
||||||
|
|
||||||
const onDemote = useCallback(async () => {
|
const onDemote = useCallback(async () => {
|
||||||
const resource = resourceFromPath(association['group-path']);
|
const resource = resourceFromPath(association.group);
|
||||||
await api.groups.removeTag(resource, { tag: 'admin' }, [
|
await api.groups.removeTag(resource, { tag: 'admin' }, [
|
||||||
`~${contact.patp}`
|
`~${contact.patp}`
|
||||||
]);
|
]);
|
||||||
}, [api, association]);
|
}, [api, association]);
|
||||||
|
|
||||||
const onBan = useCallback(async () => {
|
const onBan = useCallback(async () => {
|
||||||
const resource = resourceFromPath(association['group-path']);
|
const resource = resourceFromPath(association.group);
|
||||||
await api.groups.changePolicy(resource, {
|
await api.groups.changePolicy(resource, {
|
||||||
open: { banShips: [`~${contact.patp}`] }
|
open: { banShips: [`~${contact.patp}`] }
|
||||||
});
|
});
|
||||||
}, [api, association]);
|
}, [api, association]);
|
||||||
|
|
||||||
const onKick = useCallback(async () => {
|
const onKick = useCallback(async () => {
|
||||||
const resource = resourceFromPath(association['group-path']);
|
const resource = resourceFromPath(association.group);
|
||||||
await api.groups.remove(resource, [`~${contact.patp}`]);
|
await api.groups.remove(resource, [`~${contact.patp}`]);
|
||||||
}, [api, association]);
|
}, [api, association]);
|
||||||
|
|
||||||
|
@ -7,12 +7,15 @@ import { Contacts, Contact } from "~/types/contact-update";
|
|||||||
import { Group } from "~/types/group-update";
|
import { Group } from "~/types/group-update";
|
||||||
import { Association } from "~/types/metadata-update";
|
import { Association } from "~/types/metadata-update";
|
||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import {GroupNotificationsConfig, S3State} from "~/types";
|
import { GroupNotificationsConfig, S3State, Associations } from "~/types";
|
||||||
|
|
||||||
import { GroupSettings } from "./GroupSettings/GroupSettings";
|
import { GroupSettings } from "./GroupSettings/GroupSettings";
|
||||||
import { Participants } from "./Participants";
|
import { Participants } from "./Participants";
|
||||||
|
import {useHashLink} from "~/logic/lib/useHashLink";
|
||||||
|
import {DeleteGroup} from "./DeleteGroup";
|
||||||
|
import {resourceFromPath} from "~/logic/lib/group";
|
||||||
|
|
||||||
const SidebarItem = ({ selected, icon, text, to }) => {
|
const SidebarItem = ({ selected, icon, text, to, children = null }) => {
|
||||||
return (
|
return (
|
||||||
<HoverBoxLink
|
<HoverBoxLink
|
||||||
to={to}
|
to={to}
|
||||||
@ -20,11 +23,15 @@ const SidebarItem = ({ selected, icon, text, to }) => {
|
|||||||
bg="white"
|
bg="white"
|
||||||
bgActive="washedGray"
|
bgActive="washedGray"
|
||||||
display="flex"
|
display="flex"
|
||||||
px={3}
|
px="3"
|
||||||
py={1}
|
py="1"
|
||||||
|
justifyContent="space-between"
|
||||||
>
|
>
|
||||||
<Icon icon={icon} mr='2'/>
|
<Row>
|
||||||
<Text color={selected ? "black" : "gray"}>{text}</Text>
|
<Icon icon={icon} mr='2'/>
|
||||||
|
<Text color={selected ? "black" : "gray"}>{text}</Text>
|
||||||
|
</Row>
|
||||||
|
{children}
|
||||||
</HoverBoxLink>
|
</HoverBoxLink>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -35,6 +42,7 @@ export function PopoverRoutes(
|
|||||||
contacts: Contacts;
|
contacts: Contacts;
|
||||||
group: Group;
|
group: Group;
|
||||||
association: Association;
|
association: Association;
|
||||||
|
associations: Associations;
|
||||||
s3: S3State;
|
s3: S3State;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
notificationsGroupConfig: GroupNotificationsConfig;
|
notificationsGroupConfig: GroupNotificationsConfig;
|
||||||
@ -49,6 +57,14 @@ export function PopoverRoutes(
|
|||||||
}, [props.history.push, props.baseUrl]);
|
}, [props.history.push, props.baseUrl]);
|
||||||
useOutsideClick(innerRef, onOutsideClick);
|
useOutsideClick(innerRef, onOutsideClick);
|
||||||
|
|
||||||
|
useHashLink();
|
||||||
|
|
||||||
|
const groupSize = props.group.members.size;
|
||||||
|
|
||||||
|
const owner = resourceFromPath(props.association.group).ship.slice(1) === window.ship;
|
||||||
|
|
||||||
|
const admin = props.group?.tags?.role?.admin.has(window.ship) || false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
@ -85,28 +101,43 @@ export function PopoverRoutes(
|
|||||||
>
|
>
|
||||||
<Col
|
<Col
|
||||||
display={!!view ? ["none", "flex"] : "flex"}
|
display={!!view ? ["none", "flex"] : "flex"}
|
||||||
py={3}
|
|
||||||
borderRight={1}
|
borderRight={1}
|
||||||
borderRightColor="washedGray"
|
borderRightColor="washedGray"
|
||||||
>
|
>
|
||||||
<SidebarItem
|
<Text my="4" mx="3" fontWeight="600" fontSize="2">Group Settings</Text>
|
||||||
icon="Node"
|
<Col gapY="2">
|
||||||
selected={view === "participants"}
|
<Text my="1" mx="3" gray>Group</Text>
|
||||||
to={relativeUrl("/participants")}
|
<SidebarItem
|
||||||
text="Participants"
|
icon="Inbox"
|
||||||
/>
|
to={relativeUrl("/settings#notifications")}
|
||||||
<SidebarItem
|
text="Notifications"
|
||||||
icon="Gear"
|
/>
|
||||||
selected={view === "settings"}
|
<SidebarItem
|
||||||
to={relativeUrl("/settings")}
|
icon="Users"
|
||||||
text="Group Settings"
|
to={relativeUrl("/participants")}
|
||||||
/>
|
text="Participants"
|
||||||
<SidebarItem
|
selected={view === "participants"}
|
||||||
icon="Smiley"
|
><Text gray>{groupSize}</Text>
|
||||||
selected={view === "profile"}
|
</SidebarItem>
|
||||||
to={relativeUrl("/profile")}
|
{ admin && (
|
||||||
text="Group Profile"
|
<>
|
||||||
/>
|
<Box pt="3" mb="1" mx="3">
|
||||||
|
<Text gray>Administration</Text>
|
||||||
|
</Box>
|
||||||
|
<SidebarItem
|
||||||
|
icon="Groups"
|
||||||
|
to={relativeUrl("/settings#group-details")}
|
||||||
|
text="Group Details"
|
||||||
|
/>
|
||||||
|
<SidebarItem
|
||||||
|
icon="Spaces"
|
||||||
|
to={relativeUrl("/settings#channels")}
|
||||||
|
text="Channel Management"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<DeleteGroup owner={owner} api={props.api} association={props.association} />
|
||||||
|
</Col>
|
||||||
</Col>
|
</Col>
|
||||||
<Box
|
<Box
|
||||||
gridArea={"1 / 1 / 2 / 2"}
|
gridArea={"1 / 1 / 2 / 2"}
|
||||||
@ -120,10 +151,13 @@ export function PopoverRoutes(
|
|||||||
<Box overflow="hidden">
|
<Box overflow="hidden">
|
||||||
{view === "settings" && (
|
{view === "settings" && (
|
||||||
<GroupSettings
|
<GroupSettings
|
||||||
|
baseUrl={`${props.baseUrl}/popover`}
|
||||||
group={props.group}
|
group={props.group}
|
||||||
association={props.association}
|
association={props.association}
|
||||||
api={props.api}
|
api={props.api}
|
||||||
notificationsGroupConfig={props.notificationsGroupConfig}
|
notificationsGroupConfig={props.notificationsGroupConfig}
|
||||||
|
associations={props.associations}
|
||||||
|
s3={props.s3}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{view === "participants" && (
|
{view === "participants" && (
|
||||||
|
@ -27,12 +27,13 @@ type ResourceProps = StoreState & {
|
|||||||
} & RouteComponentProps;
|
} & RouteComponentProps;
|
||||||
|
|
||||||
export function Resource(props: ResourceProps) {
|
export function Resource(props: ResourceProps) {
|
||||||
const { association, api } = props;
|
const { association, api, notificationsGraphConfig } = props;
|
||||||
const app = association.metadata.module || association["app-name"];
|
const app = association.metadata.module || association["app-name"];
|
||||||
const appPath = association["app-path"];
|
const rid = association.resource;
|
||||||
const selectedGroup = association["group-path"];
|
const selectedGroup = association.group;
|
||||||
const relativePath = (p: string) =>
|
const relativePath = (p: string) =>
|
||||||
`${props.baseUrl}/resource/${app}${appPath}${p}`;
|
|
||||||
|
`${props.baseUrl}/resource/${app}${rid}${p}`;
|
||||||
const skelProps = { api, association };
|
const skelProps = { api, association };
|
||||||
let title = props.association.metadata.title;
|
let title = props.association.metadata.title;
|
||||||
if ('workspace' in props) {
|
if ('workspace' in props) {
|
||||||
|
@ -35,12 +35,12 @@ type ResourceSkeletonProps = {
|
|||||||
export function ResourceSkeleton(props: ResourceSkeletonProps) {
|
export function ResourceSkeleton(props: ResourceSkeletonProps) {
|
||||||
const { association, api, baseUrl, children, atRoot, groupTags } = props;
|
const { association, api, baseUrl, children, atRoot, groupTags } = props;
|
||||||
const app = association?.metadata?.module || association["app-name"];
|
const app = association?.metadata?.module || association["app-name"];
|
||||||
const appPath = association["app-path"];
|
const rid = association.resource;
|
||||||
const workspace =
|
const workspace =
|
||||||
baseUrl === "/~landscape/home" ? "/home" : association["group-path"];
|
baseUrl === "/~landscape/home" ? "/home" : association.group;
|
||||||
const title = props.title || association?.metadata?.title;
|
const title = props.title || association?.metadata?.title;
|
||||||
|
|
||||||
const [, , ship, resource] = appPath.split("/");
|
const [, , ship, resource] = rid.split("/");
|
||||||
|
|
||||||
const resourcePath = (p: string) => baseUrl + `/resource/${app}/ship/${ship}/${resource}` + p;
|
const resourcePath = (p: string) => baseUrl + `/resource/${app}/ship/${ship}/${resource}` + p;
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
|
|||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box color="blue" pr={2} mr={2}>
|
<Box color="blue" pr={2} mr={2}>
|
||||||
<Link to={`/~landscape${workspace}/resource/${app}${appPath}`}>
|
<Link to={`/~landscape${workspace}/resource/${app}${rid}`}>
|
||||||
<Text color="blue">Go back to channel</Text>
|
<Text color="blue">Go back to channel</Text>
|
||||||
</Link>
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
@ -116,7 +116,6 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
|
|||||||
)}
|
)}
|
||||||
<ChannelMenu
|
<ChannelMenu
|
||||||
graphNotificationConfig={props.notificationsGraphConfig}
|
graphNotificationConfig={props.notificationsGraphConfig}
|
||||||
chatNotificationConfig={props.notificationsChatConfig}
|
|
||||||
association={association}
|
association={association}
|
||||||
api={api}
|
api={api}
|
||||||
/>
|
/>
|
||||||
|
@ -7,7 +7,7 @@ import { SidebarAppConfigs, SidebarItemStatus } from "./Sidebar";
|
|||||||
import { HoverBoxLink } from "~/views/components/HoverBox";
|
import { HoverBoxLink } from "~/views/components/HoverBox";
|
||||||
import { Groups, Association } from "~/types";
|
import { Groups, Association } from "~/types";
|
||||||
|
|
||||||
import { cite } from "~/logic/lib/util";
|
import { cite, getModuleIcon } from "~/logic/lib/util";
|
||||||
|
|
||||||
function SidebarItemIndicator(props: { status?: SidebarItemStatus }) {
|
function SidebarItemIndicator(props: { status?: SidebarItemStatus }) {
|
||||||
switch (props.status) {
|
switch (props.status) {
|
||||||
@ -24,27 +24,19 @@ function SidebarItemIndicator(props: { status?: SidebarItemStatus }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAppIcon = (app: string, mod: string) => {
|
;
|
||||||
if (app === "graph") {
|
|
||||||
if (mod === "link") {
|
|
||||||
return "Collection";
|
|
||||||
}
|
|
||||||
return _.capitalize(mod);
|
|
||||||
}
|
|
||||||
return _.capitalize(app);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/;
|
const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/;
|
||||||
function getItemTitle(association: Association) {
|
function getItemTitle(association: Association) {
|
||||||
if(DM_REGEX.test(association['app-path'])) {
|
if(DM_REGEX.test(association.resource)) {
|
||||||
const [,,ship,name] = association['app-path'].split('/');
|
const [,,ship,name] = association.resource.split('/');
|
||||||
if(ship.slice(1) === window.ship) {
|
if(ship.slice(1) === window.ship) {
|
||||||
return cite(`~${name.slice(4)}`);
|
return cite(`~${name.slice(4)}`);
|
||||||
}
|
}
|
||||||
return cite(ship);
|
return cite(ship);
|
||||||
|
|
||||||
}
|
}
|
||||||
return association.metadata.title || association['app-path'];
|
return association.metadata.title || association.resource
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SidebarItem(props: {
|
export function SidebarItem(props: {
|
||||||
@ -59,8 +51,8 @@ export function SidebarItem(props: {
|
|||||||
const title = getItemTitle(association);
|
const title = getItemTitle(association);
|
||||||
const appName = association?.["app-name"];
|
const appName = association?.["app-name"];
|
||||||
const mod = association?.metadata?.module || appName;
|
const mod = association?.metadata?.module || appName;
|
||||||
const appPath = association?.["app-path"];
|
const rid = association?.resource
|
||||||
const groupPath = association?.["group-path"];
|
const groupPath = association?.group;
|
||||||
const app = apps[appName];
|
const app = apps[appName];
|
||||||
const isUnmanaged = groups?.[groupPath]?.hidden || false;
|
const isUnmanaged = groups?.[groupPath]?.hidden || false;
|
||||||
if (!app) {
|
if (!app) {
|
||||||
@ -74,8 +66,8 @@ export function SidebarItem(props: {
|
|||||||
const baseUrl = isUnmanaged ? `/~landscape/home` : `/~landscape${groupPath}`;
|
const baseUrl = isUnmanaged ? `/~landscape/home` : `/~landscape${groupPath}`;
|
||||||
|
|
||||||
const to = isSynced
|
const to = isSynced
|
||||||
? `${baseUrl}/resource/${mod}${appPath}`
|
? `${baseUrl}/resource/${mod}${rid}`
|
||||||
: `${baseUrl}/join/${mod}${appPath}`;
|
: `${baseUrl}/join/${mod}${rid}`;
|
||||||
|
|
||||||
const color = selected ? "black" : isSynced ? "gray" : "lightGray";
|
const color = selected ? "black" : isSynced ? "gray" : "lightGray";
|
||||||
|
|
||||||
@ -101,7 +93,7 @@ export function SidebarItem(props: {
|
|||||||
<Icon
|
<Icon
|
||||||
display="block"
|
display="block"
|
||||||
color={color}
|
color={color}
|
||||||
icon={getAppIcon(appName, mod) as any}
|
icon={getModuleIcon(mod) as any}
|
||||||
/>
|
/>
|
||||||
<Box width='100%' flexShrink={2} ml={2} display='flex' overflow='hidden'>
|
<Box width='100%' flexShrink={2} ml={2} display='flex' overflow='hidden'>
|
||||||
<Text
|
<Text
|
||||||
|
@ -56,8 +56,8 @@ export function SidebarList(props: {
|
|||||||
.filter((a) => {
|
.filter((a) => {
|
||||||
const assoc = associations[a];
|
const assoc = associations[a];
|
||||||
return group
|
return group
|
||||||
? assoc["group-path"] === group
|
? assoc.group === group
|
||||||
: !(assoc["group-path"] in props.associations.contacts);
|
: !(assoc.group in props.associations.contacts);
|
||||||
})
|
})
|
||||||
.sort(sidebarSort(associations, props.apps)[config.sortBy]);
|
.sort(sidebarSort(associations, props.apps)[config.sortBy]);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user