mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-14 17:41:33 +03:00
Merge pull request #2937 from urbit/lf/groups-refactor
groups refactor: one store to rule them all
This commit is contained in:
commit
7c4d754397
@ -9,10 +9,11 @@
|
||||
:: we concat the ship onto the head of the path,
|
||||
:: and trust it to take care of the rest.
|
||||
::
|
||||
/- view=chat-view, hook=chat-hook,
|
||||
/- view=chat-view, hook=chat-hook, *group,
|
||||
*permission-store, *group-store, *invite-store,
|
||||
*rw-security, sole
|
||||
/+ shoe, default-agent, verb, dbug, store=chat-store
|
||||
sole
|
||||
/+ shoe, default-agent, verb, dbug, store=chat-store,
|
||||
group-store, grpl=group, resource
|
||||
::
|
||||
|%
|
||||
+$ card card:shoe
|
||||
@ -195,6 +196,7 @@
|
||||
--
|
||||
::
|
||||
|_ =bowl:gall
|
||||
++ grp ~(. grpl bowl)
|
||||
:: +prep: setup & state adapter
|
||||
::
|
||||
++ prep
|
||||
@ -744,10 +746,10 @@
|
||||
=/ with-group=? ?=(%village-with-group security)
|
||||
=/ =target [with-group our-self path]
|
||||
=/ real-path=^path (target-to-path target)
|
||||
=/ =rw-security
|
||||
=/ =policy
|
||||
?- security
|
||||
%channel %channel
|
||||
?(%village %village-with-group) %village
|
||||
%channel *open:policy
|
||||
?(%village %village-with-group) *invite:policy
|
||||
==
|
||||
?^ (scry-for (unit mailbox:store) %chat-store [%mailbox real-path])
|
||||
=- [[- ~] state]
|
||||
@ -766,9 +768,10 @@
|
||||
''
|
||||
real-path :: chat
|
||||
real-path :: group
|
||||
rw-security
|
||||
policy
|
||||
~
|
||||
(fall allow-history %.y)
|
||||
with-group
|
||||
==
|
||||
:: +delete: delete local chats
|
||||
::
|
||||
@ -798,30 +801,30 @@
|
||||
:: if they weren't permitted before, some hook will send an invite.
|
||||
:: but if they already were, we want to send an invite ourselves.
|
||||
::
|
||||
?. %^ scry-for ?
|
||||
%permission-store
|
||||
[%permitted (scot %p ship) real-path]
|
||||
?. (is-member:grp ship real-path)
|
||||
~
|
||||
`(invite-card real-path ship)
|
||||
:: whitelist: empty if no matching permission, else true if whitelist
|
||||
::
|
||||
=/ whitelist=(unit ?)
|
||||
=; perm=(unit permission)
|
||||
?~(perm ~ `?=(%white kind.u.perm))
|
||||
=; grp=(unit ^group)
|
||||
?~(grp ~ `?=(%open -.u.grp))
|
||||
::TODO +permission-of-target?
|
||||
%^ scry-for (unit permission)
|
||||
%permission-store
|
||||
[%permission real-path]
|
||||
%^ scry-for (unit ^group)
|
||||
%group-store
|
||||
`^path`[%groups real-path]
|
||||
?~ whitelist
|
||||
~& [%weird-no-permission real-path]
|
||||
~
|
||||
=/ rid=resource
|
||||
(de-path:resource real-path)
|
||||
%- some
|
||||
%^ act %do-permission %group-store
|
||||
:- %group-action
|
||||
!> ^- group-action
|
||||
!> ^- action:group-store
|
||||
?: =(u.whitelist allow)
|
||||
[%add ships real-path]
|
||||
[%remove ships real-path]
|
||||
[%add-members rid ships]
|
||||
[%remove-members rid ships]
|
||||
:: +join: sync with remote mailbox
|
||||
::
|
||||
++ join
|
||||
|
@ -5,8 +5,10 @@
|
||||
/- *permission-store, *invite-store, *metadata-store,
|
||||
*permission-hook, *group-store, *permission-group-hook, ::TMP for upgrade
|
||||
hook=chat-hook,
|
||||
view=chat-view
|
||||
/+ default-agent, verb, dbug, store=chat-store
|
||||
view=chat-view,
|
||||
*group
|
||||
/+ default-agent, verb, dbug, store=chat-store, group-store, grpl=group,
|
||||
resource
|
||||
~% %chat-hook-top ..is ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
@ -15,13 +17,18 @@
|
||||
$% state-0
|
||||
state-1
|
||||
state-2
|
||||
state-3
|
||||
==
|
||||
::
|
||||
+$ state-3
|
||||
$: %3
|
||||
state-base
|
||||
==
|
||||
::
|
||||
+$ state-2
|
||||
$: %2
|
||||
state-base
|
||||
==
|
||||
::
|
||||
+$ state-1
|
||||
$: %1
|
||||
loaded-cards=*
|
||||
@ -45,7 +52,7 @@
|
||||
$% [%chat-update update:store]
|
||||
==
|
||||
--
|
||||
=| state-2
|
||||
=| state-3
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
@ -64,7 +71,7 @@
|
||||
:_ this(invite-created %.y)
|
||||
:~ (invite-poke:cc [%create /chat])
|
||||
[%pass /invites %agent [our.bol %invite-store] %watch /invitatory/chat]
|
||||
[%pass /permissions %agent [our.bol %permission-store] %watch /updates]
|
||||
watch-groups:cc
|
||||
==
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
@ -72,30 +79,101 @@
|
||||
^- (quip card _this)
|
||||
|^
|
||||
=/ old !<(versioned-state old-vase)
|
||||
=^ moves state
|
||||
^- (quip card state-2)
|
||||
?: ?=(%2 -.old)
|
||||
^- (quip card state-2)
|
||||
`old
|
||||
::
|
||||
?: ?=(%1 -.old)
|
||||
^- (quip card state-2)
|
||||
:_ [%2 +>.old]
|
||||
=| cards=(list card)
|
||||
|-
|
||||
?: ?=(%3 -.old)
|
||||
[cards this(state old)]
|
||||
?: ?=(%2 -.old)
|
||||
=. cards
|
||||
%+ weld cards
|
||||
:~ watch-groups:cc
|
||||
[%pass /permissions %agent [our.bol %permission-store] %leave ~]
|
||||
==
|
||||
=^ new-cards=(list card) old
|
||||
=| crds=(list card)
|
||||
=/ syncs
|
||||
~(tap by synced.old)
|
||||
|-
|
||||
?~ syncs
|
||||
[crds old]
|
||||
=/ [pax=path =ship]
|
||||
i.syncs
|
||||
?> ?=(^ pax)
|
||||
?. =('~' i.pax)
|
||||
$(syncs t.syncs)
|
||||
=/ new-path=path
|
||||
t.pax
|
||||
=. synced.old
|
||||
(~(del by synced.old) pax)
|
||||
?. =(ship our.bol)
|
||||
=. synced.old
|
||||
(~(put by synced.old) new-path ship)
|
||||
$(syncs t.syncs)
|
||||
=/ history=?
|
||||
(~(gut by allow-history.old) pax %.y)
|
||||
=. allow-history.old
|
||||
(~(del by allow-history.old) pax)
|
||||
=. allow-history.old
|
||||
(~(put by allow-history.old) new-path history)
|
||||
=. crds
|
||||
%+ weld crds
|
||||
:- (add-owned new-path history)
|
||||
(kick-old-subs pax)
|
||||
$(syncs t.syncs)
|
||||
=. cards
|
||||
(weld cards new-cards)
|
||||
$(-.old %3)
|
||||
::
|
||||
?: ?=(%1 -.old)
|
||||
=. cards
|
||||
%+ welp cards
|
||||
^- (list card)
|
||||
%+ murn ~(tap by wex.bol)
|
||||
|= [[=wire =ship =term] *]
|
||||
^- (unit card)
|
||||
?. &(?=([%mailbox *] wire) =(our.bol ship) =(%chat-store term))
|
||||
~
|
||||
`[%pass wire %agent [our.bol %chat-store] %leave ~]
|
||||
^- (quip card state-2)
|
||||
:: path structure ugprade logic
|
||||
$(old [%2 +>.old])
|
||||
:: path structure ugprade logic
|
||||
::
|
||||
=/ keys=(set path) (scry:cc (set path) %chat-store /keys)
|
||||
%= $
|
||||
-.old %2
|
||||
::
|
||||
=/ keys=(set path) (scry:cc (set path) %chat-store /keys)
|
||||
:_ [%2 +.old]
|
||||
cards
|
||||
%- zing
|
||||
^- (list (list card))
|
||||
(turn ~(tap in keys) generate-cards)
|
||||
[moves this]
|
||||
==
|
||||
++ kick-old-subs
|
||||
|= old-path=path
|
||||
^- (list card)
|
||||
?> ?=(^ old-path)
|
||||
?. =('~' i.old-path)
|
||||
~
|
||||
[%give %kick ~[mailbox+old-path] ~]~
|
||||
::
|
||||
++ add-members-group
|
||||
|= [=path ships=(set ship)]
|
||||
^- card
|
||||
?> ?=([@ @ ~] path)
|
||||
=/ rid=resource
|
||||
[(slav %p i.path) i.t.path]
|
||||
=- [%pass / %agent [our.bol %group-store] %poke %group-action -]
|
||||
!>(`action:group-store`[%add-members rid ships])
|
||||
::
|
||||
++ add-synced
|
||||
|= [=ship =path]
|
||||
^- card
|
||||
=- [%pass / %agent [our.bol %chat-hook] %poke %chat-hook-action -]
|
||||
!>(`action:hook`[%add-synced ship path %.y])
|
||||
::
|
||||
++ add-owned
|
||||
|= [=path history=?]
|
||||
^- card
|
||||
=- [%pass / %agent [our.bol %chat-hook] %poke %chat-hook-action -]
|
||||
!>(`action:hook`[%add-owned path history])
|
||||
::
|
||||
++ generate-cards
|
||||
|= old-chat=path
|
||||
@ -177,8 +255,8 @@
|
||||
?: =(our.bol host)
|
||||
%^ make-poke %group-store
|
||||
%group-action
|
||||
!> ^- group-action
|
||||
[%unbundle group]
|
||||
!> ^- action:group-store
|
||||
[%remove-group (de-path:resource group) ~]
|
||||
:: else, just delete the sync in the hook
|
||||
::
|
||||
%^ make-poke %permission-hook
|
||||
@ -189,15 +267,17 @@
|
||||
++ create-group
|
||||
|= [group=path who=(set ship)]
|
||||
^- (list card)
|
||||
=/ rid=resource
|
||||
(de-path:resource group)
|
||||
:~ %^ make-poke %group-store
|
||||
%group-action
|
||||
!> ^- group-action
|
||||
[%bundle group]
|
||||
!> ^- action:group-store
|
||||
[%add-group rid *invite:policy %.n]
|
||||
::
|
||||
%^ make-poke %group-store
|
||||
%group-action
|
||||
!> ^- group-action
|
||||
[%add who group]
|
||||
!> ^- action:group-store
|
||||
[%add-members rid who]
|
||||
==
|
||||
::
|
||||
++ hookup-group
|
||||
@ -285,9 +365,9 @@
|
||||
(fact-invite-update:cc wire !<(invite-update q.cage.sign))
|
||||
[cards this]
|
||||
::
|
||||
%permission-update
|
||||
%group-update
|
||||
=^ cards state
|
||||
(fact-permission-update:cc wire !<(permission-update q.cage.sign))
|
||||
(fact-group-update:cc wire !<(update:group-store q.cage.sign))
|
||||
[cards this]
|
||||
==
|
||||
==
|
||||
@ -301,6 +381,7 @@
|
||||
::
|
||||
~% %chat-hook-library ..card ~
|
||||
|_ bol=bowl:gall
|
||||
++ grp ~(. grpl bol)
|
||||
::
|
||||
++ poke-json
|
||||
|= jon=json
|
||||
@ -328,7 +409,7 @@
|
||||
?~ ship ~
|
||||
?. =(u.ship our.bol) ~
|
||||
:: check if write is permitted
|
||||
?. (is-permitted src.bol path.act) ~
|
||||
?. (is-member:grp src.bol (group-from-chat path.act)) ~
|
||||
=: author.envelope.act src.bol
|
||||
when.envelope.act now.bol
|
||||
==
|
||||
@ -403,7 +484,7 @@
|
||||
?> ?=(^ pax)
|
||||
?> (~(has by synced) pax)
|
||||
:: check if read is permitted
|
||||
?> (is-permitted src.bol pax)
|
||||
?> (is-member:grp src.bol (group-from-chat pax))
|
||||
=/ box (chat-scry pax)
|
||||
?~ box !!
|
||||
[%give %fact ~ %chat-update !>([%create pax])]~
|
||||
@ -416,8 +497,7 @@
|
||||
=/ backlog-latest=(unit @ud) (rush (snag last `(list @ta)`pax) dem:ag)
|
||||
=/ pas `path`(oust [last 1] `(list @ta)`pax)
|
||||
?> ?=([* ^] pas)
|
||||
?> (~(has by synced) pas)
|
||||
?> (is-permitted src.bol pas)
|
||||
?> (is-member:grp src.bol (group-from-chat pas))
|
||||
=/ envs envelopes:(need (chat-scry pas))
|
||||
=/ length (lent envs)
|
||||
=/ latest
|
||||
@ -445,51 +525,30 @@
|
||||
~[(chat-view-poke [%join shp app-path ask-history])]
|
||||
==
|
||||
::
|
||||
++ fact-permission-update
|
||||
|= [wir=wire fact=permission-update]
|
||||
++ fact-group-update
|
||||
|= [wir=wire =update:group-store]
|
||||
^- (quip card _state)
|
||||
|^
|
||||
:_ state
|
||||
?+ -.fact ~
|
||||
%add (handle-permissions [%add path.fact who.fact])
|
||||
%remove (handle-permissions [%remove path.fact who.fact])
|
||||
==
|
||||
::
|
||||
++ handle-permissions
|
||||
|= [kind=?(%add %remove) pax=path who=(set ship)]
|
||||
^- (list card)
|
||||
%- zing
|
||||
%+ turn
|
||||
(chats-of-group pax)
|
||||
|= chat=path
|
||||
^- (list card)
|
||||
=/ owner (~(get by synced.state) chat)
|
||||
?~ owner ~
|
||||
?. =(u.owner our.bol) ~
|
||||
%- zing
|
||||
%+ turn ~(tap in who)
|
||||
|= =ship
|
||||
?: (is-permitted ship chat)
|
||||
?: ?|(=(kind %remove) =(ship our.bol) (is-managed pax)) ~
|
||||
:: if ship has just been added to the permitted group,
|
||||
:: send them an invite
|
||||
~[(send-invite chat ship)]
|
||||
:: if ship is not permitted, kick their subscription
|
||||
[%give %kick [%mailbox chat]~ `ship]~
|
||||
::
|
||||
++ send-invite
|
||||
|= [=path =ship]
|
||||
^- card
|
||||
=/ =invite [our.bol %chat-hook path ship '']
|
||||
=/ act=invite-action [%invite /chat (shaf %msg-uid eny.bol) invite]
|
||||
[%pass / %agent [our.bol %invite-hook] %poke %invite-action !>(act)]
|
||||
::
|
||||
++ is-managed
|
||||
|= =path
|
||||
^- ?
|
||||
?> ?=(^ path)
|
||||
!=(i.path '~')
|
||||
--
|
||||
?. ?=(%remove-members -.update)
|
||||
~
|
||||
=/ =path
|
||||
(en-path:resource resource.update)
|
||||
=/ chats
|
||||
(chats-of-group path)
|
||||
%- zing
|
||||
%+ turn
|
||||
chats
|
||||
|= chat=^path
|
||||
^- (list card)
|
||||
=/ owner
|
||||
(~(get by synced) chat)
|
||||
?~ owner ~
|
||||
?. =(u.owner our.bol)
|
||||
~
|
||||
%+ turn
|
||||
~(tap in ships.update)
|
||||
|= =ship
|
||||
[%give %kick [%mailbox chat]~ `ship]
|
||||
::
|
||||
++ fact-chat-update
|
||||
|= [wir=wire =update:store]
|
||||
@ -568,8 +627,12 @@
|
||||
[%pass /permissions %agent [our.bol %permission-store] %watch /updates]~
|
||||
::
|
||||
?+ wir !!
|
||||
[%groups ~] [~[watch-groups] state]
|
||||
::
|
||||
[%store @ *]
|
||||
~& store-kick+wir
|
||||
?: =('~' i.t.wir)
|
||||
(migrate-store t.t.wir)
|
||||
?. (~(has by synced) t.wir) [~ state]
|
||||
~& %chat-store-resubscribe
|
||||
=/ mailbox=(unit mailbox:store)
|
||||
@ -579,6 +642,8 @@
|
||||
::
|
||||
[%mailbox @ *]
|
||||
~& mailbox-kick+wir
|
||||
?: =('~' i.t.wir)
|
||||
(migrate-listen t.t.wir)
|
||||
?. (~(has by synced) t.wir) [~ state]
|
||||
~& %chat-hook-resubscribe
|
||||
=/ =ship (~(got by synced) t.wir)
|
||||
@ -591,6 +656,9 @@
|
||||
::
|
||||
[%backlog @ @ *]
|
||||
=/ chat=path (oust [(dec (lent t.wir)) 1] `(list @ta)`t.wir)
|
||||
?: =('~' i.t.wir)
|
||||
?> ?=(^ chat)
|
||||
(migrate-listen t.chat)
|
||||
?. (~(has by synced) chat) [~ state]
|
||||
=/ =ship
|
||||
?: =('~' i.t.wir)
|
||||
@ -600,17 +668,38 @@
|
||||
:_ state
|
||||
[%pass path %agent [ship %chat-hook] %watch path]~
|
||||
==
|
||||
++ migrate-listen
|
||||
|= =wire
|
||||
^- (quip card _state)
|
||||
~& listen-migrate+wire
|
||||
?> ?=([@ @ ~] wire)
|
||||
=/ =ship
|
||||
(slav %p i.wire)
|
||||
:_ state
|
||||
~[(chat-view-poke %join ship wire %.y)]
|
||||
::
|
||||
++ migrate-store
|
||||
|= =wire
|
||||
^- (quip card _state)
|
||||
~& store-migrate+wire
|
||||
(kick store+wire)
|
||||
::
|
||||
++ watch-ack
|
||||
|= [wir=wire saw=(unit tang)]
|
||||
^- (quip card _state)
|
||||
?~ saw [~ state]
|
||||
?+ wir [~ state]
|
||||
::
|
||||
[%store @ *]
|
||||
?: =('~' i.t.wir)
|
||||
(migrate-store t.t.wir)
|
||||
(poke-chat-hook-action %remove t.wir)
|
||||
::
|
||||
[%backlog @ @ @ *]
|
||||
=/ chat=path (oust [(dec (lent t.wir)) 1] `(list @ta)`t.wir)
|
||||
?: =(i.t.wir '~')
|
||||
?> ?=(^ chat)
|
||||
(migrate-listen t.chat)
|
||||
:_ state
|
||||
%. ~[(chat-view-poke %delete chat)]
|
||||
%- slog
|
||||
@ -664,20 +753,20 @@
|
||||
::
|
||||
?. .^(? %gu (scot %p our.bol) %metadata-store (scot %da now.bol) ~) ~
|
||||
%+ murn
|
||||
^- (list resource)
|
||||
^- (list md-resource)
|
||||
=; resources
|
||||
%~ tap in
|
||||
%+ ~(gut by resources)
|
||||
group-path
|
||||
*(set resource)
|
||||
.^ (jug path resource)
|
||||
*(set md-resource)
|
||||
.^ (jug path md-resource)
|
||||
%gy
|
||||
(scot %p our.bol)
|
||||
%metadata-store
|
||||
(scot %da now.bol)
|
||||
/group-indices
|
||||
==
|
||||
|= resource
|
||||
|= md-resource
|
||||
^- (unit path)
|
||||
?. =(%chat app-name) ~
|
||||
`app-path
|
||||
@ -696,7 +785,7 @@
|
||||
%+ ~(gut by resources)
|
||||
[%chat chat]
|
||||
*(set group-path)
|
||||
.^ (jug resource group-path)
|
||||
.^ (jug md-resource group-path)
|
||||
%gy
|
||||
(scot %p our.bol)
|
||||
%metadata-store
|
||||
@ -704,15 +793,13 @@
|
||||
/resource-indices
|
||||
==
|
||||
::
|
||||
::NOTE this assumes permission paths match group paths
|
||||
++ is-permitted
|
||||
|= [who=ship chat=path]
|
||||
^- ?
|
||||
%+ lien (groups-of-chat chat)
|
||||
|= =group-path
|
||||
%^ scry ?
|
||||
%permission-store
|
||||
[%permitted (scot %p who) group-path]
|
||||
++ group-from-chat
|
||||
|= app-path=path
|
||||
^- group-path
|
||||
=/ groups=(list group-path)
|
||||
(groups-of-chat app-path)
|
||||
?> ?=(^ groups)
|
||||
i.groups
|
||||
::
|
||||
++ scry
|
||||
|* [=mold app=term =path]
|
||||
@ -743,4 +830,7 @@
|
||||
?: =(ship our.bol)
|
||||
[%pass wire %agent [our.bol %chat-store] %leave ~]
|
||||
[%pass wire %agent [ship %chat-hook] %leave ~]
|
||||
++ watch-groups
|
||||
^- card
|
||||
[%pass /groups %agent [our.bol %group-store] %watch /groups]
|
||||
--
|
||||
|
@ -1,6 +1,6 @@
|
||||
:: chat-store: data store that holds linear sequences of chat messages
|
||||
::
|
||||
/+ store=chat-store, default-agent, verb, dbug
|
||||
/+ store=chat-store, default-agent, verb, dbug, group-store
|
||||
~% %chat-store-top ..is ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
@ -8,17 +8,19 @@
|
||||
$% state-0
|
||||
state-1
|
||||
state-2
|
||||
state-3
|
||||
==
|
||||
::
|
||||
+$ state-0 [%0 =inbox:store]
|
||||
+$ state-1 [%1 =inbox:store]
|
||||
+$ state-2 [%2 =inbox:store]
|
||||
+$ state-3 [%3 =inbox:store]
|
||||
+$ admin-action
|
||||
$% [%trim ~]
|
||||
==
|
||||
--
|
||||
::
|
||||
=| state-2
|
||||
=| state-3
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
@ -39,11 +41,34 @@
|
||||
^- (quip card _this)
|
||||
|^
|
||||
=/ old !<(versioned-state old-vase)
|
||||
=? old ?=(%0 -.old)
|
||||
(old-to-2 inbox.old)
|
||||
=? old ?=(%1 -.old)
|
||||
(old-to-2 inbox.old)
|
||||
[~ this(state [%2 inbox.old])]
|
||||
=| cards=(list card)
|
||||
|-
|
||||
^- (quip card _this)
|
||||
?- -.old
|
||||
%3 [cards this(state old)]
|
||||
::
|
||||
%2
|
||||
=/ =inbox:store
|
||||
(migrate-path-map:group-store inbox.old)
|
||||
=/ kick-paths
|
||||
%~ tap in
|
||||
%+ roll
|
||||
~(val by sup.bowl)
|
||||
|= [[=ship sub=path] subs=(set path)]
|
||||
^- (set path)
|
||||
?. ?=([@ @ *] sub)
|
||||
subs
|
||||
?. &(=(%mailbox i.sub) =('~' i.t.sub))
|
||||
subs
|
||||
(~(put in subs) sub)
|
||||
=? cards ?=(^ kick-paths)
|
||||
:_ cards
|
||||
[%give %kick kick-paths ~]
|
||||
$(old [%3 inbox])
|
||||
::
|
||||
?(%0 %1) $(old (old-to-2 inbox.old))
|
||||
::
|
||||
==
|
||||
::
|
||||
++ old-to-2
|
||||
|= =inbox:store
|
||||
|
@ -1,19 +1,25 @@
|
||||
:: chat-view: sets up chat JS client, paginates data, and combines commands
|
||||
:: into semantic actions for the UI
|
||||
::
|
||||
/- *permission-store
|
||||
/- *permission-hook
|
||||
/- *group-store
|
||||
/- *invite-store
|
||||
/- *metadata-store
|
||||
/- *permission-group-hook
|
||||
/- *chat-hook
|
||||
/- *metadata-hook
|
||||
/- *rw-security
|
||||
/- hook=chat-hook
|
||||
/+ *server, default-agent, verb, dbug
|
||||
/+ store=chat-store
|
||||
/+ view=chat-view
|
||||
/- *permission-store,
|
||||
*permission-hook,
|
||||
*group,
|
||||
*invite-store,
|
||||
*metadata-store,
|
||||
group-hook,
|
||||
*permission-group-hook,
|
||||
*chat-hook,
|
||||
*metadata-hook,
|
||||
hook=chat-hook,
|
||||
contact-view,
|
||||
pull-hook
|
||||
/+ *server, default-agent, verb, dbug,
|
||||
store=chat-store,
|
||||
view=chat-view,
|
||||
group-store,
|
||||
grpl=group,
|
||||
resource,
|
||||
mdl=metadata
|
||||
::
|
||||
~% %chat-view-top ..is ~
|
||||
|%
|
||||
@ -25,6 +31,13 @@
|
||||
$: %0
|
||||
~
|
||||
==
|
||||
+$ poke
|
||||
$% [%chat-action action:store]
|
||||
[%group-action action:group-store]
|
||||
[%chat-hook-action action:hook]
|
||||
[%permission-hook-action permission-hook-action]
|
||||
[%permission-group-hook-action permission-group-hook-action]
|
||||
==
|
||||
::
|
||||
+$ card card:agent:gall
|
||||
--
|
||||
@ -109,7 +122,21 @@
|
||||
~/ %chat-view-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card _this)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%poke-ack
|
||||
?. ?=([%join-group @ @ @ @ @ ~] wire)
|
||||
(on-agent:def wire sign)
|
||||
?^ p.sign
|
||||
(on-agent:def wire sign)
|
||||
=/ =ship
|
||||
(slav %p i.t.wire)
|
||||
=/ ask-history=?
|
||||
=('y' i.t.t.wire)
|
||||
=/ rid=resource
|
||||
(de-path:resource t.t.t.wire)
|
||||
:_ this
|
||||
(joined-group:cc rid ship ask-history)
|
||||
::
|
||||
%kick
|
||||
:_ this
|
||||
[%pass / %agent [our.bol %chat-store] %watch /updates]~
|
||||
@ -152,6 +179,8 @@
|
||||
::
|
||||
~% %chat-view-library ..card ~
|
||||
|_ bol=bowl:gall
|
||||
++ grp ~(. grpl bol)
|
||||
++ md ~(. mdl bol)
|
||||
::
|
||||
++ poke-handle-http-request
|
||||
|= =inbound-request:eyre
|
||||
@ -183,7 +212,9 @@
|
||||
?- -.act
|
||||
%create
|
||||
?> ?=(^ app-path.act)
|
||||
?> |(=(group-path.act app-path.act) =(~(tap in members.act) ~))
|
||||
?> ?| =(+:group-path.act app-path.act)
|
||||
=(~(tap in members.act) ~)
|
||||
==
|
||||
?^ (chat-scry app-path.act)
|
||||
~& %chat-already-exists
|
||||
~
|
||||
@ -192,10 +223,11 @@
|
||||
%- create-group
|
||||
:* group-path.act
|
||||
app-path.act
|
||||
security.act
|
||||
policy.act
|
||||
members.act
|
||||
title.act
|
||||
description.act
|
||||
managed.act
|
||||
==
|
||||
(create-metadata title.act description.act group-path.act app-path.act)
|
||||
==
|
||||
@ -207,101 +239,108 @@
|
||||
:+ (chat-hook-poke [%remove app-path.act])
|
||||
(chat-poke [%delete app-path.act])
|
||||
:: if we still have metadata for the chat, remove it, and the associated
|
||||
:: group if it's unmanaged
|
||||
:: group if it's unmanaged.
|
||||
::
|
||||
:: we aren't guaranteed to have metadata: the chat might have been
|
||||
:: deleted by the host, which pushes metadata deletion down to us.
|
||||
::
|
||||
=/ group-path=(unit path)
|
||||
=/ maybe-group-path
|
||||
(maybe-group-from-chat app-path.act)
|
||||
?~ group-path ~
|
||||
=* group u.group-path
|
||||
?~ maybe-group-path
|
||||
~
|
||||
=* group-path u.maybe-group-path
|
||||
=/ rid=resource
|
||||
(de-path:resource group-path)
|
||||
=/ maybe-group
|
||||
(scry-group:grp rid)
|
||||
=/ hidden
|
||||
?~ maybe-group
|
||||
%.n
|
||||
hidden.u.maybe-group
|
||||
%- zing
|
||||
:~ ?. (is-creator group %chat app-path.act) ~
|
||||
[(metadata-poke [%remove group [%chat app-path.act]])]~
|
||||
:~ ?. (is-creator group-path %chat app-path.act)
|
||||
~
|
||||
[(metadata-poke [%remove group-path [%chat app-path.act]])]~
|
||||
::
|
||||
?: (is-managed group) ~
|
||||
:~ (group-poke [%unbundle group])
|
||||
(metadata-hook-poke [%remove group])
|
||||
(metadata-store-poke [%remove group [%chat app-path.act]])
|
||||
?. hidden
|
||||
~
|
||||
:~ (group-proxy-poke %remove-members rid (sy our.bol ~))
|
||||
(group-poke [%remove-group rid ~])
|
||||
(metadata-hook-poke [%remove group-path])
|
||||
(metadata-store-poke [%remove group-path [%chat app-path.act]])
|
||||
==
|
||||
==
|
||||
::
|
||||
%invite
|
||||
=/ =group-path
|
||||
(need (maybe-group-from-chat app-path.act))
|
||||
=/ rid=resource
|
||||
(de-path:resource group-path)
|
||||
=/ =group
|
||||
(need (scry-group:grp rid))
|
||||
?> ?=(%invite -.policy.group)
|
||||
:- (group-poke %change-policy rid %invite %add-invites ships.act)
|
||||
%+ turn
|
||||
~(tap in ships.act)
|
||||
|= =ship
|
||||
(send-invite group-path app-path.act ship)
|
||||
::
|
||||
%join
|
||||
=/ group-path
|
||||
?. (is-managed app-path.act) app-path.act
|
||||
(group-from-chat app-path.act)
|
||||
:~ (chat-hook-poke [%add-synced ship.act app-path.act ask-history.act])
|
||||
(permission-hook-poke [%add-synced ship.act group-path])
|
||||
(metadata-hook-poke [%add-synced ship.act group-path])
|
||||
==
|
||||
(maybe-group-from-chat app-path.act)
|
||||
=/ group
|
||||
?~ group-path
|
||||
~
|
||||
(scry-group-path:grp u.group-path)
|
||||
?: &(?=(^ group) =(hidden.u.group %.n))
|
||||
~[(chat-hook-poke %add-synced ship.act app-path.act ask-history.act)]
|
||||
=/ rid=resource
|
||||
(de-path:resource ship+app-path.act)
|
||||
=/ =cage
|
||||
:- %group-update
|
||||
!> ^- action:group-store
|
||||
[%add-members rid (sy our.bol ~)]
|
||||
:: we need this info in the wire to continue the flow after the
|
||||
:: poke ack
|
||||
=/ =wire
|
||||
:- %join-group
|
||||
[(scot %p ship.act) ?:(ask-history.act %y %n) ship+app-path.act]
|
||||
[%pass wire %agent [entity.rid %group-push-hook] %poke cage]~
|
||||
::
|
||||
%groupify
|
||||
?> ?=([%'~' ^] app-path.act)
|
||||
:: retrieve old data
|
||||
::
|
||||
=/ data=(unit mailbox:store)
|
||||
(scry-for (unit mailbox:store) %chat-store [%mailbox app-path.act])
|
||||
?~ data
|
||||
~& [%cannot-groupify-nonexistent app-path.act]
|
||||
~
|
||||
=/ permission=(unit permission)
|
||||
(scry-for (unit permission) %permission-store [%permission app-path.act])
|
||||
?: |(?=(~ permission) ?=(%black kind.u.permission))
|
||||
~& [%cannot-groupify-blacklist app-path.act]
|
||||
~
|
||||
=* app-path app-path.act
|
||||
=/ group-path
|
||||
(snag 0 (groups-from-resource:md %chat app-path))
|
||||
=/ scry-pax=path
|
||||
/metadata/[(scot %t (spat group-path))]/chat/[(scot %t (spat app-path))]
|
||||
=/ =metadata
|
||||
=- (fall - *metadata)
|
||||
%^ scry-for (unit metadata)
|
||||
%metadata-store
|
||||
=/ encoded-path=@ta
|
||||
(scot %t (spat app-path.act))
|
||||
/metadata/[encoded-path]/chat/[encoded-path]
|
||||
:: figure out new data
|
||||
::
|
||||
=/ chat-path=^path (slag 1 `path`app-path.act)
|
||||
:: group-path: the group to associate with the chat
|
||||
:: members: members of group, if it's new
|
||||
:: new-members: new members of group, if it already exists
|
||||
::
|
||||
=/ [group-path=path members=(set ship) new-members=(set ship)]
|
||||
?~ existing.act
|
||||
[chat-path who.u.permission ~]
|
||||
:+ group-path.u.existing.act
|
||||
~
|
||||
?. inclusive.u.existing.act ~
|
||||
%- ~(dif in who.u.permission)
|
||||
~| [%groupifying-with-nonexistent-group group-path.u.existing.act]
|
||||
%- need
|
||||
(group-scry group-path.u.existing.act)
|
||||
:: make changes
|
||||
::
|
||||
;: weld
|
||||
:: delete the old chat
|
||||
::
|
||||
(poke-chat-view-action %delete app-path.act)
|
||||
::
|
||||
:: create the new chat. if needed, creates the new group.
|
||||
::
|
||||
%- poke-chat-view-action
|
||||
:* %create
|
||||
title.metadata
|
||||
description.metadata
|
||||
chat-path
|
||||
group-path
|
||||
%village
|
||||
members
|
||||
&
|
||||
==
|
||||
::
|
||||
:: if needed, add members to the existing group
|
||||
::
|
||||
?~ new-members ~
|
||||
[(group-poke [%add new-members group-path])]~
|
||||
::
|
||||
:: import messages into the new chat
|
||||
::
|
||||
[(chat-poke %messages chat-path envelopes.u.data)]~
|
||||
(need (scry-for (unit metadata) %metadata-store scry-pax))
|
||||
=/ old-rid=resource
|
||||
(de-path:resource group-path)
|
||||
?< (is-managed:grp old-rid)
|
||||
?~ existing.act
|
||||
:: just create contacts object for group
|
||||
~[(contact-view-poke %groupify old-rid title.metadata description.metadata)]
|
||||
:: change associations
|
||||
=* group-path group-path.u.existing.act
|
||||
=/ rid=resource
|
||||
(de-path:resource group-path)
|
||||
=/ old-group=group
|
||||
(need (scry-group:grp old-rid))
|
||||
=/ =group
|
||||
(need (scry-group:grp rid))
|
||||
=/ ships=(set ship)
|
||||
(~(dif in members.old-group) members.group)
|
||||
:* (metadata-store-poke %remove ship+app-path %chat app-path)
|
||||
(metadata-store-poke %add group-path [%chat app-path] metadata)
|
||||
(group-poke %remove-group old-rid ~)
|
||||
?. inclusive.u.existing.act
|
||||
~
|
||||
:- (group-poke %add-members rid ships)
|
||||
%+ turn
|
||||
~(tap in ships)
|
||||
|= =ship
|
||||
(send-invite group-path app-path ship)
|
||||
==
|
||||
==
|
||||
::
|
||||
@ -313,43 +352,23 @@
|
||||
==
|
||||
::
|
||||
++ create-group
|
||||
|= [=path app-path=path sec=rw-security ships=(set ship) title=@t desc=@t]
|
||||
|= [=path app-path=path =policy ships=(set ship) title=@t desc=@t managed=?]
|
||||
^- (list card)
|
||||
?^ (group-scry path)
|
||||
:~ (create-security path %village)
|
||||
(permission-hook-poke [%add-owned path path])
|
||||
==
|
||||
:: do not create a managed group if this is a sig path or a blacklist
|
||||
?^ (scry-group-path:grp path) ~
|
||||
=/ rid=resource
|
||||
(de-path:resource path)
|
||||
?> =(our.bol entity.rid)
|
||||
:: do not create a contacts object if this is unmanaged
|
||||
::
|
||||
?: =(sec %channel)
|
||||
:~ (group-poke [%bundle path])
|
||||
(create-security path sec)
|
||||
(permission-hook-poke [%add-owned path path])
|
||||
==
|
||||
?: (is-managed path)
|
||||
~[(contact-view-poke [%create path ships title desc])]
|
||||
%+ welp
|
||||
:~ (group-poke [%bundle path])
|
||||
(group-poke [%add ships path])
|
||||
(create-security path sec)
|
||||
(permission-hook-poke [%add-owned path path])
|
||||
==
|
||||
%- zing
|
||||
%+ turn ~(tap in ships)
|
||||
:-
|
||||
?. managed
|
||||
(group-poke %add-group rid policy %.y)
|
||||
(contact-view-poke %create name.rid policy title desc)
|
||||
%+ murn ~(tap in ships)
|
||||
|= =ship
|
||||
^- (unit card)
|
||||
?: =(ship our.bol) ~
|
||||
[(send-invite app-path ship)]~
|
||||
::
|
||||
++ create-security
|
||||
|= [pax=path sec=rw-security]
|
||||
^- card
|
||||
?+ sec !!
|
||||
%channel
|
||||
(perm-group-hook-poke [%associate pax [[pax %black] ~ ~]])
|
||||
::
|
||||
%village
|
||||
(perm-group-hook-poke [%associate pax [[pax %white] ~ ~]])
|
||||
==
|
||||
`(send-invite path app-path ship)
|
||||
::
|
||||
++ create-metadata
|
||||
|= [title=@t description=@t group-path=path app-path=path]
|
||||
@ -360,16 +379,14 @@
|
||||
description description
|
||||
date-created now.bol
|
||||
creator
|
||||
%+ slav %p
|
||||
?: (is-managed app-path) (snag 0 app-path)
|
||||
(snag 1 app-path)
|
||||
(slav %p (snag 0 app-path))
|
||||
==
|
||||
:~ (metadata-poke [%add group-path [%chat app-path] metadata])
|
||||
(metadata-hook-poke [%add-owned group-path])
|
||||
==
|
||||
::
|
||||
++ contact-view-poke
|
||||
|= act=[%create =path ships=(set ship) title=@t description=@t]
|
||||
|= act=contact-view-action:contact-view
|
||||
^- card
|
||||
[%pass / %agent [our.bol %contact-view] %poke %contact-view-action !>(act)]
|
||||
::
|
||||
@ -383,23 +400,18 @@
|
||||
^- card
|
||||
[%pass / %agent [our.bol %metadata-store] %poke %metadata-action !>(act)]
|
||||
::
|
||||
++ metadata-hook-poke
|
||||
|= act=metadata-hook-action
|
||||
^- card
|
||||
:* %pass / %agent
|
||||
[our.bol %metadata-hook]
|
||||
%poke %metadata-hook-action
|
||||
!>(act)
|
||||
==
|
||||
::
|
||||
++ send-invite
|
||||
|= [=path =ship]
|
||||
|= [group-path=path app-path=path =ship]
|
||||
^- card
|
||||
=/ managed=?
|
||||
!=(ship+app-path group-path)
|
||||
=/ =invite
|
||||
:* our.bol %chat-hook
|
||||
path ship ''
|
||||
:* our.bol
|
||||
?:(managed %contact-hook %chat-hook)
|
||||
?:(managed group-path app-path)
|
||||
ship ''
|
||||
==
|
||||
=/ act=invite-action [%invite /chat (shaf %msg-uid eny.bol) invite]
|
||||
=/ act=invite-action [%invite ?:(managed /contacts /chat) (shaf %msg-uid eny.bol) invite]
|
||||
[%pass / %agent [our.bol %invite-hook] %poke %invite-action !>(act)]
|
||||
::
|
||||
++ chat-scry
|
||||
@ -423,7 +435,7 @@
|
||||
~& [%weird-chat app-path]
|
||||
!!
|
||||
=/ resource-indices
|
||||
.^ (jug resource group-path)
|
||||
.^ (jug md-resource group-path)
|
||||
%gy
|
||||
(scot %p our.bol)
|
||||
%metadata-store
|
||||
@ -464,6 +476,18 @@
|
||||
?~ meta !!
|
||||
=(our.bol creator.u.meta)
|
||||
--
|
||||
:: +joined-group: Successfully joined unmanaged group, continue flow
|
||||
::
|
||||
++ joined-group
|
||||
|= [rid=resource =ship ask-history=?]
|
||||
^- (list card)
|
||||
=/ =path
|
||||
(en-path:resource rid)
|
||||
?> ?=(^ path)
|
||||
:~ (group-pull-hook-poke %add ship rid)
|
||||
(chat-hook-poke %add-synced ship t.path ask-history)
|
||||
(metadata-hook-poke %add-synced ship path)
|
||||
==
|
||||
::
|
||||
++ diff-chat-update
|
||||
|= upd=update:store
|
||||
@ -478,9 +502,18 @@
|
||||
[%pass / %agent [our.bol %chat-store] %poke %chat-action !>(act)]
|
||||
::
|
||||
++ group-poke
|
||||
|= act=group-action
|
||||
|= upd=update:group-store
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-store] %poke %group-action !>(act)]
|
||||
[%pass / %agent [our.bol %group-store] %poke %group-update !>(upd)]
|
||||
++ group-pull-hook-poke
|
||||
|= act=action:pull-hook
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-pull-hook] %poke %pull-hook-action !>(act)]
|
||||
::
|
||||
++ group-proxy-poke
|
||||
|= act=action:group-store
|
||||
^- card
|
||||
[%pass / %agent [entity.resource.act %group-push-hook] %poke %group-update !>(act)]
|
||||
::
|
||||
++ permission-poke
|
||||
|= act=permission-action
|
||||
@ -506,15 +539,21 @@
|
||||
%poke %permission-group-hook-action !>(act)
|
||||
==
|
||||
::
|
||||
++ metadata-hook-poke
|
||||
|= act=metadata-hook-action
|
||||
^- card
|
||||
:* %pass / %agent
|
||||
[our.bol %metadata-hook]
|
||||
%poke %metadata-hook-action
|
||||
!>(act)
|
||||
==
|
||||
::
|
||||
++ envelope-scry
|
||||
|= pax=path
|
||||
^- (list envelope:store)
|
||||
(scry-for (list envelope:store) %chat-store [%envelopes pax])
|
||||
::
|
||||
++ group-scry
|
||||
|= pax=path
|
||||
^- (unit group)
|
||||
(scry-for (unit group) %group-store pax)
|
||||
|
||||
::
|
||||
++ scry-for
|
||||
|* [=mold app=term =path]
|
||||
|
@ -1,12 +1,13 @@
|
||||
:: contact-hook:
|
||||
::
|
||||
/- *group-store,
|
||||
*group-hook,
|
||||
/- group-hook,
|
||||
*contact-hook,
|
||||
*contact-view,
|
||||
*invite-store,
|
||||
*metadata-hook,
|
||||
*metadata-store
|
||||
/+ *contact-json, default-agent, dbug
|
||||
*metadata-store,
|
||||
*group
|
||||
/+ *contact-json, default-agent, dbug, group-store, verb, resource, grpl=group
|
||||
~% %contact-hook-top ..is ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
@ -14,18 +15,21 @@
|
||||
+$ versioned-state
|
||||
$% state-zero
|
||||
state-one
|
||||
state-two
|
||||
==
|
||||
::
|
||||
+$ state-zero [%0 state-base]
|
||||
+$ state-one [%1 state-base]
|
||||
+$ state-two [%2 state-base]
|
||||
+$ state-base
|
||||
$: =synced
|
||||
invite-created=_|
|
||||
==
|
||||
--
|
||||
=| state-one
|
||||
=| state-two
|
||||
=* state -
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ bol=bowl:gall
|
||||
@ -39,22 +43,54 @@
|
||||
:_ this(invite-created %.y)
|
||||
:~ (invite-poke:cc [%create /contacts])
|
||||
[%pass /inv %agent [our.bol %invite-store] %watch /invitatory/contacts]
|
||||
[%pass /group %agent [our.bol %group-store] %watch /updates]
|
||||
[%pass /group %agent [our.bol %group-store] %watch /groups]
|
||||
==
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
^- (quip card _this)
|
||||
=/ old !<(versioned-state old-vase)
|
||||
=| cards=(list card)
|
||||
|^
|
||||
|- ^- (quip card _this)
|
||||
?: ?=(%2 -.old)
|
||||
[cards this(state old)]
|
||||
?: ?=(%1 -.old)
|
||||
[~ this(state old)]
|
||||
=/ upgraded-state
|
||||
%* . *state-one
|
||||
synced synced
|
||||
invite-created invite-created
|
||||
%_ $
|
||||
-.old %2
|
||||
::
|
||||
synced.old
|
||||
%- malt
|
||||
%+ turn
|
||||
~(tap by synced.old)
|
||||
|= [=path =ship]
|
||||
[ship+path ship]
|
||||
::
|
||||
cards
|
||||
^- (list card)
|
||||
;: welp
|
||||
:~ [%pass /group %agent [our.bol %group-store] %leave ~]
|
||||
[%pass /group %agent [our.bol %group-store] %watch /groups]
|
||||
==
|
||||
kick-old-subs
|
||||
cards
|
||||
==
|
||||
==
|
||||
:_ this(state upgraded-state)
|
||||
[%pass /group %agent [our.bol %group-store] %watch /updates]~
|
||||
%_ $
|
||||
-.old %1
|
||||
::
|
||||
cards
|
||||
:_ cards
|
||||
[%pass /group %agent [our.bol %group-store] %watch /updates]
|
||||
==
|
||||
++ kick-old-subs
|
||||
=/ paths
|
||||
%+ turn
|
||||
~(val by sup.bol)
|
||||
|=([=ship =path] path)
|
||||
?~ paths ~
|
||||
[%give %kick paths ~]~
|
||||
--
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
@ -99,7 +135,7 @@
|
||||
::
|
||||
%group-update
|
||||
=^ cards state
|
||||
(fact-group-update:cc wire !<(group-update q.cage.sign))
|
||||
(fact-group-update:cc wire !<(update:group-store q.cage.sign))
|
||||
[cards this]
|
||||
::
|
||||
%invite-update
|
||||
@ -116,6 +152,7 @@
|
||||
--
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
++ grp ~(. grpl bol)
|
||||
::
|
||||
++ poke-json
|
||||
|= jon=json
|
||||
@ -146,7 +183,7 @@
|
||||
?. |(=(shp our.bol) =(src.bol ship)) ~
|
||||
:: scry group to check if ship is a member
|
||||
=/ =group (need (group-scry path))
|
||||
?. (~(has in group) shp) ~
|
||||
?. (~(has in members.group) shp) ~
|
||||
[%pass / %agent [our.bol %contact-store] %poke %contact-action !>(act)]~
|
||||
::
|
||||
++ poke-hook-action
|
||||
@ -206,7 +243,7 @@
|
||||
?> (~(has by synced) pax)
|
||||
:: scry groups to check if ship is a member
|
||||
=/ =group (need (group-scry pax))
|
||||
?> (~(has in group) src.bol)
|
||||
?> (~(has in members.group) src.bol)
|
||||
=/ contacts (need (contacts-scry pax))
|
||||
[%give %fact ~ %contact-update !>([%contacts pax contacts])]~
|
||||
::
|
||||
@ -224,6 +261,12 @@
|
||||
?> ?=(^ wir)
|
||||
[~ state(synced (~(del by synced) t.wir))]
|
||||
::
|
||||
++ migrate
|
||||
|= wir=wire
|
||||
^- wire
|
||||
?> ?=([%contacts @ @ *] wir)
|
||||
[%contacts %ship t.wir]
|
||||
::
|
||||
++ kick
|
||||
|= wir=wire
|
||||
^- (list card)
|
||||
@ -232,9 +275,14 @@
|
||||
[%pass /inv %agent [our.bol %invite-store] %watch /invitatory/contacts]~
|
||||
::
|
||||
[%group ~]
|
||||
[%pass /group %agent [our.bol %group-store] %watch /updates]~
|
||||
[%pass /group %agent [our.bol %group-store] %watch /groups]~
|
||||
::
|
||||
[%contacts @ *]
|
||||
=/ wir
|
||||
?: =(%ship i.t.wir)
|
||||
wir
|
||||
(migrate wir)
|
||||
?> ?=([%contacts @ @ *] wir)
|
||||
?. (~(has by synced) t.wir) ~
|
||||
=/ =ship (~(got by synced) t.wir)
|
||||
?: =(ship our.bol)
|
||||
@ -267,18 +315,10 @@
|
||||
%edit
|
||||
:_ state
|
||||
(give-fact path.fact [%edit path.fact ship.fact edit-field.fact])
|
||||
::
|
||||
%remove
|
||||
:_ state
|
||||
~[(group-poke [%remove [ship.fact ~ ~] path.fact])]
|
||||
::
|
||||
%delete
|
||||
=. synced (~(del by synced) path.fact)
|
||||
:_ state
|
||||
:~ (group-poke [%unbundle path.fact])
|
||||
(metadata-hook-poke [%remove path.fact])
|
||||
(metadata-poke [%remove path.fact [%contacts path.fact]])
|
||||
==
|
||||
`state
|
||||
==
|
||||
::
|
||||
++ foreign
|
||||
@ -331,12 +371,7 @@
|
||||
=/ owner (~(get by synced) path.fact)
|
||||
?~ owner ~
|
||||
?> |(=(u.owner src.bol) =(src.bol ship.fact))
|
||||
%+ welp
|
||||
:~ (group-poke [%remove [ship.fact ~ ~] path.fact])
|
||||
(contact-poke [%remove path.fact ship.fact])
|
||||
==
|
||||
?. =(ship.fact our.bol) ~
|
||||
~[(group-poke [%unbundle path.fact])]
|
||||
~[(contact-poke [%remove path.fact ship.fact])]
|
||||
::
|
||||
%edit
|
||||
=/ owner (~(got by synced) path.fact)
|
||||
@ -346,29 +381,37 @@
|
||||
--
|
||||
::
|
||||
++ fact-group-update
|
||||
|= [wir=wire fact=group-update]
|
||||
|= [wir=wire fact=update:group-store]
|
||||
^- (quip card _state)
|
||||
?: ?=(%initial -.fact)
|
||||
[~ state]
|
||||
=/ group=(unit group)
|
||||
(scry-group:grp resource.fact)
|
||||
|^
|
||||
?+ -.fact [~ state]
|
||||
%add (add +.fact)
|
||||
%remove (remove +.fact)
|
||||
%unbundle (unbundle +.fact)
|
||||
%initial-group (initial-group +.fact)
|
||||
%remove-members (remove +.fact)
|
||||
%remove-group (unbundle +.fact)
|
||||
==
|
||||
++ add
|
||||
|= [ships=(set ship) =path]
|
||||
::
|
||||
++ initial-group
|
||||
|= [rid=resource =^group]
|
||||
^- (quip card _state)
|
||||
=/ owner (~(get by synced) path)
|
||||
?~ owner [~ state]
|
||||
?. =(u.owner our.bol) [~ state]
|
||||
:_ state
|
||||
%+ turn ~(tap in (~(del in ships) our.bol))
|
||||
|= =ship
|
||||
(send-invite-poke path ship)
|
||||
?: hidden.group [~ state]
|
||||
=/ =path
|
||||
(en-path:resource rid)
|
||||
?: (~(has by synced) path)
|
||||
[~ state]
|
||||
(poke-hook-action %add-synced entity.rid path)
|
||||
::
|
||||
++ unbundle
|
||||
|= =path
|
||||
|= [rid=resource ~]
|
||||
^- (quip card _state)
|
||||
=/ =path
|
||||
(en-path:resource rid)
|
||||
?. (~(has by synced) path)
|
||||
?~ (contacts-scry path)
|
||||
[~ state]
|
||||
:_ state
|
||||
[(contact-poke [%delete path])]~
|
||||
:_ state(synced (~(del by synced) path))
|
||||
@ -377,18 +420,23 @@
|
||||
==
|
||||
::
|
||||
++ remove
|
||||
|= [members=group =path]
|
||||
|= [rid=resource ships=(set ship)]
|
||||
^- (quip card _state)
|
||||
:: if pax is synced, remove member from contacts and kick their sub
|
||||
?~ group
|
||||
[~ state]
|
||||
?: hidden.u.group [~ state]
|
||||
=/ =path
|
||||
(en-path:resource rid)
|
||||
=/ owner=(unit ship) (~(get by synced) path)
|
||||
?~ owner
|
||||
:_ state
|
||||
%+ turn ~(tap in members)
|
||||
%+ turn ~(tap in ships)
|
||||
|= =ship
|
||||
(contact-poke [%remove path ship])
|
||||
:_ state
|
||||
%- zing
|
||||
%+ turn ~(tap in members)
|
||||
%+ turn ~(tap in ships)
|
||||
|= =ship
|
||||
:~ [%give %kick ~[[%contacts path]] `ship]
|
||||
?: =(ship our.bol)
|
||||
@ -412,21 +460,16 @@
|
||||
^- (quip card _state)
|
||||
?+ -.fact [~ state]
|
||||
%accepted
|
||||
=/ changes
|
||||
(poke-hook-action [%add-synced ship.invite.fact path.invite.fact])
|
||||
:-
|
||||
%+ welp
|
||||
:~ (group-hook-poke [%add ship.invite.fact path.invite.fact])
|
||||
(metadata-hook-poke [%add-synced ship.invite.fact path.invite.fact])
|
||||
==
|
||||
-.changes
|
||||
+.changes
|
||||
=/ rid=resource
|
||||
(de-path:resource path.invite.fact)
|
||||
:_ state
|
||||
~[(contact-view-poke %join rid)]
|
||||
==
|
||||
::
|
||||
++ group-hook-poke
|
||||
|= act=group-hook-action
|
||||
|= =action:group-hook
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-hook] %poke %group-hook-action !>(act)]
|
||||
[%pass / %agent [our.bol %group-hook] %poke %group-hook-action !>(action)]
|
||||
::
|
||||
++ invite-poke
|
||||
|= act=invite-action
|
||||
@ -438,8 +481,13 @@
|
||||
^- card
|
||||
[%pass / %agent [our.bol %contact-store] %poke %contact-action !>(act)]
|
||||
::
|
||||
++ contact-view-poke
|
||||
|= act=contact-view-action
|
||||
^- card
|
||||
[%pass / %agent [our.bol %contact-view] %poke %contact-view-action !>(act)]
|
||||
::
|
||||
++ group-poke
|
||||
|= act=group-action
|
||||
|= act=action:group-store
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-store] %poke %group-action !>(act)]
|
||||
::
|
||||
@ -478,7 +526,7 @@
|
||||
|= pax=path
|
||||
.^ (unit group)
|
||||
%gx
|
||||
;:(weld /(scot %p our.bol)/group-store/(scot %da now.bol) pax /noun)
|
||||
;:(weld /(scot %p our.bol)/group-store/(scot %da now.bol) /groups pax /noun)
|
||||
==
|
||||
::
|
||||
++ pull-wire
|
||||
|
@ -6,6 +6,7 @@
|
||||
+$ versioned-state
|
||||
$% state-zero
|
||||
state-one
|
||||
state-two
|
||||
==
|
||||
::
|
||||
+$ rolodex-0 (map path contacts-0)
|
||||
@ -29,9 +30,13 @@
|
||||
$: %1
|
||||
=rolodex
|
||||
==
|
||||
+$ state-two
|
||||
$: %2
|
||||
=rolodex
|
||||
==
|
||||
--
|
||||
::
|
||||
=| state-one
|
||||
=| state-two
|
||||
=* state -
|
||||
%- agent:dbug
|
||||
^- agent:gall
|
||||
@ -47,8 +52,30 @@
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
=/ old !<(versioned-state old-vase)
|
||||
=| cards=(list card)
|
||||
|-
|
||||
?: ?=(%2 -.old)
|
||||
[cards this(state old)]
|
||||
?: ?=(%1 -.old)
|
||||
[~ this(state old)]
|
||||
=/ new-rolodex=^rolodex
|
||||
%- malt
|
||||
%+ turn
|
||||
~(tap by rolodex.old)
|
||||
|= [=path =contacts]
|
||||
[ship+path contacts]
|
||||
%_ $
|
||||
old [%2 new-rolodex]
|
||||
::
|
||||
cards
|
||||
=/ paths
|
||||
%+ turn
|
||||
~(val by sup.bol)
|
||||
|=([=ship =path] path)
|
||||
?~ paths cards
|
||||
:_ cards
|
||||
[%give %kick paths ~]
|
||||
==
|
||||
|
||||
=/ new-rolodex=^rolodex
|
||||
%- ~(run by rolodex.old)
|
||||
|= cons=contacts-0
|
||||
@ -64,7 +91,7 @@
|
||||
color.con
|
||||
~
|
||||
==
|
||||
[~ this(state [%1 new-rolodex])]
|
||||
$(old [%1 new-rolodex])
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
|
@ -1,16 +1,20 @@
|
||||
:: contact-view: sets up contact JS client and combines commands
|
||||
:: into semantic actions for the UI
|
||||
::
|
||||
/- *group-store
|
||||
/- *group-hook
|
||||
/- *invite-store
|
||||
/- *contact-hook
|
||||
/- *metadata-store
|
||||
/- *metadata-hook
|
||||
/- *permission-group-hook
|
||||
/- *permission-hook
|
||||
/-
|
||||
group-hook,
|
||||
*invite-store,
|
||||
*contact-hook,
|
||||
*metadata-store,
|
||||
*metadata-hook,
|
||||
*permission-group-hook,
|
||||
*permission-hook,
|
||||
pull-hook,
|
||||
push-hook
|
||||
/+ *server, *contact-json, default-agent, dbug, verb,
|
||||
grpl=group, mdl=metadata, resource,
|
||||
group-store
|
||||
::
|
||||
/+ *server, *contact-json, default-agent, dbug
|
||||
|%
|
||||
+$ versioned-state
|
||||
$% state-0
|
||||
@ -27,6 +31,7 @@
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
@ -40,9 +45,7 @@
|
||||
:_ this
|
||||
:~ [%pass /updates %agent [our.bowl %contact-store] %watch /updates]
|
||||
(contact-poke:cc [%create /~/default])
|
||||
(group-poke:cc [%bundle /~/default])
|
||||
(contact-poke:cc [%add /~/default our.bowl *contact])
|
||||
(group-poke:cc [%add [our.bowl ~ ~] /~/default])
|
||||
:* %pass /srv %agent [our.bol %file-server]
|
||||
%poke %file-server-action
|
||||
!>([%serve-dir /'~groups' /app/landscape %.n])
|
||||
@ -93,6 +96,14 @@
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card _this)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%poke-ack
|
||||
?. ?=([%join-group %ship @ @ ~] wire)
|
||||
(on-agent:def wire sign)
|
||||
?^ p.sign
|
||||
(on-agent:def wire sign)
|
||||
:_ this
|
||||
(joined-group:cc t.wire)
|
||||
::
|
||||
%kick
|
||||
[[%pass / %agent [our.bol %contact-store] %watch /updates]~ this]
|
||||
::
|
||||
@ -117,6 +128,8 @@
|
||||
--
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
++ grp ~(. grpl bol)
|
||||
++ md ~(. mdl bol)
|
||||
++ poke-json
|
||||
|= jon=json
|
||||
^- (list card)
|
||||
@ -126,30 +139,72 @@
|
||||
++ poke-contact-view-action
|
||||
|= act=contact-view-action
|
||||
^- (list card)
|
||||
?> (team:title our.bol src.bol)
|
||||
?- -.act
|
||||
%create
|
||||
?> ?=([@ *] path.act)
|
||||
%+ weld
|
||||
:~ (group-poke [%bundle path.act])
|
||||
(contact-poke [%create path.act])
|
||||
(contact-hook-poke [%add-owned path.act])
|
||||
(group-hook-poke [%add our.bol path.act])
|
||||
(group-poke [%add (~(put in ships.act) our.bol) path.act])
|
||||
(perm-group-hook-poke [%associate path.act [[path.act %white] ~ ~]])
|
||||
(permission-hook-poke [%add-owned path.act path.act])
|
||||
=/ rid=resource
|
||||
[our.bol name.act]
|
||||
=/ =path
|
||||
(en-path:resource rid)
|
||||
;: weld
|
||||
:~ (group-poke [%add-group rid policy.act %.n])
|
||||
(group-poke [%add-members rid (sy our.bol ~)])
|
||||
(group-push-poke %add rid)
|
||||
(contact-poke [%create path])
|
||||
(contact-hook-poke [%add-owned path])
|
||||
==
|
||||
(create-metadata path.act title.act description.act)
|
||||
(create-metadata path title.act description.act)
|
||||
?. ?=(%invite -.policy.act)
|
||||
~
|
||||
%+ turn
|
||||
~(tap in pending.policy.act)
|
||||
|= =ship
|
||||
(send-invite our.bol %contacts path ship '')
|
||||
==
|
||||
::
|
||||
%join
|
||||
=/ =path
|
||||
(en-path:resource resource.act)
|
||||
=/ =cage
|
||||
:- %group-update
|
||||
!> ^- update:group-store
|
||||
[%add-members resource.act (sy our.bol ~)]
|
||||
=/ =wire
|
||||
[%join-group path]
|
||||
[%pass wire %agent [entity.resource.act %group-push-hook] %poke cage]~
|
||||
::
|
||||
%invite
|
||||
=* rid resource.act
|
||||
=/ =path
|
||||
(en-path:resource rid)
|
||||
=/ =group
|
||||
(need (scry-group:grp rid))
|
||||
:- (send-invite entity.rid %contacts path ship.act text.act)
|
||||
?. ?=(%invite -.policy.group) ~
|
||||
~[(add-pending rid ship.act)]
|
||||
::
|
||||
%delete
|
||||
%+ weld
|
||||
:~ (contact-hook-poke [%remove path.act])
|
||||
(group-poke [%unbundle path.act])
|
||||
(contact-poke [%delete path.act])
|
||||
=/ rid=resource
|
||||
(de-path:resource path.act)
|
||||
=/ group-pokes=(list card)
|
||||
?: =(our.bol entity.rid)
|
||||
~[(group-push-poke %remove rid)]
|
||||
:~ (group-proxy-poke %remove-members rid (sy our.bol ~))
|
||||
(group-pull-poke %remove rid)
|
||||
==
|
||||
;: weld
|
||||
group-pokes
|
||||
:~ (contact-hook-poke [%remove path.act])
|
||||
(group-poke [%remove-group rid ~])
|
||||
(contact-poke [%delete path.act])
|
||||
==
|
||||
(delete-metadata path.act)
|
||||
==
|
||||
(delete-metadata path.act)
|
||||
::
|
||||
%remove
|
||||
:~ (group-poke [%remove [ship.act ~ ~] path.act])
|
||||
=/ rid=resource
|
||||
(de-path:resource path.act)
|
||||
:~ (group-poke %remove-members rid (sy ship.act ~))
|
||||
(contact-poke [%remove path.act ship.act])
|
||||
==
|
||||
::
|
||||
@ -157,6 +212,16 @@
|
||||
:: determine whether to send to our contact-hook or foreign
|
||||
:: send contact-action to contact-hook with %add action
|
||||
[(share-poke recipient.act [%add path.act ship.act contact.act])]~
|
||||
::
|
||||
%groupify
|
||||
=/ =path
|
||||
(en-path:resource resource.act)
|
||||
%+ weld
|
||||
:~ (group-poke %expose resource.act ~)
|
||||
(contact-poke [%create path])
|
||||
(contact-hook-poke [%add-owned path])
|
||||
==
|
||||
(create-metadata path title.act description.act)
|
||||
==
|
||||
++ poke-handle-http-request
|
||||
|= =inbound-request:eyre
|
||||
@ -183,8 +248,40 @@
|
||||
==
|
||||
==
|
||||
::
|
||||
++ joined-group
|
||||
|= =path
|
||||
^- (list card)
|
||||
=/ rid=resource
|
||||
(de-path:resource path)
|
||||
:~ (group-pull-poke [%add entity.rid rid])
|
||||
(contact-hook-poke [%add-synced entity.rid path])
|
||||
(sync-metadata entity.rid path)
|
||||
==
|
||||
::
|
||||
:: +utilities
|
||||
::
|
||||
++ add-pending
|
||||
|= [rid=resource =ship]
|
||||
^- card
|
||||
=/ app=term
|
||||
?: =(our.bol entity.rid)
|
||||
%group-store
|
||||
%group-push-hook
|
||||
=/ =cage
|
||||
:- %group-action
|
||||
!> ^- action:group-store
|
||||
[%change-policy rid %invite %add-invites (sy ship ~)]
|
||||
[%pass / %agent [entity.rid app] %poke cage]
|
||||
::
|
||||
++ send-invite
|
||||
|= =invite
|
||||
^- card
|
||||
=/ =cage
|
||||
:- %invite-action
|
||||
!> ^- invite-action
|
||||
[%invite /contacts (shaf %invite-uid eny.bol) invite]
|
||||
[%pass / %agent [recipient.invite %invite-hook] %poke cage]
|
||||
::
|
||||
++ contact-poke
|
||||
|= act=contact-action
|
||||
^- card
|
||||
@ -201,14 +298,24 @@
|
||||
[%pass / %agent [ship %contact-hook] %poke %contact-action !>(act)]
|
||||
::
|
||||
++ group-poke
|
||||
|= act=group-action
|
||||
|= act=action:group-store
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-store] %poke %group-action !>(act)]
|
||||
::
|
||||
++ group-hook-poke
|
||||
|= act=group-hook-action
|
||||
++ group-push-poke
|
||||
|= act=action:push-hook
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-hook] %poke %group-hook-action !>(act)]
|
||||
[%pass / %agent [our.bol %group-push-hook] %poke %push-hook-action !>(act)]
|
||||
::
|
||||
++ group-proxy-poke
|
||||
|= act=action:group-store
|
||||
^- card
|
||||
[%pass / %agent [entity.resource.act %group-push-hook] %poke %group-update !>(act)]
|
||||
::
|
||||
++ group-pull-poke
|
||||
|= act=action:pull-hook
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-pull-hook] %poke %pull-hook-action !>(act)]
|
||||
::
|
||||
++ metadata-poke
|
||||
|= act=metadata-action
|
||||
@ -234,6 +341,11 @@
|
||||
%poke %permission-hook-action !>(act)
|
||||
==
|
||||
::
|
||||
++ sync-metadata
|
||||
|= [=ship =path]
|
||||
^- card
|
||||
(metadata-hook-poke %add-synced ship path)
|
||||
::
|
||||
++ create-metadata
|
||||
|= [=path title=@t description=@t]
|
||||
^- (list card)
|
||||
|
@ -1,13 +1,14 @@
|
||||
:: group-hook: allow syncing group data from foreign paths to local paths
|
||||
::
|
||||
/- *group-store, *group-hook
|
||||
/+ default-agent, verb, dbug
|
||||
/- *group, hook=group-hook, *invite-store
|
||||
/+ default-agent, verb, dbug, store=group-store, grpl=group, pull-hook, push-hook, resource
|
||||
~% %group-hook-top ..is ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
::
|
||||
++ versioned-state
|
||||
$% state-zero
|
||||
state-one
|
||||
==
|
||||
::
|
||||
::
|
||||
@ -16,252 +17,96 @@
|
||||
synced=(map path ship)
|
||||
==
|
||||
::
|
||||
+$ state-one
|
||||
$: %1
|
||||
~
|
||||
==
|
||||
::
|
||||
--
|
||||
::
|
||||
=| state-zero
|
||||
=| state-one
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
group-core +>
|
||||
gc ~(. group-core bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= =vase
|
||||
^- (quip card _this)
|
||||
=/ old !<(state-zero vase)
|
||||
:_ this(state old)
|
||||
%+ murn ~(tap by synced.old)
|
||||
|= [=path =ship]
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
group-core +>
|
||||
gc ~(. group-core bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
:: ^- (quip card _this)
|
||||
:: :_ this
|
||||
:: ~[watch-store:gc]
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= =vase
|
||||
^- (quip card _this)
|
||||
=/ old !<(versioned-state vase)
|
||||
?- -.old
|
||||
%1 [~ this(state old)]
|
||||
%0
|
||||
:_ this(state *state-one)
|
||||
|^
|
||||
%+ murn
|
||||
~(tap by synced.old)
|
||||
|= [=path host=ship]
|
||||
^- (unit card)
|
||||
=/ =wire [(scot %p ship) %group path]
|
||||
=/ =term ?:(=(our.bowl ship) %group-store %group-hook)
|
||||
?: (~(has by wex.bowl) [wire ship term]) ~
|
||||
`[%pass wire %agent [ship term] %watch [%group path]]
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?. ?=(%group-hook-action mark)
|
||||
(on-poke:def mark vase)
|
||||
=^ cards state
|
||||
(poke-group-hook-action:gc !<(group-hook-action vase))
|
||||
[cards this]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
?. ?=([%group @ *] path)
|
||||
(on-watch:def path)
|
||||
?. (~(has by synced.state) t.path)
|
||||
(on-watch:def path)
|
||||
=/ scry-path=^path
|
||||
:(welp /(scot %p our.bowl)/group-store/(scot %da now.bowl) t.path /noun)
|
||||
=/ grp=(unit group)
|
||||
.^((unit group) %gx scry-path)
|
||||
?~ grp
|
||||
(on-watch:def path)
|
||||
:_ this
|
||||
[%give %fact ~ %group-update !>([%path u.grp t.path])]~
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card _this)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
?> ?=([@ @ *] path)
|
||||
:: ignore duplicate publish groups
|
||||
?: =(4 (lent path))
|
||||
~& "ignoring: {<path>}"
|
||||
~
|
||||
=/ pax=^path
|
||||
?: =('~' i.path)
|
||||
t.path
|
||||
path
|
||||
=/ rid=resource
|
||||
?> ?=([@ @ *] pax)
|
||||
=/ ship
|
||||
(slav %p i.pax)
|
||||
[ship i.t.pax]
|
||||
?: =(our.bowl host)
|
||||
`(add-push rid)
|
||||
`(add-pull rid host)
|
||||
::
|
||||
%watch-ack
|
||||
?~ p.sign
|
||||
[~ this]
|
||||
%- (slog u.p.sign)
|
||||
?> ?=([@ %group ^] wire)
|
||||
=/ =ship (slav %p i.wire)
|
||||
=* group t.t.wire
|
||||
:: only remove from synced if this watch-nack came from the ship we
|
||||
:: thought we were actively syncing from
|
||||
::
|
||||
=? synced.state
|
||||
=(ship (~(gut by synced.state) group ship))
|
||||
(~(del by synced.state) group)
|
||||
[~ this]
|
||||
++ poke-our
|
||||
|= [app=term =cage]
|
||||
^- card
|
||||
[%pass / %agent [our.bowl app] %poke cage]
|
||||
++ add-pull
|
||||
|= [rid=resource host=ship]
|
||||
^- card
|
||||
%+ poke-our
|
||||
%group-pull-hook
|
||||
:- %pull-hook-action
|
||||
!> ^- action:pull-hook
|
||||
[%add host rid]
|
||||
::
|
||||
%kick
|
||||
?> ?=([@ %group ^] wire)
|
||||
=/ =ship (slav %p i.wire)
|
||||
=* group t.t.wire
|
||||
?. (~(has by synced.state) group)
|
||||
[~ this]
|
||||
=* group-path t.wire
|
||||
:_ this
|
||||
[%pass wire %agent [ship %group-hook] %watch group-path]~
|
||||
::
|
||||
%fact
|
||||
?. ?=(%group-update p.cage.sign)
|
||||
(on-agent:def wire sign)
|
||||
=^ cards state
|
||||
?: (team:title our.bowl src.bowl)
|
||||
(handle-local:gc !<(group-update q.cage.sign))
|
||||
(handle-foreign:gc !<(group-update q.cage.sign))
|
||||
[cards this]
|
||||
==
|
||||
--
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
::
|
||||
++ poke-group-hook-action
|
||||
|= act=group-hook-action
|
||||
^- (quip card _state)
|
||||
?- -.act
|
||||
%add
|
||||
?. (team:title our.bol src.bol)
|
||||
[~ state]
|
||||
=/ group-path [%group path.act]
|
||||
=/ group-wire [(scot %p ship.act) group-path]
|
||||
?: (~(has by synced.state) path.act)
|
||||
[~ state]
|
||||
=. synced.state (~(put by synced.state) path.act ship.act)
|
||||
:_ state
|
||||
?: =(ship.act our.bol)
|
||||
[%pass group-wire %agent [ship.act %group-store] %watch group-path]~
|
||||
[%pass group-wire %agent [ship.act %group-hook] %watch group-path]~
|
||||
::
|
||||
%remove
|
||||
=/ ship (~(get by synced.state) path.act)
|
||||
?~ ship
|
||||
[~ state]
|
||||
?: &(=(u.ship our.bol) (team:title our.bol src.bol))
|
||||
:: delete one of our own paths
|
||||
=/ group-wire [(scot %p our.bol) %group path.act]
|
||||
:_ state(synced (~(del by synced.state) path.act))
|
||||
%+ snoc
|
||||
(pull-wire group-wire path.act)
|
||||
[%give %kick [%group path.act]~ ~]
|
||||
?: |(=(u.ship src.bol) (team:title our.bol src.bol))
|
||||
:: delete a foreign ship's path
|
||||
=/ group-wire [(scot %p u.ship) %group path.act]
|
||||
:_ state(synced (~(del by synced.state) path.act))
|
||||
(pull-wire group-wire path.act)
|
||||
:: don't allow
|
||||
[~ state]
|
||||
++ add-push
|
||||
|= rid=resource
|
||||
^- card
|
||||
%+ poke-our
|
||||
%group-push-hook
|
||||
:- %push-hook-action
|
||||
!> ^- action:push-hook
|
||||
[%add rid]
|
||||
--
|
||||
|
||||
==
|
||||
|
||||
::
|
||||
++ handle-local
|
||||
|= diff=group-update
|
||||
^- (quip card _state)
|
||||
?- -.diff
|
||||
%initial [~ state]
|
||||
%keys [~ state]
|
||||
%path [~ state]
|
||||
%bundle [~ state]
|
||||
%add [(update-subscribers [%group pax.diff] diff) state]
|
||||
%remove [(update-subscribers [%group pax.diff] diff) state]
|
||||
::
|
||||
%unbundle
|
||||
=/ ship (~(get by synced.state) pax.diff)
|
||||
?~ ship [~ state]
|
||||
(poke-group-hook-action [%remove pax.diff])
|
||||
==
|
||||
++ on-poke on-poke:def
|
||||
::
|
||||
++ handle-foreign
|
||||
|= diff=group-update
|
||||
^- (quip card _state)
|
||||
?- -.diff
|
||||
%initial [~ state]
|
||||
%keys [~ state]
|
||||
%bundle [~ state]
|
||||
%path
|
||||
:_ state
|
||||
?~ pax.diff ~
|
||||
=/ ship (~(get by synced.state) pax.diff)
|
||||
?~ ship ~
|
||||
?. =(src.bol u.ship) ~
|
||||
=/ have-group=(unit group)
|
||||
(group-scry pax.diff)
|
||||
?~ have-group
|
||||
:: if we don't have the group yet, create it
|
||||
::
|
||||
:~ (group-poke pax.diff [%bundle pax.diff])
|
||||
(group-poke pax.diff [%add members.diff pax.diff])
|
||||
==
|
||||
:: if we already have the group, calculate and apply the diff
|
||||
::
|
||||
=/ added=group (~(dif in members.diff) u.have-group)
|
||||
=/ removed=group (~(dif in u.have-group) members.diff)
|
||||
%+ weld
|
||||
?~ added ~
|
||||
[(group-poke pax.diff [%add added pax.diff])]~
|
||||
?~ removed ~
|
||||
[(group-poke pax.diff [%remove removed pax.diff])]~
|
||||
::
|
||||
%add
|
||||
:_ state
|
||||
?~ pax.diff ~
|
||||
=/ ship (~(get by synced.state) pax.diff)
|
||||
?~ ship ~
|
||||
?. =(src.bol u.ship) ~
|
||||
[(group-poke pax.diff diff)]~
|
||||
::
|
||||
%remove
|
||||
?~ pax.diff [~ state]
|
||||
=/ ship (~(get by synced.state) pax.diff)
|
||||
?~ ship [~ state]
|
||||
?. =(src.bol u.ship) [~ state]
|
||||
?. (~(has in members.diff) our.bol)
|
||||
:_ state
|
||||
[(group-poke pax.diff diff)]~
|
||||
=/ changes (poke-group-hook-action [%remove pax.diff])
|
||||
:_ +.changes
|
||||
%+ welp -.changes
|
||||
:~ (group-poke pax.diff diff)
|
||||
(group-poke pax.diff [%unbundle pax.diff])
|
||||
==
|
||||
::
|
||||
%unbundle
|
||||
?~ pax.diff [~ state]
|
||||
=/ ship (~(get by synced.state) pax.diff)
|
||||
?~ ship [~ state]
|
||||
?. =(src.bol u.ship) [~ state]
|
||||
(poke-group-hook-action [%remove pax.diff])
|
||||
==
|
||||
++ on-agent on-agent:def
|
||||
::
|
||||
++ group-poke
|
||||
|= [pax=path action=group-action]
|
||||
^- card
|
||||
[%pass pax %agent [our.bol %group-store] %poke %group-action !>(action)]
|
||||
++ on-watch on-watch:def
|
||||
::
|
||||
++ group-scry
|
||||
|= pax=path
|
||||
.^ (unit group)
|
||||
%gx
|
||||
(scot %p our.bol)
|
||||
%group-store
|
||||
(scot %da now.bol)
|
||||
(weld pax /noun)
|
||||
==
|
||||
::
|
||||
++ update-subscribers
|
||||
|= [pax=path diff=group-update]
|
||||
^- (list card)
|
||||
[%give %fact ~[pax] %group-update !>(diff)]~
|
||||
::
|
||||
++ pull-wire
|
||||
|= [wir=wire pax=path]
|
||||
^- (list card)
|
||||
=/ shp (~(get by synced.state) pax)
|
||||
?~ shp
|
||||
~
|
||||
?: =(u.shp our.bol)
|
||||
[%pass wir %agent [our.bol %group-store] %leave ~]~
|
||||
[%pass wir %agent [u.shp %group-hook] %leave ~]~
|
||||
++ on-leave on-leave:def
|
||||
|
||||
++ on-peek on-peek:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
|
49
pkg/arvo/app/group-pull-hook.hoon
Normal file
49
pkg/arvo/app/group-pull-hook.hoon
Normal file
@ -0,0 +1,49 @@
|
||||
:: group-hook: allow syncing group data from foreign paths to local paths
|
||||
::
|
||||
::
|
||||
/- *group, hook=group-hook, *invite-store, *resource
|
||||
/+ default-agent, verb, dbug, store=group-store, grpl=group, pull-hook
|
||||
~% %group-hook-top ..is ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
::
|
||||
++ config
|
||||
^- config:pull-hook
|
||||
:* %group-store
|
||||
update:store
|
||||
%group-update
|
||||
%group-push-hook
|
||||
==
|
||||
::
|
||||
--
|
||||
::
|
||||
::
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
%- (agent:pull-hook config)
|
||||
^- (pull-hook:pull-hook config)
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
dep ~(. (default:pull-hook this config) bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(~)
|
||||
++ on-load on-load:def
|
||||
++ on-poke on-poke:def
|
||||
++ 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
|
||||
++ on-pull-nack
|
||||
|= [=resource =tang]
|
||||
^- (quip card _this)
|
||||
[~ this]
|
||||
++ on-pull-kick
|
||||
|= =resource
|
||||
^- (unit path)
|
||||
`/
|
||||
--
|
122
pkg/arvo/app/group-push-hook.hoon
Normal file
122
pkg/arvo/app/group-push-hook.hoon
Normal file
@ -0,0 +1,122 @@
|
||||
:: group-hook: allow syncing group data from foreign paths to local paths
|
||||
::
|
||||
::
|
||||
/- *group, hook=group-hook, *invite-store
|
||||
/+ default-agent, verb, dbug, store=group-store, grpl=group, push-hook,
|
||||
resource
|
||||
~% %group-hook-top ..is ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
|
||||
::
|
||||
++ config
|
||||
^- config:push-hook
|
||||
:* %group-store
|
||||
/groups
|
||||
update:store
|
||||
%group-update
|
||||
%group-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)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(~)
|
||||
++ on-load on-load:def
|
||||
++ on-poke on-poke:def
|
||||
++ 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
|
||||
!<(update:store vase)
|
||||
?: ?=(%initial -.update)
|
||||
%.n
|
||||
|^
|
||||
=/ role=(unit (unit role-tag))
|
||||
(role-for-ship:grp resource.update src.bowl)
|
||||
?~ role
|
||||
non-member
|
||||
?~ u.role
|
||||
member
|
||||
?- u.u.role
|
||||
%admin admin
|
||||
%moderator moderator
|
||||
%janitor member
|
||||
==
|
||||
++ member
|
||||
?: ?=(%add-members -.update)
|
||||
=(~(tap in ships.update) ~[src.bowl])
|
||||
?: ?=(%remove-members -.update)
|
||||
=(~(tap in ships.update) ~[src.bowl])
|
||||
%.n
|
||||
++ admin
|
||||
!?=(?(%remove-group %add-group) -.update)
|
||||
++ moderator
|
||||
?= $? %add-members %remove-members
|
||||
%add-tag %remove-tag ==
|
||||
-.update
|
||||
++ non-member
|
||||
?& ?=(%add-members -.update)
|
||||
(can-join:grp resource.update src.bowl)
|
||||
=(~(tap in ships.update) ~[src.bowl])
|
||||
==
|
||||
--
|
||||
::
|
||||
++ resource-for-update
|
||||
|= =vase
|
||||
^- (unit resource)
|
||||
=/ =update:store
|
||||
!<(update:store vase)
|
||||
?: ?=(%initial -.update)
|
||||
~
|
||||
`resource.update
|
||||
::
|
||||
++ take-update
|
||||
|= =vase
|
||||
^- [(list card) agent]
|
||||
=/ =update:store
|
||||
!<(update:store vase)
|
||||
?: ?=(%remove-group -.update)
|
||||
=/ paths
|
||||
~[resource+(en-path:resource resource.update)]
|
||||
:_ this
|
||||
[%give %kick paths ~]~
|
||||
?. ?=(%remove-members -.update)
|
||||
[~ this]
|
||||
=/ paths
|
||||
~[resource+(en-path:resource resource.update)]
|
||||
:_ this
|
||||
%+ turn
|
||||
~(tap in ships.update)
|
||||
|= =ship
|
||||
[%give %kick paths `ship]
|
||||
::
|
||||
++ initial-watch
|
||||
|= [=path rid=resource]
|
||||
^- vase
|
||||
=/ group
|
||||
(scry-group:grp rid)
|
||||
?> ?=(^ group)
|
||||
?> (~(has in members.u.group) src.bowl)
|
||||
!> ^- update:store
|
||||
[%initial-group rid u.group]
|
||||
::
|
||||
--
|
@ -1,23 +1,60 @@
|
||||
:: group-store: data store for groups of ships
|
||||
:: group-store: Store groups of ships
|
||||
::
|
||||
/- *group-store
|
||||
/+ default-agent, verb, dbug
|
||||
:: group-store stores groups of ships, so that resources in other apps can be
|
||||
:: associated with a group. The current model of group-store rolls
|
||||
:: permissions and invites inside this store for simplicity reasons, although
|
||||
:: these should be prised apart in a future revision of group store.
|
||||
::
|
||||
::
|
||||
:: ## Scry paths
|
||||
::
|
||||
:: /y/groups:
|
||||
:: A listing of the current groups
|
||||
:: /x/groups/[resource]:
|
||||
:: The group itself
|
||||
:: /x/groups/[resource]/join/[ship]:
|
||||
:: A flag indicated if the ship is permitted to join
|
||||
::
|
||||
:: ## Subscription paths
|
||||
::
|
||||
:: /groups:
|
||||
:: A stream of the current updates to the state, sending the initial state
|
||||
:: upon subscribe.
|
||||
::
|
||||
:: ## Pokes
|
||||
::
|
||||
:: %group-action:
|
||||
:: Modify the group. Further documented in /sur/group-store.hoon
|
||||
::
|
||||
::
|
||||
/- *group, permission-store
|
||||
/+ store=group-store, default-agent, verb, dbug, resource
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
::
|
||||
+$ versioned-state
|
||||
$% state-zero
|
||||
state-one
|
||||
==
|
||||
::
|
||||
+$ state-zero
|
||||
$: %0
|
||||
=groups:state-zero:store
|
||||
==
|
||||
::
|
||||
::
|
||||
+$ state-one
|
||||
$: %1
|
||||
=groups
|
||||
==
|
||||
::
|
||||
+$ diff [%group-update group-update]
|
||||
+$ diff
|
||||
$% [%group-update update:store]
|
||||
[%group-initial groups]
|
||||
==
|
||||
--
|
||||
::
|
||||
=| state-zero
|
||||
=| state-one
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
@ -30,19 +67,153 @@
|
||||
gc ~(. group-core bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old=vase
|
||||
`this(state !<(state-zero old))
|
||||
|= =old=vase
|
||||
=/ old !<(versioned-state old-vase)
|
||||
?: ?=(%1 -.old)
|
||||
`this(state old)
|
||||
|^
|
||||
:- ~[kick-all]
|
||||
=* paths ~(key by groups.old)
|
||||
=/ [unmanaged=(list path) managed=(list path)]
|
||||
(skid ~(tap in paths) |=(=path =('~' (snag 0 path))))
|
||||
=. groups (all-unmanaged unmanaged)
|
||||
=. groups (all-managed managed)
|
||||
this
|
||||
::
|
||||
++ all-managed
|
||||
|= paths=(list path)
|
||||
^+ groups
|
||||
?~ paths
|
||||
groups
|
||||
=/ [rid=resource =group]
|
||||
(migrate-group i.paths)
|
||||
%= $
|
||||
paths t.paths
|
||||
::
|
||||
groups
|
||||
(~(put by groups) rid group)
|
||||
==
|
||||
::
|
||||
++ all-unmanaged
|
||||
|= paths=(list path)
|
||||
^+ groups
|
||||
?~ paths
|
||||
groups
|
||||
?: =(/~/default i.paths)
|
||||
$(paths t.paths)
|
||||
=/ [=resource =group]
|
||||
(migrate-unmanaged i.paths)
|
||||
%= $
|
||||
paths t.paths
|
||||
::
|
||||
groups
|
||||
(~(put by groups) resource group)
|
||||
==
|
||||
++ kick-all
|
||||
^- card
|
||||
:+ %give %kick
|
||||
:_ ~
|
||||
%~ tap by
|
||||
%+ roll ~(val by sup.bowl)
|
||||
|= [[=ship pax=path] paths=(set path)]
|
||||
(~(put in paths) pax)
|
||||
::
|
||||
++ migrate-unmanaged
|
||||
|= pax=path
|
||||
^- [resource group]
|
||||
=/ =group:state-zero:store
|
||||
(~(got by groups.old) pax)
|
||||
=/ [=policy members=(set ship)]
|
||||
(unmanaged-permissions pax)
|
||||
=. members
|
||||
(~(uni in members) group)
|
||||
?> ?=(^ pax)
|
||||
=/ rid=resource
|
||||
(resource-from-old-path t.pax)
|
||||
=/ =tags
|
||||
(~(put ju *tags) %admin entity.rid)
|
||||
[rid members tags policy %.y]
|
||||
::
|
||||
++ resource-from-old-path
|
||||
|= pax=path
|
||||
^- resource
|
||||
?> ?=([@ @ *] pax)
|
||||
=/ ship
|
||||
(slav %p i.pax)
|
||||
[ship i.t.pax]
|
||||
::
|
||||
++ unmanaged-permissions
|
||||
|= pax=path
|
||||
^- [policy (set ship)]
|
||||
=/ perm
|
||||
~| pax
|
||||
(scry-group-permissions pax)
|
||||
?~ perm
|
||||
[*invite:policy ~]
|
||||
?: ?=(%black kind.u.perm)
|
||||
:- [%open ~ who.u.perm]
|
||||
~
|
||||
:_ who.u.perm
|
||||
*invite:policy
|
||||
::
|
||||
++ migrate-group
|
||||
|= pax=path
|
||||
=/ members
|
||||
(~(got by groups.old) pax)
|
||||
=^ =policy members
|
||||
(migrate-permissions pax members)
|
||||
=/ rid=resource
|
||||
(resource-from-old-path pax)
|
||||
=/ =tags
|
||||
(~(put ju *tags) %admin entity.rid)
|
||||
[rid members tags policy %.n]
|
||||
::
|
||||
++ migrate-permissions
|
||||
|= [pax=path ships=(set ship)]
|
||||
^- [policy (set ship)]
|
||||
=/ perm
|
||||
(scry-group-permissions pax)
|
||||
?~ perm
|
||||
[*invite:policy ships]
|
||||
?> ?=(%white kind.u.perm)
|
||||
[[%invite ~] (~(uni in ships) who.u.perm)]
|
||||
::
|
||||
++ scry-unmanaged-groups
|
||||
^- (set path)
|
||||
.^ (set path)
|
||||
%gx
|
||||
(scot %p our.bowl)
|
||||
%permission-store
|
||||
(scot %da now.bowl)
|
||||
/keys/noun
|
||||
==
|
||||
::
|
||||
++ scry-group-permissions
|
||||
|= pax=path
|
||||
^- (unit permission:permission-store)
|
||||
.^ (unit permission:permission-store)
|
||||
%gx
|
||||
(scot %p our.bowl)
|
||||
%permission-store
|
||||
(scot %da now.bowl)
|
||||
;: weld
|
||||
/permission
|
||||
pax
|
||||
/noun
|
||||
==
|
||||
==
|
||||
--
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
=^ cards state
|
||||
?: ?=(%group-action mark)
|
||||
(poke-group-action:gc !<(group-action vase))
|
||||
?: ?=(?(%group-update %group-action) mark)
|
||||
(poke-group-update:gc !<(update:store vase))
|
||||
(on-poke:def mark vase)
|
||||
[cards this]
|
||||
::
|
||||
@ -50,22 +221,9 @@
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
|^
|
||||
=/ cards=(list card)
|
||||
?+ path (on-watch:def path)
|
||||
[%all ~] (give %group-update !>([%initial groups]))
|
||||
[%updates ~] ~
|
||||
[%keys ~] (give %group-update !>([%keys ~(key by groups)]))
|
||||
[%group *]
|
||||
(give %group-update !>([%path (~(got by groups) t.path) t.path]))
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ give
|
||||
|= =cage
|
||||
^- (list card)
|
||||
[%give %fact ~ cage]~
|
||||
--
|
||||
?> ?=([%groups ~] path)
|
||||
:_ this
|
||||
[%give %fact ~ %group-update !>([%initial groups])]~
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
::
|
||||
@ -73,7 +231,32 @@
|
||||
|= =path
|
||||
^- (unit (unit cage))
|
||||
?+ path (on-peek:def path)
|
||||
[%x *] ``noun+!>((~(get by groups) t.path))
|
||||
[%y %groups ~]
|
||||
=/ =arch
|
||||
:- ~
|
||||
%- malt
|
||||
%+ turn
|
||||
~(tap by groups)
|
||||
|= [rid=resource *]
|
||||
^- [@ta ~]
|
||||
=/ group=^path
|
||||
(en-path:resource rid)
|
||||
[(spat group) ~]
|
||||
``noun+!>(arch)
|
||||
::
|
||||
[%x %groups %ship @ @ ~]
|
||||
=/ rid=(unit resource)
|
||||
(de-path-soft:resource t.t.path)
|
||||
?~ rid ~
|
||||
``noun+!>((peek-group u.rid))
|
||||
::
|
||||
[%x %groups %ship @ @ %join @ ~]
|
||||
=/ rid=(unit resource)
|
||||
(de-path-soft:resource t.t.path)
|
||||
=/ =ship
|
||||
(slav %p i.t.t.t.t.t.t.path)
|
||||
?~ rid ~
|
||||
``noun+!>((peek-group-join u.rid ship))
|
||||
==
|
||||
::
|
||||
++ on-agent on-agent:def
|
||||
@ -82,85 +265,306 @@
|
||||
--
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
++ peek-group
|
||||
|= rid=resource
|
||||
^- (unit group)
|
||||
(~(get by groups) rid)
|
||||
|
||||
++ peek-group-join
|
||||
|= [rid=resource =ship]
|
||||
=/ =group
|
||||
(~(gut by groups) rid *group)
|
||||
=* policy policy.group
|
||||
?- -.policy
|
||||
%invite
|
||||
?| (~(has in pending.policy) ship)
|
||||
(~(has in members.group) ship)
|
||||
==
|
||||
%open
|
||||
?! ?|
|
||||
(~(has in banned.policy) ship)
|
||||
(~(has in ban-ranks.policy) (clan:title ship))
|
||||
==
|
||||
==
|
||||
::
|
||||
++ poke-group-action
|
||||
|= action=group-action
|
||||
++ poke-group-update
|
||||
|= =update:store
|
||||
^- (quip card _state)
|
||||
?> (team:title our.bol src.bol)
|
||||
?- -.action
|
||||
%add (handle-add action)
|
||||
%remove (handle-remove action)
|
||||
%bundle (handle-bundle action)
|
||||
%unbundle (handle-unbundle action)
|
||||
|^
|
||||
?- -.update
|
||||
%add-group (add-group +.update)
|
||||
%add-members (add-members +.update)
|
||||
%remove-members (remove-members +.update)
|
||||
%add-tag (add-tag +.update)
|
||||
%remove-tag (remove-tag +.update)
|
||||
%change-policy (change-policy +.update)
|
||||
%remove-group (remove-group +.update)
|
||||
%expose (expose +.update)
|
||||
%initial-group (initial-group +.update)
|
||||
%initial [~ state]
|
||||
==
|
||||
:: +expose: unset .hidden flag
|
||||
::
|
||||
++ expose
|
||||
|= [rid=resource ~]
|
||||
^- (quip card _state)
|
||||
=/ =group
|
||||
(~(got by groups) rid)
|
||||
=. hidden.group %.n
|
||||
=. groups
|
||||
(~(put by groups) rid group)
|
||||
:_ state
|
||||
(send-diff %expose rid ~)
|
||||
:: +add-group: add group to store
|
||||
::
|
||||
:: no-op if group already exists
|
||||
::
|
||||
++ add-group
|
||||
|= [rid=resource =policy hidden=?]
|
||||
^- (quip card _state)
|
||||
?< (~(has by groups) rid)
|
||||
=| =group
|
||||
=. policy.group policy
|
||||
=. hidden.group hidden
|
||||
=. tags.group
|
||||
(~(put ju tags.group) %admin our.bol)
|
||||
=. groups
|
||||
(~(put by groups) rid group)
|
||||
:_ state
|
||||
(send-diff %add-group rid policy hidden)
|
||||
:: +add-members: add members to group
|
||||
::
|
||||
++ add-members
|
||||
|= [rid=resource new-ships=(set ship)]
|
||||
^- (quip card _state)
|
||||
=. groups
|
||||
%+ ~(jab by groups) rid
|
||||
|= group
|
||||
%= +<
|
||||
members (~(uni in members) new-ships)
|
||||
::
|
||||
policy
|
||||
?. ?=(%invite -.policy)
|
||||
policy
|
||||
policy(pending (~(dif in pending.policy) new-ships))
|
||||
==
|
||||
:_ state
|
||||
(send-diff %add-members rid new-ships)
|
||||
:: +remove-members: remove members from group
|
||||
::
|
||||
:: no-op if group does not exist
|
||||
::
|
||||
::
|
||||
++ remove-members
|
||||
|= [rid=resource ships=(set ship)]
|
||||
^- (quip card _state)
|
||||
?. (~(has by groups) rid) [~ state]
|
||||
=. groups
|
||||
%+ ~(jab by groups) rid
|
||||
|= group
|
||||
%= +<
|
||||
members (~(dif in members) ships)
|
||||
tags (remove-tags +< ships)
|
||||
==
|
||||
:_ state
|
||||
(send-diff %remove-members rid ships)
|
||||
:: +add-tag: add tag to ships
|
||||
::
|
||||
:: crash if ships are not in group
|
||||
::
|
||||
++ add-tag
|
||||
|= [rid=resource =tag ships=(set ship)]
|
||||
^- (quip card _state)
|
||||
=. groups
|
||||
%+ ~(jab by groups) rid
|
||||
|= group
|
||||
?> ?=(~ (~(dif in ships) members))
|
||||
+<(tags (merge-tags tags ships (sy tag ~)))
|
||||
:_ state
|
||||
(send-diff %add-tag rid tag ships)
|
||||
:: +remove-tag: remove tag from ships
|
||||
::
|
||||
:: crash if ships are not in group or tag does not exist
|
||||
::
|
||||
++ remove-tag
|
||||
|= [rid=resource =tag ships=(set ship)]
|
||||
^- (quip card _state)
|
||||
=. groups
|
||||
%+ ~(jab by groups) rid
|
||||
|= group
|
||||
?> ?& ?=(~ (~(dif in ships) members))
|
||||
(~(has by tags) tag)
|
||||
==
|
||||
%= +<
|
||||
::
|
||||
tags
|
||||
%+ ~(jab by tags) tag
|
||||
|=((set ship) (~(dif in +<) ships))
|
||||
==
|
||||
:_ state
|
||||
(send-diff %remove-tag rid tag ships)
|
||||
:: initial-group: initialize foreign group
|
||||
::
|
||||
++ initial-group
|
||||
|= [rid=resource =group]
|
||||
^- (quip card _state)
|
||||
=. groups
|
||||
(~(put by groups) rid group)
|
||||
:_ state
|
||||
(send-diff %initial-group rid group)
|
||||
:: +change-policy: modify group access control
|
||||
::
|
||||
:: If the change will kick members, then send a separate
|
||||
:: %remove-members diff after the %change-policy diff
|
||||
++ change-policy
|
||||
|= [rid=resource =diff:policy]
|
||||
^- (quip card _state)
|
||||
?. (~(has by groups) rid)
|
||||
[~ state]
|
||||
=/ =group
|
||||
(~(got by groups) rid)
|
||||
|^
|
||||
=^ cards group
|
||||
?- -.diff
|
||||
%open (open +.diff)
|
||||
%invite (invite +.diff)
|
||||
%replace (replace +.diff)
|
||||
==
|
||||
=. groups
|
||||
(~(put by groups) rid group)
|
||||
:_ state
|
||||
%+ weld
|
||||
(send-diff %change-policy rid diff)
|
||||
cards
|
||||
::
|
||||
++ open
|
||||
|= =diff:open:policy
|
||||
?- -.diff
|
||||
%allow-ranks (allow-ranks +.diff)
|
||||
%ban-ranks (ban-ranks +.diff)
|
||||
%allow-ships (allow-ships +.diff)
|
||||
%ban-ships (ban-ships +.diff)
|
||||
==
|
||||
::
|
||||
++ invite
|
||||
|= =diff:invite:policy
|
||||
?- -.diff
|
||||
%add-invites (add-invites +.diff)
|
||||
%remove-invites (remove-invites +.diff)
|
||||
==
|
||||
::
|
||||
++ allow-ranks
|
||||
|= ranks=(set rank:title)
|
||||
^- (quip card _group)
|
||||
?> ?=(%open -.policy.group)
|
||||
=. ban-ranks.policy.group
|
||||
(~(dif in ban-ranks.policy.group) ranks)
|
||||
`group
|
||||
::
|
||||
++ ban-ranks
|
||||
|= ranks=(set rank:title)
|
||||
^- (quip card _group)
|
||||
?> ?=(%open -.policy.group)
|
||||
=. ban-ranks.policy.group
|
||||
(~(uni in ban-ranks.policy.group) ranks)
|
||||
`group
|
||||
::
|
||||
++ allow-ships
|
||||
|= ships=(set ship)
|
||||
^- (quip card _group)
|
||||
?> ?=(%open -.policy.group)
|
||||
=. banned.policy.group
|
||||
(~(dif in banned.policy.group) ships)
|
||||
`group
|
||||
::
|
||||
++ ban-ships
|
||||
|= ships=(set ship)
|
||||
^- (quip card _group)
|
||||
?> ?=(%open -.policy.group)
|
||||
=. banned.policy.group
|
||||
(~(uni in banned.policy.group) ships)
|
||||
=/ to-remove=(set ship)
|
||||
(~(int in members.group) banned.policy.group)
|
||||
:- ~[(poke-us %remove-members rid to-remove)]
|
||||
group
|
||||
::
|
||||
++ add-invites
|
||||
|= ships=(set ship)
|
||||
^- (quip card _group)
|
||||
?> ?=(%invite -.policy.group)
|
||||
=. pending.policy.group
|
||||
(~(uni in pending.policy.group) ships)
|
||||
`group
|
||||
::
|
||||
++ remove-invites
|
||||
|= ships=(set ship)
|
||||
^- (quip card _group)
|
||||
?> ?=(%invite -.policy.group)
|
||||
=. pending.policy.group
|
||||
(~(dif in pending.policy.group) ships)
|
||||
`group
|
||||
++ replace
|
||||
|= =policy
|
||||
^- (quip card _group)
|
||||
=. policy.group
|
||||
policy
|
||||
`group
|
||||
--
|
||||
:: +remove-group: remove group from store
|
||||
::
|
||||
:: no-op if group does not exist
|
||||
++ remove-group
|
||||
|= [rid=resource ~]
|
||||
^- (quip card _state)
|
||||
?. (~(has by groups) rid)
|
||||
`state
|
||||
=. groups
|
||||
(~(del by groups) rid)
|
||||
:_ state
|
||||
(send-diff %remove-group rid ~)
|
||||
::
|
||||
--
|
||||
|
||||
++ merge-tags
|
||||
|= [=tags ships=(set ship) new-tags=(set tag)]
|
||||
^+ tags
|
||||
=/ tags-list ~(tap in new-tags)
|
||||
|-
|
||||
?~ tags-list
|
||||
tags
|
||||
=* tag i.tags-list
|
||||
=/ old-ships=(set ship)
|
||||
(~(gut by tags) tag ~)
|
||||
%= $
|
||||
tags-list t.tags-list
|
||||
::
|
||||
tags
|
||||
%+ ~(put by tags)
|
||||
tag
|
||||
(~(uni in old-ships) ships)
|
||||
==
|
||||
++ remove-tags
|
||||
|= [=group ships=(set ship)]
|
||||
^- tags
|
||||
%- malt
|
||||
%+ turn
|
||||
~(tap by tags.group)
|
||||
|= [=tag tagged=(set ship)]
|
||||
:- tag
|
||||
(~(dif in tagged) ships)
|
||||
::
|
||||
++ handle-add
|
||||
|= act=group-action
|
||||
^- (quip card _state)
|
||||
?> ?=(%add -.act)
|
||||
?~ pax.act
|
||||
[~ state]
|
||||
?. (~(has by groups) pax.act)
|
||||
[~ state]
|
||||
=/ members (~(got by groups) pax.act)
|
||||
=. members (~(uni in members) members.act)
|
||||
?: =(members (~(got by groups) pax.act))
|
||||
[~ state]
|
||||
:- (send-diff pax.act act)
|
||||
state(groups (~(put by groups) pax.act members))
|
||||
::
|
||||
++ handle-remove
|
||||
|= act=group-action
|
||||
^- (quip card _state)
|
||||
?> ?=(%remove -.act)
|
||||
?~ pax.act
|
||||
[~ state]
|
||||
?. (~(has by groups) pax.act)
|
||||
[~ state]
|
||||
=/ members (~(got by groups) pax.act)
|
||||
=. members (~(dif in members) members.act)
|
||||
?: =(members (~(got by groups) pax.act))
|
||||
[~ state]
|
||||
:- (send-diff pax.act act)
|
||||
state(groups (~(put by groups) pax.act members))
|
||||
::
|
||||
++ handle-bundle
|
||||
|= act=group-action
|
||||
^- (quip card _state)
|
||||
?> ?=(%bundle -.act)
|
||||
?~ pax.act
|
||||
[~ state]
|
||||
?: (~(has by groups) pax.act)
|
||||
[~ state]
|
||||
:- (send-diff pax.act act)
|
||||
state(groups (~(put by groups) pax.act *group))
|
||||
::
|
||||
++ handle-unbundle
|
||||
|= act=group-action
|
||||
^- (quip card _state)
|
||||
?> ?=(%unbundle -.act)
|
||||
?~ pax.act
|
||||
[~ state]
|
||||
?. (~(has by groups) pax.act)
|
||||
[~ state]
|
||||
:- (send-diff pax.act act)
|
||||
state(groups (~(del by groups) pax.act))
|
||||
::
|
||||
++ update-subscribers
|
||||
|= [pax=path act=group-action]
|
||||
^- (list card)
|
||||
[%give %fact ~[pax] %group-update !>(act)]~
|
||||
++ poke-us
|
||||
|= =action:store
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-store] %poke %group-action !>(action)]
|
||||
:: +send-diff: update subscribers of new state
|
||||
::
|
||||
:: We only allow subscriptions on /groups
|
||||
:: so just give the fact there.
|
||||
++ send-diff
|
||||
|= [pax=path act=group-action]
|
||||
|= =update:store
|
||||
^- (list card)
|
||||
%- zing
|
||||
:~ (update-subscribers /all act)
|
||||
(update-subscribers /updates act)
|
||||
(update-subscribers [%group pax] act)
|
||||
?. |(=(%bundle -.act) =(%unbundle -.act))
|
||||
~
|
||||
(update-subscribers /keys act)
|
||||
==
|
||||
[%give %fact ~[/groups] %group-update !>(update)]~
|
||||
::
|
||||
--
|
||||
|
@ -17,6 +17,7 @@
|
||||
$% state
|
||||
state-7
|
||||
[ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)]
|
||||
[%7 drum=state:drum helm=state:helm kiln=state:kiln]
|
||||
==
|
||||
+$ any-state-tuple
|
||||
$: drum=any-state:drum
|
||||
|
@ -14,8 +14,9 @@
|
||||
:: to expede this process, we prod other potential listeners when we add
|
||||
:: them to our metadata+groups definition.
|
||||
::
|
||||
/- *link, listen-hook=link-listen-hook, *metadata-store, group-store
|
||||
/+ mdl=metadata, default-agent, verb, dbug, store=link-store
|
||||
::
|
||||
/- listen-hook=link-listen-hook, *metadata-store, *group, *link
|
||||
/+ mdl=metadata, default-agent, verb, dbug, group-store, grpl=group, resource, store=link-store
|
||||
::
|
||||
~% %link-listen-hook-top ..is ~
|
||||
|%
|
||||
@ -23,7 +24,9 @@
|
||||
$% [%0 state-0]
|
||||
[%1 state-1]
|
||||
[%2 state-2]
|
||||
[%3 state-3]
|
||||
==
|
||||
+$ state-3 state-1
|
||||
+$ state-2 state-1
|
||||
+$ state-1
|
||||
$: listening=(set app-path)
|
||||
@ -61,7 +64,7 @@
|
||||
+$ card card:agent:gall
|
||||
--
|
||||
::
|
||||
=| [%2 state-2]
|
||||
=| [%3 state-3]
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
@ -84,16 +87,24 @@
|
||||
^- (quip card _this)
|
||||
=/ old=versioned-state
|
||||
!<(versioned-state vase)
|
||||
=| cards=(list card)
|
||||
|-
|
||||
=* upgrade-loop $
|
||||
?- -.old
|
||||
%2 [~ this(state old)]
|
||||
%3 [cards this(state old)]
|
||||
::
|
||||
%2
|
||||
:_ this(state [%3 +.old])
|
||||
%+ welp cards
|
||||
:~ [%pass /groups %agent [our.bowl %group-store] %leave ~]
|
||||
watch-groups:do
|
||||
==
|
||||
::
|
||||
%1
|
||||
:: the upgrade from 0 left out local-only collections.
|
||||
:: here, we pull those back in.
|
||||
::
|
||||
=. state [%2 +.old]
|
||||
=. listening.state
|
||||
=. listening.old
|
||||
(~(run in ~(key by reasoning.old)) tail)
|
||||
=/ resources=(list [=group-path =app-path])
|
||||
%~ tap in
|
||||
@ -106,17 +117,16 @@
|
||||
(scot %da now.bowl)
|
||||
/app-indices
|
||||
==
|
||||
=| cards=(list card)
|
||||
|-
|
||||
?~ resources [cards this]
|
||||
?~ resources
|
||||
upgrade-loop(old [%2 +.old])
|
||||
=, i.resources
|
||||
=/ =group:group-store
|
||||
=- (fall - *group:group-store)
|
||||
(scry-for:do (unit group:group-store) %group-store group-path)
|
||||
=/ members=(set ship)
|
||||
(members-from-path:grp:do group-path)
|
||||
:: if we're the only group member, this got incorrectly ignored
|
||||
:: during 0's upgrade logic. watch it now.
|
||||
::
|
||||
?. &(=(1 ~(wyt in group)) (~(has in group) our.bowl))
|
||||
?. &(=(1 ~(wyt in members)) (~(has in members) our.bowl))
|
||||
$(resources t.resources)
|
||||
=^ more-cards state
|
||||
(handle-listen-action:do %watch app-path)
|
||||
@ -214,6 +224,7 @@
|
||||
::
|
||||
|_ =bowl:gall
|
||||
+* md ~(. mdl bowl)
|
||||
++ grp ~(. grpl bowl)
|
||||
::
|
||||
:: user actions & updates
|
||||
::
|
||||
@ -293,7 +304,12 @@
|
||||
?> =(%link app-name.resource.upd)
|
||||
:: auto-listen to collections in unmanaged groups only
|
||||
::
|
||||
?. ?=([%'~' ^] group-path.upd) [~ state]
|
||||
=/ rid=resource
|
||||
(de-path:resource group-path.upd)
|
||||
=/ =group
|
||||
(need (scry-group:grp rid))
|
||||
?. hidden.group
|
||||
[~ state]
|
||||
=, resource.upd
|
||||
=^ update listening
|
||||
^- (quip card _listening)
|
||||
@ -317,7 +333,7 @@
|
||||
::
|
||||
++ watch-groups
|
||||
^- card
|
||||
[%pass /groups %agent [our.bowl %group-store] %watch /all]
|
||||
[%pass /groups %agent [our.bowl %group-store] %watch /groups]
|
||||
::
|
||||
++ take-groups-sign
|
||||
|= =sign:agent:gall
|
||||
@ -338,20 +354,26 @@
|
||||
=* mark p.cage.sign
|
||||
=* vase q.cage.sign
|
||||
?+ mark ~|([dap.bowl %unexpected-mark mark] !!)
|
||||
%group-update (handle-group-update !<(group-update:group-store vase))
|
||||
%group-initial [~ state] ::NOTE initial handled using metadata
|
||||
%group-update (handle-group-update !<(update:group-store vase))
|
||||
==
|
||||
==
|
||||
::
|
||||
++ handle-group-update
|
||||
|= upd=group-update:group-store
|
||||
|= upd=update:group-store
|
||||
^- (quip card _state)
|
||||
:: NOTE initial handled using metadata
|
||||
?. ?=(?(%path %add %remove) -.upd)
|
||||
?. ?=(?(%add-members %initial-group %remove-members) -.upd)
|
||||
[~ state]
|
||||
=/ =path
|
||||
(en-path:resource resource.upd)
|
||||
=/ socs=(list app-path)
|
||||
(app-paths-from-group:md %link pax.upd)
|
||||
(app-paths-from-group:md %link path)
|
||||
=/ whos=(list ship)
|
||||
~(tap in members.upd)
|
||||
?- -.upd
|
||||
%add-members ~(tap in ships.upd)
|
||||
%remove-members ~(tap in ships.upd)
|
||||
%initial-group ~(tap in members.group.upd)
|
||||
==
|
||||
=| cards=(list card)
|
||||
|-
|
||||
=* loop-socs $
|
||||
@ -362,11 +384,11 @@
|
||||
=* loop-whos $
|
||||
?~ whos loop-socs(socs t.socs)
|
||||
=^ caz state
|
||||
?. ?=(%remove -.upd)
|
||||
(listen-to-peer i.socs pax.upd i.whos)
|
||||
?. ?=(%remove-members -.upd)
|
||||
(listen-to-peer i.socs path i.whos)
|
||||
?: =(our.bowl i.whos)
|
||||
(handle-listen-action %leave i.socs)
|
||||
(leave-from-peer i.socs pax.upd i.whos)
|
||||
(leave-from-peer i.socs path i.whos)
|
||||
loop-whos(whos t.whos, cards (weld cards caz))
|
||||
::
|
||||
:: link subscriptions
|
||||
@ -377,10 +399,7 @@
|
||||
=/ peers=(list ship)
|
||||
~| group-path
|
||||
%~ tap in
|
||||
=- (fall - *group:group-store)
|
||||
%^ scry-for (unit group:group-store)
|
||||
%group-store
|
||||
group-path
|
||||
(members-from-path:grp group-path)
|
||||
=| cards=(list card)
|
||||
|-
|
||||
?~ peers [cards state]
|
||||
@ -393,10 +412,7 @@
|
||||
^- (quip card _state)
|
||||
=/ peers=(list ship)
|
||||
%~ tap in
|
||||
=- (fall - *group:group-store)
|
||||
%^ scry-for (unit group:group-store)
|
||||
%group-store
|
||||
group-path
|
||||
(members-from-path:grp group-path)
|
||||
=| cards=(list card)
|
||||
|-
|
||||
?~ peers [cards state]
|
||||
@ -540,10 +556,9 @@
|
||||
%+ lien (groups-from-resource:md %link where.target)
|
||||
|= =group-path
|
||||
^- ?
|
||||
=- (~(has in (fall - *group:group-store)) who.target)
|
||||
%^ scry-for (unit group:group-store)
|
||||
%group-store
|
||||
group-path
|
||||
%. who.target
|
||||
~(has in (members-from-path:grp group-path))
|
||||
|
||||
::
|
||||
++ do-link-action
|
||||
|= [=wire =action:store]
|
||||
@ -594,11 +609,11 @@
|
||||
::
|
||||
%annotations
|
||||
%+ turn notes.update
|
||||
|= =^note
|
||||
|= =note
|
||||
^- card
|
||||
%+ do-link-action
|
||||
[%forward %annotation (scot %p who) where]
|
||||
[%read where url.update `comment`[who note]]
|
||||
`wire`[%forward %annotation (scot %p who) where]
|
||||
`action:store`[%read where url.update `comment`[who note]]
|
||||
==
|
||||
::
|
||||
++ take-forward-sign
|
||||
|
@ -19,8 +19,9 @@
|
||||
:: when adding support for new paths, the only things you'll likely want
|
||||
:: to touch are +permitted, +initial-response, & +kick-proxies.
|
||||
::
|
||||
/- *link, group-store, *metadata-store
|
||||
/+ store=link-store, metadata, default-agent, verb, dbug
|
||||
/- *link, *metadata-store, *group
|
||||
/+ metadata, default-agent, verb, dbug, group-store, grpl=group,
|
||||
resource, store=link-store
|
||||
~% %link-proxy-hook-top ..is ~
|
||||
|%
|
||||
+$ state-0
|
||||
@ -29,11 +30,20 @@
|
||||
:: but can't we use [wex sup]:bowl for that?
|
||||
active=(map path (set ship))
|
||||
==
|
||||
+$ state-1
|
||||
$: %1
|
||||
active=(map path (set ship))
|
||||
==
|
||||
::
|
||||
+$ versioned-state
|
||||
$% state-0
|
||||
state-1
|
||||
==
|
||||
::
|
||||
+$ card card:agent:gall
|
||||
--
|
||||
::
|
||||
=| state-0
|
||||
=| state-1
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
@ -52,9 +62,20 @@
|
||||
::
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old=vase
|
||||
|= old-vase=vase
|
||||
^- (quip card _this)
|
||||
[~ this(state !<(state-0 old))]
|
||||
=/ old
|
||||
!<(versioned-state old-vase)
|
||||
?- -.old
|
||||
%1 [~ this(state old)]
|
||||
::
|
||||
%0
|
||||
:_ this(state [%1 +.old])
|
||||
:~ [%pass /groups %agent [our.bowl %group-store] %leave ~]
|
||||
watch-groups:do
|
||||
==
|
||||
==
|
||||
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
@ -97,6 +118,7 @@
|
||||
::
|
||||
|_ =bowl:gall
|
||||
+* md ~(. metadata bowl)
|
||||
grp ~(. grpl bowl)
|
||||
::
|
||||
:: permissions
|
||||
::
|
||||
@ -117,10 +139,7 @@
|
||||
%+ lien (groups-from-resource:md %link u.target)
|
||||
|= =group-path
|
||||
^- ?
|
||||
=- (~(has in (fall - *group:group-store)) who)
|
||||
%^ scry-for (unit group:group-store)
|
||||
%group-store
|
||||
group-path
|
||||
(~(has in (members-from-path:grp group-path)) who)
|
||||
::
|
||||
++ kick-revoked-permissions
|
||||
|= [=path who=(list ship)]
|
||||
@ -177,17 +196,14 @@
|
||||
%+ kick-revoked-permissions
|
||||
app-path.resource.upd
|
||||
%~ tap in
|
||||
=- (fall - *group:group-store)
|
||||
%^ scry-for (unit group:group-store)
|
||||
%group-store
|
||||
group-path.upd
|
||||
(members-from-path:grp group-path.upd)
|
||||
::
|
||||
:: groups subscription
|
||||
::TODO largely copied from link-listen-hook. maybe make a store-listener lib?
|
||||
::
|
||||
++ watch-groups
|
||||
^- card
|
||||
[%pass /groups %agent [our.bowl %group-store] %watch /all]
|
||||
[%pass /groups %agent [our.bowl %group-store] %watch /groups]
|
||||
::
|
||||
++ take-groups-sign
|
||||
|= =sign:agent:gall
|
||||
@ -209,25 +225,25 @@
|
||||
=* vase q.cage.sign
|
||||
?+ mark ~|([dap.bowl %unexpected-mark mark] !!)
|
||||
%group-initial [~ state]
|
||||
%group-update (handle-group-update !<(group-update:group-store vase))
|
||||
%group-update (handle-group-update !<(update:group-store vase))
|
||||
==
|
||||
==
|
||||
::
|
||||
++ handle-group-update
|
||||
|= upd=group-update:group-store
|
||||
|= upd=update:group-store
|
||||
^- (quip card _state)
|
||||
:_ state
|
||||
?. ?=(%remove -.upd) ~
|
||||
?. ?=(%remove-members -.upd) ~
|
||||
:: if someone was removed from a group, find all link resources associated
|
||||
:: with that group, then kick their subscriptions if they're no longer
|
||||
::
|
||||
%- zing
|
||||
%+ turn (app-paths-from-group:md %link pax.upd)
|
||||
%+ turn (app-paths-from-group:md %link (en-path:resource resource.upd))
|
||||
|= =app-path
|
||||
^- (list card)
|
||||
%+ kick-revoked-permissions
|
||||
app-path
|
||||
~(tap in members.upd)
|
||||
~(tap in ships.upd)
|
||||
::
|
||||
:: proxy subscriptions
|
||||
::
|
||||
|
@ -15,9 +15,12 @@
|
||||
/- listen-hook=link-listen-hook
|
||||
/- group-hook, permission-hook, permission-group-hook
|
||||
/- metadata-hook, contact-view
|
||||
/+ store=link-store, metadata, *server, default-agent, verb, dbug
|
||||
/- pull-hook, *group
|
||||
/+ store=link-store, metadata, *server, default-agent, verb, dbug, grpl=group
|
||||
/+ group-store, resource
|
||||
~% %link-view-top ..is ~
|
||||
::
|
||||
::
|
||||
|%
|
||||
+$ versioned-state
|
||||
$% state-0
|
||||
@ -128,6 +131,18 @@
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card _this)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%poke-ack
|
||||
?. ?=([%join-group @ @ @ @ ~] wire)
|
||||
(on-agent:def wire sign)
|
||||
?^ p.sign
|
||||
(on-agent:def wire sign)
|
||||
=/ rid=resource
|
||||
(de-path:resource t.t.wire)
|
||||
=/ host=ship
|
||||
(slav %p i.t.wire)
|
||||
:_ this
|
||||
(joined-group:do host rid)
|
||||
::
|
||||
%kick
|
||||
:_ this
|
||||
=/ app=term
|
||||
@ -168,6 +183,7 @@
|
||||
~% %link-view-logic ..card ~
|
||||
|_ =bowl:gall
|
||||
+* md ~(. metadata bowl)
|
||||
grp ~(. grpl bowl)
|
||||
::
|
||||
++ page-size 25
|
||||
++ get-paginated
|
||||
@ -200,25 +216,49 @@
|
||||
^- card
|
||||
[%pass /create/[app]/[mark] %agent [our.bowl app] %poke mark vase]
|
||||
::
|
||||
++ joined-group
|
||||
|= [host=ship rid=resource]
|
||||
^- (list card)
|
||||
=/ =path
|
||||
(en-path:resource rid)
|
||||
:~
|
||||
:: sync the group
|
||||
::
|
||||
%^ do-poke %group-pull-hook
|
||||
%pull-hook-action
|
||||
!> ^- action:pull-hook
|
||||
[%add host rid]
|
||||
::
|
||||
:: sync the metadata
|
||||
::
|
||||
%^ do-poke %metadata-hook
|
||||
%metadata-hook-action
|
||||
!> ^- metadata-hook-action:metadata-hook
|
||||
[%add-synced host path]
|
||||
::
|
||||
:: sync the collection
|
||||
::
|
||||
%^ do-poke %link-listen-hook
|
||||
%link-listen-action
|
||||
!> ^- action:listen-hook
|
||||
[%watch ~[name.rid]]
|
||||
==
|
||||
::
|
||||
++ handle-invite-update
|
||||
|= upd=invite-update
|
||||
^- (list card)
|
||||
?. ?=(%accepted -.upd) ~
|
||||
?. =(/link path.upd) ~
|
||||
:~ :: sync the group
|
||||
::
|
||||
%^ do-poke %group-hook
|
||||
%group-hook-action
|
||||
!> ^- group-hook-action:group-hook
|
||||
[%add ship path]:invite.upd
|
||||
::
|
||||
:: sync the metadata
|
||||
::
|
||||
%^ do-poke %metadata-hook
|
||||
%metadata-hook-action
|
||||
!> ^- metadata-hook-action:metadata-hook
|
||||
[%add-synced ship path]:invite.upd
|
||||
==
|
||||
=/ rid=resource
|
||||
(de-path:resource path.invite.upd)
|
||||
:~ :: add self
|
||||
:* %pass
|
||||
[%join-group (scot %p ship.invite.upd) path.invite.upd]
|
||||
%agent [entity.rid %group-push-hook]
|
||||
%poke %group-update
|
||||
!> ^- action:group-store
|
||||
[%add-members rid (sy our.bowl ~)]
|
||||
== ==
|
||||
::
|
||||
++ handle-action
|
||||
|= =action:store
|
||||
@ -242,9 +282,7 @@
|
||||
%group path.members
|
||||
::
|
||||
%ships
|
||||
%+ weld
|
||||
?:(real-group ~ [~.~]~)
|
||||
[(scot %p our.bowl) path]
|
||||
[%ship (scot %p our.bowl) path]
|
||||
==
|
||||
=; group-setup=(list card)
|
||||
%+ weld group-setup
|
||||
@ -278,50 +316,38 @@
|
||||
==
|
||||
?: ?=(%group -.members) ~
|
||||
:: if the group is "real", make contact-view do the heavy lifting
|
||||
::
|
||||
=/ rid=resource
|
||||
(de-path:resource group-path)
|
||||
?: real-group
|
||||
:_ ~
|
||||
%^ do-poke %contact-view
|
||||
%contact-view-action
|
||||
!> ^- contact-view-action:contact-view
|
||||
[%create group-path ships.members title description]
|
||||
:- %^ do-poke %contact-view
|
||||
%contact-view-action
|
||||
!> ^- contact-view-action:contact-view
|
||||
[%groupify rid title description]
|
||||
%+ turn ~(tap in ships.members)
|
||||
|= =ship
|
||||
^- card
|
||||
%^ do-poke %invite-hook
|
||||
%invite-action
|
||||
!> ^- invite-action
|
||||
:^ %invite /link
|
||||
(sham group-path eny.bowl)
|
||||
:* our.bowl
|
||||
%group-hook
|
||||
group-path
|
||||
ship
|
||||
title
|
||||
==
|
||||
:: for "unmanaged" groups, do it ourselves
|
||||
::
|
||||
=/ =policy
|
||||
[%invite ships.members]
|
||||
:* :: create the new group
|
||||
::
|
||||
%^ do-poke %group-store
|
||||
%group-action
|
||||
!> ^- group-action:group-store
|
||||
[%bundle group-path]
|
||||
::
|
||||
:: fill the new group
|
||||
!> ^- action:group-store
|
||||
[%add-group rid policy %.y]
|
||||
::
|
||||
%^ do-poke %group-store
|
||||
%group-action
|
||||
!> ^- group-action:group-store
|
||||
[%add (~(put in ships.members) our.bowl) group-path]
|
||||
::
|
||||
:: make group available
|
||||
::
|
||||
%^ do-poke %group-hook
|
||||
%group-hook-action
|
||||
!> ^- group-hook-action:group-hook
|
||||
[%add our.bowl group-path]
|
||||
::
|
||||
:: mirror group into a permission
|
||||
::
|
||||
%^ do-poke %permission-group-hook
|
||||
%permission-group-hook-action
|
||||
!> ^- permission-group-hook-action:permission-group-hook
|
||||
[%associate group-path [group-path^%white ~ ~]]
|
||||
::
|
||||
:: expose the permission
|
||||
::
|
||||
%^ do-poke %permission-hook
|
||||
%permission-hook-action
|
||||
!> ^- permission-hook-action:permission-hook
|
||||
[%add-owned group-path group-path]
|
||||
::
|
||||
:: send invites
|
||||
::
|
||||
%+ turn ~(tap in ships.members)
|
||||
@ -348,6 +374,8 @@
|
||||
%- zing
|
||||
%+ turn groups
|
||||
|= =group=^path
|
||||
=/ rid=resource
|
||||
(de-path:resource group-path)
|
||||
%+ snoc
|
||||
^- (list card)
|
||||
:: if it's a real group, we can't/shouldn't unsync it. this leaves us with
|
||||
@ -359,8 +387,8 @@
|
||||
::
|
||||
:~ %^ do-poke %group-hook
|
||||
%group-hook-action
|
||||
!> ^- group-hook-action:group-hook
|
||||
[%remove group-path]
|
||||
!> ^- action:group-hook
|
||||
[%remove rid]
|
||||
::
|
||||
%^ do-poke %metadata-hook
|
||||
%metadata-hook-action
|
||||
@ -369,8 +397,8 @@
|
||||
::
|
||||
%^ do-poke %group-store
|
||||
%group-action
|
||||
!> ^- group-action:group-store
|
||||
[%unbundle group-path]
|
||||
!> ^- action:group-store
|
||||
[%remove-group rid ~]
|
||||
==
|
||||
:: remove collection from metadata-store
|
||||
::
|
||||
@ -386,29 +414,34 @@
|
||||
%+ turn (groups-from-resource:md %link path)
|
||||
|= =group=^path
|
||||
^- (list card)
|
||||
:- %^ do-poke %group-store
|
||||
%group-action
|
||||
!> ^- group-action:group-store
|
||||
[%add ships group-path]
|
||||
:: for managed groups, rely purely on group logic for invites
|
||||
=/ rid=resource
|
||||
(de-path:resource group-path)
|
||||
=/ =group
|
||||
(need (scry-group:grp rid))
|
||||
%- zing
|
||||
:~
|
||||
?. ?=(%invite -.policy.group)
|
||||
~
|
||||
:~ %^ do-poke %group-store
|
||||
%group-action
|
||||
!> ^- action:group-store
|
||||
[%change-policy rid %invite %add-invites ships]
|
||||
==
|
||||
::
|
||||
?. ?=([%'~' ^] group-path)
|
||||
~
|
||||
:: for unmanaged groups, send invites manually
|
||||
::
|
||||
%+ turn ~(tap in ships)
|
||||
|= =ship
|
||||
^- card
|
||||
%^ do-poke %invite-hook
|
||||
%invite-action
|
||||
!> ^- invite-action
|
||||
:^ %invite /link
|
||||
(sham group-path eny.bowl)
|
||||
:* our.bowl
|
||||
%group-hook
|
||||
group-path
|
||||
ship
|
||||
(rsh 3 1 (spat path))
|
||||
%+ turn ~(tap in ships)
|
||||
|= =ship
|
||||
^- card
|
||||
%^ do-poke %invite-hook
|
||||
%invite-action
|
||||
!> ^- invite-action
|
||||
:^ %invite /link
|
||||
(sham group-path eny.bowl)
|
||||
:* our.bowl
|
||||
%group-pull-hook
|
||||
group-path
|
||||
ship
|
||||
(rsh 3 1 (spat path))
|
||||
==
|
||||
==
|
||||
:: +give-tile-data: total unread count as json object
|
||||
::
|
||||
|
@ -4,7 +4,7 @@
|
||||
:: /group/%group-path all updates related to this group
|
||||
::
|
||||
/- *metadata-store, *metadata-hook
|
||||
/+ default-agent, dbug
|
||||
/+ default-agent, dbug, verb, grpl=group
|
||||
~% %metadata-hook-top ..is ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
@ -20,6 +20,7 @@
|
||||
=| state-zero
|
||||
=* state -
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
@ -73,6 +74,7 @@
|
||||
--
|
||||
::
|
||||
|_ =bowl:gall
|
||||
+* grp ~(. grpl bowl)
|
||||
++ poke-hook-action
|
||||
|= act=metadata-hook-action
|
||||
^- (quip card _state)
|
||||
@ -120,7 +122,7 @@
|
||||
%add (send group-path.act)
|
||||
%remove (send group-path.act)
|
||||
==
|
||||
?> (is-permitted src.bowl 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)
|
||||
@ -131,7 +133,6 @@
|
||||
^- (list card)
|
||||
=/ =ship
|
||||
%+ slav %p
|
||||
?: (is-managed group-path) (snag 0 group-path)
|
||||
(snag 1 group-path)
|
||||
=/ app ?:(=(ship our.bowl) %metadata-store %metadata-hook)
|
||||
(metadata-poke ship app)
|
||||
@ -153,11 +154,11 @@
|
||||
^- (list card)
|
||||
|^
|
||||
?> =(our.bowl (~(got by synced) path))
|
||||
?> (is-permitted src.bowl path)
|
||||
?> (is-member:grp src.bowl path)
|
||||
%+ turn ~(tap by (metadata-scry path))
|
||||
|= [[=group-path =resource] =metadata]
|
||||
|= [[=group-path =md-resource] =metadata]
|
||||
^- card
|
||||
[%give %fact ~ %metadata-update !>([%add group-path resource metadata])]
|
||||
[%give %fact ~ %metadata-update !>([%add group-path md-resource metadata])]
|
||||
::
|
||||
++ metadata-scry
|
||||
|= pax=^path
|
||||
@ -240,14 +241,4 @@
|
||||
?> ?=(^ wir)
|
||||
[~ ?~(saw state state(synced (~(del by synced) t.wir)))]
|
||||
::
|
||||
++ is-permitted
|
||||
|= [=ship pax=path]
|
||||
^- ?
|
||||
=. pax
|
||||
;: weld
|
||||
/(scot %p our.bowl)/permission-store/(scot %da now.bowl)/permitted
|
||||
[(scot %p ship) pax]
|
||||
/noun
|
||||
==
|
||||
.^(? %gx pax)
|
||||
--
|
||||
|
@ -21,24 +21,36 @@
|
||||
:: /app-name/%app-name associations for app
|
||||
:: /group/%group-path associations for group
|
||||
::
|
||||
/- *metadata-store
|
||||
/+ *metadata-json, default-agent, verb, dbug
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
::
|
||||
+$ versioned-state
|
||||
$% state-zero
|
||||
::
|
||||
+$ state-base
|
||||
$: =associations
|
||||
group-indices=(jug group-path md-resource)
|
||||
app-indices=(jug app-name [group-path app-path])
|
||||
resource-indices=(jug md-resource group-path)
|
||||
==
|
||||
::
|
||||
+$ state-zero
|
||||
$: %0
|
||||
=associations
|
||||
group-indices=(jug group-path resource)
|
||||
app-indices=(jug app-name [group-path app-path])
|
||||
resource-indices=(jug resource group-path)
|
||||
state-base
|
||||
==
|
||||
::
|
||||
+$ state-one
|
||||
$: %1
|
||||
state-base
|
||||
==
|
||||
::
|
||||
+$ versioned-state
|
||||
$% state-zero
|
||||
state-one
|
||||
==
|
||||
--
|
||||
::
|
||||
=| state-zero
|
||||
=| state-one
|
||||
=* state -
|
||||
%+ verb |
|
||||
%- agent:dbug
|
||||
@ -53,8 +65,91 @@
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old=vase
|
||||
`this(state !<(state-zero old))
|
||||
|= =vase
|
||||
^- (quip card _this)
|
||||
=/ old
|
||||
!<(versioned-state vase)
|
||||
?: ?=(%1 -.old)
|
||||
`this(state old)
|
||||
|^
|
||||
=/ new-state=state-one
|
||||
%* . *state-one
|
||||
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)
|
||||
==
|
||||
`this(state new-state)
|
||||
::
|
||||
++ 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
|
||||
::
|
||||
++ 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)]
|
||||
:_ (~(run in paths) new-group-path)
|
||||
(migrate-md-resource md-resource)
|
||||
::
|
||||
++ 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
|
||||
%- malt
|
||||
%+ turn
|
||||
~(tap by associations)
|
||||
|= [[=group-path =md-resource] =metadata]
|
||||
:_ metadata
|
||||
:_ (migrate-md-resource md-resource)
|
||||
(new-group-path group-path)
|
||||
--
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
@ -70,11 +165,11 @@
|
||||
?. ?=(%& -.val)
|
||||
(on-poke:def mark vase)
|
||||
=/ group=path +.p.val
|
||||
=/ res=(set resource) (~(get ju group-indices) group)
|
||||
=/ res=(set md-resource) (~(get ju group-indices) group)
|
||||
=. group-indices (~(del by group-indices) group)
|
||||
:- ~
|
||||
%+ roll ~(tap in res)
|
||||
|= [r=resource out=_state]
|
||||
|= [r=md-resource out=_state]
|
||||
=. resource-indices.out (~(del by resource-indices.out) r)
|
||||
=. app-indices.out
|
||||
%- ~(del ju app-indices.out)
|
||||
@ -126,8 +221,8 @@
|
||||
::
|
||||
[%x %metadata @ @ @ ~]
|
||||
=/ =group-path (stab (slav %t i.t.t.path))
|
||||
=/ =resource [`@tas`i.t.t.t.path (stab (slav %t i.t.t.t.t.path))]
|
||||
``noun+!>((~(get by associations) [group-path resource]))
|
||||
=/ =md-resource [`@tas`i.t.t.t.path (stab (slav %t i.t.t.t.t.path))]
|
||||
``noun+!>((~(get by associations) [group-path md-resource]))
|
||||
==
|
||||
::
|
||||
++ on-agent on-agent:def
|
||||
@ -149,42 +244,42 @@
|
||||
==
|
||||
::
|
||||
++ handle-add
|
||||
|= [=group-path =resource =metadata]
|
||||
|= [=group-path =md-resource =metadata]
|
||||
^- (quip card _state)
|
||||
:- %+ send-diff app-name.resource
|
||||
?. (~(has by resource-indices) resource)
|
||||
[%add group-path resource metadata]
|
||||
[%update-metadata group-path resource metadata]
|
||||
:- %+ send-diff app-name.md-resource
|
||||
?. (~(has by resource-indices) md-resource)
|
||||
[%add group-path md-resource metadata]
|
||||
[%update-metadata group-path md-resource metadata]
|
||||
%= state
|
||||
associations
|
||||
(~(put by associations) [group-path resource] metadata)
|
||||
(~(put by associations) [group-path md-resource] metadata)
|
||||
::
|
||||
group-indices
|
||||
(~(put ju group-indices) group-path resource)
|
||||
(~(put ju group-indices) group-path md-resource)
|
||||
::
|
||||
app-indices
|
||||
(~(put ju app-indices) app-name.resource [group-path app-path.resource])
|
||||
(~(put ju app-indices) app-name.md-resource [group-path app-path.md-resource])
|
||||
::
|
||||
resource-indices
|
||||
(~(put ju resource-indices) resource group-path)
|
||||
(~(put ju resource-indices) md-resource group-path)
|
||||
==
|
||||
::
|
||||
++ handle-remove
|
||||
|= [=group-path =resource]
|
||||
|= [=group-path =md-resource]
|
||||
^- (quip card _state)
|
||||
:- (send-diff app-name.resource [%remove group-path resource])
|
||||
:- (send-diff app-name.md-resource [%remove group-path md-resource])
|
||||
%= state
|
||||
associations
|
||||
(~(del by associations) [group-path resource])
|
||||
(~(del by associations) [group-path md-resource])
|
||||
::
|
||||
group-indices
|
||||
(~(del ju group-indices) group-path resource)
|
||||
(~(del ju group-indices) group-path md-resource)
|
||||
::
|
||||
app-indices
|
||||
(~(del ju app-indices) app-name.resource [group-path app-path.resource])
|
||||
(~(del ju app-indices) app-name.md-resource [group-path app-path.md-resource])
|
||||
::
|
||||
resource-indices
|
||||
(~(del ju resource-indices) resource group-path)
|
||||
(~(del ju resource-indices) md-resource group-path)
|
||||
==
|
||||
::
|
||||
++ metadata-for-app
|
||||
@ -201,9 +296,9 @@
|
||||
^- ^associations
|
||||
%- ~(gas by *^associations)
|
||||
%+ turn ~(tap in (~(gut by group-indices) group-path ~))
|
||||
|= =resource
|
||||
:- [group-path resource]
|
||||
(~(got by associations) [group-path resource])
|
||||
|= =md-resource
|
||||
:- [group-path md-resource]
|
||||
(~(got by associations) [group-path md-resource])
|
||||
::
|
||||
++ send-diff
|
||||
|= [=app-name upd=metadata-update]
|
||||
|
@ -27,213 +27,23 @@
|
||||
%+ verb |
|
||||
%- agent:dbug
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
do ~(. +> bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old=vase
|
||||
^- (quip card _this)
|
||||
[~ this(state !<(state-0 old))]
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%json
|
||||
:: only accept json from the host team
|
||||
::
|
||||
?> (team:title our.bowl src.bowl)
|
||||
=^ cards state
|
||||
%- handle-action:do
|
||||
%- json-to-perm-group-hook-action
|
||||
!<(json vase)
|
||||
[cards this]
|
||||
::
|
||||
%permission-group-hook-action
|
||||
=^ cards state
|
||||
%- handle-action:do
|
||||
!<(permission-group-hook-action vase)
|
||||
[cards this]
|
||||
==
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card _this)
|
||||
?. ?=([%group *] wire)
|
||||
(on-agent:def wire sign)
|
||||
?- -.sign
|
||||
%poke-ack ~|([dap.bowl %unexpected-poke-ack wire] !!)
|
||||
::
|
||||
%kick
|
||||
:_ this
|
||||
[(watch-group:do t.wire)]~
|
||||
::
|
||||
%watch-ack
|
||||
?~ p.sign [~ this]
|
||||
=/ =tank leaf+"{(trip dap.bowl)} failed subscribe at {(spud wire)}"
|
||||
%- (slog tank u.p.sign)
|
||||
[~ this(relation (~(del by relation) t.wire))]
|
||||
::
|
||||
%fact
|
||||
?. ?=(%group-update p.cage.sign)
|
||||
(on-agent:def wire sign)
|
||||
=^ cards state
|
||||
%- handle-group-update:do
|
||||
!<(group-update q.cage.sign)
|
||||
[cards this]
|
||||
==
|
||||
::
|
||||
++ on-peek on-peek:def
|
||||
++ on-watch on-watch:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
::
|
||||
|_ =bowl:gall
|
||||
++ handle-action
|
||||
|= act=permission-group-hook-action
|
||||
^- (quip card _state)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
?- -.act
|
||||
%associate (handle-associate group.act permissions.act)
|
||||
%dissociate (handle-dissociate group.act permissions.act)
|
||||
==
|
||||
+* this .
|
||||
do ~(. +> bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ handle-associate
|
||||
|= [group=group-path associate=(set [permission-path kind])]
|
||||
^- (quip card _state)
|
||||
=/ perms (~(get by relation) group)
|
||||
:: if relation does not exist, create it and subscribe.
|
||||
=/ perm-paths=(set path)
|
||||
(~(run in associate) head)
|
||||
?~ perms
|
||||
:_ state(relation (~(put by relation) group perm-paths))
|
||||
(snoc (recreate-permissions perm-paths associate) (watch-group group))
|
||||
::
|
||||
=/ grp (group-scry group)
|
||||
=. u.perms (~(uni in u.perms) perm-paths)
|
||||
:_ state(relation (~(put by relation) group u.perms))
|
||||
%+ weld
|
||||
(recreate-permissions perm-paths associate)
|
||||
?~ grp
|
||||
~
|
||||
(add-members group u.grp u.perms)
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old=vase
|
||||
^- (quip card _this)
|
||||
[~ this(state !<(state-0 old))]
|
||||
::
|
||||
++ handle-dissociate
|
||||
|= [group=path remove=(set permission-path)]
|
||||
^- (quip card _state)
|
||||
=/ perms=(set permission-path)
|
||||
(fall (~(get by relation) group) *(set permission-path))
|
||||
?: =(~ perms)
|
||||
[~ state]
|
||||
:: remove what we must. if that means we are no longer mirroring this group
|
||||
:: into any permissions, remove it from state entirely.
|
||||
::
|
||||
=. perms (~(del in perms) remove)
|
||||
?~ perms
|
||||
:_ state(relation (~(del by relation) group))
|
||||
[(group-pull group)]~
|
||||
[~ state(relation (~(put by relation) group perms))]
|
||||
::
|
||||
++ handle-group-update
|
||||
|= diff=group-update
|
||||
^- (quip card _state)
|
||||
?- -.diff
|
||||
%initial [~ state]
|
||||
%keys [~ state]
|
||||
%bundle [~ state]
|
||||
::
|
||||
%path
|
||||
:: set all permissions paths
|
||||
=/ perms (~(got by relation) pax.diff)
|
||||
:_ state
|
||||
(add-members pax.diff members.diff perms)
|
||||
::
|
||||
%add
|
||||
:: set all permissions paths
|
||||
=/ perms (~(get by relation) pax.diff)
|
||||
?~ perms
|
||||
[~ state]
|
||||
:_ state
|
||||
%+ turn ~(tap in u.perms)
|
||||
|= =path
|
||||
(permission-poke path [%add path members.diff])
|
||||
::
|
||||
%remove
|
||||
:: set all permissions paths
|
||||
=/ perms (~(get by relation) pax.diff)
|
||||
?~ perms
|
||||
[~ state]
|
||||
:_ state
|
||||
%+ turn ~(tap in u.perms)
|
||||
|= =path
|
||||
(permission-poke path [%remove path members.diff])
|
||||
::
|
||||
%unbundle
|
||||
:: pull subscriptions
|
||||
=/ perms (~(get by relation) pax.diff)
|
||||
?~ perms
|
||||
:_ state(relation (~(del by relation) pax.diff))
|
||||
[(group-pull pax.diff)]~
|
||||
:_ state(relation (~(del by relation) pax.diff))
|
||||
:- (group-pull pax.diff)
|
||||
%+ turn ~(tap in u.perms)
|
||||
|= =path
|
||||
(permission-poke path [%delete path])
|
||||
==
|
||||
::
|
||||
++ permission-poke
|
||||
|= [=wire action=permission-action]
|
||||
^- card
|
||||
:* %pass
|
||||
[%write wire]
|
||||
%agent
|
||||
[our.bowl %permission-store]
|
||||
%poke
|
||||
[%permission-action !>(action)]
|
||||
==
|
||||
::
|
||||
++ group-scry
|
||||
|= pax=path
|
||||
^- (unit group)
|
||||
=/ bek=path /(scot %p our.bowl)/group-store/(scot %da now.bowl)
|
||||
.^((unit group) %gx :(weld bek pax /noun))
|
||||
::
|
||||
++ add-members
|
||||
|= [pax=path mem=(set ship) perms=(set path)]
|
||||
^- (list card)
|
||||
%+ turn ~(tap in perms)
|
||||
|= =path
|
||||
(permission-poke path [%add path mem])
|
||||
::
|
||||
++ recreate-permissions
|
||||
|= [perm-paths=(set path) associate=(set [permission-path kind])]
|
||||
^- (list card)
|
||||
%+ weld
|
||||
%+ turn ~(tap in perm-paths)
|
||||
|= =path
|
||||
(permission-poke path [%delete path])
|
||||
%+ turn ~(tap in associate)
|
||||
|= [=path =kind]
|
||||
=| pem=permission
|
||||
=. kind.pem kind
|
||||
(permission-poke path [%create path pem])
|
||||
::
|
||||
::
|
||||
++ watch-group
|
||||
|= =group-path
|
||||
^- card
|
||||
=. group-path [%group group-path]
|
||||
[%pass group-path %agent [our.bowl %group-store] %watch group-path]
|
||||
::
|
||||
++ group-pull
|
||||
|= =group-path
|
||||
^- card
|
||||
[%pass [%group group-path] %agent [our.bowl %group-store] %leave ~]
|
||||
++ on-poke on-poke:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-watch on-watch:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
|
@ -1,14 +1,24 @@
|
||||
/- *publish
|
||||
/- *group-store
|
||||
/- *group-hook
|
||||
/- *group
|
||||
/- group-hook
|
||||
/- *permission-hook
|
||||
/- *permission-group-hook
|
||||
/- *permission-store
|
||||
/- *invite-store
|
||||
/- *metadata-store
|
||||
/- *metadata-hook
|
||||
/- *rw-security
|
||||
/+ *server, *publish, cram, default-agent, dbug
|
||||
/- contact-view
|
||||
/- pull-hook
|
||||
/- push-hook
|
||||
/+ *server
|
||||
/+ *publish
|
||||
/+ cram
|
||||
/+ default-agent
|
||||
/+ dbug
|
||||
/+ verb
|
||||
/+ grpl=group
|
||||
/+ group-store
|
||||
/+ resource
|
||||
::
|
||||
~% %publish ..is ~
|
||||
|%
|
||||
@ -42,6 +52,8 @@
|
||||
$% [%1 state-two]
|
||||
[%2 state-two]
|
||||
[%3 state-three]
|
||||
[%4 state-three]
|
||||
[%5 state-three]
|
||||
==
|
||||
::
|
||||
+$ metadata-delta
|
||||
@ -57,9 +69,10 @@
|
||||
==
|
||||
--
|
||||
::
|
||||
=| [%3 state-three]
|
||||
=| [%5 state-three]
|
||||
=* state -
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ bol=bowl:gall
|
||||
@ -85,6 +98,7 @@
|
||||
%poke %file-server-action
|
||||
!>([%serve-dir /'~publish' /app/landscape %.n])
|
||||
==
|
||||
[%pass /groups %agent [our.bol %group-store] %watch /groups]
|
||||
==
|
||||
::
|
||||
++ on-save !>(state)
|
||||
@ -174,15 +188,58 @@
|
||||
==
|
||||
::
|
||||
%3
|
||||
:_ this(state p.old-state)
|
||||
%+ welp cards
|
||||
:~ [%pass /bind %arvo %e %disconnect [~ /'~publish']]
|
||||
[%pass /view-bind %arvo %e %connect [~ /'publish-view'] %publish]
|
||||
:* %pass /srving %agent [our.bol %file-server]
|
||||
%poke %file-server-action
|
||||
!>([%serve-dir /'~publish' /app/landscape %.n])
|
||||
== ==
|
||||
%= $
|
||||
-.p.old-state %4
|
||||
::
|
||||
cards
|
||||
%+ welp cards
|
||||
:~ [%pass /bind %arvo %e %disconnect [~ /'~publish']]
|
||||
[%pass /view-bind %arvo %e %connect [~ /'publish-view'] %publish]
|
||||
:* %pass /srving %agent [our.bol %file-server]
|
||||
%poke %file-server-action
|
||||
!>([%serve-dir /'~publish' /app/landscape %.n])
|
||||
== ==
|
||||
==
|
||||
::
|
||||
%4
|
||||
%= $
|
||||
p.old-state
|
||||
=/ new-books=(map [@p @tas] notebook)
|
||||
%- ~(run by books.p.old-state)
|
||||
|= old-notebook=notebook-3
|
||||
^- notebook-3
|
||||
(convert-notebook-3-4 old-notebook)
|
||||
[%5 our-paths.p.old-state new-books tile-num.p.old-state [~ ~]]
|
||||
::
|
||||
cards
|
||||
%+ welp cards
|
||||
:~ [%pass /groups %agent [our.bol %group-store] %watch /groups]
|
||||
==
|
||||
==
|
||||
::
|
||||
%5
|
||||
[cards this(state p.old-state)]
|
||||
==
|
||||
++ convert-notebook-3-4
|
||||
|= prev=notebook-3
|
||||
^- notebook-3
|
||||
%= prev
|
||||
writers
|
||||
?> ?=(^ writers.prev)
|
||||
:- %ship
|
||||
?: =('~' i.writers.prev)
|
||||
t.writers.prev
|
||||
writers.prev
|
||||
::
|
||||
subscribers
|
||||
?> ?=(^ subscribers.prev)
|
||||
:- %ship
|
||||
%+ scag 2
|
||||
?: =('~' i.subscribers.prev)
|
||||
t.subscribers.prev
|
||||
subscribers.prev
|
||||
|
||||
==
|
||||
::
|
||||
++ convert-comment-2-3
|
||||
|= prev=comment-2
|
||||
@ -387,6 +444,14 @@
|
||||
^- (quip card _this)
|
||||
?- -.sin
|
||||
%poke-ack
|
||||
?: ?=([%join-group @ @ ~] wir)
|
||||
?^ p.sin
|
||||
(on-agent:def wir sin)
|
||||
=/ =ship
|
||||
(slav %p i.t.wir)
|
||||
=^ cards state
|
||||
(subscribe-notebook ship i.t.t.wir)
|
||||
[cards this]
|
||||
?~ p.sin
|
||||
[~ this]
|
||||
=^ cards state
|
||||
@ -426,6 +491,10 @@
|
||||
[%permissions ~]
|
||||
:_ this
|
||||
[%pass /permissions %agent [our.bol %permission-store] %watch /updates]~
|
||||
::
|
||||
[%groups ~]
|
||||
:_ this
|
||||
[%pass /groups %agent [our.bol %group-store] %watch /groups]~
|
||||
::
|
||||
[%invites ~]
|
||||
:_ this
|
||||
@ -445,9 +514,9 @@
|
||||
(handle-notebook-delta:main !<(notebook-delta q.cage.sin) state)
|
||||
[cards this]
|
||||
::
|
||||
[%permissions ~]
|
||||
[%groups ~]
|
||||
=^ cards state
|
||||
(handle-permission-update:main !<(permission-update q.cage.sin))
|
||||
(handle-group-update:main !<(update:group-store q.cage.sin))
|
||||
[cards this]
|
||||
::
|
||||
[%invites ~]
|
||||
@ -506,6 +575,13 @@
|
||||
--
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
++ grup ~(. grpl bol)
|
||||
::
|
||||
++ metadata-store-poke
|
||||
|= act=metadata-action
|
||||
^- card
|
||||
[%pass / %agent [our.bol %metadata-store] %poke %metadata-action !>(act)]
|
||||
::
|
||||
::
|
||||
++ get-last-update
|
||||
|= [host=@p book-name=@tas]
|
||||
@ -899,41 +975,31 @@
|
||||
%.n
|
||||
==
|
||||
::
|
||||
++ get-subscriber-paths
|
||||
|= [book-name=@tas who=@p]
|
||||
^- (list path)
|
||||
%+ roll ~(val by sup.bol)
|
||||
|= [[whom=@p pax=path] out=(list path)]
|
||||
?. =(who whom)
|
||||
out
|
||||
?. ?=([%notebook @ *] pax)
|
||||
out
|
||||
?. =(i.t.pax book-name)
|
||||
out
|
||||
[pax out]
|
||||
::
|
||||
++ handle-permission-update
|
||||
|= upd=permission-update
|
||||
++ handle-group-update
|
||||
|= =update:group-store
|
||||
^- (quip card _state)
|
||||
?. ?=(?(%remove %add) -.upd)
|
||||
?. ?=(?(%remove-members %add-members) -.update)
|
||||
[~ state]
|
||||
=* ships ships.update
|
||||
=/ =path
|
||||
(en-path:resource resource.update)
|
||||
=/ book=(unit @tas)
|
||||
%+ roll ~(tap by books)
|
||||
|= [[[who=@p nom=@tas] book=notebook] out=(unit @tas)]
|
||||
?. =(who our.bol)
|
||||
out
|
||||
?. =(path.upd subscribers.book)
|
||||
?. =(path subscribers.book)
|
||||
out
|
||||
`nom
|
||||
?~ book
|
||||
[~ state]
|
||||
:_ state
|
||||
%- zing
|
||||
%+ turn ~(tap in who.upd)
|
||||
%+ turn ~(tap in ships)
|
||||
|= who=@p
|
||||
?. (allowed who %read u.book)
|
||||
[%give %kick (get-subscriber-paths u.book who) `who]~
|
||||
?: ?|(?=(%remove -.upd) (is-managed path.upd))
|
||||
[%give %kick [/notebook/[u.book]]~ `who]~
|
||||
?: ?|(?=(%remove-members -.update) (is-managed-path:grup path))
|
||||
~
|
||||
=/ uid (sham %publish who u.book eny.bol)
|
||||
=/ inv=invite
|
||||
@ -959,11 +1025,35 @@
|
||||
[~ state]
|
||||
::
|
||||
%accepted
|
||||
?> ?=([%notebook @ ~] path.invite.upd)
|
||||
?> ?=([@ @ *] path.invite.upd)
|
||||
=/ book i.t.path.invite.upd
|
||||
=/ wir=wire /subscribe/(scot %p ship.invite.upd)/[book]
|
||||
=/ group
|
||||
(group-from-book notebook+book^~)
|
||||
?^ group
|
||||
(subscribe-notebook ship.invite.upd book)
|
||||
=/ rid=resource
|
||||
(de-path:resource ship+path.invite.upd)
|
||||
=/ join-wire=wire
|
||||
/join-group/[(scot %p ship.invite.upd)]/[book]
|
||||
=/ =cage
|
||||
:- %group-update
|
||||
!> ^- action:group-store
|
||||
[%add-members rid (sy our.bol ~)]
|
||||
:_ state
|
||||
[%pass wir %agent [ship.invite.upd %publish] %watch path.invite.upd]~
|
||||
[%pass join-wire %agent [entity.rid %group-push-hook] %poke cage]~
|
||||
==
|
||||
::
|
||||
++ subscribe-notebook
|
||||
|= [=ship book=@tas]
|
||||
^- (quip card _state)
|
||||
=/ pax=path /notebook/[book]
|
||||
=/ wir=wire /subscribe/[(scot %p ship)]/[book]
|
||||
=? tile-num (gth tile-num 0)
|
||||
(dec tile-num)
|
||||
=/ jon=json (frond:enjs:format %notifications (numb:enjs:format tile-num))
|
||||
:_ state
|
||||
:~ [%pass wir %agent [ship %publish] %watch pax]
|
||||
[%give %fact [/publishtile]~ %json !>(jon)]
|
||||
==
|
||||
::
|
||||
++ watch-notebook
|
||||
@ -984,17 +1074,26 @@
|
||||
::
|
||||
++ our-beak /(scot %p our.bol)/[q.byk.bol]/(scot %da now.bol)
|
||||
::
|
||||
++ book-writers
|
||||
|= [host=@p book=@tas]
|
||||
^- (set ship)
|
||||
=/ =notebook (~(got by books) host book)
|
||||
=/ rid=resource
|
||||
(de-path:resource writers.notebook)
|
||||
%- ~(uni in (fall (scry-tag:grup rid %admin) ~))
|
||||
%+ fall
|
||||
(scry-tag:grup rid `tag`[%publish (cat 3 %writers- book)])
|
||||
~
|
||||
::
|
||||
++ allowed
|
||||
|= [who=@p mod=?(%read %write) book=@tas]
|
||||
^- ?
|
||||
=/ scry-bek /(scot %p our.bol)/permission-store/(scot %da now.bol)
|
||||
=/ book=notebook (~(got by books) our.bol book)
|
||||
=/ scry-pax
|
||||
?: =(%read mod)
|
||||
subscribers.book
|
||||
writers.book
|
||||
=/ full-pax :(weld scry-bek /permitted/(scot %p who) scry-pax /noun)
|
||||
.^(? %gx full-pax)
|
||||
=/ =notebook (~(got by books) our.bol book)
|
||||
=/ rid=resource
|
||||
(de-path:resource writers.notebook)
|
||||
?: ?=(%read mod)
|
||||
(~(has in (members:grup rid)) who)
|
||||
(~(has in (book-writers our.bol book)) who)
|
||||
::
|
||||
++ write-file
|
||||
|= [pax=path cay=cage]
|
||||
@ -1053,21 +1152,33 @@
|
||||
[%give %fact [/primary]~ %publish-primary-delta !>(del)]
|
||||
::
|
||||
++ group-poke
|
||||
|= act=group-action
|
||||
|= act=action:group-store
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-store] %poke %group-action !>(act)]
|
||||
::
|
||||
++ group-hook-poke
|
||||
|= act=group-hook-action
|
||||
++ group-proxy-poke
|
||||
|= [who=ship act=action:group-store]
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-hook] %poke %group-hook-action !>(act)]
|
||||
[%pass / %agent [who %group-push-hook] %poke %group-update !>(act)]
|
||||
::
|
||||
++ contact-view-create
|
||||
|= [=path ships=(set ship) title=@t description=@t]
|
||||
=/ act [%create path ships title description]
|
||||
++ group-pull-hook-poke
|
||||
|= act=action:pull-hook
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-pull-hook] %poke %pull-hook-action !>(act)]
|
||||
::
|
||||
++ contact-view-poke
|
||||
|= act=contact-view-action:contact-view
|
||||
^- card
|
||||
[%pass / %agent [our.bol %contact-view] %poke %contact-view-action !>(act)]
|
||||
::
|
||||
++ contact-view-create
|
||||
|= [=path ships=(set ship) =policy title=@t description=@t]
|
||||
=/ rid=resource
|
||||
(de-path:resource path)
|
||||
=/ act=contact-view-action:contact-view
|
||||
[%create name.rid policy title description]
|
||||
(contact-view-poke act)
|
||||
::
|
||||
++ perm-hook-poke
|
||||
|= act=permission-hook-action
|
||||
^- card
|
||||
@ -1097,20 +1208,6 @@
|
||||
!>(act)
|
||||
==
|
||||
::
|
||||
++ create-security
|
||||
|= [read=path write=path sec=rw-security]
|
||||
^- (list card)
|
||||
=+ ^- [read-type=?(%black %white) write-type=?(%black %white)]
|
||||
?- sec
|
||||
%channel [%black %black]
|
||||
%village [%white %white]
|
||||
%journal [%black %white]
|
||||
%mailbox [%white %black]
|
||||
==
|
||||
:~ (perm-group-hook-poke [%associate read [[read read-type] ~ ~]])
|
||||
(perm-group-hook-poke [%associate write [[write write-type] ~ ~]])
|
||||
==
|
||||
::
|
||||
++ generate-invites
|
||||
|= [book=@tas invitees=(set ship)]
|
||||
^- (list card)
|
||||
@ -1118,7 +1215,7 @@
|
||||
|= who=ship
|
||||
=/ uid (sham %publish who book eny.bol)
|
||||
=/ inv=invite
|
||||
:* our.bol %publish /notebook/[book] who
|
||||
:* our.bol %publish /(scot %p our.bol)/[book] who
|
||||
(crip "invite for notebook {<our.bol>}/{(trip book)}")
|
||||
==
|
||||
=/ act=invite-action [%invite /publish uid inv]
|
||||
@ -1129,45 +1226,29 @@
|
||||
^- [(list card) write=path read=path]
|
||||
?> ?=(^ group-path.group)
|
||||
=/ scry-path
|
||||
;: weld
|
||||
/(scot %p our.bol)/group-store/(scot %da now.bol)
|
||||
group-path.group
|
||||
/noun
|
||||
==
|
||||
;:(welp /(scot %p our.bol)/group-store/(scot %da now.bol) [%groups group-path.group] /noun)
|
||||
=/ grp .^((unit ^group) %gx scry-path)
|
||||
?: use-preexisting.group
|
||||
?~ grp !!
|
||||
?. (is-managed group-path.group) !!
|
||||
:_ [group-path.group group-path.group]
|
||||
:~ %- perm-group-hook-poke
|
||||
[%associate group-path.group [[group-path.group %white] ~ ~]]
|
||||
::
|
||||
(perm-hook-poke [%add-owned group-path.group group-path.group])
|
||||
==
|
||||
`[group-path.group group-path.group]
|
||||
::
|
||||
=/ =policy
|
||||
*open:policy
|
||||
?: make-managed.group
|
||||
?^ grp [~ group-path.group group-path.group]
|
||||
?. (is-managed group-path.group) !!
|
||||
=/ whole-grp (~(put in invitees.group) our.bol)
|
||||
:_ [group-path.group group-path.group]
|
||||
[(contact-view-create [group-path.group whole-grp title about])]~
|
||||
[(contact-view-create [group-path.group whole-grp policy title about])]~
|
||||
:: make unmanaged group
|
||||
=* write-path group-path.group
|
||||
=/ read-path (weld write-path /read)
|
||||
?^ grp [~ write-path read-path]
|
||||
?: (is-managed group-path.group) !!
|
||||
:_ [write-path read-path]
|
||||
%- zing
|
||||
:~ [(group-poke [%bundle write-path])]~
|
||||
[(group-poke [%bundle read-path])]~
|
||||
[(group-hook-poke [%add our.bol write-path])]~
|
||||
[(group-hook-poke [%add our.bol read-path])]~
|
||||
[(group-poke [%add (sy our.bol ~) write-path])]~
|
||||
(create-security read-path write-path %journal)
|
||||
[(perm-hook-poke [%add-owned write-path write-path])]~
|
||||
[(perm-hook-poke [%add-owned read-path read-path])]~
|
||||
(generate-invites book (~(del in invitees.group) our.bol))
|
||||
==
|
||||
=* group-path group-path.group
|
||||
:_ [group-path group-path]
|
||||
?^ grp ~
|
||||
=/ rid=resource
|
||||
(de-path:resource group-path)
|
||||
:- (group-poke %add-group rid policy %.y)
|
||||
(generate-invites book (~(del in invitees.group) our.bol))
|
||||
::
|
||||
++ handle-poke-fail
|
||||
|= wir=wire
|
||||
@ -1602,14 +1683,12 @@
|
||||
?> ?=(^ writers.u.book)
|
||||
?> ?=(^ subscribers.u.book)
|
||||
=/ cards=(list card)
|
||||
:~ (delete-dir pax)
|
||||
(perm-hook-poke [%remove writers.u.book])
|
||||
(perm-hook-poke [%remove subscribers.u.book])
|
||||
==
|
||||
=? cards =('~' i.writers.u.book)
|
||||
[(group-poke [%unbundle writers.u.book]) cards]
|
||||
=? cards =('~' i.subscribers.u.book)
|
||||
[(group-poke [%unbundle subscribers.u.book]) cards]
|
||||
~[(delete-dir pax)]
|
||||
|
||||
=/ rid=resource
|
||||
(de-path:resource writers.u.book)
|
||||
=? cards (is-managed:grup rid)
|
||||
[(group-poke %remove-group rid ~) cards]
|
||||
[cards state]
|
||||
:: %del-note:
|
||||
:: If poke is from us, eagerly remove note from books, and place the
|
||||
@ -1710,18 +1789,33 @@
|
||||
::
|
||||
%subscribe
|
||||
?> (team:title our.bol src.bol)
|
||||
=/ wir=wire /subscribe/(scot %p who.act)/[book.act]
|
||||
=/ join-wire=wire
|
||||
/join-group/[(scot %p who.act)]/[book.act]
|
||||
=/ rid=resource
|
||||
[who.act book.act]
|
||||
=/ =cage
|
||||
:- %group-update
|
||||
!> ^- action:group-store
|
||||
[%add-members rid (sy our.bol ~)]
|
||||
:_ state
|
||||
[%pass wir %agent [who.act %publish] %watch /notebook/[book.act]]~
|
||||
[%pass join-wire %agent [who.act %group-push-hook] %poke cage]~
|
||||
:: %unsubscribe
|
||||
::
|
||||
%unsubscribe
|
||||
?> (team:title our.bol src.bol)
|
||||
=/ wir=wire /subscribe/(scot %p who.act)/[book.act]
|
||||
=/ del=primary-delta [%del-book who.act book.act]
|
||||
=/ book=notebook
|
||||
(~(got by books) who.act book.act)
|
||||
=/ rid=resource
|
||||
(de-path:resource writers.book)
|
||||
=/ =group
|
||||
(need (scry-group:grup rid))
|
||||
:_ state(books (~(del by books) who.act book.act))
|
||||
:~ `card`[%pass wir %agent [who.act %publish] %leave ~]
|
||||
`card`[%give %fact [/primary]~ %publish-primary-delta !>(del)]
|
||||
(group-proxy-poke who.act %remove-members rid (sy our.bol ~))
|
||||
(group-poke %remove-group rid ~)
|
||||
==
|
||||
:: %read
|
||||
::
|
||||
@ -1750,86 +1844,56 @@
|
||||
?~ book
|
||||
~|("nonexistent notebook: {<book.act>}" !!)
|
||||
::
|
||||
=/ old-write writers.u.book
|
||||
=/ old-read subscribers.u.book
|
||||
?> ?=([%'~' ^] old-write)
|
||||
=/ destroy-old-groups=(list card)
|
||||
:~ (group-poke [%unbundle old-write])
|
||||
(group-poke [%unbundle old-read])
|
||||
(group-hook-poke [%remove old-write])
|
||||
(group-hook-poke [%remove old-read])
|
||||
(perm-hook-poke [%remove old-write])
|
||||
(perm-hook-poke [%remove old-read])
|
||||
==
|
||||
::
|
||||
=* old-group-path writers.u.book
|
||||
=/ app-path /[(scot %p our.bol)]/[book.act]
|
||||
=/ =metadata
|
||||
(need (metadata-scry old-group-path app-path))
|
||||
=/ old-rid=resource
|
||||
(de-path:resource old-group-path)
|
||||
?< (is-managed:grup old-rid)
|
||||
?~ target.act
|
||||
:: create new group from subscribers
|
||||
::
|
||||
=. writers.u.book (slag 1 writers.u.book)
|
||||
=. subscribers.u.book writers.u.book
|
||||
=/ del=notebook-delta [%edit-book our.bol book.act u.book]
|
||||
:_ state(books (~(put by books) [our.bol book.act] u.book))
|
||||
%+ weld destroy-old-groups
|
||||
^- (list card)
|
||||
:~ [%give %fact [/notebook/[book.act]]~ %publish-notebook-delta !>(del)]
|
||||
[%give %fact [/primary]~ %publish-primary-delta !>(del)]
|
||||
%- contact-view-create
|
||||
:* writers.u.book
|
||||
(get-subscribers book.act)
|
||||
title.u.book
|
||||
description.u.book
|
||||
:: just create contacts object for group
|
||||
:_ state
|
||||
~[(contact-view-poke %groupify old-rid title.metadata description.metadata)]
|
||||
:: change associations
|
||||
=* group-path u.target.act
|
||||
=/ rid=resource
|
||||
(de-path:resource group-path)
|
||||
=/ old-group=group
|
||||
(need (scry-group:grup old-rid))
|
||||
=/ =group
|
||||
(need (scry-group:grup rid))
|
||||
=/ ships=(set ship)
|
||||
(~(dif in members.old-group) members.group)
|
||||
=. subscribers.u.book
|
||||
group-path
|
||||
=. writers.u.book
|
||||
group-path
|
||||
=. books
|
||||
(~(put by books) [our.bol book.act] u.book)
|
||||
=/ del
|
||||
[%edit-book our.bol book.act u.book]
|
||||
:_ state
|
||||
:* [%give %fact [/primary]~ %publish-primary-delta !>(del)]
|
||||
[%give %fact [/notebook/[book.act]]~ %publish-notebook-delta !>(del)]
|
||||
(metadata-store-poke %remove app-path %publish app-path)
|
||||
(metadata-store-poke %add group-path [%publish app-path] metadata)
|
||||
(group-poke %remove-group old-rid ~)
|
||||
?. inclusive.act
|
||||
~
|
||||
:- (group-poke %add-members rid ships)
|
||||
%+ turn
|
||||
~(tap in ships)
|
||||
|= =ship
|
||||
=/ =invite
|
||||
:* our.bol
|
||||
%contact-hook
|
||||
group-path
|
||||
ship ''
|
||||
==
|
||||
%- metadata-poke
|
||||
:* %add
|
||||
writers.u.book
|
||||
[%publish /(scot %p our.bol)/[book.act]]
|
||||
title.u.book
|
||||
description.u.book
|
||||
0x0
|
||||
date-created.u.book
|
||||
our.bol
|
||||
==
|
||||
==
|
||||
::
|
||||
?> ?=(^ u.target.act)
|
||||
=. writers.u.book u.target.act
|
||||
=. subscribers.u.book u.target.act
|
||||
=/ group-host=@p (slav %p i.u.target.act)
|
||||
::
|
||||
=/ scry-pax :(weld /=group-store/(scot %da now.bol) u.target.act /noun)
|
||||
=/ old-group=(set @p) (need .^((unit (set @p)) %gx scry-pax))
|
||||
=/ dif-peeps=(set @p) (~(dif in (get-subscribers book.act)) old-group)
|
||||
::
|
||||
=/ del=notebook-delta [%edit-book our.bol book.act u.book]
|
||||
:_ state(books (~(put by books) [our.bol book.act] u.book))
|
||||
%+ weld
|
||||
%+ weld destroy-old-groups
|
||||
^- (list card)
|
||||
:~ [%give %fact [/notebook/[book.act]]~ %publish-notebook-delta !>(del)]
|
||||
[%give %fact [/primary]~ %publish-primary-delta !>(del)]
|
||||
%- metadata-poke
|
||||
:* %add
|
||||
writers.u.book
|
||||
[%publish /(scot %p our.bol)/[book.act]]
|
||||
title.u.book
|
||||
description.u.book
|
||||
0x0
|
||||
date-created.u.book
|
||||
our.bol
|
||||
==
|
||||
==
|
||||
?: ?& inclusive.act
|
||||
=(group-host our.bol)
|
||||
==
|
||||
:: add all subscribers to group
|
||||
::
|
||||
[(group-poke [%add dif-peeps u.target.act])]~
|
||||
:: kick subscribers who are not already in group
|
||||
::
|
||||
%+ turn ~(tap in dif-peeps)
|
||||
|= who=@p
|
||||
^- card
|
||||
[%give %kick (get-subscriber-paths book.act who) `who]
|
||||
=/ act=invite-action [%invite /contacts (shaf %msg-uid eny.bol) invite]
|
||||
[%pass / %agent [our.bol %invite-hook] %poke %invite-action !>(act)]
|
||||
==
|
||||
==
|
||||
::
|
||||
++ get-subscribers
|
||||
@ -1871,6 +1935,23 @@
|
||||
^- card
|
||||
[%pass / %agent [our.bol %metadata-hook] %poke %metadata-action !>(act)]
|
||||
::
|
||||
::
|
||||
++ metadata-scry
|
||||
|= [group-path=path app-path=path]
|
||||
^- (unit metadata)
|
||||
?. .^(? %gu (scot %p our.bol) %metadata-store (scot %da now.bol) ~) ~
|
||||
.^ (unit metadata)
|
||||
%gx
|
||||
(scot %p our.bol)
|
||||
%metadata-store
|
||||
(scot %da now.bol)
|
||||
%metadata
|
||||
(scot %t (spat group-path))
|
||||
%publish
|
||||
(scot %t (spat app-path))
|
||||
/noun
|
||||
==
|
||||
::
|
||||
++ emit-metadata
|
||||
|= del=metadata-delta
|
||||
^- (list card)
|
||||
@ -1901,46 +1982,30 @@
|
||||
|= [group-path=path app-path=path =metadata]
|
||||
^- (list card)
|
||||
[(metadata-poke [%add group-path [%publish app-path] metadata])]~
|
||||
::
|
||||
++ metadata-scry
|
||||
|= [group-path=path app-path=path]
|
||||
^- (unit metadata)
|
||||
?. .^(? %gu (scot %p our.bol) %metadata-store (scot %da now.bol) ~) ~
|
||||
.^ (unit metadata)
|
||||
%gx
|
||||
--
|
||||
::
|
||||
++ group-from-book
|
||||
|= app-path=path
|
||||
^- (unit path)
|
||||
?. .^(? %gu (scot %p our.bol) %metadata-store (scot %da now.bol) ~)
|
||||
?: ?=([@ ^] app-path)
|
||||
~& [%assuming-ported-legacy-publish app-path]
|
||||
`[%'~' app-path]
|
||||
~&([%weird-publish app-path] ~)
|
||||
=/ resource-indices
|
||||
.^ (jug md-resource group-path)
|
||||
%gy
|
||||
(scot %p our.bol)
|
||||
%metadata-store
|
||||
(scot %da now.bol)
|
||||
%metadata
|
||||
(scot %t (spat group-path))
|
||||
%publish
|
||||
(scot %t (spat app-path))
|
||||
/noun
|
||||
/resource-indices
|
||||
==
|
||||
::
|
||||
++ group-from-book
|
||||
|= app-path=path
|
||||
^- (unit path)
|
||||
?. .^(? %gu (scot %p our.bol) %metadata-store (scot %da now.bol) ~)
|
||||
?: ?=([@ ^] app-path)
|
||||
~& [%assuming-ported-legacy-publish app-path]
|
||||
`[%'~' app-path]
|
||||
~&([%weird-publish app-path] ~)
|
||||
=/ resource-indices
|
||||
.^ (jug resource group-path)
|
||||
%gy
|
||||
(scot %p our.bol)
|
||||
%metadata-store
|
||||
(scot %da now.bol)
|
||||
/resource-indices
|
||||
==
|
||||
=/ groups=(unit (set path))
|
||||
(~(get by resource-indices) [%publish app-path])
|
||||
?~ groups ~
|
||||
=/ group-paths ~(tap in u.groups)
|
||||
?~ group-paths ~
|
||||
`i.group-paths
|
||||
--
|
||||
=/ groups=(unit (set path))
|
||||
(~(get by resource-indices) [%publish app-path])
|
||||
?~ groups ~
|
||||
=/ group-paths ~(tap in u.groups)
|
||||
?~ group-paths ~
|
||||
`i.group-paths
|
||||
::
|
||||
++ metadata-hook-poke
|
||||
|= act=metadata-hook-action
|
||||
@ -1977,9 +2042,10 @@
|
||||
(merge-notebooks (~(got by books) host.del book.del) data.del)
|
||||
=^ cards state
|
||||
(emit-updates-and-state host.del book.del data.del del sty)
|
||||
=/ rid=resource
|
||||
(de-path:resource writers.data.del)
|
||||
:_ state
|
||||
:* (group-hook-poke [%add host.del writers.data.del])
|
||||
(group-hook-poke [%add host.del subscribers.data.del])
|
||||
:* (group-pull-hook-poke [%add host.del rid])
|
||||
(metadata-hook-poke [%add-synced host.del writers.data.del])
|
||||
cards
|
||||
==
|
||||
@ -2137,6 +2203,19 @@
|
||||
?. =(book i.t.pax) out
|
||||
[[%s (scot %p who)] out]
|
||||
::
|
||||
++ get-writers-json
|
||||
|= [host=@p book=@tas]
|
||||
=/ =tag
|
||||
[%publish (cat 3 %writers- book)]
|
||||
^- json
|
||||
=/ writers=(list ship)
|
||||
~(tap in (book-writers host book))
|
||||
:- %a
|
||||
%+ turn writers
|
||||
|= who=@p
|
||||
^- json
|
||||
[%s (scot %p who)]
|
||||
::
|
||||
++ get-notebook-json
|
||||
|= [host=@p book-name=@tas]
|
||||
^- (unit json)
|
||||
@ -2151,6 +2230,8 @@
|
||||
=. p.notebook-json
|
||||
(~(put by p.notebook-json) %subscribers (get-subscribers-json book-name))
|
||||
=/ notebooks-json (notebooks-map:enjs our.bol books)
|
||||
=. p.notebook-json
|
||||
(~(put by p.notebook-json) %writers (get-writers-json host book-name))
|
||||
?> ?=(%o -.notebooks-json)
|
||||
=/ host-books-json (~(got by p.notebooks-json) (scot %p host))
|
||||
?> ?=(%o -.host-books-json)
|
||||
@ -2259,6 +2340,8 @@
|
||||
(~(uni by p.notebook-json) (notes-page:enjs notes.u.book 0 50))
|
||||
=. p.notebook-json
|
||||
(~(put by p.notebook-json) %subscribers (get-subscribers-json book-name))
|
||||
=. p.notebook-json
|
||||
(~(put by p.notebook-json) %writers (get-writers-json u.host book-name))
|
||||
(json-response:gen (json-to-octs (pairs notebook+notebook-json ~)))
|
||||
::
|
||||
:: single note, with initial 50 comments, as json
|
||||
|
10
pkg/arvo/gen/group-hook/add.hoon
Normal file
10
pkg/arvo/gen/group-hook/add.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: group-listen-hook|add: add a group
|
||||
::
|
||||
/- *group, *group-hook
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=ship =term ~] ~]
|
||||
==
|
||||
:- %group-hook-action
|
||||
^- action
|
||||
[%add ship term]
|
10
pkg/arvo/gen/group-hook/remove.hoon
Normal file
10
pkg/arvo/gen/group-hook/remove.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: group-listen-hook|remove: add a group
|
||||
::
|
||||
/- *group, *group-hook
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=ship =term ~] ~]
|
||||
==
|
||||
:- %group-hook-action
|
||||
^- action
|
||||
[%remove ship term]
|
@ -1,10 +1,10 @@
|
||||
:: group-store|add: add members to a group
|
||||
::
|
||||
/- *group-store
|
||||
/- *group, *group-store
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=path members=(list ship) ~] ~]
|
||||
[[=ship =term ships=(list ship) ~] ~]
|
||||
==
|
||||
:- %group-action
|
||||
^- group-action
|
||||
[%add (sy members) path]
|
||||
^- action
|
||||
[%add-members [ship term] (sy ships)]
|
||||
|
10
pkg/arvo/gen/group-store/allow-ranks.hoon
Normal file
10
pkg/arvo/gen/group-store/allow-ranks.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: group-store|allow-ranks: allow ranks for group
|
||||
::
|
||||
/- *group, *group-store
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=ship =term ranks=(list rank:title) ~] ~]
|
||||
==
|
||||
:- %group-update
|
||||
^- action
|
||||
[%change-policy [ship term] %open %allow-ranks (sy ranks)]
|
10
pkg/arvo/gen/group-store/allow-ships.hoon
Normal file
10
pkg/arvo/gen/group-store/allow-ships.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: group-store|allow-ships: remove ships from banlist
|
||||
::
|
||||
/- *group, *group-store
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=ship =term ships=(list ship) ~] ~]
|
||||
==
|
||||
:- %group-update
|
||||
^- action
|
||||
[%change-policy [ship term] %open %allow-ships (sy ships)]
|
10
pkg/arvo/gen/group-store/ban-ranks.hoon
Normal file
10
pkg/arvo/gen/group-store/ban-ranks.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: group-store|ban-ranks: ban ranks for group
|
||||
::
|
||||
/- *group, *group-store
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=ship =term ranks=(list rank:title) ~] ~]
|
||||
==
|
||||
:- %group-update
|
||||
^- action
|
||||
[%change-policy [ship term] %open %ban-ranks (sy ranks)]
|
10
pkg/arvo/gen/group-store/ban-ships.hoon
Normal file
10
pkg/arvo/gen/group-store/ban-ships.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: group-store|ban-ships: ban members from a group
|
||||
::
|
||||
/- *group, *group-store
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=ship =term ships=(list ship) ~] ~]
|
||||
==
|
||||
:- %group-update
|
||||
^- action
|
||||
[%change-policy [ship term] %open %ban-ships (sy ships)]
|
@ -1,10 +1,10 @@
|
||||
:: group-store|create: initialize a group
|
||||
::
|
||||
/- *group-store
|
||||
/- *group-store, *group
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=path ~] ~]
|
||||
[[=term ~] ~]
|
||||
==
|
||||
:- %group-action
|
||||
^- group-action
|
||||
[%bundle path]
|
||||
:- %group-update
|
||||
^- action
|
||||
[%add-group [p.beak term] *open:policy %.n]
|
||||
|
10
pkg/arvo/gen/group-store/join.hoon
Normal file
10
pkg/arvo/gen/group-store/join.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: group-store|join: join a group
|
||||
::
|
||||
/- *group-store
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=ship =term ~] ~]
|
||||
==
|
||||
:- %group-action
|
||||
^- action
|
||||
[%add-members [ship term] (sy p.beak ~)]
|
@ -3,8 +3,8 @@
|
||||
/- *group-store
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=path members=(list ship) ~] ~]
|
||||
[[=ship =term ships=(list ship) ~] ~]
|
||||
==
|
||||
:- %group-action
|
||||
^- group-action
|
||||
[%remove (sy members) path]
|
||||
^- action
|
||||
[%remove-members [p.beak term] (sy ships)]
|
||||
|
@ -1,4 +1,5 @@
|
||||
/- sur=chat-view, *rw-security
|
||||
/+ group-store
|
||||
^?
|
||||
=< [sur .]
|
||||
=, sur
|
||||
@ -17,6 +18,7 @@
|
||||
[%delete delete]
|
||||
[%join join]
|
||||
[%groupify groupify]
|
||||
[%invite invite]
|
||||
==
|
||||
::
|
||||
++ create
|
||||
@ -25,9 +27,10 @@
|
||||
[%description so]
|
||||
[%app-path pa]
|
||||
[%group-path pa]
|
||||
[%security sec]
|
||||
[%policy policy:dejs:group-store]
|
||||
[%members (as (su ;~(pfix sig fed:ag)))]
|
||||
[%allow-history bo]
|
||||
[%managed bo]
|
||||
==
|
||||
::
|
||||
++ delete
|
||||
@ -43,11 +46,11 @@
|
||||
++ groupify
|
||||
=- (ot [%app-path pa] [%existing -] ~)
|
||||
(mu (ot [%group-path pa] [%inclusive bo] ~))
|
||||
::
|
||||
++ sec
|
||||
=, dejs:format
|
||||
^- $-(json rw-security)
|
||||
(su (perk %channel %village %journal %mailbox ~))
|
||||
++ invite
|
||||
%- ot
|
||||
:~ app-path+pa
|
||||
ships+(as (su ;~(pfix sig fed:ag)))
|
||||
==
|
||||
--
|
||||
--
|
||||
--
|
||||
|
@ -1,5 +1,5 @@
|
||||
/- *contact-view, *contact-hook
|
||||
/+ base64
|
||||
/+ base64, group-store, resource
|
||||
|%
|
||||
++ nu :: parse number as hex
|
||||
|= jon/json
|
||||
@ -128,18 +128,27 @@
|
||||
%- of
|
||||
:~ [%create create]
|
||||
[%delete delete]
|
||||
[%join dejs:resource]
|
||||
[%invite invite]
|
||||
[%remove remove]
|
||||
[%share share]
|
||||
==
|
||||
::
|
||||
++ create
|
||||
%- ot
|
||||
:~ [%path pa]
|
||||
[%ships (as (su ;~(pfix sig fed:ag)))]
|
||||
:~ [%name so]
|
||||
[%policy policy:dejs:group-store]
|
||||
[%title so]
|
||||
[%description so]
|
||||
==
|
||||
::
|
||||
++ invite
|
||||
%- ot
|
||||
:~ [%resource dejs:resource]
|
||||
[%ship (su ;~(pfix sig fed:ag))]
|
||||
[%text so]
|
||||
==
|
||||
::
|
||||
++ delete (ot [%path pa]~)
|
||||
::
|
||||
++ remove
|
||||
|
468
pkg/arvo/lib/group-store.hoon
Normal file
468
pkg/arvo/lib/group-store.hoon
Normal file
@ -0,0 +1,468 @@
|
||||
/- *group, sur=group-store
|
||||
/+ resource
|
||||
^?
|
||||
=< [. sur]
|
||||
=, sur
|
||||
|%
|
||||
::
|
||||
++ dekebab
|
||||
|= str=cord
|
||||
^- cord
|
||||
=- (fall - str)
|
||||
%+ rush str
|
||||
=/ name
|
||||
%+ cook
|
||||
|= part=tape
|
||||
^- tape
|
||||
?~ part part
|
||||
:- (sub i.part 32)
|
||||
t.part
|
||||
(star low)
|
||||
%+ cook
|
||||
(cork (bake zing (list tape)) crip)
|
||||
;~(plug (star low) (more hep name))
|
||||
::
|
||||
++ enkebab
|
||||
|= str=cord
|
||||
^- cord
|
||||
~| str
|
||||
=- (fall - str)
|
||||
%+ rush str
|
||||
=/ name
|
||||
%+ cook
|
||||
|= part=tape
|
||||
^- tape
|
||||
?~ part part
|
||||
:- (add i.part 32)
|
||||
t.part
|
||||
;~(plug hig (star low))
|
||||
%+ cook
|
||||
|=(a=(list tape) (crip (zing (join "-" a))))
|
||||
;~(plug (star low) (star name))
|
||||
|
||||
++ migrate-path-map
|
||||
|* map=(map path *)
|
||||
=/ keys=(list path)
|
||||
(skim ~(tap in ~(key by map)) |=(=path =('~' (snag 0 path))))
|
||||
|-
|
||||
?~ keys
|
||||
map
|
||||
=* key i.keys
|
||||
?> ?=(^ key)
|
||||
=/ value
|
||||
(~(got by map) key)
|
||||
=. map
|
||||
(~(put by map) t.key value)
|
||||
=. map
|
||||
(~(del by map) key)
|
||||
$(keys t.keys, map (~(put by map) t.key value))
|
||||
::
|
||||
++ enjs
|
||||
=, enjs:format
|
||||
|%
|
||||
++ frond
|
||||
|= [p=@t q=json]
|
||||
^- json
|
||||
(frond:enjs:format (dekebab p) q)
|
||||
++ pairs
|
||||
|= a=(list [p=@t q=json])
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
%+ turn a
|
||||
|= [p=@t q=json]
|
||||
^- [@t json]
|
||||
[(dekebab p) q]
|
||||
::
|
||||
++ update
|
||||
|= =^update
|
||||
^- json
|
||||
%+ frond -.update
|
||||
?- -.update
|
||||
%add-group (add-group update)
|
||||
%add-members (add-members update)
|
||||
%add-tag (add-tag update)
|
||||
%remove-members (remove-members update)
|
||||
%remove-tag (remove-tag update)
|
||||
%initial (initial update)
|
||||
%initial-group (initial-group update)
|
||||
%remove-group (remove-group update)
|
||||
%change-policy (change-policy update)
|
||||
%expose (expose update)
|
||||
==
|
||||
::
|
||||
++ initial-group
|
||||
|= =^update
|
||||
?> ?=(%initial-group -.update)
|
||||
%- pairs
|
||||
:~ resource+(enjs:resource resource.update)
|
||||
group+(group group.update)
|
||||
==
|
||||
::
|
||||
++ initial
|
||||
|= =^initial
|
||||
?> ?=(%initial -.initial)
|
||||
%- pairs
|
||||
^- (list [@t json])
|
||||
%+ turn
|
||||
~(tap by groups.initial)
|
||||
|= [rid=resource grp=^group]
|
||||
^- [@t json]
|
||||
:_ (group grp)
|
||||
(enjs-path:resource rid)
|
||||
::
|
||||
++ group
|
||||
|= =^group
|
||||
^- json
|
||||
%- pairs
|
||||
:~ members+(set ship members.group)
|
||||
policy+(policy policy.group)
|
||||
tags+(tags tags.group)
|
||||
hidden+b+hidden.group
|
||||
==
|
||||
::
|
||||
++ rank
|
||||
|= =rank:title
|
||||
^- json
|
||||
[%s rank]
|
||||
++ tags
|
||||
|= =^tags
|
||||
^- json
|
||||
|^
|
||||
:- %o
|
||||
(~(uni by app) group)
|
||||
++ group
|
||||
^- (map @t json)
|
||||
%- malt
|
||||
%+ murn
|
||||
~(tap by tags)
|
||||
|= [=^tag ships=(^set ^ship)]
|
||||
^- (unit [@t json])
|
||||
?^ tag
|
||||
~
|
||||
`[tag (set ship ships)]
|
||||
++ app
|
||||
^- (map @t json)
|
||||
=| app-tags=(map @t json)
|
||||
=/ tags ~(tap by tags)
|
||||
|-
|
||||
?~ tags
|
||||
app-tags
|
||||
=* tag i.tags
|
||||
?@ p.tag
|
||||
$(tags t.tags)
|
||||
=/ app=json
|
||||
(~(gut by app-tags) app.p.tag [%o ~])
|
||||
?> ?=(%o -.app)
|
||||
=. p.app
|
||||
(~(put by p.app) tag.p.tag (set ship q.tag))
|
||||
=. app-tags
|
||||
(~(put by app-tags) app.p.tag app)
|
||||
$(tags t.tags)
|
||||
--
|
||||
::
|
||||
++ set
|
||||
|* [item=$-(* json) sit=(^set)]
|
||||
^- json
|
||||
:- %a
|
||||
%+ turn
|
||||
~(tap in sit)
|
||||
item
|
||||
++ tag
|
||||
|= =^tag
|
||||
^- json
|
||||
?@ tag
|
||||
(frond %tag s+tag)
|
||||
%- pairs
|
||||
:~ app+s+app.tag
|
||||
tag+s+tag.tag
|
||||
==
|
||||
::
|
||||
++ policy
|
||||
|= =^policy
|
||||
%+ frond -.policy
|
||||
%- pairs
|
||||
?- -.policy
|
||||
%invite
|
||||
:~ pending+(set ship pending.policy)
|
||||
==
|
||||
%open
|
||||
:~ banned+(set ship banned.policy)
|
||||
ban-ranks+(set rank ban-ranks.policy)
|
||||
==
|
||||
==
|
||||
++ policy-diff
|
||||
|= =diff:^policy
|
||||
%+ frond -.diff
|
||||
|^
|
||||
?- -.diff
|
||||
%invite (invite +.diff)
|
||||
%open (open +.diff)
|
||||
%replace (policy +.diff)
|
||||
==
|
||||
++ open
|
||||
|= =diff:open:^policy
|
||||
%+ frond -.diff
|
||||
?- -.diff
|
||||
%allow-ranks (set rank ranks.diff)
|
||||
%ban-ranks (set rank ranks.diff)
|
||||
%allow-ships (set ship ships.diff)
|
||||
%ban-ships (set ship ships.diff)
|
||||
==
|
||||
++ invite
|
||||
|= =diff:invite:^policy
|
||||
%+ frond -.diff
|
||||
?- -.diff
|
||||
%add-invites (set ship invitees.diff)
|
||||
%remove-invites (set ship invitees.diff)
|
||||
==
|
||||
--
|
||||
::
|
||||
++ expose
|
||||
|= =^update
|
||||
^- json
|
||||
?> ?=(%expose -.update)
|
||||
(frond %resource (enjs:resource resource.update))
|
||||
::
|
||||
++ remove-group
|
||||
|= =^update
|
||||
^- json
|
||||
?> ?=(%remove-group -.update)
|
||||
(frond %resource (enjs:resource resource.update))
|
||||
::
|
||||
++ add-group
|
||||
|= =action
|
||||
^- json
|
||||
?> ?=(%add-group -.action)
|
||||
%- pairs
|
||||
:~ resource+(enjs:resource resource.action)
|
||||
policy+(policy policy.action)
|
||||
hidden+b+hidden.action
|
||||
==
|
||||
::
|
||||
++ add-members
|
||||
|= =action
|
||||
^- json
|
||||
?> ?=(%add-members -.action)
|
||||
%- pairs
|
||||
:~ resource+(enjs:resource resource.action)
|
||||
ships+(set ship ships.action)
|
||||
==
|
||||
::
|
||||
++ remove-members
|
||||
|= =action
|
||||
^- json
|
||||
?> ?=(%remove-members -.action)
|
||||
%- pairs
|
||||
:~ resource+(enjs:resource resource.action)
|
||||
ships+(set ship ships.action)
|
||||
==
|
||||
::
|
||||
++ add-tag
|
||||
|= =action
|
||||
^- json
|
||||
?> ?=(%add-tag -.action)
|
||||
%- pairs
|
||||
^- (list [p=@t q=json])
|
||||
:~ resource+(enjs:resource resource.action)
|
||||
tag+(tag tag.action)
|
||||
ships+(set ship ships.action)
|
||||
==
|
||||
::
|
||||
++ remove-tag
|
||||
|= =action
|
||||
^- json
|
||||
?> ?=(%remove-tag -.action)
|
||||
%- pairs
|
||||
:~ resource+(enjs:resource resource.action)
|
||||
tag+(tag tag.action)
|
||||
ships+(set ship ships.action)
|
||||
==
|
||||
::
|
||||
++ change-policy
|
||||
|= =action
|
||||
^- json
|
||||
?> ?=(%change-policy -.action)
|
||||
%- pairs
|
||||
:~ resource+(enjs:resource resource.action)
|
||||
diff+(policy-diff diff.action)
|
||||
==
|
||||
--
|
||||
++ dejs
|
||||
=, dejs:format
|
||||
|%
|
||||
::
|
||||
++ ruk-jon
|
||||
|= [a=(map @t json) b=$-(@t @t)]
|
||||
^+ a
|
||||
=- (malt -)
|
||||
|-
|
||||
^- (list [@t json])
|
||||
?~ a ~
|
||||
:- [(b p.n.a) q.n.a]
|
||||
%+ weld
|
||||
$(a l.a)
|
||||
$(a r.a)
|
||||
::
|
||||
++ of
|
||||
|* wer/(pole {cord fist})
|
||||
|= jon/json
|
||||
?> ?=({$o {@ *} $~ $~} jon)
|
||||
|-
|
||||
?- wer
|
||||
:: {{key/@t wit/*} t/*}
|
||||
{{key/@t *} t/*}
|
||||
=> .(wer [[* wit] *]=wer)
|
||||
?: =(key.wer (enkebab p.n.p.jon))
|
||||
[key.wer ~|(val+q.n.p.jon (wit.wer q.n.p.jon))]
|
||||
?~ t.wer ~|(bad-key+p.n.p.jon !!)
|
||||
((of t.wer) jon)
|
||||
==
|
||||
++ ot
|
||||
|* wer=(pole [cord fist])
|
||||
|= jon=json
|
||||
~| jon
|
||||
%- (ot-raw:dejs:format wer)
|
||||
?> ?=(%o -.jon)
|
||||
(ruk-jon p.jon enkebab)
|
||||
::
|
||||
++ update
|
||||
^- $-(json ^update)
|
||||
|= jon=json
|
||||
^- ^update
|
||||
%. jon
|
||||
%- of
|
||||
:~
|
||||
add-group+add-group
|
||||
add-members+add-members
|
||||
remove-members+remove-members
|
||||
add-tag+add-tag
|
||||
remove-tag+remove-tag
|
||||
change-policy+change-policy
|
||||
remove-group+remove-group
|
||||
expose+expose
|
||||
==
|
||||
++ rank
|
||||
|= =json
|
||||
^- rank:title
|
||||
?> ?=(%s -.json)
|
||||
?: =('czar' p.json) %czar
|
||||
?: =('king' p.json) %king
|
||||
?: =('duke' p.json) %duke
|
||||
?: =('earl' p.json) %earl
|
||||
?: =('pawn' p.json) %pawn
|
||||
!!
|
||||
++ tag
|
||||
|= =json
|
||||
^- ^tag
|
||||
?> ?=(%o -.json)
|
||||
?. (~(has by p.json) 'app')
|
||||
=/ tag-json
|
||||
(~(got by p.json) 'tag')
|
||||
?> ?=(%s -.tag-json)
|
||||
?: =('admin' p.tag-json) %admin
|
||||
?: =('moderator' p.tag-json) %moderator
|
||||
?: =('janitor' p.tag-json) %janitor
|
||||
!!
|
||||
%. json
|
||||
%- ot
|
||||
:~ app+so
|
||||
tag+so
|
||||
==
|
||||
|
||||
:: move to zuse also
|
||||
++ oj
|
||||
|* =fist
|
||||
^- $-(json (jug cord _(fist *json)))
|
||||
(om (as fist))
|
||||
++ tags
|
||||
^- $-(json ^tags)
|
||||
*$-(json ^tags)
|
||||
:: TODO: move to zuse
|
||||
++ ship
|
||||
(su ;~(pfix sig fed:ag))
|
||||
++ policy
|
||||
^- $-(json ^policy)
|
||||
%- of
|
||||
:~ invite+invite-policy
|
||||
open+open-policy
|
||||
==
|
||||
++ invite-policy
|
||||
%- ot
|
||||
:~ pending+(as ship)
|
||||
==
|
||||
++ open-policy
|
||||
%- ot
|
||||
:~ ban-ranks+(as rank)
|
||||
banned+(as ship)
|
||||
==
|
||||
++ open-policy-diff
|
||||
%- of
|
||||
:~ allow-ranks+(as rank)
|
||||
allow-ships+(as ship)
|
||||
ban-ranks+(as rank)
|
||||
ban-ships+(as ship)
|
||||
==
|
||||
++ invite-policy-diff
|
||||
%- of
|
||||
:~ add-invites+(as ship)
|
||||
remove-invites+(as ship)
|
||||
==
|
||||
++ policy-diff
|
||||
^- $-(json diff:^policy)
|
||||
%- of
|
||||
:~ invite+invite-policy-diff
|
||||
open+open-policy-diff
|
||||
replace+policy
|
||||
==
|
||||
::
|
||||
++ remove-group
|
||||
|= =json
|
||||
?> ?=(%o -.json)
|
||||
=/ rid=resource
|
||||
(dejs:resource (~(got by p.json) 'resource'))
|
||||
[rid ~]
|
||||
::
|
||||
++ expose
|
||||
|= =json
|
||||
^- [resource ~]
|
||||
?> ?=(%o -.json)
|
||||
=/ rid=resource
|
||||
(dejs:resource (~(got by p.json) 'resource'))
|
||||
[rid ~]
|
||||
::
|
||||
++ add-group
|
||||
%- ot
|
||||
:~ resource+dejs:resource
|
||||
policy+policy
|
||||
hidden+bo
|
||||
==
|
||||
++ add-members
|
||||
%- ot
|
||||
:~ resource+dejs:resource
|
||||
ships+(as ship)
|
||||
==
|
||||
++ remove-members
|
||||
^- $-(json [resource (set ^ship)])
|
||||
%- ot
|
||||
:~ resource+dejs:resource
|
||||
ships+(as ship)
|
||||
==
|
||||
++ add-tag
|
||||
%- ot
|
||||
:~ resource+dejs:resource
|
||||
tag+tag
|
||||
ships+(as ship)
|
||||
==
|
||||
++ remove-tag
|
||||
%- ot
|
||||
:~ resource+dejs:resource
|
||||
tag+tag
|
||||
ships+(as ship)
|
||||
==
|
||||
++ change-policy
|
||||
%- ot
|
||||
:~ resource+dejs:resource
|
||||
diff+policy-diff
|
||||
==
|
||||
--
|
||||
--
|
107
pkg/arvo/lib/group.hoon
Normal file
107
pkg/arvo/lib/group.hoon
Normal file
@ -0,0 +1,107 @@
|
||||
/- *group, *metadata-store, hook=group-hook
|
||||
/+ store=group-store, resource
|
||||
::
|
||||
|_ =bowl:gall
|
||||
+$ card card:agent:gall
|
||||
++ scry-for
|
||||
|* [=mold =path]
|
||||
.^ mold
|
||||
%gx
|
||||
(scot %p our.bowl)
|
||||
%group-store
|
||||
(scot %da now.bowl)
|
||||
(snoc `^path`path %noun)
|
||||
==
|
||||
++ scry-tag
|
||||
|= [rid=resource =tag]
|
||||
^- (unit (set ship))
|
||||
=/ group
|
||||
(scry-group rid)
|
||||
?~ group
|
||||
~
|
||||
`(~(gut by tags.u.group) tag ~)
|
||||
::
|
||||
++ scry-group-path
|
||||
|= =path
|
||||
%+ scry-for
|
||||
(unit group)
|
||||
[%groups path]
|
||||
::
|
||||
++ scry-group
|
||||
|= rid=resource
|
||||
%- scry-group-path
|
||||
(en-path:resource rid)
|
||||
::
|
||||
++ members
|
||||
|= rid=resource
|
||||
%- members-from-path
|
||||
(en-path:resource rid)
|
||||
::
|
||||
++ members-from-path
|
||||
|= =group-path
|
||||
^- (set ship)
|
||||
=- members:(fall - *group)
|
||||
(scry-group-path group-path)
|
||||
::
|
||||
++ is-member
|
||||
|= [=ship =group-path]
|
||||
^- ?
|
||||
=- (~(has in -) ship)
|
||||
(members-from-path group-path)
|
||||
:: +role-for-ship: get role for user
|
||||
::
|
||||
:: Returns ~ if no such group exists or user is not
|
||||
:: a member of the group. Returns [~ ~] if the user
|
||||
:: is a member with no additional role.
|
||||
++ role-for-ship
|
||||
|= [rid=resource =ship]
|
||||
^- (unit (unit role-tag))
|
||||
=/ grp=(unit group)
|
||||
(scry-group rid)
|
||||
?~ grp ~
|
||||
=* group u.grp
|
||||
=* policy policy.group
|
||||
=* tags tags.group
|
||||
=/ admins=(set ^ship)
|
||||
(~(gut by tags) %admin ~)
|
||||
?: (~(has in admins) ship)
|
||||
``%admin
|
||||
=/ mods
|
||||
(~(gut by tags) %moderator ~)
|
||||
?: (~(has in mods) ship)
|
||||
``%moderator
|
||||
=/ janitors
|
||||
(~(gut by tags) %janitor ~)
|
||||
?: (~(has in janitors) ship)
|
||||
``%janitor
|
||||
?: (~(has in members.group) ship)
|
||||
[~ ~]
|
||||
~
|
||||
++ can-join-from-path
|
||||
|= [=path =ship]
|
||||
%+ scry-for
|
||||
?
|
||||
%+ welp
|
||||
[%groups path]
|
||||
/join/[(scot %p ship)]
|
||||
::
|
||||
++ can-join
|
||||
|= [rid=resource =ship]
|
||||
%+ can-join-from-path
|
||||
(en-path:resource rid)
|
||||
ship
|
||||
::
|
||||
++ is-managed-path
|
||||
|= =path
|
||||
^- ?
|
||||
=/ group=(unit group)
|
||||
(scry-group-path path)
|
||||
?~ group %.n
|
||||
!hidden.u.group
|
||||
::
|
||||
++ is-managed
|
||||
|= rid=resource
|
||||
%- is-managed-path
|
||||
(en-path:resource rid)
|
||||
::
|
||||
--
|
@ -80,7 +80,8 @@
|
||||
%publish
|
||||
%weather
|
||||
%group-store
|
||||
%group-hook
|
||||
%group-pull-hook
|
||||
%group-push-hook
|
||||
%permission-store
|
||||
%permission-hook
|
||||
%permission-group-hook
|
||||
@ -229,6 +230,9 @@
|
||||
(se-born | %home %file-server)
|
||||
=? ..on-load (lte hood-version %7)
|
||||
(se-born | %home %glob)
|
||||
=? ..on-load (lte hood-version %8)
|
||||
=> (se-born | %home %group-push-hook)
|
||||
(se-born | %home %group-pull-hook)
|
||||
..on-load
|
||||
::
|
||||
++ reap-phat :: ack connect
|
||||
|
@ -6,19 +6,19 @@
|
||||
^- json
|
||||
%- pairs
|
||||
%+ turn ~(tap by associations)
|
||||
|= [[=group-path =resource] =metadata]
|
||||
|= [[=group-path =md-resource] =metadata]
|
||||
^- [cord json]
|
||||
:-
|
||||
%- crip
|
||||
;: weld
|
||||
(trip (spat group-path))
|
||||
(weld "/" (trip app-name.resource))
|
||||
(trip (spat app-path.resource))
|
||||
(weld "/" (trip app-name.md-resource))
|
||||
(trip (spat app-path.md-resource))
|
||||
==
|
||||
%- pairs
|
||||
:~ [%group-path (path group-path)]
|
||||
[%app-name s+app-name.resource]
|
||||
[%app-path (path app-path.resource)]
|
||||
[%app-name s+app-name.md-resource]
|
||||
[%app-path (path app-path.md-resource)]
|
||||
[%metadata (metadata-to-json metadata)]
|
||||
==
|
||||
::
|
||||
@ -37,13 +37,13 @@
|
||||
++ add
|
||||
%- ot
|
||||
:~ [%group-path pa]
|
||||
[%resource resource]
|
||||
[%resource md-resource]
|
||||
[%metadata metadata]
|
||||
==
|
||||
++ remove
|
||||
%- ot
|
||||
:~ [%group-path pa]
|
||||
[%resource resource]
|
||||
[%resource md-resource]
|
||||
==
|
||||
::
|
||||
++ nu
|
||||
@ -59,7 +59,7 @@
|
||||
[%date-created (se %da)]
|
||||
[%creator (su ;~(pfix sig fed:ag))]
|
||||
==
|
||||
++ resource
|
||||
++ md-resource
|
||||
%- ot
|
||||
:~ [%app-name so]
|
||||
[%app-path pa]
|
||||
|
@ -9,27 +9,27 @@
|
||||
%+ murn
|
||||
%~ tap in
|
||||
=- (~(gut by -) group-path ~)
|
||||
.^ (jug ^group-path resource)
|
||||
.^ (jug ^group-path md-resource)
|
||||
%gy
|
||||
(scot %p our.bowl)
|
||||
%metadata-store
|
||||
(scot %da now.bowl)
|
||||
/group-indices
|
||||
==
|
||||
|= =resource
|
||||
|= =md-resource
|
||||
^- (unit app-path)
|
||||
?. =(app-name.resource app-name) ~
|
||||
`app-path.resource
|
||||
?. =(app-name.md-resource app-name) ~
|
||||
`app-path.md-resource
|
||||
::
|
||||
++ groups-from-resource
|
||||
|= =resource
|
||||
|= =md-resource
|
||||
^- (list group-path)
|
||||
=; resources
|
||||
%~ tap in
|
||||
%+ ~(gut by resources)
|
||||
resource
|
||||
md-resource
|
||||
*(set group-path)
|
||||
.^ (jug ^resource group-path)
|
||||
.^ (jug ^md-resource group-path)
|
||||
%gy
|
||||
(scot %p our.bowl)
|
||||
%metadata-store
|
||||
@ -38,9 +38,9 @@
|
||||
==
|
||||
::
|
||||
++ check-resource-permissions
|
||||
|= [=ship =resource]
|
||||
|= [=ship =md-resource]
|
||||
^- ?
|
||||
%+ lien (groups-from-resource resource)
|
||||
%+ lien (groups-from-resource md-resource)
|
||||
|= =group-path
|
||||
.^ ?
|
||||
%gx
|
||||
@ -51,4 +51,4 @@
|
||||
(scot %p ship)
|
||||
(snoc group-path %noun)
|
||||
==
|
||||
--
|
||||
--
|
||||
|
278
pkg/arvo/lib/pull-hook.hoon
Normal file
278
pkg/arvo/lib/pull-hook.hoon
Normal file
@ -0,0 +1,278 @@
|
||||
/- *pull-hook
|
||||
/+ default-agent, resource
|
||||
::
|
||||
::
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
::
|
||||
+$ config
|
||||
$: store-name=term
|
||||
update=mold
|
||||
update-mark=term
|
||||
push-hook-name=term
|
||||
==
|
||||
::
|
||||
+$ state-0
|
||||
$: %0
|
||||
tracking=(map resource ship)
|
||||
inner-state=vase
|
||||
==
|
||||
::
|
||||
++ default
|
||||
|* [pull-hook=* =config]
|
||||
|_ =bowl:gall
|
||||
::
|
||||
++ on-pull-nack
|
||||
|= [=resource =tang]
|
||||
=/ =tank leaf+"subscribe failed from {<dap.bowl>} for {<resource>}"
|
||||
%- (slog tank tang)
|
||||
[~ pull-hook]
|
||||
::
|
||||
++ on-pull-kick
|
||||
|= =resource
|
||||
*(unit path)
|
||||
--
|
||||
::
|
||||
++ pull-hook
|
||||
|* config
|
||||
$_ ^|
|
||||
|_ bowl:gall
|
||||
::
|
||||
++ on-init
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-save
|
||||
*vase
|
||||
::
|
||||
++ on-load
|
||||
|~ vase
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-poke
|
||||
|~ [mark vase]
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-watch
|
||||
|~ path
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-leave
|
||||
|~ path
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-peek
|
||||
|~ path
|
||||
*(unit (unit cage))
|
||||
::
|
||||
++ on-agent
|
||||
|~ [wire sign:agent:gall]
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-arvo
|
||||
|~ [wire sign-arvo]
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-fail
|
||||
|~ [term tang]
|
||||
*[(list card) _^|(..on-init)]
|
||||
:: +on-pull-nack: handle failed pull subscription
|
||||
::
|
||||
:: This arm is called when a pull subscription fails.
|
||||
::
|
||||
++ on-pull-nack
|
||||
|~ [resource tang]
|
||||
*[(list card) _^|(..on-init)]
|
||||
:: +on-pull-kick: produce any additional resubscribe path
|
||||
::
|
||||
:: If non-null, the produced path is appended to the original
|
||||
:: subscription path. This should be used to encode extra
|
||||
:: information onto the path in order to reduce the payload of a
|
||||
:: kick and resubscribe.
|
||||
::
|
||||
:: If null, a resubscribe is not attempted
|
||||
::
|
||||
++ on-pull-kick
|
||||
|~ resource
|
||||
*(unit path)
|
||||
:: ::
|
||||
--
|
||||
++ agent
|
||||
|* =config
|
||||
|= =(pull-hook config)
|
||||
=| state-0
|
||||
=* state -
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
og ~(. pull-hook bowl)
|
||||
hc ~(. +> bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
++ on-init
|
||||
^- [(list card:agent:gall) agent:gall]
|
||||
=^ cards pull-hook
|
||||
on-init:og
|
||||
[cards this]
|
||||
++ on-load
|
||||
|= =old=vase
|
||||
^- [(list card:agent:gall) agent:gall]
|
||||
=/ old
|
||||
!<(state-0 old-vase)
|
||||
=^ cards pull-hook
|
||||
(on-load:og inner-state.old)
|
||||
[cards this(state old)]
|
||||
++ on-save
|
||||
^- vase
|
||||
=. inner-state
|
||||
on-save:og
|
||||
!>(state)
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- [(list card:agent:gall) agent:gall]
|
||||
?> (team:title our.bowl src.bowl)
|
||||
?. =(mark %pull-hook-action)
|
||||
=^ cards pull-hook
|
||||
(on-poke:og mark vase)
|
||||
[cards this]
|
||||
=^ cards state
|
||||
(poke-hook-action:hc !<(action vase))
|
||||
[cards this]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- [(list card:agent:gall) agent:gall]
|
||||
?> (team:title our.bowl src.bowl)
|
||||
?. ?=([%tracking ~] path)
|
||||
=^ cards pull-hook
|
||||
(on-watch:og path)
|
||||
[cards this]
|
||||
:_ this
|
||||
~[give-update]
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- [(list card:agent:gall) agent:gall]
|
||||
?. ?=([%helper %pull-hook @ *] wire)
|
||||
=^ cards pull-hook
|
||||
(on-agent:og wire sign)
|
||||
[cards this]
|
||||
?. ?=([%pull %resource *] t.t.wire)
|
||||
(on-agent:def wire sign)
|
||||
=/ rid=resource
|
||||
(de-path:resource t.t.t.t.wire)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%kick
|
||||
=/ pax=(unit path)
|
||||
(on-pull-kick:og rid)
|
||||
?^ pax
|
||||
:_ this
|
||||
~[(watch-resource:hc rid u.pax)]
|
||||
=. tracking
|
||||
(~(del by tracking) rid)
|
||||
:_ this
|
||||
~[give-update]
|
||||
::
|
||||
%watch-ack
|
||||
?~ p.sign
|
||||
[~ this]
|
||||
=. tracking
|
||||
(~(del by tracking) rid)
|
||||
=^ cards pull-hook
|
||||
(on-pull-nack:og rid u.p.sign)
|
||||
:_ this
|
||||
[give-update cards]
|
||||
::
|
||||
%fact
|
||||
?. =(update-mark.config p.cage.sign)
|
||||
=^ cards pull-hook
|
||||
(on-agent:og wire sign)
|
||||
[cards this]
|
||||
:_ this
|
||||
~[(update-store:hc q.cage.sign)]
|
||||
==
|
||||
++ on-leave
|
||||
|= =path
|
||||
^- [(list card:agent:gall) agent:gall]
|
||||
=^ cards pull-hook
|
||||
(on-leave:og path)
|
||||
[cards this]
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- [(list card:agent:gall) agent:gall]
|
||||
=^ cards pull-hook
|
||||
(on-arvo:og wire sign-arvo)
|
||||
[cards this]
|
||||
++ on-fail
|
||||
|= [=term =tang]
|
||||
^- [(list card:agent:gall) agent:gall]
|
||||
=^ cards pull-hook
|
||||
(on-fail:og term tang)
|
||||
[cards this]
|
||||
++ on-peek on-peek:def
|
||||
--
|
||||
|_ =bowl:gall
|
||||
+* og ~(. pull-hook bowl)
|
||||
::
|
||||
++ poke-hook-action
|
||||
|= =action
|
||||
^- [(list card:agent:gall) _state]
|
||||
|^
|
||||
?- -.action
|
||||
%add (add +.action)
|
||||
%remove (remove +.action)
|
||||
==
|
||||
++ add
|
||||
|= [=ship =resource]
|
||||
~| resource
|
||||
?< (~(has by tracking) resource)
|
||||
=. tracking
|
||||
(~(put by tracking) resource ship)
|
||||
:_ state
|
||||
~[(watch-resource resource /)]
|
||||
::
|
||||
++ remove
|
||||
|= =resource
|
||||
:- ~[(leave-resource resource)]
|
||||
state(tracking (~(del by tracking) resource))
|
||||
--
|
||||
::
|
||||
++ leave-resource
|
||||
|= rid=resource
|
||||
^- card
|
||||
=/ =ship
|
||||
(~(got by tracking) rid)
|
||||
=/ =wire
|
||||
(make-wire pull+resource+(en-path:resource rid))
|
||||
[%pass wire %agent [ship push-hook-name.config] %leave ~]
|
||||
|
||||
++ watch-resource
|
||||
|= [rid=resource pax=path]
|
||||
^- card
|
||||
=/ =ship
|
||||
(~(got by tracking) rid)
|
||||
=/ =path
|
||||
(welp resource+(en-path:resource rid) pax)
|
||||
=/ =wire
|
||||
(make-wire pull+path)
|
||||
[%pass wire %agent [ship push-hook-name.config] %watch path]
|
||||
::
|
||||
++ make-wire
|
||||
|= =wire
|
||||
^+ wire
|
||||
%+ weld
|
||||
/helper/pull-hook
|
||||
wire
|
||||
::
|
||||
++ give-update
|
||||
^- card
|
||||
[%give %fact ~[/tracking] %pull-hook-update !>(tracking)]
|
||||
::
|
||||
++ update-store
|
||||
|= =vase
|
||||
^- card
|
||||
=/ =wire
|
||||
(make-wire /store)
|
||||
[%pass wire %agent [our.bowl store-name.config] %poke update-mark.config vase]
|
||||
--
|
||||
--
|
286
pkg/arvo/lib/push-hook.hoon
Normal file
286
pkg/arvo/lib/push-hook.hoon
Normal file
@ -0,0 +1,286 @@
|
||||
/- *push-hook
|
||||
/+ default-agent, resource
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
::
|
||||
+$ config
|
||||
$: store-name=term
|
||||
store-path=path
|
||||
update=mold
|
||||
update-mark=term
|
||||
pull-hook-name=term
|
||||
==
|
||||
+$ state-0
|
||||
$: %0
|
||||
sharing=(set resource)
|
||||
inner-state=vase
|
||||
==
|
||||
::
|
||||
++ push-hook
|
||||
|* =config
|
||||
$_ ^|
|
||||
|_ bowl:gall
|
||||
::
|
||||
++ on-init
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-save
|
||||
*vase
|
||||
::
|
||||
++ on-load
|
||||
|~ vase
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-poke
|
||||
|~ cage
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-watch
|
||||
|~ path
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-leave
|
||||
|~ path
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-peek
|
||||
|~ path
|
||||
*(unit (unit cage))
|
||||
::
|
||||
++ on-agent
|
||||
|~ [wire sign:agent:gall]
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-arvo
|
||||
|~ [wire sign-arvo]
|
||||
*[(list card) _^|(..on-init)]
|
||||
::
|
||||
++ on-fail
|
||||
|~ [term tang]
|
||||
*[(list card) _^|(..on-init)]
|
||||
:: +resource-for-update: get affected resource from an update
|
||||
|
||||
++ resource-for-update
|
||||
|~ vase
|
||||
*(unit resource)
|
||||
::
|
||||
:: +on-update: handle update from store
|
||||
::
|
||||
:: Do extra stuff on store update
|
||||
++ take-update
|
||||
|~ vase
|
||||
*[(list card) _^|(..on-init)]
|
||||
:: +should-proxy-update: should forward update to store
|
||||
::
|
||||
:: If %.y is produced, then the update is forwarded to the local
|
||||
:: store. If %.n is produced then the update is not forwarded and
|
||||
:: the poke fails.
|
||||
::
|
||||
++ should-proxy-update
|
||||
|~ vase
|
||||
*?
|
||||
:: +initial-watch: produce initial state for a subscription
|
||||
::
|
||||
:: .resource is the resource being subscribed to.
|
||||
:: .path is any additional information in the subscription wire
|
||||
::
|
||||
++ initial-watch
|
||||
|~ [path resource]
|
||||
*vase
|
||||
::
|
||||
--
|
||||
++ agent
|
||||
|* =config
|
||||
|= =(push-hook config)
|
||||
=| state-0
|
||||
=* state -
|
||||
^- agent:gall
|
||||
=<
|
||||
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
og ~(. push-hook bowl)
|
||||
hc ~(. +> bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
++ on-init
|
||||
=^ cards push-hook
|
||||
on-init:og
|
||||
:_ this
|
||||
[watch-store:hc cards]
|
||||
::
|
||||
++ on-load
|
||||
|= =old=vase
|
||||
=/ old
|
||||
!<(state-0 old-vase)
|
||||
=^ cards push-hook
|
||||
(on-load:og inner-state.old)
|
||||
`this(state old)
|
||||
::
|
||||
++ on-save
|
||||
=. inner-state
|
||||
on-save:og
|
||||
!>(state)
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
?: =(mark %push-hook-action)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
=^ cards state
|
||||
(poke-hook-action:hc !<(action vase))
|
||||
[cards this]
|
||||
::
|
||||
?: =(mark update-mark.config)
|
||||
=^ cards state
|
||||
(poke-update:hc vase)
|
||||
[cards this]
|
||||
::
|
||||
=^ cards push-hook
|
||||
(on-poke:og mark vase)
|
||||
[cards this]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
?. ?=([%resource *] path)
|
||||
=^ cards push-hook
|
||||
(on-watch:og path)
|
||||
[cards this]
|
||||
?> ?=([%ship @ @ *] t.path)
|
||||
=/ =resource
|
||||
(de-path:resource t.path)
|
||||
=/ =vase
|
||||
(initial-watch:og t.t.t.path resource)
|
||||
:_ this
|
||||
[%give %fact ~ update-mark.config vase]~
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
?. ?=([%helper %push-hook @ *] wire)
|
||||
=^ cards push-hook
|
||||
(on-agent:og wire sign)
|
||||
[cards this]
|
||||
?. ?=(%store i.t.t.wire)
|
||||
(on-agent:def wire sign)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%kick [~[watch-store:hc] this]
|
||||
::
|
||||
%fact
|
||||
?. =(update-mark.config p.cage.sign)
|
||||
=^ cards push-hook
|
||||
(on-agent:og wire sign)
|
||||
[cards this]
|
||||
=^ cards push-hook
|
||||
(take-update:og q.cage.sign)
|
||||
:_ this
|
||||
%+ weld
|
||||
(push-updates:hc q.cage.sign)
|
||||
cards
|
||||
|
||||
==
|
||||
++ on-leave
|
||||
|= =path
|
||||
=^ cards push-hook
|
||||
(on-leave:og path)
|
||||
[cards this]
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
=^ cards push-hook
|
||||
(on-arvo:og wire sign-arvo)
|
||||
[cards this]
|
||||
++ on-fail
|
||||
|= [=term =tang]
|
||||
=^ cards push-hook
|
||||
(on-fail:og term tang)
|
||||
[cards this]
|
||||
++ on-peek on-peek:og
|
||||
--
|
||||
|_ =bowl:gall
|
||||
+* og ~(. push-hook bowl)
|
||||
::
|
||||
++ poke-update
|
||||
|= =vase
|
||||
^- (quip card:agent:gall _state)
|
||||
?> (should-proxy-update:og vase)
|
||||
=/ wire
|
||||
(make-wire /store)
|
||||
:_ state
|
||||
[%pass wire %agent [our.bowl store-name.config] %poke update-mark.config vase]~
|
||||
::
|
||||
++ poke-hook-action
|
||||
|= =action
|
||||
^- (quip card:agent:gall _state)
|
||||
|^
|
||||
?- -.action
|
||||
%add (add +.action)
|
||||
%remove (remove +.action)
|
||||
%revoke (revoke +.action)
|
||||
==
|
||||
++ add
|
||||
|= rid=resource
|
||||
=. sharing
|
||||
(~(put in sharing) rid)
|
||||
`state
|
||||
::
|
||||
++ remove
|
||||
|= rid=resource
|
||||
=/ pax=path
|
||||
[%resource (en-path:resource rid)]
|
||||
=/ paths=(list path)
|
||||
%+ turn
|
||||
(incoming-subscriptions pax)
|
||||
|=([ship pox=path] pax)
|
||||
=. sharing
|
||||
(~(del in sharing) rid)
|
||||
:_ state
|
||||
[%give %kick ~[pax] ~]~
|
||||
::
|
||||
++ revoke
|
||||
|= [ships=(set ship) rid=resource]
|
||||
=/ pax=path
|
||||
[%resource (en-path:resource rid)]
|
||||
:_ state
|
||||
%+ murn
|
||||
(incoming-subscriptions pax)
|
||||
|= [her=ship =path]
|
||||
^- (unit card)
|
||||
?. (~(has in ships) her)
|
||||
~
|
||||
`[%give %kick ~[path] `her]
|
||||
--
|
||||
++ incoming-subscriptions
|
||||
|= prefix=path
|
||||
^- (list (pair ship path))
|
||||
%+ skim
|
||||
~(val by sup.bowl)
|
||||
|= [him=ship pax=path]
|
||||
=/ idx=(unit @)
|
||||
(find prefix pax)
|
||||
?~ idx %.n
|
||||
=(u.idx 0)
|
||||
::
|
||||
++ make-wire
|
||||
|= =wire
|
||||
^+ wire
|
||||
%+ weld
|
||||
/helper/push-hook
|
||||
wire
|
||||
::
|
||||
++ watch-store
|
||||
^- card:agent:gall
|
||||
=/ =wire
|
||||
(make-wire /store)
|
||||
[%pass wire %agent [our.bowl store-name.config] %watch store-path.config]
|
||||
::
|
||||
++ push-updates
|
||||
|= =vase
|
||||
^- (list card:agent:gall)
|
||||
=/ rid=(unit resource)
|
||||
(resource-for-update:og vase)
|
||||
?~ rid ~
|
||||
=/ =path
|
||||
resource+(en-path:resource u.rid)
|
||||
[%give %fact ~[path] update-mark.config vase]~
|
||||
--
|
||||
--
|
50
pkg/arvo/lib/resource.hoon
Normal file
50
pkg/arvo/lib/resource.hoon
Normal file
@ -0,0 +1,50 @@
|
||||
/- sur=resource
|
||||
=< resource
|
||||
|%
|
||||
+$ resource resource:sur
|
||||
++ en-path
|
||||
|= =resource
|
||||
^- path
|
||||
~[%ship (scot %p entity.resource) name.resource]
|
||||
::
|
||||
++ de-path
|
||||
|= =path
|
||||
^- resource
|
||||
(need (de-path-soft path))
|
||||
::
|
||||
++ de-path-soft
|
||||
|= =path
|
||||
^- (unit resource)
|
||||
?. ?=([%ship @ @ *] path)
|
||||
~
|
||||
=/ ship
|
||||
(slaw %p i.t.path)
|
||||
?~ ship
|
||||
~
|
||||
`[u.ship i.t.t.path]
|
||||
::
|
||||
++ enjs
|
||||
|= =resource
|
||||
^- json
|
||||
=, enjs:format
|
||||
%- pairs
|
||||
:~ ship+(ship entity.resource)
|
||||
name+s+name.resource
|
||||
==
|
||||
::
|
||||
++ enjs-path
|
||||
|= =resource
|
||||
%- spat
|
||||
(en-path resource)
|
||||
::
|
||||
++ dejs
|
||||
=, dejs:format
|
||||
^- $-(json resource)
|
||||
|= jon=json
|
||||
~| dejs+%resource
|
||||
%. jon
|
||||
%- ot
|
||||
:~ ship+(su ;~(pfix sig fed:ag))
|
||||
name+so
|
||||
==
|
||||
--
|
@ -1,38 +1,14 @@
|
||||
/+ *group-json
|
||||
/+ store=group-store
|
||||
=, dejs:format
|
||||
|_ act=group-action
|
||||
|_ =action:store
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun act
|
||||
++ noun action
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun group-action
|
||||
++ json
|
||||
|= jon=^json
|
||||
=< (parse-group-action jon)
|
||||
|%
|
||||
++ parse-group-action
|
||||
%- of
|
||||
:~
|
||||
[%add add-action]
|
||||
[%remove remove-action]
|
||||
[%bundle pa]
|
||||
[%unbundle pa]
|
||||
==
|
||||
::
|
||||
++ add-action
|
||||
%- ot
|
||||
:~ [%members (as (su ;~(pfix sig fed:ag)))]
|
||||
[%path pa]
|
||||
==
|
||||
::
|
||||
++ remove-action
|
||||
%- ot
|
||||
:~ [%members (as (su ;~(pfix sig fed:ag)))]
|
||||
[%path pa]
|
||||
==
|
||||
--
|
||||
++ noun action:store
|
||||
++ json action:dejs:store
|
||||
--
|
||||
--
|
||||
|
@ -1,6 +1,6 @@
|
||||
/- *group-hook
|
||||
=, dejs:format
|
||||
|_ act=group-hook-action
|
||||
|_ act=action
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
@ -8,7 +8,7 @@
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun group-hook-action
|
||||
++ noun action
|
||||
++ json
|
||||
|= jon=^json
|
||||
=< (parse-action jon)
|
||||
|
@ -1,68 +1,16 @@
|
||||
/+ *group-json
|
||||
|_ upd=group-update
|
||||
/+ *group-store
|
||||
|_ upd=update
|
||||
++ grad %noun
|
||||
++ grab
|
||||
|%
|
||||
++ noun group-update
|
||||
--
|
||||
++ grow
|
||||
|%
|
||||
++ noun upd
|
||||
++ json
|
||||
=, enjs:format
|
||||
^- ^json
|
||||
%+ frond %group-update
|
||||
%- pairs
|
||||
:~
|
||||
?: =(%initial -.upd)
|
||||
?> ?=(%initial -.upd)
|
||||
:- %initial
|
||||
(groups-to-json groups.upd)
|
||||
::
|
||||
:: %add
|
||||
?: =(%add -.upd)
|
||||
?> ?=(%add -.upd)
|
||||
:- %add
|
||||
%- pairs
|
||||
:~ [%members (set-to-array members.upd ship)]
|
||||
[%path (path pax.upd)]
|
||||
==
|
||||
::
|
||||
:: %remove
|
||||
?: =(%remove -.upd)
|
||||
?> ?=(%remove -.upd)
|
||||
:- %remove
|
||||
%- pairs
|
||||
:~ [%members (set-to-array members.upd ship)]
|
||||
[%path (path pax.upd)]
|
||||
==
|
||||
::
|
||||
:: %bundle
|
||||
?: =(%bundle -.upd)
|
||||
?> ?=(%bundle -.upd)
|
||||
[%bundle (pairs [%path (path pax.upd)]~)]
|
||||
::
|
||||
:: %unbundle
|
||||
?: =(%unbundle -.upd)
|
||||
?> ?=(%unbundle -.upd)
|
||||
[%unbundle (pairs [%path (path pax.upd)]~)]
|
||||
::
|
||||
:: %keys
|
||||
?: =(%keys -.upd)
|
||||
?> ?=(%keys -.upd)
|
||||
[%keys (pairs [%keys (set-to-array keys.upd path)]~)]
|
||||
::
|
||||
:: %path
|
||||
?: =(%path -.upd)
|
||||
?> ?=(%path -.upd)
|
||||
:- %path
|
||||
%- pairs
|
||||
:~ [%members (set-to-array members.upd ship)]
|
||||
[%path (path pax.upd)]
|
||||
==
|
||||
::
|
||||
:: %noop
|
||||
[*@t *^json]
|
||||
==
|
||||
%+ frond:enjs:format 'groupUpdate'
|
||||
(update:enjs upd)
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun update
|
||||
++ json update:dejs
|
||||
--
|
||||
--
|
||||
|
12
pkg/arvo/mar/pull-hook/action.hoon
Normal file
12
pkg/arvo/mar/pull-hook/action.hoon
Normal file
@ -0,0 +1,12 @@
|
||||
/- *pull-hook
|
||||
|_ act=action
|
||||
++ grab
|
||||
|%
|
||||
++ noun action
|
||||
--
|
||||
++ grow
|
||||
|%
|
||||
++ noun act
|
||||
--
|
||||
++ grad %noun
|
||||
--
|
12
pkg/arvo/mar/push-hook/action.hoon
Normal file
12
pkg/arvo/mar/push-hook/action.hoon
Normal file
@ -0,0 +1,12 @@
|
||||
/- *push-hook
|
||||
|_ act=action
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun act
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun action
|
||||
--
|
||||
--
|
@ -1,25 +1,22 @@
|
||||
/- *rw-security
|
||||
/- *group
|
||||
^?
|
||||
|%
|
||||
+$ action
|
||||
$% :: %create: create a new chat
|
||||
::
|
||||
:: if :app-path and :group-path are different, :members must be empty,
|
||||
:: as the :group-path is assumed to exist.
|
||||
:: if :app-path and :group-path are identical, and the :group-path
|
||||
:: doesn't yet exist, will create a new group with :members.
|
||||
::
|
||||
$: %create
|
||||
title=@t
|
||||
description=@t
|
||||
app-path=path
|
||||
group-path=path
|
||||
security=rw-security
|
||||
=policy
|
||||
members=(set ship)
|
||||
allow-history=?
|
||||
managed=?
|
||||
==
|
||||
[%delete app-path=path]
|
||||
[%join =ship app-path=path ask-history=?]
|
||||
[%invite app-path=path ships=(set ship)]
|
||||
:: %groupify: for unmanaged %village chats: recreate as group-based chat
|
||||
::
|
||||
:: will delete the old chat, recreate it based on a proper group,
|
||||
|
@ -1,9 +1,16 @@
|
||||
/- *contact-store
|
||||
/- *contact-store, *group, *resource
|
||||
::
|
||||
|%
|
||||
+$ contact-view-action
|
||||
$% :: %create: create in both groups and contacts
|
||||
::
|
||||
[%create =path ships=(set ship) title=@t description=@t]
|
||||
[%create name=term =policy title=@t description=@t]
|
||||
:: %join: join open group in both groups and contacts
|
||||
::
|
||||
[%join =resource]
|
||||
:: %invite: invite to invite-only group and contacts
|
||||
::
|
||||
[%invite =resource =ship text=cord]
|
||||
:: %remove: remove from both groups and contacts
|
||||
::
|
||||
[%remove =path =ship]
|
||||
@ -13,5 +20,8 @@
|
||||
:: %share: send %add contact-action to to recipient's contact-hook
|
||||
::
|
||||
[%share recipient=ship =path =ship =contact]
|
||||
:: %groupify: create contacts object for a preexisting group
|
||||
::
|
||||
[%groupify =resource title=@t description=@t]
|
||||
==
|
||||
--
|
||||
|
@ -1,12 +1,16 @@
|
||||
/- *group, store=group-store, *resource
|
||||
|%
|
||||
+$ group-hook-action
|
||||
$% [%add =ship =path] :: if ship is our, make the group publicly
|
||||
:: available for other ships to sync
|
||||
:: if ship is foreign, delete any local
|
||||
:: group at that path and mirror the
|
||||
:: foreign group at our local path
|
||||
::
|
||||
[%remove =path] :: remove the path.
|
||||
:: $action: request to change group-hook state
|
||||
::
|
||||
:: %add:
|
||||
:: if ship is ours make group available to sync, else sync foreign group
|
||||
:: to group-store.
|
||||
:: %remove:
|
||||
:: if ship is ours make unavailable to sync, else stop syncing foreign
|
||||
:: group.
|
||||
::
|
||||
+$ action
|
||||
$% [%add rid=resource]
|
||||
[%remove rid=resource]
|
||||
==
|
||||
--
|
||||
|
||||
|
@ -1,20 +1,58 @@
|
||||
/- *group, *resource
|
||||
^?
|
||||
|%
|
||||
+$ group (set ship)
|
||||
::
|
||||
+$ group-action
|
||||
$% [%add members=group pax=path] :: add member to group
|
||||
[%remove members=group pax=path] :: remove member from group
|
||||
[%bundle pax=path] :: create group at path
|
||||
[%unbundle pax=path] :: delete group at path
|
||||
++ state-zero
|
||||
|%
|
||||
+$ group (set ship)
|
||||
::
|
||||
+$ group-action
|
||||
$% [%add members=group pax=path] :: add member to group
|
||||
[%remove members=group pax=path] :: remove member from group
|
||||
[%bundle pax=path] :: create group at path
|
||||
[%unbundle pax=path] :: delete group at path
|
||||
==
|
||||
::
|
||||
+$ group-update
|
||||
$% [%keys keys=(set path)] :: keys have changed
|
||||
[%path members=group pax=path]
|
||||
group-action
|
||||
==
|
||||
::
|
||||
+$ groups (map path group)
|
||||
--
|
||||
:: $action: request to change group-store state
|
||||
::
|
||||
:: %add-group: add a group
|
||||
:: %add-members: add members to a group
|
||||
:: %remove-members: remove members from a group
|
||||
:: %add-tag: add a tag to a set of ships
|
||||
:: %remove-tag: remove a tag from a set of ships
|
||||
:: %change-policy: change a group's policy
|
||||
:: %remove-group: remove a group from the store
|
||||
:: %expose: unset .hidden flag
|
||||
::
|
||||
+$ action
|
||||
$% [%add-group =resource =policy hidden=?]
|
||||
[%add-members =resource ships=(set ship)]
|
||||
[%remove-members =resource ships=(set ship)]
|
||||
[%add-tag =resource =tag ships=(set ship)]
|
||||
[%remove-tag =resource =tag ships=(set ship)]
|
||||
[%change-policy =resource =diff:policy]
|
||||
[%remove-group =resource ~]
|
||||
[%expose =resource ~]
|
||||
==
|
||||
:: $update: a description of a processed state change
|
||||
::
|
||||
+$ group-update
|
||||
$% [%initial =groups]
|
||||
[%keys keys=(set path)] :: keys have changed
|
||||
[%path members=group pax=path]
|
||||
group-action
|
||||
:: %initial: describe groups upon new subscription
|
||||
::
|
||||
+$ update
|
||||
$% initial
|
||||
action
|
||||
==
|
||||
+$ initial
|
||||
$% [%initial-group =resource =group]
|
||||
[%initial =groups]
|
||||
==
|
||||
::
|
||||
+$ groups (map path group)
|
||||
--
|
||||
|
||||
|
93
pkg/arvo/sur/group.hoon
Normal file
93
pkg/arvo/sur/group.hoon
Normal file
@ -0,0 +1,93 @@
|
||||
/- *resource
|
||||
::
|
||||
^?
|
||||
|%
|
||||
:: $groups: a mapping from group-ids to groups
|
||||
::
|
||||
+$ groups (map resource group)
|
||||
:: $group-tag: an identifier used by groups
|
||||
::
|
||||
:: These tags should have precise semantics, as they are shared across all
|
||||
:: apps.
|
||||
::
|
||||
+$ group-tag ?(role-tag)
|
||||
:: $tag: an identifier used to identify a subset of members
|
||||
::
|
||||
:: Tags may be used and recognised differently across apps.
|
||||
:: for example, you could use tags like `%author`, `%bot`, `%flagged`...
|
||||
::
|
||||
+$ tag $@(group-tag [app=term tag=term])
|
||||
:: $role-tag: a kind of $group-tag that identifies a privileged user
|
||||
::
|
||||
:: These roles are
|
||||
:: %admin: Administrator, can do everything except delete the group
|
||||
:: %moderator: Moderator, can add/remove/ban users
|
||||
:: %janitor: Has no special meaning inside group-store,
|
||||
:: but may be given additional privileges in other apps.
|
||||
::
|
||||
+$ role-tag
|
||||
?(%admin %moderator %janitor)
|
||||
:: $tags: a mapping from a $tag to the members it identifies
|
||||
::
|
||||
+$ tags (jug tag ship)
|
||||
:: $group: description of a group of users
|
||||
::
|
||||
:: .members: members of the group
|
||||
:: .tag-queries: a map of tags to subsets of members
|
||||
:: .policy: permissions for the group
|
||||
:: .hidden: is group unmanaged
|
||||
+$ group
|
||||
$: members=(set ship)
|
||||
=tags
|
||||
=policy
|
||||
hidden=?
|
||||
==
|
||||
:: $policy: access control for a group
|
||||
::
|
||||
++ policy
|
||||
=< policy
|
||||
|%
|
||||
::
|
||||
+$ policy
|
||||
$% invite
|
||||
open
|
||||
==
|
||||
:: $diff: change group policy
|
||||
+$ diff
|
||||
$% [%invite diff:invite]
|
||||
[%open diff:open]
|
||||
[%replace =policy]
|
||||
==
|
||||
:: $invite: allow only invited ships
|
||||
++ invite
|
||||
=< invite-policy
|
||||
|%
|
||||
::
|
||||
+$ invite-policy
|
||||
[%invite pending=(set ship)]
|
||||
:: $diff: add or remove invites
|
||||
::
|
||||
+$ diff
|
||||
$% [%add-invites invitees=(set ship)]
|
||||
[%remove-invites invitees=(set ship)]
|
||||
==
|
||||
--
|
||||
:: $open: allow all unbanned ships of approriate rank
|
||||
::
|
||||
++ open
|
||||
=< open-policy
|
||||
|%
|
||||
::
|
||||
+$ open-policy
|
||||
[%open ban-ranks=(set rank:title) banned=(set ship)]
|
||||
:: $diff: ban or allow ranks and ships
|
||||
::
|
||||
+$ diff
|
||||
$% [%allow-ranks ranks=(set rank:title)]
|
||||
[%ban-ranks ranks=(set rank:title)]
|
||||
[%ban-ships ships=(set ship)]
|
||||
[%allow-ships ships=(set ship)]
|
||||
==
|
||||
--
|
||||
--
|
||||
--
|
@ -2,8 +2,8 @@
|
||||
+$ group-path path
|
||||
+$ app-name @tas
|
||||
+$ app-path path
|
||||
+$ resource [=app-name =app-path]
|
||||
+$ associations (map [group-path resource] metadata)
|
||||
+$ md-resource [=app-name =app-path]
|
||||
+$ associations (map [group-path md-resource] metadata)
|
||||
::
|
||||
+$ metadata
|
||||
$: title=@t
|
||||
@ -14,13 +14,13 @@
|
||||
==
|
||||
::
|
||||
+$ metadata-action
|
||||
$% [%add =group-path =resource =metadata]
|
||||
[%remove =group-path =resource]
|
||||
$% [%add =group-path resource=md-resource =metadata]
|
||||
[%remove =group-path resource=md-resource]
|
||||
==
|
||||
::
|
||||
+$ metadata-update
|
||||
$% metadata-action
|
||||
[%associations =associations]
|
||||
[%update-metadata =group-path =resource =metadata]
|
||||
[%update-metadata =group-path resource=md-resource =metadata]
|
||||
==
|
||||
--
|
||||
|
12
pkg/arvo/sur/pull-hook.hoon
Normal file
12
pkg/arvo/sur/pull-hook.hoon
Normal file
@ -0,0 +1,12 @@
|
||||
/- *resource
|
||||
|%
|
||||
+$ action
|
||||
$% [%add =ship =resource]
|
||||
[%remove =resource]
|
||||
==
|
||||
::
|
||||
+$ update
|
||||
$% [%tracking tracking=(map resource ship)]
|
||||
==
|
||||
::
|
||||
--
|
8
pkg/arvo/sur/push-hook.hoon
Normal file
8
pkg/arvo/sur/push-hook.hoon
Normal file
@ -0,0 +1,8 @@
|
||||
/- *resource
|
||||
|%
|
||||
+$ action
|
||||
$% [%add =resource]
|
||||
[%remove =resource]
|
||||
[%revoke ships=(set ship) =resource]
|
||||
==
|
||||
--
|
10
pkg/arvo/sur/resource.hoon
Normal file
10
pkg/arvo/sur/resource.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
^?
|
||||
|%
|
||||
+$ resource [=entity name=term]
|
||||
+$ resources (set resource)
|
||||
::
|
||||
+$ entity
|
||||
$@ ship
|
||||
$% !!
|
||||
==
|
||||
--
|
72
pkg/arvo/ted/ph/group-rejoin.hoon
Normal file
72
pkg/arvo/ted/ph/group-rejoin.hoon
Normal file
@ -0,0 +1,72 @@
|
||||
/- spider
|
||||
/+ *ph-io
|
||||
=>
|
||||
|%
|
||||
++ wait-for-agent-start
|
||||
|= [=ship agent=term]
|
||||
=/ m (strand:spider ,~)
|
||||
^- form:m
|
||||
=* loop $
|
||||
;< [her=^ship =unix-effect] bind:m take-unix-effect
|
||||
?: (is-dojo-output:util ship her unix-effect "activated app home/{(trip agent)}")
|
||||
(pure:m ~)
|
||||
loop
|
||||
::
|
||||
++ start-agent
|
||||
|= [=ship agent=term]
|
||||
=/ m (strand:spider ,~)
|
||||
^- form:m
|
||||
=* loop $
|
||||
;< ~ bind:m (dojo ship "|start {<agent>}")
|
||||
;< ~ bind:m (wait-for-agent-start ship agent)
|
||||
(pure:m ~)
|
||||
::
|
||||
++ wait-for-goad
|
||||
|= =ship
|
||||
=/ m (strand:spider ,~)
|
||||
^- form:m
|
||||
=* loop $
|
||||
;< [her=^ship =unix-effect] bind:m take-unix-effect
|
||||
?: (is-dojo-output:util ship her unix-effect "p=%hood q=%bump")
|
||||
(pure:m ~)
|
||||
loop
|
||||
::
|
||||
++ start-group-agents
|
||||
|= =ship
|
||||
=/ m (strand:spider ,~)
|
||||
^- form:m
|
||||
;< ~ bind:m (start-agent ship %group-store)
|
||||
;< ~ bind:m (start-agent ship %group-hook)
|
||||
(pure:m ~)
|
||||
--
|
||||
=, strand=strand:spider
|
||||
^- thread:spider
|
||||
|= args=vase
|
||||
=/ m (strand ,vase)
|
||||
;< az=tid:spider
|
||||
bind:m start-azimuth
|
||||
;< ~ bind:m (spawn az ~bud)
|
||||
;< ~ bind:m (spawn az ~marbud)
|
||||
;< ~ bind:m (spawn az ~zod)
|
||||
;< ~ bind:m (spawn az ~marzod)
|
||||
;< ~ bind:m (real-ship az ~bud)
|
||||
;< ~ bind:m (real-ship az ~marbud)
|
||||
;< ~ bind:m (wait-for-goad ~marbud)
|
||||
;< ~ bind:m (real-ship az ~zod)
|
||||
;< ~ bind:m (real-ship az ~marzod)
|
||||
;< ~ bind:m (wait-for-goad ~marzod)
|
||||
;< ~ bind:m (start-group-agents ~marbud)
|
||||
;< ~ bind:m (start-group-agents ~marzod)
|
||||
;< ~ bind:m (dojo ~marbud ":group-store|create 'test-group'")
|
||||
;< ~ bind:m (wait-for-output ~marbud ">=")
|
||||
;< ~ bind:m (dojo ~marzod ":group-hook|add ~marbud 'test-group'")
|
||||
;< ~ bind:m (wait-for-output ~marzod ">=")
|
||||
;< ~ bind:m (sleep ~s1)
|
||||
;< ~ bind:m (breach-and-hear az ~marzod ~marbud)
|
||||
;< ~ bind:m (real-ship az ~marzod)
|
||||
;< ~ bind:m (wait-for-goad ~marzod)
|
||||
;< ~ bind:m (start-group-agents ~marzod)
|
||||
;< ~ bind:m (dojo ~marzod ":group-hook|add ~marbud 'test-group'")
|
||||
;< ~ bind:m (sleep ~s3)
|
||||
;< ~ bind:m end-azimuth
|
||||
(pure:m *vase)
|
@ -3,9 +3,10 @@ import 'react-hot-loader';
|
||||
import * as React from 'react';
|
||||
import { BrowserRouter as Router, Route, withRouter, Switch } from 'react-router-dom';
|
||||
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
|
||||
|
||||
import './css/indigo-static.css';
|
||||
import './css/fonts.css';
|
||||
import { light } from '@tlon/indigo-react';
|
||||
import { light, dark, inverted, paperDark } from '@tlon/indigo-react';
|
||||
|
||||
import LaunchApp from './apps/launch/app';
|
||||
import ChatApp from './apps/chat/app';
|
||||
@ -58,22 +59,36 @@ class App extends React.Component {
|
||||
this.api = new GlobalApi(this.ship, this.appChannel, this.store);
|
||||
this.subscription =
|
||||
new GlobalSubscription(this.store, this.api, this.appChannel);
|
||||
|
||||
this.updateTheme = this.updateTheme.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscription.start();
|
||||
this.themeWatcher = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.api.local.setDark(this.themeWatcher.matches);
|
||||
this.themeWatcher.addListener(this.updateTheme);
|
||||
this.api.local.getBaseHash();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.themeWatcher.removeListener(this.updateTheme);
|
||||
}
|
||||
|
||||
updateTheme(e) {
|
||||
this.api.local.setDark(e.matches);
|
||||
}
|
||||
|
||||
render() {
|
||||
const channel = window.channel;
|
||||
|
||||
const associations = this.state.associations ? this.state.associations : { contacts: {} };
|
||||
const selectedGroups = this.state.selectedGroups ? this.state.selectedGroups : [];
|
||||
const { state } = this;
|
||||
const theme = state.dark ? paperDark : light;
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={light}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Root>
|
||||
<Router>
|
||||
<StatusBarWithRouter props={this.props}
|
||||
|
@ -59,7 +59,7 @@ export default class ChatApi extends BaseApi<StoreState> {
|
||||
*/
|
||||
create(
|
||||
title: string, description: string, appPath: string, groupPath: string,
|
||||
security: any, members: PatpNoSig[], allowHistory: boolean
|
||||
policy: any, members: PatpNoSig[], allowHistory: boolean, managed: boolean
|
||||
): Promise<any> {
|
||||
return this.viewAction({
|
||||
create: {
|
||||
@ -67,9 +67,10 @@ export default class ChatApi extends BaseApi<StoreState> {
|
||||
description,
|
||||
'app-path': appPath,
|
||||
'group-path': groupPath,
|
||||
security,
|
||||
policy,
|
||||
members,
|
||||
'allow-history': allowHistory
|
||||
'allow-history': allowHistory,
|
||||
managed
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -131,6 +132,10 @@ export default class ChatApi extends BaseApi<StoreState> {
|
||||
});
|
||||
}
|
||||
|
||||
invite(path: Path, ships: Patp[]) {
|
||||
return this.viewAction({ invite: { 'app-path': path, ships }})
|
||||
}
|
||||
|
||||
|
||||
private storeAction(action: ChatAction): Promise<any> {
|
||||
return this.action('chat-store', 'json', action)
|
||||
|
@ -1,26 +1,34 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Patp, Path } from '../types/noun';
|
||||
import { Patp, Path, Enc } from '../types/noun';
|
||||
import { Contact, ContactEdit } from '../types/contact-update';
|
||||
import { GroupPolicy, Resource } from '../types/group-update';
|
||||
|
||||
export default class ContactsApi extends BaseApi<StoreState> {
|
||||
|
||||
create(path: Path, ships: Patp[] = [], title: string, description: string) {
|
||||
create(
|
||||
name: string,
|
||||
policy: Enc<GroupPolicy>,
|
||||
title: string,
|
||||
description: string
|
||||
) {
|
||||
return this.viewAction({
|
||||
create: {
|
||||
path,
|
||||
ships,
|
||||
name,
|
||||
policy,
|
||||
title,
|
||||
description
|
||||
}
|
||||
description,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
share(recipient: Patp, path: Patp, ship: Patp, contact: Contact) {
|
||||
return this.viewAction({
|
||||
share: {
|
||||
recipient, path, ship, contact
|
||||
}
|
||||
recipient,
|
||||
path,
|
||||
ship,
|
||||
contact,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -32,8 +40,6 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
return this.viewAction({ remove: { path, ship } });
|
||||
}
|
||||
|
||||
|
||||
|
||||
edit(path: Path, ship: Patp, editField: ContactEdit) {
|
||||
/* editField can be...
|
||||
{nickname: ''}
|
||||
@ -47,8 +53,22 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
*/
|
||||
return this.hookAction({
|
||||
edit: {
|
||||
path, ship, 'edit-field': editField
|
||||
}
|
||||
path,
|
||||
ship,
|
||||
'edit-field': editField,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
invite(resource: Resource, ship: Patp, text = '') {
|
||||
return this.viewAction({
|
||||
invite: { resource, ship, text },
|
||||
});
|
||||
}
|
||||
|
||||
join(resource: Resource) {
|
||||
return this.viewAction({
|
||||
join: resource,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,134 +1,40 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp } from '../types/noun';
|
||||
|
||||
import { Path, Patp, Enc } from '../types/noun';
|
||||
import {
|
||||
GroupAction,
|
||||
GroupPolicy,
|
||||
Resource,
|
||||
Tag,
|
||||
GroupPolicyDiff,
|
||||
} from '../types/group-update';
|
||||
|
||||
export default class GroupsApi extends BaseApi<StoreState> {
|
||||
add(path: Path, ships: Patp[] = []) {
|
||||
return this.action('group-store', 'group-action', {
|
||||
add: { members: ships, path }
|
||||
});
|
||||
remove(resource: Resource, ships: Patp[]) {
|
||||
return this.proxyAction({ removeMembers: { resource, ships } });
|
||||
}
|
||||
|
||||
remove(path: Path, ships: Patp[] = []) {
|
||||
return this.action('group-store', 'group-action', {
|
||||
remove: { members: ships, path }
|
||||
});
|
||||
addTag(resource: Resource, tag: Tag, ships: Patp[]) {
|
||||
return this.proxyAction({ addTag: { resource, tag, ships } });
|
||||
}
|
||||
|
||||
removeTag(resource: Resource, tag: Tag, ships: Patp[]) {
|
||||
return this.proxyAction({ removeTag: { resource, tag, ships } });
|
||||
}
|
||||
|
||||
add(resource: Resource, ships: Patp[]) {
|
||||
return this.proxyAction({ addMembers: { resource, ships } });
|
||||
}
|
||||
|
||||
changePolicy(resource: Resource, diff: GroupPolicyDiff) {
|
||||
return this.proxyAction({ changePolicy: { resource, diff } });
|
||||
}
|
||||
|
||||
private proxyAction(action: GroupAction) {
|
||||
return this.action('group-push-hook', 'group-update', action);
|
||||
}
|
||||
|
||||
private storeAction(action: GroupAction) {
|
||||
return this.action('group-store', 'group-action', action);
|
||||
}
|
||||
}
|
||||
|
||||
class PrivateHelper extends BaseApi {
|
||||
contactViewAction(data) {
|
||||
return this.action('contact-view', 'json', data);
|
||||
}
|
||||
|
||||
contactCreate(path, ships = [], title, description) {
|
||||
return this.contactViewAction({
|
||||
create: {
|
||||
path,
|
||||
ships,
|
||||
title,
|
||||
description
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
contactShare(recipient, path, ship, contact) {
|
||||
return this.contactViewAction({
|
||||
share: {
|
||||
recipient, path, ship, contact
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
contactDelete(path) {
|
||||
return this.contactViewAction({ delete: { path } });
|
||||
}
|
||||
|
||||
contactRemove(path, ship) {
|
||||
return this.contactViewAction({ remove: { path, ship } });
|
||||
}
|
||||
|
||||
contactHookAction(data) {
|
||||
return this.action('contact-hook', 'contact-action', data);
|
||||
}
|
||||
|
||||
contactEdit(path, ship, editField) {
|
||||
/* editField can be...
|
||||
{nickname: ''}
|
||||
{email: ''}
|
||||
{phone: ''}
|
||||
{website: ''}
|
||||
{notes: ''}
|
||||
{color: 'fff'} // with no 0x prefix
|
||||
{avatar: null}
|
||||
{avatar: {url: ''}}
|
||||
*/
|
||||
return this.contactHookAction({
|
||||
edit: {
|
||||
path, ship, 'edit-field': editField
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteAction(data) {
|
||||
return this.action('invite-store', 'json', data);
|
||||
}
|
||||
|
||||
inviteAccept(uid) {
|
||||
return this.inviteAction({
|
||||
accept: {
|
||||
path: '/contacts',
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteDecline(uid) {
|
||||
return this.inviteAction({
|
||||
decline: {
|
||||
path: '/contacts',
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
metadataAction(data) {
|
||||
return this.action('metadata-hook', 'metadata-action', data);
|
||||
}
|
||||
|
||||
metadataAdd(appPath, groupPath, title, description, dateCreated, color) {
|
||||
const creator = `~${window.ship}`;
|
||||
return this.metadataAction({
|
||||
add: {
|
||||
'group-path': groupPath,
|
||||
resource: {
|
||||
'app-path': appPath,
|
||||
'app-name': 'contacts'
|
||||
},
|
||||
metadata: {
|
||||
title,
|
||||
description,
|
||||
color,
|
||||
'date-created': dateCreated,
|
||||
creator
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setSelected(selected) {
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
selected: selected
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,4 +29,14 @@ export default class LocalApi extends BaseApi<StoreState> {
|
||||
})
|
||||
}
|
||||
|
||||
setDark(isDark: boolean) {
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
setDark: isDark
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import { PatpNoSig } from '../../types/noun';
|
||||
import GlobalApi from '../../api/global';
|
||||
import { StoreState } from '../../store/type';
|
||||
import GlobalSubscription from '../../subscription/global';
|
||||
import {groupBunts} from '../../types/group-update';
|
||||
|
||||
type ChatAppProps = StoreState & {
|
||||
ship: PatpNoSig;
|
||||
@ -80,7 +81,7 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
return e[0];
|
||||
})
|
||||
.includes(associations.chat?.[stat]?.['group-path']) ||
|
||||
associations.chat?.[stat]?.['group-path'].startsWith('/~/'))
|
||||
props.groups[associations.chat?.[stat]?.['group-path']]?.hidden)
|
||||
) {
|
||||
totalUnreads += unread;
|
||||
}
|
||||
@ -98,11 +99,11 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
sidebarShown,
|
||||
inbox,
|
||||
contacts,
|
||||
permissions,
|
||||
chatSynced,
|
||||
api,
|
||||
chatInitialized,
|
||||
pendingMessages
|
||||
pendingMessages,
|
||||
groups
|
||||
} = props;
|
||||
|
||||
const renderChannelSidebar = (props, station?) => (
|
||||
@ -147,7 +148,7 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/~chat/new/dm/:ship"
|
||||
path="/~chat/new/dm/:ship?"
|
||||
render={(props) => {
|
||||
const ship = props.match.params.ship;
|
||||
|
||||
@ -162,7 +163,7 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
<NewDmScreen
|
||||
api={api}
|
||||
inbox={inbox}
|
||||
permissions={permissions || {}}
|
||||
groups={groups || {}}
|
||||
contacts={contacts || {}}
|
||||
associations={associations.contacts}
|
||||
chatSynced={chatSynced || {}}
|
||||
@ -188,7 +189,7 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
<NewScreen
|
||||
api={api}
|
||||
inbox={inbox || {}}
|
||||
permissions={permissions || {}}
|
||||
groups={groups}
|
||||
contacts={contacts || {}}
|
||||
associations={associations.contacts}
|
||||
chatSynced={chatSynced || {}}
|
||||
@ -200,13 +201,9 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/~chat/join/(~)?/:ship?/:station?"
|
||||
path="/~chat/join/:ship?/:station?"
|
||||
render={(props) => {
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
}
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
@ -232,10 +229,6 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
path="/~chat/(popout)?/room/(~)?/:ship/:station+"
|
||||
render={(props) => {
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
}
|
||||
const mailbox = inbox[station] || {
|
||||
config: {
|
||||
read: 0,
|
||||
@ -258,13 +251,8 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
const association =
|
||||
station in associations['chat'] ? associations.chat[station] : {};
|
||||
|
||||
const permission =
|
||||
station in permissions
|
||||
? permissions[station]
|
||||
: {
|
||||
who: new Set([]),
|
||||
kind: 'white'
|
||||
};
|
||||
const group = groups[association['group-path']] || groupBunts.group();
|
||||
|
||||
const popout = props.match.url.includes('/popout/');
|
||||
|
||||
return (
|
||||
@ -286,7 +274,7 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
envelopes={mailbox.envelopes}
|
||||
inbox={inbox}
|
||||
contacts={roomContacts}
|
||||
permission={permission}
|
||||
group={group}
|
||||
pendingMessages={pendingMessages}
|
||||
s3={s3}
|
||||
popout={popout}
|
||||
@ -303,20 +291,14 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
path="/~chat/(popout)?/members/(~)?/:ship/:station+"
|
||||
render={(props) => {
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
}
|
||||
|
||||
const permission = permissions[station] || {
|
||||
kind: '',
|
||||
who: new Set([])
|
||||
};
|
||||
const popout = props.match.url.includes('/popout/');
|
||||
|
||||
const association =
|
||||
station in associations['chat'] ? associations.chat[station] : {};
|
||||
const groupPath = association['group-path'];
|
||||
|
||||
const group = groups[groupPath] || {};
|
||||
return (
|
||||
<Skeleton
|
||||
associations={associations}
|
||||
@ -329,11 +311,12 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
<MemberScreen
|
||||
{...props}
|
||||
api={api}
|
||||
group={group}
|
||||
groups={groups}
|
||||
associations={associations}
|
||||
station={station}
|
||||
association={association}
|
||||
permission={permission}
|
||||
contacts={contacts}
|
||||
permissions={permissions}
|
||||
popout={popout}
|
||||
sidebarShown={sidebarShown}
|
||||
/>
|
||||
@ -346,20 +329,11 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
path="/~chat/(popout)?/settings/(~)?/:ship/:station+"
|
||||
render={(props) => {
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
}
|
||||
|
||||
const popout = props.match.url.includes('/popout/');
|
||||
|
||||
const permission = permissions[station] || {
|
||||
kind: '',
|
||||
who: new Set([])
|
||||
};
|
||||
|
||||
const association =
|
||||
station in associations['chat'] ? associations.chat[station] : {};
|
||||
const group = groups[association['group-path']] || groupBunts.group();
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
@ -374,8 +348,8 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
{...props}
|
||||
station={station}
|
||||
association={association}
|
||||
permission={permission}
|
||||
permissions={permissions || {}}
|
||||
groups={groups || {}}
|
||||
group={group}
|
||||
contacts={contacts || {}}
|
||||
associations={associations.contacts}
|
||||
api={api}
|
||||
|
@ -19,6 +19,7 @@ import { Contacts } from "../../../types/contact-update";
|
||||
import { Path, Patp } from "../../../types/noun";
|
||||
import GlobalApi from "../../../api/global";
|
||||
import { Association } from "../../../types/metadata-update";
|
||||
import {Group} from "../../../types/group-update";
|
||||
|
||||
function getNumPending(props: any) {
|
||||
const result = props.pendingMessages.has(props.station)
|
||||
@ -79,7 +80,7 @@ type ChatScreenProps = RouteComponentProps<{
|
||||
length: number;
|
||||
inbox: Inbox;
|
||||
contacts: Contacts;
|
||||
permission: any;
|
||||
group: Group;
|
||||
pendingMessages: Map<Path, Envelope[]>;
|
||||
s3: any;
|
||||
popout: boolean;
|
||||
@ -388,7 +389,8 @@ export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
|
||||
paddingTop={paddingTop}
|
||||
paddingBot={paddingBot}
|
||||
pending={Boolean(msg.pending)}
|
||||
group={props.association}
|
||||
group={props.group}
|
||||
association={props.association}
|
||||
/>
|
||||
);
|
||||
if (unread > 0 && i === unread - 1) {
|
||||
@ -505,7 +507,7 @@ export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
|
||||
|
||||
const lastMsgNum = messages.length > 0 ? messages.length : 0;
|
||||
|
||||
const group = Array.from(props.permission.who.values());
|
||||
const group = Array.from(props.group.members);
|
||||
|
||||
const isinPopout = props.popout ? "popout/" : "";
|
||||
|
||||
|
@ -23,16 +23,13 @@ export class JoinScreen extends Component {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { props, state } = this;
|
||||
|
||||
if ((props.autoJoin !== '/undefined/undefined' &&
|
||||
props.autoJoin !== '/~/undefined/undefined') &&
|
||||
if ((props.autoJoin !== '/undefined/undefined') &&
|
||||
(props.api && (prevProps?.api !== props.api))) {
|
||||
let station = props.autoJoin.split('/');
|
||||
const sig = props.autoJoin.includes('/~/');
|
||||
|
||||
const ship = sig ? station[2] : station[1];
|
||||
const ship = station[1];
|
||||
if (
|
||||
station.length < 2 ||
|
||||
(Boolean(sig) && station.length < 3) ||
|
||||
!urbitOb.isValidPatp(ship)
|
||||
) {
|
||||
this.setState({
|
||||
@ -59,12 +56,10 @@ export class JoinScreen extends Component {
|
||||
const { props, state } = this;
|
||||
|
||||
let station = state.station.split('/');
|
||||
const sig = state.station.includes('/~/');
|
||||
|
||||
const ship = sig ? station[2] : station[1];
|
||||
const ship = station[1];
|
||||
if (
|
||||
station.length < 2 ||
|
||||
(Boolean(sig) && station.length < 3) ||
|
||||
!urbitOb.isValidPatp(ship)
|
||||
) {
|
||||
this.setState({
|
||||
@ -84,7 +79,7 @@ export class JoinScreen extends Component {
|
||||
|
||||
stationChange(event) {
|
||||
this.setState({
|
||||
station: `/${event.target.value}`
|
||||
station: `/${event.target.value.trim()}`
|
||||
});
|
||||
}
|
||||
|
||||
@ -116,7 +111,7 @@ export class JoinScreen extends Component {
|
||||
</div>
|
||||
<h2 className="mb3 f8">Join Existing Chat</h2>
|
||||
<div className="w-100">
|
||||
<p className="f8 lh-copy mt3 db">Enter a <span className="mono">~ship/chat-name</span> or <span className="mono">~/~ship/chat-name</span></p>
|
||||
<p className="f8 lh-copy mt3 db">Enter a <span className="mono">~ship/chat-name</span></p>
|
||||
<p className="f9 gray2 mb4">Chat names use lowercase, hyphens, and slashes.</p>
|
||||
<textarea
|
||||
ref={ (e) => {
|
||||
|
@ -13,7 +13,7 @@ export class ChannelItem extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
const unreadElem = props.unread ? 'fw6' : '';
|
||||
const unreadElem = props.unread ? 'fw6 white-d' : '';
|
||||
|
||||
const title = props.title;
|
||||
|
||||
@ -23,7 +23,7 @@ export class ChannelItem extends Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={'z1 ph4 pv1 ' + selectedCss}
|
||||
className={'z1 ph5 pv1 ' + selectedCss}
|
||||
onClick={this.onClick.bind(this)}
|
||||
>
|
||||
<div className="w-100 v-mid">
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ChannelItem } from './channel-item';
|
||||
|
||||
export class GroupItem extends Component {
|
||||
@ -14,10 +15,10 @@ export class GroupItem extends Component {
|
||||
}
|
||||
|
||||
const channels = props.channels ? props.channels : [];
|
||||
const first = (props.index === 0) ? 'pt1' : 'pt4';
|
||||
const first = (props.index === 0) ? 'mt1 ' : 'mt6 ';
|
||||
|
||||
const channelItems = channels.sort((a, b) => {
|
||||
if (props.index === '/~/') {
|
||||
if (props.index === 'dm') {
|
||||
const aPreview = props.messagePreviews[a];
|
||||
const bPreview = props.messagePreviews[b];
|
||||
const aWhen = aPreview ? aPreview.when : 0;
|
||||
@ -63,10 +64,27 @@ export class GroupItem extends Component {
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
if (channelItems.length === 0) {
|
||||
channelItems.push(<p className="gray2 mt4 f9 tc">No direct messages</p>);
|
||||
}
|
||||
|
||||
let dmLink = <div />;
|
||||
|
||||
if (props.index === 'dm') {
|
||||
dmLink = <Link
|
||||
className="absolute right-0 f9 top-0 mr4 green2 bg-gray5 bg-gray1-d b--transparent br1"
|
||||
to="/~chat/new/dm"
|
||||
style={{ padding: '0rem 0.2rem' }}
|
||||
>
|
||||
+ DM
|
||||
</Link>;
|
||||
}
|
||||
return (
|
||||
<div className={first}>
|
||||
<p className="f9 ph4 fw6 pb2 gray3">{title}</p>
|
||||
{channelItems}
|
||||
<div className={first + 'relative'}>
|
||||
<p className="f9 ph4 gray3">{title}</p>
|
||||
{dmLink}
|
||||
{channelItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export class InviteElement extends Component {
|
||||
members: [],
|
||||
awaiting: true
|
||||
}, () => {
|
||||
props.api.groups.add(aud, props.path).then(() => {
|
||||
props.api.chatView.invite(props.path, aud).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,11 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { OverlaySigil } from './overlay-sigil';
|
||||
import { uxToHex, cite, writeText } from '../../../../lib/util';
|
||||
import moment from 'moment';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
|
||||
import urbitOb from 'urbit-ob';
|
||||
|
||||
const DISABLED_BLOCK_TOKENS = [
|
||||
'indentedCode',
|
||||
@ -71,7 +73,7 @@ export class Message extends Component {
|
||||
</div>
|
||||
);
|
||||
} else if ('url' in letter) {
|
||||
let imgMatch =
|
||||
const imgMatch =
|
||||
/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF|GIF|webp|WEBP|svg|SVG)$/
|
||||
.exec(letter.url);
|
||||
const youTubeRegex = new RegExp(String(/(?:https?:\/\/(?:[a-z]+.)?)/.source) // protocol
|
||||
@ -122,7 +124,6 @@ export class Message extends Component {
|
||||
<div>
|
||||
<a href={letter.url}
|
||||
className="f7 lh-copy v-top bb b--white-d word-break-all"
|
||||
href={letter.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@ -154,6 +155,22 @@ export class Message extends Component {
|
||||
</p>
|
||||
);
|
||||
} else {
|
||||
const group = letter.text.match(
|
||||
/([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z])+([/-])?)+/
|
||||
);
|
||||
if ((group !== null) // matched possible chatroom
|
||||
&& (group[2].length > 2) // possible ship?
|
||||
&& (urbitOb.isValidPatp(group[2]) // valid patp?
|
||||
&& (group[0] === letter.text))) { // entire message is room name?
|
||||
return (
|
||||
<Link
|
||||
className="bb b--black b--white-d f7 mono lh-copy v-top"
|
||||
to={'/~groups/join/' + group.input}
|
||||
>
|
||||
{letter.text}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<section className="chat-md-message">
|
||||
<MessageMarkdown
|
||||
@ -163,6 +180,7 @@ export class Message extends Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
@ -205,6 +223,7 @@ export class Message extends Component {
|
||||
contact={contact}
|
||||
color={color}
|
||||
sigilClass={sigilClass}
|
||||
association={props.association}
|
||||
group={props.group}
|
||||
className="fl pr3 v-top bg-white bg-gray0-d"
|
||||
/>
|
||||
|
@ -86,6 +86,7 @@ export class OverlaySigil extends Component {
|
||||
color={props.color}
|
||||
topSpace={state.topSpace}
|
||||
bottomSpace={state.bottomSpace}
|
||||
association={props.association}
|
||||
group={props.group}
|
||||
onDismiss={this.profileHide}
|
||||
/>
|
||||
|
@ -34,7 +34,7 @@ export class ProfileOverlay extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { contact, ship, color, topSpace, bottomSpace, group } = this.props;
|
||||
const { contact, ship, color, topSpace, bottomSpace, group, association } = this.props;
|
||||
|
||||
let top, bottom;
|
||||
if (topSpace < OVERLAY_HEIGHT / 2) {
|
||||
@ -50,11 +50,11 @@ export class ProfileOverlay extends Component {
|
||||
|
||||
const isOwn = window.ship === ship;
|
||||
|
||||
const identityHref = group['group-path'].startsWith('/~/')
|
||||
const identityHref = group.hidden
|
||||
? '/~groups/me'
|
||||
: `/~groups/view${group['group-path']}/${window.ship}`;
|
||||
: `/~groups/view${association['group-path']}/${window.ship}`;
|
||||
|
||||
const img = (contact && (contact.avatar !== null))
|
||||
let img = (contact && (contact.avatar !== null))
|
||||
? <img src={contact.avatar} height={160} width={160} className="brt2 dib" />
|
||||
: <Sigil
|
||||
ship={ship}
|
||||
@ -64,6 +64,10 @@ export class ProfileOverlay extends Component {
|
||||
svgClass="brt2"
|
||||
/>;
|
||||
|
||||
if (!group.hidden) {
|
||||
img = <Link to={`/~groups/view${association['group-path']}/${ship}`}>{img}</Link>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.popoverRef}
|
||||
|
@ -7,44 +7,23 @@ import { ChatTabBar } from './lib/chat-tabbar';
|
||||
import { MemberElement } from './lib/member-element';
|
||||
import { InviteElement } from './lib/invite-element';
|
||||
import { SidebarSwitcher } from '../../../components/SidebarSwitch';
|
||||
import { GroupView } from '../../../components/Group';
|
||||
import { PatpNoSig } from '../../../types/noun';
|
||||
|
||||
export class MemberScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.inviteShips = this.inviteShips.bind(this);
|
||||
}
|
||||
|
||||
inviteShips(ships) {
|
||||
const { props } = this;
|
||||
return props.api.chat.invite(props.station, ships.map(s => `~${s}`));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
const perm = Array.from(props.permission.who.values());
|
||||
|
||||
let memberText = '';
|
||||
let modifyText = '';
|
||||
|
||||
if (props.permission.kind === 'black') {
|
||||
memberText = 'Everyone banned from accessing this chat.';
|
||||
modifyText = 'Ban someone from accessing this chat.';
|
||||
} else if (props.permission.kind === 'white') {
|
||||
memberText = 'Everyone with permission to access this chat.';
|
||||
modifyText = 'Invite someone to this chat.';
|
||||
}
|
||||
|
||||
const contacts = (props.station in props.contacts)
|
||||
? props.contacts[props.station] : {};
|
||||
|
||||
const members = perm.map((mem) => {
|
||||
const contact = (mem in contacts)
|
||||
? contacts[mem] : false;
|
||||
|
||||
return (
|
||||
<MemberElement
|
||||
key={mem}
|
||||
owner={deSig(props.match.params.ship)}
|
||||
contact={contact}
|
||||
ship={mem}
|
||||
path={props.station}
|
||||
kind={props.permission.kind}
|
||||
api={props.api}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const isinPopout = this.props.popout ? 'popout/' : '';
|
||||
|
||||
let title = props.station.substr(1);
|
||||
@ -57,12 +36,12 @@ export class MemberScreen extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column white-d">
|
||||
<div className='h-100 w-100 overflow-x-hidden flex flex-column white-d'>
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
className='w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8'
|
||||
style={{ height: '1rem' }}
|
||||
>
|
||||
<Link to="/~chat/">{'⟵ All Chats'}</Link>
|
||||
<Link to='/~chat/'>{'⟵ All Chats'}</Link>
|
||||
</div>
|
||||
<div
|
||||
className={`pl4 pt2 bb b--gray4 b--gray1-d bg-gray0-d flex relative
|
||||
@ -74,12 +53,15 @@ export class MemberScreen extends Component {
|
||||
popout={this.props.popout}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<Link to={'/~chat/' + isinPopout + 'room' + props.station}
|
||||
className="pt2 white-d"
|
||||
<Link
|
||||
to={'/~chat/' + isinPopout + 'room' + props.station}
|
||||
className='pt2 white-d'
|
||||
>
|
||||
<h2
|
||||
className={'dib f9 fw4 lh-solid v-top ' +
|
||||
((title === props.station.substr(1)) ? 'mono' : '')}
|
||||
className={
|
||||
'dib f9 fw4 lh-solid v-top ' +
|
||||
(title === props.station.substr(1) ? 'mono' : '')
|
||||
}
|
||||
style={{ width: 'max-content' }}
|
||||
>
|
||||
{title}
|
||||
@ -88,30 +70,23 @@ export class MemberScreen extends Component {
|
||||
<ChatTabBar
|
||||
{...props}
|
||||
station={props.station}
|
||||
numPeers={perm.length}
|
||||
numPeers={5}
|
||||
isOwner={deSig(props.match.params.ship) === window.ship}
|
||||
popout={this.props.popout}
|
||||
api={props.api}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-100 pl3 mt0 mt4-m mt4-l mt4-xl cf pr6">
|
||||
<div className="w-100 w-50-l w-50-xl fl pa2 pr3 pt3 pt0-l pt0-xl">
|
||||
<p className="f8 pb2">Modify Permissions</p>
|
||||
<p className="f9 gray2 mb3">{modifyText}</p>
|
||||
{window.ship === deSig(props.match.params.ship) ? (
|
||||
<InviteElement
|
||||
path={props.station}
|
||||
permissions={props.permission}
|
||||
contacts={props.contacts}
|
||||
api={props.api}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="w-100 w-50-l w-50-xl fl pa2 pr3 pt3 pt0-l pt0-xl">
|
||||
<p className="f8 pb2">Members</p>
|
||||
<p className="f9 gray2 mb3">{memberText}</p>
|
||||
{members}
|
||||
</div>
|
||||
<div className='w-100 pl3 mt0 mt4-m mt4-l mt4-xl cf pr6'>
|
||||
{ props.association['group-path'] && (
|
||||
<GroupView
|
||||
permissions
|
||||
group={props.group}
|
||||
resourcePath={props.association['group-path'] || ''}
|
||||
associations={props.associations}
|
||||
groups={props.groups}
|
||||
inviteShips={this.inviteShips}
|
||||
contacts={props.contacts}
|
||||
/> )}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,26 +1,36 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Spinner } from '../../../components/Spinner';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { InviteSearch } from '../../../components/InviteSearch';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { deSig } from '../../../lib/util';
|
||||
|
||||
export class NewDmScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
ship: null,
|
||||
ships: [],
|
||||
station: null,
|
||||
awaiting: false
|
||||
awaiting: false,
|
||||
title: '',
|
||||
idName: '',
|
||||
description: ''
|
||||
};
|
||||
|
||||
this.titleChange = this.titleChange.bind(this);
|
||||
this.descriptionChange = this.descriptionChange.bind(this);
|
||||
this.onClickCreate = this.onClickCreate.bind(this);
|
||||
this.setInvite = this.setInvite.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { props } = this;
|
||||
if (props.autoCreate && urbitOb.isValidPatp(props.autoCreate)) {
|
||||
const addedShip = this.state.ships;
|
||||
addedShip.push(props.autoCreate.slice(1));
|
||||
this.setState(
|
||||
{
|
||||
ship: props.autoCreate.slice(1),
|
||||
ships: addedShip,
|
||||
awaiting: true
|
||||
},
|
||||
this.onClickCreate
|
||||
@ -40,43 +50,119 @@ export class NewDmScreen extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
titleChange(event) {
|
||||
const asciiSafe = event.target.value.toLowerCase()
|
||||
.replace(/[^a-z0-9~_.-]/g, '-');
|
||||
this.setState({
|
||||
idName: asciiSafe,
|
||||
title: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
descriptionChange(event) {
|
||||
this.setState({
|
||||
description: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
setInvite(value) {
|
||||
this.setState({
|
||||
ships: value.ships
|
||||
});
|
||||
}
|
||||
|
||||
onClickCreate() {
|
||||
const { props, state } = this;
|
||||
|
||||
const station = `/~/~${window.ship}/dm--${state.ship}`;
|
||||
if (state.ships.length === 1) {
|
||||
const station = `/~${window.ship}/dm--${state.ships[0]}`;
|
||||
|
||||
const theirStation = `/~/~${state.ship}/dm--${window.ship}`;
|
||||
const theirStation = `/~${state.ships[0]}/dm--${window.ship}`;
|
||||
|
||||
if (station in props.inbox) {
|
||||
props.history.push(`/~chat/room${station}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (theirStation in props.inbox) {
|
||||
props.history.push(`/~chat/room${theirStation}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
station
|
||||
},
|
||||
() => {
|
||||
const groupPath = station;
|
||||
props.api.chat.create(
|
||||
`~${window.ship} <-> ~${state.ship}`,
|
||||
'',
|
||||
station,
|
||||
groupPath,
|
||||
'village',
|
||||
state.ship !== window.ship ? [`~${state.ship}`] : [],
|
||||
true
|
||||
);
|
||||
if (station in props.inbox) {
|
||||
props.history.push(`/~chat/room${station}`);
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
if (theirStation in props.inbox) {
|
||||
props.history.push(`/~chat/room${theirStation}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const aud = state.ship !== window.ship ? [`~${state.ships[0]}`] : [];
|
||||
|
||||
let title = `~${window.ship} <-> ~${state.ships[0]}`;
|
||||
|
||||
if (state.title !== '') {
|
||||
title = state.title;
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
station, awaiting: true
|
||||
},
|
||||
() => {
|
||||
const groupPath = `/ship/~${window.ship}/dm--${state.ships[0]}`;
|
||||
props.api.chat.create(
|
||||
title,
|
||||
state.description,
|
||||
station,
|
||||
groupPath,
|
||||
{ invite: { pending: aud } },
|
||||
aud,
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (state.ships.length > 1) {
|
||||
const aud = state.ships.map(mem => `~${deSig(mem.trim())}`);
|
||||
|
||||
let title = 'Direct Message';
|
||||
|
||||
if (state.title !== '') {
|
||||
title = state.title;
|
||||
} else {
|
||||
const asciiSafe = title.toLowerCase()
|
||||
.replace(/[^a-z0-9~_.-]/g, '-');
|
||||
this.setState({ idName: asciiSafe });
|
||||
}
|
||||
|
||||
const station = `/~${window.ship}/${state.idName}-${Math.floor(Math.random() * 10000)}`;
|
||||
|
||||
this.setState(
|
||||
{
|
||||
station, awaiting: true
|
||||
},
|
||||
() => {
|
||||
const groupPath = `/ship${station}`;
|
||||
props.api.chat.create(
|
||||
title,
|
||||
state.description,
|
||||
station,
|
||||
groupPath,
|
||||
{ invite: { pending: aud } },
|
||||
aud,
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
const createClasses = (state.idName || state.ships.length >= 1)
|
||||
? 'pointer dib f9 green2 bg-gray0-d ba pv3 ph4 b--green2 mt4'
|
||||
: 'pointer dib f9 gray2 ba bg-gray0-d pa2 pv3 ph4 b--gray3 mt4';
|
||||
|
||||
const idClasses =
|
||||
'f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100 ' +
|
||||
'focus-b--black focus-b--white-d mt1 ';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
@ -87,15 +173,65 @@ export class NewDmScreen extends Component {
|
||||
<div className="w-100 dn-m dn-l dn-xl inter pt1 pb6 f8">
|
||||
<Link to="/~chat/">{'⟵ All Chats'}</Link>
|
||||
</div>
|
||||
<h2 className="mb3 f8">New DM</h2>
|
||||
<h2 className="mb3 f8">New Direct Message</h2>
|
||||
<div className="w-100">
|
||||
<p className="f8 mt4 db">
|
||||
Name
|
||||
<span className="gray3"> (Optional)</span>
|
||||
</p>
|
||||
<textarea
|
||||
className={idClasses}
|
||||
placeholder="The Passage"
|
||||
rows={1}
|
||||
style={{
|
||||
resize: 'none'
|
||||
}}
|
||||
onChange={this.titleChange}
|
||||
/>
|
||||
<p className="f8 mt4 db">
|
||||
Description
|
||||
<span className="gray3"> (Optional)</span>
|
||||
</p>
|
||||
<textarea
|
||||
className={idClasses}
|
||||
placeholder="The most beautiful direct message"
|
||||
rows={1}
|
||||
style={{
|
||||
resize: 'none'
|
||||
}}
|
||||
onChange={this.descriptionChange}
|
||||
/>
|
||||
<p className="f8 mt4 db">
|
||||
Invite Members
|
||||
</p>
|
||||
<p className="f9 gray2 db mv1">
|
||||
Selected ships will be invited to the direct message
|
||||
</p>
|
||||
<InviteSearch
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
associations={props.associations}
|
||||
groupResults={false}
|
||||
shipResults={true}
|
||||
invites={{
|
||||
groups: [],
|
||||
ships: state.ships
|
||||
}}
|
||||
setInvite={this.setInvite}
|
||||
/>
|
||||
<button
|
||||
onClick={this.onClickCreate.bind(this)}
|
||||
className={createClasses}
|
||||
>
|
||||
Create Direct Message
|
||||
</button>
|
||||
<Spinner
|
||||
awaiting={this.state.awaiting}
|
||||
classes="mt4"
|
||||
text="Creating chat..."
|
||||
text="Creating Direct Message..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import { InviteSearch } from '../../../components/InviteSearch';
|
||||
import { Spinner } from '../../../components/Spinner';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { deSig } from '../../../lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
|
||||
export class NewScreen extends Component {
|
||||
constructor(props) {
|
||||
@ -14,9 +13,8 @@ export class NewScreen extends Component {
|
||||
idName: '',
|
||||
groups: [],
|
||||
ships: [],
|
||||
security: 'channel',
|
||||
privacy: 'invite',
|
||||
idError: false,
|
||||
inviteError: false,
|
||||
allowHistory: true,
|
||||
createGroup: false,
|
||||
awaiting: false
|
||||
@ -24,9 +22,7 @@ export class NewScreen extends Component {
|
||||
|
||||
this.titleChange = this.titleChange.bind(this);
|
||||
this.descriptionChange = this.descriptionChange.bind(this);
|
||||
this.allowHistoryChange = this.allowHistoryChange.bind(this);
|
||||
this.setInvite = this.setInvite.bind(this);
|
||||
this.createGroupChange = this.createGroupChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
@ -62,32 +58,13 @@ export class NewScreen extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
createGroupChange(event) {
|
||||
if (event.target.checked) {
|
||||
this.setState({
|
||||
createGroup: Boolean(event.target.checked),
|
||||
security: 'village'
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
createGroup: Boolean(event.target.checked),
|
||||
security: 'channel'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
allowHistoryChange(event) {
|
||||
this.setState({ allowHistory: Boolean(event.target.checked) });
|
||||
}
|
||||
|
||||
onClickCreate() {
|
||||
const { props, state } = this;
|
||||
const grouped = (this.state.createGroup || (this.state.groups.length > 0));
|
||||
|
||||
if (!state.title) {
|
||||
this.setState({
|
||||
idError: true,
|
||||
inviteError: false
|
||||
idError: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -96,38 +73,20 @@ export class NewScreen extends Component {
|
||||
|
||||
if (station in props.inbox) {
|
||||
this.setState({
|
||||
inviteError: false,
|
||||
idError: true,
|
||||
success: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let isValid = true;
|
||||
const aud = state.ships.map(mem => `~${deSig(mem.trim())}`);
|
||||
aud.forEach((mem) => {
|
||||
if (!urbitOb.isValidPatp(mem)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
if(state.ships.length === 1 && state.security === 'village' && !state.createGroup) {
|
||||
props.history.push(`/~chat/new/dm/${aud[0]}`);
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
this.setState({
|
||||
inviteError: true,
|
||||
idError: false,
|
||||
success: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.textarea) {
|
||||
this.textarea.value = '';
|
||||
}
|
||||
|
||||
const policy = state.privacy === 'invite' ? { invite: { pending: aud } } : { open: { banRanks: [], banned: [] } };
|
||||
|
||||
this.setState({
|
||||
error: false,
|
||||
success: true,
|
||||
@ -135,14 +94,8 @@ export class NewScreen extends Component {
|
||||
ships: [],
|
||||
awaiting: true
|
||||
}, () => {
|
||||
// if we want a "proper group" that can be managed from the contacts UI,
|
||||
// we make a path of the form /~zod/cool-group
|
||||
// if not, we make a path of the form /~/~zod/free-chat
|
||||
let appPath = `/~${window.ship}${station}`;
|
||||
if (!state.createGroup && state.groups.length === 0) {
|
||||
appPath = `/~${appPath}`;
|
||||
}
|
||||
let groupPath = appPath;
|
||||
const appPath = `/~${window.ship}${station}`;
|
||||
let groupPath = `/ship${appPath}`;
|
||||
if (state.groups.length > 0) {
|
||||
groupPath = state.groups[0];
|
||||
}
|
||||
@ -151,9 +104,10 @@ export class NewScreen extends Component {
|
||||
state.description,
|
||||
appPath,
|
||||
groupPath,
|
||||
state.security,
|
||||
policy,
|
||||
aud,
|
||||
state.allowHistory
|
||||
state.allowHistory,
|
||||
state.createGroup
|
||||
);
|
||||
submit.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
@ -164,24 +118,14 @@ export class NewScreen extends Component {
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
let inviteSwitchClasses = (state.security === 'village')
|
||||
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
|
||||
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
|
||||
if (state.createGroup) {
|
||||
inviteSwitchClasses = inviteSwitchClasses + ' o-50';
|
||||
}
|
||||
|
||||
const createGroupClasses = state.createGroup
|
||||
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
|
||||
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
|
||||
|
||||
const createClasses = state.idName
|
||||
? 'pointer db f9 green2 bg-gray0-d ba pv3 ph4 b--green2'
|
||||
: 'pointer db f9 gray2 ba bg-gray0-d pa2 pv3 ph4 b--gray3';
|
||||
? 'pointer db f9 green2 bg-gray0-d ba pv3 ph4 b--green2 mt4'
|
||||
: 'pointer db f9 gray2 ba bg-gray0-d pa2 pv3 ph4 b--gray3 mt4';
|
||||
|
||||
const idClasses =
|
||||
'f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100 ' +
|
||||
'focus-b--black focus-b--white-d ';
|
||||
'focus-b--black focus-b--white-d mt1 ';
|
||||
|
||||
let idErrElem = (<span />);
|
||||
if (state.idError) {
|
||||
@ -192,29 +136,6 @@ export class NewScreen extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
let createGroupToggle = <div />;
|
||||
if (state.groups.length === 0) {
|
||||
createGroupToggle = (
|
||||
<div className="mv7">
|
||||
<input
|
||||
type="checkbox"
|
||||
style={{ WebkitAppearance: 'none', width: 28 }}
|
||||
className={createGroupClasses}
|
||||
onChange={this.createGroupChange}
|
||||
/>
|
||||
<span className="dib f9 white-d inter ml3">Create Group</span>
|
||||
<p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
|
||||
Participants will share this group across applications
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const groups = {};
|
||||
Object.keys(props.permissions).forEach((pem) => {
|
||||
groups[pem] = props.permissions[pem].who;
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
@ -225,9 +146,9 @@ export class NewScreen extends Component {
|
||||
<div className="w-100 dn-m dn-l dn-xl inter pt1 pb6 f8">
|
||||
<Link to="/~chat/">{'⟵ All Chats'}</Link>
|
||||
</div>
|
||||
<h2 className="mb3 f8">New Chat</h2>
|
||||
<h2 className="mb4 f8">New Group Chat</h2>
|
||||
<div className="w-100">
|
||||
<p className="f8 mt3 lh-copy db">Name</p>
|
||||
<p className="f8 mt4 db">Name</p>
|
||||
<textarea
|
||||
className={idClasses}
|
||||
placeholder="Secret Chat"
|
||||
@ -238,7 +159,7 @@ export class NewScreen extends Component {
|
||||
onChange={this.titleChange}
|
||||
/>
|
||||
{idErrElem}
|
||||
<p className="f8 mt3 lh-copy db">
|
||||
<p className="f8 mt4 db">
|
||||
Description
|
||||
<span className="gray3"> (Optional)</span>
|
||||
</p>
|
||||
@ -251,26 +172,27 @@ export class NewScreen extends Component {
|
||||
}}
|
||||
onChange={this.descriptionChange}
|
||||
/>
|
||||
<p className="f8 mt4 lh-copy db">
|
||||
Invite
|
||||
<span className="gray3"> (Optional)</span>
|
||||
<div className="mt4 db relative">
|
||||
<p className="f8">
|
||||
Select Group
|
||||
</p>
|
||||
<p className="f9 gray2 db mb2 pt1">
|
||||
Selected groups or ships will be able to post to chat
|
||||
<Link className="green2 absolute right-0 bottom-0 f9" to="/~groups/new">+New</Link>
|
||||
<p className="f9 gray2 db mv1">
|
||||
Chat will be added to selected group
|
||||
</p>
|
||||
</div>
|
||||
<InviteSearch
|
||||
groups={groups}
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
associations={props.associations}
|
||||
groupResults={true}
|
||||
shipResults={true}
|
||||
shipResults={false}
|
||||
invites={{
|
||||
groups: state.groups,
|
||||
ships: state.ships
|
||||
ships: []
|
||||
}}
|
||||
setInvite={this.setInvite}
|
||||
/>
|
||||
{createGroupToggle}
|
||||
<button
|
||||
onClick={this.onClickCreate.bind(this)}
|
||||
className={createClasses}
|
||||
|
@ -185,10 +185,10 @@ export class SettingsScreen extends Component {
|
||||
|
||||
const chatOwner = (deSig(props.match.params.ship) === window.ship);
|
||||
|
||||
const groupPath = props.association['group-path'];
|
||||
const ownedUnmanagedVillage =
|
||||
chatOwner &&
|
||||
props.station.slice(0, 3) === '/~/' &&
|
||||
props.permission.kind === 'white';
|
||||
!props.contacts[groupPath];
|
||||
|
||||
if (!ownedUnmanagedVillage) {
|
||||
return null;
|
||||
@ -217,11 +217,6 @@ export class SettingsScreen extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
const groups = {};
|
||||
Object.keys(props.permissions).forEach((pem) => {
|
||||
groups[pem] = props.permissions[pem].who;
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={'w-100 fl mt3'} style={{ maxWidth: '29rem' }}>
|
||||
@ -231,7 +226,7 @@ export class SettingsScreen extends Component {
|
||||
group to add this chat to.
|
||||
</p>
|
||||
<InviteSearch
|
||||
groups={groups}
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
associations={props.associations}
|
||||
groupResults={true}
|
||||
@ -357,7 +352,7 @@ export class SettingsScreen extends Component {
|
||||
const { props, state } = this;
|
||||
const isinPopout = this.props.popout ? 'popout/' : '';
|
||||
|
||||
const permission = Array.from(props.permission.who.values());
|
||||
const permission = Array.from(props.group.members.values());
|
||||
|
||||
if (state.isLoading) {
|
||||
let title = props.station.substr(1);
|
||||
@ -456,29 +451,6 @@ export class SettingsScreen extends Component {
|
||||
</div>
|
||||
<div className="w-100 pl3 mt4 cf">
|
||||
<h2 className="f8 pb2">Chat Settings</h2>
|
||||
<div className="w-100 mt3">
|
||||
<p className="f8 mt3 lh-copy">Share</p>
|
||||
<p className="f9 gray2 mb4">Share a shortcode to join this chat</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: '29rem' }}
|
||||
>
|
||||
<input
|
||||
className="f8 mono ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100 flex-auto mr3"
|
||||
disabled={true}
|
||||
value={props.station.substr(1)}
|
||||
/>
|
||||
<span className="f8 pointer absolute pa3 inter"
|
||||
style={{ right: 12, top: 1 }}
|
||||
ref="copy"
|
||||
onClick={() => {
|
||||
writeText(props.station.substr(1));
|
||||
this.refs.copy.innerText = 'Copied';
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{this.renderGroupify()}
|
||||
{this.renderDelete()}
|
||||
{this.renderMetadataSettings()}
|
||||
|
@ -1,40 +1,17 @@
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Welcome from './lib/welcome';
|
||||
import { alphabetiseAssociations } from '../../../lib/util';
|
||||
import { SidebarInvite } from './lib/sidebar-invite';
|
||||
import { GroupItem } from './lib/group-item';
|
||||
import { ShipSearchInput } from './lib/ship-search';
|
||||
|
||||
export class Sidebar extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
dmOverlay: false
|
||||
};
|
||||
}
|
||||
|
||||
onClickNew() {
|
||||
this.props.history.push('/~chat/new');
|
||||
}
|
||||
|
||||
onClickDm() {
|
||||
this.setState(({ dmOverlay }) => ({ dmOverlay: !dmOverlay }) );
|
||||
}
|
||||
|
||||
onClickJoin() {
|
||||
this.props.history.push('/~chat/join');
|
||||
}
|
||||
|
||||
goDm(ship) {
|
||||
this.setState({ dmOverlay: false }, () => {
|
||||
this.props.history.push(`/~chat/new/dm/~${ship}`);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
const { props } = this;
|
||||
|
||||
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
|
||||
|
||||
@ -48,26 +25,24 @@ export class Sidebar extends Component {
|
||||
|
||||
const groupedChannels = {};
|
||||
Object.keys(props.inbox).map((box) => {
|
||||
if (box.startsWith('/~/')) {
|
||||
if (groupedChannels['/~/']) {
|
||||
const array = groupedChannels['/~/'];
|
||||
const path = chatAssoc[box]
|
||||
? chatAssoc[box]['group-path'] : box;
|
||||
|
||||
if (path in contactAssoc) {
|
||||
if (groupedChannels[path]) {
|
||||
const array = groupedChannels[path];
|
||||
array.push(box);
|
||||
groupedChannels['/~/'] = array;
|
||||
groupedChannels[path] = array;
|
||||
} else {
|
||||
groupedChannels['/~/'] = [box];
|
||||
groupedChannels[path] = [box];
|
||||
}
|
||||
} else {
|
||||
const path = chatAssoc[box]
|
||||
? chatAssoc[box]['group-path'] : box;
|
||||
|
||||
if (path in contactAssoc) {
|
||||
if (groupedChannels[path]) {
|
||||
const array = groupedChannels[path];
|
||||
array.push(box);
|
||||
groupedChannels[path] = array;
|
||||
} else {
|
||||
groupedChannels[path] = [box];
|
||||
}
|
||||
if (groupedChannels['dm']) {
|
||||
const array = groupedChannels['dm'];
|
||||
array.push(box);
|
||||
groupedChannels['dm'] = array;
|
||||
} else {
|
||||
groupedChannels['dm'] = [box];
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -111,29 +86,20 @@ export class Sidebar extends Component {
|
||||
/>
|
||||
);
|
||||
});
|
||||
if (groupedChannels['/~/'] && groupedChannels['/~/'].length !== 0) {
|
||||
groupedItems.push(
|
||||
<GroupItem
|
||||
association={'/~/'}
|
||||
chatMetadata={chatAssoc}
|
||||
channels={groupedChannels['/~/']}
|
||||
inbox={props.inbox}
|
||||
station={props.station}
|
||||
unreads={props.unreads}
|
||||
index={'/~/'}
|
||||
key={'/~/'}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const candidates = state.dmOverlay
|
||||
? _.chain(this.props.contacts)
|
||||
.values()
|
||||
.map(_.keys)
|
||||
.flatten()
|
||||
.uniq()
|
||||
.value()
|
||||
: [];
|
||||
// add direct messages after groups
|
||||
groupedItems.push(
|
||||
<GroupItem
|
||||
association={'dm'}
|
||||
chatMetadata={chatAssoc}
|
||||
channels={groupedChannels['dm']}
|
||||
inbox={props.inbox}
|
||||
station={props.station}
|
||||
unreads={props.unreads}
|
||||
index={'dm'}
|
||||
key={'dm'}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -145,33 +111,7 @@ export class Sidebar extends Component {
|
||||
className="dib f9 pointer green2 gray4-d mr4"
|
||||
onClick={this.onClickNew.bind(this)}
|
||||
>
|
||||
New Chat
|
||||
</a>
|
||||
|
||||
<div className="dib relative mr4">
|
||||
{ state.dmOverlay && (
|
||||
<ShipSearchInput
|
||||
className="absolute"
|
||||
contacts={{}}
|
||||
candidates={candidates}
|
||||
onSelect={this.goDm.bind(this)}
|
||||
onClear={this.onClickDm.bind(this)}
|
||||
|
||||
/>
|
||||
)}
|
||||
<a
|
||||
className="f9 pointer green2 gray4-d"
|
||||
onClick={this.onClickDm.bind(this)}
|
||||
>
|
||||
DM
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a
|
||||
className="dib f9 pointer gray4-d"
|
||||
onClick={this.onClickJoin.bind(this)}
|
||||
>
|
||||
Join Chat
|
||||
New Group Chat
|
||||
</a>
|
||||
</div>
|
||||
<div className="overflow-y-auto h-100">
|
||||
|
@ -1,10 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import GroupsApi from '../../api/groups';
|
||||
import GroupsSubscription from '../../subscription/groups';
|
||||
import GroupsStore from '../../store/groups';
|
||||
|
||||
import './css/custom.css';
|
||||
|
||||
import { Skeleton } from './components/skeleton';
|
||||
@ -12,13 +8,22 @@ import { NewScreen } from './components/new';
|
||||
import { ContactSidebar } from './components/lib/contact-sidebar';
|
||||
import { ContactCard } from './components/lib/contact-card';
|
||||
import { AddScreen } from './components/lib/add-contact';
|
||||
import { JoinScreen } from './components/join';
|
||||
import GroupDetail from './components/lib/group-detail';
|
||||
|
||||
export default class GroupsApp extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
import { PatpNoSig } from '../../types/noun';
|
||||
import GlobalApi from '../../api/global';
|
||||
import { StoreState } from '../../store/type';
|
||||
import GlobalSubscription from '../../subscription/global';
|
||||
|
||||
|
||||
type GroupsAppProps = StoreState & {
|
||||
ship: PatpNoSig;
|
||||
api: GlobalApi;
|
||||
subscription: GlobalSubscription;
|
||||
}
|
||||
|
||||
export default class GroupsApp extends Component<GroupsAppProps, {}> {
|
||||
componentDidMount() {
|
||||
document.title = 'OS1 - Groups';
|
||||
// preload spinner asset
|
||||
@ -39,16 +44,17 @@ export default class GroupsApp extends Component {
|
||||
const defaultContacts =
|
||||
(Boolean(props.contacts) && '/~/default' in props.contacts) ?
|
||||
props.contacts['/~/default'] : {};
|
||||
const groups = props.groups ? props.groups : {};
|
||||
|
||||
const invites =
|
||||
(Boolean(props.invites) && '/contacts' in props.invites) ?
|
||||
props.invites['/contacts'] : {};
|
||||
const associations = props.associations ? props.associations : {};
|
||||
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
|
||||
const s3 = props.s3 ? props.s3 : {};
|
||||
const groups = props.groups || {};
|
||||
const associations = props.associations || {};
|
||||
const { api } = props;
|
||||
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/~groups"
|
||||
@ -98,18 +104,48 @@ export default class GroupsApp extends Component {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~groups/(detail)?/(settings)?/:ship/:group/"
|
||||
<Route exact path="/~groups/join/:ship?/:name?"
|
||||
render={(props) => {
|
||||
const ship = props.match.params.ship || '';
|
||||
const name = props.match.params.name || '';
|
||||
return (
|
||||
<Skeleton
|
||||
history={props.history}
|
||||
selectedGroups={selectedGroups}
|
||||
api={api}
|
||||
contacts={contacts}
|
||||
groups={groups}
|
||||
invites={invites}
|
||||
associations={associations}
|
||||
activeDrawer="rightPanel"
|
||||
>
|
||||
<JoinScreen
|
||||
history={props.history}
|
||||
groups={groups}
|
||||
contacts={contacts}
|
||||
api={api}
|
||||
ship={ship}
|
||||
name={name}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~groups/(detail)?/(settings)?/ship/:ship/:group/"
|
||||
render={(props) => {
|
||||
const groupPath =
|
||||
`/${props.match.params.ship}/${props.match.params.group}`;
|
||||
`/ship/${props.match.params.ship}/${props.match.params.group}`;
|
||||
const groupContacts = contacts[groupPath] || {};
|
||||
const group = groups[groupPath] || new Set([]);
|
||||
const group = groups[groupPath] ;
|
||||
const detail = Boolean(props.match.url.includes('/detail'));
|
||||
const settings = Boolean(props.match.url.includes('/settings'));
|
||||
|
||||
const association = (associations.contacts?.[groupPath])
|
||||
? associations.contacts[groupPath]
|
||||
: {};
|
||||
if(!group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
@ -130,12 +166,14 @@ export default class GroupsApp extends Component {
|
||||
activeDrawer={(detail || settings) ? 'detail' : 'contacts'}
|
||||
api={api}
|
||||
path={groupPath}
|
||||
groups={groups}
|
||||
{...props}
|
||||
/>
|
||||
<GroupDetail
|
||||
association={association}
|
||||
path={groupPath}
|
||||
group={group}
|
||||
groups={groups}
|
||||
activeDrawer={(detail || settings) ? 'detail' : 'contacts'}
|
||||
settings={settings}
|
||||
associations={associations}
|
||||
@ -146,12 +184,16 @@ export default class GroupsApp extends Component {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~groups/add/:ship/:group"
|
||||
<Route exact path="/~groups/add/ship/:ship/:group"
|
||||
render={(props) => {
|
||||
const groupPath =
|
||||
`/${props.match.params.ship}/${props.match.params.group}`;
|
||||
`/ship/${props.match.params.ship}/${props.match.params.group}`;
|
||||
const groupContacts = contacts[groupPath] || {};
|
||||
const group = groups[groupPath] || new Set([]);
|
||||
const group = groups[groupPath] || {};
|
||||
|
||||
if(!group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
@ -168,7 +210,8 @@ export default class GroupsApp extends Component {
|
||||
<ContactSidebar
|
||||
contacts={groupContacts}
|
||||
defaultContacts={defaultContacts}
|
||||
group={group}
|
||||
group={group}
|
||||
groups={groups}
|
||||
activeDrawer="rightPanel"
|
||||
path={groupPath}
|
||||
api={api}
|
||||
@ -185,10 +228,10 @@ export default class GroupsApp extends Component {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~groups/share/:ship/:group"
|
||||
<Route exact path="/~groups/share/ship/:ship/:group"
|
||||
render={(props) => {
|
||||
const groupPath =
|
||||
`/${props.match.params.ship}/${props.match.params.group}`;
|
||||
`/ship/${props.match.params.ship}/${props.match.params.group}`;
|
||||
const shipPath = `${groupPath}/${window.ship}`;
|
||||
const rootIdentity = defaultContacts[window.ship] || {};
|
||||
|
||||
@ -196,7 +239,10 @@ export default class GroupsApp extends Component {
|
||||
const contact =
|
||||
(window.ship in groupContacts) ?
|
||||
groupContacts[window.ship] : {};
|
||||
const group = groups[groupPath] || new Set([]);
|
||||
const group = groups[groupPath];
|
||||
if(!group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
@ -218,6 +264,7 @@ export default class GroupsApp extends Component {
|
||||
path={groupPath}
|
||||
api={api}
|
||||
selectedContact={shipPath}
|
||||
groups={groups}
|
||||
{...props}
|
||||
/>
|
||||
<ContactCard
|
||||
@ -234,10 +281,10 @@ export default class GroupsApp extends Component {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~groups/view/:ship/:group/:contact"
|
||||
<Route exact path="/~groups/view/ship/:ship/:group/:contact"
|
||||
render={(props) => {
|
||||
const groupPath =
|
||||
`/${props.match.params.ship}/${props.match.params.group}`;
|
||||
`/ship/${props.match.params.ship}/${props.match.params.group}`;
|
||||
const shipPath =
|
||||
`${groupPath}/${props.match.params.contact}`;
|
||||
|
||||
@ -245,11 +292,14 @@ export default class GroupsApp extends Component {
|
||||
const contact =
|
||||
(props.match.params.contact in groupContacts) ?
|
||||
groupContacts[props.match.params.contact] : {};
|
||||
const group = groups[groupPath] || new Set([]);
|
||||
const group = groups[groupPath] ;
|
||||
|
||||
const rootIdentity =
|
||||
props.match.params.contact === window.ship ?
|
||||
defaultContacts[window.ship] : null;
|
||||
defaultContacts[window.ship] : null;
|
||||
if(!group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
@ -271,6 +321,7 @@ export default class GroupsApp extends Component {
|
||||
path={groupPath}
|
||||
api={api}
|
||||
selectedContact={shipPath}
|
||||
groups={groups}
|
||||
{...props}
|
||||
/>
|
||||
<ContactCard
|
136
pkg/interface/src/apps/groups/components/join.js
Normal file
136
pkg/interface/src/apps/groups/components/join.js
Normal file
@ -0,0 +1,136 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Spinner } from '../../../components/Spinner';
|
||||
import urbitOb from 'urbit-ob';
|
||||
|
||||
export class JoinScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
group: '',
|
||||
error: false,
|
||||
awaiting: null,
|
||||
disable: false
|
||||
};
|
||||
|
||||
this.groupChange = this.groupChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.componentDidUpdate();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props, state } = this;
|
||||
// autojoin by URL, waits for group information
|
||||
if ((props.ship && props.name) &&
|
||||
(prevProps && (prevProps.groups !== props.groups))) {
|
||||
console.log('autojoining');
|
||||
const incomingGroup = `${props.ship}/${props.name}`;
|
||||
// push to group if already exists
|
||||
if (`/ship/${incomingGroup}` in props.groups) {
|
||||
this.props.history.push(`/~groups/ship/${incomingGroup}`);
|
||||
return;
|
||||
}
|
||||
this.setState({ group: incomingGroup }, () => {
|
||||
this.onClickJoin();
|
||||
});
|
||||
}
|
||||
// once we've joined, push to group page
|
||||
if (props.groups) {
|
||||
if (state.awaiting) {
|
||||
const group = `/ship/${state.group}`;
|
||||
if (group in props.groups) {
|
||||
props.history.push(`/~groups${group}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onClickJoin() {
|
||||
const { props, state } = this;
|
||||
console.log('i am joining');
|
||||
|
||||
const { group } = state;
|
||||
const [ship, name] = group.split('/');
|
||||
|
||||
const text = 'Joining group';
|
||||
|
||||
this.props.api.contacts.join({ ship, name }).then(() => {
|
||||
this.setState({ awaiting: text });
|
||||
});
|
||||
}
|
||||
|
||||
groupChange(event) {
|
||||
const [ship, name] = event.target.value.split('/');
|
||||
const validGroup = urbitOb.isValidPatp(ship);
|
||||
this.setState({
|
||||
group: event.target.value,
|
||||
error: !validGroup
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state } = this;
|
||||
|
||||
let joinClasses = 'db f9 green2 ba pa2 b--green2 bg-gray0-d pointer';
|
||||
|
||||
let errElem = (<span />);
|
||||
if (state.error) {
|
||||
joinClasses = 'db f9 gray2 ba pa2 b--gray3 bg-gray0-d';
|
||||
errElem = (
|
||||
<span className="f9 inter red2 db">
|
||||
Group must have a valid name.
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'h-100 w-100 pt4 overflow-x-hidden flex flex-column ' +
|
||||
'bg-gray0-d white-d pa3'}
|
||||
>
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt1 pb6 f8"
|
||||
>
|
||||
<Link to="/~groups/">{'⟵ All Groups'}</Link>
|
||||
</div>
|
||||
<h2 className="mb3 f8">Join an Existing Group</h2>
|
||||
<div className="w-100">
|
||||
<p className="f8 lh-copy mt3 db">Enter a <span className="mono">~ship/group-name</span></p>
|
||||
<p className="f9 gray2 mb4">Group names use lowercase, hyphens, and slashes.</p>
|
||||
<textarea
|
||||
ref={ (e) => {
|
||||
this.textarea = e;
|
||||
} }
|
||||
className={'f7 mono ba bg-gray0-d white-d pa3 mb2 db ' +
|
||||
'focus-b--black focus-b--white-d b--gray3 b--gray2-d nowrap '}
|
||||
placeholder="~zod/group-name"
|
||||
spellCheck="false"
|
||||
rows={1}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.onClickJoin();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
resize: 'none'
|
||||
}}
|
||||
onChange={this.groupChange}
|
||||
value={this.state.group}
|
||||
/>
|
||||
{errElem}
|
||||
<br />
|
||||
<button
|
||||
disabled={this.state.error}
|
||||
onClick={this.onClickJoin.bind(this)}
|
||||
className={joinClasses}
|
||||
>Join Group</button>
|
||||
<Spinner awaiting={this.state.awaiting} classes="mt4" text="Joining group..." />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { InviteSearch } from '../../../../components/InviteSearch';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
|
||||
export class AddScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
invites: {
|
||||
groups: [],
|
||||
ships: []
|
||||
},
|
||||
awaiting: false
|
||||
};
|
||||
|
||||
this.invChange = this.invChange.bind(this);
|
||||
}
|
||||
|
||||
invChange(value) {
|
||||
this.setState({
|
||||
invites: value
|
||||
});
|
||||
}
|
||||
|
||||
onClickAdd() {
|
||||
const { props, state } = this;
|
||||
|
||||
const aud = state.invites.ships
|
||||
.map(ship => `~${ship}`);
|
||||
|
||||
if (this.textarea) {
|
||||
this.textarea.value = '';
|
||||
}
|
||||
this.setState({
|
||||
error: false,
|
||||
success: true,
|
||||
invites: {
|
||||
groups: [],
|
||||
ships: []
|
||||
},
|
||||
awaiting: true
|
||||
}, () => {
|
||||
const submit = props.api.groups.add(props.path, aud);
|
||||
submit.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push('/~groups' + props.path);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 flex flex-column overflow-y-scroll white-d">
|
||||
<div className="w-100 dn-m dn-l dn-xl inter pt1 pb6 pl3 pt3 f8">
|
||||
<Link to={'/~groups' + props.path}>{'⟵ All Contacts'}</Link>
|
||||
</div>
|
||||
<div className="w-100 w-70-l w-70-xl mb4 pr6 pr0-l pr0-xl">
|
||||
<h2 className="f8 pl4 pt4">Add Group Members</h2>
|
||||
<p className="f9 pl4 gray2 lh-copy">Invite ships to your group</p>
|
||||
<div className="relative pl4 mt2 pb6">
|
||||
<InviteSearch
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
groupResults={false}
|
||||
shipResults={true}
|
||||
invites={this.state.invites}
|
||||
setInvite={this.invChange}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={this.onClickAdd.bind(this)}
|
||||
className="ml4 f8 ba pa2 b--green2 green2 pointer bg-transparent"
|
||||
>
|
||||
Add Members
|
||||
</button>
|
||||
<Link to="/~groups">
|
||||
<button className="f8 ml4 ba pa2 b--black pointer bg-transparent b--white-d white-d">Cancel</button>
|
||||
</Link>
|
||||
<Spinner awaiting={this.state.awaiting} classes="mt4 pl4" text="Inviting to group..." />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddScreen;
|
122
pkg/interface/src/apps/groups/components/lib/add-contact.tsx
Normal file
122
pkg/interface/src/apps/groups/components/lib/add-contact.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { InviteSearch, Invites } from '../../../../components/InviteSearch';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
import { uuid } from '../../../../lib/util';
|
||||
import { Groups } from '../../../../types/group-update';
|
||||
import { Rolodex } from '../../../../types/contact-update';
|
||||
import { Path } from '../../../../types/noun';
|
||||
import GlobalApi from '../../../../api/global';
|
||||
import { History } from 'history';
|
||||
|
||||
interface AddScreenState {
|
||||
invites: Invites;
|
||||
awaiting: boolean;
|
||||
}
|
||||
|
||||
interface AddScreenProps {
|
||||
path: Path;
|
||||
contacts: Rolodex;
|
||||
groups: Groups;
|
||||
api: GlobalApi;
|
||||
history: History;
|
||||
}
|
||||
|
||||
export class AddScreen extends Component<AddScreenProps, AddScreenState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
invites: {
|
||||
groups: [],
|
||||
ships: [],
|
||||
},
|
||||
awaiting: false,
|
||||
};
|
||||
|
||||
this.invChange = this.invChange.bind(this);
|
||||
}
|
||||
|
||||
invChange(value) {
|
||||
this.setState({
|
||||
invites: value,
|
||||
});
|
||||
}
|
||||
|
||||
onClickAdd() {
|
||||
const { props, state } = this;
|
||||
|
||||
let [, , ship, name] = props.path.split('/');
|
||||
const resource = { ship, name };
|
||||
|
||||
const aud = state.invites.ships.map((ship) => `~${ship}`);
|
||||
|
||||
this.setState(
|
||||
{
|
||||
invites: {
|
||||
groups: [],
|
||||
ships: [],
|
||||
},
|
||||
awaiting: true,
|
||||
},
|
||||
() => {
|
||||
const submit = aud.reduce(
|
||||
(acc, recipient) =>
|
||||
acc.then(() => {
|
||||
return props.api.contacts.invite(resource, recipient);
|
||||
}),
|
||||
Promise.resolve()
|
||||
);
|
||||
submit.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push('/~groups' + props.path);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
return (
|
||||
<div className='h-100 w-100 flex flex-column overflow-y-scroll white-d'>
|
||||
<div className='w-100 dn-m dn-l dn-xl inter pt1 pb6 pl3 pt3 f8'>
|
||||
<Link to={'/~groups' + props.path}>{'⟵ All Contacts'}</Link>
|
||||
</div>
|
||||
<div className='w-100 w-70-l w-70-xl mb4 pr6 pr0-l pr0-xl'>
|
||||
<h2 className='f8 pl4 pt4'>Add Group Members</h2>
|
||||
<p className='f9 pl4 gray2 lh-copy'>Invite ships to your group</p>
|
||||
<div className='relative pl4 mt2 pb6'>
|
||||
<InviteSearch
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
groupResults={false}
|
||||
shipResults={true}
|
||||
invites={this.state.invites}
|
||||
setInvite={this.invChange}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={this.onClickAdd.bind(this)}
|
||||
className='ml4 f8 ba pa2 b--green2 green2 pointer bg-transparent'
|
||||
>
|
||||
Add Members
|
||||
</button>
|
||||
<Link to='/~groups'>
|
||||
<button className='f8 ml4 ba pa2 b--black pointer bg-transparent b--white-d white-d'>
|
||||
Cancel
|
||||
</button>
|
||||
</Link>
|
||||
<Spinner
|
||||
awaiting={this.state.awaiting}
|
||||
classes='mt4 pl4'
|
||||
text='Inviting to group...'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddScreen;
|
@ -5,8 +5,29 @@ import { ShareSheet } from './share-sheet';
|
||||
import { Sigil } from '../../../../lib/sigil';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
import { cite } from '../../../../lib/util';
|
||||
import { roleForShip, resourceFromPath } from '../../../../lib/group';
|
||||
import { Path, PatpNoSig } from '../../../../types/noun';
|
||||
import { Rolodex, Contacts, Contact } from '../../../../types/contact-update';
|
||||
import { Groups, Group } from '../../../../types/group-update';
|
||||
import GlobalApi from '../../../../api/global';
|
||||
|
||||
export class ContactSidebar extends Component {
|
||||
interface ContactSidebarProps {
|
||||
activeDrawer: 'contacts' | 'detail' | 'rightPanel';
|
||||
groups: Groups;
|
||||
group: Group
|
||||
contacts: Contacts;
|
||||
path: Path;
|
||||
api: GlobalApi;
|
||||
defaultContacts: Contacts;
|
||||
selectedContact?: PatpNoSig;
|
||||
}
|
||||
interface ContactSidebarState {
|
||||
awaiting: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class ContactSidebar extends Component<ContactSidebarProps, ContactSidebarState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@ -16,10 +37,14 @@ export class ContactSidebar extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
const group = new Set(Array.from(props.group));
|
||||
const responsiveClasses =
|
||||
props.activeDrawer === 'contacts' ? 'db' : 'dn db-ns';
|
||||
|
||||
|
||||
const group = props.groups[props.path];
|
||||
|
||||
const members = new Set(group.members || []);
|
||||
|
||||
const me = (window.ship in props.contacts)
|
||||
? props.contacts[window.ship]
|
||||
: (window.ship in props.defaultContacts)
|
||||
@ -49,13 +74,13 @@ export class ContactSidebar extends Component {
|
||||
/>
|
||||
</>
|
||||
);
|
||||
group.delete(window.ship);
|
||||
members.delete(window.ship);
|
||||
|
||||
const contactItems =
|
||||
Object.keys(props.contacts)
|
||||
.filter(c => c !== window.ship)
|
||||
.map((contact) => {
|
||||
group.delete(contact);
|
||||
members.delete(contact);
|
||||
const path = props.path + '/' + contact;
|
||||
const obj = props.contacts[contact];
|
||||
return (
|
||||
@ -72,11 +97,16 @@ export class ContactSidebar extends Component {
|
||||
);
|
||||
});
|
||||
|
||||
const adminOpt = (props.path.includes(`~${window.ship}/`))
|
||||
? 'dib' : 'dn';
|
||||
const role = roleForShip(group, window.ship);
|
||||
|
||||
const resource = resourceFromPath(props.path);
|
||||
const groupItems =
|
||||
Array.from(group).map((member) => {
|
||||
Array.from(members).map((member) => {
|
||||
const memberRole = roleForShip(group, member);
|
||||
const adminOpt = (role === 'admin' && memberRole !== 'admin')
|
||||
|| (role === 'moderator' &&
|
||||
(memberRole !== 'admin' && memberRole !== 'moderator'))
|
||||
? 'dib' : 'dn';
|
||||
return (
|
||||
<div
|
||||
key={member}
|
||||
@ -99,7 +129,7 @@ export class ContactSidebar extends Component {
|
||||
style={{ paddingTop: 6 }}
|
||||
onClick={() => {
|
||||
this.setState({ awaiting: true }, (() => {
|
||||
props.api.groups.remove(props.path, [`~${member}`])
|
||||
props.api.groups.remove(resource, [`~${member}`])
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
@ -1,7 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
import { deSig, uxToHex } from '../../../../lib/util';
|
||||
import { GroupView } from '../../../../components/Group';
|
||||
import { deSig, uxToHex, writeText } from '../../../../lib/util';
|
||||
|
||||
export class GroupDetail extends Component {
|
||||
constructor(props) {
|
||||
@ -158,8 +159,8 @@ export class GroupDetail extends Component {
|
||||
<p className="f9 mw5 mw3-m mw4-l">{title}</p>
|
||||
<p className="f9 gray2">{description}</p>
|
||||
<p className="f9">
|
||||
{props.group.size + ' participant' +
|
||||
((props.group.size === 1) ? '' : 's')}
|
||||
{props.group.members.size + ' participant' +
|
||||
((props.group.members.size === 1) ? '' : 's')}
|
||||
</p>
|
||||
</div>
|
||||
<p className={'gray2 f9 mb2 pt6 ' + (isEmpty ? 'dn' : '')}>Group Channels</p>
|
||||
@ -172,25 +173,60 @@ export class GroupDetail extends Component {
|
||||
renderSettings() {
|
||||
const { props } = this;
|
||||
|
||||
const groupOwner = (deSig(props.match.params.ship) === window.ship);
|
||||
const { group, association } = props;
|
||||
|
||||
const association = props.association;
|
||||
const groupOwner = (deSig(props.match.params.ship) === window.ship);
|
||||
|
||||
const deleteButtonClasses = (groupOwner) ? 'b--red2 red2 pointer bg-gray0-d' : 'b--gray3 gray3 bg-gray0-d c-default';
|
||||
|
||||
const tags = [
|
||||
{ description: 'Admin', tag: 'admin', addDescription: 'Make Admin' },
|
||||
{ description: 'Moderator', tag: 'moderator', addDescription: 'Make Moderator' },
|
||||
{ description: 'Janitor', tag: 'janitor', addDescription: 'Make Janitor' }
|
||||
];
|
||||
|
||||
let shortcode = <div />;
|
||||
|
||||
if (group?.policy?.open) {
|
||||
shortcode = <div className="mt4">
|
||||
<p className="f9 mt4 lh-copy">Share</p>
|
||||
<p className="f9 gray2 mb2">Share a shortcode to join this group</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: '29rem' }}
|
||||
>
|
||||
<input
|
||||
className="f8 mono ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100 flex-auto mr3"
|
||||
disabled={true}
|
||||
value={props.path.substr(6)}
|
||||
/>
|
||||
<span className="lh-solid f8 pointer absolute pa3 inter"
|
||||
style={{ right: 12, top: 1 }}
|
||||
ref="copy"
|
||||
onClick={() => {
|
||||
writeText(props.path.substr(6));
|
||||
this.refs.copy.innerText = 'Copied';
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</span>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
return (
|
||||
<div className="pa4 w-100 h-100 white-d">
|
||||
<div className="pa4 w-100 h-100 white-d overflow-y-auto">
|
||||
<div className="f8 f9-m f9-l f9-xl w-100">
|
||||
<Link to={'/~groups/detail' + props.path}>{'⟵ Channels'}</Link>
|
||||
</div>
|
||||
{shortcode}
|
||||
{ group && <GroupView permissions className="mt6" resourcePath={props.path} group={group} tags={tags} api={props.api} /> }
|
||||
<div className={(groupOwner) ? '' : 'o-30'}>
|
||||
<p className="f8 mt3 lh-copy">Rename</p>
|
||||
<p className="f9 gray2 mb4">Change the name of this group</p>
|
||||
<p className="f9 mt3 lh-copy">Rename</p>
|
||||
<p className="f9 gray2 mb2">Change the name of this group</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: '29rem' }}
|
||||
>
|
||||
<input
|
||||
className={'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
className={'f9 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
|
||||
value={this.state.title}
|
||||
disabled={!groupOwner}
|
||||
@ -214,13 +250,13 @@ export class GroupDetail extends Component {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="f8 mt3 lh-copy">Change description</p>
|
||||
<p className="f9 gray2 mb4">Change the description of this group</p>
|
||||
<p className="f9 mt3 lh-copy">Change description</p>
|
||||
<p className="f9 gray2 mb2">Change the description of this group</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: '29rem' }}
|
||||
>
|
||||
<input
|
||||
className={'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
className={'f9 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
|
||||
value={this.state.description}
|
||||
disabled={!groupOwner}
|
||||
@ -244,8 +280,8 @@ export class GroupDetail extends Component {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="f8 mt3 lh-copy">Delete Group</p>
|
||||
<p className="f9 gray2 mb4">
|
||||
<p className="f9 mt3 lh-copy">Delete Group</p>
|
||||
<p className="f9 gray2 mb2">
|
||||
Permanently delete this group. All current members will no longer see this group.
|
||||
</p>
|
||||
<a className={'dib f9 ba pa2 ' + deleteButtonClasses}
|
||||
|
@ -8,7 +8,7 @@ export class GroupItem extends Component {
|
||||
|
||||
const selectedClass = (props.selected) ? 'bg-gray4 bg-gray1-d' : '';
|
||||
const memberCount = Math.max(
|
||||
props.group.size,
|
||||
props.group.members.size,
|
||||
Object.keys(props.contacts).length
|
||||
);
|
||||
|
||||
|
@ -13,6 +13,7 @@ export class GroupSidebar extends Component {
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const { api } = props;
|
||||
|
||||
const selectedClass = (props.selected === 'me') ? 'bg-gray4 bg-gray1-d' : 'bg-white bg-gray0-d';
|
||||
|
||||
@ -123,6 +124,9 @@ export class GroupSidebar extends Component {
|
||||
<Link to="/~groups/new" className="dib">
|
||||
<p className="f9 pt4 pl4 green2 bn">Create Group</p>
|
||||
</Link>
|
||||
<Link to="/~groups/join" className="dib">
|
||||
<p className="f9 pt4 pl4 green2 bn">Join Group</p>
|
||||
</Link>
|
||||
<Welcome contacts={props.contacts} />
|
||||
<h2 className="f9 pt4 pr4 pb2 pl4 gray2 c-default">Your Identity</h2>
|
||||
{rootIdentity}
|
||||
|
@ -3,7 +3,11 @@ import React, { Component } from 'react';
|
||||
export class SidebarInvite extends Component {
|
||||
onAccept() {
|
||||
const { props } = this;
|
||||
props.api.invite.accept('/contacts', props.uid);
|
||||
const [,,ship, name] = props.invite.path.split('/');
|
||||
const resource = { ship, name };
|
||||
props.api.contacts.join(resource).then(() => {
|
||||
props.api.invite.accept('/contacts', props.uid);
|
||||
});
|
||||
props.history.push(`/~groups${props.invite.path}`);
|
||||
}
|
||||
|
||||
|
@ -1,156 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import { InviteSearch } from '../../../components/InviteSearch';
|
||||
import { Spinner } from '../../../components/Spinner';
|
||||
|
||||
export class NewScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
groupName: '',
|
||||
title: '',
|
||||
description: '',
|
||||
invites: {
|
||||
groups: [],
|
||||
ships: []
|
||||
},
|
||||
// color: '',
|
||||
groupNameError: false,
|
||||
awaiting: false
|
||||
};
|
||||
|
||||
this.groupNameChange = this.groupNameChange.bind(this);
|
||||
this.descriptionChange = this.descriptionChange.bind(this);
|
||||
this.invChange = this.invChange.bind(this);
|
||||
}
|
||||
|
||||
groupNameChange(event) {
|
||||
const asciiSafe = event.target.value.toLowerCase()
|
||||
.replace(/[^a-z0-9~_.-]/g, '-');
|
||||
this.setState({
|
||||
groupName: asciiSafe,
|
||||
title: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
descriptionChange(event) {
|
||||
this.setState({ description: event.target.value });
|
||||
}
|
||||
|
||||
invChange(value) {
|
||||
this.setState({
|
||||
invites: value
|
||||
});
|
||||
}
|
||||
|
||||
onClickCreate() {
|
||||
const { props, state } = this;
|
||||
|
||||
if (!state.groupName) {
|
||||
this.setState({
|
||||
groupNameError: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const group = `/~${window.ship}` + `/${state.groupName}`;
|
||||
const aud = state.invites.ships.map(ship => `~${ship}`);
|
||||
|
||||
if (this.textarea) {
|
||||
this.textarea.value = '';
|
||||
}
|
||||
this.setState({
|
||||
error: false,
|
||||
success: true,
|
||||
invites: '',
|
||||
awaiting: true
|
||||
}, () => {
|
||||
props.api.contacts.create(
|
||||
group,
|
||||
aud,
|
||||
this.state.title,
|
||||
this.state.description
|
||||
).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push(`/~groups${group}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let groupNameErrElem = (<span />);
|
||||
if (this.state.groupNameError) {
|
||||
groupNameErrElem = (
|
||||
<span className="f9 inter red2 ml3 mt1 db">
|
||||
Group must have a name.
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 mw6 pa3 pt4 overflow-x-hidden bg-gray0-d white-d flex flex-column">
|
||||
<div className="w-100 dn-m dn-l dn-xl inter pt1 pb6 f8">
|
||||
<Link to="/~groups/">{'⟵ All Groups'}</Link>
|
||||
</div>
|
||||
<div className="w-100 mb4 pr6 pr0-l pr0-xl">
|
||||
<h2 className="f8">Create New Group</h2>
|
||||
<h2 className="f8 pt6">Group Name</h2>
|
||||
<textarea
|
||||
className={
|
||||
'f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100 mt2 ' +
|
||||
'focus-b--black focus-b--white-d'
|
||||
}
|
||||
rows={1}
|
||||
placeholder="Jazz Maximalists Research Unit"
|
||||
style={{
|
||||
resize: 'none',
|
||||
height: 48,
|
||||
paddingTop: 14
|
||||
}}
|
||||
onChange={this.groupNameChange}
|
||||
/>
|
||||
{groupNameErrElem}
|
||||
<h2 className="f8 pt6">Description <span className="gray2">(Optional)</span></h2>
|
||||
<textarea
|
||||
className={
|
||||
'f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100 mt2 ' +
|
||||
'focus-b--black focus-b--white-d'
|
||||
}
|
||||
rows={1}
|
||||
placeholder="Two trumpeters and a microphone"
|
||||
style={{
|
||||
resize: 'none',
|
||||
height: 48,
|
||||
paddingTop: 14
|
||||
}}
|
||||
onChange={this.descriptionChange}
|
||||
/>
|
||||
<h2 className="f8 pt6">Invite <span className="gray2">(Optional)</span></h2>
|
||||
<p className="f9 gray2 lh-copy">Selected ships will be invited to your group</p>
|
||||
<div className="relative pb6 mt2">
|
||||
<InviteSearch
|
||||
groups={this.props.groups}
|
||||
contacts={this.props.contacts}
|
||||
groupResults={false}
|
||||
shipResults={true}
|
||||
invites={this.state.invites}
|
||||
setInvite={this.invChange}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={this.onClickCreate.bind(this)}
|
||||
className="f9 ba pa2 b--green2 green2 pointer bg-transparent"
|
||||
>
|
||||
Start Group
|
||||
</button>
|
||||
<Link to="/~groups">
|
||||
<button className="f9 ml3 ba pa2 b--black pointer bg-transparent b--white-d white-d">Cancel</button>
|
||||
</Link>
|
||||
<Spinner awaiting={this.state.awaiting} classes="mt4" text="Creating group..." />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
228
pkg/interface/src/apps/groups/components/new.tsx
Normal file
228
pkg/interface/src/apps/groups/components/new.tsx
Normal file
@ -0,0 +1,228 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import { InviteSearch, Invites } from '../../../components/InviteSearch';
|
||||
import { Spinner } from '../../../components/Spinner';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { Groups, GroupPolicy, Resource } from '../../../types/group-update';
|
||||
import { Contacts, Rolodex } from '../../../types/contact-update';
|
||||
import GlobalApi from '../../../api/global';
|
||||
import { Patp, PatpNoSig, Enc } from '../../../types/noun';
|
||||
|
||||
type NewScreenProps = Pick<RouteComponentProps, 'history'> & {
|
||||
groups: Groups;
|
||||
contacts: Rolodex;
|
||||
api: GlobalApi;
|
||||
};
|
||||
|
||||
type TextChange = React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>;
|
||||
type BooleanChange = React.ChangeEvent<HTMLInputElement>;
|
||||
|
||||
|
||||
interface NewScreenState {
|
||||
groupName: string;
|
||||
title: string;
|
||||
description: string;
|
||||
invites: Invites;
|
||||
privacy: boolean;
|
||||
groupNameError: boolean;
|
||||
awaiting: boolean;
|
||||
}
|
||||
|
||||
export class NewScreen extends Component<NewScreenProps, NewScreenState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
groupName: '',
|
||||
title: '',
|
||||
description: '',
|
||||
invites: { ships: [], groups: [] },
|
||||
privacy: false,
|
||||
// color: '',
|
||||
groupNameError: false,
|
||||
awaiting: false,
|
||||
};
|
||||
|
||||
this.groupNameChange = this.groupNameChange.bind(this);
|
||||
this.descriptionChange = this.descriptionChange.bind(this);
|
||||
this.invChange = this.invChange.bind(this);
|
||||
this.groupPrivacyChange = this.groupPrivacyChange.bind(this);
|
||||
}
|
||||
|
||||
groupNameChange(event: TextChange) {
|
||||
const asciiSafe = event.target.value
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9~_.-]/g, '-');
|
||||
this.setState({
|
||||
groupName: asciiSafe,
|
||||
title: event.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
descriptionChange(event: TextChange) {
|
||||
this.setState({ description: event.target.value });
|
||||
}
|
||||
|
||||
invChange(value: Invites) {
|
||||
this.setState({
|
||||
invites: value,
|
||||
});
|
||||
}
|
||||
|
||||
groupPrivacyChange(event: BooleanChange) {
|
||||
this.setState({
|
||||
privacy: event.target.checked,
|
||||
});
|
||||
}
|
||||
|
||||
onClickCreate() {
|
||||
const { props, state } = this;
|
||||
|
||||
if (!state.groupName) {
|
||||
this.setState({
|
||||
groupNameError: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const aud = state.invites.ships.map((ship) => `~${ship}`);
|
||||
|
||||
const policy: Enc<GroupPolicy> = state.privacy
|
||||
? {
|
||||
invite: {
|
||||
pending: aud,
|
||||
},
|
||||
}
|
||||
: {
|
||||
open: {
|
||||
banRanks: [],
|
||||
banned: [],
|
||||
},
|
||||
};
|
||||
|
||||
const { groupName } = this.state;
|
||||
this.setState(
|
||||
{
|
||||
invites: { ships: [], groups: [] },
|
||||
awaiting: true,
|
||||
},
|
||||
() => {
|
||||
props.api.contacts
|
||||
.create(groupName, policy, this.state.title, this.state.description)
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push(
|
||||
`/~groups/ship/~${window.ship}/${state.groupName}`
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let groupNameErrElem = <span />;
|
||||
if (this.state.groupNameError) {
|
||||
groupNameErrElem = (
|
||||
<span className='f9 inter red2 ml3 mt1 db'>
|
||||
Group must have a name.
|
||||
</span>
|
||||
);
|
||||
}
|
||||
const privacySwitchClasses = this.state.privacy
|
||||
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
|
||||
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
|
||||
|
||||
return (
|
||||
<div className='h-100 w-100 mw6 pa3 pt4 overflow-x-hidden bg-gray0-d white-d flex flex-column'>
|
||||
<div className='w-100 dn-m dn-l dn-xl inter pt1 pb6 f8'>
|
||||
<Link to='/~groups/'>{'⟵ All Groups'}</Link>
|
||||
</div>
|
||||
<div className='w-100 mb4 pr6 pr0-l pr0-xl'>
|
||||
<h2 className='f8'>Create New Group</h2>
|
||||
<h2 className='f8 pt6'>Group Name</h2>
|
||||
<textarea
|
||||
className={
|
||||
'f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100 mt2 ' +
|
||||
'focus-b--black focus-b--white-d'
|
||||
}
|
||||
rows={1}
|
||||
placeholder='Jazz Maximalists Research Unit'
|
||||
style={{
|
||||
resize: 'none',
|
||||
height: 48,
|
||||
paddingTop: 14,
|
||||
}}
|
||||
onChange={this.groupNameChange}
|
||||
/>
|
||||
{groupNameErrElem}
|
||||
<h2 className='f8 pt6'>
|
||||
Description <span className='gray2'>(Optional)</span>
|
||||
</h2>
|
||||
<textarea
|
||||
className={
|
||||
'f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100 mt2 ' +
|
||||
'focus-b--black focus-b--white-d'
|
||||
}
|
||||
rows={1}
|
||||
placeholder='Two trumpeters and a microphone'
|
||||
style={{
|
||||
resize: 'none',
|
||||
height: 48,
|
||||
paddingTop: 14,
|
||||
}}
|
||||
onChange={this.descriptionChange}
|
||||
/>
|
||||
<div className='mv7'>
|
||||
<input
|
||||
type='checkbox'
|
||||
style={{ WebkitAppearance: 'none', width: 28 }}
|
||||
onChange={this.groupPrivacyChange}
|
||||
className={privacySwitchClasses}
|
||||
/>
|
||||
<span className='dib f9 white-d inter ml3'>Private Group</span>
|
||||
<p className='f9 gray2 pt1' style={{ paddingLeft: 40 }}>
|
||||
If private, new members must be invited
|
||||
</p>
|
||||
</div>
|
||||
{this.state.privacy && (
|
||||
<>
|
||||
<h2 className='f8 pt6'>
|
||||
Invite <span className='gray2'>(Optional)</span>
|
||||
</h2>
|
||||
<p className='f9 gray2 lh-copy'>
|
||||
Selected ships will be invited to your group
|
||||
</p>
|
||||
<div className='relative pb6 mt2'>
|
||||
<InviteSearch
|
||||
groups={{}}
|
||||
contacts={this.props.contacts}
|
||||
groupResults={false}
|
||||
shipResults={true}
|
||||
invites={this.state.invites}
|
||||
setInvite={this.invChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={this.onClickCreate.bind(this)}
|
||||
className='f9 ba pa2 b--green2 green2 pointer bg-transparent'
|
||||
>
|
||||
Start Group
|
||||
</button>
|
||||
<Link to='/~groups'>
|
||||
<button className='f9 ml3 ba pa2 b--black pointer bg-transparent b--white-d white-d'>
|
||||
Cancel
|
||||
</button>
|
||||
</Link>
|
||||
<Spinner
|
||||
awaiting={this.state.awaiting}
|
||||
classes='mt4'
|
||||
text='Creating group...'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ export default class Welcome extends React.Component {
|
||||
super();
|
||||
this.state = {
|
||||
show: true
|
||||
}
|
||||
};
|
||||
this.disableWelcome = this.disableWelcome.bind(this);
|
||||
}
|
||||
|
||||
@ -16,19 +16,23 @@ export default class Welcome extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let firstTime = this.props.firstTime;
|
||||
const firstTime = this.props.firstTime;
|
||||
return (firstTime && this.state.show) ? (
|
||||
<div className={'fl ma2 bg-white bg-gray0-d white-d overflow-hidden ' +
|
||||
'ba b--black b--gray1-d pa2 lh-copy'}>
|
||||
'ba b--black b--gray1-d pa2 lh-copy'}
|
||||
>
|
||||
<p className="f9">Welcome. This virtual computer belongs to you completely. The Urbit ID you used to boot it is yours as well.</p>
|
||||
<p className="f9 pt2">Since your ID and OS belong to you, it’s up to you to keep them safe. Be sure your ID is somewhere you won’t lose it and you keep your OS on a machine you trust.</p>
|
||||
<p className="f9 pt2">Urbit OS is designed to keep your data secure and hard to lose. But the system is still young — so don’t put anything critical in here just yet.</p>
|
||||
<p className="f9 pt2">To begin exploring, you should probably pop into a chat and verify there are signs of life in this new place. If you were invited by a friend, you probably already have access to a few groups.</p>
|
||||
<p className="f9 pt2">If you don't know where to go, feel free to <Link className="no-underline bb b--black b--gray1-d dib" to="/~chat/join/~/~dopzod/urbit-help">join our lobby.</Link>
|
||||
<p className="f9 pt2">If you don't know where to go, feel free to <Link className="no-underline bb b--black b--gray1-d dib" to="/~groups/join/~bitbet-bolbel/urbit-community">join the Urbit Community group</Link>.
|
||||
</p>
|
||||
<p className="f9 pt2">Have fun!</p>
|
||||
<p className="dib f9 pt2 bb b--black b--gray1-d pointer"
|
||||
onClick={(() => {this.disableWelcome()})}>
|
||||
onClick={(() => {
|
||||
this.disableWelcome();
|
||||
})}
|
||||
>
|
||||
Close this note
|
||||
</p>
|
||||
</div>
|
||||
|
@ -3,10 +3,6 @@ import { Switch, Route } from 'react-router-dom';
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import LinksApi from '../../api/links';
|
||||
import LinksStore from '../../store/links';
|
||||
import LinksSubscription from '../../subscription/links';
|
||||
|
||||
import './css/custom.css';
|
||||
|
||||
import { Skeleton } from './components/skeleton';
|
||||
@ -45,7 +41,8 @@ export class LinksApp extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
const contacts = props.contacts ? props.contacts : {};
|
||||
const contacts = props.contacts ? props.contacts : {};
|
||||
|
||||
const groups = props.groups ? props.groups : {};
|
||||
|
||||
const associations = props.associations ? props.associations : { link: {}, contacts: {} };
|
||||
@ -172,6 +169,8 @@ export class LinksApp extends Component {
|
||||
contactDetails={contactDetails}
|
||||
groupPath={resource['group-path']}
|
||||
group={group}
|
||||
groups={groups}
|
||||
associations={associations}
|
||||
amOwner={amOwner}
|
||||
resourcePath={resourcePath}
|
||||
popout={popout}
|
||||
|
@ -31,15 +31,7 @@ export class ChannelsSidebar extends Component {
|
||||
const groupPath = props.associations.link[path] ?
|
||||
props.associations.link[path]['group-path'] : '';
|
||||
|
||||
if (groupPath.startsWith('/~/')) {
|
||||
if (groupedChannels['/~/']) {
|
||||
const array = groupedChannels['/~/'];
|
||||
array.push(path);
|
||||
groupedChannels['/~/'] = array;
|
||||
} else {
|
||||
groupedChannels['/~/'] = [path];
|
||||
};
|
||||
}
|
||||
|
||||
if (groupPath in associations) {
|
||||
if (groupedChannels[groupPath]) {
|
||||
const array = groupedChannels[groupPath];
|
||||
@ -48,6 +40,14 @@ export class ChannelsSidebar extends Component {
|
||||
} else {
|
||||
groupedChannels[groupPath] = [path];
|
||||
}
|
||||
} else {
|
||||
if (groupedChannels['/~/']) {
|
||||
const array = groupedChannels['/~/'];
|
||||
array.push(path);
|
||||
groupedChannels['/~/'] = array;
|
||||
} else {
|
||||
groupedChannels['/~/'] = [path];
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -16,7 +16,7 @@ export class ChannelsItem extends Component {
|
||||
|
||||
return (
|
||||
<Link to={makeRoutePath(props.link)}>
|
||||
<div className={'w-100 v-mid f9 ph4 z1 pv1 relative ' + selectedClass}>
|
||||
<div className={'w-100 v-mid f9 ph5 z1 pv1 relative ' + selectedClass}>
|
||||
<p className="f9 dib">{props.name}</p>
|
||||
<p className="f9 dib fr">
|
||||
{unseenCount}
|
||||
|
@ -14,7 +14,7 @@ export class GroupItem extends Component {
|
||||
}
|
||||
|
||||
const channels = props.channels ? props.channels : [];
|
||||
const first = (props.index === 0) ? 'pt1' : 'pt4';
|
||||
const first = (props.index === 0) ? 'pt1' : 'pt6';
|
||||
|
||||
const channelItems = channels.map((each, i) => {
|
||||
const meta = props.linkMetadata[each];
|
||||
@ -36,7 +36,7 @@ export class GroupItem extends Component {
|
||||
});
|
||||
return (
|
||||
<div className={first}>
|
||||
<p className="f9 ph4 pb2 fw6 gray3">{title}</p>
|
||||
<p className="f9 ph4 pb2 gray3">{title}</p>
|
||||
{channelItems}
|
||||
</div>
|
||||
);
|
||||
|
@ -5,9 +5,9 @@ import { Link } from 'react-router-dom';
|
||||
import { LoadingScreen } from './loading';
|
||||
import { LinksTabBar } from './lib/links-tabbar';
|
||||
import { MemberElement } from './lib/member-element';
|
||||
import { InviteElement } from './lib/invite-element';
|
||||
import { SidebarSwitcher } from '../../../components/SidebarSwitch';
|
||||
import { makeRoutePath } from '../../../lib/util';
|
||||
import { GroupView } from '../../../components/Group';
|
||||
|
||||
export class MemberScreen extends Component {
|
||||
render() {
|
||||
@ -17,32 +17,13 @@ export class MemberScreen extends Component {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
const isManaged = ('/~/' !== props.groupPath.slice(0,3));
|
||||
|
||||
const members = Array.from(props.group).map((mem) => {
|
||||
const contact = (mem in props.contactDetails)
|
||||
? props.contactDetails[mem] : false;
|
||||
|
||||
return (
|
||||
<MemberElement
|
||||
key={mem}
|
||||
amOwner={props.amOwner}
|
||||
contact={contact}
|
||||
ship={mem}
|
||||
groupPath={props.groupPath}
|
||||
resourcePath={props.resourcePath}
|
||||
api={props.api}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column white-d">
|
||||
<div className='h-100 w-100 overflow-x-hidden flex flex-column white-d'>
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
className='w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8'
|
||||
style={{ height: '1rem' }}
|
||||
>
|
||||
<Link to="/~link">{'⟵ All Collections'}</Link>
|
||||
<Link to='/~link'>{'⟵ All Collections'}</Link>
|
||||
</div>
|
||||
<div
|
||||
className={`pl4 pt2 bb b--gray4 b--gray1-d bg-gray0-d flex relative
|
||||
@ -54,11 +35,12 @@ export class MemberScreen extends Component {
|
||||
popout={this.props.popout}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<Link to={makeRoutePath(props.resourcePath, props.popout)}
|
||||
className="pt2 white-d"
|
||||
<Link
|
||||
to={makeRoutePath(props.resourcePath, props.popout)}
|
||||
className='pt2 white-d'
|
||||
>
|
||||
<h2
|
||||
className="dib f9 fw4 lh-solid v-top"
|
||||
className='dib f9 fw4 lh-solid v-top'
|
||||
style={{ width: 'max-content' }}
|
||||
>
|
||||
{props.resource.metadata.title}
|
||||
@ -72,36 +54,15 @@ export class MemberScreen extends Component {
|
||||
popout={props.popout}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-100 pl3 mt0 mt4-m mt4-l mt4-xl cf pr6">
|
||||
{!props.amOwner ? null : (
|
||||
<div className="w-100 w-50-l w-50-xl fl pa2 pr3 pt3 pt0-l pt0-xl">
|
||||
<p className="f8 pb2">Modify Permissions</p>
|
||||
<p className="f9 gray2 mb3">
|
||||
{'Invite someone to this collection.' +
|
||||
(isManaged
|
||||
? ' Adding someone adds them to the group.'
|
||||
: '')
|
||||
}
|
||||
</p>
|
||||
<InviteElement
|
||||
groupPath={props.groupPath}
|
||||
resourcePath={props.resourcePath}
|
||||
permissions={props.permission}
|
||||
contacts={props.contacts}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="w-100 w-50-l w-50-xl fl pa2 pr3 pt3 pt0-l pt0-xl">
|
||||
<p className="f8 pb2">Members</p>
|
||||
<p className="f9 gray2 mb3">
|
||||
{ 'Everyone with permission to use this collection.' +
|
||||
((isManaged && props.amOwner)
|
||||
? ' Removing someone removes them from the group.'
|
||||
: '')
|
||||
}
|
||||
</p>
|
||||
{members}
|
||||
</div>
|
||||
<div className='w-100 pl3 mt0 mt4-m mt4-l mt4-xl cf pr6'>
|
||||
<GroupView
|
||||
group={props.group}
|
||||
permissions
|
||||
resourcePath={props.groupPath}
|
||||
contacts={props.contacts}
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -23,7 +23,6 @@ export class NewScreen extends Component {
|
||||
this.titleChange = this.titleChange.bind(this);
|
||||
this.descriptionChange = this.descriptionChange.bind(this);
|
||||
this.setInvite = this.setInvite.bind(this);
|
||||
this.createGroupChange = this.createGroupChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
@ -59,12 +58,6 @@ export class NewScreen extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
createGroupChange(event) {
|
||||
this.setState({
|
||||
createGroup: Boolean(event.target.checked)
|
||||
});
|
||||
}
|
||||
|
||||
onClickCreate() {
|
||||
const { props, state } = this;
|
||||
|
||||
@ -136,10 +129,6 @@ export class NewScreen extends Component {
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
const createGroupClasses = state.createGroup
|
||||
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
|
||||
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
|
||||
|
||||
const createClasses = state.idName
|
||||
? 'pointer db f9 mt7 green2 bg-gray0-d ba pv3 ph4 b--green2'
|
||||
: 'pointer db f9 mt7 gray2 ba bg-gray0-d pa2 pv3 ph4 b--gray3';
|
||||
@ -157,24 +146,6 @@ export class NewScreen extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
let createGroupToggle = <div />;
|
||||
if (state.groups.length === 0) {
|
||||
createGroupToggle = (
|
||||
<div className="mt7">
|
||||
<input
|
||||
type="checkbox"
|
||||
style={{ WebkitAppearance: 'none', width: 28 }}
|
||||
className={createGroupClasses}
|
||||
onChange={this.createGroupChange}
|
||||
/>
|
||||
<span className="dib f9 white-d inter ml3">Create Group</span>
|
||||
<p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
|
||||
Participants will share this group across applications
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
@ -211,13 +182,16 @@ export class NewScreen extends Component {
|
||||
}}
|
||||
onChange={this.descriptionChange}
|
||||
/>
|
||||
<p className="f8 mt4 lh-copy db">
|
||||
Invite
|
||||
<span className="gray3"> (Optional)</span>
|
||||
<div className="mt4 db relative">
|
||||
<p className="f8">
|
||||
Invite
|
||||
<span className="gray3"> (Optional)</span>
|
||||
</p>
|
||||
<p className="f9 gray2 db mb2 pt1">
|
||||
Selected groups or ships will be able to post to collection
|
||||
<Link className="green2 absolute right-0 bottom-0 f9" to="/~groups/new">Create Group</Link>
|
||||
<p className="f9 gray2 db mv1">
|
||||
Selected group or ships will be invited to the collection
|
||||
</p>
|
||||
</div>
|
||||
<InviteSearch
|
||||
associations={props.associations.contacts}
|
||||
groups={props.groups}
|
||||
@ -230,7 +204,6 @@ export class NewScreen extends Component {
|
||||
}}
|
||||
setInvite={this.setInvite}
|
||||
/>
|
||||
{createGroupToggle}
|
||||
<button
|
||||
onClick={this.onClickCreate.bind(this)}
|
||||
className={createClasses}
|
||||
|
@ -4,10 +4,6 @@ import _ from 'lodash';
|
||||
|
||||
import './css/custom.css';
|
||||
|
||||
import PublishApi from '../../api/publish';
|
||||
import PublishStore from '../../store/publish';
|
||||
import PublishSubscription from '../../subscription/publish';
|
||||
|
||||
import { Skeleton } from './components/skeleton';
|
||||
import { NewScreen } from './components/lib/new';
|
||||
import { JoinScreen } from './components/lib/join';
|
||||
@ -69,7 +65,7 @@ export default class PublishApp extends React.Component {
|
||||
this.unreadTotal = unreadTotal;
|
||||
}
|
||||
|
||||
const { api, groups, permissions, sidebarShown } = props;
|
||||
const { api, groups, sidebarShown } = props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
@ -88,11 +84,12 @@ export default class PublishApp extends React.Component {
|
||||
contacts={contacts}
|
||||
api={api}
|
||||
>
|
||||
<div className={`h-100 w-100 overflow-x-hidden flex flex-column
|
||||
<div
|
||||
className={`h-100 w-100 overflow-x-hidden flex flex-column
|
||||
bg-white bg-gray0-d dn db-ns`}
|
||||
>
|
||||
<div className="pl3 pr3 pt2 dt pb3 w-100 h-100">
|
||||
<p className="f9 pt3 gray2 w-100 h-100 dtc v-mid tc">
|
||||
<div className='pl3 pr3 pt2 dt pb3 w-100 h-100'>
|
||||
<p className='f9 pt3 gray2 w-100 h-100 dtc v-mid tc'>
|
||||
Select or create a notebook to begin.
|
||||
</p>
|
||||
</div>
|
||||
@ -101,8 +98,10 @@ export default class PublishApp extends React.Component {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~publish/new"
|
||||
render={(props) => {
|
||||
<Route
|
||||
exact
|
||||
path='/~publish/new'
|
||||
render={props => {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={false}
|
||||
@ -128,8 +127,10 @@ export default class PublishApp extends React.Component {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~publish/join/:ship?/:notebook?"
|
||||
render={(props) => {
|
||||
<Route
|
||||
exact
|
||||
path='/~publish/join/:ship?/:notebook?'
|
||||
render={props => {
|
||||
const ship = props.match.params.ship || '';
|
||||
const notebook = props.match.params.notebook || '';
|
||||
return (
|
||||
@ -156,9 +157,11 @@ export default class PublishApp extends React.Component {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~publish/:popout?/notebook/:ship/:notebook/:view?"
|
||||
render={(props) => {
|
||||
const view = (props.match.params.view)
|
||||
<Route
|
||||
exact
|
||||
path='/~publish/:popout?/notebook/:ship/:notebook/:view?'
|
||||
render={props => {
|
||||
const view = props.match.params.view
|
||||
? props.match.params.view
|
||||
: 'posts';
|
||||
|
||||
@ -172,8 +175,8 @@ export default class PublishApp extends React.Component {
|
||||
const bookGroupPath =
|
||||
notebooks?.[ship]?.[notebook]?.['subscribers-group-path'];
|
||||
|
||||
const notebookContacts = (bookGroupPath in contacts)
|
||||
? contacts[bookGroupPath] : {};
|
||||
const notebookContacts =
|
||||
bookGroupPath in contacts ? contacts[bookGroupPath] : {};
|
||||
|
||||
if (view === 'new') {
|
||||
return (
|
||||
@ -227,7 +230,6 @@ export default class PublishApp extends React.Component {
|
||||
associations={associations.contacts}
|
||||
sidebarShown={sidebarShown}
|
||||
popout={popout}
|
||||
permissions={permissions}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
@ -236,8 +238,10 @@ export default class PublishApp extends React.Component {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~publish/:popout?/note/:ship/:notebook/:note/:edit?"
|
||||
render={(props) => {
|
||||
<Route
|
||||
exact
|
||||
path='/~publish/:popout?/note/:ship/:notebook/:note/:edit?'
|
||||
render={props => {
|
||||
const ship = props.match.params.ship || '';
|
||||
const notebook = props.match.params.notebook || '';
|
||||
const path = `${ship}/${notebook}`;
|
||||
|
@ -13,7 +13,7 @@ export class GroupItem extends Component {
|
||||
}
|
||||
|
||||
const groupedBooks = props.groupedBooks ? props.groupedBooks : [];
|
||||
const first = (props.index === 0) ? 'pt1' : 'pt4';
|
||||
const first = (props.index === 0) ? 'pt1' : 'pt6';
|
||||
|
||||
const notebookItems = groupedBooks.map((each, i) => {
|
||||
const unreads = props.notebooks[each]['num-unread'] || 0;
|
||||
|
@ -23,7 +23,6 @@ export class NewScreen extends Component {
|
||||
this.idChange = this.idChange.bind(this);
|
||||
this.descriptionChange = this.descriptionChange.bind(this);
|
||||
this.setInvite = this.setInvite.bind(this);
|
||||
this.createGroupChange = this.createGroupChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@ -48,10 +47,6 @@ export class NewScreen extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
createGroupChange(event) {
|
||||
this.setState({ createGroup: Boolean(event.target.checked) });
|
||||
}
|
||||
|
||||
setInvite(value) {
|
||||
this.setState({ invites: value });
|
||||
}
|
||||
@ -69,14 +64,14 @@ export class NewScreen extends Component {
|
||||
};
|
||||
} else if (this.state.createGroup) {
|
||||
groupInfo = {
|
||||
'group-path': `/~${window.ship}/${bookId}`,
|
||||
'group-path': `/ship/~${window.ship}/${bookId}`,
|
||||
'invitees': state.invites.ships,
|
||||
'use-preexisting': false,
|
||||
'make-managed': true
|
||||
};
|
||||
} else {
|
||||
groupInfo = {
|
||||
'group-path': `/~/~${window.ship}/${bookId}`,
|
||||
'group-path': `/ship/~${window.ship}/${bookId}`,
|
||||
'invitees': state.invites.ships,
|
||||
'use-preexisting': false,
|
||||
'make-managed': false
|
||||
@ -99,31 +94,12 @@ export class NewScreen extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const createGroupClasses = this.state.createGroup
|
||||
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
|
||||
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
|
||||
|
||||
let createClasses = 'pointer db f9 green2 bg-gray0-d ba pv3 ph4 mv7 b--green2';
|
||||
if (!this.state.idName || this.state.disabled) {
|
||||
createClasses = 'db f9 gray2 ba bg-gray0-d pa2 pv3 ph4 mv7 b--gray3';
|
||||
}
|
||||
|
||||
const createGroupToggle =
|
||||
!((this.state.invites.ships.length > 0) && (this.state.invites.groups.length === 0))
|
||||
? null
|
||||
: <div className="mv7">
|
||||
<input
|
||||
type="checkbox"
|
||||
style={{ WebkitAppearance: 'none', width: 28 }}
|
||||
className={createGroupClasses}
|
||||
onChange={this.createGroupChange}
|
||||
/>
|
||||
<span className="dib f9 white-d inter ml3">Create Group</span>
|
||||
<p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
|
||||
Participants will share this group across applications
|
||||
</p>
|
||||
</div>;
|
||||
|
||||
let idErrElem = <span />;
|
||||
if (this.state.idError) {
|
||||
idErrElem = (
|
||||
@ -182,14 +158,17 @@ export class NewScreen extends Component {
|
||||
onChange={this.descriptionChange}
|
||||
value={this.state.description}
|
||||
/>
|
||||
<p className="f8 mt4 lh-copy db">
|
||||
Invite
|
||||
<span className="gray3 ml1">(Optional)</span>
|
||||
</p>
|
||||
<p className="f9 gray2 db mb2 pt1">
|
||||
Selected ships will be invited to read your notebook. Selected
|
||||
groups will be invited to read and write notes.
|
||||
<div className="mt4 db relative">
|
||||
<p className="f8">
|
||||
Invite
|
||||
<span className="gray3"> (Optional)</span>
|
||||
</p>
|
||||
<Link className="green2 absolute right-0 bottom-0 f9" to="/~groups/new">Create Group</Link>
|
||||
<p className="f9 gray2 db mv1 pb4">
|
||||
Selected ships will be invited to read your notebook. Selected
|
||||
groups will be invited to read and write notes.
|
||||
</p>
|
||||
</div>
|
||||
<InviteSearch
|
||||
associations={this.props.associations}
|
||||
groupResults={true}
|
||||
@ -199,7 +178,6 @@ export class NewScreen extends Component {
|
||||
invites={this.state.invites}
|
||||
setInvite={this.setInvite}
|
||||
/>
|
||||
{createGroupToggle}
|
||||
<button
|
||||
disabled={this.state.disabled}
|
||||
onClick={this.onClickCreate.bind(this)}
|
||||
|
@ -16,7 +16,7 @@ export class NotebookItem extends Component {
|
||||
<Link
|
||||
to={'/~publish/notebook/' + props.path}
|
||||
>
|
||||
<div className={'w-100 v-mid f9 ph4 pv1 ' + selectedClass}>
|
||||
<div className={'w-100 v-mid f9 ph5 pv1 ' + selectedClass}>
|
||||
<p className="dib f9">{props.title}</p>
|
||||
{unread}
|
||||
</div>
|
||||
|
@ -110,7 +110,8 @@ export class Notebook extends Component {
|
||||
host={this.props.ship}
|
||||
book={this.props.book}
|
||||
notebook={notebook}
|
||||
permissions={this.props.permissions}
|
||||
contacts={this.props.contacts}
|
||||
associations={this.props.associations}
|
||||
groups={this.props.groups}
|
||||
api={this.props.api}
|
||||
/>;
|
||||
@ -153,8 +154,9 @@ export class Notebook extends Component {
|
||||
|
||||
let newPost = null;
|
||||
if (notebook?.['writers-group-path'] in props.groups) {
|
||||
const writers = notebook?.['writers-group-path'];
|
||||
if (props.groups?.[writers].has(window.ship)) {
|
||||
const group = props.groups[notebook?.['writers-group-path']];
|
||||
const writers = group.tags?.publish?.[`writers-${props.book}`] || new Set();
|
||||
if (props.ship === `~${window.ship}` || writers.has(ship)) {
|
||||
newPost = (
|
||||
<Link
|
||||
to={newUrl}
|
||||
|
@ -125,7 +125,7 @@ export class Settings extends Component {
|
||||
|
||||
const ownedUnmanaged =
|
||||
owner &&
|
||||
props.notebook?.['writers-group-path'].slice(0, 3) === '/~/';
|
||||
!props.contacts[props.notebook?.['writers-group-path']];
|
||||
|
||||
if (!ownedUnmanaged) {
|
||||
return null;
|
||||
@ -205,32 +205,9 @@ export class Settings extends Component {
|
||||
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
|
||||
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
|
||||
|
||||
const copyShortcode = <div>
|
||||
{this.renderHeader('Share', 'Share a shortcode to join this notebook')}
|
||||
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
|
||||
<input
|
||||
className={'f8 mono ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'pa3 db w-100 flex-auto mr3'}
|
||||
disabled={true}
|
||||
value={`${this.props.host}/${this.props.book}` || ''}
|
||||
/>
|
||||
<span className="f8 pointer absolute pa3 inter"
|
||||
style={{ right: 12, top: 1 }}
|
||||
ref="copy"
|
||||
onClick={() => {
|
||||
writeText(`${this.props.host}/${this.props.book}`);
|
||||
this.refs.copy.innerText = 'Copied';
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</span>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
if (this.props.host.slice(1) === window.ship) {
|
||||
return (
|
||||
<div className="flex-column">
|
||||
{copyShortcode}
|
||||
{this.renderGroupify()}
|
||||
{this.renderHeader(
|
||||
'Delete Notebook',
|
||||
|
@ -43,15 +43,6 @@ export class Sidebar extends Component {
|
||||
|
||||
const groupedNotebooks = {};
|
||||
Object.keys(notebooks).map((book) => {
|
||||
if (notebooks[book]['subscribers-group-path'].startsWith('/~/')) {
|
||||
if (groupedNotebooks['/~/']) {
|
||||
const array = groupedNotebooks['/~/'];
|
||||
array.push(book);
|
||||
groupedNotebooks['/~/'] = array;
|
||||
} else {
|
||||
groupedNotebooks['/~/'] = [book];
|
||||
};
|
||||
};
|
||||
const path = notebooks[book]['subscribers-group-path']
|
||||
? notebooks[book]['subscribers-group-path'] : book;
|
||||
if (path in associations) {
|
||||
@ -62,6 +53,14 @@ export class Sidebar extends Component {
|
||||
} else {
|
||||
groupedNotebooks[path] = [book];
|
||||
}
|
||||
} else {
|
||||
if (groupedNotebooks['/~/']) {
|
||||
const array = groupedNotebooks['/~/'];
|
||||
array.push(book);
|
||||
groupedNotebooks['/~/'] = array;
|
||||
} else {
|
||||
groupedNotebooks['/~/'] = [book];
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@ -124,9 +123,6 @@ export class Sidebar extends Component {
|
||||
<Link to="/~publish/new" className="green2 pa4 f9 dib">
|
||||
New Notebook
|
||||
</Link>
|
||||
<Link to="/~publish/join" className="f9 gray2">
|
||||
Join Notebook
|
||||
</Link>
|
||||
</div>
|
||||
<div className="overflow-y-auto pb1"
|
||||
style={{ height: 'calc(100% - 82px)' }}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Dropdown } from './dropdown';
|
||||
import { cite } from '../../../../lib/util';
|
||||
import { GroupView } from '../../../../components/Group';
|
||||
|
||||
export class Subscribers extends Component {
|
||||
constructor(props) {
|
||||
@ -23,160 +22,40 @@ export class Subscribers extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const readPath = this.props.notebook['subscribers-group-path'];
|
||||
const readPerms = (readPath)
|
||||
? this.props.permissions[readPath]
|
||||
: null;
|
||||
const writePath = this.props.notebook['writers-group-path'];
|
||||
const writePerms = (writePath)
|
||||
? this.props.permissions[writePath]
|
||||
: null;
|
||||
const path = this.props.notebook['writers-group-path'];
|
||||
const group = path ? this.props.groups[path] : null;
|
||||
|
||||
let writers = [];
|
||||
if (writePerms && writePerms.kind === 'white') {
|
||||
const withoutUs = new Set(writePerms.who);
|
||||
withoutUs.delete(window.ship);
|
||||
writers = Array.from(withoutUs).map((who, i) => {
|
||||
let width = 0;
|
||||
let options = [];
|
||||
if (readPath === writePath) {
|
||||
width = 258;
|
||||
const url = `/~groups${writePath}`;
|
||||
options = [{
|
||||
cls: 'bg-transparent white-d tl pointer w-100 db hover-bg-gray4 hover-bg-gray1-d ph2 pv3',
|
||||
txt: 'Manage this group',
|
||||
action: () => {
|
||||
this.redirect(url);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
width = 157;
|
||||
options = [{
|
||||
cls: 'bg-transparent white-d tl pointer w-100 db hover-bg-gray4 hover-bg-gray1-d ph2 pv3',
|
||||
txt: 'Demote to subscriber',
|
||||
action: () => {
|
||||
this.removeUser(`~${who}`, writePath);
|
||||
}
|
||||
}];
|
||||
}
|
||||
return (
|
||||
<div className="flex justify-between" key={i}>
|
||||
<div className="f9 mono mr2">{`${cite(who)}`}</div>
|
||||
<Dropdown
|
||||
options={options}
|
||||
width={width}
|
||||
buttonText={'Options'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (writers.length === 0) {
|
||||
writers =
|
||||
<div className="f9">
|
||||
There are no participants on this notebook.
|
||||
</div>;
|
||||
}
|
||||
const tags = [
|
||||
{
|
||||
description: 'Writer',
|
||||
tag: `writers-${this.props.book}`,
|
||||
addDescription: 'Make Writer',
|
||||
app: 'publish',
|
||||
},
|
||||
];
|
||||
|
||||
let subscribers = null;
|
||||
if (readPath !== writePath) {
|
||||
if (this.props.notebook.subscribers) {
|
||||
const width = 162;
|
||||
subscribers = this.props.notebook.subscribers.map((who, i) => {
|
||||
const options = [
|
||||
{ cls: 'white-d tl pointer w-100 db hover-bg-gray4 hover-bg-gray1-d bg-transparent ph2 pv3',
|
||||
txt: 'Promote to participant',
|
||||
action: () => {
|
||||
this.addUser(who, writePath);
|
||||
}
|
||||
},
|
||||
{ cls: 'tl red2 pointer w-100 db hover-bg-gray4 hover-bg-gray1-d bg-transparent ph2 pv3',
|
||||
txt: 'Ban',
|
||||
action: () => {
|
||||
this.addUser(who, readPath);
|
||||
}
|
||||
}
|
||||
];
|
||||
return (
|
||||
<div className="flex justify-between" key={i}>
|
||||
<div className="f9 mono mr2">{cite(who)}</div>
|
||||
<Dropdown
|
||||
options={options}
|
||||
width={width}
|
||||
buttonText={'Options'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
if (subscribers.length === 0) {
|
||||
subscribers =
|
||||
<div className="f9">
|
||||
There are no subscribers to this notebook.
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
const subsContainer = (readPath === writePath)
|
||||
? null
|
||||
: <div className="flex flex-column">
|
||||
<div className="f9 gray2 mt6 mb3">Subscribers (read access only)</div>
|
||||
{subscribers}
|
||||
</div>;
|
||||
|
||||
let bannedContainer = null;
|
||||
if (readPerms && readPerms.kind === 'black') {
|
||||
const width = 72;
|
||||
let banned = Array.from(readPerms.who).map((who, i) => {
|
||||
const options = [{
|
||||
cls: 'tl red2 pointer',
|
||||
txt: 'Unban',
|
||||
action: () => {
|
||||
this.removeUser(`~${who}`, readPath);
|
||||
}
|
||||
}];
|
||||
return (
|
||||
<div className="flex justify-between" key={i}>
|
||||
<div className="f9 mono mr2">{`~${who}`}</div>
|
||||
<Dropdown
|
||||
options={options}
|
||||
width={width}
|
||||
buttonText={'Options'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
if (banned.length === 0) {
|
||||
banned =
|
||||
<div className="f9">
|
||||
There are no users banned from this notebook.
|
||||
</div>;
|
||||
}
|
||||
bannedContainer =
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 gray2 mt6 mb3">Banned</div>
|
||||
{banned}
|
||||
</div>;
|
||||
}
|
||||
const appTags = [
|
||||
{
|
||||
app: 'publish',
|
||||
tag: `writers-${this.props.book}`,
|
||||
desc: `Writer`,
|
||||
addDesc: 'Allow user to write to this notebook'
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 gray2">Host</div>
|
||||
<div className="flex justify-between mt3">
|
||||
<div className="f9 mono mr2">{cite(this.props.host)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 gray2 mt6 mb3">
|
||||
Participants (read and write access)
|
||||
</div>
|
||||
{writers}
|
||||
</div>
|
||||
{subsContainer}
|
||||
{bannedContainer}
|
||||
</div>
|
||||
<GroupView
|
||||
permissions
|
||||
resourcePath={path}
|
||||
group={group}
|
||||
tags={tags}
|
||||
appTags={appTags}
|
||||
contacts={props.contacts}
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
api={this.props.api}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
345
pkg/interface/src/components/Group.tsx
Normal file
345
pkg/interface/src/components/Group.tsx
Normal file
@ -0,0 +1,345 @@
|
||||
import React, { Component } from 'react';
|
||||
import _, { capitalize } from 'lodash';
|
||||
import { Dropdown } from '../apps/publish/components/lib/dropdown';
|
||||
import { cite, deSig } from '../lib/util';
|
||||
import { roleForShip, resourceFromPath } from '../lib/group';
|
||||
import {
|
||||
Group,
|
||||
InvitePolicy,
|
||||
OpenPolicy,
|
||||
roleTags,
|
||||
Groups,
|
||||
} from '../types/group-update';
|
||||
import { Path, PatpNoSig, Patp } from '../types/noun';
|
||||
import GlobalApi from '../api/global';
|
||||
import { Menu, MenuButton, MenuList, MenuItem } from '@tlon/indigo-react';
|
||||
import InviteSearch, { Invites } from './InviteSearch';
|
||||
import { Spinner } from './Spinner';
|
||||
import { Rolodex } from '../types/contact-update';
|
||||
import { Associations } from '../types/metadata-update';
|
||||
|
||||
class GroupMember extends Component<{ ship: Patp; options: any[] }, {}> {
|
||||
render() {
|
||||
const { ship, options, children } = this.props;
|
||||
|
||||
return (
|
||||
<div className='flex justify-between f9 items-center'>
|
||||
<div className='flex flex-column'>
|
||||
<div className='mono mr2'>{`${cite(ship)}`}</div>
|
||||
{children}
|
||||
</div>
|
||||
{options.length > 0 && (
|
||||
<Menu>
|
||||
<MenuButton sm>Options</MenuButton>
|
||||
<MenuList>
|
||||
{options.map(({ onSelect, text }) => (
|
||||
<MenuItem onSelect={onSelect}>{text}</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Tag extends Component<{ description: string; onRemove?: () => any }, {}> {
|
||||
render() {
|
||||
const { description, onRemove } = this.props;
|
||||
return (
|
||||
<div className='br-pill ba b-black r-full items-center ph2 f9 mr2 flex'>
|
||||
<div>{description}</div>
|
||||
{Boolean(onRemove) && (
|
||||
<div onClick={onRemove} className='ml1 f9 pointer'>
|
||||
✗
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface GroupViewAppTag {
|
||||
tag: string;
|
||||
app: string;
|
||||
desc: string;
|
||||
addDesc: string;
|
||||
}
|
||||
|
||||
interface GroupViewProps {
|
||||
group: Group;
|
||||
groups: Groups;
|
||||
contacts: Rolodex;
|
||||
associations: Associations;
|
||||
resourcePath: Path;
|
||||
appTags?: GroupViewAppTag[];
|
||||
api: GlobalApi;
|
||||
className: string;
|
||||
permissions?: boolean;
|
||||
inviteShips: (ships: PatpNoSig[]) => Promise<any>;
|
||||
}
|
||||
|
||||
export class GroupView extends Component<
|
||||
GroupViewProps,
|
||||
{ invites: Invites; awaiting: boolean }
|
||||
> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.setInvites = this.setInvites.bind(this);
|
||||
this.inviteShips = this.inviteShips.bind(this);
|
||||
this.state = {
|
||||
invites: {
|
||||
ships: [],
|
||||
groups: [],
|
||||
},
|
||||
awaiting: false
|
||||
};
|
||||
}
|
||||
|
||||
removeUser(who: PatpNoSig) {
|
||||
return () => {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
this.props.api.groups.remove(resource, [`~${who}`]);
|
||||
};
|
||||
}
|
||||
|
||||
banUser(who: PatpNoSig) {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
this.props.api.groups.changePolicy(resource, {
|
||||
open: {
|
||||
banShips: [`~${who}`],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
allowUser(who: PatpNoSig) {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
this.props.api.groups.changePolicy(resource, {
|
||||
open: {
|
||||
allowShips: [`~${who}`],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
removeInvite(who: PatpNoSig) {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
this.props.api.groups.changePolicy(resource, {
|
||||
invite: {
|
||||
removeInvites: [`~${who}`],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
removeTag(who: PatpNoSig, tag: any) {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
|
||||
return this.props.api.groups.removeTag(resource, tag, [`~${who}`]);
|
||||
}
|
||||
|
||||
addTag(who: PatpNoSig, tag: any) {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
return this.props.api.groups.addTag(resource, tag, [`~${who}`]);
|
||||
}
|
||||
|
||||
isAdmin(): boolean {
|
||||
const us = `~${window.ship}`;
|
||||
const role = roleForShip(this.props.group, us);
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
return resource.ship == us || role === 'admin';
|
||||
}
|
||||
|
||||
optionsForShip(ship: Patp, missing: GroupViewAppTag[]) {
|
||||
const { permissions, resourcePath, group } = this.props;
|
||||
const resource = resourceFromPath(resourcePath);
|
||||
let options: any[] = [];
|
||||
if (!permissions) {
|
||||
return options;
|
||||
}
|
||||
const role = roleForShip(group, ship);
|
||||
if (role === 'admin' || resource.ship === ship) {
|
||||
return [];
|
||||
}
|
||||
if ('open' in group.policy) {
|
||||
options.unshift({ text: 'Ban', onSelect: () => this.banUser(ship) });
|
||||
}
|
||||
if (this.isAdmin() && !role) {
|
||||
options = options.concat(
|
||||
missing.map(({ addDesc, tag, app }) => ({
|
||||
text: addDesc,
|
||||
onSelect: () => this.addTag(ship, { tag, app }),
|
||||
}))
|
||||
);
|
||||
options = options.concat(
|
||||
roleTags.reduce(
|
||||
(acc, role) => [
|
||||
...acc,
|
||||
{
|
||||
text: `Make ${capitalize(role)}`,
|
||||
onSelect: () => this.addTag(ship, { tag: role }),
|
||||
},
|
||||
],
|
||||
[] as any[]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
doIfAdmin<Ret>(f: () => Ret) {
|
||||
return this.isAdmin() ? f : undefined;
|
||||
}
|
||||
|
||||
getAppTags(ship: Patp): [GroupViewAppTag[], GroupViewAppTag[]] {
|
||||
const { tags } = this.props.group;
|
||||
const { appTags } = this.props;
|
||||
|
||||
return _.partition(appTags, ({ app, tag }) => {
|
||||
return tags?.[app]?.[tag]?.has(ship);
|
||||
});
|
||||
}
|
||||
|
||||
renderMembers() {
|
||||
const { group, permissions } = this.props;
|
||||
const { members } = group;
|
||||
const isAdmin = this.isAdmin();
|
||||
return (
|
||||
<div className='flex flex-column'>
|
||||
<div className='f9 gray2 mt6 mb3'>Members</div>
|
||||
{Array.from(members).map((ship) => {
|
||||
const role = roleForShip(group, deSig(ship));
|
||||
const onRoleRemove =
|
||||
role && isAdmin
|
||||
? () => {
|
||||
this.removeTag(ship, { tag: role });
|
||||
}
|
||||
: undefined;
|
||||
const [present, missing] = this.getAppTags(ship);
|
||||
const options = this.optionsForShip(ship, missing);
|
||||
|
||||
return (
|
||||
<div key={ship} className='flex flex-column pv3'>
|
||||
<GroupMember ship={ship} options={options}>
|
||||
{((permissions && role) || present.length > 0) && (
|
||||
<div className='flex mt1'>
|
||||
{role && (
|
||||
<Tag
|
||||
onRemove={onRoleRemove}
|
||||
description={capitalize(role)}
|
||||
/>
|
||||
)}
|
||||
{present.map((tag, idx) => (
|
||||
<Tag
|
||||
key={idx}
|
||||
onRemove={this.doIfAdmin(() =>
|
||||
this.removeTag(ship, tag)
|
||||
)}
|
||||
description={tag.desc}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</GroupMember>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
setInvites(invites: Invites) {
|
||||
this.setState({ invites });
|
||||
}
|
||||
|
||||
inviteShips(invites: Invites) {
|
||||
const { props, state } = this;
|
||||
this.setState({ awaiting: true });
|
||||
props.inviteShips(invites.ships).then(() => {
|
||||
this.setState({ invites: { ships: [], groups: [] }, awaiting: false });
|
||||
});
|
||||
}
|
||||
|
||||
renderInvites(policy: InvitePolicy) {
|
||||
const { props, state } = this;
|
||||
const ships = Array.from(policy.invite.pending || []);
|
||||
|
||||
const options = (ship: Patp) => [
|
||||
{ text: 'Uninvite', onSelect: () => this.removeInvite(ship) },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='flex flex-column'>
|
||||
<div className='f9 gray2 mt6 mb3'>Pending</div>
|
||||
{ships.map((ship) => (
|
||||
<GroupMember key={ship} ship={ship} options={options(ship)} />
|
||||
))}
|
||||
{ships.length === 0 && <div className='f9'>No ships are pending</div>}
|
||||
{props.inviteShips && this.isAdmin() && (
|
||||
<>
|
||||
<div className='f9 gray2 mt6 mb3'>Invite</div>
|
||||
<div style={{ width: 'calc(min(400px, 100%)' }}>
|
||||
<InviteSearch
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
shipResults
|
||||
groupResults={false}
|
||||
invites={state.invites}
|
||||
setInvite={this.setInvites}
|
||||
associations={props.associations}
|
||||
/>
|
||||
</div>
|
||||
<a
|
||||
onClick={() => this.inviteShips(state.invites)}
|
||||
className='db ba tc w-auto mr-auto mt2 ph2 black white-d f8 pointer'
|
||||
>
|
||||
Invite
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBanned(policy: OpenPolicy) {
|
||||
const ships = Array.from(policy.open.banned || []);
|
||||
|
||||
const options = (ship: Patp) => [
|
||||
{ text: 'Unban', onSelect: () => this.allowUser(ship) },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='flex flex-column'>
|
||||
<div className='f9 gray2 mt6 mb3'>Banned</div>
|
||||
{ships.map((ship) => (
|
||||
<GroupMember key={ship} ship={ship} options={options(ship)} />
|
||||
))}
|
||||
{ships.length === 0 && <div className='f9'>No ships are banned</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { group, resourcePath, className } = this.props;
|
||||
const resource = resourceFromPath(resourcePath);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='flex flex-column'>
|
||||
<div className='f9 gray2'>Host</div>
|
||||
<div className='flex justify-between mt3'>
|
||||
<div className='f9 mono mr2'>{cite(resource.ship)}</div>
|
||||
</div>
|
||||
</div>
|
||||
{'invite' in group.policy && this.renderInvites(group.policy)}
|
||||
{'open' in group.policy && this.renderBanned(group.policy)}
|
||||
{this.renderMembers()}
|
||||
|
||||
<Spinner
|
||||
awaiting={this.state.awaiting}
|
||||
classes='mt4'
|
||||
text='Inviting to chat...'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user