mirror of
https://github.com/urbit/shrub.git
synced 2024-12-01 22:55:03 +03:00
Merge remote-tracking branch 'origin/la/contact-store' into lf/join-cleanup
This commit is contained in:
commit
97502838d6
@ -1,571 +1,27 @@
|
|||||||
:: contact-hook [landscape]
|
:: contact-hook [landscape]: deprecated
|
||||||
::
|
::
|
||||||
::
|
/+ default-agent
|
||||||
/- *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 ~
|
|
||||||
|%
|
|%
|
||||||
+$ card card:agent:gall
|
+$ 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
|
^- 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
|
|_ bol=bowl:gall
|
||||||
++ grp ~(. grpl bol)
|
+* this .
|
||||||
|
def ~(. (default-agent this %|) bol)
|
||||||
::
|
::
|
||||||
++ poke-json
|
++ on-init on-init:def
|
||||||
|= jon=json
|
++ on-poke on-poke:def
|
||||||
^- (quip card _state)
|
++ on-watch on-watch:def
|
||||||
(poke-contact-action (json-to-action jon))
|
++ on-agent on-agent:def
|
||||||
|
++ on-arvo on-arvo:def
|
||||||
|
++ on-save !>(~)
|
||||||
|
++ on-load
|
||||||
|
|= old-vase=vase
|
||||||
|
^- (quip card _this)
|
||||||
|
[~ this]
|
||||||
::
|
::
|
||||||
++ poke-contact-action
|
++ on-leave on-leave:def
|
||||||
|= act=contact-action
|
++ on-peek on-peek:def
|
||||||
^- (quip card _state)
|
++ on-fail on-fail:def
|
||||||
:_ 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 ~]~
|
|
||||||
--
|
--
|
||||||
|
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]:
|
:: 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
|
+$ card card:agent:gall
|
||||||
|
+$ state-4
|
||||||
|
$: %4
|
||||||
|
=rolodex:store
|
||||||
|
allowed-groups=(set resource)
|
||||||
|
allowed-ships=(set ship)
|
||||||
|
is-public=_|
|
||||||
|
==
|
||||||
+$ versioned-state
|
+$ versioned-state
|
||||||
$% state-zero
|
$% [%0 *]
|
||||||
state-one
|
[%1 *]
|
||||||
state-two
|
[%2 *]
|
||||||
state-three
|
[%3 *]
|
||||||
==
|
state-4
|
||||||
::
|
|
||||||
+$ 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
|
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
=| state-three
|
=| state-4
|
||||||
=* state -
|
=* state -
|
||||||
%- agent:dbug
|
%- agent:dbug
|
||||||
^- agent:gall
|
^- agent:gall
|
||||||
=<
|
|_ =bowl:gall
|
||||||
|_ =bowl:gall
|
+* this .
|
||||||
+* this .
|
def ~(. (default-agent this %|) bowl)
|
||||||
contact-core +>
|
::
|
||||||
cc ~(. contact-core bowl)
|
++ on-init
|
||||||
def ~(. (default-agent this %|) bowl)
|
=. 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
|
%4 [~ this(state old)]
|
||||||
++ on-save !>(state)
|
==
|
||||||
++ on-load
|
::
|
||||||
|= old-vase=vase
|
++ on-watch
|
||||||
=/ old !<(versioned-state old-vase)
|
|= =path
|
||||||
=| cards=(list card)
|
^- (quip card _this)
|
||||||
|-
|
?> (team:title our.bowl src.bowl)
|
||||||
?: ?=(%3 -.old)
|
|^
|
||||||
[cards this(state old)]
|
=/ cards=(list card)
|
||||||
?: ?=(%2 -.old)
|
?+ path (on-watch:def path)
|
||||||
%_ $
|
[%all ~] (give [%initial rolodex is-public])
|
||||||
-.old %3
|
[%updates ~] ~
|
||||||
::
|
|
||||||
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]
|
|
||||||
::
|
::
|
||||||
++ give
|
[%our ~]
|
||||||
|= =cage
|
%- give
|
||||||
^- (list card)
|
:+ %add
|
||||||
[%give %fact ~ cage]~
|
our.bowl
|
||||||
--
|
=/ contact=(unit contact:store) (~(get by rolodex) our.bowl)
|
||||||
::
|
?~ contact *contact:store
|
||||||
++ on-leave on-leave:def
|
u.contact
|
||||||
++ 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)
|
|
||||||
==
|
==
|
||||||
|
[cards this]
|
||||||
::
|
::
|
||||||
++ on-agent on-agent:def
|
++ give
|
||||||
++ on-arvo on-arvo:def
|
|= =update:store
|
||||||
++ on-fail on-fail:def
|
^- (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
|
++ on-peek
|
||||||
::
|
|= =path
|
||||||
::++ poke-json
|
^- (unit (unit cage))
|
||||||
:: |= =json
|
?+ path (on-peek:def path)
|
||||||
:: ^- (quip move _this)
|
[%x %all ~] ``noun+!>(rolodex)
|
||||||
:: ?> (team:title our.bol src.bol)
|
::
|
||||||
:: (poke-contact-action (json-to-action json))
|
[%x %contact @ ~]
|
||||||
::
|
=/ =ship (slav %p i.t.t.path)
|
||||||
++ poke-contact-action
|
=/ contact=(unit contact:store) (~(get by rolodex) ship)
|
||||||
|= action=contact-action
|
?~ contact [~ ~]
|
||||||
^- (quip card _state)
|
:- ~ :- ~ :- %contact-update
|
||||||
?> (team:title our.bol src.bol)
|
!> ^- update:store
|
||||||
?- -.action
|
[%add ship u.contact]
|
||||||
%create (handle-create +.action)
|
::
|
||||||
%delete (handle-delete +.action)
|
[%x %allowed-ship @ ~]
|
||||||
%add (handle-add +.action)
|
=/ =ship (slav %p i.t.t.path)
|
||||||
%remove (handle-remove +.action)
|
``noun+!>((~(has in allowed-ships) ship))
|
||||||
%edit (handle-edit +.action)
|
::
|
||||||
|
[%x %allowed-groups ~]
|
||||||
|
``noun+!>(allowed-groups)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ poke-import
|
++ on-leave on-leave:def
|
||||||
|= arc=*
|
++ on-agent on-agent:def
|
||||||
^- (quip card _state)
|
++ on-arvo on-arvo:def
|
||||||
=/ sty=state-three
|
++ on-fail on-fail:def
|
||||||
:- %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)
|
|
||||||
== ==
|
|
||||||
--
|
--
|
||||||
|
@ -1,343 +1,27 @@
|
|||||||
:: contact-view [landscape]:
|
:: contact-view [landscape]: deprecated
|
||||||
::
|
|
||||||
:: 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
|
|
||||||
::
|
::
|
||||||
|
/+ default-agent
|
||||||
|%
|
|%
|
||||||
+$ versioned-state
|
|
||||||
$% state-0
|
|
||||||
==
|
|
||||||
::
|
|
||||||
+$ state-0
|
|
||||||
$: %0
|
|
||||||
~
|
|
||||||
==
|
|
||||||
::
|
|
||||||
+$ card card:agent:gall
|
+$ card card:agent:gall
|
||||||
--
|
--
|
||||||
=| state-0
|
|
||||||
=* state -
|
|
||||||
::
|
::
|
||||||
%- agent:dbug
|
|
||||||
%+ verb |
|
|
||||||
^- agent:gall
|
^- 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
|
|_ bol=bowl:gall
|
||||||
++ grp ~(. grpl bol)
|
+* this .
|
||||||
++ md ~(. mdl bol)
|
def ~(. (default-agent this %|) bol)
|
||||||
++ poke-json
|
|
||||||
|= jon=json
|
|
||||||
^- (list card)
|
|
||||||
?> (team:title our.bol src.bol)
|
|
||||||
(poke-contact-view-action (json-to-view-action jon))
|
|
||||||
::
|
::
|
||||||
++ poke-contact-view-action
|
++ on-init on-init:def
|
||||||
|= act=contact-view-action
|
++ on-poke on-poke:def
|
||||||
^- (list card)
|
++ on-watch on-watch:def
|
||||||
?> (team:title our.bol src.bol)
|
++ on-agent on-agent:def
|
||||||
?- -.act
|
++ on-arvo on-arvo:def
|
||||||
%create
|
++ on-save !>(~)
|
||||||
=/ rid=resource
|
++ on-load
|
||||||
[our.bol name.act]
|
|= old-vase=vase
|
||||||
=/ =path
|
^- (quip card _this)
|
||||||
(en-path:resource rid)
|
[~ this]
|
||||||
;: 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]
|
|
||||||
==
|
|
||||||
==
|
|
||||||
::
|
::
|
||||||
++ joined-group
|
++ on-leave on-leave:def
|
||||||
|= =path
|
++ on-peek on-peek:def
|
||||||
^- (list card)
|
++ on-fail on-fail:def
|
||||||
=/ 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)
|
|
||||||
--
|
--
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
:: Modify the group. Further documented in /sur/group-store.hoon
|
:: Modify the group. Further documented in /sur/group-store.hoon
|
||||||
::
|
::
|
||||||
::
|
::
|
||||||
/- *group, *contact-view
|
/- *group
|
||||||
/+ store=group-store, default-agent, verb, dbug, resource, *migrate
|
/+ store=group-store, default-agent, verb, dbug, resource, *migrate
|
||||||
|%
|
|%
|
||||||
+$ card card:agent:gall
|
+$ card card:agent:gall
|
||||||
@ -284,11 +284,8 @@
|
|||||||
|= [recipient=@p out=(list card)]
|
|= [recipient=@p out=(list card)]
|
||||||
?: =(recipient our.bol)
|
?: =(recipient our.bol)
|
||||||
out
|
out
|
||||||
:_ out
|
:: TODO: figure out contacts integration
|
||||||
%- poke-contact
|
out
|
||||||
:* %invite rid recipient
|
|
||||||
(crip "Rejoin disconnected group {<entity.rid>}/{<name.rid>}")
|
|
||||||
==
|
|
||||||
:_ out
|
:_ out
|
||||||
(try-rejoin rid 0)
|
(try-rejoin rid 0)
|
||||||
::
|
::
|
||||||
@ -610,11 +607,6 @@
|
|||||||
|= =action:store
|
|= =action:store
|
||||||
^- card
|
^- card
|
||||||
[%pass / %agent [our.bol %group-store] %poke %group-action !>(action)]
|
[%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
|
:: +send-diff: update subscribers of new state
|
||||||
::
|
::
|
||||||
:: We only allow subscriptions on /groups
|
:: We only allow subscriptions on /groups
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
/+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln
|
/+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln
|
||||||
|%
|
|%
|
||||||
+$ state
|
+$ state
|
||||||
$: %11
|
$: %12
|
||||||
drum=state:drum
|
drum=state:drum
|
||||||
helm=state:helm
|
helm=state:helm
|
||||||
kiln=state:kiln
|
kiln=state:kiln
|
||||||
@ -14,6 +14,7 @@
|
|||||||
[%8 drum=state:drum helm=state:helm kiln=state:kiln]
|
[%8 drum=state:drum helm=state:helm kiln=state:kiln]
|
||||||
[%9 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]
|
[%10 drum=state:drum helm=state:helm kiln=state:kiln]
|
||||||
|
[%11 drum=state:drum helm=state:helm kiln=state:kiln]
|
||||||
==
|
==
|
||||||
+$ any-state-tuple
|
+$ any-state-tuple
|
||||||
$: drum=any-state:drum
|
$: drum=any-state:drum
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
+$ versioned-state
|
+$ versioned-state
|
||||||
$% state-0
|
$% state-0
|
||||||
state-1
|
state-1
|
||||||
|
state-2
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ invitatory-0 (map serial:store invite-0)
|
+$ invitatory-0 (map serial:store invite-0)
|
||||||
@ -19,9 +20,10 @@
|
|||||||
::
|
::
|
||||||
+$ state-0 [%0 invites=(map path invitatory-0)]
|
+$ state-0 [%0 invites=(map path invitatory-0)]
|
||||||
+$ state-1 [%1 =invites:store]
|
+$ state-1 [%1 =invites:store]
|
||||||
|
+$ state-2 [%2 =invites:store]
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
=| state-1
|
=| state-2
|
||||||
=* state -
|
=* state -
|
||||||
%- agent:dbug
|
%- agent:dbug
|
||||||
^- agent:gall
|
^- agent:gall
|
||||||
@ -43,37 +45,22 @@
|
|||||||
++ on-load
|
++ on-load
|
||||||
|= old-vase=vase
|
|= old-vase=vase
|
||||||
=/ old !<(versioned-state old-vase)
|
=/ old !<(versioned-state old-vase)
|
||||||
|
=| cards=(list card)
|
||||||
|
|-
|
||||||
|
?: ?=(%2 -.old)
|
||||||
|
[cards this(state old)]
|
||||||
?: ?=(%1 -.old)
|
?: ?=(%1 -.old)
|
||||||
`this(state old)
|
=. cards
|
||||||
:- =- [%pass / %agent [our.bowl %invite-store] %poke %invite-action -]~
|
:~ =- [%pass / %agent [our.bowl %invite-store] %poke %invite-action -]
|
||||||
!> ^- action:store
|
!> ^- action:store
|
||||||
[%create %graph]
|
[%create %groups]
|
||||||
%= this
|
::
|
||||||
state
|
=- [%pass / %agent [our.bowl %invite-store] %poke %invite-action -]
|
||||||
:- %1
|
!> ^- action:store
|
||||||
%- ~(gas by *invites:store)
|
[%delete %contacts]
|
||||||
%+ murn ~(tap by invites.old)
|
==
|
||||||
|= [=path =invitatory-0]
|
$(-.old %2)
|
||||||
^- (unit [term invitatory:store])
|
$(old [%1 (~(gas by *invites:store) [%graph *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
|
|
||||||
==
|
|
||||||
==
|
|
||||||
::
|
::
|
||||||
++ on-agent on-agent:def
|
++ on-agent on-agent:def
|
||||||
++ on-arvo on-arvo:def
|
++ on-arvo on-arvo:def
|
||||||
@ -109,11 +96,19 @@
|
|||||||
++ poke-import
|
++ poke-import
|
||||||
|= arc=*
|
|= arc=*
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
=/ sty=state-1
|
=/ sty=state-2
|
||||||
:- %1
|
:- %2
|
||||||
%- remake-map-of-map
|
%- remake-map-of-map
|
||||||
;;((tree [term (tree [serial:store invite:store])]) +.arc)
|
;;((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
|
++ poke-invite-action
|
||||||
|= =action:store
|
|= =action:store
|
||||||
|
@ -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)
|
||||||
|
--
|
@ -91,6 +91,8 @@
|
|||||||
%herm
|
%herm
|
||||||
%contact-store
|
%contact-store
|
||||||
%contact-hook
|
%contact-hook
|
||||||
|
%contact-push-hook
|
||||||
|
%contact-pull-hook
|
||||||
%contact-view
|
%contact-view
|
||||||
%metadata-store
|
%metadata-store
|
||||||
%s3-store
|
%s3-store
|
||||||
@ -106,6 +108,7 @@
|
|||||||
%observe-hook
|
%observe-hook
|
||||||
%metadata-push-hook
|
%metadata-push-hook
|
||||||
%metadata-pull-hook
|
%metadata-pull-hook
|
||||||
|
%group-view
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ deft-fish :: default connects
|
++ deft-fish :: default connects
|
||||||
@ -251,6 +254,10 @@
|
|||||||
=> (se-born | %home %metadata-pull-hook)
|
=> (se-born | %home %metadata-pull-hook)
|
||||||
=> (se-born | %home %metadata-push-hook)
|
=> (se-born | %home %metadata-push-hook)
|
||||||
(se-born | %home %herm)
|
(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
|
..on-load
|
||||||
::
|
::
|
||||||
++ reap-phat :: ack connect
|
++ reap-phat :: ack connect
|
||||||
|
@ -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
|
/+ *contact-store
|
||||||
|_ upd=contact-update
|
::
|
||||||
|
|_ upd=update
|
||||||
++ grad %noun
|
++ grad %noun
|
||||||
++ grow
|
++ grow
|
||||||
|%
|
|%
|
||||||
++ noun upd
|
++ 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
|
++ 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
|
|
||||||
--
|
|
||||||
--
|
|
@ -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)
|
+$ rolodex (map ship contact)
|
||||||
+$ contacts (map ship contact)
|
|
||||||
+$ avatar
|
|
||||||
$% [%octt content-type=@t octs=[p=@ud q=@t]]
|
|
||||||
[%url url=@t]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
+$ contact
|
+$ contact
|
||||||
$: nickname=@t
|
$: nickname=@t
|
||||||
email=@t
|
bio=@t
|
||||||
phone=@t
|
status=@t
|
||||||
website=@t
|
|
||||||
notes=@t
|
|
||||||
color=@ux
|
color=@ux
|
||||||
avatar=(unit avatar)
|
avatar=(unit @t)
|
||||||
|
cover=(unit @t)
|
||||||
|
groups=(set resource)
|
||||||
|
last-updated=@da
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ edit-field
|
+$ edit-field
|
||||||
$% [%nickname nickname=@t]
|
$% [%nickname nickname=@t]
|
||||||
[%email email=@t]
|
[%bio bio=@t]
|
||||||
[%phone phone=@t]
|
[%status status=@t]
|
||||||
[%website website=@t]
|
|
||||||
[%notes notes=@t]
|
|
||||||
[%color color=@ux]
|
[%color color=@ux]
|
||||||
[%avatar avatar=(unit avatar)]
|
[%avatar avatar=(unit @t)]
|
||||||
|
[%add-group =resource]
|
||||||
|
[%remove-group =resource]
|
||||||
|
[%cover cover=(unit @t)]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ contact-action
|
+$ beings
|
||||||
$% [%create =path]
|
$% [%ships ships=(set ship)]
|
||||||
[%delete =path]
|
[%group =resource]
|
||||||
[%add =path =ship =contact]
|
|
||||||
[%remove =path =ship]
|
|
||||||
[%edit =path =ship =edit-field]
|
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ contact-update
|
+$ update
|
||||||
$% [%initial =rolodex]
|
$% [%initial =rolodex is-public=?]
|
||||||
[%contacts =path =contacts]
|
[%add =ship =contact]
|
||||||
contact-action
|
[%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]
|
|
||||||
==
|
|
||||||
--
|
|
@ -1,6 +1,6 @@
|
|||||||
/- spider,
|
/- spider,
|
||||||
graph=graph-store,
|
graph=graph-store,
|
||||||
metadata=metadata-store,
|
met=metadata-store,
|
||||||
*group,
|
*group,
|
||||||
group-store,
|
group-store,
|
||||||
inv=invite-store,
|
inv=invite-store,
|
||||||
@ -65,8 +65,8 @@
|
|||||||
::
|
::
|
||||||
:: Setup metadata
|
:: Setup metadata
|
||||||
::
|
::
|
||||||
=/ =metadatum:metadata
|
=/ =metadatum:met
|
||||||
%* . *metadatum:metadata
|
%* . *metadatum:met
|
||||||
title title.action
|
title title.action
|
||||||
description description.action
|
description description.action
|
||||||
date-created now.bowl
|
date-created now.bowl
|
||||||
@ -74,7 +74,7 @@
|
|||||||
module module.action
|
module module.action
|
||||||
preview %.n
|
preview %.n
|
||||||
==
|
==
|
||||||
=/ met-action=action:metadata
|
=/ met-action=action:met
|
||||||
[%add group graph+rid.action metadatum]
|
[%add group graph+rid.action metadatum]
|
||||||
;< ~ bind:m
|
;< ~ bind:m
|
||||||
(poke-our %metadata-push-hook metadata-update+!>(met-action))
|
(poke-our %metadata-push-hook metadata-update+!>(met-action))
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
<<<<<<< HEAD
|
||||||
/- spider, graph-view, graph=graph-store, metadata=metadata-store, *group
|
/- 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
|
/+ strandio, resource
|
||||||
=>
|
=>
|
||||||
|%
|
|%
|
||||||
@ -8,7 +12,11 @@
|
|||||||
::
|
::
|
||||||
++ scry-metadata
|
++ scry-metadata
|
||||||
|= rid=resource
|
|= rid=resource
|
||||||
|
<<<<<<< HEAD
|
||||||
=/ m (strand ,resource)
|
=/ m (strand ,resource)
|
||||||
|
=======
|
||||||
|
=/ m (strand ,(unit resource))
|
||||||
|
>>>>>>> origin/la/contact-store
|
||||||
;< group=(unit resource) bind:m
|
;< group=(unit resource) bind:m
|
||||||
%+ scry:strandio ,(unit resource)
|
%+ scry:strandio ,(unit resource)
|
||||||
;: weld
|
;: weld
|
||||||
@ -16,7 +24,11 @@
|
|||||||
(en-path:resource rid)
|
(en-path:resource rid)
|
||||||
/noun
|
/noun
|
||||||
==
|
==
|
||||||
|
<<<<<<< HEAD
|
||||||
(pure:m (need group))
|
(pure:m (need group))
|
||||||
|
=======
|
||||||
|
(pure:m group)
|
||||||
|
>>>>>>> origin/la/contact-store
|
||||||
::
|
::
|
||||||
++ scry-group
|
++ scry-group
|
||||||
|= rid=resource
|
|= rid=resource
|
||||||
|
@ -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
|
/+ strandio, res=resource
|
||||||
::
|
::
|
||||||
=* strand strand:spider
|
=* strand strand:spider
|
||||||
@ -34,21 +34,6 @@
|
|||||||
[our.bowl %group-pull-hook]
|
[our.bowl %group-pull-hook]
|
||||||
:- %pull-hook-action
|
:- %pull-hook-action
|
||||||
!>([%remove resource.update])
|
!>([%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
|
:: stop serving or syncing metadata associated with group
|
||||||
::
|
::
|
||||||
;< ~ bind:m
|
;< ~ bind:m
|
||||||
@ -65,7 +50,7 @@
|
|||||||
(en-path:res resource.update)
|
(en-path:res resource.update)
|
||||||
/noun
|
/noun
|
||||||
==
|
==
|
||||||
=/ entries=(list [m=md-resource:met g=resource:res =metadata:met])
|
=/ entries=(list [m=md-resource:met g=resource:res *])
|
||||||
~(tap by associations)
|
~(tap by associations)
|
||||||
|- ^- form:m
|
|- ^- form:m
|
||||||
=* loop $
|
=* loop $
|
||||||
@ -77,7 +62,7 @@
|
|||||||
%+ raw-poke
|
%+ raw-poke
|
||||||
[our.bowl %metadata-store]
|
[our.bowl %metadata-store]
|
||||||
:- %metadata-action
|
:- %metadata-action
|
||||||
!> ^- metadata-action:met
|
!> ^- action:met
|
||||||
[%remove g.i.entries m.i.entries]
|
[%remove g.i.entries m.i.entries]
|
||||||
:: archive graph associated with group
|
:: archive graph associated with group
|
||||||
::
|
::
|
||||||
|
@ -5,74 +5,50 @@ import { Contact, ContactEdit } from '~/types/contact-update';
|
|||||||
import { GroupPolicy, Resource } from '~/types/group-update';
|
import { GroupPolicy, Resource } from '~/types/group-update';
|
||||||
|
|
||||||
export default class ContactsApi extends BaseApi<StoreState> {
|
export default class ContactsApi extends BaseApi<StoreState> {
|
||||||
create(
|
add(ship: Patp, contact: any) {
|
||||||
name: string,
|
return this.storeAction({ add: { ship, contact } });
|
||||||
policy: Enc<GroupPolicy>,
|
|
||||||
title: string,
|
|
||||||
description: string
|
|
||||||
) {
|
|
||||||
return this.viewAction({
|
|
||||||
create: {
|
|
||||||
name,
|
|
||||||
policy,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
share(recipient: Patp, path: Patp, ship: Patp, contact: Contact) {
|
remove(ship: Patp) {
|
||||||
return this.viewAction({
|
return this.storeAction({ remove: { ship } });
|
||||||
share: {
|
|
||||||
recipient,
|
|
||||||
path,
|
|
||||||
ship,
|
|
||||||
contact,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(path: Path, ship: Patp) {
|
edit(ship: Patp, editField: ContactEdit) {
|
||||||
return this.viewAction({ remove: { path, ship } });
|
|
||||||
}
|
|
||||||
|
|
||||||
edit(path: Path, ship: Patp, editField: ContactEdit) {
|
|
||||||
/* editField can be...
|
/* editField can be...
|
||||||
{nickname: ''}
|
{nickname: ''}
|
||||||
{email: ''}
|
{email: ''}
|
||||||
{phone: ''}
|
{phone: ''}
|
||||||
{website: ''}
|
{website: ''}
|
||||||
{notes: ''}
|
|
||||||
{color: 'fff'} // with no 0x prefix
|
{color: 'fff'} // with no 0x prefix
|
||||||
{avatar: null}
|
{avatar: null}
|
||||||
{avatar: {url: ''}}
|
{avatar: ''}
|
||||||
|
{add-group: {ship, name}}
|
||||||
|
{remove-group: {ship, name}}
|
||||||
*/
|
*/
|
||||||
return this.hookAction({
|
console.log(ship, editField);
|
||||||
|
return this.storeAction({
|
||||||
edit: {
|
edit: {
|
||||||
path,
|
|
||||||
ship,
|
ship,
|
||||||
'edit-field': editField,
|
'edit-field': editField,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
invite(resource: Resource, ship: Patp, text = '') {
|
setPublic(setPublic: any) {
|
||||||
return this.viewAction({
|
return this.storeAction({
|
||||||
invite: { resource, ship, text },
|
'set-public': setPublic
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
join(resource: Resource) {
|
private storeAction(action: any): Promise<any> {
|
||||||
return this.viewAction({
|
return this.action('contact-store', 'contact-update', action)
|
||||||
join: resource,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private hookAction(data) {
|
private viewAction(threadName: string, action: any) {
|
||||||
return this.action('contact-hook', 'contact-action', data);
|
return this.spider('contact-view-action', 'json', threadName, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
private viewAction(data) {
|
private hookAction(ship: Patp, action: any): Promise<any> {
|
||||||
return this.action('contact-view', 'json', data);
|
return this.action('contact-push-hook', 'contact-update', action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { cite } from '~/logic/lib/util';
|
import { cite } from '~/logic/lib/util';
|
||||||
|
|
||||||
const indexes = new Map([
|
const indexes = new Map([
|
||||||
|
['ships', []],
|
||||||
['commands', []],
|
['commands', []],
|
||||||
['subscriptions', []],
|
['subscriptions', []],
|
||||||
['groups', []],
|
['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) {
|
const commandIndex = function (currentGroup) {
|
||||||
// commands are special cased for default suite
|
// commands are special cased for default suite
|
||||||
const commands = [];
|
const commands = [];
|
||||||
@ -62,7 +71,8 @@ const otherIndex = function() {
|
|||||||
return other;
|
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
|
// all metadata from all apps is indexed
|
||||||
// into subscriptions and landscape
|
// into subscriptions and landscape
|
||||||
const subscriptions = [];
|
const subscriptions = [];
|
||||||
@ -106,7 +116,7 @@ export default function index(associations, apps, currentGroup, groups) {
|
|||||||
title,
|
title,
|
||||||
`/~landscape${group}/join/${app}${each.resource}`,
|
`/~landscape${group}/join/${app}${each.resource}`,
|
||||||
app.charAt(0).toUpperCase() + app.slice(1),
|
app.charAt(0).toUpperCase() + app.slice(1),
|
||||||
(associations?.contacts?.[each.group]?.metadata?.title || null)
|
(associations?.groups?.[each.group]?.metadata?.title || null)
|
||||||
);
|
);
|
||||||
subscriptions.push(obj);
|
subscriptions.push(obj);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ export function getTitleFromWorkspace(
|
|||||||
case "home":
|
case "home":
|
||||||
return "DMs + Drafts";
|
return "DMs + Drafts";
|
||||||
case "group":
|
case "group":
|
||||||
const association = associations.contacts[workspace.group];
|
const association = associations.groups[workspace.group];
|
||||||
return association?.metadata?.title || "";
|
return association?.metadata?.title || "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,74 +5,60 @@ import { ContactUpdate } from '~/types/contact-update';
|
|||||||
|
|
||||||
type ContactState = Pick<StoreState, 'contacts'>;
|
type ContactState = Pick<StoreState, 'contacts'>;
|
||||||
|
|
||||||
export default class ContactReducer<S extends ContactState> {
|
export const ContactReducer = (json, state) => {
|
||||||
reduce(json: Cage, state: S) {
|
const data = _.get(json, 'contact-update', false);
|
||||||
const data = _.get(json, 'contact-update', false);
|
if (data) {
|
||||||
if (data) {
|
initial(data, state);
|
||||||
this.initial(data, state);
|
add(data, state);
|
||||||
this.create(data, state);
|
remove(data, state);
|
||||||
this.delete(data, state);
|
edit(data, state);
|
||||||
this.add(data, state);
|
setPublic(data, state);
|
||||||
this.remove(data, state);
|
|
||||||
this.edit(data, state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
initial(json: ContactUpdate, state: S) {
|
const initial = (json: ContactUpdate, state: S) => {
|
||||||
const data = _.get(json, 'initial', false);
|
const data = _.get(json, 'initial', false);
|
||||||
if (data) {
|
if (data) {
|
||||||
state.contacts = data;
|
state.contacts = data.rolodex;
|
||||||
}
|
state.isContactPublic = data['is-public'];
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
create(json: ContactUpdate, state: S) {
|
const add = (json: ContactUpdate, state: S) => {
|
||||||
const data = _.get(json, 'create', false);
|
const data = _.get(json, 'add', false);
|
||||||
if (data) {
|
if (data) {
|
||||||
state.contacts[data.path] = {};
|
state.contacts[data.ship] = data.contact;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
delete(json: ContactUpdate, state: S) {
|
const remove = (json: ContactUpdate, state: S) => {
|
||||||
const data = _.get(json, 'delete', false);
|
const data = _.get(json, 'remove', false);
|
||||||
if (data) {
|
if (
|
||||||
delete state.contacts[data.path];
|
data &&
|
||||||
}
|
(data.ship in state.contacts)
|
||||||
|
) {
|
||||||
|
delete state.contacts[data.ship];
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
add(json: ContactUpdate, state: S) {
|
const edit = (json: ContactUpdate, state: S) => {
|
||||||
const data = _.get(json, 'add', false);
|
const data = _.get(json, 'edit', false);
|
||||||
if (
|
const ship = `~${data.ship}`;
|
||||||
data &&
|
if (
|
||||||
(data.path in state.contacts)
|
data &&
|
||||||
) {
|
(ship in state.contacts)
|
||||||
state.contacts[data.path][data.ship] = data.contact;
|
) {
|
||||||
|
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]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -8,10 +8,10 @@ import LocalReducer from '../reducers/local';
|
|||||||
import { StoreState } from './type';
|
import { StoreState } from './type';
|
||||||
import { Timebox } from '~/types';
|
import { Timebox } from '~/types';
|
||||||
import { Cage } from '~/types/cage';
|
import { Cage } from '~/types/cage';
|
||||||
import ContactReducer from '../reducers/contact-update';
|
|
||||||
import S3Reducer from '../reducers/s3-update';
|
import S3Reducer from '../reducers/s3-update';
|
||||||
import { GraphReducer } from '../reducers/graph-update';
|
import { GraphReducer } from '../reducers/graph-update';
|
||||||
import { HarkReducer } from '../reducers/hark-update';
|
import { HarkReducer } from '../reducers/hark-update';
|
||||||
|
import { ContactReducer } from '../reducers/contact-update';
|
||||||
import GroupReducer from '../reducers/group-update';
|
import GroupReducer from '../reducers/group-update';
|
||||||
import LaunchReducer from '../reducers/launch-update';
|
import LaunchReducer from '../reducers/launch-update';
|
||||||
import ConnectionReducer from '../reducers/connection';
|
import ConnectionReducer from '../reducers/connection';
|
||||||
@ -25,7 +25,6 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
|||||||
inviteReducer = new InviteReducer();
|
inviteReducer = new InviteReducer();
|
||||||
metadataReducer = new MetadataReducer();
|
metadataReducer = new MetadataReducer();
|
||||||
localReducer = new LocalReducer();
|
localReducer = new LocalReducer();
|
||||||
contactReducer = new ContactReducer();
|
|
||||||
s3Reducer = new S3Reducer();
|
s3Reducer = new S3Reducer();
|
||||||
groupReducer = new GroupReducer();
|
groupReducer = new GroupReducer();
|
||||||
launchReducer = new LaunchReducer();
|
launchReducer = new LaunchReducer();
|
||||||
@ -58,7 +57,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
|||||||
baseHash: null,
|
baseHash: null,
|
||||||
invites: {},
|
invites: {},
|
||||||
associations: {
|
associations: {
|
||||||
contacts: {},
|
groups: {},
|
||||||
graph: {},
|
graph: {},
|
||||||
},
|
},
|
||||||
groups: {},
|
groups: {},
|
||||||
@ -79,6 +78,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
|||||||
},
|
},
|
||||||
credentials: null
|
credentials: null
|
||||||
},
|
},
|
||||||
|
isContactPublic: false,
|
||||||
contacts: {},
|
contacts: {},
|
||||||
notifications: new BigIntOrderedMap<Timebox>(),
|
notifications: new BigIntOrderedMap<Timebox>(),
|
||||||
archivedNotifications: new BigIntOrderedMap<Timebox>(),
|
archivedNotifications: new BigIntOrderedMap<Timebox>(),
|
||||||
@ -108,13 +108,13 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
|||||||
this.inviteReducer.reduce(data, this.state);
|
this.inviteReducer.reduce(data, this.state);
|
||||||
this.metadataReducer.reduce(data, this.state);
|
this.metadataReducer.reduce(data, this.state);
|
||||||
this.localReducer.reduce(data, this.state);
|
this.localReducer.reduce(data, this.state);
|
||||||
this.contactReducer.reduce(data, this.state);
|
|
||||||
this.s3Reducer.reduce(data, this.state);
|
this.s3Reducer.reduce(data, this.state);
|
||||||
this.groupReducer.reduce(data, this.state);
|
this.groupReducer.reduce(data, this.state);
|
||||||
this.launchReducer.reduce(data, this.state);
|
this.launchReducer.reduce(data, this.state);
|
||||||
this.connReducer.reduce(data, this.state);
|
this.connReducer.reduce(data, this.state);
|
||||||
GraphReducer(data, this.state);
|
GraphReducer(data, this.state);
|
||||||
HarkReducer(data, this.state);
|
HarkReducer(data, this.state);
|
||||||
|
ContactReducer(data, this.state);
|
||||||
this.settingsReducer.reduce(data, this.state);
|
this.settingsReducer.reduce(data, this.state);
|
||||||
GroupViewReducer(data, this.state);
|
GroupViewReducer(data, this.state);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import _ from 'lodash';
|
|||||||
type AppSubscription = [Path, string];
|
type AppSubscription = [Path, string];
|
||||||
|
|
||||||
const groupSubscriptions: AppSubscription[] = [
|
const groupSubscriptions: AppSubscription[] = [
|
||||||
['/synced', 'contact-hook']
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const graphSubscriptions: AppSubscription[] = [
|
const graphSubscriptions: AppSubscription[] = [
|
||||||
@ -37,8 +36,8 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
|
|||||||
this.subscribe('/groups', 'group-store');
|
this.subscribe('/groups', 'group-store');
|
||||||
this.clearQueue();
|
this.clearQueue();
|
||||||
|
|
||||||
|
// TODO: update to get /updates
|
||||||
this.subscribe('/primary', 'contact-view');
|
this.subscribe('/all', 'contact-store');
|
||||||
this.subscribe('/all', 's3-store');
|
this.subscribe('/all', 's3-store');
|
||||||
this.subscribe('/keys', 'graph-store');
|
this.subscribe('/keys', 'graph-store');
|
||||||
this.subscribe('/updates', 'hark-store');
|
this.subscribe('/updates', 'hark-store');
|
||||||
|
@ -18,7 +18,7 @@ export type Serial = string;
|
|||||||
export type Jug<K,V> = Map<K,Set<V>>;
|
export type Jug<K,V> = Map<K,Set<V>>;
|
||||||
|
|
||||||
// name of app
|
// 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 {
|
export function getTagFromFrond<O>(frond: O): keyof O {
|
||||||
const tags = Object.keys(frond) as Array<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 notificationsCount = state.notificationsCount || 0;
|
||||||
const doNotDisturb = state.doNotDisturb || false;
|
const doNotDisturb = state.doNotDisturb || false;
|
||||||
|
const ourContact = this.state.contacts[`~${this.ship}`] || null;
|
||||||
const showBanner = localStorage.getItem("2020BreachBanner") || "flex";
|
|
||||||
let banner = null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
@ -156,6 +154,7 @@ class App extends React.Component {
|
|||||||
props={this.props}
|
props={this.props}
|
||||||
associations={associations}
|
associations={associations}
|
||||||
invites={this.state.invites}
|
invites={this.state.invites}
|
||||||
|
ourContact={ourContact}
|
||||||
api={this.api}
|
api={this.api}
|
||||||
connection={this.state.connection}
|
connection={this.state.connection}
|
||||||
subscription={this.subscription}
|
subscription={this.subscription}
|
||||||
@ -169,6 +168,7 @@ class App extends React.Component {
|
|||||||
associations={state.associations}
|
associations={state.associations}
|
||||||
apps={state.launch}
|
apps={state.launch}
|
||||||
api={this.api}
|
api={this.api}
|
||||||
|
contacts={state.contacts}
|
||||||
notifications={state.notificationsCount}
|
notifications={state.notificationsCount}
|
||||||
invites={state.invites}
|
invites={state.invites}
|
||||||
groups={state.groups}
|
groups={state.groups}
|
||||||
|
@ -24,7 +24,7 @@ export function ChatResource(props: ChatResourceProps) {
|
|||||||
const station = props.association.resource;
|
const station = props.association.resource;
|
||||||
const groupPath = props.association.group;
|
const groupPath = props.association.group;
|
||||||
const group = props.groups[groupPath];
|
const group = props.groups[groupPath];
|
||||||
const contacts = props.contacts[groupPath] || {};
|
const contacts = props.contacts;
|
||||||
|
|
||||||
const graph = props.graphs[station.slice(7)];
|
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 unreadCount = props.unreads.graph?.[station]?.['/']?.unreads || 0;
|
||||||
|
|
||||||
const [,, owner, name] = station.split('/');
|
const [,, owner, name] = station.split('/');
|
||||||
const ourContact = contacts?.[window.ship];
|
const ourContact = contacts?.[`~${window.ship}`];
|
||||||
|
|
||||||
const chatInput = useRef<ChatInput>();
|
const chatInput = useRef<ChatInput>();
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ export const MessageWithSigil = (props) => {
|
|||||||
const dark = useLocalState(state => state.dark);
|
const dark = useLocalState(state => state.dark);
|
||||||
|
|
||||||
const datestamp = moment.unix(msg['time-sent'] / 1000).format(DATESTAMP_FORMAT);
|
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 showNickname = useShowNickname(contact);
|
||||||
const name = showNickname ? contact.nickname : cite(msg.author);
|
const name = showNickname ? contact.nickname : cite(msg.author);
|
||||||
const color = contact ? `#${uxToHex(contact.color)}` : dark ? '#000000' :'#FFFFFF'
|
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);
|
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.flow(
|
||||||
f.pickBy((a: Association) => a.group === path),
|
f.pickBy((a: Association) => a.group === path),
|
||||||
f.map('resource'),
|
f.map('resource'),
|
||||||
@ -24,7 +24,7 @@ const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path:
|
|||||||
f.reduce(f.add, 0)
|
f.reduce(f.add, 0)
|
||||||
)(associations.graph);
|
)(associations.graph);
|
||||||
|
|
||||||
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
|
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
|
||||||
f.flow(
|
f.flow(
|
||||||
f.pickBy((a: Association) => a.group === path),
|
f.pickBy((a: Association) => a.group === path),
|
||||||
f.map('resource'),
|
f.map('resource'),
|
||||||
@ -36,7 +36,7 @@ const getGraphNotifications = (associations: Associations, unreads: Unreads) =>
|
|||||||
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||||
const { associations, unreads, inbox, ...boxProps } = props;
|
const { associations, unreads, inbox, ...boxProps } = props;
|
||||||
|
|
||||||
const groups = Object.values(associations?.contacts || {})
|
const groups = Object.values(associations?.groups || {})
|
||||||
.filter((e) => e?.group in props.groups)
|
.filter((e) => e?.group in props.groups)
|
||||||
.sort(sortGroupsAlph);
|
.sort(sortGroupsAlph);
|
||||||
const graphUnreads = getGraphUnreads(associations || {}, unreads);
|
const graphUnreads = getGraphUnreads(associations || {}, unreads);
|
||||||
@ -78,10 +78,10 @@ function Group(props: GroupProps) {
|
|||||||
<Col height="100%" justifyContent="space-between">
|
<Col height="100%" justifyContent="space-between">
|
||||||
<Text>{title}</Text>
|
<Text>{title}</Text>
|
||||||
<Col>
|
<Col>
|
||||||
{unreads > 0 &&
|
{unreads > 0 &&
|
||||||
(<Text gray>{unreads} unread{unreads !== 1 && 's'} </Text>)
|
(<Text gray>{unreads} unread{unreads !== 1 && 's'} </Text>)
|
||||||
}
|
}
|
||||||
{updates > 0 &&
|
{updates > 0 &&
|
||||||
(<Text mt="1" color="blue">{updates} update{updates !== 1 && 's'} </Text>)
|
(<Text mt="1" color="blue">{updates} update{updates !== 1 && 's'} </Text>)
|
||||||
}
|
}
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -48,7 +48,6 @@ export function LinkWindow(props: LinkWindowProps) {
|
|||||||
}, [graph.size]);
|
}, [graph.size]);
|
||||||
|
|
||||||
const first = graph.peekLargest()?.[0];
|
const first = graph.peekLargest()?.[0];
|
||||||
|
|
||||||
const [,,ship, name] = association.resource.split('/');
|
const [,,ship, name] = association.resource.split('/');
|
||||||
|
|
||||||
const style = useMemo(() =>
|
const style = useMemo(() =>
|
||||||
|
@ -63,7 +63,7 @@ export function Header(props: {
|
|||||||
|
|
||||||
const time = moment(props.time).format("HH:mm");
|
const time = moment(props.time).format("HH:mm");
|
||||||
const groupTitle =
|
const groupTitle =
|
||||||
props.associations.contacts?.[props.group]?.metadata?.title;
|
props.associations.groups?.[props.group]?.metadata?.title;
|
||||||
|
|
||||||
const app = props.chat ? 'chat' : 'graph';
|
const app = props.chat ? 'chat' : 'graph';
|
||||||
const channelTitle =
|
const channelTitle =
|
||||||
|
@ -9,7 +9,6 @@ import { BigInteger } from "big-integer";
|
|||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { Notification } from "./notification";
|
import { Notification } from "./notification";
|
||||||
import { Associations } from "~/types";
|
import { Associations } from "~/types";
|
||||||
import { cite } from '~/logic/lib/util';
|
|
||||||
import { InviteItem } from '~/views/components/Invite';
|
import { InviteItem } from '~/views/components/Invite';
|
||||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
@ -21,8 +21,6 @@ interface InvitesProps {
|
|||||||
export function Invites(props: InvitesProps) {
|
export function Invites(props: InvitesProps) {
|
||||||
const { api, invites, pendingJoin } = props;
|
const { api, invites, pendingJoin } = props;
|
||||||
const [selected, setSelected] = useState<[string, string, Invite] | undefined>()
|
const [selected, setSelected] = useState<[string, string, Invite] | undefined>()
|
||||||
const history = useHistory();
|
|
||||||
const waiter = useWaitForProps(props);
|
|
||||||
|
|
||||||
const acceptInvite = (
|
const acceptInvite = (
|
||||||
app: string,
|
app: string,
|
||||||
|
@ -44,7 +44,7 @@ export default function NotificationsScreen(props: any) {
|
|||||||
filter.groups.length === 0
|
filter.groups.length === 0
|
||||||
? "All"
|
? "All"
|
||||||
: filter.groups
|
: filter.groups
|
||||||
.map((g) => props.associations?.contacts?.[g]?.metadata?.title)
|
.map((g) => props.associations?.groups?.[g]?.metadata?.title)
|
||||||
.join(", ");
|
.join(", ");
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
|
@ -135,6 +135,7 @@ export function ContactCard(props: ContactCardProps) {
|
|||||||
gridTemplateColumns="100%"
|
gridTemplateColumns="100%"
|
||||||
gridRowGap="5"
|
gridRowGap="5"
|
||||||
maxWidth="400px"
|
maxWidth="400px"
|
||||||
|
width="100%"
|
||||||
>
|
>
|
||||||
<Row
|
<Row
|
||||||
borderBottom={1}
|
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 React from "react";
|
||||||
import { Route, Link, Switch } from "react-router-dom";
|
import { Route, Link } from "react-router-dom";
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import { Box, Text, Row, Col, Icon, BaseImage } from "@tlon/indigo-react";
|
import { Box, Text, Row, Col, Icon, BaseImage } from "@tlon/indigo-react";
|
||||||
|
|
||||||
import { Sigil } from "~/logic/lib/sigil";
|
import { uxToHex } from "~/logic/lib/util";
|
||||||
import { uxToHex, MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
|
|
||||||
|
|
||||||
import Settings from "./components/settings";
|
import { Profile } from "./components/Profile";
|
||||||
import { ContactCard } from "~/views/landscape/components/ContactCard";
|
|
||||||
import useLocalState from "~/logic/state/local";
|
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) {
|
export default function ProfileScreen(props: any) {
|
||||||
const { ship, dark } = props;
|
const { dark } = props;
|
||||||
const hideAvatars = useLocalState(state => state.hideAvatars);
|
const hideAvatars = useLocalState(state => state.hideAvatars);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet defer={false}>
|
<Helmet defer={false}>
|
||||||
<title>{ props.notificationsCount ? `(${String(props.notificationsCount) }) `: '' }Landscape - Profile</title>
|
<title>{ props.notificationsCount ? `(${String(props.notificationsCount) }) `: '' }Landscape - Profile</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Switch>
|
|
||||||
<Route
|
<Route
|
||||||
path={["/~profile/:view", "/~profile"]}
|
path={"/~profile/:ship/:edit?"}
|
||||||
render={({ match, history }) => {
|
render={({ match, history }) => {
|
||||||
const { view } = match.params;
|
const ship = match.params.ship;
|
||||||
const contact = props.contacts?.["/~/default"]?.[window.ship];
|
const isEdit = match.url.includes('edit');
|
||||||
|
const isPublic = props.isContactPublic;
|
||||||
|
const contact = props.contacts?.[ship];
|
||||||
const sigilColor = contact?.color
|
const sigilColor = contact?.color
|
||||||
? `#${uxToHex(contact.color)}`
|
? `#${uxToHex(contact.color)}`
|
||||||
: dark
|
: dark
|
||||||
? "#FFFFFF"
|
? "#FFFFFF"
|
||||||
: "#000000";
|
: "#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 (
|
return (
|
||||||
<Box height="100%" px={[0, 3]} pb={[0, 3]} borderRadius={1}>
|
<Box height="100%" px={[0, 3]} pb={[0, 3]} borderRadius={1}>
|
||||||
<Box
|
<Box
|
||||||
height="100%"
|
height="100%"
|
||||||
width="100%"
|
width="100%"
|
||||||
display="grid"
|
|
||||||
gridTemplateColumns={["100%", "250px 1fr"]}
|
|
||||||
gridTemplateRows={["48px 1fr", "1fr"]}
|
|
||||||
borderRadius={1}
|
borderRadius={1}
|
||||||
bg="white"
|
bg="white"
|
||||||
border={1}
|
border={1}
|
||||||
borderColor="washedGray"
|
borderColor="washedGray"
|
||||||
|
overflowY="auto"
|
||||||
|
flexGrow
|
||||||
>
|
>
|
||||||
<Col
|
<Box>
|
||||||
display={!view ? "flex" : ["none", "flex"]}
|
<Profile
|
||||||
alignItems="center"
|
ship={ship}
|
||||||
borderRight={1}
|
associations={props.associations}
|
||||||
borderColor="washedGray"
|
groups={props.groups}
|
||||||
>
|
contact={contact}
|
||||||
<Box width="100%" borderBottom={1} borderBottomColor="washedGray">
|
api={props.api}
|
||||||
<Box
|
s3={props.s3}
|
||||||
mx="auto"
|
isEdit={isEdit}
|
||||||
bg={sigilColor}
|
isPublic={isPublic}
|
||||||
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>
|
</Box>
|
||||||
</Box>
|
</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 {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Text,
|
Text,
|
||||||
@ -6,17 +6,17 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
Icon,
|
Icon,
|
||||||
ErrorLabel,
|
ErrorLabel
|
||||||
} from "@tlon/indigo-react";
|
} from '@tlon/indigo-react';
|
||||||
import _ from "lodash";
|
import _ from 'lodash';
|
||||||
import { useField } from "formik";
|
import { useField } from 'formik';
|
||||||
import styled from "styled-components";
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { roleForShip } from "~/logic/lib/group";
|
import { roleForShip } from '~/logic/lib/group';
|
||||||
|
|
||||||
import { DropdownSearch } from "./DropdownSearch";
|
import { DropdownSearch } from './DropdownSearch';
|
||||||
import { Groups } from "~/types";
|
import { Groups } from '~/types';
|
||||||
import { Associations, Association } from "~/types/metadata-update";
|
import { Associations, Association } from '~/types/metadata-update';
|
||||||
|
|
||||||
interface InviteSearchProps {
|
interface InviteSearchProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@ -26,11 +26,12 @@ interface InviteSearchProps {
|
|||||||
label: string;
|
label: string;
|
||||||
caption?: string;
|
caption?: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
maxLength?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CandidateBox = styled(Box)<{ selected: boolean }>`
|
const CandidateBox = styled(Box)<{ selected: boolean }>`
|
||||||
&:hover {
|
&: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) {
|
export function GroupSearch(props: InviteSearchProps) {
|
||||||
const { id, caption, label } = props;
|
const { id, caption, label } = props;
|
||||||
|
const [selected, setSelected] = useState([] as string[]);
|
||||||
const groups: Association[] = useMemo(() => {
|
const groups: Association[] = useMemo(() => {
|
||||||
return props.adminOnly
|
return props.adminOnly
|
||||||
? Object.values(
|
? Object.values(
|
||||||
Object.keys(props.associations?.contacts)
|
Object.keys(props.associations?.groups)
|
||||||
.filter(
|
.filter(
|
||||||
(e) => roleForShip(props.groups[e], window.ship) === "admin"
|
e => roleForShip(props.groups[e], window.ship) === 'admin'
|
||||||
)
|
)
|
||||||
.reduce((obj, key) => {
|
.reduce((obj, key) => {
|
||||||
obj[key] = props.associations?.contacts[key];
|
obj[key] = props.associations?.groups[key];
|
||||||
return obj;
|
return obj;
|
||||||
}, {}) || {}
|
}, {}) || {}
|
||||||
)
|
)
|
||||||
: Object.values(props.associations?.contacts || {});
|
: Object.values(props.associations?.groups || {});
|
||||||
}, [props.associations?.contacts]);
|
}, [props.associations?.groups]);
|
||||||
|
|
||||||
const [{ value }, meta, { setValue, setTouched }] = useField(props.id);
|
const [{ value }, meta, { setValue, setTouched }] = useField(props.id);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(selected);
|
||||||
|
}, [selected])
|
||||||
|
|
||||||
const { title: groupTitle } =
|
const { title: groupTitle } =
|
||||||
props.associations.contacts?.[value]?.metadata || {};
|
props.associations.groups?.[value]?.metadata || {};
|
||||||
|
|
||||||
const onSelect = useCallback(
|
const onSelect = useCallback(
|
||||||
(a: Association) => {
|
(s: string) => {
|
||||||
setValue(a.group);
|
|
||||||
setTouched(true);
|
setTouched(true);
|
||||||
|
setSelected(v => _.uniq([...v, s]));
|
||||||
},
|
},
|
||||||
[setValue]
|
[setTouched, setSelected]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onUnselect = useCallback(() => {
|
const onRemove = useCallback(
|
||||||
setValue(undefined);
|
(s: string) => {
|
||||||
setTouched(true);
|
setSelected(groups => groups.filter(group => group !== s))
|
||||||
}, [setValue]);
|
},
|
||||||
|
[setSelected]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col>
|
<Col>
|
||||||
@ -105,25 +113,11 @@ export function GroupSearch(props: InviteSearchProps) {
|
|||||||
{caption}
|
{caption}
|
||||||
</Label>
|
</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>
|
<DropdownSearch<Association>
|
||||||
mt="2"
|
mt="2"
|
||||||
candidates={groups}
|
candidates={groups}
|
||||||
|
placeholder="Search for groups..."
|
||||||
|
disabled={props.maxLength ? selected.length >= props.maxLength : false}
|
||||||
renderCandidate={renderCandidate}
|
renderCandidate={renderCandidate}
|
||||||
search={(s: string, a: Association) =>
|
search={(s: string, a: Association) =>
|
||||||
a.metadata.title.toLowerCase().startsWith(s.toLowerCase())
|
a.metadata.title.toLowerCase().startsWith(s.toLowerCase())
|
||||||
@ -131,8 +125,27 @@ export function GroupSearch(props: InviteSearchProps) {
|
|||||||
getKey={(a: Association) => a.group}
|
getKey={(a: Association) => a.group}
|
||||||
onSelect={onSelect}
|
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}
|
{meta.error}
|
||||||
</ErrorLabel>
|
</ErrorLabel>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -110,7 +110,6 @@ class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
cursor='pointer'
|
|
||||||
position='relative'
|
position='relative'
|
||||||
onClick={this.profileShow}
|
onClick={this.profileShow}
|
||||||
ref={this.containerRef}
|
ref={this.containerRef}
|
||||||
|
@ -4,7 +4,8 @@ import { Contact, Group } from '~/types';
|
|||||||
import { cite, useShowNickname } from '~/logic/lib/util';
|
import { cite, useShowNickname } from '~/logic/lib/util';
|
||||||
import { Sigil } from '~/logic/lib/sigil';
|
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';
|
import { withLocalState } from '~/logic/state/local';
|
||||||
|
|
||||||
export const OVERLAY_HEIGHT = 250;
|
export const OVERLAY_HEIGHT = 250;
|
||||||
@ -25,11 +26,13 @@ type ProfileOverlayProps = ColProps & {
|
|||||||
|
|
||||||
class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
||||||
public popoverRef: React.Ref<typeof Col>;
|
public popoverRef: React.Ref<typeof Col>;
|
||||||
|
public dropdownRef: React.Ref<typeof Col>;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.popoverRef = React.createRef();
|
this.popoverRef = React.createRef();
|
||||||
|
this.dropdownRef = React.createRef();
|
||||||
this.onDocumentClick = this.onDocumentClick.bind(this);
|
this.onDocumentClick = this.onDocumentClick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +47,9 @@ class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDocumentClick(event) {
|
onDocumentClick(event) {
|
||||||
const { popoverRef } = this;
|
const { popoverRef, dropdownRef } = this;
|
||||||
// Do nothing if clicking ref's element or descendent elements
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +63,6 @@ class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
|||||||
color,
|
color,
|
||||||
topSpace,
|
topSpace,
|
||||||
bottomSpace,
|
bottomSpace,
|
||||||
group = false,
|
|
||||||
hideAvatars,
|
hideAvatars,
|
||||||
hideNicknames,
|
hideNicknames,
|
||||||
history,
|
history,
|
||||||
@ -78,75 +80,113 @@ class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
|||||||
if (!(top || bottom)) {
|
if (!(top || bottom)) {
|
||||||
bottom = `-${Math.round(OVERLAY_HEIGHT / 2)}px`;
|
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 isOwn = window.ship === ship;
|
||||||
|
|
||||||
const img = contact?.avatar && !hideAvatars
|
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
|
: <Sigil
|
||||||
ship={ship}
|
ship={ship}
|
||||||
size={160}
|
size={72}
|
||||||
color={color}
|
color={color}
|
||||||
classes="brt2"
|
classes="brt2"
|
||||||
svgClass="brt2"
|
svgClass="brt2"
|
||||||
/>;
|
/>;
|
||||||
const showNickname = useShowNickname(contact, hideNicknames);
|
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"));
|
const rootSettings = history.location.pathname.slice(0, history.location.pathname.indexOf("/resource"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col
|
<Col
|
||||||
ref={this.popoverRef}
|
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'
|
position='absolute'
|
||||||
backgroundColor='white'
|
|
||||||
zIndex='3'
|
zIndex='3'
|
||||||
fontSize='0'
|
fontSize='0'
|
||||||
|
height="250px"
|
||||||
|
width="250px"
|
||||||
|
padding={3}
|
||||||
|
justifyContent="space-between"
|
||||||
style={containerStyle}
|
style={containerStyle}
|
||||||
{...rest}
|
{...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}
|
{img}
|
||||||
</Box>
|
</Box>
|
||||||
<Box p='3'>
|
<Col height="3rem" alignItems="end" justifyContent="flex-end">
|
||||||
{showNickname && (
|
|
||||||
<Text
|
<Text
|
||||||
fontWeight='600'
|
fontWeight='600'
|
||||||
|
mono={!showNickname}
|
||||||
display='block'
|
display='block'
|
||||||
textOverflow='ellipsis'
|
textOverflow='ellipsis'
|
||||||
overflow='hidden'
|
overflow='hidden'
|
||||||
whiteSpace='pre'
|
whiteSpace='pre'
|
||||||
|
lineHeight="tall"
|
||||||
>
|
>
|
||||||
{contact.nickname}
|
{showNickname ? contact.nickname : cite(ship)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
<Text
|
||||||
<Text mono gray>{cite(`~${ship}`)}</Text>
|
contentEditable={isOwn}
|
||||||
{!isOwn && (
|
display={(!contact?.status && !isOwn) ? 'none' : 'inline'}
|
||||||
<Button mt={2} fontSize='0' width="100%" style={{ cursor: 'pointer' }} onClick={() => history.push(`/~landscape/dm/${ship}`)}>
|
gray={(!contact?.status && isOwn)}
|
||||||
Send Message
|
// onBlur={() => api.contacts.edit()...}
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{(isOwn) ? (
|
|
||||||
<Button
|
|
||||||
mt='2'
|
|
||||||
width='100%'
|
|
||||||
style={{ cursor: 'pointer ' }}
|
|
||||||
onClick={() => (isHidden) ? history.push('/~profile/identity') : history.push(`${rootSettings}/popover/profile`)}
|
|
||||||
>
|
>
|
||||||
Edit Identity
|
{(!contact?.status && isOwn) ? "Set a status" : contact.status}
|
||||||
</Button>
|
</Text>
|
||||||
) : <div />}
|
</Col>
|
||||||
</Box>
|
|
||||||
</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);
|
const result = ob.isValidPatp(ship);
|
||||||
return result ? deSig(s) ?? undefined : undefined;
|
return result ? deSig(s) ?? undefined : undefined;
|
||||||
}}
|
}}
|
||||||
placeholder="Search for ships"
|
placeholder="Search for ships..."
|
||||||
candidates={peers}
|
candidates={peers}
|
||||||
renderCandidate={renderCandidate}
|
renderCandidate={renderCandidate}
|
||||||
disabled={props.maxLength ? selected.length >= props.maxLength : false}
|
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 ReconnectButton from './ReconnectButton';
|
||||||
|
import { Dropdown } from './Dropdown';
|
||||||
import { StatusBarItem } from './StatusBarItem';
|
import { StatusBarItem } from './StatusBarItem';
|
||||||
import { Sigil } from '~/logic/lib/sigil';
|
import { Sigil } from '~/logic/lib/sigil';
|
||||||
|
import { uxToHex } from "~/logic/lib/util";
|
||||||
|
import { SetStatusBarModal } from './SetStatusBarModal';
|
||||||
|
|
||||||
import useLocalState from '~/logic/state/local';
|
import useLocalState from '~/logic/state/local';
|
||||||
import { cite } from '~/logic/lib/util';
|
|
||||||
|
|
||||||
const StatusBar = (props) => {
|
const StatusBar = (props) => {
|
||||||
|
const { ourContact, api, ship } = props;
|
||||||
const invites = [].concat(...Object.values(props.invites).map(obj => Object.values(obj)));
|
const invites = [].concat(...Object.values(props.invites).map(obj => Object.values(obj)));
|
||||||
const metaKey = (window.navigator.platform.includes('Mac')) ? '⌘' : 'Ctrl+';
|
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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
display='grid'
|
display='grid'
|
||||||
@ -25,7 +57,6 @@ const StatusBar = (props) => {
|
|||||||
<Button width="32px" borderColor='washedGray' mr='2' px='2' onClick={() => props.history.push('/')} {...props}>
|
<Button width="32px" borderColor='washedGray' mr='2' px='2' onClick={() => props.history.push('/')} {...props}>
|
||||||
<Icon icon='Spaces' color='black'/>
|
<Icon icon='Spaces' color='black'/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<StatusBarItem mr={2} onClick={() => toggleOmnibox()}>
|
<StatusBarItem mr={2} onClick={() => toggleOmnibox()}>
|
||||||
{ !props.doNotDisturb && (props.notificationsCount > 0 || invites.length > 0) &&
|
{ !props.doNotDisturb && (props.notificationsCount > 0 || invites.length > 0) &&
|
||||||
(<Box display="block" right="-8px" top="-8px" position="absolute" >
|
(<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>
|
<Text color='#000000'>Submit <Text color='#000000' display={['none', 'inline']}>an</Text> issue</Text>
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
<StatusBarItem width={['32px', 'auto']} px={'2'} flexShrink='0' onClick={() => props.history.push('/~profile')}>
|
<Dropdown
|
||||||
<Sigil ship={props.ship} size={16} color='black' classes='mix-blend-diff' icon />
|
dropWidth="150px"
|
||||||
</StatusBarItem>
|
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>
|
</Row>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -32,7 +32,7 @@ export class Omnibox extends Component {
|
|||||||
const { pathname } = this.props.location;
|
const { pathname } = this.props.location;
|
||||||
const selectedGroup = pathname.startsWith('/~landscape/ship/') ? '/' + pathname.split('/').slice(2,5).join('/') : null;
|
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 === '')) {
|
if (prevProps && (prevProps.apps !== this.props.apps) && (this.state.query === '')) {
|
||||||
@ -56,7 +56,7 @@ export class Omnibox extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSearchedCategories() {
|
getSearchedCategories() {
|
||||||
return ['other', 'commands', 'groups', 'subscriptions', 'apps'];
|
return ['ships', 'other', 'commands', 'groups', 'subscriptions', 'apps'];
|
||||||
}
|
}
|
||||||
|
|
||||||
control(evt) {
|
control(evt) {
|
||||||
@ -249,6 +249,7 @@ export class Omnibox extends Component {
|
|||||||
selected={selected}
|
selected={selected}
|
||||||
invites={props.invites}
|
invites={props.invites}
|
||||||
notifications={props.notifications}
|
notifications={props.notifications}
|
||||||
|
contacts={props.contacts}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
|||||||
import { Box, Row, Icon, Text } from '@tlon/indigo-react';
|
import { Box, Row, Icon, Text } from '@tlon/indigo-react';
|
||||||
import defaultApps from '~/logic/lib/default-apps';
|
import defaultApps from '~/logic/lib/default-apps';
|
||||||
import Sigil from '~/logic/lib/sigil';
|
import Sigil from '~/logic/lib/sigil';
|
||||||
|
import { uxToHex } from '~/logic/lib/util';
|
||||||
|
|
||||||
export class OmniboxResult extends Component {
|
export class OmniboxResult extends Component {
|
||||||
constructor(props) {
|
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 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 bulletFill = (this.state.hovered || (selected === link)) ? 'white' : 'blue';
|
||||||
|
|
||||||
const inviteCount = [].concat(...Object.values(invites).map(obj => Object.values(obj)));
|
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 = (icon === 'Link') ? 'Collection' :
|
||||||
(icon === 'Terminal') ? 'Dojo' : icon;
|
(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') {
|
} else if (icon === 'inbox') {
|
||||||
graphic = <Box display='flex' verticalAlign='middle' position="relative">
|
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) && (
|
{(notifications > 0 || inviteCount.length > 0) && (
|
||||||
<Icon display='inline-block' icon='Bullet' style={{ position: 'absolute', top: -5, left: 5 }} color={bulletFill} />
|
<Icon display='inline-block' icon='Bullet' style={{ position: 'absolute', top: -5, left: 5 }} color={bulletFill} />
|
||||||
)}
|
)}
|
||||||
</Box>;
|
</Box>;
|
||||||
} else if (icon === 'logout') {
|
} 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') {
|
} 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') {
|
} 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') {
|
} 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 {
|
} else {
|
||||||
graphic = <Icon display='inline-block' icon='NullIcon' verticalAlign="middle" mr='2' size="16px" color={iconFill} />;
|
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() {
|
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 (
|
return (
|
||||||
<Row
|
<Row
|
||||||
@ -89,6 +91,7 @@ export class OmniboxResult extends Component {
|
|||||||
<Text
|
<Text
|
||||||
display="inline-block"
|
display="inline-block"
|
||||||
verticalAlign="middle"
|
verticalAlign="middle"
|
||||||
|
mono={(icon == 'profile' && text.startsWith('~'))}
|
||||||
color={this.state.hovered || selected === link ? 'white' : 'black'}
|
color={this.state.hovered || selected === link ? 'white' : 'black'}
|
||||||
maxWidth="60%"
|
maxWidth="60%"
|
||||||
style={{ flexShrink: 0 }}
|
style={{ flexShrink: 0 }}
|
||||||
|
@ -7,6 +7,7 @@ import LaunchApp from '~/views/apps/launch/app';
|
|||||||
import TermApp from '~/views/apps/term/app';
|
import TermApp from '~/views/apps/term/app';
|
||||||
import Landscape from '~/views/landscape/index';
|
import Landscape from '~/views/landscape/index';
|
||||||
import Profile from '~/views/apps/profile/profile';
|
import Profile from '~/views/apps/profile/profile';
|
||||||
|
import Settings from '~/views/apps/settings/settings';
|
||||||
import ErrorComponent from '~/views/components/Error';
|
import ErrorComponent from '~/views/components/Error';
|
||||||
import Notifications from '~/views/apps/notifications/notifications';
|
import Notifications from '~/views/apps/notifications/notifications';
|
||||||
import GraphApp from '../../apps/graph/app';
|
import GraphApp from '../../apps/graph/app';
|
||||||
@ -63,6 +64,14 @@ export const Content = (props) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/~settings"
|
||||||
|
render={ p => (
|
||||||
|
<Settings
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/~notifications"
|
path="/~notifications"
|
||||||
render={ p => (
|
render={ p => (
|
||||||
|
@ -42,9 +42,9 @@ function RecentGroups(props: { recent: string[]; associations: Associations }) {
|
|||||||
Recent Groups
|
Recent Groups
|
||||||
</Box>
|
</Box>
|
||||||
{props.recent.filter((e) => {
|
{props.recent.filter((e) => {
|
||||||
return (e in associations?.contacts);
|
return (e in associations?.groups);
|
||||||
}).slice(1, 5).map((g) => {
|
}).slice(1, 5).map((g) => {
|
||||||
const assoc = associations.contacts[g];
|
const assoc = associations.groups[g];
|
||||||
const color = uxToHex(assoc?.metadata?.color || '0x0');
|
const color = uxToHex(assoc?.metadata?.color || '0x0');
|
||||||
return (
|
return (
|
||||||
<Link key={g} style={{ minWidth: 0 }} to={`/~landscape${g}`}>
|
<Link key={g} style={{ minWidth: 0 }} to={`/~landscape${g}`}>
|
||||||
@ -78,7 +78,7 @@ export function GroupSwitcher(props: {
|
|||||||
}) {
|
}) {
|
||||||
const { associations, workspace, isAdmin } = props;
|
const { associations, workspace, isAdmin } = props;
|
||||||
const title = getTitleFromWorkspace(associations, workspace);
|
const title = getTitleFromWorkspace(associations, workspace);
|
||||||
const metadata = workspace.type === 'home' ? undefined : associations.contacts[workspace.group].metadata;
|
const metadata = workspace.type === 'home' ? undefined : associations.groups[workspace.group].metadata;
|
||||||
const navTo = (to: string) => `${props.baseUrl}${to}`;
|
const navTo = (to: string) => `${props.baseUrl}${to}`;
|
||||||
return (
|
return (
|
||||||
<Row width="100%" alignItems="center" height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" pl='3' borderBottom='1px solid' borderColor='washedGray'>
|
<Row width="100%" alignItems="center" height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" pl='3' borderBottom='1px solid' borderColor='washedGray'>
|
||||||
|
@ -14,7 +14,7 @@ const formSchema = Yup.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
interface FormSchema {
|
interface FormSchema {
|
||||||
group: string | null;
|
group: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GroupifyFormProps {
|
interface GroupifyFormProps {
|
||||||
@ -37,7 +37,7 @@ export function GroupifyForm(props: GroupifyFormProps) {
|
|||||||
await props.api.graph.groupifyGraph(
|
await props.api.graph.groupifyGraph(
|
||||||
ship,
|
ship,
|
||||||
name,
|
name,
|
||||||
values.group || undefined
|
values.group?.toString() || undefined
|
||||||
);
|
);
|
||||||
const mod = association.metadata.module || association['app-name'];
|
const mod = association.metadata.module || association['app-name'];
|
||||||
const newGroup = values.group || association.group;
|
const newGroup = values.group || association.group;
|
||||||
@ -79,6 +79,7 @@ export function GroupifyForm(props: GroupifyFormProps) {
|
|||||||
groups={props.groups}
|
groups={props.groups}
|
||||||
associations={props.associations}
|
associations={props.associations}
|
||||||
adminOnly
|
adminOnly
|
||||||
|
maxLength={1}
|
||||||
/>
|
/>
|
||||||
<AsyncButton primary loadingText="Groupifying..." border>
|
<AsyncButton primary loadingText="Groupifying..." border>
|
||||||
Groupify
|
Groupify
|
||||||
|
@ -43,7 +43,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
const groupContacts = (groupPath && contacts[groupPath]) || undefined;
|
const groupContacts = (groupPath && contacts[groupPath]) || undefined;
|
||||||
const rootIdentity = contacts?.["/~/default"]?.[window.ship];
|
const rootIdentity = contacts?.["/~/default"]?.[window.ship];
|
||||||
const groupAssociation =
|
const groupAssociation =
|
||||||
(groupPath && associations.contacts[groupPath]) || undefined;
|
(groupPath && associations.groups[groupPath]) || undefined;
|
||||||
const group = (groupPath && groups[groupPath]) || undefined;
|
const group = (groupPath && groups[groupPath]) || undefined;
|
||||||
const [recentGroups, setRecentGroups] = useLocalStorageState<string[]>(
|
const [recentGroups, setRecentGroups] = useLocalStorageState<string[]>(
|
||||||
"recent-groups",
|
"recent-groups",
|
||||||
@ -196,7 +196,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
let summary: ReactNode;
|
let summary: ReactNode;
|
||||||
if(groupAssociation?.group) {
|
if(groupAssociation?.group) {
|
||||||
const memberCount = props.groups[groupAssociation.group].members.size;
|
const memberCount = props.groups[groupAssociation.group].members.size;
|
||||||
summary = <GroupSummary
|
summary = <GroupSummary
|
||||||
memberCount={memberCount}
|
memberCount={memberCount}
|
||||||
channelCount={0}
|
channelCount={0}
|
||||||
metadata={groupAssociation.metadata}
|
metadata={groupAssociation.metadata}
|
||||||
|
@ -65,7 +65,7 @@ export function NewGroup(props: NewGroupProps & RouteComponentProps) {
|
|||||||
await api.contacts.create(name, policy, title, description);
|
await api.contacts.create(name, policy, title, description);
|
||||||
const path = `/ship/~${window.ship}/${name}`;
|
const path = `/ship/~${window.ship}/${name}`;
|
||||||
await waiter(({ contacts, groups, associations }) => {
|
await waiter(({ contacts, groups, associations }) => {
|
||||||
return path in contacts && path in groups && path in associations.contacts;
|
return path in contacts && path in groups && path in associations.groups;
|
||||||
});
|
});
|
||||||
|
|
||||||
actions.setStatus({ success: null });
|
actions.setStatus({ success: null });
|
||||||
|
@ -9,7 +9,6 @@ import { Association } from "~/types/metadata-update";
|
|||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { GroupNotificationsConfig, S3State, Associations } from "~/types";
|
import { GroupNotificationsConfig, S3State, Associations } from "~/types";
|
||||||
|
|
||||||
import { ContactCard } from "./ContactCard";
|
|
||||||
import { GroupSettings } from "./GroupSettings/GroupSettings";
|
import { GroupSettings } from "./GroupSettings/GroupSettings";
|
||||||
import { Participants } from "./Participants";
|
import { Participants } from "./Participants";
|
||||||
import {useHashLink} from "~/logic/lib/useHashLink";
|
import {useHashLink} from "~/logic/lib/useHashLink";
|
||||||
|
@ -37,8 +37,8 @@ export function Resource(props: ResourceProps) {
|
|||||||
const skelProps = { api, association };
|
const skelProps = { api, association };
|
||||||
let title = props.association.metadata.title;
|
let title = props.association.metadata.title;
|
||||||
if ('workspace' in props) {
|
if ('workspace' in props) {
|
||||||
if ('group' in props.workspace && props.workspace.group in props.associations.contacts) {
|
if ('group' in props.workspace && props.workspace.group in props.associations.groups) {
|
||||||
title = `${props.associations.contacts[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
|
title = `${props.associations.groups[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -57,7 +57,7 @@ export function SidebarList(props: {
|
|||||||
const assoc = associations[a];
|
const assoc = associations[a];
|
||||||
return group
|
return group
|
||||||
? assoc.group === group
|
? assoc.group === group
|
||||||
: !(assoc.group in props.associations.contacts);
|
: !(assoc.group in props.associations.groups);
|
||||||
})
|
})
|
||||||
.sort(sidebarSort(associations, props.apps)[config.sortBy]);
|
.sort(sidebarSort(associations, props.apps)[config.sortBy]);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user