mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-02 07:06:41 +03:00
Merge pull request #4356 from urbit/la/push-hook-list-resource
contact: networking updated to send out data with the appropriate resources
This commit is contained in:
commit
adee5d6562
@ -9,6 +9,7 @@
|
||||
update:store
|
||||
%contact-update
|
||||
%contact-push-hook
|
||||
%.y :: necessary to enable p2p
|
||||
==
|
||||
--
|
||||
::
|
||||
@ -30,16 +31,20 @@
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-watch on-watch:def
|
||||
++ on-watch
|
||||
|= =path
|
||||
?. ?=([%nacks ~] path)
|
||||
(on-watch:def path)
|
||||
?> (team:title [src our]:bowl)
|
||||
`this
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
++ resource-for-update resource-for-update:con
|
||||
++ 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]
|
||||
[%give %fact ~[/nacks] resource+!>(resource)]~
|
||||
::
|
||||
++ on-pull-kick |=(=resource `/)
|
||||
--
|
||||
|
@ -1,4 +1,6 @@
|
||||
/+ store=contact-store, res=resource, contact, default-agent, dbug, push-hook
|
||||
/- pull-hook
|
||||
/+ store=contact-store, res=resource, contact, group,
|
||||
default-agent, dbug, push-hook, agentio, verb
|
||||
~% %contact-push-hook-top ..part ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
@ -12,21 +14,46 @@
|
||||
==
|
||||
::
|
||||
+$ agent (push-hook:push-hook config)
|
||||
::
|
||||
+$ share [%share =ship]
|
||||
--
|
||||
::
|
||||
%- agent:dbug
|
||||
^- agent:gall
|
||||
%+ verb |
|
||||
%- (agent:push-hook config)
|
||||
^- agent
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
con ~(. contact bowl)
|
||||
grp ~(. group bowl)
|
||||
io ~(. agentio bowl)
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card _this)
|
||||
:_ this :_ ~
|
||||
=- [%pass /us %agent [our.bowl %contact-push-hook] %poke %push-hook-action -]
|
||||
!> ^- action:push-hook
|
||||
[%add [our.bowl %'']]
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(~)
|
||||
++ on-load on-load:def
|
||||
++ on-poke on-poke:def
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?. =(mark %contact-share) (on-poke:def mark vase)
|
||||
=/ =share !<(share vase)
|
||||
:_ this :_ ~
|
||||
?: =(our.bowl src.bowl)
|
||||
:: proxy poke
|
||||
%+ poke:pass:io [ship.share dap.bowl]
|
||||
contact-share+!>([%share our.bowl])
|
||||
:: accept share
|
||||
?> =(src.bowl ship.share)
|
||||
%+ poke-our:pass:io %contact-pull-hook
|
||||
pull-hook-action+!>([%add src.bowl [src.bowl %$]])
|
||||
::
|
||||
++ on-agent on-agent:def
|
||||
++ on-watch on-watch:def
|
||||
++ on-leave on-leave:def
|
||||
@ -48,16 +75,31 @@
|
||||
%set-public %.n
|
||||
==
|
||||
::
|
||||
++ resource-for-update resource-for-update:con
|
||||
::
|
||||
++ initial-watch
|
||||
|= [=path =resource:res]
|
||||
^- vase
|
||||
?> (is-allowed:con src.bowl)
|
||||
|^
|
||||
?> (is-allowed:con resource src.bowl)
|
||||
!> ^- update:store
|
||||
=/ contact=(unit contact:store) (get-contact:con our.bowl)
|
||||
:+ %add
|
||||
our.bowl
|
||||
?^ contact u.contact
|
||||
*contact:store
|
||||
[%initial rolo %.n]
|
||||
::
|
||||
++ rolo
|
||||
^- rolodex:store
|
||||
=/ ugroup (scry-group:grp resource)
|
||||
%- ~(gas by *rolodex:store)
|
||||
?~ ugroup
|
||||
=/ c=(unit contact:store) (get-contact:con our.bowl)
|
||||
?~ c
|
||||
[our.bowl *contact:store]~
|
||||
[our.bowl u.c]~
|
||||
%+ murn ~(tap in (members:grp resource))
|
||||
|= s=ship
|
||||
^- (unit [ship contact:store])
|
||||
=/ c=(unit contact:store) (get-contact:con s)
|
||||
?~(c ~ `[s u.c])
|
||||
--
|
||||
::
|
||||
++ take-update
|
||||
|= =vase
|
||||
|
@ -3,7 +3,7 @@
|
||||
:: data store that holds individual contact data
|
||||
::
|
||||
/- store=contact-store, *resource
|
||||
/+ default-agent, dbug, *migrate
|
||||
/+ default-agent, dbug, *migrate, contact
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
+$ state-4
|
||||
@ -29,6 +29,7 @@
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
con ~(. contact bowl)
|
||||
::
|
||||
++ on-init
|
||||
=. rolodex (~(put by rolodex) our.bowl *contact:store)
|
||||
@ -101,8 +102,10 @@
|
||||
++ handle-initial
|
||||
|= [rolo=rolodex:store is-public=?]
|
||||
^- (quip card _state)
|
||||
=/ our-contact (~(got by rolodex) our.bowl)
|
||||
=. rolodex (~(uni by rolodex) rolo)
|
||||
:_ state(rolodex rolodex, is-public is-public)
|
||||
=. rolodex (~(put by rolodex) our.bowl our-contact)
|
||||
:_ state(rolodex rolodex)
|
||||
(send-diff [%initial rolodex is-public] %.n)
|
||||
::
|
||||
++ handle-add
|
||||
@ -208,9 +211,22 @@
|
||||
[%x %allowed-ship @ ~]
|
||||
=/ =ship (slav %p i.t.t.path)
|
||||
``noun+!>((~(has in allowed-ships) ship))
|
||||
::
|
||||
[%x %is-public ~]
|
||||
``noun+!>(is-public)
|
||||
::
|
||||
[%x %allowed-groups ~]
|
||||
``noun+!>(allowed-groups)
|
||||
|
||||
::
|
||||
[%x %is-allowed @ @ @ @ ~]
|
||||
=/ is-personal =(i.t.t.t.t.t.path 'true')
|
||||
=/ =resource
|
||||
?: is-personal
|
||||
[our.bowl %'']
|
||||
[(slav %p i.t.t.path) i.t.t.t.path]
|
||||
=/ =ship (slav %p i.t.t.t.t.path)
|
||||
``json+!>(`json`b+(is-allowed:con resource ship))
|
||||
==
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
|
@ -9,6 +9,7 @@
|
||||
update:store
|
||||
%graph-update
|
||||
%graph-push-hook
|
||||
%.n
|
||||
==
|
||||
--
|
||||
::
|
||||
@ -35,7 +36,7 @@
|
||||
++ on-pull-nack
|
||||
|= [=resource =tang]
|
||||
^- (quip card _this)
|
||||
~& nacked+resource
|
||||
%- (slog leaf+"nacked {<resource>}" tang)
|
||||
:_ this
|
||||
?. (~(has in get-keys:gra) resource) ~
|
||||
=- [%pass /pull-nack %agent [our.bowl %graph-store] %poke %graph-update -]~
|
||||
@ -48,4 +49,6 @@
|
||||
=/ maybe-time (peek-update-log:gra resource)
|
||||
?~ maybe-time `/
|
||||
`/(scot %da u.maybe-time)
|
||||
::
|
||||
++ resource-for-update resource-for-update:gra
|
||||
--
|
||||
|
@ -93,6 +93,7 @@
|
||||
%tag-queries %.n
|
||||
%run-updates %.n
|
||||
==
|
||||
++ resource-for-update resource-for-update:gra
|
||||
::
|
||||
++ initial-watch
|
||||
|= [=path =resource:res]
|
||||
|
@ -14,6 +14,7 @@
|
||||
update:store
|
||||
%group-update
|
||||
%group-push-hook
|
||||
%.n
|
||||
==
|
||||
::
|
||||
--
|
||||
@ -28,6 +29,7 @@
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
dep ~(. (default:pull-hook this config) bowl)
|
||||
grp ~(. grpl bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(~)
|
||||
@ -45,8 +47,11 @@
|
||||
:_ this
|
||||
=- [%pass / %agent [our.bowl %group-store] %poke -]~
|
||||
group-update+!>([%remove-group resource ~])
|
||||
::
|
||||
++ on-pull-kick
|
||||
|= =resource
|
||||
^- (unit path)
|
||||
`/
|
||||
::
|
||||
++ resource-for-update resource-for-update:grp
|
||||
--
|
||||
|
@ -141,6 +141,7 @@
|
||||
=(~(tap in ships.update) ~[src.bowl])
|
||||
==
|
||||
--
|
||||
++ resource-for-update resource-for-update:grp
|
||||
::
|
||||
++ take-update
|
||||
|= =vase
|
||||
|
@ -1,5 +1,6 @@
|
||||
/- view-sur=group-view, group-store, *group, metadata=metadata-store
|
||||
/+ default-agent, agentio, mdl=metadata, resource, dbug, grpl=group, verb
|
||||
/+ default-agent, agentio, mdl=metadata,
|
||||
resource, dbug, grpl=group, conl=contact, verb
|
||||
|%
|
||||
++ card card:agent:gall
|
||||
+$ state-zero
|
||||
@ -69,15 +70,14 @@
|
||||
[cards this]
|
||||
::
|
||||
++ on-arvo on-arvo:def
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
::
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
|_ =bowl:gall
|
||||
++ met ~(. mdl bowl)
|
||||
++ grp ~(. grpl bowl)
|
||||
++ io ~(. agentio bowl)
|
||||
++ con ~(. conl bowl)
|
||||
::
|
||||
::
|
||||
++ join
|
||||
@ -86,6 +86,7 @@
|
||||
++ emit-many
|
||||
|= crds=(list card)
|
||||
jn-core(cards (weld (flop crds) cards))
|
||||
::
|
||||
++ emit
|
||||
|= =card
|
||||
jn-core(cards [card cards])
|
||||
@ -152,43 +153,65 @@
|
||||
%+ poke-our:(jn-pass-io /pull-groups) %group-pull-hook
|
||||
pull-hook-action+!>([%add ship rid])
|
||||
(tx-progress %added)
|
||||
::
|
||||
::
|
||||
%pull-groups
|
||||
?> ?=(%poke-ack -.sign)
|
||||
(ack +.sign)
|
||||
::
|
||||
::
|
||||
%groups
|
||||
?+ -.sign !!
|
||||
%fact (groups-fact +.sign)
|
||||
%watch-ack (ack +.sign)
|
||||
%kick watch-groups
|
||||
==
|
||||
::
|
||||
::
|
||||
%pull-md
|
||||
?> ?=(%poke-ack -.sign)
|
||||
(ack +.sign)
|
||||
::
|
||||
::
|
||||
%pull-co
|
||||
?> ?=(%poke-ack -.sign)
|
||||
(ack +.sign)
|
||||
::
|
||||
%share-co
|
||||
?> ?=(%poke-ack -.sign)
|
||||
(ack +.sign)
|
||||
::
|
||||
%push-co
|
||||
?> ?=(%poke-ack -.sign)
|
||||
(ack +.sign)
|
||||
::
|
||||
%md
|
||||
?+ -.sign !!
|
||||
%fact (md-fact +.sign)
|
||||
%watch-ack (ack +.sign)
|
||||
%kick watch-md
|
||||
==
|
||||
::
|
||||
::
|
||||
%pull-graphs
|
||||
?> ?=(%poke-ack -.sign)
|
||||
%- cleanup
|
||||
?^(p.sign %strange %done)
|
||||
==
|
||||
::
|
||||
++ groups-fact
|
||||
|= =cage
|
||||
?. ?=(%group-update p.cage) jn-core
|
||||
=+ !<(=update:group-store q.cage)
|
||||
?. ?=(%initial-group -.update) jn-core
|
||||
?. =(rid resource.update) jn-core
|
||||
%- emit
|
||||
%+ poke-our:(jn-pass-io /pull-md) %metadata-pull-hook
|
||||
pull-hook-action+!>([%add [entity .]:rid])
|
||||
%- emit-many
|
||||
=/ cag=^cage pull-hook-action+!>([%add [entity .]:rid])
|
||||
%- zing
|
||||
:~ [(poke-our:(jn-pass-io /pull-md) %metadata-pull-hook cag)]~
|
||||
[(poke-our:(jn-pass-io /pull-co) %contact-pull-hook cag)]~
|
||||
::
|
||||
?. scry-is-public:con ~
|
||||
:_ ~
|
||||
%+ poke:(jn-pass-io /share-co)
|
||||
[entity.rid %contact-push-hook]
|
||||
[%contact-share !>([%share our.bowl])]
|
||||
==
|
||||
::
|
||||
++ md-fact
|
||||
|= [=mark =vase]
|
||||
|
@ -2,9 +2,9 @@
|
||||
::
|
||||
:: allow syncing group data from foreign paths to local paths
|
||||
::
|
||||
/- *group, invite-store, metadata=metadata-store
|
||||
/- *group, invite-store, metadata=metadata-store, contact=contact-store
|
||||
/+ default-agent, verb, dbug, store=group-store, grpl=group, pull-hook
|
||||
/+ resource, mdl=metadata
|
||||
/+ resource, mdl=metadata, agn=agentio
|
||||
~% %group-hook-top ..part ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
@ -15,6 +15,7 @@
|
||||
update:metadata
|
||||
%metadata-update
|
||||
%metadata-push-hook
|
||||
%.n
|
||||
==
|
||||
+$ state-zero
|
||||
[%0 previews=(map resource group-preview:metadata)]
|
||||
@ -31,20 +32,19 @@
|
||||
=* state -
|
||||
=> |_ =bowl:gall
|
||||
++ def ~(. (default-agent state %|) bowl)
|
||||
++ met ~(. mdl bowl)
|
||||
++ io ~(. agn bowl)
|
||||
++ get-preview
|
||||
|= rid=resource
|
||||
^- card
|
||||
=/ =path
|
||||
preview+(en-path:resource rid)
|
||||
=/ =dock
|
||||
[entity.rid %metadata-push-hook]
|
||||
=/ =cage
|
||||
metadata-hook-update+!>([%req-preview rid])
|
||||
[%pass path %agent dock %poke cage]
|
||||
%+ ~(poke pass:io path) dock
|
||||
metadata-hook-update+!>([%req-preview rid])
|
||||
::
|
||||
++ watch-invites
|
||||
^- card
|
||||
[%pass /invites %agent [our.bowl %invite-store] %watch /updates]
|
||||
(~(watch-our pass:io /invites) %invite-store /updates)
|
||||
::
|
||||
++ take-invites
|
||||
|= =sign:agent:gall
|
||||
@ -59,6 +59,68 @@
|
||||
::
|
||||
%kick [watch-invites^~ state]
|
||||
==
|
||||
::
|
||||
++ watch-contacts
|
||||
(~(watch-our pass:io /contacts) %contact-store /all)
|
||||
::
|
||||
++ take-contacts
|
||||
|= =sign:agent:gall
|
||||
^- (quip card _state)
|
||||
?+ -.sign (on-agent:def /contacts sign)
|
||||
%kick [~[watch-contacts] state]
|
||||
::
|
||||
%fact
|
||||
:_ state
|
||||
?> ?=(%contact-update p.cage.sign)
|
||||
=+ !<(=update:contact q.cage.sign)
|
||||
?+ -.update ~
|
||||
%add
|
||||
(check-contact contact.update)
|
||||
::
|
||||
%edit
|
||||
?. ?=(%add-group -.edit-field.update) ~
|
||||
%- add-missing-previews
|
||||
(~(gas in *(set resource)) resource.edit-field.update ~)
|
||||
::
|
||||
%initial
|
||||
^- (list card)
|
||||
%- zing
|
||||
%+ turn ~(tap by rolodex.update)
|
||||
|=([ship =contact:contact] (check-contact contact))
|
||||
==
|
||||
==
|
||||
::
|
||||
++ check-contact
|
||||
|= =contact:contact
|
||||
^- (list card)
|
||||
(add-missing-previews groups.contact)
|
||||
::
|
||||
++ add-missing-previews
|
||||
|= groups=(set resource)
|
||||
^- (list card)
|
||||
=/ missing=(set resource)
|
||||
(~(dif in ~(key by previews)) groups)
|
||||
%+ murn ~(tap by missing)
|
||||
|= group=resource
|
||||
^- (unit card)
|
||||
?^ (peek-metadatum:met %groups group) ~
|
||||
`(get-preview group)
|
||||
::
|
||||
++ watch-store
|
||||
(~(watch-our pass:io /store) %metadata-store /all)
|
||||
::
|
||||
++ take-store
|
||||
|= =sign:agent:gall
|
||||
^- (quip card _state)
|
||||
?+ -.sign (on-agent:def /store sign)
|
||||
%kick [watch-store^~ state]
|
||||
::
|
||||
%fact
|
||||
?> ?=(%metadata-update p.cage.sign)
|
||||
=+ !<(=update:metadata q.cage.sign)
|
||||
?. ?=(%initial-group -.update) `state
|
||||
`state(previews (~(del by previews) group.update))
|
||||
==
|
||||
--
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
@ -73,8 +135,14 @@
|
||||
|= =vase
|
||||
=+ !<(old=state-zero vase)
|
||||
:_ this(state old)
|
||||
?: (~(has by wex.bowl) [/invites our.bowl %invite-store]) ~
|
||||
~[watch-invites:hc]
|
||||
%- zing
|
||||
:~ ?: (~(has by wex.bowl) [/invites our.bowl %invite-store]) ~
|
||||
~[watch-invites:hc]
|
||||
?: (~(has by wex.bowl) [/contacts our.bowl %contact-store]) ~
|
||||
~[watch-contacts:hc]
|
||||
?: (~(has by wex.bowl) [/store our.bowl %metadata-store]) ~
|
||||
~[watch-store:hc]
|
||||
==
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
@ -84,26 +152,24 @@
|
||||
?. ?=(%preview -.hook-update)
|
||||
(on-poke:def mark vase)
|
||||
:_ this(previews (~(put by previews) group.hook-update +.hook-update))
|
||||
=/ paths=(list path)
|
||||
~[preview+(en-path:resource group.hook-update)]
|
||||
:~ [%give %fact paths mark^vase]
|
||||
[%give %kick paths ~]
|
||||
==
|
||||
=/ =path
|
||||
preview+(en-path:resource group.hook-update)
|
||||
(fact-kick:io path mark^vase)
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
=^ cards state
|
||||
?+ wire (on-agent:def:hc wire sign)
|
||||
[%invites ~] (take-invites:hc sign)
|
||||
[%contacts ~] (take-contacts:hc sign)
|
||||
[%store ~] (take-store:hc sign)
|
||||
::
|
||||
[%preview @ @ @ ~]
|
||||
?. ?=(%poke-ack -.sign)
|
||||
(on-agent:def:hc wire sign)
|
||||
:_ state
|
||||
?~ p.sign ~
|
||||
:~ [%give %fact ~[wire] tang+!>(u.p.sign)]
|
||||
[%give %kick ~[wire] ~]
|
||||
==
|
||||
(fact-kick:io wire tang+!>(u.p.sign))
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
@ -119,13 +185,14 @@
|
||||
:_ this
|
||||
?~ prev
|
||||
(get-preview rid)^~
|
||||
[%give %fact ~ metadata-hook-update+!>([%preview u.prev])]~
|
||||
(fact-init:io metadata-hook-update+!>([%preview u.prev]))^~
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-arvo on-arvo:def
|
||||
::
|
||||
++ on-fail on-fail:def
|
||||
++ resource-for-update resource-for-update:met
|
||||
++ on-pull-nack
|
||||
|= [=resource =tang]
|
||||
^- (quip card _this)
|
||||
@ -134,7 +201,7 @@
|
||||
:_ this
|
||||
%+ turn ~(tap by associations)
|
||||
|= [=md-resource:metadata =association:metadata]
|
||||
=- [%pass / %agent [our.bowl %metadata-store] %poke -]
|
||||
%+ poke-our:pass:io %metadata-store
|
||||
:- %metadata-update
|
||||
!> ^- update:metadata
|
||||
[%remove resource md-resource]
|
||||
|
@ -92,6 +92,7 @@
|
||||
?=(%member-metadata vip.metadatum)
|
||||
==
|
||||
::
|
||||
++ resource-for-update resource-for-update:met
|
||||
++ take-update
|
||||
|= =vase
|
||||
^- [(list card) agent]
|
||||
|
@ -88,6 +88,18 @@
|
||||
|= [paths=(list path) fac=mold]
|
||||
(fact mark^!>(fac) paths)
|
||||
::
|
||||
++ fact-kick
|
||||
|= [=path =cage]
|
||||
^- (list card)
|
||||
:~ (fact cage ~[path])
|
||||
(kick ~[path])
|
||||
==
|
||||
::
|
||||
++ fact-init
|
||||
|= =cage
|
||||
^- card
|
||||
[%give %fact ~ cage]
|
||||
::
|
||||
++ fact
|
||||
|= [=cage paths=(list path)]
|
||||
^- card
|
||||
|
@ -173,4 +173,12 @@
|
||||
==
|
||||
--
|
||||
--
|
||||
::
|
||||
++ share-dejs
|
||||
=, dejs:format
|
||||
|%
|
||||
++ share
|
||||
^- $-(json [%share ship])
|
||||
(of share+(su ;~(pfix sig fed:ag)) ~)
|
||||
--
|
||||
--
|
||||
|
@ -1,6 +1,7 @@
|
||||
/- store=contact-store, *resource
|
||||
/+ group
|
||||
/+ group, grpl=group
|
||||
|_ =bowl:gall
|
||||
+* grp ~(. grpl bowl)
|
||||
++ scry-for
|
||||
|* [=mold =path]
|
||||
.^ mold
|
||||
@ -11,24 +12,88 @@
|
||||
(snoc `^path`path %noun)
|
||||
==
|
||||
::
|
||||
++ resource-for-update
|
||||
|= =vase
|
||||
^- (list resource)
|
||||
|^
|
||||
=/ =update:store !<(update:store vase)
|
||||
?- -.update
|
||||
%initial ~
|
||||
%add (rids-for-ship ship.update)
|
||||
%remove (rids-for-ship ship.update)
|
||||
%edit (rids-for-ship ship.update)
|
||||
%allow ~
|
||||
%disallow ~
|
||||
%set-public ~
|
||||
==
|
||||
::
|
||||
++ rids-for-ship
|
||||
|= s=ship
|
||||
^- (list resource)
|
||||
:: if the ship is in any group that I am pushing updates for, push
|
||||
:: it out to that resource.
|
||||
::
|
||||
=/ rids
|
||||
%+ skim ~(tap in scry-sharing)
|
||||
|= r=resource
|
||||
(is-member:grp s r)
|
||||
?. =(s our.bowl)
|
||||
rids
|
||||
(snoc rids [our.bowl %''])
|
||||
--
|
||||
++ scry-sharing
|
||||
.^ (set resource)
|
||||
%gx
|
||||
(scot %p our.bowl)
|
||||
%contact-push-hook
|
||||
(scot %da now.bowl)
|
||||
/sharing/noun
|
||||
==
|
||||
::
|
||||
++ get-contact
|
||||
|= =ship
|
||||
^- (unit contact:store)
|
||||
=/ upd (scry-for (unit update:store) /contact/(scot %p ship))
|
||||
?~ upd ~
|
||||
?> ?=(%add -.u.upd)
|
||||
`contact.u.upd
|
||||
=/ =rolodex:store
|
||||
(scry-for rolodex:store /all)
|
||||
(~(get by rolodex) ship)
|
||||
::
|
||||
++ scry-is-public
|
||||
.^ ?
|
||||
%gx
|
||||
(scot %p our.bowl)
|
||||
%contact-store
|
||||
(scot %da now.bowl)
|
||||
/is-public/noun
|
||||
==
|
||||
::
|
||||
++ is-allowed
|
||||
|= =ship
|
||||
|= [rid=resource =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)
|
||||
=/ allowed-groups (scry-for (set resource) /allowed-groups)
|
||||
?| :: if they are requesting our personal profile, check if we are
|
||||
:: either public, or if they are on the allowed-ships list.
|
||||
:: this is used for direct messages and leap searches
|
||||
::
|
||||
?& =(rid [our.bowl %''])
|
||||
?| :: if our profile is public, allow
|
||||
::
|
||||
scry-is-public
|
||||
:: if the requester is an allowed-ship, allow
|
||||
::
|
||||
(scry-for ? /allowed-ship/(scot %p ship))
|
||||
:: if the requester of our profile is the host of one of
|
||||
:: our allowed-groups, allow
|
||||
::
|
||||
%+ lien ~(tap in allowed-groups)
|
||||
|= res=resource
|
||||
=(entity.res ship)
|
||||
== ==
|
||||
:: if they are requesting our contact data within a group,
|
||||
:: we make sure that we are sharing that group,
|
||||
:: and that they are a member of the group
|
||||
::
|
||||
?& (~(has in scry-sharing) rid)
|
||||
(~(has in (members:grp rid)) ship)
|
||||
== ==
|
||||
--
|
||||
|
@ -11,6 +11,27 @@
|
||||
(snoc `^path`path %noun)
|
||||
==
|
||||
::
|
||||
++ resource-for-update
|
||||
|= =vase
|
||||
^- (list resource)
|
||||
=/ =update:store !<(update:store vase)
|
||||
?- -.q.update
|
||||
%add-graph ~[resource.q.update]
|
||||
%remove-graph ~[resource.q.update]
|
||||
%add-nodes ~[resource.q.update]
|
||||
%remove-nodes ~[resource.q.update]
|
||||
%add-signatures ~[resource.uid.q.update]
|
||||
%remove-signatures ~[resource.uid.q.update]
|
||||
%archive-graph ~[resource.q.update]
|
||||
%unarchive-graph ~
|
||||
%add-tag ~
|
||||
%remove-tag ~
|
||||
%keys ~
|
||||
%tags ~
|
||||
%tag-queries ~
|
||||
%run-updates ~[resource.q.update]
|
||||
==
|
||||
::
|
||||
++ get-graph
|
||||
|= res=resource
|
||||
^- update:store
|
||||
|
@ -3,6 +3,15 @@
|
||||
::
|
||||
|_ =bowl:gall
|
||||
+$ card card:agent:gall
|
||||
::
|
||||
++ resource-for-update
|
||||
|= =vase
|
||||
^- (list resource)
|
||||
=/ =update:store !<(update:store vase)
|
||||
?: ?=(%initial -.update)
|
||||
~
|
||||
~[resource.update]
|
||||
::
|
||||
++ scry-for
|
||||
|* [=mold =path]
|
||||
=. path
|
||||
@ -28,6 +37,15 @@
|
||||
%+ scry-for ,(unit group)
|
||||
`path`groups+(en-path:resource rid)
|
||||
::
|
||||
++ scry-groups
|
||||
.^ ,(set resource)
|
||||
%gy
|
||||
(scot %p our.bowl)
|
||||
%group-store
|
||||
(scot %da now.bowl)
|
||||
/groups/noun
|
||||
==
|
||||
::
|
||||
++ members
|
||||
|= rid=resource
|
||||
=; =group
|
||||
|
@ -90,10 +90,8 @@
|
||||
%chat-cli
|
||||
%herm
|
||||
%contact-store
|
||||
%contact-hook
|
||||
%contact-push-hook
|
||||
%contact-pull-hook
|
||||
%contact-view
|
||||
%metadata-store
|
||||
%s3-store
|
||||
%file-server
|
||||
@ -109,6 +107,7 @@
|
||||
%metadata-push-hook
|
||||
%metadata-pull-hook
|
||||
%group-view
|
||||
%settings-store
|
||||
==
|
||||
::
|
||||
++ deft-fish :: default connects
|
||||
@ -257,6 +256,7 @@
|
||||
=? ..on-load (lte hood-version %12)
|
||||
=> (se-born | %home %contact-push-hook)
|
||||
=> (se-born | %home %contact-pull-hook)
|
||||
=> (se-born | %home %settings-store)
|
||||
(se-born | %home %group-view)
|
||||
..on-load
|
||||
::
|
||||
|
@ -4,6 +4,13 @@
|
||||
/+ resource
|
||||
::
|
||||
|_ =bowl:gall
|
||||
++ resource-for-update
|
||||
|= =vase
|
||||
^- (list resource)
|
||||
=/ =update:store !<(update:store vase)
|
||||
?. ?=(?(%add %remove %initial-group) -.update) ~
|
||||
~[group.update]
|
||||
::
|
||||
++ app-paths-from-group
|
||||
|= [=app-name:store group=resource]
|
||||
^- (list resource)
|
||||
|
@ -20,6 +20,22 @@
|
||||
::
|
||||
/- *pull-hook
|
||||
/+ default-agent, resource
|
||||
|%
|
||||
:: JSON conversions
|
||||
++ dejs
|
||||
=, dejs:format
|
||||
|%
|
||||
++ action
|
||||
%- of
|
||||
:~ add+add
|
||||
==
|
||||
++ add
|
||||
%- ot
|
||||
:~ ship+(su ;~(pfix sig fed:ag))
|
||||
resource+dejs:resource
|
||||
==
|
||||
--
|
||||
--
|
||||
::
|
||||
::
|
||||
|%
|
||||
@ -30,12 +46,15 @@
|
||||
:: .store-name: name of the store to send subscription updates to.
|
||||
:: .update-mark: mark that updates will be tagged with
|
||||
:: .push-hook-name: name of the corresponding push-hook
|
||||
:: .no-validate: If true, don't validate that resource/wire/src match
|
||||
:: up
|
||||
::
|
||||
+$ config
|
||||
$: store-name=term
|
||||
update=mold
|
||||
update-mark=term
|
||||
push-hook-name=term
|
||||
no-validate=_|
|
||||
==
|
||||
::
|
||||
:: $base-state-0: state for the pull hook
|
||||
@ -106,6 +125,14 @@
|
||||
++ on-pull-kick
|
||||
|~ resource
|
||||
*(unit path)
|
||||
:: +resource-for-update: get resources from vase
|
||||
::
|
||||
:: This should be identical to the +resource-for-update arm in the
|
||||
:: corresponding push-hook
|
||||
::
|
||||
++ resource-for-update
|
||||
|~ vase
|
||||
*(list resource)
|
||||
::
|
||||
:: from agent:gall
|
||||
++ on-init
|
||||
@ -470,24 +497,30 @@
|
||||
/helper/pull-hook
|
||||
wire
|
||||
::
|
||||
++ get-conversion
|
||||
.^ tube:clay
|
||||
%cc (scot %p our.bowl) %home (scot %da now.bowl)
|
||||
/[update-mark.config]/resource
|
||||
==
|
||||
::
|
||||
++ give-update
|
||||
^- card
|
||||
[%give %fact ~[/tracking] %pull-hook-update !>(tracking)]
|
||||
::
|
||||
++ check-src
|
||||
|= resources=(set resource)
|
||||
^- ?
|
||||
%+ roll ~(tap in resources)
|
||||
|= [rid=resource out=_|]
|
||||
?: out %.y
|
||||
?~ ship=(~(get by tracking) rid)
|
||||
%.n
|
||||
=(src.bowl u.ship)
|
||||
::
|
||||
++ update-store
|
||||
|= [wire-rid=resource =vase]
|
||||
^- card
|
||||
=/ =wire
|
||||
(make-wire /store)
|
||||
=+ !<(rid=resource (get-conversion vase))
|
||||
?> =(src.bowl (~(got by tracking) rid))
|
||||
?> =(wire-rid rid)
|
||||
=+ resources=(~(gas in *(set resource)) (resource-for-update:og vase))
|
||||
?> ?| no-validate.config
|
||||
?& (check-src resources)
|
||||
(~(has in resources) wire-rid)
|
||||
== ==
|
||||
[%pass wire %agent [our.bowl store-name.config] %poke update-mark.config vase]
|
||||
--
|
||||
--
|
||||
|
@ -67,6 +67,16 @@
|
||||
|* =config
|
||||
$_ ^|
|
||||
|_ bowl:gall
|
||||
::
|
||||
:: +resource-for-update: get affected resources from an update
|
||||
::
|
||||
:: Given a vase of the update, the mark of which is
|
||||
:: update-mark.config, produce the affected resources, if any.
|
||||
::
|
||||
++ resource-for-update
|
||||
|~ vase
|
||||
*(list resource)
|
||||
::
|
||||
:: +take-update: handle update from store
|
||||
::
|
||||
:: Given an update from the store, do other things after proxying
|
||||
@ -145,12 +155,12 @@
|
||||
=* state -
|
||||
^- agent:gall
|
||||
=<
|
||||
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
og ~(. push-hook bowl)
|
||||
hc ~(. +> bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init
|
||||
=^ cards push-hook
|
||||
on-init:og
|
||||
@ -165,11 +175,9 @@
|
||||
|^
|
||||
?- -.old
|
||||
%1
|
||||
=. cards
|
||||
:_(cards (build-mark:hc %sing))
|
||||
=^ og-cards push-hook
|
||||
(on-load:og inner-state.old)
|
||||
[(weld (flop cards) og-cards) this(state old)]
|
||||
[(weld cards og-cards) this(state old)]
|
||||
::
|
||||
%0
|
||||
%_ $
|
||||
@ -261,6 +269,7 @@
|
||||
(push-updates:hc q.cage.sign)
|
||||
cards
|
||||
==
|
||||
::
|
||||
++ on-leave
|
||||
|= =path
|
||||
=^ cards push-hook
|
||||
@ -269,20 +278,16 @@
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
?. ?=([%helper %push-hook @ *] wire)
|
||||
=^ cards push-hook
|
||||
(on-arvo:og wire sign-arvo)
|
||||
[cards this]
|
||||
?. ?=(%resource-conversion i.t.t.wire)
|
||||
(on-arvo:def wire sign-arvo)
|
||||
:_ this
|
||||
~[(build-mark:hc %next)]
|
||||
=^ cards push-hook
|
||||
(on-arvo:og wire sign-arvo)
|
||||
[cards this]
|
||||
::
|
||||
++ on-fail
|
||||
|= [=term =tang]
|
||||
=^ cards push-hook
|
||||
(on-fail:og term tang)
|
||||
[cards this]
|
||||
::
|
||||
++ on-peek
|
||||
|= =path
|
||||
^- (unit (unit cage))
|
||||
@ -311,6 +316,7 @@
|
||||
%remove (remove +.action)
|
||||
%revoke (revoke +.action)
|
||||
==
|
||||
::
|
||||
++ add
|
||||
|= rid=resource
|
||||
=. sharing
|
||||
@ -322,7 +328,7 @@
|
||||
=/ pax=path
|
||||
[%resource (en-path:resource rid)]
|
||||
=/ paths=(set path)
|
||||
%- sy
|
||||
%- silt
|
||||
%+ turn
|
||||
(incoming-subscriptions pax)
|
||||
|=([ship pox=path] pox)
|
||||
@ -344,6 +350,7 @@
|
||||
~
|
||||
`[%give %kick ~[path] `her]
|
||||
--
|
||||
::
|
||||
++ incoming-subscriptions
|
||||
|= prefix=path
|
||||
^- (list (pair ship path))
|
||||
@ -371,58 +378,50 @@
|
||||
++ push-updates
|
||||
|= =vase
|
||||
^- (list card:agent:gall)
|
||||
=/ rid=(unit resource)
|
||||
(resource-for-update vase)
|
||||
?~ rid ~
|
||||
=/ rids=(list resource) (resource-for-update vase)
|
||||
=| cards=(list card:agent:gall)
|
||||
|-
|
||||
?~ rids cards
|
||||
=/ prefix=path
|
||||
resource+(en-path:resource u.rid)
|
||||
resource+(en-path:resource i.rids)
|
||||
=/ paths=(list path)
|
||||
%~ tap in
|
||||
%- silt
|
||||
%+ turn
|
||||
(incoming-subscriptions prefix)
|
||||
|=([ship pax=path] pax)
|
||||
?~ paths ~
|
||||
[%give %fact paths update-mark.config vase]~
|
||||
?~ paths $(rids t.rids)
|
||||
%_ $
|
||||
rids t.rids
|
||||
cards (snoc cards [%give %fact paths update-mark.config vase])
|
||||
==
|
||||
::
|
||||
++ forward-update
|
||||
|= update=vase
|
||||
|= =vase
|
||||
^- (list card:agent:gall)
|
||||
=/ rid=resource
|
||||
(need (resource-for-update update))
|
||||
=/ rids=(list resource) (resource-for-update vase)
|
||||
=| cards=(list card:agent:gall)
|
||||
|-
|
||||
?~ rids cards
|
||||
=/ =path
|
||||
resource+(en-path:resource rid)
|
||||
resource+(en-path:resource i.rids)
|
||||
=/ =wire
|
||||
(make-wire resource+(en-path:resource rid))
|
||||
(make-wire resource+(en-path:resource i.rids))
|
||||
=/ dap=term
|
||||
?:(=(our.bowl entity.rid) store-name.config dap.bowl)
|
||||
[%pass wire %agent [entity.rid dap] %poke update-mark.config update]~
|
||||
::
|
||||
++ get-conversion
|
||||
.^ tube:clay
|
||||
%cc (scot %p our.bowl) %home (scot %da now.bowl)
|
||||
/[update-mark.config]/resource
|
||||
?:(=(our.bowl entity.i.rids) store-name.config dap.bowl)
|
||||
%_ $
|
||||
rids t.rids
|
||||
::
|
||||
cards
|
||||
%+ snoc cards
|
||||
[%pass wire %agent [entity.i.rids dap] %poke update-mark.config vase]
|
||||
==
|
||||
::
|
||||
++ resource-for-update
|
||||
|= update=vase
|
||||
^- (unit resource)
|
||||
=/ converted=(each vase (list tank))
|
||||
(mule |.((get-conversion update)))
|
||||
?: ?=(%| -.converted)
|
||||
%- (slog p.converted)
|
||||
~
|
||||
[~ !<(resource p.converted)]
|
||||
::
|
||||
++ build-mark
|
||||
|= rav=?(%sing %next)
|
||||
^- card
|
||||
=/ =wire
|
||||
(make-wire /resource-conversion)
|
||||
=/ =mood:clay
|
||||
[%c da+now.bowl /[update-mark.config]/resource]
|
||||
=/ =rave:clay
|
||||
?:(?=(%next rav) [rav mood] [rav mood])
|
||||
[%pass wire %arvo %c %warp our.bowl [%home `rave]]
|
||||
|= =vase
|
||||
^- (list resource)
|
||||
%~ tap in
|
||||
%- silt
|
||||
(resource-for-update:og vase)
|
||||
--
|
||||
--
|
||||
|
15
pkg/arvo/mar/contact/share.hoon
Normal file
15
pkg/arvo/mar/contact/share.hoon
Normal file
@ -0,0 +1,15 @@
|
||||
/+ *contact-store
|
||||
::
|
||||
|_ share=[%share =ship]
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun share
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
+$ noun [%share ship]
|
||||
++ json share:share-dejs
|
||||
--
|
||||
--
|
@ -6,22 +6,6 @@
|
||||
|%
|
||||
++ noun 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
|
||||
|
@ -7,13 +7,6 @@
|
||||
|%
|
||||
++ noun upd
|
||||
++ json (update:enjs upd)
|
||||
++ resource
|
||||
?+ -.q.upd !!
|
||||
?(%run-updates %add-nodes %remove-nodes %add-graph) resource.q.upd
|
||||
?(%remove-graph %archive-graph %unarchive-graph) resource.q.upd
|
||||
?(%add-tag %remove-tag) resource.q.upd
|
||||
?(%add-signatures %remove-signatures) resource.uid.q.upd
|
||||
==
|
||||
++ mime [/application/x-urb-graph-update (as-octs (jam upd))]
|
||||
--
|
||||
::
|
||||
|
@ -4,10 +4,6 @@
|
||||
++ grow
|
||||
|%
|
||||
++ noun upd
|
||||
++ resource
|
||||
?< ?=(%initial -.upd)
|
||||
resource.upd
|
||||
::
|
||||
++ json
|
||||
%+ frond:enjs:format 'groupUpdate'
|
||||
(update:enjs upd)
|
||||
|
@ -4,9 +4,6 @@
|
||||
++ grow
|
||||
|%
|
||||
++ noun update
|
||||
++ resource
|
||||
?> ?=(?(%add %remove %initial-group) -.update)
|
||||
group.update
|
||||
++ json (update:enjs:store update)
|
||||
--
|
||||
::
|
||||
|
@ -1,12 +1,13 @@
|
||||
/- *pull-hook
|
||||
|_ act=action
|
||||
/+ pull-hook
|
||||
|_ =action:pull-hook
|
||||
++ grab
|
||||
|%
|
||||
++ noun action
|
||||
++ noun action:pull-hook
|
||||
++ json action:dejs:pull-hook
|
||||
--
|
||||
++ grow
|
||||
|%
|
||||
++ noun act
|
||||
++ noun action
|
||||
--
|
||||
++ grad %noun
|
||||
--
|
||||
|
@ -4,7 +4,9 @@
|
||||
++ grow
|
||||
|%
|
||||
++ noun rid
|
||||
++ json (enjs:resource rid)
|
||||
++ json
|
||||
%+ frond:enjs:format %resource
|
||||
(enjs:resource rid)
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
|
@ -45,6 +45,7 @@
|
||||
[%add rid groups+rid metadatum]
|
||||
;< ~ bind:m (poke-our %metadata-store %metadata-action !>(met-action))
|
||||
;< ~ bind:m (poke-our %metadata-push-hook push-hook-act)
|
||||
;< ~ bind:m (poke-our %contact-push-hook push-hook-act)
|
||||
(pure:m !>(~))
|
||||
|
||||
|
||||
|
@ -26,5 +26,6 @@
|
||||
;< ~ bind:m (cleanup-md:view rid)
|
||||
;< ~ bind:m (poke-our %group-store %group-update !>([%remove-group rid ~]))
|
||||
;< ~ bind:m (poke-our %metadata-push-hook push-hook-act)
|
||||
;< ~ bind:m (poke-our %contact-push-hook push-hook-act)
|
||||
;< ~ bind:m (poke-our %group-push-hook push-hook-act)
|
||||
(pure:m !>(~))
|
||||
|
@ -22,6 +22,7 @@
|
||||
:- %pull-hook-action
|
||||
!> ^- action:pull-hook
|
||||
[%remove rid]
|
||||
;< ~ bind:m (poke-our %contact-pull-hook pull-hook-act)
|
||||
;< ~ bind:m (poke-our %metadata-pull-hook pull-hook-act)
|
||||
;< ~ bind:m (poke-our %group-pull-hook pull-hook-act)
|
||||
;< ~ bind:m (poke-our %group-store %group-update !>([%remove-group rid ~]))
|
||||
|
@ -57,6 +57,7 @@ export default class BaseApi<S extends object = {}> {
|
||||
}
|
||||
|
||||
scry<T>(app: string, path: Path): Promise<T> {
|
||||
console.log(path);
|
||||
return fetch(`/~/scry/${app}${path}.json`).then(r => r.json() as Promise<T>);
|
||||
}
|
||||
|
||||
|
@ -34,12 +34,55 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
});
|
||||
}
|
||||
|
||||
allowShips(ships: Patp[]) {
|
||||
return this.storeAction({
|
||||
allow: {
|
||||
ships
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
allowGroup(ship: string, name: string) {
|
||||
const group = { ship, name };
|
||||
return this.storeAction({
|
||||
allow: {
|
||||
group
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setPublic(setPublic: any) {
|
||||
return this.storeAction({
|
||||
'set-public': setPublic
|
||||
});
|
||||
}
|
||||
|
||||
share(recipient: Patp) {
|
||||
return this.action(
|
||||
'contact-push-hook',
|
||||
'contact-share',
|
||||
{ share: recipient },
|
||||
);
|
||||
}
|
||||
|
||||
fetchIsAllowed(entity, name, ship, personal) {
|
||||
const isPersonal = personal ? 'true' : 'false';
|
||||
return this.scry<any>(
|
||||
'contact-store',
|
||||
`/is-allowed/${entity}/${name}/${ship}/${isPersonal}`
|
||||
);
|
||||
}
|
||||
|
||||
retrieve(ship: string) {
|
||||
const resource = { ship, name: '' };
|
||||
return this.action('contact-pull-hook', 'pull-hook-action', {
|
||||
add: {
|
||||
resource,
|
||||
ship
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private storeAction(action: any): Promise<any> {
|
||||
return this.action('contact-store', 'contact-update', action)
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ 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.push(result(e, `/~profile/${e}`, 'profile', contacts[e]?.status || ""));
|
||||
});
|
||||
return ships;
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import _ from 'lodash';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { ContactUpdate } from '~/types/contact-update';
|
||||
import {resourceAsPath} from '../lib/util';
|
||||
|
||||
type ContactState = Pick<StoreState, 'contacts'>;
|
||||
|
||||
@ -14,6 +15,12 @@ export const ContactReducer = (json, state) => {
|
||||
edit(data, state);
|
||||
setPublic(data, state);
|
||||
}
|
||||
|
||||
// TODO: better isolation
|
||||
const res = _.get(json, 'resource', false);
|
||||
if(res) {
|
||||
state.nackedContacts = state.nackedContacts.add(`~${res.ship}`);
|
||||
}
|
||||
};
|
||||
|
||||
const initial = (json: ContactUpdate, state: S) => {
|
||||
|
@ -80,6 +80,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
},
|
||||
isContactPublic: false,
|
||||
contacts: {},
|
||||
nackedContacts: new Set(),
|
||||
notifications: new BigIntOrderedMap<Timebox>(),
|
||||
archivedNotifications: new BigIntOrderedMap<Timebox>(),
|
||||
notificationsGroupConfig: [],
|
||||
|
@ -12,7 +12,8 @@ import {
|
||||
NotificationGraphConfig,
|
||||
GroupNotificationsConfig,
|
||||
Unreads,
|
||||
JoinRequests
|
||||
JoinRequests,
|
||||
Patp
|
||||
} from "~/types";
|
||||
|
||||
export interface StoreState {
|
||||
@ -29,6 +30,7 @@ export interface StoreState {
|
||||
// groups state
|
||||
groups: Groups;
|
||||
groupKeys: Set<Path>;
|
||||
nackedContacts: Set<Patp>
|
||||
s3: S3State;
|
||||
graphs: Graphs;
|
||||
graphKeys: Set<string>;
|
||||
|
@ -45,6 +45,7 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
|
||||
this.subscribe('/updates', 'hark-group-hook');
|
||||
this.subscribe('/all', 'settings-store');
|
||||
this.subscribe('/all', 'group-view');
|
||||
this.subscribe('/nacks', 'contact-pull-hook');
|
||||
}
|
||||
|
||||
restart() {
|
||||
|
@ -167,12 +167,14 @@ class App extends React.Component {
|
||||
<Omnibox
|
||||
associations={state.associations}
|
||||
apps={state.launch}
|
||||
tiles={state.launch.tiles}
|
||||
api={this.api}
|
||||
contacts={state.contacts}
|
||||
notifications={state.notificationsCount}
|
||||
invites={state.invites}
|
||||
groups={state.groups}
|
||||
show={this.props.omniboxShown}
|
||||
toggle={this.props.toggleOmnibox}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useRef, useCallback, useEffect } from 'react';
|
||||
import React, { useRef, useCallback, useEffect, useState } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
@ -36,6 +36,7 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
|
||||
const [,, owner, name] = station.split('/');
|
||||
const ourContact = contacts?.[`~${window.ship}`];
|
||||
console.log(contacts);
|
||||
|
||||
const chatInput = useRef<ChatInput>();
|
||||
|
||||
@ -89,7 +90,13 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
|
||||
return (
|
||||
<Col {...bind} height="100%" overflow="hidden" position="relative">
|
||||
<ShareProfile our={ourContact} />
|
||||
<ShareProfile
|
||||
our={ourContact}
|
||||
api={props.api}
|
||||
recipient={owner}
|
||||
group={group}
|
||||
groupPath={groupPath}
|
||||
/>
|
||||
{dragging && <SubmitDragger />}
|
||||
<ChatWindow
|
||||
mailboxSize={5}
|
||||
|
@ -1,16 +1,111 @@
|
||||
import React from 'react';
|
||||
import React, {
|
||||
useState,
|
||||
useEffect
|
||||
} from 'react';
|
||||
import { Box, Row, Text, BaseImage } from '@tlon/indigo-react';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
|
||||
export const ShareProfile = (props) => {
|
||||
const image = (props?.our?.avatar)
|
||||
? <BaseImage src={props.our.avatar} width='24px' height='24px' borderRadius={2} style={{ objectFit: 'cover' }} />
|
||||
: <Row p={1} alignItems="center" borderRadius={2} backgroundColor={`#${uxToHex(props?.our?.color)}` || "#000000"}>
|
||||
<Sigil ship={window.ship} size={16} icon color={`#${uxToHex(props?.our?.color)}` || "#000000"} />
|
||||
</Row>;
|
||||
const pathAsResource = (path) => {
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
const pathArr = path.split('/');
|
||||
if (pathArr.length !== 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
return {
|
||||
entity: pathArr[2],
|
||||
name: pathArr[3]
|
||||
};
|
||||
};
|
||||
|
||||
export const ShareProfile = (props) => {
|
||||
const { api, hideBanner, group, groupPath } = props;
|
||||
|
||||
const [showBanner, setShowBanner] = useState(false);
|
||||
const res = pathAsResource(groupPath);
|
||||
const [recipients, setRecipients] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!res) { return; }
|
||||
if (!group) { return; }
|
||||
if (group.hidden) {
|
||||
const members = _.compact(await Promise.all(
|
||||
Array.from(group.members)
|
||||
.map(s => {
|
||||
const ship = `~${s}`;
|
||||
if(s === window.ship) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return props.api.contacts.fetchIsAllowed(
|
||||
`~${window.ship}`,
|
||||
'personal',
|
||||
ship,
|
||||
true
|
||||
).then(isAllowed => {
|
||||
return isAllowed ? null : ship;
|
||||
});
|
||||
})
|
||||
));
|
||||
if(members.length > 0) {
|
||||
setShowBanner(true);
|
||||
setRecipients(members);
|
||||
} else {
|
||||
setShowBanner(false);
|
||||
}
|
||||
|
||||
} else {
|
||||
const groupShared = await props.api.contacts.fetchIsAllowed(
|
||||
res.entity,
|
||||
res.name,
|
||||
res.entity,
|
||||
false
|
||||
);
|
||||
setShowBanner(!groupShared);
|
||||
}
|
||||
})();
|
||||
|
||||
}, [groupPath]);
|
||||
|
||||
const image = (props?.our?.avatar)
|
||||
? (
|
||||
<BaseImage
|
||||
src={props.our.avatar}
|
||||
width='24px'
|
||||
height='24px'
|
||||
borderRadius={2}
|
||||
style={{ objectFit: 'cover' }} />
|
||||
) : (
|
||||
<Row
|
||||
p={1}
|
||||
alignItems="center"
|
||||
borderRadius={2}
|
||||
backgroundColor={!props.our ? `#${uxToHex(props.our.color)}` : "#000000"}>
|
||||
<Sigil
|
||||
ship={window.ship}
|
||||
size={16}
|
||||
color={`#${uxToHex(props?.our?.color)}` || "#000000"}
|
||||
icon />
|
||||
</Row>
|
||||
);
|
||||
|
||||
const onClick = async () => {
|
||||
if(group.hidden && recipients.length > 0) {
|
||||
await api.contacts.allowShips(recipients);
|
||||
await Promise.all(recipients.map(r => api.contacts.share(r)))
|
||||
setShowBanner(false);
|
||||
} else if (!group.hidden) {
|
||||
const [,,ship,name] = groupPath.split('/');
|
||||
await api.contacts.allowGroup(ship,name);
|
||||
await api.contacts.share(ship);
|
||||
setShowBanner(false);
|
||||
}
|
||||
};
|
||||
|
||||
return showBanner ? (
|
||||
<Row
|
||||
height="48px"
|
||||
alignItems="center"
|
||||
@ -22,9 +117,9 @@ export const ShareProfile = (props) => {
|
||||
{image}
|
||||
<Text verticalAlign="middle" pl={2}>Share private profile?</Text>
|
||||
</Row>
|
||||
<Box pr={2}>
|
||||
<Box pr={2} onClick={onClick}>
|
||||
<Text color="blue" bold cursor="pointer">Share</Text>
|
||||
</Box>
|
||||
</Row>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
@ -1,167 +0,0 @@
|
||||
import React from "react";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import * as Yup from "yup";
|
||||
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
import {
|
||||
ManagedForm as Form,
|
||||
Col,
|
||||
ManagedTextInputField as Input,
|
||||
Box,
|
||||
Text,
|
||||
Row,
|
||||
BaseImage
|
||||
} from "@tlon/indigo-react";
|
||||
import { Formik, FormikHelpers } from "formik";
|
||||
import { Contact } from "~/types/contact-update";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import { ColorInput } from "~/views/components/ColorInput";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { ImageInput } from "~/views/components/ImageInput";
|
||||
import { S3State } from "~/types";
|
||||
import useLocalState from "~/logic/state/local";
|
||||
|
||||
interface ContactCardProps {
|
||||
contact: Contact;
|
||||
path: string;
|
||||
api: GlobalApi;
|
||||
s3: S3State;
|
||||
rootIdentity: Contact;
|
||||
}
|
||||
|
||||
const formSchema = Yup.object({
|
||||
color: Yup.string(),
|
||||
nickname: Yup.string(),
|
||||
email: Yup.string().matches(
|
||||
new RegExp(
|
||||
String(
|
||||
/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*/.source
|
||||
) +
|
||||
/@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/
|
||||
.source
|
||||
),
|
||||
"Not a valid email"
|
||||
),
|
||||
phone: Yup.string().matches(
|
||||
new RegExp(
|
||||
String(/^\s*(?:\+?(\d{1,3}))?/.source) +
|
||||
/([-. (]*(\d{3})[-. )]*)?((\d{3})[-. ]*(\d{2,4})(?:[-.x ]*(\d+))?)\s*$/
|
||||
.source
|
||||
),
|
||||
"Not a valid phone"
|
||||
),
|
||||
|
||||
website: Yup.string().matches(
|
||||
new RegExp(
|
||||
String(/[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}/.source) +
|
||||
/\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/.source
|
||||
),
|
||||
"Not a valid website"
|
||||
),
|
||||
});
|
||||
|
||||
const emptyContact = {
|
||||
avatar: null,
|
||||
color: '0',
|
||||
nickname: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
website: '',
|
||||
notes: ''
|
||||
};
|
||||
|
||||
export function ContactCard(props: ContactCardProps) {
|
||||
const { hideAvatars, hideNicknames } = useLocalState(({ hideAvatars, hideNicknames }) => ({
|
||||
hideAvatars, hideNicknames
|
||||
}));
|
||||
const us = `~${window.ship}`;
|
||||
const { contact, rootIdentity } = props;
|
||||
const onSubmit = async (values: any, actions: FormikHelpers<Contact>) => {
|
||||
try {
|
||||
if(!contact) {
|
||||
const [,,ship] = props.path.split('/');
|
||||
values.color = uxToHex(values.color);
|
||||
const sharedValues = Object.assign({}, values);
|
||||
sharedValues.avatar = !values.avatar ? null : { url: values.avatar };
|
||||
console.log(values);
|
||||
await props.api.contacts.share(ship, props.path, us, sharedValues);
|
||||
actions.setStatus({ success: null });
|
||||
return;
|
||||
}
|
||||
|
||||
await Object.keys(values).reduce((acc, key) => {
|
||||
const newValue = key !== "color" ? values[key] : uxToHex(values[key]);
|
||||
if (newValue !== contact[key]) {
|
||||
if (key === "avatar") {
|
||||
return acc.then(() =>
|
||||
props.api.contacts.edit(props.path, us, {
|
||||
avatar: { url: newValue },
|
||||
} as any)
|
||||
);
|
||||
}
|
||||
|
||||
return acc.then(() =>
|
||||
props.api.contacts.edit(props.path, us, {
|
||||
[key]: newValue,
|
||||
} as any)
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}, Promise.resolve());
|
||||
actions.setStatus({ success: null });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: e.message });
|
||||
}
|
||||
};
|
||||
|
||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : "#000000";
|
||||
const image = (!hideAvatars && contact?.avatar)
|
||||
? <BaseImage src={contact.avatar} width='100%' height='100%' style={{ objectFit: 'cover' }} />
|
||||
: <Sigil ship={us} size={32} color={hexColor} />;
|
||||
|
||||
const nickname = (!hideNicknames && contact?.nickname) ? contact.nickname : "";
|
||||
|
||||
return (
|
||||
<Box p={4} height="100%" overflowY="auto">
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={contact || rootIdentity || emptyContact}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form
|
||||
display="grid"
|
||||
gridAutoRows="auto"
|
||||
gridTemplateColumns="100%"
|
||||
gridRowGap="5"
|
||||
maxWidth="400px"
|
||||
width="100%"
|
||||
>
|
||||
<Row
|
||||
borderBottom={1}
|
||||
borderBottomColor="washedGray"
|
||||
pb={3}
|
||||
alignItems="center"
|
||||
>
|
||||
<Box height='32px' width='32px'>
|
||||
{image}
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<Text mono={!Boolean(nickname)}>{nickname}</Text>
|
||||
</Box>
|
||||
</Row>
|
||||
<ImageInput id="avatar" label="Avatar" s3={props.s3} />
|
||||
<ColorInput id="color" label="Sigil Color" />
|
||||
<Input id="nickname" label="Nickname" />
|
||||
<Input id="email" label="Email" />
|
||||
<Input id="phone" label="Phone" />
|
||||
<Input id="website" label="Website" />
|
||||
<Input id="notes" label="Notes" />
|
||||
<AsyncButton primary loadingText="Updating..." border>
|
||||
{(contact) ? "Save" : "Share Contact"}
|
||||
</AsyncButton>
|
||||
</Form>
|
||||
</Formik>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, {useEffect} from "react";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import { ViewProfile } from './ViewProfile';
|
||||
import { EditProfile } from './EditProfile';
|
||||
@ -24,7 +24,16 @@ export function Profile(props: any) {
|
||||
if (!props.ship) {
|
||||
return null;
|
||||
}
|
||||
const { contact, isPublic, isEdit, ship } = props;
|
||||
const { contact, nackedContacts, hasLoaded, isPublic, isEdit, ship } = props;
|
||||
const nacked = nackedContacts.has(ship);
|
||||
|
||||
useEffect(() => {
|
||||
if(hasLoaded && !contact && !nacked) {
|
||||
props.api.contacts.retrieve(ship);
|
||||
}
|
||||
}, [hasLoaded, contact])
|
||||
|
||||
|
||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : "#000000";
|
||||
const cover = (contact?.cover)
|
||||
? <BaseImage src={contact.cover} width='100%' height='100%' style={{ objectFit: 'cover' }} />
|
||||
@ -70,7 +79,13 @@ export function Profile(props: any) {
|
||||
associations={props.associations}
|
||||
isPublic={isPublic}/>
|
||||
) : (
|
||||
<ViewProfile ship={ship} contact={contact} isPublic={isPublic} />
|
||||
<ViewProfile
|
||||
api={props.api}
|
||||
nacked={nacked}
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
isPublic={isPublic}
|
||||
/>
|
||||
) }
|
||||
</Box>
|
||||
</Center>
|
||||
|
@ -59,3 +59,4 @@ export function SetStatus(props: any) {
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
|
||||
import {
|
||||
@ -7,17 +7,28 @@ import {
|
||||
Text,
|
||||
Row,
|
||||
Button,
|
||||
Col
|
||||
Col,
|
||||
LoadingSpinner
|
||||
} from "@tlon/indigo-react";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import RichText from "~/views/components/RichText";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {GroupSummary} from "~/views/landscape/components/GroupSummary";
|
||||
import {MetadataUpdatePreview} from "~/types";
|
||||
|
||||
|
||||
export function ViewProfile(props: any) {
|
||||
const history = useHistory();
|
||||
const { contact, isPublic, ship } = props;
|
||||
const { api, contact, nacked, isPublic, ship } = props;
|
||||
|
||||
const [previews, setPreviews] = useState<MetadataUpdatePreview[]>([]);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setPreviews(
|
||||
await Promise.all((contact?.groups || []).map(g => api.metadata.preview(g)))
|
||||
);
|
||||
})();
|
||||
}, [contact?.groups])
|
||||
return (
|
||||
<>
|
||||
<Row
|
||||
@ -48,7 +59,27 @@ export function ViewProfile(props: any) {
|
||||
{(contact?.bio ? contact.bio : "")}
|
||||
</RichText>
|
||||
</Center>
|
||||
</Col>
|
||||
{ (contact?.groups || []).length > 0 && (
|
||||
<Col gapY="3" my="3" alignItems="center">
|
||||
<Text fontWeight="medium">Pinned Groups</Text>
|
||||
{previews.length > 0 ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<Row justifyContent="center" gapX="3">
|
||||
{previews.map(p => (
|
||||
<Box p="2" border="1" borderColor="washedGray">
|
||||
<GroupSummary
|
||||
metadata={p.metadata}
|
||||
memberCount={p.members}
|
||||
channelCount={p?.['channel-count']}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
)}
|
||||
{ (ship === `~${window.ship}`) ? (
|
||||
<Row
|
||||
pb={2}
|
||||
@ -65,7 +96,7 @@ export function ViewProfile(props: any) {
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
{ !isPublic && ship === `~${window.ship}` ? (
|
||||
{ (nacked || (!isPublic && ship === `~${window.ship}`)) ? (
|
||||
<Box
|
||||
height="200px"
|
||||
borderRadius={1}
|
||||
|
@ -45,6 +45,7 @@ export default function ProfileScreen(props: any) {
|
||||
<Box>
|
||||
<Profile
|
||||
ship={ship}
|
||||
hasLoaded={Object.keys(props.contacts).length !== 0}
|
||||
associations={props.associations}
|
||||
groups={props.groups}
|
||||
contact={contact}
|
||||
@ -52,6 +53,7 @@ export default function ProfileScreen(props: any) {
|
||||
s3={props.s3}
|
||||
isEdit={isEdit}
|
||||
isPublic={isPublic}
|
||||
nackedContacts={props.nackedContacts}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -1,304 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Box, Row, Rule, Text } from '@tlon/indigo-react';
|
||||
import index from '~/logic/lib/omnibox';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import OmniboxInput from './OmniboxInput';
|
||||
import OmniboxResult from './OmniboxResult';
|
||||
import { withLocalState } from '~/logic/state/local';
|
||||
|
||||
import defaultApps from '~/logic/lib/default-apps';
|
||||
|
||||
export class Omnibox extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
index: new Map([]),
|
||||
query: '',
|
||||
results: this.initialResults(),
|
||||
selected: []
|
||||
};
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||
this.search = this.search.bind(this);
|
||||
this.navigate = this.navigate.bind(this);
|
||||
this.control - this.control.bind(this);
|
||||
this.setPreviousSelected = this.setPreviousSelected.bind(this);
|
||||
this.setNextSelected = this.setNextSelected.bind(this);
|
||||
this.renderResults = this.renderResults.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps !== this.props) {
|
||||
const { pathname } = this.props.location;
|
||||
const selectedGroup = pathname.startsWith('/~landscape/ship/') ? '/' + pathname.split('/').slice(2,5).join('/') : null;
|
||||
|
||||
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 === '')) {
|
||||
this.setState({ results: this.initialResults() });
|
||||
}
|
||||
|
||||
if (prevProps && this.props.show && prevProps.show !== this.props.show) {
|
||||
Mousetrap.bind('escape', this.props.toggle);
|
||||
document.addEventListener('mousedown', this.handleClickOutside);
|
||||
const touchstart = new Event('touchstart');
|
||||
this.omniInput.input.dispatchEvent(touchstart);
|
||||
this.omniInput.input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUpdate(prevProps) {
|
||||
if (this.props.show && prevProps.show !== this.props.show) {
|
||||
Mousetrap.unbind('escape');
|
||||
document.removeEventListener('mousedown', this.handleClickOutside);
|
||||
}
|
||||
}
|
||||
|
||||
getSearchedCategories() {
|
||||
return ['ships', 'other', 'commands', 'groups', 'subscriptions', 'apps'];
|
||||
}
|
||||
|
||||
control(evt) {
|
||||
if (evt.key === 'Escape') {
|
||||
if (this.state.query.length > 0) {
|
||||
this.setState({ query: '', results: this.initialResults(), selected: [] });
|
||||
} else if (this.props.show) {
|
||||
this.props.toggleOmnibox();
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
evt.key === 'ArrowUp' ||
|
||||
(evt.shiftKey && evt.key === 'Tab')) {
|
||||
evt.preventDefault();
|
||||
return this.setPreviousSelected();
|
||||
}
|
||||
|
||||
if (evt.key === 'ArrowDown' || evt.key === 'Tab') {
|
||||
evt.preventDefault();
|
||||
this.setNextSelected();
|
||||
}
|
||||
|
||||
if (evt.key === 'Enter') {
|
||||
evt.preventDefault();
|
||||
if (this.state.selected.length > 0) {
|
||||
this.navigate(this.state.selected[0], this.state.selected[1]);
|
||||
} else if (Array.from(this.state.results.values()).flat().length === 0) {
|
||||
return;
|
||||
} else {
|
||||
this.navigate(
|
||||
Array.from(this.state.results.values()).flat()[0].app,
|
||||
Array.from(this.state.results.values()).flat()[0].link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOutside(evt) {
|
||||
if (this.props.show && !this.omniBox.contains(evt.target)) {
|
||||
this.setState({ results: this.initialResults(), query: '', selected: [] }, () => {
|
||||
this.props.toggleOmnibox();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
initialResults() {
|
||||
return new Map(this.getSearchedCategories().map((category) => {
|
||||
if (!this.state) {
|
||||
return [category, []];
|
||||
}
|
||||
if (category === 'other') {
|
||||
return ['other', this.state.index.get('other')];
|
||||
}
|
||||
return [category, []];
|
||||
}));
|
||||
}
|
||||
|
||||
navigate(app, link) {
|
||||
const { props } = this;
|
||||
this.setState({ results: this.initialResults(), query: '' }, () => {
|
||||
props.toggleOmnibox();
|
||||
if (defaultApps.includes(app.toLowerCase())
|
||||
|| app === 'profile'
|
||||
|| app === 'Links'
|
||||
|| app === 'Terminal'
|
||||
|| app === 'home'
|
||||
|| app === 'inbox')
|
||||
{
|
||||
props.history.push(link);
|
||||
} else {
|
||||
window.location.href = link;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
search(event) {
|
||||
const { state } = this;
|
||||
let query = event.target.value;
|
||||
const results = this.initialResults();
|
||||
let selected = state.selected;
|
||||
|
||||
this.setState({ query });
|
||||
|
||||
// wipe results if backspacing
|
||||
if (query.length === 0) {
|
||||
this.setState({ results: results, selected: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
// don't search for single characters
|
||||
if (query.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
query = query.toLowerCase();
|
||||
|
||||
this.getSearchedCategories().map((category) => {
|
||||
const categoryIndex = state.index.get(category);
|
||||
results.set(category,
|
||||
categoryIndex.filter((result) => {
|
||||
return (
|
||||
result.title.toLowerCase().includes(query) ||
|
||||
result.link.toLowerCase().includes(query) ||
|
||||
result.app.toLowerCase().includes(query) ||
|
||||
(result.host !== null ? result.host.toLowerCase().includes(query) : false)
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const flattenedResultLinks = Array.from(results.values()).flat().map(result => [result.app, result.link]);
|
||||
if (!flattenedResultLinks.includes(selected)) {
|
||||
selected = flattenedResultLinks[0] || [];
|
||||
}
|
||||
|
||||
this.setState({ results, selected });
|
||||
}
|
||||
|
||||
setPreviousSelected() {
|
||||
const current = this.state.selected;
|
||||
const flattenedResults = Array.from(this.state.results.values()).flat();
|
||||
const totalLength = flattenedResults.length;
|
||||
if (current !== []) {
|
||||
const currentIndex = flattenedResults.indexOf(
|
||||
...flattenedResults.filter((e) => {
|
||||
return e.link === current[1];
|
||||
})
|
||||
);
|
||||
if (currentIndex > 0) {
|
||||
const { app, link } = flattenedResults[currentIndex - 1];
|
||||
this.setState({ selected: [app, link] });
|
||||
} else {
|
||||
const { app, link } = flattenedResults[totalLength - 1];
|
||||
this.setState({ selected: [app, link] });
|
||||
}
|
||||
} else {
|
||||
const { app, link } = flattenedResults[totalLength - 1];
|
||||
this.setState({ selected: [app, link] });
|
||||
}
|
||||
}
|
||||
|
||||
setNextSelected() {
|
||||
const current = this.state.selected;
|
||||
const flattenedResults = Array.from(this.state.results.values()).flat();
|
||||
if (current !== []) {
|
||||
const currentIndex = flattenedResults.indexOf(
|
||||
...flattenedResults.filter((e) => {
|
||||
return e.link === current[1];
|
||||
})
|
||||
);
|
||||
if (currentIndex < flattenedResults.length - 1) {
|
||||
const { app, link } = flattenedResults[currentIndex + 1];
|
||||
this.setState({ selected: [app, link] });
|
||||
} else {
|
||||
const { app, link } = flattenedResults[0];
|
||||
this.setState({ selected: [app, link] });
|
||||
}
|
||||
} else {
|
||||
const { app, link } = flattenedResults[0];
|
||||
this.setState({ selected: [app, link] });
|
||||
}
|
||||
}
|
||||
|
||||
renderResults() {
|
||||
const { props, state } = this;
|
||||
return <Box
|
||||
maxHeight={['200px', "400px"]}
|
||||
overflowY="auto"
|
||||
overflowX="hidden"
|
||||
borderBottomLeftRadius='2'
|
||||
borderBottomRightRadius='2'
|
||||
>
|
||||
{this.getSearchedCategories()
|
||||
.map(category => Object({ category, categoryResults: state.results.get(category) }))
|
||||
.filter(category => category.categoryResults.length > 0)
|
||||
.map(({ category, categoryResults }, i) => {
|
||||
const categoryTitle = (category === 'other')
|
||||
? null : <Row pl='2' height='5' alignItems='center' bg='washedGray'><Text gray bold>{category.charAt(0).toUpperCase() + category.slice(1)}</Text></Row>;
|
||||
const selected = this.state.selected?.length ? this.state.selected[1] : '';
|
||||
return (<Box key={i} width='max(50vw, 300px)' maxWidth='600px'>
|
||||
{categoryTitle}
|
||||
{categoryResults.map((result, i2) => (
|
||||
<OmniboxResult
|
||||
key={i2}
|
||||
icon={result.app}
|
||||
text={result.title}
|
||||
subtext={result.host}
|
||||
link={result.link}
|
||||
navigate={() => this.navigate(result.app, result.link)}
|
||||
selected={selected}
|
||||
invites={props.invites}
|
||||
notifications={props.notifications}
|
||||
contacts={props.contacts}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Box>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
if (state?.selected?.length === 0 && Array.from(this.state.results.values()).flat().length) {
|
||||
this.setNextSelected();
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
backgroundColor='scales.black30'
|
||||
width='100%'
|
||||
height='100%'
|
||||
position='absolute'
|
||||
top='0'
|
||||
right='0'
|
||||
zIndex='9'
|
||||
display={props.show ? 'block' : 'none'}>
|
||||
<Row justifyContent='center'>
|
||||
<Box
|
||||
mt={['10vh', '20vh']}
|
||||
width='max(50vw, 300px)'
|
||||
maxWidth='600px'
|
||||
borderRadius='2'
|
||||
backgroundColor='white'
|
||||
ref={(el) => {
|
||||
this.omniBox = el;
|
||||
}}>
|
||||
<OmniboxInput
|
||||
ref={(el) => {
|
||||
this.omniInput = el;
|
||||
}}
|
||||
control={e => this.control(e)}
|
||||
search={this.search}
|
||||
query={state.query}
|
||||
/>
|
||||
{this.renderResults()}
|
||||
</Box>
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(withLocalState(Omnibox, ['toggleOmnibox', 'omniboxShown']));
|
298
pkg/interface/src/views/components/leap/Omnibox.tsx
Normal file
298
pkg/interface/src/views/components/leap/Omnibox.tsx
Normal file
@ -0,0 +1,298 @@
|
||||
import React, { useMemo, useRef, useCallback, useEffect, useState } from 'react';
|
||||
import { withRouter, useLocation, useHistory } from 'react-router-dom';
|
||||
import { Box, Row, Rule, Text } from '@tlon/indigo-react';
|
||||
import * as ob from 'urbit-ob';
|
||||
import makeIndex from '~/logic/lib/omnibox';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import OmniboxInput from './OmniboxInput';
|
||||
import OmniboxResult from './OmniboxResult';
|
||||
import { withLocalState } from '~/logic/state/local';
|
||||
import { deSig } from '~/logic/lib/util';
|
||||
|
||||
import defaultApps from '~/logic/lib/default-apps';
|
||||
import {Associations, Contacts, Groups, Tile, Invites} from '~/types';
|
||||
import {useOutsideClick} from '~/logic/lib/useOutsideClick';
|
||||
|
||||
interface OmniboxProps {
|
||||
associations: Associations;
|
||||
contacts: Contacts;
|
||||
groups: Groups;
|
||||
tiles: {
|
||||
[app: string]: Tile;
|
||||
};
|
||||
show: boolean;
|
||||
toggle: () => void;
|
||||
notifications: number;
|
||||
invites: Invites;
|
||||
}
|
||||
|
||||
const SEARCHED_CATEGORIES = ['ships', 'other', 'commands', 'groups', 'subscriptions', 'apps'];
|
||||
|
||||
export function Omnibox(props: OmniboxProps) {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const omniboxRef = useRef<HTMLDivElement | null>(null)
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const [query, setQuery] = useState('');
|
||||
const [selected, setSelected] = useState<[] | [string, string]>([])
|
||||
|
||||
const contacts = useMemo(() => {
|
||||
const maybeShip = `~${deSig(query)}`;
|
||||
return ob.isValidPatp(maybeShip)
|
||||
? {...props.contacts, [maybeShip]: {} }
|
||||
: props.contacts;
|
||||
}, [props.contacts, query]);
|
||||
|
||||
const index = useMemo(() => {
|
||||
const selectedGroup = location.pathname.startsWith('/~landscape/ship/')
|
||||
? '/' + location.pathname.split('/').slice(2,5).join('/')
|
||||
: null;
|
||||
return makeIndex(
|
||||
contacts,
|
||||
props.associations,
|
||||
props.tiles,
|
||||
selectedGroup,
|
||||
props.groups
|
||||
);
|
||||
}, [location.pathname, contacts, props.associations, props.groups, props.tiles]);
|
||||
|
||||
const onOutsideClick = useCallback(() => {
|
||||
props.show && props.toggle()
|
||||
}, [props.show, props.toggle]);
|
||||
|
||||
useOutsideClick(omniboxRef, onOutsideClick)
|
||||
|
||||
// handle omnibox show
|
||||
useEffect(() => {
|
||||
if(!props.show) {
|
||||
return;
|
||||
}
|
||||
Mousetrap.bind('escape', props.toggle);
|
||||
const touchstart = new Event('touchstart');
|
||||
inputRef?.current?.input?.dispatchEvent(touchstart);
|
||||
inputRef?.current?.input?.focus();
|
||||
return () => {
|
||||
Mousetrap.unbind('escape');
|
||||
setQuery('');
|
||||
};
|
||||
}, [props.show]);
|
||||
|
||||
const initialResults = useMemo(() => {
|
||||
return new Map(SEARCHED_CATEGORIES.map((category) => {
|
||||
if (category === 'other') {
|
||||
return ['other', index.get('other')];
|
||||
}
|
||||
return [category, []];
|
||||
}));
|
||||
}, [index]);
|
||||
|
||||
const results = useMemo(() => {
|
||||
if(query.length <= 1) {
|
||||
return initialResults;
|
||||
}
|
||||
const q = query.toLowerCase();
|
||||
let resultsMap = new Map();
|
||||
SEARCHED_CATEGORIES.map((category) => {
|
||||
const categoryIndex = index.get(category);
|
||||
resultsMap.set(category,
|
||||
categoryIndex.filter((result) => {
|
||||
return (
|
||||
result.title.toLowerCase().includes(q) ||
|
||||
result.link.toLowerCase().includes(q) ||
|
||||
result.app.toLowerCase().includes(q) ||
|
||||
(result.host !== null ? result.host.toLowerCase().includes(q) : false)
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
return resultsMap;
|
||||
}, [query, index]);
|
||||
|
||||
const navigate = useCallback((app: string, link: string) => {
|
||||
props.toggle();
|
||||
if (defaultApps.includes(app.toLowerCase())
|
||||
|| app === 'profile'
|
||||
|| app === 'Links'
|
||||
|| app === 'Terminal'
|
||||
|| app === 'home'
|
||||
|| app === 'inbox')
|
||||
{
|
||||
history.push(link);
|
||||
} else {
|
||||
window.location.href = link;
|
||||
}
|
||||
}, [history, props.toggle]);
|
||||
|
||||
const setPreviousSelected = useCallback(() => {
|
||||
const flattenedResults = Array.from(results.values()).flat();
|
||||
const totalLength = flattenedResults.length;
|
||||
if (selected.length) {
|
||||
const currentIndex = flattenedResults.indexOf(
|
||||
...flattenedResults.filter((e) => {
|
||||
return e.link === selected[1];
|
||||
})
|
||||
);
|
||||
if (currentIndex > 0) {
|
||||
const { app, link } = flattenedResults[currentIndex - 1];
|
||||
setSelected([app, link]);
|
||||
} else {
|
||||
const { app, link } = flattenedResults[totalLength - 1];
|
||||
setSelected([app, link]);
|
||||
}
|
||||
} else {
|
||||
const { app, link } = flattenedResults[totalLength - 1];
|
||||
setSelected([app, link]);
|
||||
}
|
||||
}, [results, selected]);
|
||||
|
||||
const setNextSelected = useCallback(() => {
|
||||
const flattenedResults = Array.from(results.values()).flat();
|
||||
if (selected.length){
|
||||
const currentIndex = flattenedResults.indexOf(
|
||||
...flattenedResults.filter((e) => {
|
||||
return e.link === selected[1];
|
||||
})
|
||||
);
|
||||
if (currentIndex < flattenedResults.length - 1) {
|
||||
const { app, link } = flattenedResults[currentIndex + 1];
|
||||
setSelected([app, link]);
|
||||
} else {
|
||||
const { app, link } = flattenedResults[0];
|
||||
setSelected([app, link]);
|
||||
}
|
||||
} else {
|
||||
const { app, link } = flattenedResults[0];
|
||||
setSelected([app, link]);
|
||||
}
|
||||
}, [selected, results]);
|
||||
|
||||
const control = useCallback((evt) => {
|
||||
if (evt.key === 'Escape') {
|
||||
if (query.length > 0) {
|
||||
setQuery('');
|
||||
return;
|
||||
} else if (props.show) {
|
||||
props.toggle();
|
||||
return;
|
||||
}
|
||||
};
|
||||
if (
|
||||
evt.key === 'ArrowUp' ||
|
||||
(evt.shiftKey && evt.key === 'Tab')) {
|
||||
evt.preventDefault();
|
||||
setPreviousSelected();
|
||||
return;
|
||||
}
|
||||
if (evt.key === 'ArrowDown' || evt.key === 'Tab') {
|
||||
evt.preventDefault();
|
||||
setNextSelected();
|
||||
return;
|
||||
}
|
||||
if (evt.key === 'Enter') {
|
||||
evt.preventDefault();
|
||||
if (selected.length) {
|
||||
navigate(selected[0], selected[1]);
|
||||
} else if (Array.from(results.values()).flat().length === 0) {
|
||||
return;
|
||||
} else {
|
||||
navigate(
|
||||
Array.from(results.values()).flat()[0].app,
|
||||
Array.from(results.values()).flat()[0].link);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
props.toggle,
|
||||
selected,
|
||||
navigate,
|
||||
query,
|
||||
props.show,
|
||||
results,
|
||||
setPreviousSelected,
|
||||
setNextSelected
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const flattenedResultLinks = Array.from(results.values())
|
||||
.flat()
|
||||
.map(result => [result.app, result.link]);
|
||||
if (!flattenedResultLinks.includes(selected)) {
|
||||
setSelected(flattenedResultLinks[0] || []);
|
||||
}
|
||||
}, [results]);
|
||||
|
||||
const search = useCallback((event) => {
|
||||
setQuery(event.target.value);
|
||||
}, []);
|
||||
|
||||
const renderResults = useCallback(() => {
|
||||
return <Box
|
||||
maxHeight={['200px', "400px"]}
|
||||
overflowY="auto"
|
||||
overflowX="hidden"
|
||||
borderBottomLeftRadius='2'
|
||||
borderBottomRightRadius='2'
|
||||
>
|
||||
{SEARCHED_CATEGORIES
|
||||
.map(category => Object({ category, categoryResults: results.get(category) }))
|
||||
.filter(category => category.categoryResults.length > 0)
|
||||
.map(({ category, categoryResults }, i) => {
|
||||
const categoryTitle = (category === 'other')
|
||||
? null : <Row pl='2' height='5' alignItems='center' bg='washedGray'><Text gray bold>{category.charAt(0).toUpperCase() + category.slice(1)}</Text></Row>;
|
||||
const sel = selected?.length ? selected[1] : '';
|
||||
return (<Box key={i} width='max(50vw, 300px)' maxWidth='600px'>
|
||||
{categoryTitle}
|
||||
{categoryResults.map((result, i2) => (
|
||||
<OmniboxResult
|
||||
key={i2}
|
||||
icon={result.app}
|
||||
text={result.title}
|
||||
subtext={result.host}
|
||||
link={result.link}
|
||||
navigate={() => navigate(result.app, result.link)}
|
||||
selected={sel}
|
||||
invites={props.invites}
|
||||
notifications={props.notifications}
|
||||
contacts={props.contacts}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Box>;
|
||||
}, [results, navigate, selected, props.contacts, props.notifications, props.invites]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
backgroundColor='scales.black30'
|
||||
width='100%'
|
||||
height='100%'
|
||||
position='absolute'
|
||||
top='0'
|
||||
right='0'
|
||||
zIndex='9'
|
||||
display={props.show ? 'block' : 'none'}>
|
||||
<Row justifyContent='center'>
|
||||
<Box
|
||||
mt={['10vh', '20vh']}
|
||||
width='max(50vw, 300px)'
|
||||
maxWidth='600px'
|
||||
borderRadius='2'
|
||||
backgroundColor='white'
|
||||
ref={(el) => { omniboxRef.current = el; }}
|
||||
>
|
||||
<OmniboxInput
|
||||
ref={(el) => { inputRef.current = el; }}
|
||||
control={e => control(e)}
|
||||
search={search}
|
||||
query={query}
|
||||
/>
|
||||
{renderResults()}
|
||||
</Box>
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default withLocalState(Omnibox, ['toggleOmnibox', 'omniboxShown']);
|
Loading…
Reference in New Issue
Block a user