mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-10 10:05:09 +03:00
Merge pull request #4268 from urbit/lf/join-cleanup
group-view (nee graph-view)
This commit is contained in:
commit
deac282fad
@ -1,571 +1,27 @@
|
||||
:: contact-hook [landscape]
|
||||
:: contact-hook [landscape]: deprecated
|
||||
::
|
||||
::
|
||||
/- *contact-hook,
|
||||
*contact-view,
|
||||
inv=invite-store,
|
||||
*metadata-hook,
|
||||
*metadata-store,
|
||||
*group
|
||||
/+ *contact-json,
|
||||
default-agent,
|
||||
dbug,
|
||||
group-store,
|
||||
verb,
|
||||
resource,
|
||||
grpl=group,
|
||||
*migrate
|
||||
~% %contact-hook-top ..part ~
|
||||
/+ default-agent
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
::
|
||||
+$ versioned-state
|
||||
$% state-zero
|
||||
state-one
|
||||
state-two
|
||||
state-three
|
||||
==
|
||||
::
|
||||
+$ state-zero [%0 state-base]
|
||||
+$ state-one [%1 state-base]
|
||||
+$ state-two [%2 state-base]
|
||||
+$ state-three [%3 state-base]
|
||||
+$ state-base
|
||||
$: =synced
|
||||
invite-created=_|
|
||||
==
|
||||
--
|
||||
=| state-three
|
||||
=* state -
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
::
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ bol=bowl:gall
|
||||
+* this .
|
||||
contact-core +>
|
||||
cc ~(. contact-core bol)
|
||||
def ~(. (default-agent this %|) bol)
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card _this)
|
||||
:_ 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 /groups]
|
||||
==
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
^- (quip card _this)
|
||||
=/ old !<(versioned-state old-vase)
|
||||
=| cards=(list card)
|
||||
|^
|
||||
|- ^- (quip card _this)
|
||||
?: ?=(%3 -.old)
|
||||
[cards this(state old)]
|
||||
?: ?=(%2 -.old)
|
||||
%_ $
|
||||
old [%3 +.old]
|
||||
::
|
||||
cards
|
||||
%+ welp
|
||||
cards
|
||||
%- zing
|
||||
%+ turn
|
||||
~(tap by synced.old)
|
||||
|= [=path =ship]
|
||||
^- (list card)
|
||||
?. =(ship our.bol)
|
||||
~
|
||||
?> ?=([%ship *] path)
|
||||
:~ (pass-store contacts+t.path %leave ~)
|
||||
(pass-store contacts+path %watch contacts+path)
|
||||
==
|
||||
==
|
||||
?: ?=(%1 -.old)
|
||||
%_ $
|
||||
-.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
|
||||
==
|
||||
==
|
||||
%_ $
|
||||
-.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 ~]~
|
||||
::
|
||||
++ pass-store
|
||||
|= [=wire =task:agent:gall]
|
||||
^- card
|
||||
[%pass wire %agent [our.bol %contact-store] task]
|
||||
--
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
=^ cards state
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%json
|
||||
(poke-json:cc !<(json vase))
|
||||
::
|
||||
%contact-action
|
||||
(poke-contact-action:cc !<(contact-action vase))
|
||||
::
|
||||
%contact-hook-action
|
||||
(poke-hook-action:cc !<(contact-hook-action vase))
|
||||
::
|
||||
%import
|
||||
?> (team:title our.bol src.bol)
|
||||
(poke-import:cc q.vase)
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
?+ path (on-watch:def path)
|
||||
[%contacts *] [(watch-contacts:cc t.path) this]
|
||||
[%synced *] [(watch-synced:cc t.path) this]
|
||||
==
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card _this)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%kick [(kick:cc wire) this]
|
||||
%watch-ack
|
||||
=^ cards state
|
||||
(watch-ack:cc wire p.sign)
|
||||
[cards this]
|
||||
::
|
||||
%fact
|
||||
?+ p.cage.sign (on-agent:def wire sign)
|
||||
%contact-update
|
||||
=^ cards state
|
||||
(fact-contact-update:cc wire !<(contact-update q.cage.sign))
|
||||
[cards this]
|
||||
::
|
||||
%group-update
|
||||
=^ cards state
|
||||
(fact-group-update:cc wire !<(update:group-store q.cage.sign))
|
||||
[cards this]
|
||||
::
|
||||
%invite-update [~ this]
|
||||
==
|
||||
==
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek
|
||||
|= =path
|
||||
^- (unit (unit cage))
|
||||
?+ path (on-peek:def path)
|
||||
[%x %export ~]
|
||||
``noun+!>(state)
|
||||
[%x %synced ~]
|
||||
``noun+!>(~(key by synced))
|
||||
==
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card _this)
|
||||
?. ?=([%try-rejoin @ @ *] wire)
|
||||
(on-arvo:def wire sign-arvo)
|
||||
=/ nack-count=@ud (slav %ud i.t.wire)
|
||||
=/ who=@p (slav %p i.t.t.wire)
|
||||
=/ pax t.t.t.wire
|
||||
?> ?=([%behn %wake *] sign-arvo)
|
||||
~? ?=(^ error.sign-arvo)
|
||||
"behn errored in backoff timers, continuing anyway"
|
||||
:_ this
|
||||
[(try-rejoin:cc who pax +(nack-count))]~
|
||||
::
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
++ grp ~(. grpl bol)
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bol)
|
||||
::
|
||||
++ poke-json
|
||||
|= jon=json
|
||||
^- (quip card _state)
|
||||
(poke-contact-action (json-to-action jon))
|
||||
++ on-init on-init:def
|
||||
++ on-poke on-poke:def
|
||||
++ on-watch on-watch:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-save !>(~)
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
^- (quip card _this)
|
||||
[~ this]
|
||||
::
|
||||
++ poke-contact-action
|
||||
|= act=contact-action
|
||||
^- (quip card _state)
|
||||
:_ state
|
||||
?+ -.act !!
|
||||
%edit (handle-contact-action path.act ship.act act)
|
||||
%add (handle-contact-action path.act ship.act act)
|
||||
%remove (handle-contact-action path.act ship.act act)
|
||||
==
|
||||
::
|
||||
++ handle-contact-action
|
||||
|= [=path =ship act=contact-action]
|
||||
^- (list card)
|
||||
:: local
|
||||
?: (team:title our.bol src.bol)
|
||||
?. |(=(path /~/default) (~(has by synced) path)) ~
|
||||
=/ shp ?:(=(path /~/default) our.bol (~(got by synced) path))
|
||||
=/ appl ?:(=(shp our.bol) %contact-store %contact-hook)
|
||||
[%pass / %agent [shp appl] %poke %contact-action !>(act)]~
|
||||
:: foreign
|
||||
=/ shp (~(got by synced) path)
|
||||
?. |(=(shp our.bol) =(src.bol ship)) ~
|
||||
:: scry group to check if ship is a member
|
||||
=/ =group (need (group-scry path))
|
||||
?. (~(has in members.group) shp) ~
|
||||
[%pass / %agent [our.bol %contact-store] %poke %contact-action !>(act)]~
|
||||
::
|
||||
++ poke-hook-action
|
||||
|= act=contact-hook-action
|
||||
^- (quip card _state)
|
||||
?- -.act
|
||||
%add-owned
|
||||
?> (team:title our.bol src.bol)
|
||||
=/ contact-path [%contacts path.act]
|
||||
?: (~(has by synced) path.act)
|
||||
[~ state]
|
||||
=. synced (~(put by synced) path.act our.bol)
|
||||
:_ state
|
||||
:~ [%pass contact-path %agent [our.bol %contact-store] %watch contact-path]
|
||||
[%give %fact [/synced]~ %contact-hook-update !>([%initial synced])]
|
||||
==
|
||||
::
|
||||
%add-synced
|
||||
?> (team:title our.bol src.bol)
|
||||
?: (~(has by synced) path.act) [~ state]
|
||||
=. synced (~(put by synced) path.act ship.act)
|
||||
=/ contact-path [%contacts path.act]
|
||||
:_ state
|
||||
:~ [%pass contact-path %agent [ship.act %contact-hook] %watch contact-path]
|
||||
[%give %fact [/synced]~ %contact-hook-update !>([%initial synced])]
|
||||
==
|
||||
::
|
||||
%remove
|
||||
=/ ship (~(get by synced) path.act)
|
||||
?~ ship [~ state]
|
||||
?: &(=(u.ship our.bol) (team:title our.bol src.bol))
|
||||
:: delete one of our.bol own paths
|
||||
:_ state(synced (~(del by synced) path.act))
|
||||
%- zing
|
||||
:~ (pull-wire [%contacts path.act])
|
||||
[%give %kick ~[[%contacts path.act]] ~]~
|
||||
[%give %fact [/synced]~ %contact-hook-update !>([%initial synced])]~
|
||||
==
|
||||
?. |(=(u.ship src.bol) (team:title our.bol src.bol))
|
||||
:: if neither ship = source or source = us, do nothing
|
||||
[~ state]
|
||||
:: delete a foreign ship's path
|
||||
=/ cards
|
||||
(handle-contact-action path.act our.bol [%remove path.act our.bol])
|
||||
:_ state(synced (~(del by synced) path.act))
|
||||
%- zing
|
||||
:~ (pull-wire [%contacts path.act])
|
||||
[%give %fact [/synced]~ %contact-hook-update !>([%initial synced])]~
|
||||
cards
|
||||
==
|
||||
==
|
||||
::
|
||||
++ poke-import
|
||||
|= arc=*
|
||||
^- (quip card _state)
|
||||
=/ sty=state-three
|
||||
[%3 (remake-map ;;((tree [path ship]) +<.arc)) ;;(? +>.arc)]
|
||||
:_ sty
|
||||
%+ turn ~(tap by synced.sty)
|
||||
|= [=path =ship]
|
||||
^- card
|
||||
=/ contact-path [%contacts path]
|
||||
?: =(our.bol ship)
|
||||
[%pass contact-path %agent [our.bol %contact-store] %watch contact-path]
|
||||
(try-rejoin ship contact-path 0)
|
||||
::
|
||||
++ try-rejoin
|
||||
|= [who=@p pax=path nack-count=@ud]
|
||||
^- card
|
||||
=/ =wire
|
||||
[%try-rejoin (scot %ud nack-count) (scot %p who) pax]
|
||||
[%pass wire %agent [who %contact-hook] %watch pax]
|
||||
::
|
||||
++ watch-contacts
|
||||
|= pax=path
|
||||
^- (list card)
|
||||
?> ?=(^ pax)
|
||||
?> (~(has by synced) pax)
|
||||
:: scry groups to check if ship is a member
|
||||
=/ =group (need (group-scry pax))
|
||||
?> (~(has in members.group) src.bol)
|
||||
=/ contacts (need (contacts-scry pax))
|
||||
[%give %fact ~ %contact-update !>([%contacts pax contacts])]~
|
||||
::
|
||||
++ watch-synced
|
||||
|= pax=path
|
||||
^- (list card)
|
||||
?> (team:title our.bol src.bol)
|
||||
[%give %fact ~ %contact-hook-update !>([%initial synced])]~
|
||||
::
|
||||
++ watch-ack
|
||||
|= [wir=wire saw=(unit tang)]
|
||||
^- (quip card _state)
|
||||
?~ saw
|
||||
[~ state]
|
||||
?: ?=([%try-rejoin @ *] wir)
|
||||
=/ nack-count=@ud (slav %ud i.t.wir)
|
||||
=/ wakeup=@da
|
||||
(add now.bol (mul ~s1 (bex (min 19 nack-count))))
|
||||
:_ state
|
||||
[%pass wir %arvo %b %wait wakeup]~
|
||||
::
|
||||
?> ?=(^ wir)
|
||||
[~ state(synced (~(del by synced) t.wir))]
|
||||
::
|
||||
++ migrate
|
||||
|= wir=wire
|
||||
^- wire
|
||||
?> ?=([%contacts @ @ *] wir)
|
||||
[%contacts %ship t.wir]
|
||||
::
|
||||
++ kick
|
||||
|= wir=wire
|
||||
^- (list card)
|
||||
?+ wir !!
|
||||
[%try-rejoin @ @ *]
|
||||
$(wir t.t.t.wir)
|
||||
::
|
||||
[%inv ~]
|
||||
[%pass /inv %agent [our.bol %invite-store] %watch /invitatory/contacts]~
|
||||
::
|
||||
[%group ~]
|
||||
[%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)
|
||||
[%pass wir %agent [our.bol %contact-store] %watch wir]~
|
||||
[%pass wir %agent [ship %contact-hook] %watch wir]~
|
||||
==
|
||||
::
|
||||
++ fact-contact-update
|
||||
|= [wir=wire fact=contact-update]
|
||||
^- (quip card _state)
|
||||
|^
|
||||
?: (team:title our.bol src.bol)
|
||||
(local fact)
|
||||
:_ state
|
||||
(foreign fact)
|
||||
::
|
||||
++ give-fact
|
||||
|= [=path update=contact-update]
|
||||
^- (list card)
|
||||
[%give %fact ~[[%contacts path]] %contact-update !>(update)]~
|
||||
::
|
||||
++ local
|
||||
|= fact=contact-update
|
||||
^- (quip card _state)
|
||||
?+ -.fact [~ state]
|
||||
%add
|
||||
:_ state
|
||||
(give-fact path.fact [%add path.fact ship.fact contact.fact])
|
||||
::
|
||||
%edit
|
||||
:_ state
|
||||
(give-fact path.fact [%edit path.fact ship.fact edit-field.fact])
|
||||
::
|
||||
%delete
|
||||
=. synced (~(del by synced) path.fact)
|
||||
`state
|
||||
==
|
||||
::
|
||||
++ foreign
|
||||
|= fact=contact-update
|
||||
^- (list card)
|
||||
?+ -.fact ~
|
||||
%contacts
|
||||
=/ owner (~(got by synced) path.fact)
|
||||
?> =(owner src.bol)
|
||||
=/ have-contacts=(unit contacts)
|
||||
(contacts-scry path.fact)
|
||||
?~ have-contacts
|
||||
:: if we don't have any contacts yet,
|
||||
:: create the entry, and %add every contact
|
||||
::
|
||||
:- (contact-poke [%create path.fact])
|
||||
%+ turn ~(tap by contacts.fact)
|
||||
|= [=ship =contact]
|
||||
(contact-poke [%add path.fact ship contact])
|
||||
:: if we already have some, decide between %add, %remove and recreate
|
||||
:: on a per-contact basis
|
||||
::
|
||||
%- zing
|
||||
%+ turn
|
||||
%~ tap in
|
||||
%- ~(uni in ~(key by contacts.fact))
|
||||
~(key by u.have-contacts)
|
||||
|= =ship
|
||||
^- (list card)
|
||||
=/ have=(unit contact) (~(get by u.have-contacts) ship)
|
||||
=/ want=(unit contact) (~(get by contacts.fact) ship)
|
||||
?~ have
|
||||
[(contact-poke %add path.fact ship (need want))]~
|
||||
?~ want
|
||||
[(contact-poke %remove path.fact ship)]~
|
||||
?: =(u.want u.have) ~
|
||||
::TODO probably want an %all edit-field that resolves to more granular
|
||||
:: updates within the contact-store?
|
||||
:~ (contact-poke %remove path.fact ship)
|
||||
(contact-poke %add path.fact ship u.want)
|
||||
==
|
||||
::
|
||||
%add
|
||||
=/ owner (~(get by synced) path.fact)
|
||||
?~ owner ~
|
||||
?> |(=(u.owner src.bol) =(src.bol ship.fact))
|
||||
~[(contact-poke [%add path.fact ship.fact contact.fact])]
|
||||
::
|
||||
%remove
|
||||
=/ owner (~(get by synced) path.fact)
|
||||
?~ owner ~
|
||||
?> |(=(u.owner src.bol) =(src.bol ship.fact))
|
||||
~[(contact-poke [%remove path.fact ship.fact])]
|
||||
::
|
||||
%edit
|
||||
=/ owner (~(got by synced) path.fact)
|
||||
?> |(=(owner src.bol) =(src.bol ship.fact))
|
||||
~[(contact-poke [%edit path.fact ship.fact edit-field.fact])]
|
||||
==
|
||||
--
|
||||
::
|
||||
++ 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]
|
||||
%initial-group (initial-group +.fact)
|
||||
%remove-members (remove +.fact)
|
||||
%remove-group (unbundle +.fact)
|
||||
==
|
||||
::
|
||||
++ initial-group
|
||||
|= [rid=resource =^group]
|
||||
^- (quip card _state)
|
||||
?: hidden.group [~ state]
|
||||
=/ =path
|
||||
(en-path:resource rid)
|
||||
?: (~(has by synced) path)
|
||||
[~ state]
|
||||
(poke-hook-action %add-synced entity.rid path)
|
||||
::
|
||||
++ unbundle
|
||||
|= [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))
|
||||
:~ [%pass [%contacts path] %agent [our.bol %contact-store] %leave ~]
|
||||
[(contact-poke [%delete path])]
|
||||
==
|
||||
::
|
||||
++ remove
|
||||
|= [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 ships)
|
||||
|= =ship
|
||||
(contact-poke [%remove path ship])
|
||||
:_ state
|
||||
%- zing
|
||||
%+ turn ~(tap in ships)
|
||||
|= =ship
|
||||
:~ [%give %kick ~[[%contacts path]] `ship]
|
||||
?: =(ship our.bol)
|
||||
(contact-poke [%delete path])
|
||||
(contact-poke [%remove path ship])
|
||||
==
|
||||
--
|
||||
::
|
||||
++ invite-poke
|
||||
|= act=action:inv
|
||||
^- card
|
||||
[%pass / %agent [our.bol %invite-store] %poke %invite-action !>(act)]
|
||||
::
|
||||
++ contact-poke
|
||||
|= act=contact-action
|
||||
^- card
|
||||
[%pass / %agent [our.bol %contact-store] %poke %contact-action !>(act)]
|
||||
::
|
||||
++ contacts-scry
|
||||
|= pax=path
|
||||
^- (unit contacts)
|
||||
=. pax
|
||||
;: weld
|
||||
/(scot %p our.bol)/contact-store/(scot %da now.bol)/contacts
|
||||
pax
|
||||
/noun
|
||||
==
|
||||
.^((unit contacts) %gx pax)
|
||||
::
|
||||
++ group-scry
|
||||
|= pax=path
|
||||
.^ (unit group)
|
||||
%gx
|
||||
;:(weld /(scot %p our.bol)/group-store/(scot %da now.bol) /groups pax /noun)
|
||||
==
|
||||
::
|
||||
++ pull-wire
|
||||
|= pax=path
|
||||
^- (list card)
|
||||
?> ?=(^ pax)
|
||||
=/ shp (~(get by synced) t.pax)
|
||||
?~ shp ~
|
||||
?: =(u.shp our.bol)
|
||||
[%pass pax %agent [our.bol %contact-store] %leave ~]~
|
||||
[%pass pax %agent [u.shp %contact-hook] %leave ~]~
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
|
45
pkg/arvo/app/contact-pull-hook.hoon
Normal file
45
pkg/arvo/app/contact-pull-hook.hoon
Normal file
@ -0,0 +1,45 @@
|
||||
/- *resource
|
||||
/+ store=contact-store, contact, default-agent, verb, dbug, pull-hook
|
||||
~% %contact-pull-hook-top ..part ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
++ config
|
||||
^- config:pull-hook
|
||||
:* %contact-store
|
||||
update:store
|
||||
%contact-update
|
||||
%contact-push-hook
|
||||
==
|
||||
--
|
||||
::
|
||||
%- agent:dbug
|
||||
^- 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)
|
||||
con ~(. contact bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(~)
|
||||
++ on-load on-load:def
|
||||
++ on-poke on-poke:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-watch on-watch:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-pull-nack
|
||||
|= [=resource =tang]
|
||||
^- (quip card _this)
|
||||
:_ this
|
||||
?~ (get-contact:con entity.resource) ~
|
||||
=- [%pass /pl-nack %agent [our.bowl %contact-store] %poke %contact-update -]~
|
||||
!> ^- update:store
|
||||
[%remove entity.resource]
|
||||
::
|
||||
++ on-pull-kick |=(=resource `/)
|
||||
--
|
69
pkg/arvo/app/contact-push-hook.hoon
Normal file
69
pkg/arvo/app/contact-push-hook.hoon
Normal file
@ -0,0 +1,69 @@
|
||||
/+ store=contact-store, res=resource, contact, default-agent, dbug, push-hook
|
||||
~% %contact-push-hook-top ..part ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
++ config
|
||||
^- config:push-hook
|
||||
:* %contact-store
|
||||
/updates
|
||||
update:store
|
||||
%contact-update
|
||||
%contact-pull-hook
|
||||
==
|
||||
::
|
||||
+$ agent (push-hook:push-hook config)
|
||||
--
|
||||
::
|
||||
%- agent:dbug
|
||||
^- agent:gall
|
||||
%- (agent:push-hook config)
|
||||
^- agent
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
con ~(. contact 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)
|
||||
?- -.update
|
||||
%initial %.n
|
||||
%add %.y
|
||||
%remove %.y
|
||||
%edit %.y
|
||||
%allow %.n
|
||||
%disallow %.n
|
||||
%set-public %.n
|
||||
==
|
||||
::
|
||||
++ initial-watch
|
||||
|= [=path =resource:res]
|
||||
^- vase
|
||||
?> (is-allowed:con src.bowl)
|
||||
!> ^- update:store
|
||||
=/ contact=(unit contact:store) (get-contact:con our.bowl)
|
||||
:+ %add
|
||||
our.bowl
|
||||
?^ contact u.contact
|
||||
*contact:store
|
||||
::
|
||||
++ take-update
|
||||
|= =vase
|
||||
^- [(list card) agent]
|
||||
=/ =update:store !<(update:store vase)
|
||||
?. ?=(%disallow -.update) [~ this]
|
||||
:_ this
|
||||
[%give %kick ~[resource+(en-path:res [our.bowl %our])] ~]~
|
||||
--
|
@ -1,279 +1,220 @@
|
||||
:: contact-store [landscape]:
|
||||
::
|
||||
:: data store that holds group-based contact data
|
||||
:: data store that holds individual contact data
|
||||
::
|
||||
/+ *contact-json, default-agent, dbug, *migrate
|
||||
/- store=contact-store, *resource
|
||||
/+ default-agent, dbug, *migrate
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
+$ state-4
|
||||
$: %4
|
||||
=rolodex:store
|
||||
allowed-groups=(set resource)
|
||||
allowed-ships=(set ship)
|
||||
is-public=_|
|
||||
==
|
||||
+$ versioned-state
|
||||
$% state-zero
|
||||
state-one
|
||||
state-two
|
||||
state-three
|
||||
==
|
||||
::
|
||||
+$ rolodex-0 (map path contacts-0)
|
||||
+$ contacts-0 (map ship contact-0)
|
||||
+$ avatar-0 [content-type=@t octs=[p=@ud q=@t]]
|
||||
+$ contact-0
|
||||
$: nickname=@t
|
||||
email=@t
|
||||
phone=@t
|
||||
website=@t
|
||||
notes=@t
|
||||
color=@ux
|
||||
avatar=(unit avatar-0)
|
||||
==
|
||||
::
|
||||
+$ state-zero
|
||||
$: %0
|
||||
rolodex=rolodex-0
|
||||
==
|
||||
+$ state-one
|
||||
$: %1
|
||||
=rolodex
|
||||
==
|
||||
+$ state-two
|
||||
$: %2
|
||||
=rolodex
|
||||
==
|
||||
+$ state-three
|
||||
$: %3
|
||||
=rolodex
|
||||
$% [%0 *]
|
||||
[%1 *]
|
||||
[%2 *]
|
||||
[%3 *]
|
||||
state-4
|
||||
==
|
||||
--
|
||||
::
|
||||
=| state-three
|
||||
=| state-4
|
||||
=* state -
|
||||
%- agent:dbug
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
contact-core +>
|
||||
cc ~(. contact-core bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init
|
||||
=. rolodex (~(put by rolodex) our.bowl *contact:store)
|
||||
[~ this(state state)]
|
||||
::
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
^- (quip card _this)
|
||||
=/ old !<(versioned-state old-vase)
|
||||
?+ -.old
|
||||
=. rolodex (~(put by rolodex) our.bowl *contact:store)
|
||||
[~ this(state state)]
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
=/ old !<(versioned-state old-vase)
|
||||
=| cards=(list card)
|
||||
|-
|
||||
?: ?=(%3 -.old)
|
||||
[cards this(state old)]
|
||||
?: ?=(%2 -.old)
|
||||
%_ $
|
||||
-.old %3
|
||||
::
|
||||
rolodex.old
|
||||
=/ def
|
||||
(~(get by rolodex.old) /ship/~/default)
|
||||
?~ def
|
||||
rolodex.old
|
||||
=. rolodex.old
|
||||
(~(del by rolodex.old) /ship/~/default)
|
||||
=. rolodex.old
|
||||
(~(put by rolodex.old) /~/default u.def)
|
||||
rolodex.old
|
||||
==
|
||||
?: ?=(%1 -.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
|
||||
^- contacts
|
||||
%- ~(run by cons)
|
||||
|= con=contact-0
|
||||
^- contact
|
||||
:* nickname.con
|
||||
email.con
|
||||
phone.con
|
||||
website.con
|
||||
notes.con
|
||||
color.con
|
||||
~
|
||||
==
|
||||
$(old [%1 new-rolodex])
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
=^ cards state
|
||||
?+ mark (on-poke:def mark vase)
|
||||
::%json (poke-json:cc !<(json vase))
|
||||
%contact-action
|
||||
(poke-contact-action:cc !<(contact-action vase))
|
||||
::
|
||||
%import
|
||||
(poke-import:cc q.vase)
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
|^
|
||||
=/ cards=(list card)
|
||||
?+ path (on-watch:def path)
|
||||
[%all ~] (give %contact-update !>([%initial rolodex]))
|
||||
[%updates ~] ~
|
||||
[%contacts @ *]
|
||||
%+ give %contact-update
|
||||
!>([%contacts t.path (~(got by rolodex) t.path)])
|
||||
==
|
||||
[cards this]
|
||||
%4 [~ this(state old)]
|
||||
==
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
|^
|
||||
=/ cards=(list card)
|
||||
?+ path (on-watch:def path)
|
||||
[%all ~] (give [%initial rolodex is-public])
|
||||
[%updates ~] ~
|
||||
::
|
||||
++ give
|
||||
|= =cage
|
||||
^- (list card)
|
||||
[%give %fact ~ cage]~
|
||||
--
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek
|
||||
|= =path
|
||||
^- (unit (unit cage))
|
||||
?+ path (on-peek:def path)
|
||||
[%x %all ~] ``noun+!>(rolodex)
|
||||
[%x %contacts *]
|
||||
?~ t.t.path
|
||||
~
|
||||
``noun+!>((~(get by rolodex) t.t.path))
|
||||
::
|
||||
[%x %contact *]
|
||||
:: /:path/:ship
|
||||
=/ pax `^path`(flop t.t.path)
|
||||
?~ pax ~
|
||||
=/ =ship (slav %p i.pax)
|
||||
?~ t.pax ~
|
||||
=> .(pax `(list @ta)`(flop t.pax))
|
||||
=/ contacts=(unit contacts) (~(get by rolodex) pax)
|
||||
?~ contacts
|
||||
~
|
||||
``noun+!>((~(get by u.contacts) ship))
|
||||
::
|
||||
[%x %export ~]
|
||||
``noun+!>(state)
|
||||
[%our ~]
|
||||
%- give
|
||||
:+ %add
|
||||
our.bowl
|
||||
=/ contact=(unit contact:store) (~(get by rolodex) our.bowl)
|
||||
?~ contact *contact:store
|
||||
u.contact
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ on-agent on-agent:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
++ give
|
||||
|= =update:store
|
||||
^- (list card)
|
||||
[%give %fact ~ [%contact-update !>(update)]]~
|
||||
--
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
|^
|
||||
=^ cards state
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%contact-update (update !<(update:store vase))
|
||||
%import (import q.vase)
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ update
|
||||
|= =update:store
|
||||
^- (quip card _state)
|
||||
|^
|
||||
?- -.update
|
||||
%initial (handle-initial +.update)
|
||||
%add (handle-add +.update)
|
||||
%remove (handle-remove +.update)
|
||||
%edit (handle-edit +.update)
|
||||
%allow (handle-allow +.update)
|
||||
%disallow (handle-disallow +.update)
|
||||
%set-public (handle-set-public +.update)
|
||||
==
|
||||
::
|
||||
++ handle-initial
|
||||
|= [rolo=rolodex:store is-public=?]
|
||||
^- (quip card _state)
|
||||
=. rolodex (~(uni by rolodex) rolo)
|
||||
:_ state(rolodex rolodex, is-public is-public)
|
||||
(send-diff [%initial rolodex is-public] %.n)
|
||||
::
|
||||
++ handle-add
|
||||
|= [=ship =contact:store]
|
||||
^- (quip card _state)
|
||||
=. last-updated.contact now.bowl
|
||||
:- (send-diff [%add ship contact] =(ship our.bowl))
|
||||
state(rolodex (~(put by rolodex) ship contact))
|
||||
::
|
||||
++ handle-remove
|
||||
|= =ship
|
||||
^- (quip card _state)
|
||||
?> (~(has by rolodex) ship)
|
||||
:- (send-diff [%remove ship] =(ship our.bowl))
|
||||
?: =(ship our.bowl)
|
||||
state(rolodex (~(put by rolodex) our.bowl *contact:store))
|
||||
state(rolodex (~(del by rolodex) ship))
|
||||
::
|
||||
++ handle-edit
|
||||
|= [=ship =edit-field:store]
|
||||
|^
|
||||
^- (quip card _state)
|
||||
=/ contact (~(got by rolodex) ship)
|
||||
=. contact (edit-contact contact edit-field)
|
||||
=. last-updated.contact now.bowl
|
||||
:- (send-diff [%edit ship edit-field] =(ship our.bowl))
|
||||
state(rolodex (~(put by rolodex) ship contact))
|
||||
::
|
||||
++ edit-contact
|
||||
|= [=contact:store edit=edit-field:store]
|
||||
^- contact:store
|
||||
?- -.edit
|
||||
%nickname contact(nickname nickname.edit)
|
||||
%bio contact(bio bio.edit)
|
||||
%status contact(status status.edit)
|
||||
%color contact(color color.edit)
|
||||
%avatar contact(avatar avatar.edit)
|
||||
%cover contact(cover cover.edit)
|
||||
::
|
||||
%add-group
|
||||
contact(groups (~(put in groups.contact) resource.edit))
|
||||
::
|
||||
%remove-group
|
||||
contact(groups (~(del in groups.contact) resource.edit))
|
||||
==
|
||||
--
|
||||
::
|
||||
++ handle-allow
|
||||
|= =beings:store
|
||||
^- (quip card _state)
|
||||
:- (send-diff [%allow beings] %.n)
|
||||
?- -.beings
|
||||
%group state(allowed-groups (~(put in allowed-groups) resource.beings))
|
||||
%ships state(allowed-ships (~(uni in allowed-ships) ships.beings))
|
||||
==
|
||||
::
|
||||
++ handle-disallow
|
||||
|= =beings:store
|
||||
^- (quip card _state)
|
||||
:- (send-diff [%disallow beings] %.y)
|
||||
?- -.beings
|
||||
%group state(allowed-groups (~(del in allowed-groups) resource.beings))
|
||||
%ships state(allowed-ships (~(dif in allowed-ships) ships.beings))
|
||||
==
|
||||
::
|
||||
++ handle-set-public
|
||||
|= public=?
|
||||
^- (quip card _state)
|
||||
:_ state(is-public public)
|
||||
(send-diff [%set-public public] %.n)
|
||||
::
|
||||
++ send-diff
|
||||
|= [=update:store our=?]
|
||||
^- (list card)
|
||||
=/ paths=(list path)
|
||||
?: our
|
||||
[/updates /our /all ~]
|
||||
[/updates /all ~]
|
||||
[%give %fact paths %contact-update !>(update)]~
|
||||
--
|
||||
::
|
||||
++ import
|
||||
|= arc=*
|
||||
^- (quip card _state)
|
||||
:: note: we are purposefully wiping all state before state-4
|
||||
[~ *state-4]
|
||||
--
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
::
|
||||
::++ poke-json
|
||||
:: |= =json
|
||||
:: ^- (quip move _this)
|
||||
:: ?> (team:title our.bol src.bol)
|
||||
:: (poke-contact-action (json-to-action json))
|
||||
::
|
||||
++ poke-contact-action
|
||||
|= action=contact-action
|
||||
^- (quip card _state)
|
||||
?> (team:title our.bol src.bol)
|
||||
?- -.action
|
||||
%create (handle-create +.action)
|
||||
%delete (handle-delete +.action)
|
||||
%add (handle-add +.action)
|
||||
%remove (handle-remove +.action)
|
||||
%edit (handle-edit +.action)
|
||||
++ on-peek
|
||||
|= =path
|
||||
^- (unit (unit cage))
|
||||
?+ path (on-peek:def path)
|
||||
[%x %all ~] ``noun+!>(rolodex)
|
||||
::
|
||||
[%x %contact @ ~]
|
||||
=/ =ship (slav %p i.t.t.path)
|
||||
=/ contact=(unit contact:store) (~(get by rolodex) ship)
|
||||
?~ contact [~ ~]
|
||||
:- ~ :- ~ :- %contact-update
|
||||
!> ^- update:store
|
||||
[%add ship u.contact]
|
||||
::
|
||||
[%x %allowed-ship @ ~]
|
||||
=/ =ship (slav %p i.t.t.path)
|
||||
``noun+!>((~(has in allowed-ships) ship))
|
||||
::
|
||||
[%x %allowed-groups ~]
|
||||
``noun+!>(allowed-groups)
|
||||
==
|
||||
::
|
||||
++ poke-import
|
||||
|= arc=*
|
||||
^- (quip card _state)
|
||||
=/ sty=state-three
|
||||
:- %3
|
||||
%- remake-map-of-map
|
||||
;;((tree [path (tree [ship contact])]) +.arc)
|
||||
[~ sty]
|
||||
::
|
||||
++ handle-create
|
||||
|= =path
|
||||
^- (quip card _state)
|
||||
?< (~(has by rolodex) path)
|
||||
:- (send-diff path [%create path])
|
||||
state(rolodex (~(put by rolodex) path *contacts))
|
||||
::
|
||||
++ handle-delete
|
||||
|= =path
|
||||
^- (quip card _state)
|
||||
?. (~(has by rolodex) path) [~ state]
|
||||
:- (send-diff path [%delete path])
|
||||
state(rolodex (~(del by rolodex) path))
|
||||
::
|
||||
++ handle-add
|
||||
|= [=path =ship =contact]
|
||||
^- (quip card _state)
|
||||
=/ contacts (~(got by rolodex) path)
|
||||
?< (~(has by contacts) ship)
|
||||
=. contacts (~(put by contacts) ship contact)
|
||||
:- (send-diff path [%add path ship contact])
|
||||
state(rolodex (~(put by rolodex) path contacts))
|
||||
::
|
||||
++ handle-remove
|
||||
|= [=path =ship]
|
||||
^- (quip card _state)
|
||||
=/ contacts (~(got by rolodex) path)
|
||||
?. (~(has by contacts) ship) [~ state]
|
||||
=. contacts (~(del by contacts) ship)
|
||||
:- (send-diff path [%remove path ship])
|
||||
state(rolodex (~(put by rolodex) path contacts))
|
||||
::
|
||||
++ handle-edit
|
||||
|= [=path =ship =edit-field]
|
||||
^- (quip card _state)
|
||||
=/ contacts (~(got by rolodex) path)
|
||||
=/ contact (~(got by contacts) ship)
|
||||
=. contact (edit-contact contact edit-field)
|
||||
=. contacts (~(put by contacts) ship contact)
|
||||
:- (send-diff path [%edit path ship edit-field])
|
||||
state(rolodex (~(put by rolodex) path contacts))
|
||||
::
|
||||
++ edit-contact
|
||||
|= [con=contact edit=edit-field]
|
||||
^- contact
|
||||
?- -.edit
|
||||
%nickname con(nickname nickname.edit)
|
||||
%email con(email email.edit)
|
||||
%phone con(phone phone.edit)
|
||||
%website con(website website.edit)
|
||||
%notes con(notes notes.edit)
|
||||
%color con(color color.edit)
|
||||
%avatar con(avatar avatar.edit)
|
||||
==
|
||||
::
|
||||
++ send-diff
|
||||
|= [pax=path upd=contact-update]
|
||||
^- (list card)
|
||||
:~ :*
|
||||
%give %fact
|
||||
~[/all /updates [%contacts pax]]
|
||||
%contact-update !>(upd)
|
||||
== ==
|
||||
++ on-leave on-leave:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
|
@ -1,343 +1,27 @@
|
||||
:: contact-view [landscape]:
|
||||
::
|
||||
:: sets up contact JS client and combines commands
|
||||
:: into semantic actions for the UI
|
||||
::
|
||||
/-
|
||||
inv=invite-store,
|
||||
*contact-hook,
|
||||
metadata=metadata-store,
|
||||
pull-hook,
|
||||
push-hook
|
||||
/+ *server, *contact-json, default-agent, dbug, verb,
|
||||
grpl=group, mdl=metadata, resource,
|
||||
group-store
|
||||
:: contact-view [landscape]: deprecated
|
||||
::
|
||||
/+ default-agent
|
||||
|%
|
||||
+$ versioned-state
|
||||
$% state-0
|
||||
==
|
||||
::
|
||||
+$ state-0
|
||||
$: %0
|
||||
~
|
||||
==
|
||||
::
|
||||
+$ card card:agent:gall
|
||||
--
|
||||
=| state-0
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
contact-core +>
|
||||
cc ~(. contact-core bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card _this)
|
||||
:_ this
|
||||
:~ [%pass /updates %agent [our.bowl %contact-store] %watch /updates]
|
||||
(contact-poke:cc [%create /~/default])
|
||||
(contact-poke:cc [%add /~/default our.bowl *contact])
|
||||
:* %pass /srv %agent [our.bol %file-server]
|
||||
%poke %file-server-action
|
||||
!>([%serve-dir /'~groups' /app/landscape %.n %.y])
|
||||
==
|
||||
==
|
||||
::
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
^- (quip card _this)
|
||||
=/ old ((soft state-0) q.old-vase)
|
||||
?^ old [~ this]
|
||||
:_ this(state [%0 ~])
|
||||
:~ [%pass / %arvo %e %disconnect [~ /'~groups']]
|
||||
[%pass / %arvo %e %connect [~ /'contact-view'] %contact-view]
|
||||
:* %pass /srv %agent [our.bol %file-server]
|
||||
%poke %file-server-action
|
||||
!>([%serve-dir /'~groups' /app/landscape %.n %.y])
|
||||
==
|
||||
==
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%json [(poke-json:cc !<(json vase)) this]
|
||||
%contact-view-action
|
||||
[(poke-contact-view-action:cc !<(contact-view-action vase)) this]
|
||||
::
|
||||
%handle-http-request
|
||||
=+ !<([eyre-id=@ta =inbound-request:eyre] vase)
|
||||
:_ this
|
||||
%+ give-simple-payload:app eyre-id
|
||||
%+ require-authorization:app inbound-request
|
||||
poke-handle-http-request:cc
|
||||
==
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
?: ?=([%http-response *] path) [~ this]
|
||||
?. =(/primary path) (on-watch:def path)
|
||||
[[%give %fact ~ %json !>((update-to-json [%initial all-scry:cc]))]~ this]
|
||||
::
|
||||
++ on-agent
|
||||
|= [=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]
|
||||
::
|
||||
%fact
|
||||
?+ p.cage.sign (on-agent:def wire sign)
|
||||
%contact-update
|
||||
=/ update=json (update-to-json !<(contact-update q.cage.sign))
|
||||
[[%give %fact ~[/primary] %json !>(update)]~ this]
|
||||
==
|
||||
==
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card _this)
|
||||
?. ?=(%bound +<.sign-arvo)
|
||||
(on-arvo:def wire sign-arvo)
|
||||
[~ this]
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
++ grp ~(. grpl bol)
|
||||
++ md ~(. mdl bol)
|
||||
++ poke-json
|
||||
|= jon=json
|
||||
^- (list card)
|
||||
?> (team:title our.bol src.bol)
|
||||
(poke-contact-view-action (json-to-view-action jon))
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bol)
|
||||
::
|
||||
++ poke-contact-view-action
|
||||
|= act=contact-view-action
|
||||
^- (list card)
|
||||
?> (team:title our.bol src.bol)
|
||||
?- -.act
|
||||
%create
|
||||
=/ 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 rid title.act description.act)
|
||||
?. ?=(%invite -.policy.act)
|
||||
~
|
||||
%+ turn
|
||||
~(tap in pending.policy.act)
|
||||
|= =ship
|
||||
(send-invite our.bol %contacts rid ship '')
|
||||
==
|
||||
::
|
||||
%join
|
||||
=/ =cage
|
||||
:- %group-update
|
||||
!> ^- update:group-store
|
||||
[%add-members resource.act (sy our.bol ~)]
|
||||
=/ =wire
|
||||
[%join-group (en-path:resource resource.act)]
|
||||
[%pass wire %agent [entity.resource.act %group-push-hook] %poke cage]~
|
||||
::
|
||||
%invite
|
||||
=* rid resource.act
|
||||
=/ =group (need (scry-group:grp rid))
|
||||
:- (send-invite entity.rid %contacts rid ship.act text.act)
|
||||
?. ?=(%invite -.policy.group) ~
|
||||
~[(add-pending rid ship.act)]
|
||||
::
|
||||
%delete
|
||||
~
|
||||
::
|
||||
%remove
|
||||
=/ rid=resource
|
||||
(de-path:resource path.act)
|
||||
:~ (group-poke %remove-members rid (sy ship.act ~))
|
||||
(contact-poke [%remove path.act ship.act])
|
||||
==
|
||||
::
|
||||
%share
|
||||
:: 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 resource.act title.act description.act)
|
||||
==
|
||||
++ poke-handle-http-request
|
||||
|= =inbound-request:eyre
|
||||
^- simple-payload:http
|
||||
=+ url=(parse-request-line url.request.inbound-request)
|
||||
=/ name=@t
|
||||
=+ back-path=(flop site.url)
|
||||
?~ back-path
|
||||
''
|
||||
i.back-path
|
||||
?+ site.url not-found:gen
|
||||
[%'contact-view' @ *]
|
||||
=/ =path (flop t.t.site.url)
|
||||
?~ path not-found:gen
|
||||
=/ contact (contact-scry `^path`(snoc (flop t.path) name))
|
||||
?~ contact not-found:gen
|
||||
?~ avatar.u.contact not-found:gen
|
||||
?- -.u.avatar.u.contact
|
||||
%url [[307 ['location' url.u.avatar.u.contact]~] ~]
|
||||
%octt
|
||||
=/ max-3-days ['cache-control' 'max-age=259200']
|
||||
=/ content-type ['content-type' content-type.u.avatar.u.contact]
|
||||
[[200 [content-type max-3-days ~]] `octs.u.avatar.u.contact]
|
||||
==
|
||||
==
|
||||
++ on-init on-init:def
|
||||
++ on-poke on-poke:def
|
||||
++ on-watch on-watch:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-save !>(~)
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
^- (quip card _this)
|
||||
[~ this]
|
||||
::
|
||||
++ 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])
|
||||
(pull-metadata rid)
|
||||
==
|
||||
::
|
||||
:: +utilities
|
||||
::
|
||||
++ add-pending
|
||||
|= [rid=resource =ship]
|
||||
^- card
|
||||
=/ app=term
|
||||
?: =(our.bol entity.rid)
|
||||
%group-store
|
||||
%group-push-hook
|
||||
=/ =cage
|
||||
:- %group-update
|
||||
!> ^- action:group-store
|
||||
[%change-policy rid %invite %add-invites (sy ship ~)]
|
||||
[%pass / %agent [entity.rid app] %poke cage]
|
||||
::
|
||||
++ send-invite
|
||||
|= =invite:inv
|
||||
^- card
|
||||
=/ =cage
|
||||
:- %invite-action
|
||||
!> ^- action:inv
|
||||
[%invite %contacts (shaf %invite-uid eny.bol) invite]
|
||||
[%pass / %agent [recipient.invite %invite-hook] %poke cage]
|
||||
::
|
||||
++ contact-poke
|
||||
|= act=contact-action
|
||||
^- card
|
||||
[%pass / %agent [our.bol %contact-store] %poke %contact-action !>(act)]
|
||||
::
|
||||
++ contact-hook-poke
|
||||
|= act=contact-hook-action
|
||||
^- card
|
||||
[%pass / %agent [our.bol %contact-hook] %poke %contact-hook-action !>(act)]
|
||||
::
|
||||
++ share-poke
|
||||
|= [=ship act=contact-action]
|
||||
^- card
|
||||
[%pass / %agent [ship %contact-hook] %poke %contact-action !>(act)]
|
||||
::
|
||||
++ group-poke
|
||||
|= act=action:group-store
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-store] %poke %group-action !>(act)]
|
||||
::
|
||||
++ group-push-poke
|
||||
|= act=action:push-hook
|
||||
^- card
|
||||
[%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
|
||||
|= =action:metadata
|
||||
^- card
|
||||
[%pass / %agent [our.bol %metadata-store] %poke metadata-action+!>(action)]
|
||||
::
|
||||
++ create-metadata
|
||||
|= [rid=resource title=@t description=@t]
|
||||
^- (list card)
|
||||
=/ =metadatum:metadata
|
||||
%* . *metadatum:metadata
|
||||
title title
|
||||
description description
|
||||
date-created now.bol
|
||||
creator our.bol
|
||||
==
|
||||
:~ (metadata-poke [%add rid [%contacts rid] metadatum])
|
||||
(push-metadata rid)
|
||||
==
|
||||
::
|
||||
++ push-metadata
|
||||
|= rid=resource
|
||||
^- card
|
||||
=- [%pass / %agent [our.bol %metadata-push-hook] %poke -]
|
||||
push-hook-action+!>([%add rid])
|
||||
::
|
||||
++ pull-metadata
|
||||
|= rid=resource
|
||||
^- card
|
||||
=- [%pass / %agent [our.bol %metadata-pull-hook] %poke -]
|
||||
pull-hook-action+!>([%add [entity .]:rid])
|
||||
::
|
||||
++ all-scry
|
||||
^- rolodex
|
||||
.^(rolodex %gx /(scot %p our.bol)/contact-store/(scot %da now.bol)/all/noun)
|
||||
::
|
||||
++ contact-scry
|
||||
|= pax=path
|
||||
^- (unit contact)
|
||||
=. pax
|
||||
;: weld
|
||||
/(scot %p our.bol)/contact-store/(scot %da now.bol)/contact
|
||||
pax
|
||||
/noun
|
||||
==
|
||||
.^((unit contact) %gx pax)
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
|
@ -29,7 +29,7 @@
|
||||
:: Modify the group. Further documented in /sur/group-store.hoon
|
||||
::
|
||||
::
|
||||
/- *group, *contact-view
|
||||
/- *group
|
||||
/+ store=group-store, default-agent, verb, dbug, resource, *migrate
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
@ -284,11 +284,8 @@
|
||||
|= [recipient=@p out=(list card)]
|
||||
?: =(recipient our.bol)
|
||||
out
|
||||
:_ out
|
||||
%- poke-contact
|
||||
:* %invite rid recipient
|
||||
(crip "Rejoin disconnected group {<entity.rid>}/{<name.rid>}")
|
||||
==
|
||||
:: TODO: figure out contacts integration
|
||||
out
|
||||
:_ out
|
||||
(try-rejoin rid 0)
|
||||
::
|
||||
@ -610,11 +607,6 @@
|
||||
|= =action:store
|
||||
^- card
|
||||
[%pass / %agent [our.bol %group-store] %poke %group-action !>(action)]
|
||||
::
|
||||
++ poke-contact
|
||||
|= act=contact-view-action
|
||||
^- card
|
||||
[%pass / %agent [our.bol %contact-view] %poke %contact-view-action !>(act)]
|
||||
:: +send-diff: update subscribers of new state
|
||||
::
|
||||
:: We only allow subscriptions on /groups
|
||||
|
227
pkg/arvo/app/group-view.hoon
Normal file
227
pkg/arvo/app/group-view.hoon
Normal file
@ -0,0 +1,227 @@
|
||||
/- view-sur=group-view, group-store, *group, metadata=metadata-store
|
||||
/+ default-agent, agentio, mdl=metadata, resource, dbug, grpl=group, verb
|
||||
|%
|
||||
++ card card:agent:gall
|
||||
+$ state-zero
|
||||
$: %0
|
||||
joining=(map rid=resource [=ship =progress:view])
|
||||
==
|
||||
++ view view-sur
|
||||
--
|
||||
=| state-zero
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
gc ~(. +> bowl)
|
||||
io ~(. agentio bowl)
|
||||
++ on-init
|
||||
`this
|
||||
++ on-save
|
||||
!>(state)
|
||||
::
|
||||
++ on-load
|
||||
|= =vase
|
||||
=+ !<(old=state-zero vase)
|
||||
`this(state old)
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?. ?=(?(%group-view-action %noun) mark)
|
||||
(on-poke:def mark vase)
|
||||
=+ !<(=action:view vase)
|
||||
?> ?=(%join -.action)
|
||||
=^ cards state
|
||||
jn-abet:(jn-start:join:gc +.action)
|
||||
[cards this]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
?+ path (on-watch:def path)
|
||||
[%all ~]
|
||||
:_ this
|
||||
:_ ~
|
||||
%+ fact:io
|
||||
:- %group-view-update
|
||||
!> ^- update:view
|
||||
[%initial (~(run by joining) |=([=ship =progress:view] progress))]
|
||||
~
|
||||
==
|
||||
::
|
||||
++ on-peek on-peek:def
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
=^ cards state
|
||||
?+ wire `state
|
||||
[%join %ship @ @ *]
|
||||
=/ rid
|
||||
(de-path:resource t.wire)
|
||||
?. (~(has by joining) rid) `state
|
||||
jn-abet:(jn-agent:(jn-abed:join:gc rid) t.t.t.t.wire sign)
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ on-arvo on-arvo:def
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
::
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
|_ =bowl:gall
|
||||
++ met ~(. mdl bowl)
|
||||
++ grp ~(. grpl bowl)
|
||||
++ io ~(. agentio bowl)
|
||||
::
|
||||
::
|
||||
++ join
|
||||
|_ [rid=resource =ship cards=(list card)]
|
||||
++ jn-core .
|
||||
++ emit-many
|
||||
|= crds=(list card)
|
||||
jn-core(cards (weld (flop crds) cards))
|
||||
++ emit
|
||||
|= =card
|
||||
jn-core(cards [card cards])
|
||||
::
|
||||
++ tx-progress
|
||||
|= =progress:view
|
||||
=. joining
|
||||
(~(put by joining) rid [ship progress])
|
||||
=; =cage
|
||||
(emit (fact:io cage /all tx+(en-path:resource rid) ~))
|
||||
group-view-update+!>([%progress rid progress])
|
||||
::
|
||||
++ watch-md
|
||||
(emit (watch-our:(jn-pass-io /md) %metadata-store /updates))
|
||||
::
|
||||
++ watch-groups
|
||||
(emit (watch-our:(jn-pass-io /groups) %group-store /groups))
|
||||
::
|
||||
++ jn-pass-io
|
||||
|= pax=path
|
||||
~(. pass:io (welp join+(en-path:resource rid) pax))
|
||||
::
|
||||
++ jn-abed
|
||||
|= r=resource
|
||||
=/ [s=^ship =progress:view]
|
||||
(~(got by joining) r)
|
||||
jn-core(rid r, ship s)
|
||||
::
|
||||
++ jn-abet
|
||||
^- (quip card _state)
|
||||
[(flop cards) state]
|
||||
::
|
||||
++ jn-start
|
||||
|= [rid=resource =^ship]
|
||||
^+ jn-core
|
||||
?< (~(has by joining) rid)
|
||||
=. joining
|
||||
(~(put by joining) rid [ship %start])
|
||||
=. jn-core
|
||||
(jn-abed rid)
|
||||
=/ maybe-group
|
||||
(peek-group:met %groups rid)
|
||||
?^ maybe-group
|
||||
~|("already joined group {<rid>}" !!)
|
||||
=. jn-core
|
||||
%- emit
|
||||
%+ poke:(jn-pass-io /add)
|
||||
[ship %group-push-hook]
|
||||
group-update+!>([%add-members rid (silt our.bowl ~)])
|
||||
=. jn-core (tx-progress %start)
|
||||
=> watch-md
|
||||
watch-groups
|
||||
::
|
||||
++ jn-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^+ jn-core
|
||||
|^
|
||||
?+ -.wire ~|("bad %join wire" !!)
|
||||
%add :: join group
|
||||
?> ?=(%poke-ack -.sign)
|
||||
?^ p.sign
|
||||
(cleanup %no-perms)
|
||||
=> %- emit
|
||||
%+ poke-our:(jn-pass-io /pull-groups) %group-pull-hook
|
||||
pull-hook-action+!>([%add ship rid])
|
||||
(tx-progress %added)
|
||||
::
|
||||
%pull-groups
|
||||
?> ?=(%poke-ack -.sign)
|
||||
(ack +.sign)
|
||||
::
|
||||
%groups
|
||||
?+ -.sign !!
|
||||
%fact (groups-fact +.sign)
|
||||
%watch-ack (ack +.sign)
|
||||
%kick watch-groups
|
||||
==
|
||||
::
|
||||
%pull-md
|
||||
?> ?=(%poke-ack -.sign)
|
||||
(ack +.sign)
|
||||
::
|
||||
%md
|
||||
?+ -.sign !!
|
||||
%fact (md-fact +.sign)
|
||||
%watch-ack (ack +.sign)
|
||||
%kick watch-md
|
||||
==
|
||||
::
|
||||
%pull-graphs
|
||||
?> ?=(%poke-ack -.sign)
|
||||
%- cleanup
|
||||
?^(p.sign %strange %done)
|
||||
==
|
||||
++ groups-fact
|
||||
|= =cage
|
||||
?. ?=(%group-update p.cage) jn-core
|
||||
=+ !<(=update:group-store q.cage)
|
||||
?. ?=(%initial-group -.update) jn-core
|
||||
?. =(rid resource.update) jn-core
|
||||
%- emit
|
||||
%+ poke-our:(jn-pass-io /pull-md) %metadata-pull-hook
|
||||
pull-hook-action+!>([%add [entity .]:rid])
|
||||
::
|
||||
++ md-fact
|
||||
|= [=mark =vase]
|
||||
?. ?=(%metadata-update mark) jn-core
|
||||
=+ !<(=update:metadata vase)
|
||||
?. ?=(%initial-group -.update) jn-core
|
||||
?. =(group.update rid) jn-core
|
||||
=. jn-core (cleanup %done)
|
||||
?. hidden:(need (scry-group:grp rid)) jn-core
|
||||
%- emit-many
|
||||
%+ murn ~(tap by associations.update)
|
||||
|= [=md-resource:metadata =association:metadata]
|
||||
^- (unit card)
|
||||
?. =(app-name.md-resource %graph) ~
|
||||
=* rid resource.md-resource
|
||||
:- ~
|
||||
%+ poke-our:(jn-pass-io /pull-graph) %graph-pull-hook
|
||||
pull-hook-action+!>([%add [entity .]:rid])
|
||||
::
|
||||
++ ack
|
||||
|= err=(unit tang)
|
||||
?~ err jn-core
|
||||
%- (slog u.err)
|
||||
(cleanup %strange)
|
||||
::
|
||||
++ cleanup
|
||||
|= =progress:view
|
||||
=. jn-core
|
||||
(tx-progress progress)
|
||||
=. joining (~(del by joining) rid)
|
||||
=. jn-core
|
||||
(emit (leave-our:(jn-pass-io /groups) %group-store))
|
||||
(emit (leave-our:(jn-pass-io /md) %metadata-store))
|
||||
--
|
||||
--
|
||||
--
|
@ -2,7 +2,7 @@
|
||||
/+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln
|
||||
|%
|
||||
+$ state
|
||||
$: %11
|
||||
$: %12
|
||||
drum=state:drum
|
||||
helm=state:helm
|
||||
kiln=state:kiln
|
||||
@ -14,6 +14,7 @@
|
||||
[%8 drum=state:drum helm=state:helm kiln=state:kiln]
|
||||
[%9 drum=state:drum helm=state:helm kiln=state:kiln]
|
||||
[%10 drum=state:drum helm=state:helm kiln=state:kiln]
|
||||
[%11 drum=state:drum helm=state:helm kiln=state:kiln]
|
||||
==
|
||||
+$ any-state-tuple
|
||||
$: drum=any-state:drum
|
||||
|
@ -6,6 +6,7 @@
|
||||
+$ versioned-state
|
||||
$% state-0
|
||||
state-1
|
||||
state-2
|
||||
==
|
||||
::
|
||||
+$ invitatory-0 (map serial:store invite-0)
|
||||
@ -19,9 +20,10 @@
|
||||
::
|
||||
+$ state-0 [%0 invites=(map path invitatory-0)]
|
||||
+$ state-1 [%1 =invites:store]
|
||||
+$ state-2 [%2 =invites:store]
|
||||
--
|
||||
::
|
||||
=| state-1
|
||||
=| state-2
|
||||
=* state -
|
||||
%- agent:dbug
|
||||
^- agent:gall
|
||||
@ -43,37 +45,22 @@
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
=/ old !<(versioned-state old-vase)
|
||||
=| cards=(list card)
|
||||
|-
|
||||
?: ?=(%2 -.old)
|
||||
[cards this(state old)]
|
||||
?: ?=(%1 -.old)
|
||||
`this(state old)
|
||||
:- =- [%pass / %agent [our.bowl %invite-store] %poke %invite-action -]~
|
||||
!> ^- action:store
|
||||
[%create %graph]
|
||||
%= this
|
||||
state
|
||||
:- %1
|
||||
%- ~(gas by *invites:store)
|
||||
%+ murn ~(tap by invites.old)
|
||||
|= [=path =invitatory-0]
|
||||
^- (unit [term invitatory:store])
|
||||
?. ?=([@ ~] path) ~
|
||||
:- ~
|
||||
:- i.path
|
||||
%- ~(gas by *invitatory:store)
|
||||
%+ murn ~(tap by invitatory-0)
|
||||
|= [=serial:store =invite-0]
|
||||
^- (unit [serial:store invite:store])
|
||||
=/ resource=(unit resource:res) (de-path-soft:res path.invite-0)
|
||||
?~ resource ~
|
||||
:- ~
|
||||
:- serial
|
||||
^- invite:store
|
||||
:* ship.invite-0
|
||||
app.invite-0
|
||||
u.resource
|
||||
recipient.invite-0
|
||||
text.invite-0
|
||||
==
|
||||
==
|
||||
=. cards
|
||||
:~ =- [%pass / %agent [our.bowl %invite-store] %poke %invite-action -]
|
||||
!> ^- action:store
|
||||
[%create %groups]
|
||||
::
|
||||
=- [%pass / %agent [our.bowl %invite-store] %poke %invite-action -]
|
||||
!> ^- action:store
|
||||
[%delete %contacts]
|
||||
==
|
||||
$(-.old %2)
|
||||
$(old [%1 (~(gas by *invites:store) [%graph *invitatory:store]~)])
|
||||
::
|
||||
++ on-agent on-agent:def
|
||||
++ on-arvo on-arvo:def
|
||||
@ -109,11 +96,19 @@
|
||||
++ poke-import
|
||||
|= arc=*
|
||||
^- (quip card _state)
|
||||
=/ sty=state-1
|
||||
:- %1
|
||||
=/ sty=state-2
|
||||
:- %2
|
||||
%- remake-map-of-map
|
||||
;;((tree [term (tree [serial:store invite:store])]) +.arc)
|
||||
[~ sty]
|
||||
:_ sty
|
||||
:~ =- [%pass / %agent [our.bowl %invite-store] %poke %invite-action -]
|
||||
!> ^- action:store
|
||||
[%create %groups]
|
||||
::
|
||||
=- [%pass / %agent [our.bowl %invite-store] %poke %invite-action -]
|
||||
!> ^- action:store
|
||||
[%delete %contacts]
|
||||
==
|
||||
::
|
||||
++ poke-invite-action
|
||||
|= =action:store
|
||||
|
@ -55,7 +55,6 @@
|
||||
=+ !<(=update:invite-store q.cage.sign)
|
||||
:_ state
|
||||
?. ?=(%invite -.update) ~
|
||||
?. =(%contacts term.update) ~
|
||||
(get-preview resource.invite.update)^~
|
||||
::
|
||||
%kick [watch-invites^~ state]
|
||||
|
@ -49,7 +49,9 @@
|
||||
=/ members
|
||||
~(wyt in (members:grp rid))
|
||||
=/ =metadatum:store
|
||||
(need (peek-metadatum:met %contacts rid))
|
||||
%- need
|
||||
%+ mate (peek-metadatum:met %groups rid)
|
||||
(peek-metadatum:met %graph rid)
|
||||
:_ this
|
||||
=; =cage
|
||||
[%pass / %agent [src.bowl %metadata-pull-hook] %poke cage]~
|
||||
|
@ -24,7 +24,7 @@
|
||||
:: /group/%path associations for group
|
||||
::
|
||||
/- store=metadata-store
|
||||
/+ *metadata-json, default-agent, verb, dbug, resource, *migrate
|
||||
/+ default-agent, verb, dbug, resource, *migrate
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
+$ base-state-0
|
||||
@ -353,7 +353,7 @@
|
||||
++ handle-add
|
||||
|= [group=resource =md-resource:store =metadatum:store]
|
||||
^- (quip card _state)
|
||||
:- %+ send-diff app-name.md-resource
|
||||
:- %- send-diff
|
||||
[%add group md-resource metadatum]
|
||||
%= state
|
||||
associations
|
||||
@ -374,7 +374,7 @@
|
||||
++ handle-remove
|
||||
|= [group=resource =md-resource:store]
|
||||
^- (quip card _state)
|
||||
:- (send-diff app-name.md-resource [%remove group md-resource])
|
||||
:- (send-diff [%remove group md-resource])
|
||||
%= state
|
||||
associations
|
||||
(~(del by associations) md-resource)
|
||||
@ -395,15 +395,15 @@
|
||||
|= [group=resource =associations:store]
|
||||
=/ assocs=(list [=md-resource:store grp=resource =metadatum:store])
|
||||
~(tap by associations)
|
||||
=| cards=(list card)
|
||||
:- (send-diff %initial-group group associations)
|
||||
|-
|
||||
?~ assocs
|
||||
[cards state]
|
||||
state
|
||||
=, assocs
|
||||
?> =(group grp.i)
|
||||
=^ new-cards state
|
||||
=^ cards state
|
||||
(handle-add group [md-resource metadatum]:i)
|
||||
$(cards (weld cards new-cards), assocs t)
|
||||
$(assocs t)
|
||||
::
|
||||
++ metadata-for-app
|
||||
|= =app-name:store
|
||||
@ -428,13 +428,12 @@
|
||||
(~(put by out) md-resource [group metadatum])
|
||||
::
|
||||
++ send-diff
|
||||
|= [=app-name:store =update:store]
|
||||
|= =update:store
|
||||
^- (list card)
|
||||
|^
|
||||
%- zing
|
||||
:~ (update-subscribers /all update)
|
||||
(update-subscribers /updates update)
|
||||
(update-subscribers [%app-name app-name ~] update)
|
||||
==
|
||||
::
|
||||
++ update-subscribers
|
||||
|
103
pkg/arvo/lib/agentio.hoon
Normal file
103
pkg/arvo/lib/agentio.hoon
Normal file
@ -0,0 +1,103 @@
|
||||
=>
|
||||
|%
|
||||
++ card card:agent:gall
|
||||
--
|
||||
::
|
||||
|_ =bowl:gall
|
||||
++ scry
|
||||
|* [desk=@tas =path]
|
||||
?> ?=(^ path)
|
||||
?> ?=(^ t.path)
|
||||
%+ weld
|
||||
/(scot %p our.bowl)/[desk]/(scot %da now.bowl)
|
||||
t.t.path
|
||||
::
|
||||
++ pass
|
||||
|_ =wire
|
||||
++ poke
|
||||
|= [=dock =cage]
|
||||
[%pass wire %agent dock %poke cage]
|
||||
::
|
||||
++ poke-our
|
||||
|= [app=term =cage]
|
||||
^- card
|
||||
(poke [our.bowl app] cage)
|
||||
::
|
||||
++ arvo
|
||||
|= =note-arvo
|
||||
^- card
|
||||
[%pass wire %arvo note-arvo]
|
||||
::
|
||||
++ watch
|
||||
|= [=dock =path]
|
||||
[%pass (watch-wire path) %agent dock %watch path]
|
||||
::
|
||||
++ watch-our
|
||||
|= [app=term =path]
|
||||
(watch [our.bowl app] path)
|
||||
::
|
||||
++ watch-wire
|
||||
|= =path
|
||||
^+ wire
|
||||
?. ?=(~ wire)
|
||||
wire
|
||||
agentio-watch+path
|
||||
::
|
||||
++ leave
|
||||
|= =dock
|
||||
[%pass wire %agent dock %leave ~]
|
||||
::
|
||||
++ leave-our
|
||||
|= app=term
|
||||
(leave our.bowl app)
|
||||
::
|
||||
++ leave-path
|
||||
|= [=dock =path]
|
||||
=. wire
|
||||
(watch-wire path)
|
||||
(leave dock)
|
||||
::
|
||||
++ wait
|
||||
|= p=@da
|
||||
(arvo %b %wait p)
|
||||
::
|
||||
++ rest
|
||||
|= p=@da
|
||||
(arvo %b %wait p)
|
||||
::
|
||||
++ warp
|
||||
|= [wer=ship =riff:clay]
|
||||
(arvo %c %warp wer riff)
|
||||
::
|
||||
++ warp-our
|
||||
|= =riff:clay
|
||||
(warp our.bowl riff)
|
||||
::
|
||||
:: right here, right now
|
||||
++ warp-slim
|
||||
|= [genre=?(%sing %next) =care:clay =path]
|
||||
=/ =mood:clay
|
||||
[care r.byk.bowl path]
|
||||
=/ =rave:clay
|
||||
?:(?=(%sing genre) [genre mood] [genre mood])
|
||||
(warp-our q.byk.bowl `rave)
|
||||
--
|
||||
::
|
||||
++ fact-curry
|
||||
|* [=mark =mold]
|
||||
|= [paths=(list path) fac=mold]
|
||||
(fact mark^!>(fac) paths)
|
||||
::
|
||||
++ fact
|
||||
|= [=cage paths=(list path)]
|
||||
^- card
|
||||
[%give %fact paths cage]
|
||||
::
|
||||
++ kick
|
||||
|= paths=(list path)
|
||||
[%give %kick paths ~]
|
||||
::
|
||||
++ kick-only
|
||||
|= [=ship paths=(list path)]
|
||||
[%give %kick paths `ship]
|
||||
--
|
@ -1,265 +0,0 @@
|
||||
/- *contact-view, *contact-hook
|
||||
/+ group-store, resource
|
||||
|%
|
||||
++ nu :: parse number as hex
|
||||
|= jon=json
|
||||
?> ?=([%s *] jon)
|
||||
(rash p.jon hex)
|
||||
::
|
||||
++ hook-update-to-json
|
||||
|= upd=contact-hook-update
|
||||
=, enjs:format
|
||||
^- json
|
||||
%+ frond %contact-hook-update
|
||||
%- pairs
|
||||
%+ turn ~(tap by synced.upd)
|
||||
|= [pax=^path shp=^ship]
|
||||
^- [cord json]
|
||||
[(spat pax) s+(scot %p shp)]
|
||||
::
|
||||
++ rolodex-to-json
|
||||
|= rolo=rolodex
|
||||
=, enjs:format
|
||||
^- json
|
||||
%- pairs
|
||||
%+ turn ~(tap by rolo)
|
||||
|= [pax=^path =contacts]
|
||||
^- [cord json]
|
||||
:- (spat pax)
|
||||
(contacts-to-json pax contacts)
|
||||
::
|
||||
++ contacts-to-json
|
||||
|= [=path con=contacts]
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
%+ turn ~(tap by con)
|
||||
|= [=ship =contact]
|
||||
^- [cord json]
|
||||
[(crip (slag 1 (scow %p ship))) (contact-to-json path ship contact)]
|
||||
::
|
||||
++ contact-to-json
|
||||
|= [=path =ship con=contact]
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
:~ [%nickname s+nickname.con]
|
||||
[%email s+email.con]
|
||||
[%phone s+phone.con]
|
||||
[%website s+website.con]
|
||||
[%notes s+notes.con]
|
||||
[%color s+(scot %ux color.con)]
|
||||
[%avatar (avatar-to-json path ship avatar.con)]
|
||||
==
|
||||
::
|
||||
++ edit-to-json
|
||||
|= [=path =ship edit=edit-field]
|
||||
^- json
|
||||
%+ frond:enjs:format -.edit
|
||||
?- -.edit
|
||||
%nickname s+nickname.edit
|
||||
%email s+email.edit
|
||||
%phone s+phone.edit
|
||||
%website s+website.edit
|
||||
%notes s+notes.edit
|
||||
%color s+(scot %ux color.edit)
|
||||
%avatar (avatar-to-json path ship avatar.edit)
|
||||
==
|
||||
::
|
||||
++ avatar-to-json
|
||||
|= [=path =ship avat=(unit avatar)]
|
||||
^- json
|
||||
?~ avat ~
|
||||
?- -.u.avat
|
||||
%octt
|
||||
:- %s
|
||||
%- crip
|
||||
%- zing
|
||||
:~ "/contact-view"
|
||||
(trip (spat path))
|
||||
"/"
|
||||
(trip (scot %p ship))
|
||||
==
|
||||
::
|
||||
%url s+url.u.avat
|
||||
==
|
||||
::
|
||||
++ update-to-json
|
||||
|= upd=contact-update
|
||||
=, enjs:format
|
||||
^- json
|
||||
%+ frond %contact-update
|
||||
%- pairs
|
||||
:~
|
||||
?: ?=(%initial -.upd)
|
||||
[%initial (rolodex-to-json rolodex.upd)]
|
||||
?: ?=(%create -.upd)
|
||||
[%create (pairs [%path (path path.upd)]~)]
|
||||
?: ?=(%delete -.upd)
|
||||
[%delete (pairs [%path (path path.upd)]~)]
|
||||
?: ?=(%add -.upd)
|
||||
:- %add
|
||||
%- pairs
|
||||
:~ [%path (path path.upd)]
|
||||
[%ship (ship ship.upd)]
|
||||
[%contact (contact-to-json path.upd ship.upd contact.upd)]
|
||||
==
|
||||
?: ?=(%remove -.upd)
|
||||
:- %remove
|
||||
%- pairs
|
||||
:~ [%path (path path.upd)]
|
||||
[%ship (ship ship.upd)]
|
||||
==
|
||||
?: ?=(%edit -.upd)
|
||||
:- %edit
|
||||
%- pairs
|
||||
:~ [%path (path path.upd)]
|
||||
[%ship (ship ship.upd)]
|
||||
[%edit-field (edit-to-json path.upd ship.upd edit-field.upd)]
|
||||
==
|
||||
[*@t *^json]
|
||||
==
|
||||
::
|
||||
++ json-to-view-action
|
||||
|= jon=json
|
||||
^- contact-view-action
|
||||
=, dejs:format
|
||||
=< (parse-json jon)
|
||||
|%
|
||||
++ parse-json
|
||||
%- of
|
||||
:~ [%create create]
|
||||
[%delete delete]
|
||||
[%join dejs:resource]
|
||||
[%invite invite]
|
||||
[%remove remove]
|
||||
[%share share]
|
||||
==
|
||||
::
|
||||
++ create
|
||||
%- ot
|
||||
:~ [%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
|
||||
%- ot
|
||||
:~ [%path pa]
|
||||
[%ship (su ;~(pfix sig fed:ag))]
|
||||
==
|
||||
::
|
||||
++ share
|
||||
%- ot
|
||||
:~ [%recipient (su ;~(pfix sig fed:ag))]
|
||||
[%path pa]
|
||||
[%ship (su ;~(pfix sig fed:ag))]
|
||||
[%contact cont]
|
||||
==
|
||||
--
|
||||
::
|
||||
++ json-to-action
|
||||
|= jon=json
|
||||
^- contact-action
|
||||
=, dejs:format
|
||||
=< (parse-json jon)
|
||||
|%
|
||||
++ parse-json
|
||||
%- of
|
||||
:~ [%create create]
|
||||
[%delete delete]
|
||||
[%add add]
|
||||
[%remove remove]
|
||||
[%edit edit]
|
||||
==
|
||||
::
|
||||
++ create
|
||||
(ot [%path pa]~)
|
||||
::
|
||||
++ delete
|
||||
(ot [%path pa]~)
|
||||
::
|
||||
++ add
|
||||
%- ot
|
||||
:~ [%path pa]
|
||||
[%ship (su ;~(pfix sig fed:ag))]
|
||||
[%contact cont]
|
||||
==
|
||||
::
|
||||
++ remove
|
||||
%- ot
|
||||
:~ [%path pa]
|
||||
[%ship (su ;~(pfix sig fed:ag))]
|
||||
==
|
||||
::
|
||||
++ edit
|
||||
%- ot
|
||||
:~ [%path pa]
|
||||
[%ship (su ;~(pfix sig fed:ag))]
|
||||
[%edit-field edit-fi]
|
||||
==
|
||||
--
|
||||
::
|
||||
++ octet
|
||||
%- ot:dejs:format
|
||||
:~ [%p ni:dejs:format]
|
||||
[%q so:dejs:format]
|
||||
==
|
||||
::
|
||||
++ avat
|
||||
|= jon=json
|
||||
^- avatar
|
||||
|^
|
||||
=/ =avatar (parse-json jon)
|
||||
?- -.avatar
|
||||
%url avatar
|
||||
%octt
|
||||
=. octs.avatar (need (de:base64:mimes:html q.octs.avatar))
|
||||
avatar
|
||||
==
|
||||
::
|
||||
++ parse-json
|
||||
%- of:dejs:format
|
||||
:~ [%octt octt]
|
||||
[%url url]
|
||||
==
|
||||
::
|
||||
++ octt
|
||||
%- ot:dejs:format
|
||||
:~ [%content-type so:dejs:format]
|
||||
[%octs octet]
|
||||
==
|
||||
::
|
||||
++ url so:dejs:format
|
||||
--
|
||||
::
|
||||
++ cont
|
||||
%- ot:dejs:format
|
||||
:~ [%nickname so:dejs:format]
|
||||
[%email so:dejs:format]
|
||||
[%phone so:dejs:format]
|
||||
[%website so:dejs:format]
|
||||
[%notes so:dejs:format]
|
||||
[%color nu]
|
||||
[%avatar (mu:dejs:format avat)]
|
||||
==
|
||||
::
|
||||
++ edit-fi
|
||||
%- of:dejs:format
|
||||
:~ [%nickname so:dejs:format]
|
||||
[%email so:dejs:format]
|
||||
[%phone so:dejs:format]
|
||||
[%website so:dejs:format]
|
||||
[%notes so:dejs:format]
|
||||
[%color nu]
|
||||
[%avatar (mu:dejs:format avat)]
|
||||
==
|
||||
--
|
176
pkg/arvo/lib/contact-store.hoon
Normal file
176
pkg/arvo/lib/contact-store.hoon
Normal file
@ -0,0 +1,176 @@
|
||||
/- sur=contact-store
|
||||
/+ res=resource
|
||||
=< [sur .]
|
||||
=, sur
|
||||
|%
|
||||
++ nu :: parse number as hex
|
||||
|= jon=json
|
||||
?> ?=([%s *] jon)
|
||||
(rash p.jon hex)
|
||||
::
|
||||
++ enjs
|
||||
=, enjs:format
|
||||
|%
|
||||
++ update
|
||||
|= upd=^update
|
||||
^- json
|
||||
%+ frond %contact-update
|
||||
%- pairs
|
||||
:_ ~
|
||||
^- [cord json]
|
||||
?- -.upd
|
||||
%initial
|
||||
:- %initial
|
||||
%- pairs
|
||||
:~ [%rolodex (rolo rolodex.upd)]
|
||||
[%is-public b+is-public.upd]
|
||||
==
|
||||
::
|
||||
%add
|
||||
:- %add
|
||||
%- pairs
|
||||
:~ [%ship (ship ship.upd)]
|
||||
[%contact (cont contact.upd)]
|
||||
==
|
||||
::
|
||||
%remove
|
||||
:- %remove
|
||||
(pairs [%ship (ship ship.upd)]~)
|
||||
::
|
||||
%edit
|
||||
:- %edit
|
||||
%- pairs
|
||||
:~ [%ship (ship ship.upd)]
|
||||
[%edit-field (edit edit-field.upd)]
|
||||
==
|
||||
::
|
||||
%allow
|
||||
:- %allow
|
||||
(pairs [%beings (beng beings.upd)]~)
|
||||
::
|
||||
%disallow
|
||||
:- %disallow
|
||||
(pairs [%beings (beng beings.upd)]~)
|
||||
::
|
||||
%set-public
|
||||
[%set-public b+public.upd]
|
||||
==
|
||||
::
|
||||
++ rolo
|
||||
|= =rolodex
|
||||
^- json
|
||||
%- pairs
|
||||
%+ turn ~(tap by rolodex)
|
||||
|= [=^ship =contact]
|
||||
^- [cord json]
|
||||
[(scot %p ship) (cont contact)]
|
||||
::
|
||||
++ cont
|
||||
|= =contact
|
||||
^- json
|
||||
%- pairs
|
||||
:~ [%nickname s+nickname.contact]
|
||||
[%bio s+bio.contact]
|
||||
[%status s+status.contact]
|
||||
[%color s+(scot %ux color.contact)]
|
||||
[%avatar ?~(avatar.contact ~ s+u.avatar.contact)]
|
||||
[%cover ?~(cover.contact ~ s+u.cover.contact)]
|
||||
[%groups a+(turn ~(tap in groups.contact) |=(r=resource (enjs:res r)))]
|
||||
[%last-updated (time last-updated.contact)]
|
||||
==
|
||||
::
|
||||
++ edit
|
||||
|= field=edit-field
|
||||
^- json
|
||||
%+ frond -.field
|
||||
?- -.field
|
||||
%nickname s+nickname.field
|
||||
%bio s+bio.field
|
||||
%status s+status.field
|
||||
%color s+(scot %ux color.field)
|
||||
%avatar ?~(avatar.field ~ s+u.avatar.field)
|
||||
%cover ?~(cover.field ~ s+u.cover.field)
|
||||
%add-group (enjs:res resource.field)
|
||||
%remove-group (enjs:res resource.field)
|
||||
==
|
||||
::
|
||||
++ beng
|
||||
|= =beings
|
||||
^- json
|
||||
?- -.beings
|
||||
%ships [%a (turn ~(tap in ships.beings) |=(s=^ship s+(scot %p s)))]
|
||||
%group (enjs:res resource.beings)
|
||||
==
|
||||
--
|
||||
::
|
||||
++ dejs
|
||||
=, dejs:format
|
||||
|%
|
||||
++ update
|
||||
|= jon=json
|
||||
^- ^update
|
||||
=< (decode jon)
|
||||
|%
|
||||
++ decode
|
||||
%- of
|
||||
:~ [%initial initial]
|
||||
[%add add-contact]
|
||||
[%remove remove-contact]
|
||||
[%edit edit-contact]
|
||||
[%allow beings]
|
||||
[%disallow beings]
|
||||
[%set-public bo]
|
||||
==
|
||||
::
|
||||
++ initial
|
||||
%- ot
|
||||
:~ [%rolodex (op ;~(pfix sig fed:ag) cont)]
|
||||
[%is-public bo]
|
||||
==
|
||||
::
|
||||
++ add-contact
|
||||
%- ot
|
||||
:~ [%ship (su ;~(pfix sig fed:ag))]
|
||||
[%contact cont]
|
||||
==
|
||||
::
|
||||
++ remove-contact (ot [%ship (su ;~(pfix sig fed:ag))]~)
|
||||
::
|
||||
++ edit-contact
|
||||
%- ot
|
||||
:~ [%ship (su ;~(pfix sig fed:ag))]
|
||||
[%edit-field edit]
|
||||
==
|
||||
::
|
||||
++ beings
|
||||
%- of
|
||||
:~ [%ships (as (su ;~(pfix sig fed:ag)))]
|
||||
[%group dejs:res]
|
||||
==
|
||||
::
|
||||
++ cont
|
||||
%- ot
|
||||
:~ [%nickname so]
|
||||
[%bio so]
|
||||
[%status so]
|
||||
[%color nu]
|
||||
[%avatar (mu so)]
|
||||
[%cover (mu so)]
|
||||
[%groups (as dejs:res)]
|
||||
[%last-updated di]
|
||||
==
|
||||
::
|
||||
++ edit
|
||||
%- of
|
||||
:~ [%nickname so]
|
||||
[%bio so]
|
||||
[%status so]
|
||||
[%color nu]
|
||||
[%avatar (mu so)]
|
||||
[%cover (mu so)]
|
||||
[%add-group dejs:res]
|
||||
[%remove-group dejs:res]
|
||||
==
|
||||
--
|
||||
--
|
||||
--
|
34
pkg/arvo/lib/contact.hoon
Normal file
34
pkg/arvo/lib/contact.hoon
Normal file
@ -0,0 +1,34 @@
|
||||
/- store=contact-store, *resource
|
||||
/+ group
|
||||
|_ =bowl:gall
|
||||
++ scry-for
|
||||
|* [=mold =path]
|
||||
.^ mold
|
||||
%gx
|
||||
(scot %p our.bowl)
|
||||
%contact-store
|
||||
(scot %da now.bowl)
|
||||
(snoc `^path`path %noun)
|
||||
==
|
||||
::
|
||||
++ get-contact
|
||||
|= =ship
|
||||
^- (unit contact:store)
|
||||
=/ upd (scry-for (unit update:store) /contact/(scot %p ship))
|
||||
?~ upd ~
|
||||
?> ?=(%add -.u.upd)
|
||||
`contact.u.upd
|
||||
::
|
||||
++ is-allowed
|
||||
|= =ship
|
||||
^- ?
|
||||
=/ shp (scry-for ? /allowed-ship/(scot %p ship))
|
||||
?: shp %.y
|
||||
=/ allowed-groups ~(tap in (scry-for (set resource) /allowed-groups))
|
||||
=/ grp ~(. group bowl)
|
||||
|-
|
||||
?~ allowed-groups %.n
|
||||
?: (~(has in (members:grp i.allowed-groups)) ship)
|
||||
%.y
|
||||
$(allowed-groups t.allowed-groups)
|
||||
--
|
84
pkg/arvo/lib/group-view.hoon
Normal file
84
pkg/arvo/lib/group-view.hoon
Normal file
@ -0,0 +1,84 @@
|
||||
/- sur=group-view, spider
|
||||
/+ resource, strandio, metadata=metadata-store, store=group-store
|
||||
^?
|
||||
=< [. sur]
|
||||
=, sur
|
||||
|%
|
||||
++ dejs
|
||||
=, dejs:format
|
||||
|%
|
||||
++ action
|
||||
^- $-(json ^action)
|
||||
%- of
|
||||
:~ create+create
|
||||
remove+remove
|
||||
join+join
|
||||
leave+leave
|
||||
==
|
||||
::
|
||||
++ create
|
||||
%- ot
|
||||
:~ name+so
|
||||
policy+policy:dejs:store
|
||||
title+so
|
||||
description+so
|
||||
==
|
||||
::
|
||||
++ remove dejs:resource
|
||||
::
|
||||
++ leave dejs:resource
|
||||
::
|
||||
++ join
|
||||
%- ot
|
||||
:~ resource+dejs:resource
|
||||
ship+(su ;~(pfix sig fed:ag))
|
||||
==
|
||||
--
|
||||
::
|
||||
++ enjs
|
||||
=, enjs:format
|
||||
|%
|
||||
++ update
|
||||
|= upd=^update
|
||||
%+ frond %group-view-update
|
||||
%+ frond -.upd
|
||||
?- -.upd
|
||||
%initial (initial +.upd)
|
||||
%progress (progress +.upd)
|
||||
==
|
||||
::
|
||||
++ progress
|
||||
|= [rid=resource prog=^progress]
|
||||
%- pairs
|
||||
:~ resource+s+(enjs-path:resource rid)
|
||||
progress+s+prog
|
||||
==
|
||||
::
|
||||
++ initial
|
||||
|= init=(map resource ^progress)
|
||||
%- pairs
|
||||
%+ turn ~(tap by init)
|
||||
|= [rid=resource prog=^progress]
|
||||
:_ s+prog
|
||||
(enjs-path:resource rid)
|
||||
--
|
||||
++ cleanup-md
|
||||
|= rid=resource
|
||||
=/ m (strand:spider ,~)
|
||||
^- form:m
|
||||
;< =associations:metadata bind:m
|
||||
%+ scry:strandio associations:metadata
|
||||
%+ weld /gx/metadata-store/group
|
||||
(snoc (en-path:resource rid) %noun)
|
||||
~& associations
|
||||
=/ assocs=(list [=md-resource:metadata association:metadata])
|
||||
~(tap by associations)
|
||||
|-
|
||||
=* loop $
|
||||
?~ assocs
|
||||
(pure:m ~)
|
||||
;< ~ bind:m
|
||||
%+ poke-our:strandio %metadata-store
|
||||
metadata-action+!>([%remove rid md-resource.i.assocs])
|
||||
loop(assocs t.assocs)
|
||||
--
|
@ -91,6 +91,8 @@
|
||||
%herm
|
||||
%contact-store
|
||||
%contact-hook
|
||||
%contact-push-hook
|
||||
%contact-pull-hook
|
||||
%contact-view
|
||||
%metadata-store
|
||||
%s3-store
|
||||
@ -106,6 +108,7 @@
|
||||
%observe-hook
|
||||
%metadata-push-hook
|
||||
%metadata-pull-hook
|
||||
%group-view
|
||||
==
|
||||
::
|
||||
++ deft-fish :: default connects
|
||||
@ -251,6 +254,10 @@
|
||||
=> (se-born | %home %metadata-pull-hook)
|
||||
=> (se-born | %home %metadata-push-hook)
|
||||
(se-born | %home %herm)
|
||||
=? ..on-load (lte hood-version %12)
|
||||
=> (se-born | %home %contact-push-hook)
|
||||
=> (se-born | %home %contact-pull-hook)
|
||||
(se-born | %home %group-view)
|
||||
..on-load
|
||||
::
|
||||
++ reap-phat :: ack connect
|
||||
|
@ -7,6 +7,14 @@
|
||||
++ enjs
|
||||
=, enjs:format
|
||||
|%
|
||||
::
|
||||
++ initial-group
|
||||
|= [group=resource assocs=^associations]
|
||||
%- pairs
|
||||
:~ group+s+(enjs-path:resource group)
|
||||
associations+(associations assocs)
|
||||
==
|
||||
::
|
||||
++ associations
|
||||
|= =^associations
|
||||
=, enjs:format
|
||||
@ -49,7 +57,7 @@
|
||||
^- json
|
||||
%+ frond %metadata-update
|
||||
%- pairs
|
||||
:~ ?+ -.upd *[cord json]
|
||||
:~ ?- -.upd
|
||||
%add
|
||||
:- %add
|
||||
%- pairs
|
||||
@ -77,6 +85,9 @@
|
||||
::
|
||||
%associations
|
||||
[%associations (associations associations.upd)]
|
||||
::
|
||||
%initial-group
|
||||
[%initial-group (initial-group +.upd)]
|
||||
::
|
||||
== ==
|
||||
::
|
||||
@ -110,7 +121,7 @@
|
||||
::
|
||||
++ initial-group
|
||||
|= json
|
||||
[%initial-group *resource *associations]
|
||||
[*resource *associations]
|
||||
::
|
||||
++ add
|
||||
%- ot
|
||||
|
@ -386,18 +386,17 @@
|
||||
[%give %fact paths update-mark.config vase]~
|
||||
::
|
||||
++ forward-update
|
||||
|= =vase
|
||||
|= update=vase
|
||||
^- (list card:agent:gall)
|
||||
=/ rid=(unit resource)
|
||||
(resource-for-update vase)
|
||||
?~ rid ~
|
||||
=/ rid=resource
|
||||
(need (resource-for-update update))
|
||||
=/ =path
|
||||
resource+(en-path:resource u.rid)
|
||||
resource+(en-path:resource rid)
|
||||
=/ =wire
|
||||
(make-wire resource+(en-path:resource u.rid))
|
||||
(make-wire resource+(en-path:resource rid))
|
||||
=/ dap=term
|
||||
?:(=(our.bowl entity.u.rid) store-name.config dap.bowl)
|
||||
[%pass wire %agent [entity.u.rid dap] %poke update-mark.config vase]~
|
||||
?:(=(our.bowl entity.rid) store-name.config dap.bowl)
|
||||
[%pass wire %agent [entity.rid dap] %poke update-mark.config update]~
|
||||
::
|
||||
++ get-conversion
|
||||
.^ tube:clay
|
||||
@ -407,11 +406,13 @@
|
||||
::
|
||||
++ resource-for-update
|
||||
|= update=vase
|
||||
=/ =tube:clay
|
||||
get-conversion
|
||||
%+ bind
|
||||
(mole |.((tube update)))
|
||||
|=(=vase !<(resource vase))
|
||||
^- (unit resource)
|
||||
=/ converted=(each vase (list tank))
|
||||
(mule |.((get-conversion update)))
|
||||
?: ?=(%| -.converted)
|
||||
%- (slog p.converted)
|
||||
~
|
||||
[~ !<(resource p.converted)]
|
||||
::
|
||||
++ build-mark
|
||||
|= rav=?(%sing %next)
|
||||
|
@ -1,10 +0,0 @@
|
||||
/- *contact-hook
|
||||
|_ act=contact-hook-action
|
||||
++ grab |%
|
||||
++ noun contact-hook-action
|
||||
--
|
||||
++ grow |%
|
||||
++ noun act
|
||||
--
|
||||
++ grad %noun
|
||||
--
|
@ -1,15 +0,0 @@
|
||||
/+ *contact-json
|
||||
|_ act=contact-action
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun act
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun contact-action
|
||||
++ json
|
||||
|= jon=^json
|
||||
(json-to-action jon)
|
||||
--
|
||||
--
|
@ -1,15 +0,0 @@
|
||||
/+ *contact-json
|
||||
|_ upd=contact-hook-update
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun upd
|
||||
++ json (hook-update-to-json upd)
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
++ noun contact-hook-update
|
||||
--
|
||||
::
|
||||
--
|
@ -1,16 +0,0 @@
|
||||
/+ *contact-json
|
||||
|_ rolo=rolodex
|
||||
::
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun +<.grow
|
||||
++ json (rolodex-to-json rolo)
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
++ noun rolodex
|
||||
--
|
||||
::
|
||||
--
|
@ -1,15 +1,32 @@
|
||||
/+ *contact-json
|
||||
|_ upd=contact-update
|
||||
/+ *contact-store
|
||||
::
|
||||
|_ upd=update
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun upd
|
||||
++ json (update-to-json upd)
|
||||
++ json (update:enjs upd)
|
||||
++ resource
|
||||
|^
|
||||
?- -.upd
|
||||
%initial [nobody %contacts]
|
||||
%add [nobody %contacts]
|
||||
%remove [nobody %contacts]
|
||||
%edit [nobody %contacts]
|
||||
%allow !!
|
||||
%disallow !!
|
||||
%set-public !!
|
||||
==
|
||||
::
|
||||
++ nobody
|
||||
^- @p
|
||||
(bex 128)
|
||||
--
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
++ noun contact-update
|
||||
++ noun update
|
||||
++ json update:dejs
|
||||
--
|
||||
::
|
||||
--
|
||||
|
@ -1,12 +0,0 @@
|
||||
/- *contact-view
|
||||
|_ act=contact-view-action
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun act
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun contact-view-action
|
||||
--
|
||||
--
|
13
pkg/arvo/mar/group/view-action.hoon
Normal file
13
pkg/arvo/mar/group/view-action.hoon
Normal file
@ -0,0 +1,13 @@
|
||||
/+ view=group-view
|
||||
|_ =action:view
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun action
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun action:view
|
||||
++ json action:dejs:view
|
||||
--
|
||||
--
|
13
pkg/arvo/mar/group/view-update.hoon
Normal file
13
pkg/arvo/mar/group/view-update.hoon
Normal file
@ -0,0 +1,13 @@
|
||||
/+ view=group-view
|
||||
|_ =update:view
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun update
|
||||
++ json (update:enjs:view update)
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun update:view
|
||||
--
|
||||
--
|
@ -1,18 +0,0 @@
|
||||
|%
|
||||
+$ contact-hook-action
|
||||
$% :: %add-owned: make a contacts list accessible to foreign ships
|
||||
:: who are members of that list
|
||||
::
|
||||
[%add-owned =path]
|
||||
:: %add-synced: mirror a foreign contacts list to our contact-store
|
||||
::
|
||||
[%add-synced =ship =path]
|
||||
:: %remove: stop mirroring a foreign contacts list or stop allowing
|
||||
:: a local contacts list to be mirrored
|
||||
::
|
||||
[%remove =path]
|
||||
==
|
||||
::
|
||||
+$ synced (map path ship)
|
||||
+$ contact-hook-update [%initial =synced]
|
||||
--
|
@ -1,43 +1,40 @@
|
||||
/- *identity
|
||||
/- *resource
|
||||
|%
|
||||
+$ rolodex (map path contacts)
|
||||
+$ contacts (map ship contact)
|
||||
+$ avatar
|
||||
$% [%octt content-type=@t octs=[p=@ud q=@t]]
|
||||
[%url url=@t]
|
||||
==
|
||||
::
|
||||
+$ rolodex (map ship contact)
|
||||
+$ contact
|
||||
$: nickname=@t
|
||||
email=@t
|
||||
phone=@t
|
||||
website=@t
|
||||
notes=@t
|
||||
bio=@t
|
||||
status=@t
|
||||
color=@ux
|
||||
avatar=(unit avatar)
|
||||
avatar=(unit @t)
|
||||
cover=(unit @t)
|
||||
groups=(set resource)
|
||||
last-updated=@da
|
||||
==
|
||||
::
|
||||
+$ edit-field
|
||||
$% [%nickname nickname=@t]
|
||||
[%email email=@t]
|
||||
[%phone phone=@t]
|
||||
[%website website=@t]
|
||||
[%notes notes=@t]
|
||||
[%bio bio=@t]
|
||||
[%status status=@t]
|
||||
[%color color=@ux]
|
||||
[%avatar avatar=(unit avatar)]
|
||||
[%avatar avatar=(unit @t)]
|
||||
[%add-group =resource]
|
||||
[%remove-group =resource]
|
||||
[%cover cover=(unit @t)]
|
||||
==
|
||||
::
|
||||
+$ contact-action
|
||||
$% [%create =path]
|
||||
[%delete =path]
|
||||
[%add =path =ship =contact]
|
||||
[%remove =path =ship]
|
||||
[%edit =path =ship =edit-field]
|
||||
+$ beings
|
||||
$% [%ships ships=(set ship)]
|
||||
[%group =resource]
|
||||
==
|
||||
::
|
||||
+$ contact-update
|
||||
$% [%initial =rolodex]
|
||||
[%contacts =path =contacts]
|
||||
contact-action
|
||||
+$ update
|
||||
$% [%initial =rolodex is-public=?]
|
||||
[%add =ship =contact]
|
||||
[%remove =ship]
|
||||
[%edit =ship =edit-field]
|
||||
[%allow =beings]
|
||||
[%disallow =beings]
|
||||
[%set-public public=?]
|
||||
==
|
||||
--
|
||||
|
@ -1,27 +0,0 @@
|
||||
/- *contact-store, *group, *resource
|
||||
::
|
||||
|%
|
||||
+$ contact-view-action
|
||||
$% :: %create: create in both groups and contacts
|
||||
::
|
||||
[%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]
|
||||
:: %delete: delete in both groups and contacts
|
||||
::
|
||||
[%delete =path]
|
||||
:: %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]
|
||||
==
|
||||
--
|
25
pkg/arvo/sur/group-view.hoon
Normal file
25
pkg/arvo/sur/group-view.hoon
Normal file
@ -0,0 +1,25 @@
|
||||
/- *resource, *group
|
||||
^?
|
||||
|%
|
||||
::
|
||||
+$ action
|
||||
$% :: host side
|
||||
[%create name=term =policy title=@t description=@t]
|
||||
[%remove =resource]
|
||||
:: client side
|
||||
[%join =resource =ship]
|
||||
[%leave =resource]
|
||||
==
|
||||
|
||||
::
|
||||
+$ progress
|
||||
?(%start %added final)
|
||||
::
|
||||
+$ final
|
||||
?(%no-perms %strange %done)
|
||||
::
|
||||
+$ update
|
||||
$% [%initial initial=(map resource progress)]
|
||||
[%progress =resource =progress]
|
||||
==
|
||||
--
|
@ -1,9 +1,10 @@
|
||||
/- spider,
|
||||
graph=graph-store,
|
||||
*metadata-store,
|
||||
met=metadata-store,
|
||||
*group,
|
||||
group-store,
|
||||
inv=invite-store
|
||||
inv=invite-store,
|
||||
push-hook
|
||||
/+ strandio, resource, graph-view
|
||||
=>
|
||||
|%
|
||||
@ -16,13 +17,22 @@
|
||||
=/ m (strand ,resource)
|
||||
?: ?=(%group -.associated)
|
||||
(pure:m rid.associated)
|
||||
=/ =action:group-store
|
||||
[%add-group rid policy.associated %&]
|
||||
;< ~ bind:m (poke-our %group-store %group-action !>(action))
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
;< ~ bind:m (poke-our %group-store %group-action !>([%add-members rid (sy our.bowl ~)]))
|
||||
=/ push-hook-act=cage
|
||||
:- %push-hook-action
|
||||
!> ^- action:push-hook
|
||||
[%add rid]
|
||||
;< ~ bind:m
|
||||
(poke-our %metadata-push-hook push-hook-act)
|
||||
;< ~ bind:m
|
||||
(poke-our %group-push-hook %push-hook-action !>([%add rid]))
|
||||
%+ poke-our %group-store
|
||||
:- %group-update
|
||||
!> ^- update:group-store
|
||||
[%add-group rid policy.associated %.y]
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
;< ~ bind:m
|
||||
(poke-our %group-store group-update+!>([%add-members rid (sy our.bowl ~)]))
|
||||
;< ~ bind:m
|
||||
(poke-our %group-push-hook push-hook-act)
|
||||
(pure:m rid)
|
||||
--
|
||||
::
|
||||
@ -52,25 +62,22 @@
|
||||
::
|
||||
;< group=resource bind:m
|
||||
(handle-group rid.action associated.action)
|
||||
=/ group-path=path
|
||||
(en-path:resource group)
|
||||
::
|
||||
:: Setup metadata
|
||||
::
|
||||
=/ =metadata
|
||||
%* . *metadata
|
||||
=/ =metadatum:met
|
||||
%* . *metadatum:met
|
||||
title title.action
|
||||
description description.action
|
||||
date-created now.bowl
|
||||
creator our.bowl
|
||||
module module.action
|
||||
preview %.n
|
||||
==
|
||||
=/ =metadata-action
|
||||
[%add group graph+rid.action metadata]
|
||||
=/ met-action=action:met
|
||||
[%add group graph+rid.action metadatum]
|
||||
;< ~ bind:m
|
||||
(poke-our %metadata-store %metadata-action !>(metadata-action))
|
||||
;< ~ bind:m
|
||||
(poke-our %metadata-push-hook %push-hook-action !>([%add group]))
|
||||
(poke-our %metadata-push-hook metadata-update+!>(met-action))
|
||||
::
|
||||
:: Send invites
|
||||
::
|
||||
|
@ -1,4 +1,8 @@
|
||||
/- spider, graph-view, graph=graph-store, *metadata-store, *group
|
||||
<<<<<<< HEAD
|
||||
/- spider, graph-view, graph=graph-store, metadata=metadata-store, *group
|
||||
=======
|
||||
/- spider, graph-view, graph=graph-store, met=metadata-store, *group
|
||||
>>>>>>> origin/la/contact-store
|
||||
/+ strandio, resource
|
||||
=>
|
||||
|%
|
||||
@ -8,17 +12,23 @@
|
||||
::
|
||||
++ scry-metadata
|
||||
|= rid=resource
|
||||
<<<<<<< HEAD
|
||||
=/ m (strand ,resource)
|
||||
=======
|
||||
=/ m (strand ,(unit resource))
|
||||
;< paxs=(unit (set path)) bind:m
|
||||
%+ scry:strandio ,(unit (set path))
|
||||
>>>>>>> origin/la/contact-store
|
||||
;< group=(unit resource) bind:m
|
||||
%+ scry:strandio ,(unit resource)
|
||||
;: weld
|
||||
/gx/metadata-store/resource/graph
|
||||
(en-path:resource rid)
|
||||
/noun
|
||||
==
|
||||
?~ paxs (pure:m ~)
|
||||
?~ u.paxs (pure:m ~)
|
||||
(pure:m `(de-path:resource n.u.paxs))
|
||||
<<<<<<< HEAD
|
||||
(pure:m (need group))
|
||||
=======
|
||||
(pure:m group)
|
||||
>>>>>>> origin/la/contact-store
|
||||
::
|
||||
++ scry-group
|
||||
|= rid=resource
|
||||
@ -42,11 +52,10 @@
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-push-hook %push-hook-action !>([%remove rid]))
|
||||
;< ~ bind:m
|
||||
%+ poke-our %metadata-hook
|
||||
:- %metadata-action
|
||||
!> :+ %remove
|
||||
(en-path:resource group-rid)
|
||||
[%graph (en-path:resource rid)]
|
||||
%+ poke-our %metadata-push-hook
|
||||
:- %metadata-update
|
||||
!> ^- action:metadata
|
||||
[%remove group-rid [%graph rid]]
|
||||
(pure:m ~)
|
||||
--
|
||||
::
|
||||
@ -59,21 +68,15 @@
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
?. =(our.bowl entity.rid.action)
|
||||
(strand-fail:strandio %bad-request ~)
|
||||
;< ugroup-rid=(unit resource) bind:m
|
||||
;< group-rid=resource bind:m
|
||||
(scry-metadata rid.action)
|
||||
?~ ugroup-rid !!
|
||||
;< =group bind:m
|
||||
(scry-group u.ugroup-rid)
|
||||
(scry-group group-rid)
|
||||
;< ~ bind:m
|
||||
(delete-graph group-rid rid.action)
|
||||
?. hidden.group
|
||||
;< ~ bind:m
|
||||
(delete-graph u.ugroup-rid rid.action)
|
||||
(pure:m !>(~))
|
||||
;< ~ bind:m
|
||||
(poke-our %group-store %group-action !>([%remove-group rid.action ~]))
|
||||
;< ~ bind:m
|
||||
(poke-our %group-push-hook %push-hook-action !>([%remove rid.action]))
|
||||
;< ~ bind:m (delete-graph u.ugroup-rid rid.action)
|
||||
;< ~ bind:m
|
||||
%+ poke-our %metadata-hook
|
||||
metadata-hook-action+!>([%remove (en-path:resource u.ugroup-rid)])
|
||||
;< =thread-result:strandio bind:m
|
||||
(await-thread:strandio %group-delete !>(`[%remove rid.action]))
|
||||
(pure:m !>(~))
|
||||
|
||||
|
@ -19,42 +19,6 @@
|
||||
/noun
|
||||
==
|
||||
(pure:m res)
|
||||
::
|
||||
++ wait-for-group-join
|
||||
|= rid=resource
|
||||
=/ m (strand ,~)
|
||||
^- form:m
|
||||
=/ pax
|
||||
(en-path:resource rid)
|
||||
=/ hold=@dr ~s0..8000
|
||||
|- ^- form:m
|
||||
?> (lte hold ~m5)
|
||||
=* loop $
|
||||
;< u-group=(unit group) bind:m
|
||||
(scry:strandio ,(unit group) (weld /gx/group-store/groups (snoc pax %noun)))
|
||||
?^ u-group
|
||||
(pure:m ~)
|
||||
;< ~ bind:m (sleep:strandio hold)
|
||||
=. hold (mul hold 2)
|
||||
loop
|
||||
::
|
||||
++ wait-for-md
|
||||
|= rid=resource
|
||||
=/ m (strand ,~)
|
||||
^- form:m
|
||||
=/ pax
|
||||
(en-path:resource rid)
|
||||
=/ hold=@dr ~s0..8000
|
||||
|- ^- form:m
|
||||
?> (lte hold ~m5)
|
||||
=* loop $
|
||||
;< groups=(jug path md-resource) bind:m
|
||||
(scry:strandio ,(jug path md-resource) /gy/metadata-store/group-indices)
|
||||
?: (~(has by groups) pax)
|
||||
(pure:m ~)
|
||||
;< ~ bind:m (sleep:strandio hold)
|
||||
=. hold (mul hold 2)
|
||||
loop
|
||||
--
|
||||
::
|
||||
^- thread:spider
|
||||
@ -67,30 +31,10 @@
|
||||
?: =(our.bowl entity.rid.action)
|
||||
(fail %bad-request ~)
|
||||
;< group=(unit resource) bind:m (scry-metadata rid.action)
|
||||
?^ group
|
||||
:: We have group, graph is managed
|
||||
;< ~ bind:m
|
||||
%+ poke-our %graph-pull-hook
|
||||
pull-hook-action+!>([%add ship.action rid.action])
|
||||
(pure:m !>(~))
|
||||
:: Else, add group then join
|
||||
?> ?=(^ group)
|
||||
:: We have group, graph is managed
|
||||
;< ~ bind:m
|
||||
%+ (map-err:strandio ,~) |=(* [%forbidden ~])
|
||||
%+ poke
|
||||
[ship.action %group-push-hook]
|
||||
group-update+!>([%add-members rid.action (sy our.bowl ~)])
|
||||
::
|
||||
;< ~ bind:m
|
||||
%+ poke-our %group-pull-hook
|
||||
pull-hook-action+!>([%add ship.action rid.action])
|
||||
;< ~ bind:m (wait-for-group-join rid.action)
|
||||
::
|
||||
;< ~ bind:m
|
||||
%+ poke-our %metadata-pull-hook
|
||||
pull-hook-action+!>([%add ship.action rid.action])::
|
||||
;< ~ bind:m (wait-for-md rid.action)
|
||||
::
|
||||
;< ~ bind:m
|
||||
%+ poke-our %graph-pull-hook
|
||||
pull-hook-action+!>([%add ship.action rid.action])
|
||||
%+ poke-our %graph-pull-hook
|
||||
pull-hook-action+!>([%add ship.action rid.action])
|
||||
(pure:m !>(~))
|
||||
|
||||
|
@ -10,16 +10,14 @@
|
||||
|= rid=resource
|
||||
=/ m (strand ,resource)
|
||||
^- form:m
|
||||
;< pax=(unit (set path)) bind:m
|
||||
%+ scry:strandio ,(unit (set path))
|
||||
;< group=(unit resource) bind:m
|
||||
%+ scry:strandio ,(unit resource)
|
||||
;: weld
|
||||
/gx/metadata-store/resource/graph
|
||||
(en-path:resource rid)
|
||||
/noun
|
||||
==
|
||||
?> ?=(^ pax)
|
||||
?> ?=(^ u.pax)
|
||||
(pure:m (de-path:resource n.u.pax))
|
||||
(pure:m (need group))
|
||||
::
|
||||
++ scry-group
|
||||
|= rid=resource
|
||||
@ -56,21 +54,9 @@
|
||||
(strand-fail:strandio %bad-request ~)
|
||||
;< group-rid=resource bind:m (scry-metadata rid.action)
|
||||
;< g=group bind:m (scry-group group-rid)
|
||||
?. hidden.g
|
||||
;< ~ bind:m (delete-graph now.bowl rid.action)
|
||||
(pure:m !>(~))
|
||||
;< ~ bind:m
|
||||
%+ poke-our %metadata-hook
|
||||
metadata-hook-action+!>([%remove (en-path:resource rid.action)])
|
||||
;< ~ bind:m
|
||||
%+ poke-our %metadata-store
|
||||
:- %metadata-action
|
||||
!> :+ %remove
|
||||
(en-path:resource rid.action)
|
||||
[%graph (en-path:resource rid.action)]
|
||||
;< ~ bind:m
|
||||
(poke-our %group-store %group-action !>([%remove-group rid.action ~]))
|
||||
;< ~ bind:m
|
||||
(poke-our %group-pull-hook %pull-hook-action !>([%remove rid.action]))
|
||||
;< ~ bind:m (delete-graph now.bowl rid.action)
|
||||
?. hidden.g
|
||||
(pure:m !>(~))
|
||||
;< =thread-result:strandio bind:m
|
||||
(await-thread:strandio %group-leave !>([~ [%leave rid.action]]))
|
||||
(pure:m !>(~))
|
||||
|
50
pkg/arvo/ted/group/create.hoon
Normal file
50
pkg/arvo/ted/group/create.hoon
Normal file
@ -0,0 +1,50 @@
|
||||
/- spider,
|
||||
metadata=metadata-store,
|
||||
*group,
|
||||
inv=invite-store,
|
||||
store=group-store,
|
||||
push-hook
|
||||
/+ strandio, resource, view=group-view
|
||||
=>
|
||||
|%
|
||||
++ strand strand:spider
|
||||
++ poke poke:strandio
|
||||
++ poke-our poke-our:strandio
|
||||
--
|
||||
^- thread:spider
|
||||
|= arg=vase
|
||||
=/ m (strand ,vase)
|
||||
^- form:m
|
||||
=+ !<([~ =action:view] arg)
|
||||
?> ?=(%create -.action)
|
||||
?> ((sane %tas) name.action)
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
:: Add graph to graph-store
|
||||
::
|
||||
=/ rid=resource
|
||||
[our.bowl name.action]
|
||||
=/ group-act=action:store
|
||||
[%add-group rid policy.action %.n]
|
||||
;< ~ bind:m (poke-our %group-store %group-action !>(group-act))
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
;< ~ bind:m (poke-our %group-store %group-action !>([%add-members rid (sy our.bowl ~)]))
|
||||
=/ push-hook-act=cage
|
||||
:- %push-hook-action
|
||||
!> ^- action:push-hook
|
||||
[%add rid]
|
||||
;< ~ bind:m
|
||||
(poke-our %group-push-hook push-hook-act)
|
||||
=/ =metadatum:metadata
|
||||
%* . *metadatum:metadata
|
||||
title title.action
|
||||
description description.action
|
||||
date-created now.bowl
|
||||
creator our.bowl
|
||||
==
|
||||
=/ met-action=action:metadata
|
||||
[%add rid groups+rid metadatum]
|
||||
;< ~ bind:m (poke-our %metadata-store %metadata-action !>(met-action))
|
||||
;< ~ bind:m (poke-our %metadata-push-hook push-hook-act)
|
||||
(pure:m !>(~))
|
||||
|
||||
|
30
pkg/arvo/ted/group/delete.hoon
Normal file
30
pkg/arvo/ted/group/delete.hoon
Normal file
@ -0,0 +1,30 @@
|
||||
/- spider,
|
||||
metadata=metadata-store,
|
||||
*group,
|
||||
inv=invite-store,
|
||||
store=group-store,
|
||||
push-hook
|
||||
/+ strandio, resource, view=group-view
|
||||
=>
|
||||
|%
|
||||
++ strand strand:spider
|
||||
++ poke poke:strandio
|
||||
++ poke-our poke-our:strandio
|
||||
--
|
||||
^- thread:spider
|
||||
|= arg=vase
|
||||
=/ m (strand ,vase)
|
||||
=+ !<([~ =action:view] arg)
|
||||
?> ?=(%remove -.action)
|
||||
=* rid resource.action
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
?> =(our.bowl entity.rid)
|
||||
=/ push-hook-act=cage
|
||||
:- %push-hook-action
|
||||
!> ^- action:push-hook
|
||||
[%remove resource.action]
|
||||
;< ~ bind:m (cleanup-md:view rid)
|
||||
;< ~ bind:m (poke-our %group-store %group-update !>([%remove-group rid ~]))
|
||||
;< ~ bind:m (poke-our %metadata-push-hook push-hook-act)
|
||||
;< ~ bind:m (poke-our %group-push-hook push-hook-act)
|
||||
(pure:m !>(~))
|
29
pkg/arvo/ted/group/leave.hoon
Normal file
29
pkg/arvo/ted/group/leave.hoon
Normal file
@ -0,0 +1,29 @@
|
||||
/- spider,
|
||||
metadata=metadata-store,
|
||||
*group,
|
||||
inv=invite-store,
|
||||
store=group-store,
|
||||
pull-hook
|
||||
/+ strandio, resource, view=group-view
|
||||
=>
|
||||
|%
|
||||
++ strand strand:spider
|
||||
++ poke poke:strandio
|
||||
++ poke-our poke-our:strandio
|
||||
--
|
||||
^- thread:spider
|
||||
|= arg=vase
|
||||
=/ m (strand ,vase)
|
||||
=+ !<([~ =action:view] arg)
|
||||
?> ?=(%leave -.action)
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
=* rid resource.action
|
||||
=/ pull-hook-act=cage
|
||||
:- %pull-hook-action
|
||||
!> ^- action:pull-hook
|
||||
[%remove rid]
|
||||
;< ~ bind:m (poke-our %metadata-pull-hook pull-hook-act)
|
||||
;< ~ bind:m (poke-our %group-pull-hook pull-hook-act)
|
||||
;< ~ bind:m (poke-our %group-store %group-update !>([%remove-group rid ~]))
|
||||
;< ~ bind:m (cleanup-md:view rid)
|
||||
(pure:m !>(~))
|
@ -1,4 +1,4 @@
|
||||
/- spider, grp=group-store, gra=graph-store, met=metadata-store, con=contact-store
|
||||
/- spider, grp=group-store, gra=graph-store, met=metadata-store
|
||||
/+ strandio, res=resource
|
||||
::
|
||||
=* strand strand:spider
|
||||
@ -34,21 +34,6 @@
|
||||
[our.bowl %group-pull-hook]
|
||||
:- %pull-hook-action
|
||||
!>([%remove resource.update])
|
||||
:: stop serving or syncing contacts associated with group
|
||||
::
|
||||
;< ~ bind:m
|
||||
%+ raw-poke
|
||||
[our.bowl %contact-hook]
|
||||
:- %contact-hook-action
|
||||
!>([%remove (en-path:res resource.update)])
|
||||
:: remove contact data associated with group
|
||||
::
|
||||
;< ~ bind:m
|
||||
%+ raw-poke
|
||||
[our.bowl %contact-store]
|
||||
:- %contact-action
|
||||
!> ^- contact-action:con
|
||||
[%delete (en-path:res resource.update)]
|
||||
:: stop serving or syncing metadata associated with group
|
||||
::
|
||||
;< ~ bind:m
|
||||
@ -65,7 +50,7 @@
|
||||
(en-path:res resource.update)
|
||||
/noun
|
||||
==
|
||||
=/ entries=(list [m=md-resource:met g=resource:res =metadata:met])
|
||||
=/ entries=(list [m=md-resource:met g=resource:res *])
|
||||
~(tap by associations)
|
||||
|- ^- form:m
|
||||
=* loop $
|
||||
@ -77,7 +62,7 @@
|
||||
%+ raw-poke
|
||||
[our.bowl %metadata-store]
|
||||
:- %metadata-action
|
||||
!> ^- metadata-action:met
|
||||
!> ^- action:met
|
||||
[%remove g.i.entries m.i.entries]
|
||||
:: archive graph associated with group
|
||||
::
|
||||
|
51
pkg/interface/package-lock.json
generated
51
pkg/interface/package-lock.json
generated
@ -10150,6 +10150,57 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
||||
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"remove-trailing-separator": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
|
||||
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
|
||||
"dev": true
|
||||
},
|
||||
"braces": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
||||
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arr-flatten": "^1.1.0",
|
||||
"array-unique": "^0.3.2",
|
||||
"extend-shallow": "^2.0.1",
|
||||
"fill-range": "^4.0.0",
|
||||
"isobject": "^3.0.1",
|
||||
"repeat-element": "^1.1.2",
|
||||
"snapdragon": "^0.8.1",
|
||||
"snapdragon-node": "^2.0.1",
|
||||
"split-string": "^3.0.2",
|
||||
"to-regex": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
||||
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "^2.0.0",
|
||||
"async-each": "^1.0.1",
|
||||
"braces": "^2.3.2",
|
||||
"fsevents": "^1.2.7",
|
||||
"glob-parent": "^3.1.0",
|
||||
"inherits": "^2.0.3",
|
||||
"is-binary-path": "^1.0.0",
|
||||
"is-glob": "^4.0.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"readdirp": "^2.2.1",
|
||||
"upath": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
|
@ -5,74 +5,50 @@ import { Contact, ContactEdit } from '~/types/contact-update';
|
||||
import { GroupPolicy, Resource } from '~/types/group-update';
|
||||
|
||||
export default class ContactsApi extends BaseApi<StoreState> {
|
||||
create(
|
||||
name: string,
|
||||
policy: Enc<GroupPolicy>,
|
||||
title: string,
|
||||
description: string
|
||||
) {
|
||||
return this.viewAction({
|
||||
create: {
|
||||
name,
|
||||
policy,
|
||||
title,
|
||||
description,
|
||||
},
|
||||
});
|
||||
add(ship: Patp, contact: any) {
|
||||
return this.storeAction({ add: { ship, contact } });
|
||||
}
|
||||
|
||||
share(recipient: Patp, path: Patp, ship: Patp, contact: Contact) {
|
||||
return this.viewAction({
|
||||
share: {
|
||||
recipient,
|
||||
path,
|
||||
ship,
|
||||
contact,
|
||||
},
|
||||
});
|
||||
remove(ship: Patp) {
|
||||
return this.storeAction({ remove: { ship } });
|
||||
}
|
||||
|
||||
remove(path: Path, ship: Patp) {
|
||||
return this.viewAction({ remove: { path, ship } });
|
||||
}
|
||||
|
||||
edit(path: Path, ship: Patp, editField: ContactEdit) {
|
||||
edit(ship: Patp, editField: ContactEdit) {
|
||||
/* editField can be...
|
||||
{nickname: ''}
|
||||
{email: ''}
|
||||
{phone: ''}
|
||||
{website: ''}
|
||||
{notes: ''}
|
||||
{color: 'fff'} // with no 0x prefix
|
||||
{avatar: null}
|
||||
{avatar: {url: ''}}
|
||||
{avatar: ''}
|
||||
{add-group: {ship, name}}
|
||||
{remove-group: {ship, name}}
|
||||
*/
|
||||
return this.hookAction({
|
||||
console.log(ship, editField);
|
||||
return this.storeAction({
|
||||
edit: {
|
||||
path,
|
||||
ship,
|
||||
'edit-field': editField,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
invite(resource: Resource, ship: Patp, text = '') {
|
||||
return this.viewAction({
|
||||
invite: { resource, ship, text },
|
||||
setPublic(setPublic: any) {
|
||||
return this.storeAction({
|
||||
'set-public': setPublic
|
||||
});
|
||||
}
|
||||
|
||||
join(resource: Resource) {
|
||||
return this.viewAction({
|
||||
join: resource,
|
||||
});
|
||||
private storeAction(action: any): Promise<any> {
|
||||
return this.action('contact-store', 'contact-update', action)
|
||||
}
|
||||
|
||||
private hookAction(data) {
|
||||
return this.action('contact-hook', 'contact-action', data);
|
||||
private viewAction(threadName: string, action: any) {
|
||||
return this.spider('contact-view-action', 'json', threadName, action);
|
||||
}
|
||||
|
||||
private viewAction(data) {
|
||||
return this.action('contact-view', 'json', data);
|
||||
private hookAction(ship: Patp, action: any): Promise<any> {
|
||||
return this.action('contact-push-hook', 'contact-update', action);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
Tag,
|
||||
GroupPolicyDiff,
|
||||
} from '~/types/group-update';
|
||||
import {makeResource} from '../lib/group';
|
||||
|
||||
export default class GroupsApi extends BaseApi<StoreState> {
|
||||
remove(resource: Resource, ships: Patp[]) {
|
||||
@ -34,12 +35,52 @@ export default class GroupsApi extends BaseApi<StoreState> {
|
||||
return this.proxyAction({ changePolicy: { resource, diff } });
|
||||
}
|
||||
|
||||
join(ship: string, name: string) {
|
||||
const resource = makeResource(ship, name);
|
||||
|
||||
return this.viewAction({ join: { resource, ship }});
|
||||
}
|
||||
|
||||
create(name: string, policy: Enc<GroupPolicy>, title: string, description: string) {
|
||||
return this.viewThread('group-create', {
|
||||
create: {
|
||||
name,
|
||||
policy,
|
||||
title,
|
||||
description
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteGroup(ship: string, name: string) {
|
||||
const resource = makeResource(ship, name);
|
||||
|
||||
return this.viewThread('group-delete', {
|
||||
remove: resource
|
||||
});
|
||||
}
|
||||
|
||||
leaveGroup(ship: string, name: string) {
|
||||
const resource = makeResource(ship, name);
|
||||
return this.viewThread('group-leave', {
|
||||
leave: resource
|
||||
});
|
||||
}
|
||||
|
||||
private proxyAction(action: GroupAction) {
|
||||
return this.action('group-push-hook', 'group-update', action);
|
||||
}
|
||||
|
||||
private storeAction(action: GroupAction) {
|
||||
console.log(action);
|
||||
return this.action('group-store', 'group-update', action);
|
||||
}
|
||||
|
||||
private viewThread(thread: string, action: any) {
|
||||
return this.spider('group-view-action', 'json', thread, action);
|
||||
}
|
||||
|
||||
private viewAction(action: any) {
|
||||
return this.action('group-view', 'group-view-action', action);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ export default class MetadataApi extends BaseApi<StoreState> {
|
||||
done = true;
|
||||
tempChannel.delete();
|
||||
reject(new Error("offline"))
|
||||
}, 30000);
|
||||
}, 15000);
|
||||
|
||||
tempChannel.subscribe(window.ship, "metadata-pull-hook", `/preview${group}`,
|
||||
(err) => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { cite } from '~/logic/lib/util';
|
||||
|
||||
const indexes = new Map([
|
||||
['ships', []],
|
||||
['commands', []],
|
||||
['subscriptions', []],
|
||||
['groups', []],
|
||||
@ -18,6 +19,14 @@ const result = function(title, link, app, host) {
|
||||
};
|
||||
};
|
||||
|
||||
const shipIndex = function(contacts) {
|
||||
const ships = [];
|
||||
Object.keys(contacts).map((e) => {
|
||||
return ships.push(result(e, `/~profile/${e}`, 'profile', contacts[e]?.status));
|
||||
});
|
||||
return ships;
|
||||
};
|
||||
|
||||
const commandIndex = function (currentGroup) {
|
||||
// commands are special cased for default suite
|
||||
const commands = [];
|
||||
@ -62,7 +71,8 @@ const otherIndex = function() {
|
||||
return other;
|
||||
};
|
||||
|
||||
export default function index(associations, apps, currentGroup, groups) {
|
||||
export default function index(contacts, associations, apps, currentGroup, groups) {
|
||||
indexes.set('ships', shipIndex(contacts));
|
||||
// all metadata from all apps is indexed
|
||||
// into subscriptions and landscape
|
||||
const subscriptions = [];
|
||||
@ -106,7 +116,7 @@ export default function index(associations, apps, currentGroup, groups) {
|
||||
title,
|
||||
`/~landscape${group}/join/${app}${each.resource}`,
|
||||
app.charAt(0).toUpperCase() + app.slice(1),
|
||||
(associations?.contacts?.[each.group]?.metadata?.title || null)
|
||||
(associations?.groups?.[each.group]?.metadata?.title || null)
|
||||
);
|
||||
subscriptions.push(obj);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ export function getTitleFromWorkspace(
|
||||
case "home":
|
||||
return "DMs + Drafts";
|
||||
case "group":
|
||||
const association = associations.contacts[workspace.group];
|
||||
const association = associations.groups[workspace.group];
|
||||
return association?.metadata?.title || "";
|
||||
}
|
||||
}
|
||||
|
@ -5,74 +5,60 @@ import { ContactUpdate } from '~/types/contact-update';
|
||||
|
||||
type ContactState = Pick<StoreState, 'contacts'>;
|
||||
|
||||
export default class ContactReducer<S extends ContactState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'contact-update', false);
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
this.create(data, state);
|
||||
this.delete(data, state);
|
||||
this.add(data, state);
|
||||
this.remove(data, state);
|
||||
this.edit(data, state);
|
||||
}
|
||||
export const ContactReducer = (json, state) => {
|
||||
const data = _.get(json, 'contact-update', false);
|
||||
if (data) {
|
||||
initial(data, state);
|
||||
add(data, state);
|
||||
remove(data, state);
|
||||
edit(data, state);
|
||||
setPublic(data, state);
|
||||
}
|
||||
};
|
||||
|
||||
initial(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.contacts = data;
|
||||
}
|
||||
const initial = (json: ContactUpdate, state: S) => {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.contacts = data.rolodex;
|
||||
state.isContactPublic = data['is-public'];
|
||||
}
|
||||
};
|
||||
|
||||
create(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.contacts[data.path] = {};
|
||||
}
|
||||
const add = (json: ContactUpdate, state: S) => {
|
||||
const data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
state.contacts[data.ship] = data.contact;
|
||||
}
|
||||
};
|
||||
|
||||
delete(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.contacts[data.path];
|
||||
}
|
||||
const remove = (json: ContactUpdate, state: S) => {
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (
|
||||
data &&
|
||||
(data.ship in state.contacts)
|
||||
) {
|
||||
delete state.contacts[data.ship];
|
||||
}
|
||||
};
|
||||
|
||||
add(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'add', false);
|
||||
if (
|
||||
data &&
|
||||
(data.path in state.contacts)
|
||||
) {
|
||||
state.contacts[data.path][data.ship] = data.contact;
|
||||
const edit = (json: ContactUpdate, state: S) => {
|
||||
const data = _.get(json, 'edit', false);
|
||||
const ship = `~${data.ship}`;
|
||||
if (
|
||||
data &&
|
||||
(ship in state.contacts)
|
||||
) {
|
||||
const edit = Object.keys(data['edit-field']);
|
||||
if (edit.length !== 1) {
|
||||
return;
|
||||
}
|
||||
state.contacts[ship][edit[0]] = data['edit-field'][edit[0]];
|
||||
}
|
||||
};
|
||||
|
||||
const setPublic = (json: ContactUpdate, state: S) => {
|
||||
const data = _.get(json, 'set-public', state.isContactPublic);
|
||||
state.isContactPublic = data;
|
||||
};
|
||||
|
||||
remove(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (
|
||||
data &&
|
||||
(data.path in state.contacts) &&
|
||||
(data.ship in state.contacts[data.path])
|
||||
) {
|
||||
delete state.contacts[data.path][data.ship];
|
||||
}
|
||||
}
|
||||
|
||||
edit(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'edit', false);
|
||||
if (
|
||||
data &&
|
||||
(data.path in state.contacts) &&
|
||||
(data.ship in state.contacts[data.path])
|
||||
) {
|
||||
const edit = Object.keys(data['edit-field']);
|
||||
if (edit.length !== 1) {
|
||||
return;
|
||||
}
|
||||
state.contacts[data.path][data.ship][edit[0]] =
|
||||
data['edit-field'][edit[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
pkg/interface/src/logic/reducers/group-view.ts
Normal file
30
pkg/interface/src/logic/reducers/group-view.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { resourceAsPath } from "~/logic/lib/util";
|
||||
|
||||
|
||||
const initial = (json: any, state: any) => {
|
||||
const data = json.initial;
|
||||
if(data) {
|
||||
state.pendingJoin = data;
|
||||
}
|
||||
}
|
||||
|
||||
const progress = (json: any, state: any) => {
|
||||
const data = json.progress;
|
||||
if(data) {
|
||||
const { progress, resource } = data;
|
||||
state.pendingJoin = {...state.pendingJoin, [resource]: progress };
|
||||
if(progress === 'done') {
|
||||
setTimeout(() => {
|
||||
delete state.pendingJoin[resource];
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const GroupViewReducer = (json: any, state: any) => {
|
||||
const data = json['group-view-update'];
|
||||
if(data) {
|
||||
progress(data, state);
|
||||
initial(data, state);
|
||||
}
|
||||
}
|
@ -16,6 +16,15 @@ export default class MetadataReducer<S extends MetadataState> {
|
||||
this.add(data, state);
|
||||
this.update(data, state);
|
||||
this.remove(data, state);
|
||||
this.groupInitial(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
groupInitial(json: MetadataUpdate, state: S) {
|
||||
const data = _.get(json, 'initial-group', false);
|
||||
console.log(data);
|
||||
if(data) {
|
||||
this.associations(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,23 +8,23 @@ import LocalReducer from '../reducers/local';
|
||||
import { StoreState } from './type';
|
||||
import { Timebox } from '~/types';
|
||||
import { Cage } from '~/types/cage';
|
||||
import ContactReducer from '../reducers/contact-update';
|
||||
import S3Reducer from '../reducers/s3-update';
|
||||
import { GraphReducer } from '../reducers/graph-update';
|
||||
import { HarkReducer } from '../reducers/hark-update';
|
||||
import { ContactReducer } from '../reducers/contact-update';
|
||||
import GroupReducer from '../reducers/group-update';
|
||||
import LaunchReducer from '../reducers/launch-update';
|
||||
import ConnectionReducer from '../reducers/connection';
|
||||
import SettingsReducer from '../reducers/settings-update';
|
||||
import {OrderedMap} from '../lib/OrderedMap';
|
||||
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
|
||||
import {GroupViewReducer} from '../reducers/group-view';
|
||||
|
||||
|
||||
export default class GlobalStore extends BaseStore<StoreState> {
|
||||
inviteReducer = new InviteReducer();
|
||||
metadataReducer = new MetadataReducer();
|
||||
localReducer = new LocalReducer();
|
||||
contactReducer = new ContactReducer();
|
||||
s3Reducer = new S3Reducer();
|
||||
groupReducer = new GroupReducer();
|
||||
launchReducer = new LaunchReducer();
|
||||
@ -57,7 +57,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
baseHash: null,
|
||||
invites: {},
|
||||
associations: {
|
||||
contacts: {},
|
||||
groups: {},
|
||||
graph: {},
|
||||
},
|
||||
groups: {},
|
||||
@ -78,6 +78,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
},
|
||||
credentials: null
|
||||
},
|
||||
isContactPublic: false,
|
||||
contacts: {},
|
||||
notifications: new BigIntOrderedMap<Timebox>(),
|
||||
archivedNotifications: new BigIntOrderedMap<Timebox>(),
|
||||
@ -93,7 +94,8 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
group: {}
|
||||
},
|
||||
notificationsCount: 0,
|
||||
settings: {}
|
||||
settings: {},
|
||||
pendingJoin: {},
|
||||
};
|
||||
}
|
||||
|
||||
@ -106,13 +108,14 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
this.inviteReducer.reduce(data, this.state);
|
||||
this.metadataReducer.reduce(data, this.state);
|
||||
this.localReducer.reduce(data, this.state);
|
||||
this.contactReducer.reduce(data, this.state);
|
||||
this.s3Reducer.reduce(data, this.state);
|
||||
this.groupReducer.reduce(data, this.state);
|
||||
this.launchReducer.reduce(data, this.state);
|
||||
this.connReducer.reduce(data, this.state);
|
||||
GraphReducer(data, this.state);
|
||||
HarkReducer(data, this.state);
|
||||
ContactReducer(data, this.state);
|
||||
this.settingsReducer.reduce(data, this.state);
|
||||
GroupViewReducer(data, this.state);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ import {
|
||||
Notifications,
|
||||
NotificationGraphConfig,
|
||||
GroupNotificationsConfig,
|
||||
Unreads
|
||||
Unreads,
|
||||
JoinRequests
|
||||
} from "~/types";
|
||||
|
||||
export interface StoreState {
|
||||
@ -46,5 +47,5 @@ export interface StoreState {
|
||||
notificationsCount: number,
|
||||
unreads: Unreads;
|
||||
doNotDisturb: boolean;
|
||||
unreads: Unreads;
|
||||
pendingJoin: JoinRequests;
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import _ from 'lodash';
|
||||
type AppSubscription = [Path, string];
|
||||
|
||||
const groupSubscriptions: AppSubscription[] = [
|
||||
['/synced', 'contact-hook']
|
||||
];
|
||||
|
||||
const graphSubscriptions: AppSubscription[] = [
|
||||
@ -37,14 +36,15 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
|
||||
this.subscribe('/groups', 'group-store');
|
||||
this.clearQueue();
|
||||
|
||||
|
||||
this.subscribe('/primary', 'contact-view');
|
||||
// TODO: update to get /updates
|
||||
this.subscribe('/all', 'contact-store');
|
||||
this.subscribe('/all', 's3-store');
|
||||
this.subscribe('/keys', 'graph-store');
|
||||
this.subscribe('/updates', 'hark-store');
|
||||
this.subscribe('/updates', 'hark-graph-hook');
|
||||
this.subscribe('/updates', 'hark-group-hook');
|
||||
this.subscribe('/all', 'settings-store');
|
||||
this.subscribe('/all', 'group-view');
|
||||
}
|
||||
|
||||
restart() {
|
||||
|
11
pkg/interface/src/types/group-view.ts
Normal file
11
pkg/interface/src/types/group-view.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export const joinError = ['no-perms', 'strange'] as const;
|
||||
export type JoinError = typeof joinError[number];
|
||||
export const joinResult = ['done', ...joinError] as const;
|
||||
export type JoinResult = typeof joinResult[number];
|
||||
|
||||
export const joinProgress = ['start', 'added', ...joinResult] as const;
|
||||
export type JoinProgress = typeof joinProgress[number];
|
||||
|
||||
export interface JoinRequests {
|
||||
[rid: string]: JoinProgress;
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
export * from './cage';
|
||||
export * from './chat-hook-update';
|
||||
export * from './chat-update';
|
||||
export * from './connection';
|
||||
export * from './contact-update';
|
||||
export * from './global';
|
||||
export * from './group-update';
|
||||
export * from './group-view';
|
||||
export * from './graph-update';
|
||||
export * from './hark-update';
|
||||
export * from './invite-update';
|
||||
|
@ -18,7 +18,7 @@ export type Serial = string;
|
||||
export type Jug<K,V> = Map<K,Set<V>>;
|
||||
|
||||
// name of app
|
||||
export type AppName = 'chat' | 'link' | 'contacts' | 'publish' | 'graph';
|
||||
export type AppName = 'contacts' | 'groups' | 'graph';
|
||||
|
||||
export function getTagFromFrond<O>(frond: O): keyof O {
|
||||
const tags = Object.keys(frond) as Array<keyof O>;
|
||||
|
@ -138,9 +138,7 @@ class App extends React.Component {
|
||||
|
||||
const notificationsCount = state.notificationsCount || 0;
|
||||
const doNotDisturb = state.doNotDisturb || false;
|
||||
|
||||
const showBanner = localStorage.getItem("2020BreachBanner") || "flex";
|
||||
let banner = null;
|
||||
const ourContact = this.state.contacts[`~${this.ship}`] || null;
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
@ -156,6 +154,7 @@ class App extends React.Component {
|
||||
props={this.props}
|
||||
associations={associations}
|
||||
invites={this.state.invites}
|
||||
ourContact={ourContact}
|
||||
api={this.api}
|
||||
connection={this.state.connection}
|
||||
subscription={this.subscription}
|
||||
@ -169,6 +168,7 @@ class App extends React.Component {
|
||||
associations={state.associations}
|
||||
apps={state.launch}
|
||||
api={this.api}
|
||||
contacts={state.contacts}
|
||||
notifications={state.notificationsCount}
|
||||
invites={state.invites}
|
||||
groups={state.groups}
|
||||
|
@ -24,7 +24,7 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
const station = props.association.resource;
|
||||
const groupPath = props.association.group;
|
||||
const group = props.groups[groupPath];
|
||||
const contacts = props.contacts[groupPath] || {};
|
||||
const contacts = props.contacts;
|
||||
|
||||
const graph = props.graphs[station.slice(7)];
|
||||
|
||||
@ -33,7 +33,7 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
const unreadCount = props.unreads.graph?.[station]?.['/']?.unreads || 0;
|
||||
|
||||
const [,, owner, name] = station.split('/');
|
||||
const ourContact = contacts?.[window.ship];
|
||||
const ourContact = contacts?.[`~${window.ship}`];
|
||||
|
||||
const chatInput = useRef<ChatInput>();
|
||||
|
||||
|
@ -178,7 +178,7 @@ export const MessageWithSigil = (props) => {
|
||||
const dark = useLocalState(state => state.dark);
|
||||
|
||||
const datestamp = moment.unix(msg['time-sent'] / 1000).format(DATESTAMP_FORMAT);
|
||||
const contact = msg.author in contacts ? contacts[msg.author] : false;
|
||||
const contact = `~${msg.author}` in contacts ? contacts[`~${msg.author}`] : false;
|
||||
const showNickname = useShowNickname(contact);
|
||||
const name = showNickname ? contact.nickname : cite(msg.author);
|
||||
const color = contact ? `#${uxToHex(contact.color)}` : dark ? '#000000' :'#FFFFFF'
|
||||
|
@ -16,7 +16,7 @@ const sortGroupsAlph = (a: Association, b: Association) =>
|
||||
alphabeticalOrder(a.metadata.title, b.metadata.title);
|
||||
|
||||
|
||||
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string) =>
|
||||
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string) =>
|
||||
f.flow(
|
||||
f.pickBy((a: Association) => a.group === path),
|
||||
f.map('resource'),
|
||||
@ -24,7 +24,7 @@ const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path:
|
||||
f.reduce(f.add, 0)
|
||||
)(associations.graph);
|
||||
|
||||
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
|
||||
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
|
||||
f.flow(
|
||||
f.pickBy((a: Association) => a.group === path),
|
||||
f.map('resource'),
|
||||
@ -36,7 +36,7 @@ const getGraphNotifications = (associations: Associations, unreads: Unreads) =>
|
||||
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||
const { associations, unreads, inbox, ...boxProps } = props;
|
||||
|
||||
const groups = Object.values(associations?.contacts || {})
|
||||
const groups = Object.values(associations?.groups || {})
|
||||
.filter((e) => e?.group in props.groups)
|
||||
.sort(sortGroupsAlph);
|
||||
const graphUnreads = getGraphUnreads(associations || {}, unreads);
|
||||
@ -78,10 +78,10 @@ function Group(props: GroupProps) {
|
||||
<Col height="100%" justifyContent="space-between">
|
||||
<Text>{title}</Text>
|
||||
<Col>
|
||||
{unreads > 0 &&
|
||||
{unreads > 0 &&
|
||||
(<Text gray>{unreads} unread{unreads !== 1 && 's'} </Text>)
|
||||
}
|
||||
{updates > 0 &&
|
||||
{updates > 0 &&
|
||||
(<Text mt="1" color="blue">{updates} update{updates !== 1 && 's'} </Text>)
|
||||
}
|
||||
</Col>
|
||||
|
@ -48,7 +48,6 @@ export function LinkWindow(props: LinkWindowProps) {
|
||||
}, [graph.size]);
|
||||
|
||||
const first = graph.peekLargest()?.[0];
|
||||
|
||||
const [,,ship, name] = association.resource.split('/');
|
||||
|
||||
const style = useMemo(() =>
|
||||
|
@ -63,7 +63,7 @@ export function Header(props: {
|
||||
|
||||
const time = moment(props.time).format("HH:mm");
|
||||
const groupTitle =
|
||||
props.associations.contacts?.[props.group]?.metadata?.title;
|
||||
props.associations.groups?.[props.group]?.metadata?.title;
|
||||
|
||||
const app = props.chat ? 'chat' : 'graph';
|
||||
const channelTitle =
|
||||
|
@ -1,20 +1,20 @@
|
||||
import React, { useEffect, useCallback, useRef, useState } from "react";
|
||||
import f from "lodash/fp";
|
||||
import _ from "lodash";
|
||||
import { Icon, Col, Row, Box, Text, Anchor, Rule, Center } from "@tlon/indigo-react";
|
||||
import { Icon, Col, Center, Row, Box, Text, Anchor, Rule } from "@tlon/indigo-react";
|
||||
import moment from "moment";
|
||||
import { Notifications, Rolodex, Timebox, IndexedNotification, Groups, GroupNotificationsConfig, NotificationGraphConfig } from "~/types";
|
||||
import { Notifications, Rolodex, Timebox, IndexedNotification, Groups, joinProgress, JoinRequests, GroupNotificationsConfig, NotificationGraphConfig } from "~/types";
|
||||
import { MOMENT_CALENDAR_DATE, daToUnix, resourceAsPath } from "~/logic/lib/util";
|
||||
import { BigInteger } from "big-integer";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Notification } from "./notification";
|
||||
import { Associations } from "~/types";
|
||||
import { cite } from '~/logic/lib/util';
|
||||
import { InviteItem } from '~/views/components/Invite';
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {useModal} from "~/logic/lib/useModal";
|
||||
import {JoinGroup} from "~/views/landscape/components/JoinGroup";
|
||||
import {JoiningStatus} from "./joining";
|
||||
import {Invites} from "./invites";
|
||||
import {useLazyScroll} from "~/logic/lib/useLazyScroll";
|
||||
|
||||
@ -46,6 +46,7 @@ export default function Inbox(props: {
|
||||
contacts: Rolodex;
|
||||
filter: string[];
|
||||
invites: any;
|
||||
pendingJoin: JoinRequests;
|
||||
notificationsGroupConfig: GroupNotificationsConfig;
|
||||
notificationsGraphConfig: NotificationGraphConfig;
|
||||
}) {
|
||||
@ -98,67 +99,6 @@ export default function Inbox(props: {
|
||||
|
||||
const scrollRef = useRef(null);
|
||||
|
||||
const [joining, setJoining] = useState<[string, string] | null>(null);
|
||||
|
||||
const { modal, showModal } = useModal(
|
||||
{ modal: useCallback(
|
||||
(dismiss) => (
|
||||
<JoinGroup
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
api={props.api}
|
||||
autojoin={joining?.[0]?.slice(6)}
|
||||
inviteUid={joining?.[1]}
|
||||
/>
|
||||
),
|
||||
[props.contacts, props.groups, props.api, joining]
|
||||
)})
|
||||
|
||||
const joinGroup = useCallback((group: string, uid: string) => {
|
||||
setJoining([group, uid]);
|
||||
showModal();
|
||||
}, [setJoining, showModal]);
|
||||
|
||||
const acceptInvite = (app: string, uid: string) => async (invite) => {
|
||||
const resource = {
|
||||
ship: `~${invite.resource.ship}`,
|
||||
name: invite.resource.name
|
||||
};
|
||||
|
||||
const resourcePath = resourceAsPath(invite.resource);
|
||||
if(app === 'contacts') {
|
||||
joinGroup(resourcePath, uid);
|
||||
} else if ( app === 'chat') {
|
||||
await api.invite.accept(app, uid);
|
||||
history.push(`/~landscape/home/resource/chat${resourcePath.slice(5)}`);
|
||||
} else if ( app === 'graph') {
|
||||
await api.invite.accept(app, uid);
|
||||
history.push(`/~graph/join${resourcePath}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const inviteItems = (invites, api) => {
|
||||
const returned = [];
|
||||
Object.keys(invites).map((appKey) => {
|
||||
const app = invites[appKey];
|
||||
Object.keys(app).map((uid) => {
|
||||
const invite = app[uid];
|
||||
const inviteItem =
|
||||
<InviteItem
|
||||
key={uid}
|
||||
invite={invite}
|
||||
onAccept={acceptInvite(appKey, uid)}
|
||||
onDecline={() => api.invite.decline(appKey, uid)}
|
||||
/>;
|
||||
returned.push(inviteItem);
|
||||
});
|
||||
});
|
||||
return returned;
|
||||
};
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
return api.hark.getMore();
|
||||
}, [api]);
|
||||
@ -168,8 +108,7 @@ export default function Inbox(props: {
|
||||
|
||||
return (
|
||||
<Col ref={scrollRef} position="relative" height="100%" overflowY="auto">
|
||||
{modal}
|
||||
<Invites invites={invites} api={api} associations={associations} />
|
||||
<Invites groups={props.groups} pendingJoin={props.pendingJoin} invites={invites} api={api} associations={associations} />
|
||||
{[...notificationsByDayMap.keys()].sort().reverse().map((day, index) => {
|
||||
const timeboxes = notificationsByDayMap.get(day)!;
|
||||
return timeboxes.length > 0 && (
|
||||
|
@ -1,43 +1,34 @@
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { Box, Row, Col } from "@tlon/indigo-react";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Invites as IInvites, Associations, Invite } from "~/types";
|
||||
import { Invites as IInvites, Associations, Invite, JoinRequests, Groups } from "~/types";
|
||||
import { resourceAsPath } from "~/logic/lib/util";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import InviteItem from "~/views/components/Invite";
|
||||
import {JoiningStatus} from "./joining";
|
||||
import {useModal} from "~/logic/lib/useModal";
|
||||
import {JoinGroup} from "~/views/landscape/components/JoinGroup";
|
||||
|
||||
interface InvitesProps {
|
||||
api: GlobalApi;
|
||||
invites: IInvites;
|
||||
groups: Groups;
|
||||
associations: Associations;
|
||||
pendingJoin: JoinRequests;
|
||||
}
|
||||
|
||||
export function Invites(props: InvitesProps) {
|
||||
const { api, invites } = props;
|
||||
const history = useHistory();
|
||||
const waiter = useWaitForProps(props);
|
||||
const { api, invites, pendingJoin } = props;
|
||||
const [selected, setSelected] = useState<[string, string, Invite] | undefined>()
|
||||
|
||||
const acceptInvite = (
|
||||
app: string,
|
||||
uid: string,
|
||||
invite: Invite
|
||||
) => async () => {
|
||||
const resource = {
|
||||
ship: `~${invite.resource.ship}`,
|
||||
name: invite.resource.name,
|
||||
};
|
||||
|
||||
const resourcePath = resourceAsPath(invite.resource);
|
||||
if (app === "contacts") {
|
||||
await api.contacts.join(resource);
|
||||
await waiter((p) => resourcePath in p.associations?.contacts);
|
||||
await api.invite.accept(app, uid);
|
||||
history.push(`/~landscape${resourcePath}`);
|
||||
} else if (app === "graph") {
|
||||
await api.invite.accept(app, uid);
|
||||
history.push(`/~graph/join${resourcePath}`);
|
||||
}
|
||||
setSelected([app, uid, invite]);
|
||||
showModal();
|
||||
};
|
||||
|
||||
const declineInvite = useCallback(
|
||||
@ -45,6 +36,22 @@ export function Invites(props: InvitesProps) {
|
||||
[api]
|
||||
);
|
||||
|
||||
const { modal, showModal } = useModal({ modal: () => {
|
||||
const [app, uid, invite] = selected!;
|
||||
const autojoin = `~${invite.resource.ship}/${invite.resource.name}`;
|
||||
return (
|
||||
<JoinGroup
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
api={api}
|
||||
autojoin={autojoin}
|
||||
inviteUid={uid}
|
||||
inviteApp={app}
|
||||
/>
|
||||
)}});
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Col
|
||||
zIndex={4}
|
||||
@ -54,6 +61,18 @@ export function Invites(props: InvitesProps) {
|
||||
position="sticky"
|
||||
flexShrink={0}
|
||||
>
|
||||
{modal}
|
||||
{ Object
|
||||
.keys(props.pendingJoin)
|
||||
.map(resource => (
|
||||
<JoiningStatus
|
||||
key={resource}
|
||||
resource={resource}
|
||||
status={pendingJoin[resource]}
|
||||
api={api} />
|
||||
))
|
||||
}
|
||||
|
||||
{Object.keys(invites).reduce((items, appKey) => {
|
||||
const app = invites[appKey];
|
||||
let appItems = Object.keys(app).map((uid) => {
|
||||
|
49
pkg/interface/src/views/apps/notifications/joining.tsx
Normal file
49
pkg/interface/src/views/apps/notifications/joining.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Col, Text, SegmentedProgressBar } from "@tlon/indigo-react";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { JoinProgress, joinProgress, MetadataUpdatePreview, joinError } from "~/types";
|
||||
import { clamp } from "~/logic/lib/util";
|
||||
|
||||
interface JoiningStatusProps {
|
||||
resource: string;
|
||||
api: GlobalApi;
|
||||
status: JoinProgress;
|
||||
}
|
||||
|
||||
const description: string[] =
|
||||
["Attempting to contact group host",
|
||||
"Retrieving group data",
|
||||
"Finished join",
|
||||
"Unable to join group, you do not have the correct permissions",
|
||||
"Internal error, please file an issue"
|
||||
];
|
||||
|
||||
|
||||
|
||||
export function JoiningStatus(props: JoiningStatusProps) {
|
||||
const { resource, status, api } = props;
|
||||
|
||||
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const prev = await api.metadata.preview(resource);
|
||||
setPreview(prev)
|
||||
})();
|
||||
return () => {
|
||||
setPreview(null);
|
||||
}
|
||||
}, [resource])
|
||||
|
||||
const current = joinProgress.indexOf(status);
|
||||
const desc = description?.[current] || "";
|
||||
const title = preview?.metadata?.title ?? resource;
|
||||
const isError = joinError.indexOf(status as any) !== -1;
|
||||
return (
|
||||
<Col py="3" mx="5" gapY="2">
|
||||
<Text fontSize="1">{isError ? "Failed to join " : "Joining "} {title}</Text>
|
||||
<Text color={isError ? "red" : "gray"}>{desc}</Text>
|
||||
<SegmentedProgressBar current={current + 1} segments={3} />
|
||||
</Col>
|
||||
);
|
||||
}
|
@ -44,7 +44,7 @@ export default function NotificationsScreen(props: any) {
|
||||
filter.groups.length === 0
|
||||
? "All"
|
||||
: filter.groups
|
||||
.map((g) => props.associations?.contacts?.[g]?.metadata?.title)
|
||||
.map((g) => props.associations?.groups?.[g]?.metadata?.title)
|
||||
.join(", ");
|
||||
return (
|
||||
<Switch>
|
||||
|
@ -135,6 +135,7 @@ export function ContactCard(props: ContactCardProps) {
|
||||
gridTemplateColumns="100%"
|
||||
gridRowGap="5"
|
||||
maxWidth="400px"
|
||||
width="100%"
|
||||
>
|
||||
<Row
|
||||
borderBottom={1}
|
125
pkg/interface/src/views/apps/profile/components/EditProfile.tsx
Normal file
125
pkg/interface/src/views/apps/profile/components/EditProfile.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import React from "react";
|
||||
import * as Yup from "yup";
|
||||
|
||||
import {
|
||||
ManagedForm as Form,
|
||||
ManagedTextInputField as Input,
|
||||
ManagedCheckboxField as Checkbox,
|
||||
Center,
|
||||
Col,
|
||||
Box,
|
||||
Text,
|
||||
Row,
|
||||
Button,
|
||||
} from "@tlon/indigo-react";
|
||||
import { Formik, FormikHelpers } from "formik";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import { ColorInput } from "~/views/components/ColorInput";
|
||||
import { ImageInput } from "~/views/components/ImageInput";
|
||||
import { MarkdownField } from "~/views/apps/publish/components/MarkdownField";
|
||||
import { resourceFromPath } from "~/logic/lib/group";
|
||||
import GroupSearch from "~/views/components/GroupSearch";
|
||||
|
||||
|
||||
const formSchema = Yup.object({
|
||||
nickname: Yup.string(),
|
||||
bio: Yup.string(),
|
||||
color: Yup.string(),
|
||||
avatar: Yup.string().nullable()
|
||||
});
|
||||
|
||||
const emptyContact = {
|
||||
nickname: '',
|
||||
bio: '',
|
||||
status: '',
|
||||
color: '0',
|
||||
avatar: null,
|
||||
cover: null,
|
||||
groups: [],
|
||||
'last-updated': 0,
|
||||
isPublic: false
|
||||
};
|
||||
|
||||
|
||||
export function EditProfile(props: any) {
|
||||
const { contact, ship, api, isPublic } = props;
|
||||
const history = useHistory();
|
||||
if (contact) {
|
||||
contact.isPublic = isPublic;
|
||||
}
|
||||
|
||||
const onSubmit = async (values: any, actions: any) => {
|
||||
console.log(values);
|
||||
try {
|
||||
await Object.keys(values).reduce((acc, key) => {
|
||||
console.log(key);
|
||||
const newValue = key !== "color" ? values[key] : uxToHex(values[key]);
|
||||
|
||||
if (newValue !== contact[key]) {
|
||||
if (key === "isPublic") {
|
||||
return acc.then(() =>
|
||||
api.contacts.setPublic(newValue)
|
||||
);
|
||||
} else if (key === 'groups') {
|
||||
newValue.map((e) => {
|
||||
if (!contact['groups']?.[e]) {
|
||||
return acc.then(() => {
|
||||
api.contacts.edit(ship, { 'add-group': resourceFromPath(e) });
|
||||
});
|
||||
}
|
||||
})
|
||||
} else if (
|
||||
key !== "last-updated" &&
|
||||
key !== "isPublic"
|
||||
) {
|
||||
return acc.then(() =>
|
||||
api.contacts.edit(ship, { [key]: newValue })
|
||||
);
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, Promise.resolve());
|
||||
//actions.setStatus({ success: null });
|
||||
history.push(`/~profile/${ship}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: e.message });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={contact || emptyContact}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form width="100%" height="100%" p={2}>
|
||||
<Input id="nickname" label="Name" mb={3} />
|
||||
<Col width="100%">
|
||||
<Text mb={2}>Description</Text>
|
||||
<MarkdownField id="bio" mb={3} s3={props.s3} />
|
||||
</Col>
|
||||
<ColorInput id="color" label="Sigil Color" mb={3} />
|
||||
<Row mb={3} width="100%">
|
||||
<Col pr={2} width="50%">
|
||||
<ImageInput id="cover" label="Cover Image" s3={props.s3} />
|
||||
</Col>
|
||||
<Col pl={2} width="50%">
|
||||
<ImageInput id="avatar" label="Profile Image" s3={props.s3} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Checkbox mb={3} id="isPublic" label="Public Profile" />
|
||||
<GroupSearch label="Pinned Groups" id="groups" groups={props.groups} associations={props.associations} />
|
||||
<AsyncButton primary loadingText="Updating..." border mt={3}>
|
||||
Submit
|
||||
</AsyncButton>
|
||||
</Form>
|
||||
</Formik>
|
||||
</>
|
||||
);
|
||||
}
|
78
pkg/interface/src/views/apps/profile/components/Profile.tsx
Normal file
78
pkg/interface/src/views/apps/profile/components/Profile.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import React from "react";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import { ViewProfile } from './ViewProfile';
|
||||
import { EditProfile } from './EditProfile';
|
||||
import { SetStatus } from './SetStatus';
|
||||
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
import {
|
||||
Center,
|
||||
Box,
|
||||
Row,
|
||||
BaseImage,
|
||||
StatelessTextInput as Input,
|
||||
Button
|
||||
} from "@tlon/indigo-react";
|
||||
import useLocalState from "~/logic/state/local";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
export function Profile(props: any) {
|
||||
const { hideAvatars } = useLocalState(({ hideAvatars }) => ({
|
||||
hideAvatars
|
||||
}));
|
||||
if (!props.ship) {
|
||||
return null;
|
||||
}
|
||||
const { contact, isPublic, isEdit, ship } = props;
|
||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : "#000000";
|
||||
const cover = (contact?.cover)
|
||||
? <BaseImage src={contact.cover} width='100%' height='100%' style={{ objectFit: 'cover' }} />
|
||||
: <Box display="block" width='100%' height='100%' backgroundColor='washedGray' />;
|
||||
|
||||
const image = (!hideAvatars && contact?.avatar)
|
||||
? <BaseImage src={contact.avatar} width='100%' height='100%' style={{ objectFit: 'cover' }} />
|
||||
: <Sigil ship={ship} size={96} color={hexColor} />;
|
||||
|
||||
return (
|
||||
<Center
|
||||
p={4}
|
||||
height="100%"
|
||||
width="100%">
|
||||
<Box
|
||||
maxWidth="600px"
|
||||
width="100%">
|
||||
{ ship === `~${window.ship}` ? (
|
||||
<SetStatus ship={ship} contact={contact} api={props.api} />
|
||||
) : null
|
||||
}
|
||||
<Row width="100%" height="300px">
|
||||
{cover}
|
||||
</Row>
|
||||
<Row
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
width="100%"
|
||||
>
|
||||
<Center width="100%" marginTop="-48px">
|
||||
<Box height='96px' width='96px' borderRadius="2" overflow="hidden">
|
||||
{image}
|
||||
</Box>
|
||||
</Center>
|
||||
</Row>
|
||||
{ isEdit ? (
|
||||
<EditProfile
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
s3={props.s3}
|
||||
api={props.api}
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
isPublic={isPublic}/>
|
||||
) : (
|
||||
<ViewProfile ship={ship} contact={contact} isPublic={isPublic} />
|
||||
) }
|
||||
</Box>
|
||||
</Center>
|
||||
);
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import React, {
|
||||
useState,
|
||||
useCallback,
|
||||
useEffect,
|
||||
ChangeEvent
|
||||
} from "react";
|
||||
|
||||
import {
|
||||
Row,
|
||||
Button,
|
||||
StatelessTextInput as Input,
|
||||
} from "@tlon/indigo-react";
|
||||
|
||||
|
||||
export function SetStatus(props: any) {
|
||||
const { contact, ship, api, callback } = props;
|
||||
const [_status, setStatus] = useState('');
|
||||
const onStatusChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
setStatus(e.target.value);
|
||||
},
|
||||
[setStatus]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setStatus(!!contact ? contact.status : '');
|
||||
}, [contact]);
|
||||
|
||||
const editStatus = () => {
|
||||
api.contacts.edit(ship, {status: _status});
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Row width="100%" my={3}>
|
||||
<Input
|
||||
onChange={onStatusChange}
|
||||
value={_status}
|
||||
autocomplete="off"
|
||||
width="75%"
|
||||
mr={2}
|
||||
onKeyPress={(evt) => {
|
||||
if (evt.key === 'Enter') {
|
||||
editStatus();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
backgroundColor="black"
|
||||
color="white"
|
||||
ml={2}
|
||||
width="25%"
|
||||
onClick={editStatus}>
|
||||
Set Status
|
||||
</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import React from "react";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
|
||||
import {
|
||||
Center,
|
||||
Box,
|
||||
Text,
|
||||
Row,
|
||||
Button,
|
||||
Col
|
||||
} from "@tlon/indigo-react";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import RichText from "~/views/components/RichText";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
export function ViewProfile(props: any) {
|
||||
const history = useHistory();
|
||||
const { contact, isPublic, ship } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
width="100%">
|
||||
<Center width="100%">
|
||||
<Text>
|
||||
{(contact?.nickname ? contact.nickname : "")}
|
||||
</Text>
|
||||
</Center>
|
||||
</Row>
|
||||
<Row
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
width="100%">
|
||||
<Center width="100%">
|
||||
<Text mono color="darkGray">{ship}</Text>
|
||||
</Center>
|
||||
</Row>
|
||||
<Col
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
width="100%">
|
||||
<Center flexDirection="column" maxWidth='32rem'>
|
||||
<RichText width='100%'>
|
||||
{(contact?.bio ? contact.bio : "")}
|
||||
</RichText>
|
||||
</Center>
|
||||
</Col>
|
||||
{ (ship === `~${window.ship}`) ? (
|
||||
<Row
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
width="100%">
|
||||
<Center width="100%">
|
||||
<Button
|
||||
backgroundColor="black"
|
||||
color="white"
|
||||
onClick={() => {history.push(`/~profile/${ship}/edit`)}}>
|
||||
Edit Profile
|
||||
</Button>
|
||||
</Center>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
{ !isPublic && ship === `~${window.ship}` ? (
|
||||
<Box
|
||||
height="200px"
|
||||
borderRadius={1}
|
||||
bg="white"
|
||||
border={1}
|
||||
borderColor="washedGray">
|
||||
<Center height="100%">
|
||||
<Text mono pr={1} color="gray">{ship}</Text>
|
||||
<Text color="gray">remains private</Text>
|
||||
</Center>
|
||||
</Box>
|
||||
) : null
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,149 +1,64 @@
|
||||
import React from "react";
|
||||
import { Route, Link, Switch } from "react-router-dom";
|
||||
import { Route, Link } from "react-router-dom";
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import { Box, Text, Row, Col, Icon, BaseImage } from "@tlon/indigo-react";
|
||||
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import { uxToHex, MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
|
||||
import Settings from "./components/settings";
|
||||
import { ContactCard } from "~/views/landscape/components/ContactCard";
|
||||
import { Profile } from "./components/Profile";
|
||||
import useLocalState from "~/logic/state/local";
|
||||
|
||||
const SidebarItem = ({ children, view, current }) => {
|
||||
const selected = current === view;
|
||||
const icon = (view) => {
|
||||
switch(view) {
|
||||
case 'identity':
|
||||
return 'Smiley';
|
||||
case 'settings':
|
||||
return 'Adjust';
|
||||
default:
|
||||
return 'Circle'
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Link to={`/~profile/${view}`}>
|
||||
<Row
|
||||
alignItems="center"
|
||||
verticalAlign="middle"
|
||||
py={1}
|
||||
px={3}
|
||||
backgroundColor={selected ? "washedGray" : "white"}
|
||||
>
|
||||
<Icon mr={2} display="inline-block" icon={icon(view)} color='black' />
|
||||
<Text color='black'>
|
||||
{children}
|
||||
</Text>
|
||||
</Row>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default function ProfileScreen(props: any) {
|
||||
const { ship, dark } = props;
|
||||
const { dark } = props;
|
||||
const hideAvatars = useLocalState(state => state.hideAvatars);
|
||||
return (
|
||||
<>
|
||||
<Helmet defer={false}>
|
||||
<title>{ props.notificationsCount ? `(${String(props.notificationsCount) }) `: '' }Landscape - Profile</title>
|
||||
</Helmet>
|
||||
<Switch>
|
||||
<Route
|
||||
path={["/~profile/:view", "/~profile"]}
|
||||
path={"/~profile/:ship/:edit?"}
|
||||
render={({ match, history }) => {
|
||||
const { view } = match.params;
|
||||
const contact = props.contacts?.["/~/default"]?.[window.ship];
|
||||
const ship = match.params.ship;
|
||||
const isEdit = match.url.includes('edit');
|
||||
const isPublic = props.isContactPublic;
|
||||
const contact = props.contacts?.[ship];
|
||||
const sigilColor = contact?.color
|
||||
? `#${uxToHex(contact.color)}`
|
||||
: dark
|
||||
? "#FFFFFF"
|
||||
: "#000000";
|
||||
if(!contact) {
|
||||
return null;
|
||||
}
|
||||
if (!view && !MOBILE_BROWSER_REGEX.test(window.navigator.userAgent)) {
|
||||
history.replace("/~profile/identity");
|
||||
}
|
||||
|
||||
const image = (!hideAvatars && contact?.avatar)
|
||||
? <BaseImage src={contact.avatar} width='100%' height='100%' style={{ objectFit: 'cover' }} />
|
||||
: <Sigil ship={`~${ship}`} size={80} color={sigilColor} />;
|
||||
return (
|
||||
<Box height="100%" px={[0, 3]} pb={[0, 3]} borderRadius={1}>
|
||||
<Box
|
||||
height="100%"
|
||||
width="100%"
|
||||
display="grid"
|
||||
gridTemplateColumns={["100%", "250px 1fr"]}
|
||||
gridTemplateRows={["48px 1fr", "1fr"]}
|
||||
borderRadius={1}
|
||||
bg="white"
|
||||
border={1}
|
||||
borderColor="washedGray"
|
||||
overflowY="auto"
|
||||
flexGrow
|
||||
>
|
||||
<Col
|
||||
display={!view ? "flex" : ["none", "flex"]}
|
||||
alignItems="center"
|
||||
borderRight={1}
|
||||
borderColor="washedGray"
|
||||
>
|
||||
<Box width="100%" borderBottom={1} borderBottomColor="washedGray">
|
||||
<Box
|
||||
mx="auto"
|
||||
bg={sigilColor}
|
||||
borderRadius={8}
|
||||
my={4}
|
||||
height={160}
|
||||
width={160}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
{image}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box width="100%" py={3} zIndex='2'>
|
||||
<SidebarItem current={view} view="identity">
|
||||
Your Identity
|
||||
</SidebarItem>
|
||||
<SidebarItem current={view} view="settings">
|
||||
Ship Settings
|
||||
</SidebarItem>
|
||||
</Box>
|
||||
</Col>
|
||||
<Box
|
||||
display={!view ? "none" : ["flex", "none"]}
|
||||
alignItems="center"
|
||||
px={3}
|
||||
borderBottom={1}
|
||||
fontSize='0'
|
||||
borderBottomColor="washedGray"
|
||||
>
|
||||
<Link to="/~profile">{"<- Back"}</Link>
|
||||
</Box>
|
||||
<Box overflowY="auto" flexGrow={1}>
|
||||
{view === "settings" && <Settings {...props} />}
|
||||
|
||||
{view === "identity" && (
|
||||
<>
|
||||
<Text display='block' gray px='3' pt='3'>Your identity provides the default information you can optionally share with groups in the group settings panel.</Text>
|
||||
<ContactCard
|
||||
contact={contact}
|
||||
path="/~/default"
|
||||
api={props.api}
|
||||
s3={props.s3}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Box>
|
||||
<Profile
|
||||
ship={ship}
|
||||
associations={props.associations}
|
||||
groups={props.groups}
|
||||
contact={contact}
|
||||
api={props.api}
|
||||
s3={props.s3}
|
||||
isEdit={isEdit}
|
||||
isPublic={isPublic}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
></Route>
|
||||
</Switch>
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
48
pkg/interface/src/views/apps/settings/settings.tsx
Normal file
48
pkg/interface/src/views/apps/settings/settings.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { Route, Link, Switch } from "react-router-dom";
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import { Box, Text, Row, Col, Icon, BaseImage } from "@tlon/indigo-react";
|
||||
|
||||
import Settings from "./components/settings";
|
||||
import useLocalState from "~/logic/state/local";
|
||||
|
||||
export default function SettingsScreen(props: any) {
|
||||
const { ship, dark } = props;
|
||||
const hideAvatars = useLocalState(state => state.hideAvatars);
|
||||
return (
|
||||
<>
|
||||
<Helmet defer={false}>
|
||||
<title>Landscape - Settings</title>
|
||||
</Helmet>
|
||||
<Route
|
||||
path={["/~settings"]}
|
||||
render={({ match, history }) => {
|
||||
return (
|
||||
<Box height="100%"
|
||||
width="100%"
|
||||
px={[0, 3]}
|
||||
pb={[0, 3]}
|
||||
borderRadius={1}>
|
||||
<Box
|
||||
height="100%"
|
||||
width="100%"
|
||||
display="grid"
|
||||
gridTemplateColumns={["100%", "400px 1fr"]}
|
||||
gridTemplateRows={["48px 1fr", "1fr"]}
|
||||
borderRadius={1}
|
||||
bg="white"
|
||||
border={1}
|
||||
borderColor="washedGray"
|
||||
overflowY="auto"
|
||||
flexGrow
|
||||
>
|
||||
<Settings {...props} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useCallback } from "react";
|
||||
import React, { useMemo, useCallback, useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Text,
|
||||
@ -6,17 +6,17 @@ import {
|
||||
Row,
|
||||
Col,
|
||||
Icon,
|
||||
ErrorLabel,
|
||||
} from "@tlon/indigo-react";
|
||||
import _ from "lodash";
|
||||
import { useField } from "formik";
|
||||
import styled from "styled-components";
|
||||
ErrorLabel
|
||||
} from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
import { useField } from 'formik';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { roleForShip } from "~/logic/lib/group";
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
|
||||
import { DropdownSearch } from "./DropdownSearch";
|
||||
import { Groups } from "~/types";
|
||||
import { Associations, Association } from "~/types/metadata-update";
|
||||
import { DropdownSearch } from './DropdownSearch';
|
||||
import { Groups } from '~/types';
|
||||
import { Associations, Association } from '~/types/metadata-update';
|
||||
|
||||
interface InviteSearchProps {
|
||||
disabled?: boolean;
|
||||
@ -26,11 +26,12 @@ interface InviteSearchProps {
|
||||
label: string;
|
||||
caption?: string;
|
||||
id: string;
|
||||
maxLength?: number;
|
||||
}
|
||||
|
||||
const CandidateBox = styled(Box)<{ selected: boolean }>`
|
||||
&:hover {
|
||||
background-color: ${(p) => p.theme.colors.washedGray};
|
||||
background-color: ${p => p.theme.colors.washedGray};
|
||||
}
|
||||
`;
|
||||
|
||||
@ -64,38 +65,45 @@ function renderCandidate(
|
||||
|
||||
export function GroupSearch(props: InviteSearchProps) {
|
||||
const { id, caption, label } = props;
|
||||
const [selected, setSelected] = useState([] as string[]);
|
||||
const groups: Association[] = useMemo(() => {
|
||||
return props.adminOnly
|
||||
? Object.values(
|
||||
Object.keys(props.associations?.contacts)
|
||||
Object.keys(props.associations?.groups)
|
||||
.filter(
|
||||
(e) => roleForShip(props.groups[e], window.ship) === "admin"
|
||||
e => roleForShip(props.groups[e], window.ship) === 'admin'
|
||||
)
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = props.associations?.contacts[key];
|
||||
obj[key] = props.associations?.groups[key];
|
||||
return obj;
|
||||
}, {}) || {}
|
||||
)
|
||||
: Object.values(props.associations?.contacts || {});
|
||||
}, [props.associations?.contacts]);
|
||||
: Object.values(props.associations?.groups || {});
|
||||
}, [props.associations?.groups]);
|
||||
|
||||
const [{ value }, meta, { setValue, setTouched }] = useField(props.id);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(selected);
|
||||
}, [selected])
|
||||
|
||||
const { title: groupTitle } =
|
||||
props.associations.contacts?.[value]?.metadata || {};
|
||||
props.associations.groups?.[value]?.metadata || {};
|
||||
|
||||
const onSelect = useCallback(
|
||||
(a: Association) => {
|
||||
setValue(a.group);
|
||||
setTouched(true);
|
||||
setSelected(v => _.uniq([...v, a.group]));
|
||||
},
|
||||
[setValue]
|
||||
[setTouched, setSelected]
|
||||
);
|
||||
|
||||
const onUnselect = useCallback(() => {
|
||||
setValue(undefined);
|
||||
setTouched(true);
|
||||
}, [setValue]);
|
||||
const onRemove = useCallback(
|
||||
(s: string) => {
|
||||
setSelected(groups => groups.filter(group => group !== s))
|
||||
},
|
||||
[setSelected]
|
||||
);
|
||||
|
||||
return (
|
||||
<Col>
|
||||
@ -105,25 +113,11 @@ export function GroupSearch(props: InviteSearchProps) {
|
||||
{caption}
|
||||
</Label>
|
||||
)}
|
||||
{value && (
|
||||
<Row
|
||||
borderRadius="1"
|
||||
mt="2"
|
||||
width="fit-content"
|
||||
border="1"
|
||||
borderColor="gray"
|
||||
height="32px"
|
||||
px="2"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text mr="2">{groupTitle || value}</Text>
|
||||
<Icon onClick={onUnselect} icon="X" />
|
||||
</Row>
|
||||
)}
|
||||
{!value && (
|
||||
<DropdownSearch<Association>
|
||||
mt="2"
|
||||
candidates={groups}
|
||||
placeholder="Search for groups..."
|
||||
disabled={props.maxLength ? selected.length >= props.maxLength : false}
|
||||
renderCandidate={renderCandidate}
|
||||
search={(s: string, a: Association) =>
|
||||
a.metadata.title.toLowerCase().startsWith(s.toLowerCase())
|
||||
@ -131,8 +125,27 @@ export function GroupSearch(props: InviteSearchProps) {
|
||||
getKey={(a: Association) => a.group}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
{value?.length > 0 && (
|
||||
value.map((e) => {
|
||||
return (
|
||||
<Row
|
||||
key={e}
|
||||
borderRadius="1"
|
||||
mt="2"
|
||||
width="fit-content"
|
||||
border="1"
|
||||
borderColor="gray"
|
||||
height="32px"
|
||||
px="2"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text mr="2">{groupTitle || e}</Text>
|
||||
<Icon onClick={onRemove} icon="X" />
|
||||
</Row>
|
||||
);
|
||||
})
|
||||
)}
|
||||
<ErrorLabel hasError={!!(meta.touched && meta.error)}>
|
||||
<ErrorLabel hasError={Boolean(meta.touched && meta.error)}>
|
||||
{meta.error}
|
||||
</ErrorLabel>
|
||||
</Col>
|
||||
|
@ -110,7 +110,6 @@ class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
|
||||
|
||||
return (
|
||||
<Box
|
||||
cursor='pointer'
|
||||
position='relative'
|
||||
onClick={this.profileShow}
|
||||
ref={this.containerRef}
|
||||
|
@ -4,7 +4,8 @@ import { Contact, Group } from '~/types';
|
||||
import { cite, useShowNickname } from '~/logic/lib/util';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
|
||||
import { Box, Col, Button, Text, BaseImage, ColProps } from '@tlon/indigo-react';
|
||||
import { Box, Col, Row, Text, BaseImage, ColProps, Icon } from '@tlon/indigo-react';
|
||||
import { Dropdown } from './Dropdown';
|
||||
import { withLocalState } from '~/logic/state/local';
|
||||
|
||||
export const OVERLAY_HEIGHT = 250;
|
||||
@ -25,11 +26,13 @@ type ProfileOverlayProps = ColProps & {
|
||||
|
||||
class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
||||
public popoverRef: React.Ref<typeof Col>;
|
||||
public dropdownRef: React.Ref<typeof Col>;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.popoverRef = React.createRef();
|
||||
this.dropdownRef = React.createRef();
|
||||
this.onDocumentClick = this.onDocumentClick.bind(this);
|
||||
}
|
||||
|
||||
@ -44,9 +47,9 @@ class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
||||
}
|
||||
|
||||
onDocumentClick(event) {
|
||||
const { popoverRef } = this;
|
||||
const { popoverRef, dropdownRef } = this;
|
||||
// Do nothing if clicking ref's element or descendent elements
|
||||
if (!popoverRef.current || popoverRef.current.contains(event.target)) {
|
||||
if (!popoverRef.current || dropdownRef.current.contains(event.target) || popoverRef.current.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -60,7 +63,6 @@ class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
||||
color,
|
||||
topSpace,
|
||||
bottomSpace,
|
||||
group = false,
|
||||
hideAvatars,
|
||||
hideNicknames,
|
||||
history,
|
||||
@ -78,75 +80,113 @@ class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
||||
if (!(top || bottom)) {
|
||||
bottom = `-${Math.round(OVERLAY_HEIGHT / 2)}px`;
|
||||
}
|
||||
const containerStyle = { top, bottom, left: '100%', maxWidth: '160px' };
|
||||
const containerStyle = { top, bottom, left: '100%' };
|
||||
|
||||
const isOwn = window.ship === ship;
|
||||
|
||||
const img = contact?.avatar && !hideAvatars
|
||||
? <BaseImage display='inline-block' src={contact.avatar} height={160} width={160} className="brt2" />
|
||||
? <BaseImage display='inline-block' src={contact.avatar} height={72} width={72} className="brt2" />
|
||||
: <Sigil
|
||||
ship={ship}
|
||||
size={160}
|
||||
size={72}
|
||||
color={color}
|
||||
classes="brt2"
|
||||
svgClass="brt2"
|
||||
/>;
|
||||
const showNickname = useShowNickname(contact, hideNicknames);
|
||||
|
||||
// TODO: we need to rethink this "top-level profile view" of other ships
|
||||
/* if (!group.hidden) {
|
||||
}*/
|
||||
|
||||
const isHidden = group ? group.hidden : false;
|
||||
|
||||
const rootSettings = history.location.pathname.slice(0, history.location.pathname.indexOf("/resource"));
|
||||
|
||||
return (
|
||||
<Col
|
||||
ref={this.popoverRef}
|
||||
boxShadow="2px 4px 20px rgba(0, 0, 0, 0.25)"
|
||||
backgroundColor="white"
|
||||
color="washedGray"
|
||||
border={1}
|
||||
borderRadius={2}
|
||||
borderColor="lightGray"
|
||||
boxShadow="0px 0px 0px 3px"
|
||||
position='absolute'
|
||||
backgroundColor='white'
|
||||
zIndex='3'
|
||||
fontSize='0'
|
||||
height="250px"
|
||||
width="250px"
|
||||
padding={3}
|
||||
justifyContent="space-between"
|
||||
style={containerStyle}
|
||||
{...rest}
|
||||
>
|
||||
<Box height='160px' width='160px'>
|
||||
<Row color='black' width='100%' height="3rem">
|
||||
<Dropdown
|
||||
dropWidth="150px"
|
||||
width="auto"
|
||||
alignY="top"
|
||||
alignX="left"
|
||||
options={
|
||||
<Col
|
||||
mt='4'
|
||||
p='1'
|
||||
backgroundColor="white"
|
||||
color="washedGray"
|
||||
border={1}
|
||||
borderRadius={2}
|
||||
borderColor="lightGray"
|
||||
ref={this.dropdownRef}
|
||||
boxShadow="0px 0px 0px 3px">
|
||||
<Row
|
||||
p={1}
|
||||
color='black'
|
||||
cursor='pointer'
|
||||
fontSize={0}
|
||||
onClick={() => history.push('/~profile/~' + window.ship)}>
|
||||
View Profile
|
||||
</Row>
|
||||
{(!isOwn) && (
|
||||
<Row
|
||||
p={1}
|
||||
color='black'
|
||||
cursor='pointer'
|
||||
fontSize={0}
|
||||
onClick={() => history.push(`/~landscape/dm/${ship}`)}
|
||||
>
|
||||
Send Message
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
}>
|
||||
<Icon icon="Menu" mr='3'/>
|
||||
</Dropdown>
|
||||
{(!isOwn) && (
|
||||
<Icon icon="Chat" size={16} onClick={() => history.push(`/~landscape/dm/${ship}`)}/>
|
||||
)}
|
||||
</Row>
|
||||
<Box alignSelf="center" height="72px">
|
||||
{img}
|
||||
</Box>
|
||||
<Box p='3'>
|
||||
{showNickname && (
|
||||
<Col height="3rem" alignItems="end" justifyContent="flex-end">
|
||||
<Text
|
||||
fontWeight='600'
|
||||
mono={!showNickname}
|
||||
display='block'
|
||||
textOverflow='ellipsis'
|
||||
overflow='hidden'
|
||||
whiteSpace='pre'
|
||||
lineHeight="tall"
|
||||
>
|
||||
{contact.nickname}
|
||||
{showNickname ? contact.nickname : cite(ship)}
|
||||
</Text>
|
||||
)}
|
||||
<Text mono gray>{cite(`~${ship}`)}</Text>
|
||||
{!isOwn && (
|
||||
<Button mt={2} fontSize='0' width="100%" style={{ cursor: 'pointer' }} onClick={() => history.push(`/~landscape/dm/${ship}`)}>
|
||||
Send Message
|
||||
</Button>
|
||||
)}
|
||||
{(isOwn) ? (
|
||||
<Button
|
||||
mt='2'
|
||||
width='100%'
|
||||
style={{ cursor: 'pointer ' }}
|
||||
onClick={() => (isHidden) ? history.push('/~profile/identity') : history.push(`${rootSettings}/popover/profile`)}
|
||||
<Text
|
||||
contentEditable={isOwn}
|
||||
display={(!contact?.status && !isOwn) ? 'none' : 'inline'}
|
||||
gray={(!contact?.status && isOwn)}
|
||||
// onBlur={() => api.contacts.edit()...}
|
||||
>
|
||||
Edit Identity
|
||||
</Button>
|
||||
) : <div />}
|
||||
</Box>
|
||||
{(!contact?.status && isOwn) ? "Set a status" : contact.status}
|
||||
</Text>
|
||||
</Col>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withLocalState(ProfileOverlay, ['hideAvatars', 'hideNicknames']);
|
||||
export default withLocalState(ProfileOverlay, ['hideAvatars', 'hideNicknames']);
|
||||
|
88
pkg/interface/src/views/components/SetStatusBarModal.js
Normal file
88
pkg/interface/src/views/components/SetStatusBarModal.js
Normal file
@ -0,0 +1,88 @@
|
||||
import React, {
|
||||
useState,
|
||||
useEffect
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Box
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import { SetStatus } from '~/views/apps/profile/components/SetStatus';
|
||||
|
||||
|
||||
export const SetStatusBarModal = (props) => {
|
||||
const {
|
||||
ship,
|
||||
contact,
|
||||
api,
|
||||
...rest
|
||||
} = props;
|
||||
const [modalShown, setModalShown] = useState(false);
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setModalShown(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [modalShown]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{modalShown && (
|
||||
<Box
|
||||
backgroundColor='scales.black30'
|
||||
left="0px"
|
||||
top="0px"
|
||||
width="100%"
|
||||
height="100%"
|
||||
zIndex={4}
|
||||
position="fixed"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
onClick={() => setModalShown(false)}
|
||||
>
|
||||
<Box
|
||||
maxWidth="500px"
|
||||
width="100%"
|
||||
bg="white"
|
||||
borderRadius={2}
|
||||
border={[0, 1]}
|
||||
borderColor={["washedGray", "washedGray"]}
|
||||
onClick={e => e.stopPropagation()}
|
||||
display="flex"
|
||||
alignItems="stretch"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Box m={3}>
|
||||
<SetStatus
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
api={api}
|
||||
callback={() => {
|
||||
setModalShown(false);
|
||||
}} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Row
|
||||
p={1}
|
||||
color='black'
|
||||
cursor='pointer'
|
||||
fontSize={0}
|
||||
onClick={() => setModalShown(true)}>
|
||||
Set Status
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ export function ShipSearch(props: InviteSearchProps) {
|
||||
const result = ob.isValidPatp(ship);
|
||||
return result ? deSig(s) ?? undefined : undefined;
|
||||
}}
|
||||
placeholder="Search for ships"
|
||||
placeholder="Search for ships..."
|
||||
candidates={peers}
|
||||
renderCandidate={renderCandidate}
|
||||
disabled={props.maxLength ? selected.length >= props.maxLength : false}
|
||||
|
@ -1,16 +1,48 @@
|
||||
import React from 'react';
|
||||
import React, {
|
||||
useState,
|
||||
useEffect
|
||||
} from 'react';
|
||||
|
||||
import { Row, Box, Text, Icon, Button } from '@tlon/indigo-react';
|
||||
import {
|
||||
Col,
|
||||
Row,
|
||||
Box,
|
||||
Text,
|
||||
Icon,
|
||||
Button,
|
||||
BaseImage
|
||||
} from '@tlon/indigo-react';
|
||||
import ReconnectButton from './ReconnectButton';
|
||||
import { Dropdown } from './Dropdown';
|
||||
import { StatusBarItem } from './StatusBarItem';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
import { SetStatusBarModal } from './SetStatusBarModal';
|
||||
|
||||
import useLocalState from '~/logic/state/local';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
|
||||
|
||||
const StatusBar = (props) => {
|
||||
const { ourContact, api, ship } = props;
|
||||
const invites = [].concat(...Object.values(props.invites).map(obj => Object.values(obj)));
|
||||
const metaKey = (window.navigator.platform.includes('Mac')) ? '⌘' : 'Ctrl+';
|
||||
const toggleOmnibox = useLocalState(state => state.toggleOmnibox);
|
||||
const { toggleOmnibox, hideAvatars } =
|
||||
useLocalState(({ toggleOmnibox, hideAvatars }) =>
|
||||
({ toggleOmnibox, hideAvatars })
|
||||
);
|
||||
|
||||
const color = !!ourContact ? `#${uxToHex(props.ourContact.color)}` : '#000';
|
||||
const xPadding = (!hideAvatars && ourContact?.avatar) ? '0' : '2';
|
||||
const bgColor = (!hideAvatars && ourContact?.avatar) ? '' : color;
|
||||
const profileImage = (!hideAvatars && ourContact?.avatar) ? (
|
||||
<BaseImage
|
||||
src={ourContact.avatar}
|
||||
borderRadius={2}
|
||||
width='32px'
|
||||
height='32px'
|
||||
style={{ objectFit: 'cover' }} />
|
||||
) : <Sigil ship={ship} size={16} color={color} icon />;
|
||||
|
||||
return (
|
||||
<Box
|
||||
display='grid'
|
||||
@ -25,7 +57,6 @@ const StatusBar = (props) => {
|
||||
<Button width="32px" borderColor='washedGray' mr='2' px='2' onClick={() => props.history.push('/')} {...props}>
|
||||
<Icon icon='Spaces' color='black'/>
|
||||
</Button>
|
||||
|
||||
<StatusBarItem mr={2} onClick={() => toggleOmnibox()}>
|
||||
{ !props.doNotDisturb && (props.notificationsCount > 0 || invites.length > 0) &&
|
||||
(<Box display="block" right="-8px" top="-8px" position="absolute" >
|
||||
@ -60,9 +91,50 @@ const StatusBar = (props) => {
|
||||
>
|
||||
<Text color='#000000'>Submit <Text color='#000000' display={['none', 'inline']}>an</Text> issue</Text>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem width={['32px', 'auto']} px={'2'} flexShrink='0' onClick={() => props.history.push('/~profile')}>
|
||||
<Sigil ship={props.ship} size={16} color='black' classes='mix-blend-diff' icon />
|
||||
</StatusBarItem>
|
||||
<Dropdown
|
||||
dropWidth="150px"
|
||||
width="auto"
|
||||
alignY="top"
|
||||
alignX="right"
|
||||
options={
|
||||
<Col
|
||||
mt='6'
|
||||
p='1'
|
||||
backgroundColor="white"
|
||||
color="washedGray"
|
||||
border={1}
|
||||
borderRadius={2}
|
||||
borderColor="lightGray"
|
||||
boxShadow="0px 0px 0px 3px">
|
||||
<Row
|
||||
p={1}
|
||||
color='black'
|
||||
cursor='pointer'
|
||||
fontSize={0}
|
||||
onClick={() => props.history.push(`/~profile/~${ship}`)}>
|
||||
View Profile
|
||||
</Row>
|
||||
<SetStatusBarModal
|
||||
ship={`~${ship}`}
|
||||
contact={ourContact}
|
||||
api={api} />
|
||||
<Row
|
||||
p={1}
|
||||
color='black'
|
||||
cursor='pointer'
|
||||
fontSize={0}
|
||||
onClick={() => props.history.push('/~settings')}>
|
||||
System Settings
|
||||
</Row>
|
||||
</Col>
|
||||
}>
|
||||
<StatusBarItem
|
||||
px={xPadding}
|
||||
flexShrink='0'
|
||||
backgroundColor={bgColor}>
|
||||
{profileImage}
|
||||
</StatusBarItem>
|
||||
</Dropdown>
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
|
@ -32,7 +32,7 @@ export class Omnibox extends Component {
|
||||
const { pathname } = this.props.location;
|
||||
const selectedGroup = pathname.startsWith('/~landscape/ship/') ? '/' + pathname.split('/').slice(2,5).join('/') : null;
|
||||
|
||||
this.setState({ index: index(this.props.associations, this.props.apps.tiles, selectedGroup, this.props.groups) });
|
||||
this.setState({ index: index(this.props.contacts, this.props.associations, this.props.apps.tiles, selectedGroup, this.props.groups) });
|
||||
}
|
||||
|
||||
if (prevProps && (prevProps.apps !== this.props.apps) && (this.state.query === '')) {
|
||||
@ -56,7 +56,7 @@ export class Omnibox extends Component {
|
||||
}
|
||||
|
||||
getSearchedCategories() {
|
||||
return ['other', 'commands', 'groups', 'subscriptions', 'apps'];
|
||||
return ['ships', 'other', 'commands', 'groups', 'subscriptions', 'apps'];
|
||||
}
|
||||
|
||||
control(evt) {
|
||||
@ -249,6 +249,7 @@ export class Omnibox extends Component {
|
||||
selected={selected}
|
||||
invites={props.invites}
|
||||
notifications={props.notifications}
|
||||
contacts={props.contacts}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import { Box, Row, Icon, Text } from '@tlon/indigo-react';
|
||||
import defaultApps from '~/logic/lib/default-apps';
|
||||
import Sigil from '~/logic/lib/sigil';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
|
||||
export class OmniboxResult extends Component {
|
||||
constructor(props) {
|
||||
@ -25,9 +26,8 @@ export class OmniboxResult extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
getIcon(icon, selected, link, invites, notifications) {
|
||||
getIcon(icon, selected, link, invites, notifications, text, color) {
|
||||
const iconFill = (this.state.hovered || (selected === link)) ? 'white' : 'black';
|
||||
const sigilFill = (this.state.hovered || (selected === link)) ? '#3a8ff7' : '#ffffff';
|
||||
const bulletFill = (this.state.hovered || (selected === link)) ? 'white' : 'blue';
|
||||
|
||||
const inviteCount = [].concat(...Object.values(invites).map(obj => Object.values(obj)));
|
||||
@ -39,22 +39,23 @@ export class OmniboxResult extends Component {
|
||||
{
|
||||
icon = (icon === 'Link') ? 'Collection' :
|
||||
(icon === 'Terminal') ? 'Dojo' : icon;
|
||||
graphic = <Icon display="inline-block" verticalAlign="middle" icon={icon} mr='2' size='16px' color={iconFill} />;
|
||||
graphic = <Icon display="inline-block" verticalAlign="middle" icon={icon} mr='2' size='18px' color={iconFill} />;
|
||||
} else if (icon === 'inbox') {
|
||||
graphic = <Box display='flex' verticalAlign='middle' position="relative">
|
||||
<Icon display='inline-block' verticalAlign='middle' icon='Inbox' mr='2' size='16px' color={iconFill} />
|
||||
<Icon display='inline-block' verticalAlign='middle' icon='Inbox' mr='2' size='18px' color={iconFill} />
|
||||
{(notifications > 0 || inviteCount.length > 0) && (
|
||||
<Icon display='inline-block' icon='Bullet' style={{ position: 'absolute', top: -5, left: 5 }} color={bulletFill} />
|
||||
)}
|
||||
</Box>;
|
||||
} else if (icon === 'logout') {
|
||||
graphic = <Icon display="inline-block" verticalAlign="middle" icon='SignOut' mr='2' size='16px' color={iconFill} />;
|
||||
graphic = <Icon display="inline-block" verticalAlign="middle" icon='SignOut' mr='2' size='18px' color={iconFill} />;
|
||||
} else if (icon === 'profile') {
|
||||
graphic = <Sigil color={sigilFill} classes='dib flex-shrink-0 v-mid mr2' ship={window.ship} size={16} icon padded />;
|
||||
text = text.startsWith('Profile') ? window.ship : text;
|
||||
graphic = <Sigil color={color} classes='dib flex-shrink-0 v-mid mr2' ship={text} size={18} icon padded />;
|
||||
} else if (icon === 'home') {
|
||||
graphic = <Icon display='inline-block' verticalAlign='middle' icon='Mail' mr='2' size='16px' color={iconFill} />;
|
||||
graphic = <Icon display='inline-block' verticalAlign='middle' icon='Mail' mr='2' size='18px' color={iconFill} />;
|
||||
} else if (icon === 'notifications') {
|
||||
graphic = <Icon display='inline-block' verticalAlign='middle' icon='Inbox' mr='2' size='16px' color={iconFill} />;
|
||||
graphic = <Icon display='inline-block' verticalAlign='middle' icon='Inbox' mr='2' size='18px' color={iconFill} />;
|
||||
} else {
|
||||
graphic = <Icon display='inline-block' icon='NullIcon' verticalAlign="middle" mr='2' size="16px" color={iconFill} />;
|
||||
}
|
||||
@ -67,9 +68,10 @@ export class OmniboxResult extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { icon, text, subtext, link, navigate, selected, invites, notifications } = this.props;
|
||||
const { icon, text, subtext, link, navigate, selected, invites, notifications, contacts } = this.props;
|
||||
|
||||
const graphic = this.getIcon(icon, selected, link, invites, notifications);
|
||||
const color = contacts?.[text] ? `#${uxToHex(contacts[text].color)}` : "#000000";
|
||||
const graphic = this.getIcon(icon, selected, link, invites, notifications, text, color);
|
||||
|
||||
return (
|
||||
<Row
|
||||
@ -89,6 +91,7 @@ export class OmniboxResult extends Component {
|
||||
<Text
|
||||
display="inline-block"
|
||||
verticalAlign="middle"
|
||||
mono={(icon == 'profile' && text.startsWith('~'))}
|
||||
color={this.state.hovered || selected === link ? 'white' : 'black'}
|
||||
maxWidth="60%"
|
||||
style={{ flexShrink: 0 }}
|
||||
|
@ -7,6 +7,7 @@ import LaunchApp from '~/views/apps/launch/app';
|
||||
import TermApp from '~/views/apps/term/app';
|
||||
import Landscape from '~/views/landscape/index';
|
||||
import Profile from '~/views/apps/profile/profile';
|
||||
import Settings from '~/views/apps/settings/settings';
|
||||
import ErrorComponent from '~/views/components/Error';
|
||||
import Notifications from '~/views/apps/notifications/notifications';
|
||||
import GraphApp from '../../apps/graph/app';
|
||||
@ -63,6 +64,14 @@ export const Content = (props) => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/~settings"
|
||||
render={ p => (
|
||||
<Settings
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/~notifications"
|
||||
render={ p => (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Col, Label, Row, Button } from "@tlon/indigo-react";
|
||||
import { Icon, Text, Col, Label, Row, Button, Action } from "@tlon/indigo-react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
@ -7,6 +7,8 @@ import { Association } from "~/types";
|
||||
import { resourceFromPath } from "~/logic/lib/group";
|
||||
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
|
||||
import ModalButton from "~/views/apps/launch/components/ModalButton";
|
||||
import {useModal} from "~/logic/lib/useModal";
|
||||
import {SidebarItem} from "./Sidebar/SidebarItem";
|
||||
|
||||
export function DeleteGroup(props: {
|
||||
owner: boolean;
|
||||
@ -15,14 +17,17 @@ export function DeleteGroup(props: {
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const onDelete = async () => {
|
||||
const name = props.association.group.split("/").pop();
|
||||
const { ship, name } = resourceFromPath(props.association.group);
|
||||
if (props.owner) {
|
||||
const shouldDelete =
|
||||
prompt(`To confirm deleting this group, type ${name}`) === name;
|
||||
if (!shouldDelete) return;
|
||||
}
|
||||
const resource = resourceFromPath(props.association.group);
|
||||
await props.api.groups.removeGroup(resource);
|
||||
if(props.owner) {
|
||||
await props.api.groups.deleteGroup(ship, name);
|
||||
} else {
|
||||
await props.api.groups.leaveGroup(ship, name);
|
||||
}
|
||||
history.push("/");
|
||||
};
|
||||
|
||||
@ -32,15 +37,8 @@ export function DeleteGroup(props: {
|
||||
: "You can rejoin if it is an open group, or if you are reinvited";
|
||||
|
||||
const icon = props.owner ? "X" : "SignOut";
|
||||
return (
|
||||
<ModalButton
|
||||
ml="2"
|
||||
color="red"
|
||||
boxShadow="none"
|
||||
icon={icon}
|
||||
text={`${action} group`}
|
||||
>
|
||||
{(dismiss: () => void) => (
|
||||
const { modal, showModal } = useModal({ modal:
|
||||
(dismiss: () => void) => (
|
||||
<Col p="4">
|
||||
<Label>{action} Group</Label>
|
||||
<Label gray mt="2">
|
||||
@ -59,7 +57,14 @@ export function DeleteGroup(props: {
|
||||
</StatelessAsyncButton>
|
||||
</Row>
|
||||
</Col>
|
||||
)}
|
||||
</ModalButton>
|
||||
)});
|
||||
return (
|
||||
<Row px="3" py="1" onClick={showModal} cursor="pointer">
|
||||
{modal}
|
||||
<Icon icon={icon} color="red" mr="2" />
|
||||
<Text color="red">
|
||||
{action} group
|
||||
</Text>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
@ -42,9 +42,9 @@ function RecentGroups(props: { recent: string[]; associations: Associations }) {
|
||||
Recent Groups
|
||||
</Box>
|
||||
{props.recent.filter((e) => {
|
||||
return (e in associations?.contacts);
|
||||
return (e in associations?.groups);
|
||||
}).slice(1, 5).map((g) => {
|
||||
const assoc = associations.contacts[g];
|
||||
const assoc = associations.groups[g];
|
||||
const color = uxToHex(assoc?.metadata?.color || '0x0');
|
||||
return (
|
||||
<Link key={g} style={{ minWidth: 0 }} to={`/~landscape${g}`}>
|
||||
@ -78,7 +78,7 @@ export function GroupSwitcher(props: {
|
||||
}) {
|
||||
const { associations, workspace, isAdmin } = props;
|
||||
const title = getTitleFromWorkspace(associations, workspace);
|
||||
const metadata = workspace.type === 'home' ? undefined : associations.contacts[workspace.group].metadata;
|
||||
const metadata = workspace.type === 'home' ? undefined : associations.groups[workspace.group].metadata;
|
||||
const navTo = (to: string) => `${props.baseUrl}${to}`;
|
||||
return (
|
||||
<Row width="100%" alignItems="center" height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" pl='3' borderBottom='1px solid' borderColor='washedGray'>
|
||||
|
@ -14,7 +14,7 @@ const formSchema = Yup.object({
|
||||
});
|
||||
|
||||
interface FormSchema {
|
||||
group: string | null;
|
||||
group: string[] | null;
|
||||
}
|
||||
|
||||
interface GroupifyFormProps {
|
||||
@ -37,7 +37,7 @@ export function GroupifyForm(props: GroupifyFormProps) {
|
||||
await props.api.graph.groupifyGraph(
|
||||
ship,
|
||||
name,
|
||||
values.group || undefined
|
||||
values.group?.toString() || undefined
|
||||
);
|
||||
const mod = association.metadata.module || association['app-name'];
|
||||
const newGroup = values.group || association.group;
|
||||
@ -79,6 +79,7 @@ export function GroupifyForm(props: GroupifyFormProps) {
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
adminOnly
|
||||
maxLength={1}
|
||||
/>
|
||||
<AsyncButton primary loadingText="Groupifying..." border>
|
||||
Groupify
|
||||
|
@ -43,7 +43,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
const groupContacts = (groupPath && contacts[groupPath]) || undefined;
|
||||
const rootIdentity = contacts?.["/~/default"]?.[window.ship];
|
||||
const groupAssociation =
|
||||
(groupPath && associations.contacts[groupPath]) || undefined;
|
||||
(groupPath && associations.groups[groupPath]) || undefined;
|
||||
const group = (groupPath && groups[groupPath]) || undefined;
|
||||
const [recentGroups, setRecentGroups] = useLocalStorageState<string[]>(
|
||||
"recent-groups",
|
||||
@ -57,7 +57,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
setRecentGroups((gs) => _.uniq([workspace.group, ...gs]));
|
||||
}, [workspace]);
|
||||
|
||||
if (!associations) {
|
||||
if (!(associations && (groupPath ? groupPath in groups : true))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -196,7 +196,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
let summary: ReactNode;
|
||||
if(groupAssociation?.group) {
|
||||
const memberCount = props.groups[groupAssociation.group].members.size;
|
||||
summary = <GroupSummary
|
||||
summary = <GroupSummary
|
||||
memberCount={memberCount}
|
||||
channelCount={0}
|
||||
metadata={groupAssociation.metadata}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState, useCallback, useEffect } from "react";
|
||||
import _ from 'lodash';
|
||||
import { Body } from "~/views/components/Body";
|
||||
import {
|
||||
Col,
|
||||
@ -11,7 +12,7 @@ import {
|
||||
import { Formik, Form, FormikHelpers, useFormikContext } from "formik";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import * as Yup from "yup";
|
||||
import { Groups, Rolodex } from "~/types";
|
||||
import { Groups, Rolodex, MetadataUpdatePreview, Associations } from "~/types";
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { RouteComponentProps, useHistory } from "react-router-dom";
|
||||
@ -41,10 +42,11 @@ interface FormSchema {
|
||||
|
||||
interface JoinGroupProps {
|
||||
groups: Groups;
|
||||
contacts: Rolodex;
|
||||
associations: Associations;
|
||||
api: GlobalApi;
|
||||
autojoin?: string;
|
||||
inviteUid?: string;
|
||||
inviteApp?: string;
|
||||
}
|
||||
|
||||
function Autojoin(props: { autojoin: string | null }) {
|
||||
@ -60,38 +62,53 @@ function Autojoin(props: { autojoin: string | null }) {
|
||||
}
|
||||
|
||||
export function JoinGroup(props: JoinGroupProps) {
|
||||
const { api, autojoin } = props;
|
||||
const { api, autojoin, associations, groups } = props;
|
||||
const history = useHistory();
|
||||
const initialValues: FormSchema = {
|
||||
group: autojoin || "",
|
||||
};
|
||||
const [preview, setPreview] = useState<
|
||||
MetadataUpdatePreview | string | null
|
||||
>(null);
|
||||
|
||||
const waiter = useWaitForProps(props);
|
||||
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
||||
|
||||
const waiter = useWaitForProps(props, _.isString(preview) ? 1 : 5000);
|
||||
|
||||
const onConfirm = useCallback(async () => {
|
||||
const { group } = preview;
|
||||
await api.contacts.join(resourceFromPath(group));
|
||||
if (props.inviteUid) {
|
||||
api.invite.accept("contacts", props.inviteUid);
|
||||
const group = _.isString(preview) ? preview : preview?.group!;
|
||||
const [,,ship,name] = group.split('/');
|
||||
await api.groups.join(ship, name);
|
||||
if (props.inviteUid && props.inviteApp) {
|
||||
api.invite.accept(props.inviteApp, props.inviteUid);
|
||||
}
|
||||
await waiter(({ contacts, groups }) => {
|
||||
return group in contacts && group in groups;
|
||||
});
|
||||
history.push(`/~landscape${group}`);
|
||||
}, [api, preview, waiter]);
|
||||
try {
|
||||
await waiter((p: JoinGroupProps) => {
|
||||
return group in p.groups &&
|
||||
(group in (p.associations?.graph ?? {})
|
||||
|| group in (p.associations?.contacts ?? {}))
|
||||
});
|
||||
if(props.groups?.[group]?.hidden) {
|
||||
const { metadata } = associations.graph[group];
|
||||
history.push(`/~landscape/home/resource/${metadata.module}${group}`);
|
||||
return;
|
||||
} else {
|
||||
history.push(`/~landscape${group}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// drop them into inbox to show join request still pending
|
||||
history.push('/~notifications');
|
||||
}
|
||||
}, [api, preview, waiter, history, associations, groups]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (values: FormSchema, actions: FormikHelpers<FormSchema>) => {
|
||||
const [ship, name] = values.group.split("/");
|
||||
const path = `/ship/${ship}/${name}`;
|
||||
try {
|
||||
const [ship, name] = values.group.split("/");
|
||||
const path = `/ship/${ship}/${name}`;
|
||||
|
||||
const prev = await api.metadata.preview(path);
|
||||
actions.setStatus({ success: null });
|
||||
setPreview(prev);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (!(e instanceof Error)) {
|
||||
actions.setStatus({ error: "Unknown error" });
|
||||
} else if (e.message === "no-permissions") {
|
||||
@ -100,9 +117,7 @@ export function JoinGroup(props: JoinGroupProps) {
|
||||
"Unable to join group, you do not have the correct permissions",
|
||||
});
|
||||
} else if (e.message === "offline") {
|
||||
actions.setStatus({
|
||||
error: "Group host is offline, please try again later",
|
||||
});
|
||||
setPreview(path);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -117,13 +132,21 @@ export function JoinGroup(props: JoinGroupProps) {
|
||||
Join a Group
|
||||
</Text>
|
||||
</Box>
|
||||
{preview ? (
|
||||
{_.isString(preview) ? (
|
||||
<Col width="100%" maxWidth="300px" gapY="4">
|
||||
<Text>The host appears to be offline. Join anyway?</Text>
|
||||
<StatelessAsyncButton primary name="join" onClick={onConfirm}>
|
||||
Join anyway
|
||||
</StatelessAsyncButton>
|
||||
</Col>
|
||||
) : preview ? (
|
||||
<GroupSummary
|
||||
metadata={preview.metadata}
|
||||
memberCount={preview?.members}
|
||||
channelCount={preview?.["channel-count"]}
|
||||
>
|
||||
<Col
|
||||
{ Object.keys(preview.channels).length > 0 && (
|
||||
<Col
|
||||
gapY="2"
|
||||
p="2"
|
||||
borderRadius="2"
|
||||
@ -145,6 +168,7 @@ export function JoinGroup(props: JoinGroupProps) {
|
||||
</Row>
|
||||
))}
|
||||
</Col>
|
||||
)}
|
||||
<StatelessAsyncButton primary name="join" onClick={onConfirm}>
|
||||
Join {preview.metadata.title}
|
||||
</StatelessAsyncButton>
|
||||
@ -157,7 +181,7 @@ export function JoinGroup(props: JoinGroupProps) {
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form style={{ display: "contents" }}>
|
||||
<Autojoin autojoin={autojoin} />
|
||||
<Autojoin autojoin={autojoin ?? null} />
|
||||
<Input
|
||||
id="group"
|
||||
label="Group"
|
||||
|
@ -62,10 +62,10 @@ export function NewGroup(props: NewGroupProps & RouteComponentProps) {
|
||||
banned: [],
|
||||
},
|
||||
};
|
||||
await api.contacts.create(name, policy, title, description);
|
||||
await api.groups.create(name, policy, title, description);
|
||||
const path = `/ship/~${window.ship}/${name}`;
|
||||
await waiter(({ contacts, groups, associations }) => {
|
||||
return path in contacts && path in groups && path in associations.contacts;
|
||||
await waiter(({ groups, associations }) => {
|
||||
return path in groups && path in associations.groups;
|
||||
});
|
||||
|
||||
actions.setStatus({ success: null });
|
||||
|
@ -9,7 +9,6 @@ import { Association } from "~/types/metadata-update";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { GroupNotificationsConfig, S3State, Associations } from "~/types";
|
||||
|
||||
import { ContactCard } from "./ContactCard";
|
||||
import { GroupSettings } from "./GroupSettings/GroupSettings";
|
||||
import { Participants } from "./Participants";
|
||||
import {useHashLink} from "~/logic/lib/useHashLink";
|
||||
|
@ -37,8 +37,8 @@ export function Resource(props: ResourceProps) {
|
||||
const skelProps = { api, association };
|
||||
let title = props.association.metadata.title;
|
||||
if ('workspace' in props) {
|
||||
if ('group' in props.workspace && props.workspace.group in props.associations.contacts) {
|
||||
title = `${props.associations.contacts[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
|
||||
if ('group' in props.workspace && props.workspace.group in props.associations.groups) {
|
||||
title = `${props.associations.groups[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
@ -57,7 +57,7 @@ export function SidebarList(props: {
|
||||
const assoc = associations[a];
|
||||
return group
|
||||
? assoc.group === group
|
||||
: !(assoc.group in props.associations.contacts);
|
||||
: !(assoc.group in props.associations.groups);
|
||||
})
|
||||
.sort(sidebarSort(associations, props.apps)[config.sortBy]);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user