mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-05 22:03:50 +03:00
Merge pull request #4074 from urbit/release/next-userspace
release/next-userspace -> na-release/candidate
This commit is contained in:
commit
604f0acb57
@ -21,11 +21,15 @@
|
|||||||
:: We get ++unix-event and ++pill from /-aquarium
|
:: We get ++unix-event and ++pill from /-aquarium
|
||||||
::
|
::
|
||||||
/- aquarium
|
/- aquarium
|
||||||
/+ pill, default-agent
|
/+ pill, default-agent, aqua-azimuth, dbug, verb
|
||||||
=, pill-lib=pill
|
=, pill-lib=pill
|
||||||
=, aquarium
|
=, aquarium
|
||||||
=> $~ |%
|
=> $~ |%
|
||||||
+$ state
|
+$ versioned-state
|
||||||
|
$% state-0
|
||||||
|
state-1
|
||||||
|
==
|
||||||
|
+$ state-0
|
||||||
$: %0
|
$: %0
|
||||||
pil=pill
|
pil=pill
|
||||||
assembled=*
|
assembled=*
|
||||||
@ -33,7 +37,16 @@
|
|||||||
fleet-snaps=(map term (map ship pier))
|
fleet-snaps=(map term (map ship pier))
|
||||||
piers=(map ship pier)
|
piers=(map ship pier)
|
||||||
==
|
==
|
||||||
|
+$ state-1
|
||||||
|
$: %1
|
||||||
|
pil=pill
|
||||||
|
assembled=*
|
||||||
|
tym=@da
|
||||||
|
fleet-snaps=(map term fleet)
|
||||||
|
piers=fleet
|
||||||
|
==
|
||||||
::
|
::
|
||||||
|
+$ fleet [ships=(map ship pier) azi=az-state]
|
||||||
+$ pier
|
+$ pier
|
||||||
$: snap=*
|
$: snap=*
|
||||||
event-log=(list unix-timed-event)
|
event-log=(list unix-timed-event)
|
||||||
@ -42,9 +55,11 @@
|
|||||||
==
|
==
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
=| state
|
=| state-1
|
||||||
=* all-state -
|
=* state -
|
||||||
=<
|
=<
|
||||||
|
%- agent:dbug
|
||||||
|
%+ verb |
|
||||||
^- agent:gall
|
^- agent:gall
|
||||||
|_ =bowl:gall
|
|_ =bowl:gall
|
||||||
+* this .
|
+* this .
|
||||||
@ -52,24 +67,37 @@
|
|||||||
ac ~(. aqua-core bowl)
|
ac ~(. aqua-core bowl)
|
||||||
def ~(. (default-agent this %|) bowl)
|
def ~(. (default-agent this %|) bowl)
|
||||||
++ on-init `this
|
++ on-init `this
|
||||||
++ on-save !>(all-state)
|
++ on-save !>(state)
|
||||||
++ on-load
|
++ on-load
|
||||||
|= old-state=vase
|
|= old-vase=vase
|
||||||
^- step:agent:gall
|
^- step:agent:gall
|
||||||
~& prep=%aqua
|
~& prep=%aqua
|
||||||
=+ new=((soft state) !<(* old-state))
|
=+ !<(old=versioned-state old-vase)
|
||||||
?~ new
|
=| cards=(list card:agent:gall)
|
||||||
`this
|
|-
|
||||||
`this(all-state u.new)
|
?- -.old
|
||||||
|
:: wipe fleets and piers rather than give them falsely nulled azimuth state
|
||||||
|
::
|
||||||
|
%0
|
||||||
|
%_ $
|
||||||
|
-.old %1
|
||||||
|
fleet-snaps.old *(map term fleet)
|
||||||
|
piers.old *fleet
|
||||||
|
==
|
||||||
|
::
|
||||||
|
%1
|
||||||
|
[cards this(state old)]
|
||||||
|
==
|
||||||
::
|
::
|
||||||
++ on-poke
|
++ on-poke
|
||||||
|= [=mark =vase]
|
|= [=mark =vase]
|
||||||
^- step:agent:gall
|
^- step:agent:gall
|
||||||
=^ cards all-state
|
=^ cards state
|
||||||
?+ mark ~|([%aqua-bad-mark mark] !!)
|
?+ mark ~|([%aqua-bad-mark mark] !!)
|
||||||
%aqua-events (poke-aqua-events:ac !<((list aqua-event) vase))
|
%aqua-events (poke-aqua-events:ac !<((list aqua-event) vase))
|
||||||
%pill (poke-pill:ac !<(pill vase))
|
%pill (poke-pill:ac !<(pill vase))
|
||||||
%noun (poke-noun:ac !<(* vase))
|
%noun (poke-noun:ac !<(* vase))
|
||||||
|
%azimuth-action (poke-azimuth-action:ac !<(azimuth-action vase))
|
||||||
==
|
==
|
||||||
[cards this]
|
[cards this]
|
||||||
::
|
::
|
||||||
@ -92,7 +120,18 @@
|
|||||||
++ on-peek peek:ac
|
++ on-peek peek:ac
|
||||||
::
|
::
|
||||||
++ on-agent on-agent:def
|
++ on-agent on-agent:def
|
||||||
++ on-arvo on-arvo:def
|
::
|
||||||
|
++ on-arvo
|
||||||
|
|= [=wire sign=sign-arvo]
|
||||||
|
^- step:agent:gall
|
||||||
|
?+ wire (on-arvo:def wire sign)
|
||||||
|
[%wait @ ~]
|
||||||
|
?> ?=(%wake +<.sign)
|
||||||
|
=/ wen=@da (slav %da i.t.wire)
|
||||||
|
=^ cards state
|
||||||
|
(handle-wake:ac wen)
|
||||||
|
[cards this]
|
||||||
|
==
|
||||||
++ on-fail on-fail:def
|
++ on-fail on-fail:def
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
@ -110,7 +149,7 @@
|
|||||||
::
|
::
|
||||||
++ pe
|
++ pe
|
||||||
|= who=ship
|
|= who=ship
|
||||||
=+ (~(gut by piers) who *pier)
|
=+ (~(gut by ships.piers) who *pier)
|
||||||
=* pier-data -
|
=* pier-data -
|
||||||
|%
|
|%
|
||||||
::
|
::
|
||||||
@ -118,7 +157,7 @@
|
|||||||
::
|
::
|
||||||
++ abet-pe
|
++ abet-pe
|
||||||
^+ this
|
^+ this
|
||||||
=. piers (~(put by piers) who pier-data)
|
=. ships.piers (~(put by ships.piers) who pier-data)
|
||||||
this
|
this
|
||||||
::
|
::
|
||||||
:: Initialize new ship
|
:: Initialize new ship
|
||||||
@ -248,7 +287,19 @@
|
|||||||
this
|
this
|
||||||
::
|
::
|
||||||
++ abet-aqua
|
++ abet-aqua
|
||||||
^- (quip card:agent:gall state)
|
^- (quip card:agent:gall _state)
|
||||||
|
::
|
||||||
|
:: interecept %request effects to handle azimuth subscription
|
||||||
|
::
|
||||||
|
=. this
|
||||||
|
%- emit-cards
|
||||||
|
%- zing
|
||||||
|
%+ turn ~(tap by unix-effects)
|
||||||
|
|= [=ship ufs=(list unix-effect)]
|
||||||
|
%+ murn ufs
|
||||||
|
|= uf=unix-effect
|
||||||
|
(router:aqua-azimuth our.hid ship uf azi.piers)
|
||||||
|
::
|
||||||
=. this
|
=. this
|
||||||
=/ =path /effect
|
=/ =path /effect
|
||||||
%- emit-cards
|
%- emit-cards
|
||||||
@ -300,20 +351,19 @@
|
|||||||
=/ =path /boths/(scot %p ship)
|
=/ =path /boths/(scot %p ship)
|
||||||
[%give %fact ~[path] %aqua-boths !>(`aqua-boths`[ship (flop bo)])]
|
[%give %fact ~[path] %aqua-boths !>(`aqua-boths`[ship (flop bo)])]
|
||||||
::
|
::
|
||||||
[(flop cards) all-state]
|
[(flop cards) state]
|
||||||
::
|
::
|
||||||
++ emit-cards
|
++ emit-cards
|
||||||
|= ms=(list card:agent:gall)
|
|= ms=(list card:agent:gall)
|
||||||
=. cards (weld ms cards)
|
=. cards (weld ms cards)
|
||||||
this
|
this
|
||||||
::
|
::
|
||||||
::
|
|
||||||
:: Run all events on all ships until all queues are empty
|
:: Run all events on all ships until all queues are empty
|
||||||
::
|
::
|
||||||
++ plow-all
|
++ plow-all
|
||||||
|- ^+ this
|
|- ^+ this
|
||||||
=/ who
|
=/ who
|
||||||
=/ pers ~(tap by piers)
|
=/ pers ~(tap by ships.piers)
|
||||||
|- ^- (unit ship)
|
|- ^- (unit ship)
|
||||||
?~ pers
|
?~ pers
|
||||||
~
|
~
|
||||||
@ -331,7 +381,7 @@
|
|||||||
::
|
::
|
||||||
++ poke-pill
|
++ poke-pill
|
||||||
|= p=pill
|
|= p=pill
|
||||||
^- (quip card:agent:gall state)
|
^- (quip card:agent:gall _state)
|
||||||
=. this apex-aqua =< abet-aqua
|
=. this apex-aqua =< abet-aqua
|
||||||
=. pil p
|
=. pil p
|
||||||
~& lent=(met 3 (jam boot-ova.pil))
|
~& lent=(met 3 (jam boot-ova.pil))
|
||||||
@ -362,7 +412,7 @@
|
|||||||
::
|
::
|
||||||
++ poke-noun
|
++ poke-noun
|
||||||
|= val=*
|
|= val=*
|
||||||
^- (quip card:agent:gall state)
|
^- (quip card:agent:gall _state)
|
||||||
=. this apex-aqua =< abet-aqua
|
=. this apex-aqua =< abet-aqua
|
||||||
^+ this
|
^+ this
|
||||||
:: Could potentially factor out the three lines of turn-ships
|
:: Could potentially factor out the three lines of turn-ships
|
||||||
@ -391,7 +441,7 @@
|
|||||||
=/ txt .^(@ %cx (weld pax /hoon))
|
=/ txt .^(@ %cx (weld pax /hoon))
|
||||||
[/vane/[vane] [%veer v pax txt]]
|
[/vane/[vane] [%veer v pax txt]]
|
||||||
=> .(this ^+(this this))
|
=> .(this ^+(this this))
|
||||||
=^ ms all-state (poke-pill pil)
|
=^ ms state (poke-pill pil)
|
||||||
(emit-cards ms)
|
(emit-cards ms)
|
||||||
::
|
::
|
||||||
[%swap-files ~]
|
[%swap-files ~]
|
||||||
@ -402,7 +452,7 @@
|
|||||||
%- unix-event
|
%- unix-event
|
||||||
%- %*(. file-ovum:pill-lib directories slim-dirs)
|
%- %*(. file-ovum:pill-lib directories slim-dirs)
|
||||||
/(scot %p our.hid)/work/(scot %da now.hid)
|
/(scot %p our.hid)/work/(scot %da now.hid)
|
||||||
=^ ms all-state (poke-pill pil)
|
=^ ms state (poke-pill pil)
|
||||||
(emit-cards ms)
|
(emit-cards ms)
|
||||||
::
|
::
|
||||||
[%wish hers=* p=@t]
|
[%wish hers=* p=@t]
|
||||||
@ -412,12 +462,14 @@
|
|||||||
(wish:(pe who) p.val)
|
(wish:(pe who) p.val)
|
||||||
::
|
::
|
||||||
[%unpause-events hers=*]
|
[%unpause-events hers=*]
|
||||||
|
=. this start-azimuth-timer
|
||||||
%+ turn-ships ((list ship) hers.val)
|
%+ turn-ships ((list ship) hers.val)
|
||||||
|= [who=ship thus=_this]
|
|= [who=ship thus=_this]
|
||||||
=. this thus
|
=. this thus
|
||||||
start-processing-events:(pe who)
|
start-processing-events:(pe who)
|
||||||
::
|
::
|
||||||
[%pause-events hers=*]
|
[%pause-events hers=*]
|
||||||
|
=. this stop-azimuth-timer
|
||||||
%+ turn-ships ((list ship) hers.val)
|
%+ turn-ships ((list ship) hers.val)
|
||||||
|= [who=ship thus=_this]
|
|= [who=ship thus=_this]
|
||||||
=. this thus
|
=. this thus
|
||||||
@ -428,17 +480,47 @@
|
|||||||
this
|
this
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
:: Make changes to azimuth state for the current fleet
|
||||||
|
::
|
||||||
|
++ poke-azimuth-action
|
||||||
|
|= act=azimuth-action
|
||||||
|
^- (quip card:agent:gall _state)
|
||||||
|
=. this apex-aqua =< abet-aqua
|
||||||
|
^+ this
|
||||||
|
?- -.act
|
||||||
|
::
|
||||||
|
%init-azimuth
|
||||||
|
=. azi.piers *az-state
|
||||||
|
start-azimuth-timer
|
||||||
|
::
|
||||||
|
%spawn
|
||||||
|
=. state (spawn who.act)
|
||||||
|
this
|
||||||
|
::
|
||||||
|
%breach
|
||||||
|
:: should we remove the pier from state here?
|
||||||
|
=. state (breach who.act)
|
||||||
|
this
|
||||||
|
::
|
||||||
|
==
|
||||||
|
::
|
||||||
:: Apply a list of events tagged by ship
|
:: Apply a list of events tagged by ship
|
||||||
::
|
::
|
||||||
++ poke-aqua-events
|
++ poke-aqua-events
|
||||||
|= events=(list aqua-event)
|
|= events=(list aqua-event)
|
||||||
^- (quip card:agent:gall state)
|
^- (quip card:agent:gall _state)
|
||||||
=. this apex-aqua =< abet-aqua
|
=. this apex-aqua =< abet-aqua
|
||||||
%+ turn-events events
|
%+ turn-events events
|
||||||
|= [ae=aqua-event thus=_this]
|
|= [ae=aqua-event thus=_this]
|
||||||
=. this thus
|
=. this thus
|
||||||
?- -.ae
|
?- -.ae
|
||||||
|
::
|
||||||
%init-ship
|
%init-ship
|
||||||
|
:: XX Note that the keys that get passed in are unused. The keys field
|
||||||
|
:: should be deleted now that aqua is capable of managing azimuth state
|
||||||
|
:: internally. Its been left this way for now until all the ph tests
|
||||||
|
:: can be rewritten
|
||||||
|
=/ keys=dawn-event:able:jael (dawn who.ae)
|
||||||
=. this abet-pe:(publish-effect:(pe who.ae) [/ %sleep ~])
|
=. this abet-pe:(publish-effect:(pe who.ae) [/ %sleep ~])
|
||||||
=/ initted
|
=/ initted
|
||||||
=< plow
|
=< plow
|
||||||
@ -451,7 +533,7 @@
|
|||||||
:^ //term/1 %boot &
|
:^ //term/1 %boot &
|
||||||
?~ keys.ae
|
?~ keys.ae
|
||||||
[%fake who.ae]
|
[%fake who.ae]
|
||||||
[%dawn u.keys.ae]
|
[%dawn keys]
|
||||||
-.userspace-ova.pil
|
-.userspace-ova.pil
|
||||||
[//http-client/0v1n.2m9vh %born ~]
|
[//http-client/0v1n.2m9vh %born ~]
|
||||||
[//http-server/0v1n.2m9vh %born ~]
|
[//http-server/0v1n.2m9vh %born ~]
|
||||||
@ -464,27 +546,36 @@
|
|||||||
stop-processing-events:(pe who.ae)
|
stop-processing-events:(pe who.ae)
|
||||||
::
|
::
|
||||||
%snap-ships
|
%snap-ships
|
||||||
|
=. this
|
||||||
|
%+ turn-ships (turn ~(tap by ships.piers) head)
|
||||||
|
|= [who=ship thus=_this]
|
||||||
|
=. this thus
|
||||||
|
(publish-effect:(pe who) [/ %kill ~])
|
||||||
=. fleet-snaps
|
=. fleet-snaps
|
||||||
%+ ~(put by fleet-snaps) lab.ae
|
%+ ~(put by fleet-snaps) lab.ae
|
||||||
|
:_ azi.piers
|
||||||
%- malt
|
%- malt
|
||||||
%+ murn hers.ae
|
%+ murn hers.ae
|
||||||
|= her=ship
|
|= her=ship
|
||||||
^- (unit (pair ship pier))
|
^- (unit (pair ship pier))
|
||||||
=+ per=(~(get by piers) her)
|
=+ per=(~(get by ships.piers) her)
|
||||||
?~ per
|
?~ per
|
||||||
~
|
~
|
||||||
`[her u.per]
|
`[her u.per]
|
||||||
|
=. this stop-azimuth-timer
|
||||||
|
=. piers *fleet
|
||||||
(pe -.hers.ae)
|
(pe -.hers.ae)
|
||||||
::
|
::
|
||||||
%restore-snap
|
%restore-snap
|
||||||
=. this
|
=. this
|
||||||
%+ turn-ships (turn ~(tap by piers) head)
|
%+ turn-ships (turn ~(tap by ships.piers) head)
|
||||||
|= [who=ship thus=_this]
|
|= [who=ship thus=_this]
|
||||||
=. this thus
|
=. this thus
|
||||||
(publish-effect:(pe who) [/ %sleep ~])
|
(publish-effect:(pe who) [/ %kill ~])
|
||||||
=. piers (~(uni by piers) (~(got by fleet-snaps) lab.ae))
|
=. piers (~(got by fleet-snaps) lab.ae)
|
||||||
|
=. this start-azimuth-timer
|
||||||
=. this
|
=. this
|
||||||
%+ turn-ships (turn ~(tap by piers) head)
|
%+ turn-ships (turn ~(tap by ships.piers) head)
|
||||||
|= [who=ship thus=_this]
|
|= [who=ship thus=_this]
|
||||||
=. this thus
|
=. this thus
|
||||||
(publish-effect:(pe who) [/ %restore ~])
|
(publish-effect:(pe who) [/ %restore ~])
|
||||||
@ -537,18 +628,163 @@
|
|||||||
^- (unit (unit cage))
|
^- (unit (unit cage))
|
||||||
?+ path ~
|
?+ path ~
|
||||||
[%x %fleet-snap @ ~] ``noun+!>((~(has by fleet-snaps) i.t.t.path))
|
[%x %fleet-snap @ ~] ``noun+!>((~(has by fleet-snaps) i.t.t.path))
|
||||||
[%x %ships ~] ``noun+!>((turn ~(tap by piers) head))
|
[%x %fleets ~] ``noun+!>((turn ~(tap by fleet-snaps) head))
|
||||||
|
[%x %ships ~] ``noun+!>((turn ~(tap by ships.piers) head))
|
||||||
[%x %pill ~] ``pill+!>(pil)
|
[%x %pill ~] ``pill+!>(pil)
|
||||||
[%x %i @ @ @ @ @ *]
|
[%x %i @ @ @ @ @ *]
|
||||||
=/ who (slav %p i.t.t.path)
|
=/ who (slav %p i.t.t.path)
|
||||||
=/ pier (~(get by piers) who)
|
=/ pier (~(get by ships.piers) who)
|
||||||
?~ pier
|
?~ pier
|
||||||
~
|
~
|
||||||
:^ ~ ~ %noun !>
|
:^ ~ ~ %noun !>
|
||||||
(peek:(pe who) t.t.t.path)
|
(peek:(pe who) t.t.t.path)
|
||||||
|
[%x %log-info ~]
|
||||||
|
``noun+!>([lives.azi.piers (lent logs.azi.piers) tym.azi.piers])
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
:: Trivial scry for mock
|
:: Trivial scry for mock
|
||||||
::
|
::
|
||||||
++ scry |=([* *] ~)
|
++ scry |=([* *] ~)
|
||||||
|
::
|
||||||
|
++ handle-wake
|
||||||
|
|= wen=@da
|
||||||
|
^- (quip card:agent:gall _state)
|
||||||
|
=. this apex-aqua =< abet-aqua
|
||||||
|
?. =(wen tym.azi.piers)
|
||||||
|
this
|
||||||
|
=. state (spam-logs 10)
|
||||||
|
start-azimuth-timer
|
||||||
|
::
|
||||||
|
++ start-azimuth-timer
|
||||||
|
^+ this
|
||||||
|
=? this !=(tym.azi.piers *@da)
|
||||||
|
stop-azimuth-timer
|
||||||
|
=/ until=@da (add now.hid ~s40)
|
||||||
|
=. tym.azi.piers until
|
||||||
|
%- emit-cards
|
||||||
|
[%pass /wait/(scot %da until) %arvo %b %wait until]~
|
||||||
|
::
|
||||||
|
++ stop-azimuth-timer
|
||||||
|
^+ this
|
||||||
|
=* tym tym.azi.piers
|
||||||
|
?: =(tym *@da)
|
||||||
|
this
|
||||||
|
%- emit-cards
|
||||||
|
[%pass /wait/(scot %da tym) %arvo %b %rest tym]~
|
||||||
|
::
|
||||||
|
++ spam-logs
|
||||||
|
|= n=@
|
||||||
|
^- _state
|
||||||
|
=* loop $
|
||||||
|
?: =(n 0)
|
||||||
|
state
|
||||||
|
=/ new-state=_state
|
||||||
|
?. (~(has by lives.azi.piers) ~fes)
|
||||||
|
(spawn ~fes)
|
||||||
|
(cycle-keys ~fes)
|
||||||
|
=. state new-state
|
||||||
|
loop(n (dec n))
|
||||||
|
::
|
||||||
|
++ spawn
|
||||||
|
|= who=@p
|
||||||
|
^- _state
|
||||||
|
?< (~(has by lives.azi.piers) who)
|
||||||
|
=. lives.azi.piers (~(put by lives.azi.piers) who [1 0])
|
||||||
|
=. logs.azi.piers
|
||||||
|
%+ weld logs.azi.piers
|
||||||
|
:_ ~
|
||||||
|
%- changed-keys:lo:aqua-azimuth
|
||||||
|
:* who
|
||||||
|
(get-public:aqua-azimuth who 1 %crypt)
|
||||||
|
(get-public:aqua-azimuth who 1 %auth)
|
||||||
|
1
|
||||||
|
1
|
||||||
|
==
|
||||||
|
(spam-logs 10)
|
||||||
|
::
|
||||||
|
++ cycle-keys
|
||||||
|
|= who=@p
|
||||||
|
^- _state
|
||||||
|
=/ prev
|
||||||
|
~| no-such-ship+who
|
||||||
|
(~(got by lives.azi.piers) who)
|
||||||
|
=/ lyfe +(lyfe.prev)
|
||||||
|
=. lives.azi.piers (~(put by lives.azi.piers) who [lyfe rut.prev])
|
||||||
|
=. logs.azi.piers
|
||||||
|
%+ weld logs.azi.piers
|
||||||
|
:_ ~
|
||||||
|
%- changed-keys:lo:aqua-azimuth
|
||||||
|
:* who
|
||||||
|
(get-public:aqua-azimuth who lyfe %crypt)
|
||||||
|
(get-public:aqua-azimuth who lyfe %auth)
|
||||||
|
1
|
||||||
|
lyfe
|
||||||
|
==
|
||||||
|
state
|
||||||
|
::
|
||||||
|
++ breach
|
||||||
|
|= who=@p
|
||||||
|
^- _state
|
||||||
|
=. state (cycle-keys who)
|
||||||
|
=/ prev (~(got by lives.azi.piers) who)
|
||||||
|
=/ rut +(rut.prev)
|
||||||
|
=. lives.azi.piers (~(put by lives.azi.piers) who [lyfe.prev rut])
|
||||||
|
=. logs.azi.piers
|
||||||
|
%+ weld logs.azi.piers
|
||||||
|
[(broke-continuity:lo:aqua-azimuth who rut) ~]
|
||||||
|
(spam-logs 10)
|
||||||
|
::
|
||||||
|
++ dawn
|
||||||
|
|= who=ship
|
||||||
|
^- dawn-event:able:jael
|
||||||
|
?> ?=(?(%czar %king %duke) (clan:title who))
|
||||||
|
=/ spon=(list [ship point:azimuth])
|
||||||
|
%- flop
|
||||||
|
|- ^- (list [ship point:azimuth])
|
||||||
|
=/ =ship (^sein:title who)
|
||||||
|
=/ a-point=[^ship point:azimuth]
|
||||||
|
=/ spon-spon [& (^sein:title ship)]
|
||||||
|
=/ life-rift ~|([ship lives.azi.piers] (~(got by lives.azi.piers) ship))
|
||||||
|
=/ =life lyfe.life-rift
|
||||||
|
=/ =rift rut.life-rift
|
||||||
|
=/ =pass
|
||||||
|
%^ pass-from-eth:azimuth
|
||||||
|
(as-octs:mimes:html (get-public:aqua-azimuth ship life %crypt))
|
||||||
|
(as-octs:mimes:html (get-public:aqua-azimuth ship life %auth))
|
||||||
|
1
|
||||||
|
:^ ship
|
||||||
|
*[address address address address]:azimuth
|
||||||
|
`[life=life pass rift spon-spon ~]
|
||||||
|
~
|
||||||
|
?: ?=(%czar (clan:title ship))
|
||||||
|
[a-point]~
|
||||||
|
[a-point $(who ship)]
|
||||||
|
=/ =seed:able:jael
|
||||||
|
=/ life-rift (~(got by lives.azi.piers) who)
|
||||||
|
=/ =life lyfe.life-rift
|
||||||
|
[who life sec:ex:(get-keys:aqua-azimuth who life) ~]
|
||||||
|
:* seed
|
||||||
|
spon
|
||||||
|
get-czars
|
||||||
|
~[~['arvo' 'netw' 'ork']]
|
||||||
|
0
|
||||||
|
`(need (de-purl:html 'http://localhost:8545'))
|
||||||
|
==
|
||||||
|
::
|
||||||
|
:: Should only do galaxies
|
||||||
|
::
|
||||||
|
++ get-czars
|
||||||
|
^- (map ship [rift life pass])
|
||||||
|
%- malt
|
||||||
|
%+ murn
|
||||||
|
~(tap by lives.azi.piers)
|
||||||
|
|= [who=ship lyfe=life rut=rift]
|
||||||
|
?. =(%czar (clan:title who))
|
||||||
|
~
|
||||||
|
%- some
|
||||||
|
:^ who rut lyfe
|
||||||
|
%^ pass-from-eth:azimuth
|
||||||
|
(as-octs:mimes:html (get-public:aqua-azimuth who lyfe %crypt))
|
||||||
|
(as-octs:mimes:html (get-public:aqua-azimuth who lyfe %auth))
|
||||||
|
1
|
||||||
--
|
--
|
||||||
|
@ -178,22 +178,7 @@
|
|||||||
~[(add-pending rid ship.act)]
|
~[(add-pending rid ship.act)]
|
||||||
::
|
::
|
||||||
%delete
|
%delete
|
||||||
=/ rid=resource
|
~
|
||||||
(de-path:resource path.act)
|
|
||||||
=/ group-pokes=(list card)
|
|
||||||
?: =(our.bol entity.rid)
|
|
||||||
~[(group-push-poke %remove rid)]
|
|
||||||
:~ (group-proxy-poke %remove-members rid (sy our.bol ~))
|
|
||||||
(group-pull-poke %remove rid)
|
|
||||||
==
|
|
||||||
;: weld
|
|
||||||
group-pokes
|
|
||||||
:~ (contact-hook-poke [%remove path.act])
|
|
||||||
(group-poke [%remove-group rid ~])
|
|
||||||
(contact-poke [%delete path.act])
|
|
||||||
==
|
|
||||||
(delete-metadata path.act)
|
|
||||||
==
|
|
||||||
::
|
::
|
||||||
%remove
|
%remove
|
||||||
=/ rid=resource
|
=/ rid=resource
|
||||||
@ -340,13 +325,6 @@
|
|||||||
(metadata-hook-poke [%add-owned path])
|
(metadata-hook-poke [%add-owned path])
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ delete-metadata
|
|
||||||
|= =path
|
|
||||||
^- (list card)
|
|
||||||
:~ (metadata-poke [%remove path [%contacts path]])
|
|
||||||
(metadata-hook-poke [%remove path])
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ all-scry
|
++ all-scry
|
||||||
^- rolodex
|
^- rolodex
|
||||||
.^(rolodex %gx /(scot %p our.bol)/contact-store/(scot %da now.bol)/all/noun)
|
.^(rolodex %gx /(scot %p our.bol)/contact-store/(scot %da now.bol)/all/noun)
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
/- glob
|
/- glob
|
||||||
/+ default-agent, verb, dbug
|
/+ default-agent, verb, dbug
|
||||||
|%
|
|%
|
||||||
++ hash 0v4.fpa4r.s6dtc.h8tps.62jv0.qn0fj
|
++ hash 0v5.0umdn.af5hq.bp84b.66eao.q0b98
|
||||||
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
|
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
|
||||||
+$ all-states
|
+$ all-states
|
||||||
$% state-0
|
$% state-0
|
||||||
|
@ -748,12 +748,14 @@
|
|||||||
[%x %peek-update-log @ @ ~]
|
[%x %peek-update-log @ @ ~]
|
||||||
=/ =ship (slav %p i.t.t.path)
|
=/ =ship (slav %p i.t.t.path)
|
||||||
=/ =term i.t.t.t.path
|
=/ =term i.t.t.t.path
|
||||||
=/ update-log=(unit update-log:store) (~(get by update-logs) [ship term])
|
=/ m-update-log=(unit update-log:store) (~(get by update-logs) [ship term])
|
||||||
?~ update-log [~ ~]
|
:- ~ :- ~ :- %noun
|
||||||
=/ result=(unit [time update:store])
|
!> ^- (unit time)
|
||||||
(peek:orm-log:store u.update-log)
|
%+ biff m-update-log
|
||||||
?~ result [~ ~]
|
|= =update-log:store
|
||||||
``noun+!>([~ -.u.result])
|
=/ result=(unit [=time =update:store])
|
||||||
|
(peek:orm-log:store update-log)
|
||||||
|
(bind result |=([=time update:store] time))
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ get-node
|
++ get-node
|
||||||
|
@ -166,11 +166,16 @@
|
|||||||
?~ existing-notif
|
?~ existing-notif
|
||||||
notification
|
notification
|
||||||
(merge-notification:ha u.existing-notif notification)
|
(merge-notification:ha u.existing-notif notification)
|
||||||
|
=/ new-read=?
|
||||||
|
?~ existing-notif
|
||||||
|
%.y
|
||||||
|
read.u.existing-notif
|
||||||
|
=. read.new %.n
|
||||||
=/ new-timebox=timebox:store
|
=/ new-timebox=timebox:store
|
||||||
(~(put by timebox) index new)
|
(~(put by timebox) index new)
|
||||||
:- (give:ha [/updates]~ %added last-seen index new)
|
:- (give:ha [/updates]~ %added last-seen index new)
|
||||||
%_ state
|
%_ state
|
||||||
+ ?~(existing-notif (upd-unreads:ha index last-seen %.n) +.state)
|
+ ?.(new-read +.state (upd-unreads:ha index last-seen %.n))
|
||||||
notifications (put:orm notifications last-seen new-timebox)
|
notifications (put:orm notifications last-seen new-timebox)
|
||||||
==
|
==
|
||||||
++ read-index
|
++ read-index
|
||||||
|
@ -24,6 +24,6 @@
|
|||||||
<div id="portal-root"></div>
|
<div id="portal-root"></div>
|
||||||
<script src="/~landscape/js/channel.js"></script>
|
<script src="/~landscape/js/channel.js"></script>
|
||||||
<script src="/~landscape/js/session.js"></script>
|
<script src="/~landscape/js/session.js"></script>
|
||||||
<script src="/~landscape/js/bundle/index.c099f574cf3ccea90625.js"></script>
|
<script src="/~landscape/js/bundle/index.2e81ebfd000b4945c3fe.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -15,6 +15,7 @@ class Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
this.debounceInterval = 500;
|
||||||
// unique identifier: current time and random number
|
// unique identifier: current time and random number
|
||||||
//
|
//
|
||||||
this.uid =
|
this.uid =
|
||||||
@ -55,6 +56,20 @@ class Channel {
|
|||||||
// disconnect function may be called exactly once.
|
// disconnect function may be called exactly once.
|
||||||
//
|
//
|
||||||
this.outstandingSubscriptions = new Map();
|
this.outstandingSubscriptions = new Map();
|
||||||
|
|
||||||
|
this.outstandingJSON = [];
|
||||||
|
|
||||||
|
this.debounceTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetDebounceTimer() {
|
||||||
|
if(this.debounceTimer) {
|
||||||
|
clearTimeout(this.debounceTimer);
|
||||||
|
this.debounceTimer = null;
|
||||||
|
}
|
||||||
|
this.debounceTimer = setTimeout(() => {
|
||||||
|
this.sendJSONToChannel();
|
||||||
|
}, this.debounceInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnChannelError(onError = (err) => {}) {
|
setOnChannelError(onError = (err) => {}) {
|
||||||
@ -71,6 +86,12 @@ class Channel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearQueue() {
|
||||||
|
clearTimeout(this.debounceTimer);
|
||||||
|
this.debounceTimer = null;
|
||||||
|
this.sendJSONToChannel();
|
||||||
|
}
|
||||||
|
|
||||||
// sends a poke to an app on an urbit ship
|
// sends a poke to an app on an urbit ship
|
||||||
//
|
//
|
||||||
poke(ship, app, mark, json, successFunc, failureFunc) {
|
poke(ship, app, mark, json, successFunc, failureFunc) {
|
||||||
@ -83,14 +104,16 @@ class Channel {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.sendJSONToChannel({
|
const j = {
|
||||||
id,
|
id,
|
||||||
action: "poke",
|
action: "poke",
|
||||||
ship,
|
ship,
|
||||||
app,
|
app,
|
||||||
mark,
|
mark,
|
||||||
json
|
json
|
||||||
});
|
};
|
||||||
|
|
||||||
|
this.sendJSONToChannel(j);
|
||||||
}
|
}
|
||||||
|
|
||||||
// subscribes to a path on an specific app and ship.
|
// subscribes to a path on an specific app and ship.
|
||||||
@ -104,7 +127,8 @@ class Channel {
|
|||||||
connectionErrFunc = () => {},
|
connectionErrFunc = () => {},
|
||||||
eventFunc = () => {},
|
eventFunc = () => {},
|
||||||
quitFunc = () => {},
|
quitFunc = () => {},
|
||||||
subAckFunc = () => {}) {
|
subAckFunc = () => {},
|
||||||
|
) {
|
||||||
let id = this.nextId();
|
let id = this.nextId();
|
||||||
this.outstandingSubscriptions.set(
|
this.outstandingSubscriptions.set(
|
||||||
id,
|
id,
|
||||||
@ -116,14 +140,17 @@ class Channel {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.sendJSONToChannel({
|
const json = {
|
||||||
id,
|
id,
|
||||||
action: "subscribe",
|
action: "subscribe",
|
||||||
ship,
|
ship,
|
||||||
app,
|
app,
|
||||||
path
|
path
|
||||||
});
|
}
|
||||||
|
|
||||||
|
this.resetDebounceTimer();
|
||||||
|
|
||||||
|
this.outstandingJSON.push(json);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,6 +158,7 @@ class Channel {
|
|||||||
//
|
//
|
||||||
delete() {
|
delete() {
|
||||||
let id = this.nextId();
|
let id = this.nextId();
|
||||||
|
clearInterval(this.ackTimer);
|
||||||
navigator.sendBeacon(this.channelURL(), JSON.stringify([{
|
navigator.sendBeacon(this.channelURL(), JSON.stringify([{
|
||||||
id,
|
id,
|
||||||
action: "delete"
|
action: "delete"
|
||||||
@ -154,12 +182,18 @@ class Channel {
|
|||||||
// sends a JSON command command to the server.
|
// sends a JSON command command to the server.
|
||||||
//
|
//
|
||||||
sendJSONToChannel(j) {
|
sendJSONToChannel(j) {
|
||||||
|
if(!j && this.outstandingJSON.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("PUT", this.channelURL());
|
req.open("PUT", this.channelURL());
|
||||||
req.setRequestHeader("Content-Type", "application/json");
|
req.setRequestHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
if (this.lastEventId == this.lastAcknowledgedEventId) {
|
if (this.lastEventId == this.lastAcknowledgedEventId) {
|
||||||
let x = JSON.stringify([j]);
|
if(j) {
|
||||||
|
this.outstandingJSON.push(j);
|
||||||
|
}
|
||||||
|
let x = JSON.stringify(this.outstandingJSON);
|
||||||
req.send(x);
|
req.send(x);
|
||||||
} else {
|
} else {
|
||||||
// we add an acknowledgment to clear the server side queue
|
// we add an acknowledgment to clear the server side queue
|
||||||
@ -167,7 +201,10 @@ class Channel {
|
|||||||
// The server side puts messages it sends us in a queue until we
|
// The server side puts messages it sends us in a queue until we
|
||||||
// acknowledge that we received it.
|
// acknowledge that we received it.
|
||||||
//
|
//
|
||||||
let payload = [{action: "ack", "event-id": parseInt(this.lastEventId)}];
|
let payload = [
|
||||||
|
...this.outstandingJSON,
|
||||||
|
{action: "ack", "event-id": parseInt(this.lastEventId)}
|
||||||
|
];
|
||||||
if(j) {
|
if(j) {
|
||||||
payload.push(j)
|
payload.push(j)
|
||||||
}
|
}
|
||||||
@ -176,6 +213,7 @@ class Channel {
|
|||||||
|
|
||||||
this.lastEventId = this.lastAcknowledgedEventId;
|
this.lastEventId = this.lastAcknowledgedEventId;
|
||||||
}
|
}
|
||||||
|
this.outstandingJSON = [];
|
||||||
|
|
||||||
this.connectIfDisconnected();
|
this.connectIfDisconnected();
|
||||||
}
|
}
|
||||||
@ -217,8 +255,11 @@ class Channel {
|
|||||||
funcs["subAck"](obj);
|
funcs["subAck"](obj);
|
||||||
}
|
}
|
||||||
} else if (obj.response == "diff") {
|
} else if (obj.response == "diff") {
|
||||||
// ack subscription
|
// ensure we ack before channel clogs
|
||||||
this.sendJSONToChannel();
|
if((this.lastEventId - this.lastAcknowledgedEventId) > 30) {
|
||||||
|
this.clearQueue();
|
||||||
|
}
|
||||||
|
|
||||||
let funcs = subFuncs;
|
let funcs = subFuncs;
|
||||||
funcs["event"](obj.json);
|
funcs["event"](obj.json);
|
||||||
} else if (obj.response == "quit") {
|
} else if (obj.response == "quit") {
|
||||||
|
@ -9,11 +9,12 @@
|
|||||||
|%
|
|%
|
||||||
+$ card card:agent:gall
|
+$ card card:agent:gall
|
||||||
+$ versioned-state
|
+$ versioned-state
|
||||||
$% state-0
|
$% [%0 observers=(map serial observer:sur)]
|
||||||
|
[%1 observers=(map serial observer:sur)]
|
||||||
|
[%2 observers=(map serial observer:sur)]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ serial @uv
|
+$ serial @uv
|
||||||
+$ state-0 [%0 observers=(map serial observer:sur)]
|
|
||||||
++ got-by-val
|
++ got-by-val
|
||||||
|= [a=(map serial observer:sur) b=observer:sur]
|
|= [a=(map serial observer:sur) b=observer:sur]
|
||||||
^- serial
|
^- serial
|
||||||
@ -24,7 +25,7 @@
|
|||||||
--
|
--
|
||||||
::
|
::
|
||||||
%- agent:dbug
|
%- agent:dbug
|
||||||
=| state-0
|
=| [%2 observers=(map serial observer:sur)]
|
||||||
=* state -
|
=* state -
|
||||||
::
|
::
|
||||||
^- agent:gall
|
^- agent:gall
|
||||||
@ -35,14 +36,16 @@
|
|||||||
++ on-init
|
++ on-init
|
||||||
|^ ^- (quip card _this)
|
|^ ^- (quip card _this)
|
||||||
:_ this
|
:_ this
|
||||||
:_ ~
|
:~ (act [%watch %invite-store /invitatory/graph %invite-accepted-graph])
|
||||||
(act /inv-gra [%watch %invite-store /invitatory/graph %invite-accepted-graph])
|
(act [%watch %group-store /groups %group-on-leave])
|
||||||
|
(act [%watch %group-store /groups %group-on-remove-member])
|
||||||
|
==
|
||||||
::
|
::
|
||||||
++ act
|
++ act
|
||||||
|= [=wire =action:sur]
|
|= =action:sur
|
||||||
^- card
|
^- card
|
||||||
:* %pass
|
:* %pass
|
||||||
wire
|
/poke
|
||||||
%agent
|
%agent
|
||||||
[our.bowl %observe-hook]
|
[our.bowl %observe-hook]
|
||||||
%poke
|
%poke
|
||||||
@ -56,7 +59,35 @@
|
|||||||
++ on-load
|
++ on-load
|
||||||
|= old-vase=vase
|
|= old-vase=vase
|
||||||
^- (quip card _this)
|
^- (quip card _this)
|
||||||
`this(state !<(state-0 old-vase))
|
|^
|
||||||
|
=/ old-state !<(versioned-state old-vase)
|
||||||
|
=| cards=(list card)
|
||||||
|
|-
|
||||||
|
?: ?=(%2 -.old-state)
|
||||||
|
[cards this(state old-state)]
|
||||||
|
?: ?=(%1 -.old-state)
|
||||||
|
=. cards
|
||||||
|
:_ cards
|
||||||
|
(act [%watch %group-store /groups %group-on-leave])
|
||||||
|
$(-.old-state %2)
|
||||||
|
=. cards
|
||||||
|
:_ cards
|
||||||
|
(act [%watch %group-store /groups %group-on-remove-member])
|
||||||
|
$(-.old-state %1)
|
||||||
|
::
|
||||||
|
++ act
|
||||||
|
|= =action:sur
|
||||||
|
^- card
|
||||||
|
:* %pass
|
||||||
|
/poke
|
||||||
|
%agent
|
||||||
|
[our.bowl %observe-hook]
|
||||||
|
%poke
|
||||||
|
%observe-action
|
||||||
|
!> ^- action:sur
|
||||||
|
action
|
||||||
|
==
|
||||||
|
--
|
||||||
::
|
::
|
||||||
++ on-poke
|
++ on-poke
|
||||||
|= [=mark =vase]
|
|= [=mark =vase]
|
||||||
|
@ -83,7 +83,7 @@
|
|||||||
?. ?=(%s -.jon)
|
?. ?=(%s -.jon)
|
||||||
[~ state]
|
[~ state]
|
||||||
=/ str=@t +.jon
|
=/ str=@t +.jon
|
||||||
=/ req=request:http (request-darksky str)
|
=/ req=request:http (request-wttr str)
|
||||||
=/ out *outbound-config:iris
|
=/ out *outbound-config:iris
|
||||||
=/ lismov=(list card)
|
=/ lismov=(list card)
|
||||||
[%pass /[(scot %da now.bol)] %arvo %i %request req out]~
|
[%pass /[(scot %da now.bol)] %arvo %i %request req out]~
|
||||||
@ -102,11 +102,11 @@
|
|||||||
^- (list card)
|
^- (list card)
|
||||||
[%give %fact ~[/all] %json !>((frond:enjs:format %location jon))]~
|
[%give %fact ~[/all] %json !>((frond:enjs:format %location jon))]~
|
||||||
::
|
::
|
||||||
++ request-darksky
|
++ request-wttr
|
||||||
|= location=@t
|
|= location=@t
|
||||||
^- request:http
|
^- request:http
|
||||||
=/ base 'https://api.darksky.net/forecast/634639c10670c7376dc66b6692fe57ca/'
|
=/ base 'https://wttr.in/'
|
||||||
=/ url=@t (cat 3 (cat 3 base location) '?units=auto')
|
=/ url=@t (cat 3 (cat 3 base location) '?format=j1')
|
||||||
=/ hed [['Accept' 'application/json']]~
|
=/ hed [['Accept' 'application/json']]~
|
||||||
[%'GET' url hed *(unit octs)]
|
[%'GET' url hed *(unit octs)]
|
||||||
::
|
::
|
||||||
@ -133,8 +133,9 @@
|
|||||||
=/ jon=json
|
=/ jon=json
|
||||||
%+ frond:enjs:format %weather
|
%+ frond:enjs:format %weather
|
||||||
%- pairs:enjs:format
|
%- pairs:enjs:format
|
||||||
:~ [%currently (~(got by p.u.ujon) 'currently')]
|
:~ [%current-condition (~(got by p.u.ujon) 'current_condition')]
|
||||||
[%daily (~(got by p.u.ujon) 'daily')]
|
[%weather (~(got by p.u.ujon) 'weather')]
|
||||||
|
[%nearest-area (~(got by p.u.ujon) 'nearest_area')]
|
||||||
==
|
==
|
||||||
:- [%give %fact ~[/all] %json !>(jon)]~
|
:- [%give %fact ~[/all] %json !>(jon)]~
|
||||||
%= state
|
%= state
|
||||||
@ -146,7 +147,7 @@
|
|||||||
|= [wir=wire err=(unit tang)]
|
|= [wir=wire err=(unit tang)]
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
?~ err
|
?~ err
|
||||||
=/ req=request:http (request-darksky location)
|
=/ req=request:http (request-wttr location)
|
||||||
=/ out *outbound-config:iris
|
=/ out *outbound-config:iris
|
||||||
:_ state(timer `(add now.bol ~h3))
|
:_ state(timer `(add now.bol ~h3))
|
||||||
:~ [%pass /[(scot %da now.bol)] %arvo %i %request req out]
|
:~ [%pass /[(scot %da now.bol)] %arvo %i %request req out]
|
||||||
|
4
pkg/arvo/gen/aqua/breach.hoon
Normal file
4
pkg/arvo/gen/aqua/breach.hoon
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
:- %say
|
||||||
|
|= [* [her=ship ~] ~]
|
||||||
|
:- %azimuth-action
|
||||||
|
[%breach her]
|
4
pkg/arvo/gen/aqua/init-azimuth.hoon
Normal file
4
pkg/arvo/gen/aqua/init-azimuth.hoon
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
:- %say
|
||||||
|
|= [* ~ ~]
|
||||||
|
:- %azimuth-action
|
||||||
|
[%init-azimuth ~]
|
@ -3,4 +3,4 @@
|
|||||||
:- %say
|
:- %say
|
||||||
|= [* [her=ship ~] ~]
|
|= [* [her=ship ~] ~]
|
||||||
:- %aqua-events
|
:- %aqua-events
|
||||||
[%init-ship her ~]~
|
[%init-ship her `*dawn-event:able:jael]~
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/- aquarium
|
/- aquarium
|
||||||
=, aquarium
|
=, aquarium
|
||||||
:- %say
|
:- %say
|
||||||
|= [* [label=@ta] ~]
|
|= [* [label=@ta ~] ~]
|
||||||
:- %aqua-events
|
:- %aqua-events
|
||||||
[%snap-ships label]~
|
[%restore-snap label]~
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/- aquarium
|
/- aquarium
|
||||||
=, aquarium
|
=, aquarium
|
||||||
:- %say
|
:- %say
|
||||||
|= [[now=@da eny=@uvJ bec=beak] [label=@ta] ships=(list ship)]
|
|= [[now=@da eny=@uvJ bec=beak] [label=@ta ships=(list ship)] ~]
|
||||||
:- %aqua-events
|
:- %aqua-events
|
||||||
=? ships ?=(~ ships)
|
=? ships ?=(~ ships)
|
||||||
.^((list ship) %gx /(scot %p p.bec)/aqua/(scot %da now)/ships/noun)
|
.^((list ship) %gx /(scot %p p.bec)/aqua/(scot %da now)/ships/noun)
|
||||||
|
4
pkg/arvo/gen/aqua/spawn.hoon
Normal file
4
pkg/arvo/gen/aqua/spawn.hoon
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
:- %say
|
||||||
|
|= [* [her=ship ~] ~]
|
||||||
|
:- %azimuth-action
|
||||||
|
[%spawn her]
|
5
pkg/arvo/gen/sponsor.hoon
Normal file
5
pkg/arvo/gen/sponsor.hoon
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
:: Print the sponsor of this ship
|
||||||
|
:- %say
|
||||||
|
|= [[now=time @ our=ship ^] * ~]
|
||||||
|
:- %ship
|
||||||
|
(sein:title our now our)
|
241
pkg/arvo/lib/aqua-azimuth.hoon
Normal file
241
pkg/arvo/lib/aqua-azimuth.hoon
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
/- *aquarium
|
||||||
|
::
|
||||||
|
|%
|
||||||
|
::
|
||||||
|
++ extract-request
|
||||||
|
|= [uf=unix-effect dest=@t]
|
||||||
|
^- (unit [num=@ud =request:http])
|
||||||
|
?. ?=(%request -.q.uf) ~
|
||||||
|
?. =(dest url.request.q.uf) ~
|
||||||
|
`[id.q.uf request.q.uf]
|
||||||
|
::
|
||||||
|
++ router
|
||||||
|
|= [our=ship her=ship uf=unix-effect azi=az-state]
|
||||||
|
^- (unit card:agent:gall)
|
||||||
|
=, enjs:format
|
||||||
|
=/ ask (extract-request uf 'http://localhost:8545/')
|
||||||
|
?~ ask
|
||||||
|
~
|
||||||
|
?~ body.request.u.ask
|
||||||
|
~
|
||||||
|
=/ req q.u.body.request.u.ask
|
||||||
|
|^ ^- (unit card:agent:gall)
|
||||||
|
=/ method (get-method req)
|
||||||
|
?: =(method 'eth_blockNumber')
|
||||||
|
:- ~
|
||||||
|
%+ answer-request req
|
||||||
|
s+(crip (num-to-hex:ethereum latest-block))
|
||||||
|
?: =(method 'eth_getBlockByNumber')
|
||||||
|
:- ~
|
||||||
|
%+ answer-request req
|
||||||
|
:- %o
|
||||||
|
=/ number (hex-to-num:ethereum (get-first-param req))
|
||||||
|
=/ hash (number-to-hash number)
|
||||||
|
=/ parent-hash (number-to-hash ?~(number number (dec number)))
|
||||||
|
%- malt
|
||||||
|
^- (list (pair term json))
|
||||||
|
:~ hash+s+(crip (prefix-hex:ethereum (render-hex-bytes:ethereum 32 hash)))
|
||||||
|
number+s+(crip (num-to-hex:ethereum number))
|
||||||
|
'parentHash'^s+(crip (num-to-hex:ethereum parent-hash))
|
||||||
|
==
|
||||||
|
?: =(method 'eth_getLogs')
|
||||||
|
:- ~
|
||||||
|
%+ answer-request req
|
||||||
|
?^ (get-param-obj-maybe req 'blockHash')
|
||||||
|
%- logs-by-hash
|
||||||
|
(get-param-obj req 'blockHash')
|
||||||
|
%+ logs-by-range
|
||||||
|
(get-param-obj req 'fromBlock')
|
||||||
|
(get-param-obj req 'toBlock')
|
||||||
|
~& [%ph-azimuth-miss req]
|
||||||
|
~
|
||||||
|
::
|
||||||
|
++ latest-block
|
||||||
|
(add launch:contracts:azimuth (dec (lent logs.azi)))
|
||||||
|
::
|
||||||
|
++ get-single-req
|
||||||
|
|= req=@t
|
||||||
|
=/ batch
|
||||||
|
((ar:dejs:format same) (need (de-json:html req)))
|
||||||
|
?> ?=([* ~] batch)
|
||||||
|
i.batch
|
||||||
|
::
|
||||||
|
++ get-id
|
||||||
|
|= req=@t
|
||||||
|
=, dejs:format
|
||||||
|
%. (get-single-req req)
|
||||||
|
(ot id+so ~)
|
||||||
|
::
|
||||||
|
++ get-method
|
||||||
|
|= req=@t
|
||||||
|
=, dejs:format
|
||||||
|
~| req=req
|
||||||
|
%. (get-single-req req)
|
||||||
|
(ot method+so ~)
|
||||||
|
::
|
||||||
|
++ get-param-obj
|
||||||
|
|= [req=@t param=@t]
|
||||||
|
=, dejs:format
|
||||||
|
%- hex-to-num:ethereum
|
||||||
|
=/ array
|
||||||
|
%. (get-single-req req)
|
||||||
|
(ot params+(ar (ot param^so ~)) ~)
|
||||||
|
?> ?=([* ~] array)
|
||||||
|
i.array
|
||||||
|
::
|
||||||
|
++ get-param-obj-maybe
|
||||||
|
|= [req=@t param=@t]
|
||||||
|
^- (unit @ud)
|
||||||
|
=, dejs-soft:format
|
||||||
|
=/ array
|
||||||
|
%. (get-single-req req)
|
||||||
|
(ot params+(ar (ot param^so ~)) ~)
|
||||||
|
?~ array
|
||||||
|
~
|
||||||
|
:- ~
|
||||||
|
?> ?=([* ~] u.array)
|
||||||
|
%- hex-to-num:ethereum
|
||||||
|
i.u.array
|
||||||
|
::
|
||||||
|
++ get-first-param
|
||||||
|
|= req=@t
|
||||||
|
=, dejs:format
|
||||||
|
=/ id
|
||||||
|
%. (get-single-req req)
|
||||||
|
(ot params+(at so bo ~) ~)
|
||||||
|
-.id
|
||||||
|
::
|
||||||
|
++ answer-request
|
||||||
|
|= [req=@t result=json]
|
||||||
|
^- card:agent:gall
|
||||||
|
=/ resp
|
||||||
|
%- crip
|
||||||
|
%- en-json:html
|
||||||
|
:- %a :_ ~
|
||||||
|
%- pairs
|
||||||
|
:~ id+s+(get-id req)
|
||||||
|
jsonrpc+s+'2.0'
|
||||||
|
result+result
|
||||||
|
==
|
||||||
|
=/ events=(list aqua-event)
|
||||||
|
:_ ~
|
||||||
|
:* %event
|
||||||
|
her
|
||||||
|
//http-client/0v1n.2m9vh
|
||||||
|
%receive
|
||||||
|
num.u.ask
|
||||||
|
[%start [200 ~] `(as-octs:mimes:html resp) &]
|
||||||
|
==
|
||||||
|
:* %pass /aqua-events
|
||||||
|
%agent [our %aqua]
|
||||||
|
%poke %aqua-events
|
||||||
|
!>(events)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ number-to-hash
|
||||||
|
|= =number:block:able:jael
|
||||||
|
^- @
|
||||||
|
?: (lth number launch:contracts:azimuth)
|
||||||
|
(cat 3 0x5364 (sub launch:contracts:azimuth number))
|
||||||
|
(cat 3 0x5363 (sub number launch:contracts:azimuth))
|
||||||
|
::
|
||||||
|
++ hash-to-number
|
||||||
|
|= =hash:block:able:jael
|
||||||
|
(add launch:contracts:azimuth (div hash 0x1.0000))
|
||||||
|
::
|
||||||
|
++ logs-by-range
|
||||||
|
|= [from-block=@ud to-block=@ud]
|
||||||
|
%+ logs-to-json (max launch:contracts:azimuth from-block)
|
||||||
|
?: (lth to-block launch:contracts:azimuth)
|
||||||
|
~
|
||||||
|
%+ swag
|
||||||
|
?: (lth from-block launch:contracts:azimuth)
|
||||||
|
[0 +((sub to-block launch:contracts:azimuth))]
|
||||||
|
:- (sub from-block launch:contracts:azimuth)
|
||||||
|
+((sub to-block from-block))
|
||||||
|
logs.azi
|
||||||
|
::
|
||||||
|
++ logs-by-hash
|
||||||
|
|= =hash:block:able:jael
|
||||||
|
=/ =number:block:able:jael (hash-to-number hash)
|
||||||
|
(logs-by-range number number)
|
||||||
|
::
|
||||||
|
++ logs-to-json
|
||||||
|
|= [count=@ud selected-logs=(list az-log)]
|
||||||
|
^- json
|
||||||
|
:- %a
|
||||||
|
|- ^- (list json)
|
||||||
|
?~ selected-logs
|
||||||
|
~
|
||||||
|
:_ $(selected-logs t.selected-logs, count +(count))
|
||||||
|
%- pairs
|
||||||
|
:~ 'logIndex'^s+'0x0'
|
||||||
|
'transactionIndex'^s+'0x0'
|
||||||
|
:+ 'transactionHash' %s
|
||||||
|
(crip (prefix-hex:ethereum (render-hex-bytes:ethereum 32 `@`0x5362)))
|
||||||
|
::
|
||||||
|
:+ 'blockHash' %s
|
||||||
|
=/ hash (number-to-hash count)
|
||||||
|
(crip (prefix-hex:ethereum (render-hex-bytes:ethereum 32 hash)))
|
||||||
|
::
|
||||||
|
:+ 'blockNumber' %s
|
||||||
|
(crip (num-to-hex:ethereum count))
|
||||||
|
::
|
||||||
|
:+ 'address' %s
|
||||||
|
(crip (address-to-hex:ethereum azimuth:contracts:azimuth))
|
||||||
|
::
|
||||||
|
'type'^s+'mined'
|
||||||
|
::
|
||||||
|
'data'^s+data.i.selected-logs
|
||||||
|
:+ 'topics' %a
|
||||||
|
%+ turn topics.i.selected-logs
|
||||||
|
|= topic=@ux
|
||||||
|
^- json
|
||||||
|
:- %s
|
||||||
|
%- crip
|
||||||
|
%- prefix-hex:ethereum
|
||||||
|
(render-hex-bytes:ethereum 32 `@`topic)
|
||||||
|
==
|
||||||
|
--
|
||||||
|
::
|
||||||
|
++ get-keys
|
||||||
|
|= [who=@p lyfe=life]
|
||||||
|
^- acru:ames
|
||||||
|
%+ pit:nu:crub:crypto 32
|
||||||
|
(can 5 [1 (scot %p who)] [1 (scot %ud lyfe)] ~)
|
||||||
|
::
|
||||||
|
++ get-public
|
||||||
|
|= [who=@p lyfe=life typ=?(%auth %crypt)]
|
||||||
|
=/ bod (rsh 3 1 pub:ex:(get-keys who lyfe))
|
||||||
|
=+ [enc=(rsh 8 1 bod) aut=(end 8 1 bod)]
|
||||||
|
?: =(%auth typ)
|
||||||
|
aut
|
||||||
|
enc
|
||||||
|
::
|
||||||
|
:: Generate logs
|
||||||
|
::
|
||||||
|
++ lo
|
||||||
|
=, azimuth-events:azimuth
|
||||||
|
|%
|
||||||
|
++ broke-continuity
|
||||||
|
|= [who=ship rut=rift]
|
||||||
|
^- az-log
|
||||||
|
:- ~[^broke-continuity who]
|
||||||
|
%- crip
|
||||||
|
%- prefix-hex:ethereum
|
||||||
|
(render-hex-bytes:ethereum 32 `@`rut)
|
||||||
|
::
|
||||||
|
++ changed-keys
|
||||||
|
|= [who=ship enc=@ux aut=@ux crypto=@ud lyfe=life]
|
||||||
|
^- az-log
|
||||||
|
:- ~[^changed-keys who]
|
||||||
|
%- crip
|
||||||
|
%- prefix-hex:ethereum
|
||||||
|
;: welp
|
||||||
|
(render-hex-bytes:ethereum 32 `@`enc)
|
||||||
|
(render-hex-bytes:ethereum 32 `@`aut)
|
||||||
|
(render-hex-bytes:ethereum 32 `@`crypto)
|
||||||
|
(render-hex-bytes:ethereum 32 `@`lyfe)
|
||||||
|
==
|
||||||
|
--
|
||||||
|
--
|
@ -417,6 +417,7 @@
|
|||||||
::
|
::
|
||||||
++ remove-group
|
++ remove-group
|
||||||
|= =json
|
|= =json
|
||||||
|
^- [resource ~]
|
||||||
?> ?=(%o -.json)
|
?> ?=(%o -.json)
|
||||||
=/ rid=resource
|
=/ rid=resource
|
||||||
(dejs:resource (~(got by p.json) 'resource'))
|
(dejs:resource (~(got by p.json) 'resource'))
|
||||||
|
@ -8,6 +8,12 @@
|
|||||||
^- form:m
|
^- form:m
|
||||||
(poke-our %aqua %aqua-events !>(events))
|
(poke-our %aqua %aqua-events !>(events))
|
||||||
::
|
::
|
||||||
|
++ send-azimuth-action
|
||||||
|
|= =azimuth-action
|
||||||
|
=/ m (strand ,~)
|
||||||
|
^- form:m
|
||||||
|
(poke-our %aqua %azimuth-action !>(azimuth-action))
|
||||||
|
::
|
||||||
++ take-unix-effect
|
++ take-unix-effect
|
||||||
=/ m (strand ,[ship unix-effect])
|
=/ m (strand ,[ship unix-effect])
|
||||||
^- form:m
|
^- form:m
|
||||||
@ -34,7 +40,7 @@
|
|||||||
=/ m (strand ,~)
|
=/ m (strand ,~)
|
||||||
^- form:m
|
^- form:m
|
||||||
~& > "starting"
|
~& > "starting"
|
||||||
;< ~ bind:m (start-threads vane-threads)
|
;< tids=(map term tid:spider) bind:m (start-threads vane-threads)
|
||||||
;< ~ bind:m (watch-our /effect %aqua /effect)
|
;< ~ bind:m (watch-our /effect %aqua /effect)
|
||||||
:: Get our very own event with no mistakes in it... yet.
|
:: Get our very own event with no mistakes in it... yet.
|
||||||
::
|
::
|
||||||
@ -64,16 +70,20 @@
|
|||||||
::
|
::
|
||||||
++ start-threads
|
++ start-threads
|
||||||
|= threads=(list term)
|
|= threads=(list term)
|
||||||
=/ m (strand ,~)
|
=/ m (strand ,(map term tid:spider))
|
||||||
^- form:m
|
^- form:m
|
||||||
;< =bowl:spider bind:m get-bowl
|
;< =bowl:spider bind:m get-bowl
|
||||||
|
=| tids=(map term tid:spider)
|
||||||
|- ^- form:m
|
|- ^- form:m
|
||||||
=* loop $
|
=* loop $
|
||||||
?~ threads
|
?~ threads
|
||||||
(pure:m ~)
|
(pure:m tids)
|
||||||
|
=/ tid
|
||||||
|
%+ scot %ta
|
||||||
|
(cat 3 (cat 3 'strand_' i.threads) (scot %uv (sham i.threads eny.bowl)))
|
||||||
=/ poke-vase !>([`tid.bowl ~ i.threads *vase])
|
=/ poke-vase !>([`tid.bowl ~ i.threads *vase])
|
||||||
;< ~ bind:m (poke-our %spider %spider-start poke-vase)
|
;< ~ bind:m (poke-our %spider %spider-start poke-vase)
|
||||||
loop(threads t.threads)
|
loop(threads t.threads, tids (~(put by tids) i.threads tid))
|
||||||
::
|
::
|
||||||
++ stop-threads
|
++ stop-threads
|
||||||
|= threads=(list term)
|
|= threads=(list term)
|
||||||
@ -81,6 +91,29 @@
|
|||||||
^- form:m
|
^- form:m
|
||||||
(pure:m ~)
|
(pure:m ~)
|
||||||
::
|
::
|
||||||
|
:: XX +spawn-aqua and +breach-aqua mean do these actions using aqua's internal
|
||||||
|
:: azimuth management system, eventually these should just replace +spawn
|
||||||
|
:: +breach
|
||||||
|
::
|
||||||
|
++ init-azimuth
|
||||||
|
=/ m (strand ,~)
|
||||||
|
^- form:m
|
||||||
|
(send-azimuth-action %init-azimuth ~)
|
||||||
|
::
|
||||||
|
++ spawn-aqua
|
||||||
|
|= =ship
|
||||||
|
~& > "spawning {<ship>}"
|
||||||
|
=/ m (strand ,~)
|
||||||
|
^- form:m
|
||||||
|
(send-azimuth-action %spawn ship)
|
||||||
|
::
|
||||||
|
++ breach-aqua
|
||||||
|
|= =ship
|
||||||
|
~& > "breaching {<ship>}"
|
||||||
|
=/ m (strand ,~)
|
||||||
|
^- form:m
|
||||||
|
(send-azimuth-action %breach ship)
|
||||||
|
::
|
||||||
++ spawn
|
++ spawn
|
||||||
|= [=tid:spider =ship]
|
|= [=tid:spider =ship]
|
||||||
~& > "spawning {<ship>}"
|
~& > "spawning {<ship>}"
|
||||||
@ -127,6 +160,39 @@
|
|||||||
(pure:m ~)
|
(pure:m ~)
|
||||||
loop
|
loop
|
||||||
::
|
::
|
||||||
|
++ breach-and-hear-aqua
|
||||||
|
|= [who=ship her=ship]
|
||||||
|
=/ m (strand ,~)
|
||||||
|
;< =bowl:spider bind:m get-bowl
|
||||||
|
=/ aqua-pax
|
||||||
|
:- %i
|
||||||
|
/(scot %p her)/j/(scot %p her)/rift/(scot %da now.bowl)/(scot %p who)/noun
|
||||||
|
=/ old-rut ;;((unit @) (scry-aqua:util noun our.bowl now.bowl aqua-pax))
|
||||||
|
=/ new-rut
|
||||||
|
?~ old-rut
|
||||||
|
1
|
||||||
|
+(+.old-rut)
|
||||||
|
;< ~ bind:m (send-azimuth-action %breach who)
|
||||||
|
|- ^- form:m
|
||||||
|
=* loop $
|
||||||
|
;< ~ bind:m (sleep ~s1)
|
||||||
|
;< =bowl:spider bind:m get-bowl
|
||||||
|
=/ aqua-pax
|
||||||
|
:- %i
|
||||||
|
/(scot %p her)/j/(scot %p her)/rift/(scot %da now.bowl)/(scot %p who)/noun
|
||||||
|
=/ rut (scry-aqua:util noun our.bowl now.bowl aqua-pax)
|
||||||
|
?: =([~ new-rut] rut)
|
||||||
|
(pure:m ~)
|
||||||
|
loop
|
||||||
|
::
|
||||||
|
++ init-ship
|
||||||
|
|= =ship
|
||||||
|
=/ m (strand ,~)
|
||||||
|
^- form:m
|
||||||
|
~& > "starting {<ship>}"
|
||||||
|
;< ~ bind:m (send-events (init:util ship `*dawn-event:able:jael))
|
||||||
|
(check-ship-booted ship)
|
||||||
|
::
|
||||||
++ real-ship
|
++ real-ship
|
||||||
|= [=tid:spider =ship]
|
|= [=tid:spider =ship]
|
||||||
~& > "booting real {<ship>}"
|
~& > "booting real {<ship>}"
|
||||||
|
@ -49,13 +49,21 @@
|
|||||||
inner-state=vase
|
inner-state=vase
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
+$ base-state-1
|
||||||
|
$: base-state-0
|
||||||
|
failed-kicks=(map resource ship)
|
||||||
|
==
|
||||||
|
::
|
||||||
+$ state-0 [%0 base-state-0]
|
+$ state-0 [%0 base-state-0]
|
||||||
::
|
::
|
||||||
+$ state-1 [%1 base-state-0]
|
+$ state-1 [%1 base-state-0]
|
||||||
::
|
::
|
||||||
|
+$ state-2 [%2 base-state-1]
|
||||||
|
::
|
||||||
+$ versioned-state
|
+$ versioned-state
|
||||||
$% state-0
|
$% state-0
|
||||||
state-1
|
state-1
|
||||||
|
state-2
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ default
|
++ default
|
||||||
@ -141,7 +149,7 @@
|
|||||||
++ agent
|
++ agent
|
||||||
|* =config
|
|* =config
|
||||||
|= =(pull-hook config)
|
|= =(pull-hook config)
|
||||||
=| state-1
|
=| state-2
|
||||||
=* state -
|
=* state -
|
||||||
^- agent:gall
|
^- agent:gall
|
||||||
=<
|
=<
|
||||||
@ -150,11 +158,13 @@
|
|||||||
og ~(. pull-hook bowl)
|
og ~(. pull-hook bowl)
|
||||||
hc ~(. +> bowl)
|
hc ~(. +> bowl)
|
||||||
def ~(. (default-agent this %|) bowl)
|
def ~(. (default-agent this %|) bowl)
|
||||||
|
::
|
||||||
++ on-init
|
++ on-init
|
||||||
^- [(list card:agent:gall) agent:gall]
|
^- [(list card:agent:gall) agent:gall]
|
||||||
=^ cards pull-hook
|
=^ cards pull-hook
|
||||||
on-init:og
|
on-init:og
|
||||||
[cards this]
|
[cards this]
|
||||||
|
::
|
||||||
++ on-load
|
++ on-load
|
||||||
|= =old=vase
|
|= =old=vase
|
||||||
=/ old
|
=/ old
|
||||||
@ -162,10 +172,16 @@
|
|||||||
=| cards=(list card:agent:gall)
|
=| cards=(list card:agent:gall)
|
||||||
|^
|
|^
|
||||||
?- -.old
|
?- -.old
|
||||||
%1
|
%2
|
||||||
=^ og-cards pull-hook
|
=^ og-cards pull-hook
|
||||||
(on-load:og inner-state.old)
|
(on-load:og inner-state.old)
|
||||||
[(weld cards og-cards) this(state old)]
|
=. state old
|
||||||
|
=^ retry-cards state
|
||||||
|
retry-failed-kicks
|
||||||
|
:_ this
|
||||||
|
:(weld cards og-cards retry-cards)
|
||||||
|
::
|
||||||
|
%1 $(old [%2 +.old ~])
|
||||||
::
|
::
|
||||||
%0
|
%0
|
||||||
%_ $
|
%_ $
|
||||||
@ -175,6 +191,22 @@
|
|||||||
(weld cards (missing-subscriptions tracking.old))
|
(weld cards (missing-subscriptions tracking.old))
|
||||||
==
|
==
|
||||||
==
|
==
|
||||||
|
::
|
||||||
|
++ retry-failed-kicks
|
||||||
|
=| acc-cards=(list card)
|
||||||
|
=/ failures=(list [rid=resource =ship])
|
||||||
|
~(tap by failed-kicks)
|
||||||
|
=. tracking
|
||||||
|
(~(uni by tracking) failed-kicks)
|
||||||
|
=. failed-kicks ~
|
||||||
|
|- ^- (quip card _state)
|
||||||
|
?~ failures
|
||||||
|
[acc-cards state]
|
||||||
|
=, failures
|
||||||
|
=^ crds state
|
||||||
|
(handle-kick:hc i)
|
||||||
|
$(failures t, acc-cards (weld acc-cards crds))
|
||||||
|
::
|
||||||
++ missing-subscriptions
|
++ missing-subscriptions
|
||||||
|= tracking=(map resource ship)
|
|= tracking=(map resource ship)
|
||||||
^- (list card:agent:gall)
|
^- (list card:agent:gall)
|
||||||
@ -232,15 +264,9 @@
|
|||||||
(de-path:resource t.t.t.t.wire)
|
(de-path:resource t.t.t.t.wire)
|
||||||
?+ -.sign (on-agent:def wire sign)
|
?+ -.sign (on-agent:def wire sign)
|
||||||
%kick
|
%kick
|
||||||
=/ pax=(unit path)
|
=^ cards state
|
||||||
(on-pull-kick:og rid)
|
(handle-kick:hc rid src.bowl)
|
||||||
?^ pax
|
[cards this]
|
||||||
:_ this
|
|
||||||
~[(watch-resource:hc rid u.pax)]
|
|
||||||
=. tracking
|
|
||||||
(~(del by tracking) rid)
|
|
||||||
:_ this
|
|
||||||
~[give-update]
|
|
||||||
::
|
::
|
||||||
%watch-ack
|
%watch-ack
|
||||||
?~ p.sign
|
?~ p.sign
|
||||||
@ -287,6 +313,59 @@
|
|||||||
|_ =bowl:gall
|
|_ =bowl:gall
|
||||||
+* og ~(. pull-hook bowl)
|
+* og ~(. pull-hook bowl)
|
||||||
::
|
::
|
||||||
|
++ mule-scry
|
||||||
|
|= [ref=* raw=*]
|
||||||
|
=/ pax=(unit path)
|
||||||
|
((soft path) raw)
|
||||||
|
?~ pax ~
|
||||||
|
?. ?=([@ @ @ @ *] u.pax) ~
|
||||||
|
=/ ship
|
||||||
|
(slaw %p i.t.u.pax)
|
||||||
|
=/ ved
|
||||||
|
(slay i.t.t.t.u.pax)
|
||||||
|
=/ dat
|
||||||
|
?~ ved now.bowl
|
||||||
|
=/ cas=(unit case)
|
||||||
|
((soft case) p.u.ved)
|
||||||
|
?~ cas now.bowl
|
||||||
|
?: ?=(%da -.u.cas)
|
||||||
|
p.u.cas
|
||||||
|
now.bowl
|
||||||
|
:: catch bad gall scries early
|
||||||
|
?: ?& =((end 3 1 i.u.pax) %g)
|
||||||
|
?| !=(`our.bowl ship)
|
||||||
|
!=(dat now.bowl)
|
||||||
|
==
|
||||||
|
==
|
||||||
|
~
|
||||||
|
``.^(* u.pax)
|
||||||
|
++ handle-kick
|
||||||
|
|= [rid=resource =ship]
|
||||||
|
^- (quip card _state)
|
||||||
|
=/ res=toon
|
||||||
|
(mock [|.((on-pull-kick:og rid)) %9 2 %0 1] mule-scry)
|
||||||
|
=/ pax=(unit path)
|
||||||
|
!< (unit path)
|
||||||
|
:- -:!>(*(unit path))
|
||||||
|
?:(?=(%0 -.res) p.res ~)
|
||||||
|
=? failed-kicks !?=(%0 -.res)
|
||||||
|
=/ tang
|
||||||
|
:+ leaf+"failed kick handler, please report"
|
||||||
|
leaf+"{<rid>} in {(trip dap.bowl)}"
|
||||||
|
?: ?=(%2 -.res)
|
||||||
|
p.res
|
||||||
|
?> ?=(%1 -.res)
|
||||||
|
(turn `(list *)`p.res (cork path smyt))
|
||||||
|
%- (slog tang)
|
||||||
|
(~(put by failed-kicks) rid ship)
|
||||||
|
?^ pax
|
||||||
|
:_ state
|
||||||
|
(watch-resource rid u.pax)
|
||||||
|
=. tracking
|
||||||
|
(~(del by tracking) rid)
|
||||||
|
:_ state
|
||||||
|
~[give-update]
|
||||||
|
::
|
||||||
++ poke-hook-action
|
++ poke-hook-action
|
||||||
|= =action
|
|= =action
|
||||||
^- [(list card:agent:gall) _state]
|
^- [(list card:agent:gall) _state]
|
||||||
@ -304,33 +383,35 @@
|
|||||||
=. tracking
|
=. tracking
|
||||||
(~(put by tracking) resource ship)
|
(~(put by tracking) resource ship)
|
||||||
:_ state
|
:_ state
|
||||||
~[(watch-resource resource /)]
|
(watch-resource resource /)
|
||||||
::
|
::
|
||||||
++ remove
|
++ remove
|
||||||
|= =resource
|
|= =resource
|
||||||
:- ~[(leave-resource resource)]
|
:- (leave-resource resource)
|
||||||
state(tracking (~(del by tracking) resource))
|
state(tracking (~(del by tracking) resource))
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
++ leave-resource
|
++ leave-resource
|
||||||
|= rid=resource
|
|= rid=resource
|
||||||
^- card
|
^- (list card)
|
||||||
=/ =ship
|
=/ ship=(unit ship)
|
||||||
(~(got by tracking) rid)
|
(~(get by tracking) rid)
|
||||||
|
?~ ship ~
|
||||||
=/ =wire
|
=/ =wire
|
||||||
(make-wire pull+resource+(en-path:resource rid))
|
(make-wire pull+resource+(en-path:resource rid))
|
||||||
[%pass wire %agent [ship push-hook-name.config] %leave ~]
|
[%pass wire %agent [u.ship push-hook-name.config] %leave ~]~
|
||||||
|
|
||||||
++ watch-resource
|
++ watch-resource
|
||||||
|= [rid=resource pax=path]
|
|= [rid=resource pax=path]
|
||||||
^- card
|
^- (list card)
|
||||||
=/ =ship
|
=/ ship=(unit ship)
|
||||||
(~(got by tracking) rid)
|
(~(get by tracking) rid)
|
||||||
|
?~ ship ~
|
||||||
=/ =path
|
=/ =path
|
||||||
(welp resource+(en-path:resource rid) pax)
|
(welp resource+(en-path:resource rid) pax)
|
||||||
=/ =wire
|
=/ =wire
|
||||||
(make-wire pull+path)
|
(make-wire pull+path)
|
||||||
[%pass wire %agent [ship push-hook-name.config] %watch path]
|
[%pass wire %agent [u.ship push-hook-name.config] %watch path]~
|
||||||
::
|
::
|
||||||
++ make-wire
|
++ make-wire
|
||||||
|= =wire
|
|= =wire
|
||||||
|
@ -219,6 +219,25 @@
|
|||||||
;< ~ bind:m (send-raw-card card)
|
;< ~ bind:m (send-raw-card card)
|
||||||
(take-poke-ack /poke)
|
(take-poke-ack /poke)
|
||||||
::
|
::
|
||||||
|
++ raw-poke
|
||||||
|
|= [=dock =cage]
|
||||||
|
=/ m (strand ,~)
|
||||||
|
^- form:m
|
||||||
|
=/ =card:agent:gall [%pass /poke %agent dock %poke cage]
|
||||||
|
;< ~ bind:m (send-raw-card card)
|
||||||
|
=/ m (strand ,~)
|
||||||
|
^- form:m
|
||||||
|
|= tin=strand-input:strand
|
||||||
|
?+ in.tin `[%skip ~]
|
||||||
|
~
|
||||||
|
`[%wait ~]
|
||||||
|
::
|
||||||
|
[~ %agent * %poke-ack *]
|
||||||
|
?. =(/poke wire.u.in.tin)
|
||||||
|
`[%skip ~]
|
||||||
|
`[%done ~]
|
||||||
|
==
|
||||||
|
::
|
||||||
++ poke-our
|
++ poke-our
|
||||||
|= [=term =cage]
|
|= [=term =cage]
|
||||||
=/ m (strand ,~)
|
=/ m (strand ,~)
|
||||||
@ -654,7 +673,8 @@
|
|||||||
=/ m (strand ,tid:spider)
|
=/ m (strand ,tid:spider)
|
||||||
^- form:m
|
^- form:m
|
||||||
;< =bowl:spider bind:m get-bowl
|
;< =bowl:spider bind:m get-bowl
|
||||||
=/ tid (scot %ta (cat 3 'strand_' (scot %uv (sham file eny.bowl))))
|
=/ tid
|
||||||
|
(scot %ta (cat 3 (cat 3 'strand_' file) (scot %uv (sham file eny.bowl))))
|
||||||
=/ poke-vase !>([`tid.bowl `tid file *vase])
|
=/ poke-vase !>([`tid.bowl `tid file *vase])
|
||||||
;< ~ bind:m (poke-our %spider %spider-start poke-vase)
|
;< ~ bind:m (poke-our %spider %spider-start poke-vase)
|
||||||
;< ~ bind:m (sleep ~s0) :: wait for thread to start
|
;< ~ bind:m (sleep ~s0) :: wait for thread to start
|
||||||
|
@ -5,8 +5,9 @@
|
|||||||
++ noun i
|
++ noun i
|
||||||
++ notification-kind
|
++ notification-kind
|
||||||
?+ index.p.i ~
|
?+ index.p.i ~
|
||||||
[@ ~] `[%link 0]
|
[@ ~] `[%link 0]
|
||||||
[@ @ @ ~] `[%comment 1]
|
[@ @ %1 ~] `[%comment 1]
|
||||||
|
[@ @ @ ~] `[%edit-comment 1]
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
++ grab
|
++ grab
|
||||||
|
@ -8,8 +8,10 @@
|
|||||||
::
|
::
|
||||||
++ notification-kind
|
++ notification-kind
|
||||||
?+ index.p.i ~
|
?+ index.p.i ~
|
||||||
[@ %1 @ ~] `[%note 0]
|
[@ %1 %1 ~] `[%note 0]
|
||||||
[@ %2 @ @ ~] `[%comment 1]
|
[@ %1 @ ~] `[%edit-note 0]
|
||||||
|
[@ %2 @ %1 ~] `[%comment 1]
|
||||||
|
[@ %2 @ @ ~] `[%edit-comment 1]
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
++ grab
|
++ grab
|
||||||
|
@ -13,6 +13,12 @@
|
|||||||
/+ pill
|
/+ pill
|
||||||
=, pill-lib=pill
|
=, pill-lib=pill
|
||||||
|%
|
|%
|
||||||
|
+$ az-log [topics=(lest @) data=@t]
|
||||||
|
+$ az-state
|
||||||
|
$: logs=(list az-log)
|
||||||
|
lives=(map ship [lyfe=life rut=rift])
|
||||||
|
tym=@da
|
||||||
|
==
|
||||||
++ ph-event
|
++ ph-event
|
||||||
$% [%test-done p=?]
|
$% [%test-done p=?]
|
||||||
aqua-event
|
aqua-event
|
||||||
@ -29,6 +35,12 @@
|
|||||||
[%event who=ship ue=unix-event]
|
[%event who=ship ue=unix-event]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
+$ azimuth-action
|
||||||
|
$% [%init-azimuth ~]
|
||||||
|
[%spawn who=ship]
|
||||||
|
[%breach who=ship]
|
||||||
|
==
|
||||||
|
::
|
||||||
+$ aqua-effects
|
+$ aqua-effects
|
||||||
[who=ship ufs=(list unix-effect)]
|
[who=ship ufs=(list unix-effect)]
|
||||||
::
|
::
|
||||||
@ -57,6 +69,7 @@
|
|||||||
[%ergo p=@tas q=mode:clay]
|
[%ergo p=@tas q=mode:clay]
|
||||||
[%sleep ~]
|
[%sleep ~]
|
||||||
[%restore ~]
|
[%restore ~]
|
||||||
|
[%kill ~]
|
||||||
[%init ~]
|
[%init ~]
|
||||||
[%request id=@ud request=request:http]
|
[%request id=@ud request=request:http]
|
||||||
==
|
==
|
||||||
|
@ -85,7 +85,7 @@
|
|||||||
--
|
--
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
%+ aqua-vane-thread ~[%sleep %restore %doze]
|
%+ aqua-vane-thread ~[%sleep %restore %doze %kill]
|
||||||
|_ =bowl:spider
|
|_ =bowl:spider
|
||||||
+* this .
|
+* this .
|
||||||
++ handle-unix-effect
|
++ handle-unix-effect
|
||||||
@ -96,6 +96,7 @@
|
|||||||
%sleep abet-pe:handle-sleep:(pe bowl who)
|
%sleep abet-pe:handle-sleep:(pe bowl who)
|
||||||
%restore abet-pe:handle-restore:(pe bowl who)
|
%restore abet-pe:handle-restore:(pe bowl who)
|
||||||
%doze abet-pe:(handle-doze:(pe bowl who) ue)
|
%doze abet-pe:(handle-doze:(pe bowl who) ue)
|
||||||
|
%kill `(~(del by piers) who)
|
||||||
==
|
==
|
||||||
[cards this]
|
[cards this]
|
||||||
::
|
::
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
%sag ~& [%save-jamfile-to p.b] line
|
%sag ~& [%save-jamfile-to p.b] line
|
||||||
%sav ~& [%save-file-to p.b] line
|
%sav ~& [%save-file-to p.b] line
|
||||||
%url ~& [%activate-url p.b] line
|
%url ~& [%activate-url p.b] line
|
||||||
|
%klr ~& %unhandled-case-klr ""
|
||||||
==
|
==
|
||||||
~? !=(~ last-line) last-line
|
~? !=(~ last-line) last-line
|
||||||
~
|
~
|
||||||
|
@ -100,7 +100,7 @@
|
|||||||
--
|
--
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
%+ aqua-vane-thread ~[%sleep %restore %thus]
|
%+ aqua-vane-thread ~[%sleep %restore %thus %kill]
|
||||||
|_ =bowl:spider
|
|_ =bowl:spider
|
||||||
+* this .
|
+* this .
|
||||||
++ handle-unix-effect
|
++ handle-unix-effect
|
||||||
@ -111,6 +111,7 @@
|
|||||||
%sleep abet-pe:handle-sleep:(pe bowl who)
|
%sleep abet-pe:handle-sleep:(pe bowl who)
|
||||||
%restore abet-pe:handle-restore:(pe bowl who)
|
%restore abet-pe:handle-restore:(pe bowl who)
|
||||||
%thus abet-pe:(handle-thus:(pe bowl who) ue)
|
%thus abet-pe:(handle-thus:(pe bowl who) ue)
|
||||||
|
%kill `(~(del by piers) who)
|
||||||
==
|
==
|
||||||
[cards this]
|
[cards this]
|
||||||
::
|
::
|
||||||
|
94
pkg/arvo/ted/group/on-leave.hoon
Normal file
94
pkg/arvo/ted/group/on-leave.hoon
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/- spider, grp=group-store, gra=graph-store, met=metadata-store, con=contact-store
|
||||||
|
/+ strandio, res=resource
|
||||||
|
::
|
||||||
|
=* strand strand:spider
|
||||||
|
=* raw-poke raw-poke:strandio
|
||||||
|
=* scry scry:strandio
|
||||||
|
::
|
||||||
|
^- thread:spider
|
||||||
|
|= arg=vase
|
||||||
|
=/ m (strand ,vase)
|
||||||
|
^- form:m
|
||||||
|
=+ !<([=update:grp ~] arg)
|
||||||
|
?. ?=(%remove-group -.update)
|
||||||
|
(pure:m !>(~))
|
||||||
|
;< =bowl:spider bind:m get-bowl:strandio
|
||||||
|
:: tell group host to remove us as member
|
||||||
|
::
|
||||||
|
;< ~ bind:m
|
||||||
|
%+ raw-poke
|
||||||
|
[entity.resource.update %group-push-hook]
|
||||||
|
:- %group-update
|
||||||
|
!> ^- update:grp
|
||||||
|
[%remove-members resource.update (silt [our.bowl ~])]
|
||||||
|
:: stop serving or syncing group updates
|
||||||
|
::
|
||||||
|
;< ~ bind:m
|
||||||
|
%+ raw-poke
|
||||||
|
[our.bowl %group-push-hook]
|
||||||
|
:- %push-hook-action
|
||||||
|
!>([%remove resource.update])
|
||||||
|
;< ~ bind:m
|
||||||
|
%+ raw-poke
|
||||||
|
[our.bowl %group-pull-hook]
|
||||||
|
:- %pull-hook-action
|
||||||
|
!>([%remove resource.update])
|
||||||
|
:: stop serving or syncing contacts associated with group
|
||||||
|
::
|
||||||
|
;< ~ bind:m
|
||||||
|
%+ raw-poke
|
||||||
|
[our.bowl %contact-hook]
|
||||||
|
:- %contact-hook-action
|
||||||
|
!>([%remove (en-path:res resource.update)])
|
||||||
|
:: remove contact data associated with group
|
||||||
|
::
|
||||||
|
;< ~ bind:m
|
||||||
|
%+ raw-poke
|
||||||
|
[our.bowl %contact-store]
|
||||||
|
:- %contact-action
|
||||||
|
!> ^- contact-action:con
|
||||||
|
[%delete (en-path:res resource.update)]
|
||||||
|
:: stop serving or syncing metadata associated with group
|
||||||
|
::
|
||||||
|
;< ~ bind:m
|
||||||
|
%+ raw-poke
|
||||||
|
[our.bowl %metadata-hook]
|
||||||
|
:- %metadata-hook-action
|
||||||
|
!>([%remove (en-path:res resource.update)])
|
||||||
|
:: get metadata associated with group
|
||||||
|
::
|
||||||
|
;< =associations:met bind:m
|
||||||
|
%+ scry associations:met
|
||||||
|
;: weld
|
||||||
|
/gx/metadata-store/group
|
||||||
|
(en-path:res resource.update)
|
||||||
|
/noun
|
||||||
|
==
|
||||||
|
=/ entries=(list [g=group-path:met m=md-resource:met])
|
||||||
|
~(tap in ~(key by associations))
|
||||||
|
|- ^- form:m
|
||||||
|
=* loop $
|
||||||
|
?~ entries
|
||||||
|
(pure:m !>(~))
|
||||||
|
:: remove metadata associated with group
|
||||||
|
::
|
||||||
|
;< ~ bind:m
|
||||||
|
%+ raw-poke
|
||||||
|
[our.bowl %metadata-store]
|
||||||
|
:- %metadata-action
|
||||||
|
!> ^- metadata-action:met
|
||||||
|
[%remove g.i.entries m.i.entries]
|
||||||
|
:: archive graph associated with group
|
||||||
|
::
|
||||||
|
;< ~ bind:m
|
||||||
|
%+ raw-poke
|
||||||
|
[our.bowl %graph-store]
|
||||||
|
:- %graph-update
|
||||||
|
!> ^- update:gra
|
||||||
|
[%0 now.bowl [%archive-graph (de-path:res app-path.m.i.entries)]]
|
||||||
|
;< ~ bind:m
|
||||||
|
%+ raw-poke
|
||||||
|
[our.bowl %graph-pull-hook]
|
||||||
|
:- %pull-hook-action
|
||||||
|
!>([%remove (de-path:res app-path.m.i.entries)])
|
||||||
|
loop(entries t.entries)
|
23
pkg/arvo/ted/group/on-remove-member.hoon
Normal file
23
pkg/arvo/ted/group/on-remove-member.hoon
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/- spider, grp=group-store
|
||||||
|
/+ strandio, res=resource
|
||||||
|
::
|
||||||
|
=* strand strand:spider
|
||||||
|
=* raw-poke raw-poke:strandio
|
||||||
|
::
|
||||||
|
^- thread:spider
|
||||||
|
|= arg=vase
|
||||||
|
=/ m (strand ,vase)
|
||||||
|
^- form:m
|
||||||
|
=+ !<([=update:grp ~] arg)
|
||||||
|
?. ?=(%remove-members -.update)
|
||||||
|
(pure:m !>(~))
|
||||||
|
;< =bowl:spider bind:m get-bowl:strandio
|
||||||
|
?. (~(has in ships.update) our.bowl)
|
||||||
|
(pure:m !>(~))
|
||||||
|
;< ~ bind:m
|
||||||
|
%+ raw-poke
|
||||||
|
[our.bowl %group-store]
|
||||||
|
:- %group-action
|
||||||
|
!> ^- action:grp
|
||||||
|
[%remove-group resource.update ~]
|
||||||
|
(pure:m !>(~))
|
19
pkg/arvo/ted/ph/breach-hi-aqua.hoon
Normal file
19
pkg/arvo/ted/ph/breach-hi-aqua.hoon
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/- spider
|
||||||
|
/+ *ph-io, *ph-util
|
||||||
|
=, strand=strand:spider
|
||||||
|
^- thread:spider
|
||||||
|
|= vase
|
||||||
|
=/ m (strand ,vase)
|
||||||
|
;< =bowl:spider bind:m get-bowl
|
||||||
|
;< ~ bind:m start-simple
|
||||||
|
;< ~ bind:m init-azimuth
|
||||||
|
;< ~ bind:m (spawn-aqua ~bud)
|
||||||
|
;< ~ bind:m (spawn-aqua ~dev)
|
||||||
|
;< ~ bind:m (init-ship ~bud)
|
||||||
|
;< ~ bind:m (init-ship ~dev)
|
||||||
|
;< ~ bind:m (send-hi ~bud ~dev)
|
||||||
|
;< ~ bind:m (breach-and-hear-aqua ~dev ~bud)
|
||||||
|
;< ~ bind:m (send-hi-not-responding ~bud ~dev)
|
||||||
|
;< ~ bind:m (init-ship ~dev)
|
||||||
|
;< ~ bind:m (wait-for-output ~bud "hi ~dev successful")
|
||||||
|
(pure:m *vase)
|
13
pkg/arvo/ted/ph/start-drivers.hoon
Normal file
13
pkg/arvo/ted/ph/start-drivers.hoon
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/- spider
|
||||||
|
/+ *ph-io, *ph-util
|
||||||
|
=, strand=strand:spider
|
||||||
|
^- thread:spider
|
||||||
|
|= vase
|
||||||
|
=/ m (strand ,vase)
|
||||||
|
;< =bowl:spider bind:m get-bowl
|
||||||
|
;< ~ bind:m start-simple
|
||||||
|
|-
|
||||||
|
=* loop $
|
||||||
|
~& >> %looping
|
||||||
|
;< ~ bind:m (sleep ~s5)
|
||||||
|
loop
|
6
pkg/interface/package-lock.json
generated
6
pkg/interface/package-lock.json
generated
@ -1693,9 +1693,9 @@
|
|||||||
"integrity": "sha512-3OPSdf9cejP/TSzWXuBaYbzLtAfBzQnc75SlPLkoPfwpxnv1Bvy9hiWngLY0WnKRR6lMOldnkYQCCuNWeDibYQ=="
|
"integrity": "sha512-3OPSdf9cejP/TSzWXuBaYbzLtAfBzQnc75SlPLkoPfwpxnv1Bvy9hiWngLY0WnKRR6lMOldnkYQCCuNWeDibYQ=="
|
||||||
},
|
},
|
||||||
"@tlon/indigo-react": {
|
"@tlon/indigo-react": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.5.tgz",
|
||||||
"integrity": "sha512-6qYLjVcGZtDjI+BqS2PRrfAh9mUCDtYwDOHuYuPyV87mdVRAhduBlQ/3tDVlTNWICF9DeAhozeClxalACs5Ipw==",
|
"integrity": "sha512-NOQTwH74l/XXMIfQ4ZzymvZuk1WK1nmO552TmXrQxBUSb7HmdlA8anG5oRrvnLJTkajLCY59McLkDca+lCcvwg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@reach/menu-button": "^0.10.5",
|
"@reach/menu-button": "^0.10.5",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"@reach/menu-button": "^0.10.5",
|
"@reach/menu-button": "^0.10.5",
|
||||||
"@reach/tabs": "^0.10.5",
|
"@reach/tabs": "^0.10.5",
|
||||||
"@tlon/indigo-light": "^1.0.3",
|
"@tlon/indigo-light": "^1.0.3",
|
||||||
"@tlon/indigo-react": "1.2.13",
|
"@tlon/indigo-react": "1.2.15",
|
||||||
"@tlon/sigil-js": "^1.4.2",
|
"@tlon/sigil-js": "^1.4.2",
|
||||||
"aws-sdk": "^2.726.0",
|
"aws-sdk": "^2.726.0",
|
||||||
"big-integer": "^1.6.48",
|
"big-integer": "^1.6.48",
|
||||||
|
@ -11,7 +11,7 @@ export default class BaseApi<S extends object = {}> {
|
|||||||
this.channel.unsubscribe(id);
|
this.channel.unsubscribe(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(path: Path, method, ship = this.ship, app: string, success, fail, quit) {
|
subscribe(path: Path, method, ship = this.ship, app: string, success, fail, quit, queue = false) {
|
||||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
||||||
|
|
||||||
return this.channel.subscribe(
|
return this.channel.subscribe(
|
||||||
@ -32,7 +32,9 @@ export default class BaseApi<S extends object = {}> {
|
|||||||
},
|
},
|
||||||
(qui) => {
|
(qui) => {
|
||||||
quit(qui);
|
quit(qui);
|
||||||
}
|
},
|
||||||
|
() => {},
|
||||||
|
queue
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,10 +32,6 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(path: Path) {
|
|
||||||
return this.viewAction({ delete: { path } });
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(path: Path, ship: Patp) {
|
remove(path: Path, ship: Patp) {
|
||||||
return this.viewAction({ remove: { path, ship } });
|
return this.viewAction({ remove: { path, ship } });
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,10 @@ export default class GroupsApi extends BaseApi<StoreState> {
|
|||||||
return this.proxyAction({ addMembers: { resource, ships } });
|
return this.proxyAction({ addMembers: { resource, ships } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeGroup(resource: Resource) {
|
||||||
|
return this.storeAction({ removeGroup: { resource } });
|
||||||
|
}
|
||||||
|
|
||||||
changePolicy(resource: Resource, diff: Enc<GroupPolicyDiff>) {
|
changePolicy(resource: Resource, diff: Enc<GroupPolicyDiff>) {
|
||||||
return this.proxyAction({ changePolicy: { resource, diff } });
|
return this.proxyAction({ changePolicy: { resource, diff } });
|
||||||
}
|
}
|
||||||
@ -35,6 +39,7 @@ export default class GroupsApi extends BaseApi<StoreState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private storeAction(action: GroupAction) {
|
private storeAction(action: GroupAction) {
|
||||||
return this.action('group-store', 'group-action', action);
|
console.log(action);
|
||||||
|
return this.action('group-store', 'group-update', action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@ export default class LaunchApi extends BaseApi<StoreState> {
|
|||||||
return this.launchAction({ 'change-is-shown': { name, isShown }});
|
return this.launchAction({ 'change-is-shown': { name, isShown }});
|
||||||
}
|
}
|
||||||
|
|
||||||
weather(latlng: any) {
|
weather(location: string) {
|
||||||
return this.action('weather', 'json', latlng);
|
return this.action('weather', 'json', location);
|
||||||
}
|
}
|
||||||
|
|
||||||
private launchAction(data) {
|
private launchAction(data) {
|
||||||
|
@ -106,13 +106,13 @@ export function getLatestCommentRevision(node: GraphNode): [number, Post] {
|
|||||||
export function getComments(node: GraphNode): GraphNode {
|
export function getComments(node: GraphNode): GraphNode {
|
||||||
const comments = node.children.get(bigInt(2));
|
const comments = node.children.get(bigInt(2));
|
||||||
if(!comments) {
|
if(!comments) {
|
||||||
return { post: buntPost(), children: new BigIntOrderedMap() }
|
return { post: buntPost(), children: new BigIntOrderedMap() }
|
||||||
}
|
}
|
||||||
return comments;
|
return comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSnippet(body: string) {
|
export function getSnippet(body: string) {
|
||||||
const start = body.slice(0, 400);
|
const start = body.slice(0, body.indexOf('\n', 2));
|
||||||
return start === body ? start : `${start}...`;
|
return (start === body || start.startsWith("![")) ? start : `${start}...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,10 @@ export default class BaseSubscription<S extends object> {
|
|||||||
this.channel.setOnChannelOpen(this.onChannelOpen.bind(this));
|
this.channel.setOnChannelOpen(this.onChannelOpen.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearQueue() {
|
||||||
|
this.channel.clearQueue();
|
||||||
|
}
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
this.channel.delete();
|
this.channel.delete();
|
||||||
}
|
}
|
||||||
|
@ -43,13 +43,16 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
this.subscribe('/all', 'invite-store');
|
|
||||||
this.subscribe('/groups', 'group-store');
|
|
||||||
this.subscribe('/primary', 'contact-view');
|
|
||||||
this.subscribe('/all', 'metadata-store');
|
this.subscribe('/all', 'metadata-store');
|
||||||
this.subscribe('/all', 's3-store');
|
this.subscribe('/all', 'invite-store');
|
||||||
this.subscribe('/all', 'launch');
|
this.subscribe('/all', 'launch');
|
||||||
this.subscribe('/all', 'weather');
|
this.subscribe('/all', 'weather');
|
||||||
|
this.subscribe('/groups', 'group-store');
|
||||||
|
this.clearQueue();
|
||||||
|
|
||||||
|
|
||||||
|
this.subscribe('/primary', 'contact-view');
|
||||||
|
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');
|
||||||
this.subscribe('/updates', 'hark-graph-hook');
|
this.subscribe('/updates', 'hark-graph-hook');
|
||||||
|
@ -14,6 +14,8 @@ import './css/fonts.css';
|
|||||||
import light from './themes/light';
|
import light from './themes/light';
|
||||||
import dark from './themes/old-dark';
|
import dark from './themes/old-dark';
|
||||||
|
|
||||||
|
import { Text, Anchor, Row } from '@tlon/indigo-react';
|
||||||
|
|
||||||
import { Content } from './landscape/components/Content';
|
import { Content } from './landscape/components/Content';
|
||||||
import StatusBar from './components/StatusBar';
|
import StatusBar from './components/StatusBar';
|
||||||
import Omnibox from './components/leap/Omnibox';
|
import Omnibox from './components/leap/Omnibox';
|
||||||
@ -25,6 +27,7 @@ import GlobalApi from '~/logic/api/global';
|
|||||||
import { uxToHex } from '~/logic/lib/util';
|
import { uxToHex } from '~/logic/lib/util';
|
||||||
import { foregroundFromBackground } from '~/logic/lib/sigil';
|
import { foregroundFromBackground } from '~/logic/lib/sigil';
|
||||||
|
|
||||||
|
|
||||||
const Root = styled.div`
|
const Root = styled.div`
|
||||||
font-family: ${p => p.theme.fonts.sans};
|
font-family: ${p => p.theme.fonts.sans};
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -128,6 +131,9 @@ 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 showBanner = localStorage.getItem("2020BreachBanner") || "flex";
|
||||||
|
let banner = null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@ -136,6 +142,21 @@ class App extends React.Component {
|
|||||||
: null}
|
: null}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Root background={background}>
|
<Root background={background}>
|
||||||
|
<Row
|
||||||
|
ref={e => banner = e}
|
||||||
|
display={showBanner}
|
||||||
|
justifyContent="space-between"
|
||||||
|
width='100%'
|
||||||
|
p='2'
|
||||||
|
backgroundColor='yellow'>
|
||||||
|
<Text color='#000000'>
|
||||||
|
A network-wide breach is scheduled for early December 2020. Please visit <Anchor target="_blank" href="https://urbit.org/breach" color='inherit'>urbit.org/breach</Anchor> for more information.
|
||||||
|
</Text>
|
||||||
|
<Text cursor='pointer' fontWeight='500' onClick={() => {
|
||||||
|
banner.style.display = "none";
|
||||||
|
localStorage.setItem("2020BreachBanner", "none");
|
||||||
|
}}>Dismiss</Text>
|
||||||
|
</Row>
|
||||||
<Router>
|
<Router>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<StatusBarWithRouter
|
<StatusBarWithRouter
|
||||||
|
@ -3,7 +3,7 @@ import moment from "moment";
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { Box, Row, Text, Rule } from "@tlon/indigo-react";
|
import { Box, Row, Text, Rule } from "@tlon/indigo-react";
|
||||||
|
|
||||||
import { OverlaySigil } from './overlay-sigil';
|
import OverlaySigil from '~/views/components/OverlaySigil';
|
||||||
import { uxToHex, cite, writeText } from '~/logic/lib/util';
|
import { uxToHex, cite, writeText } from '~/logic/lib/util';
|
||||||
import { Envelope, IMessage } from "~/types/chat-update";
|
import { Envelope, IMessage } from "~/types/chat-update";
|
||||||
import { Group, Association, Contacts, LocalUpdateRemoteContentPolicy } from "~/types";
|
import { Group, Association, Contacts, LocalUpdateRemoteContentPolicy } from "~/types";
|
||||||
@ -23,9 +23,9 @@ export const UnreadMarker = React.forwardRef(({ dayBreak, when }, ref) => (
|
|||||||
));
|
));
|
||||||
|
|
||||||
export const DayBreak = ({ when }) => (
|
export const DayBreak = ({ when }) => (
|
||||||
<div className="pv3 gray2 b--gray2 flex items-center justify-center f9 w-100">
|
<Row pb='3' alignItems="center" justifyContent="center" width='100%'>
|
||||||
<p>{moment(when).calendar(null, { sameElse: DATESTAMP_FORMAT })}</p>
|
<Text gray>{moment(when).calendar(null, { sameElse: DATESTAMP_FORMAT })}</Text>
|
||||||
</div>
|
</Row>
|
||||||
);
|
);
|
||||||
|
|
||||||
interface ChatMessageProps {
|
interface ChatMessageProps {
|
||||||
@ -191,7 +191,7 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const datestamp = moment.unix(msg.when / 1000).format(DATESTAMP_FORMAT);
|
const datestamp = moment.unix(msg.when / 1000).format(DATESTAMP_FORMAT);
|
||||||
const contact = msg.author in contacts ? contacts[msg.author] : false;
|
const contact = msg.author in contacts ? contacts[msg.author] : undefined;
|
||||||
const showNickname = !hideNicknames && contact && contact.nickname;
|
const showNickname = !hideNicknames && contact && contact.nickname;
|
||||||
const name = showNickname ? contact.nickname : cite(msg.author);
|
const name = showNickname ? contact.nickname : cite(msg.author);
|
||||||
const color = contact ? `#${uxToHex(contact.color)}` : this.isDark ? '#000000' :'#FFFFFF'
|
const color = contact ? `#${uxToHex(contact.color)}` : this.isDark ? '#000000' :'#FFFFFF'
|
||||||
@ -215,16 +215,16 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
|||||||
contact={contact}
|
contact={contact}
|
||||||
color={color}
|
color={color}
|
||||||
sigilClass={sigilClass}
|
sigilClass={sigilClass}
|
||||||
association={association}
|
|
||||||
group={group}
|
group={group}
|
||||||
hideAvatars={hideAvatars}
|
hideAvatars={hideAvatars}
|
||||||
hideNicknames={hideNicknames}
|
hideNicknames={hideNicknames}
|
||||||
scrollWindow={scrollWindow}
|
scrollWindow={scrollWindow}
|
||||||
history={history}
|
history={history}
|
||||||
api={api}
|
api={api}
|
||||||
|
bg="white"
|
||||||
className="fl pr3 v-top pt1"
|
className="fl pr3 v-top pt1"
|
||||||
/>
|
/>
|
||||||
<Box flexGrow='1' display='block' className="clamp-message">
|
<Box flexGrow={1} display='block' className="clamp-message">
|
||||||
<Box
|
<Box
|
||||||
className="hide-child"
|
className="hide-child"
|
||||||
pt={1}
|
pt={1}
|
||||||
@ -240,12 +240,12 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
|||||||
className={`mw5 db truncate pointer`}
|
className={`mw5 db truncate pointer`}
|
||||||
ref={e => nameSpan = e}
|
ref={e => nameSpan = e}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
writeText(msg.author);
|
writeText(`~${msg.author}`);
|
||||||
copyNotice(name);
|
copyNotice(name);
|
||||||
}}
|
}}
|
||||||
title={`~${msg.author}`}
|
title={`~${msg.author}`}
|
||||||
>{name}</Text>
|
>{name}</Text>
|
||||||
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
|
<Text flexShrink={0} gray mono className="v-mid">{timestamp}</Text>
|
||||||
<Text gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
|
<Text gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box fontSize={fontSize ? fontSize : '14px'}><MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure} fontSize={fontSize} /></Box>
|
<Box fontSize={fontSize ? fontSize : '14px'}><MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure} fontSize={fontSize} /></Box>
|
||||||
@ -279,6 +279,11 @@ export const MessageContent = ({ content, remoteContentPolicy, measure, fontSize
|
|||||||
}}}
|
}}}
|
||||||
videoProps={{style: {
|
videoProps={{style: {
|
||||||
maxWidth: '18rem'
|
maxWidth: '18rem'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
textProps={{style: {
|
||||||
|
fontSize: 'inherit',
|
||||||
|
textDecoration: 'underline'
|
||||||
}}}
|
}}}
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -182,6 +182,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
if (this.props.unreadCount === 0) return;
|
if (this.props.unreadCount === 0) return;
|
||||||
this.props.api.chat.read(this.props.station);
|
this.props.api.chat.read(this.props.station);
|
||||||
this.props.api.hark.readIndex({ chat: { chat: this.props.station, mention: false }});
|
this.props.api.hark.readIndex({ chat: { chat: this.props.station, mention: false }});
|
||||||
|
this.props.api.hark.readIndex({ chat: { chat: this.props.station, mention: true }});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchMessages(start, end, force = false): Promise<void> {
|
fetchMessages(start, end, force = false): Promise<void> {
|
||||||
|
@ -3,7 +3,7 @@ import { UnControlled as CodeEditor } from 'react-codemirror2';
|
|||||||
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
|
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
|
||||||
import CodeMirror from 'codemirror';
|
import CodeMirror from 'codemirror';
|
||||||
|
|
||||||
import { Row, BaseInput } from '@tlon/indigo-react';
|
import { Row, BaseTextArea } from '@tlon/indigo-react';
|
||||||
|
|
||||||
import 'codemirror/mode/markdown/markdown';
|
import 'codemirror/mode/markdown/markdown';
|
||||||
import 'codemirror/addon/display/placeholder';
|
import 'codemirror/addon/display/placeholder';
|
||||||
@ -167,9 +167,10 @@ export default class ChatEditor extends Component {
|
|||||||
color="black"
|
color="black"
|
||||||
>
|
>
|
||||||
{MOBILE_BROWSER_REGEX.test(navigator.userAgent)
|
{MOBILE_BROWSER_REGEX.test(navigator.userAgent)
|
||||||
? <BaseInput
|
? <BaseTextArea
|
||||||
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
|
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
|
||||||
fontSize="14px"
|
fontSize="14px"
|
||||||
|
lineHeight="tall"
|
||||||
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}
|
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}
|
||||||
placeholder={inCodeMode ? "Code..." : "Message..."}
|
placeholder={inCodeMode ? "Code..." : "Message..."}
|
||||||
onKeyUp={event => {
|
onKeyUp={event => {
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
import { Sigil } from '~/logic/lib/sigil';
|
|
||||||
import {
|
|
||||||
ProfileOverlay,
|
|
||||||
OVERLAY_HEIGHT
|
|
||||||
} from './profile-overlay';
|
|
||||||
import { Box, BaseImage } from '@tlon/indigo-react';
|
|
||||||
|
|
||||||
export class OverlaySigil extends PureComponent {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.state = {
|
|
||||||
clicked: false,
|
|
||||||
captured: false,
|
|
||||||
topSpace: 0,
|
|
||||||
bottomSpace: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
this.containerRef = React.createRef();
|
|
||||||
|
|
||||||
this.profileShow = this.profileShow.bind(this);
|
|
||||||
this.profileHide = this.profileHide.bind(this);
|
|
||||||
this.updateContainerOffset = this.updateContainerOffset.bind(this);
|
|
||||||
this.updateContainerInterval = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
profileShow() {
|
|
||||||
this.updateContainerOffset();
|
|
||||||
this.setState({ profileClicked: true });
|
|
||||||
this.props.scrollWindow.addEventListener('scroll', this.updateContainerOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
profileHide() {
|
|
||||||
this.setState({ profileClicked: false });
|
|
||||||
this.props.scrollWindow.removeEventListener('scroll', this.updateContainerOffset, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateContainerOffset() {
|
|
||||||
if (this.containerRef && this.containerRef.current) {
|
|
||||||
const container = this.containerRef.current;
|
|
||||||
const scrollWindow = this.props.scrollWindow;
|
|
||||||
|
|
||||||
const bottomSpace = scrollWindow.scrollHeight - container.offsetTop - scrollWindow.scrollTop;
|
|
||||||
const topSpace = scrollWindow.offsetHeight - bottomSpace - OVERLAY_HEIGHT;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
topSpace,
|
|
||||||
bottomSpace
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.props.scrollWindow?.removeEventListener('scroll', this.updateContainerOffset, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { props, state } = this;
|
|
||||||
const { hideAvatars } = props;
|
|
||||||
|
|
||||||
const img = (props.contact && (props.contact.avatar !== null) && !hideAvatars)
|
|
||||||
? <BaseImage display='inline-block' src={props.contact.avatar} height={16} width={16} />
|
|
||||||
: <Sigil
|
|
||||||
ship={props.ship}
|
|
||||||
size={16}
|
|
||||||
color={props.color}
|
|
||||||
classes={props.sigilClass}
|
|
||||||
icon
|
|
||||||
padded
|
|
||||||
/>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
cursor='pointer'
|
|
||||||
position='relative'
|
|
||||||
onClick={this.profileShow}
|
|
||||||
className={props.className}
|
|
||||||
ref={this.containerRef}
|
|
||||||
>
|
|
||||||
{state.profileClicked && (
|
|
||||||
<ProfileOverlay
|
|
||||||
ship={props.ship}
|
|
||||||
contact={props.contact}
|
|
||||||
color={props.color}
|
|
||||||
topSpace={state.topSpace}
|
|
||||||
bottomSpace={state.bottomSpace}
|
|
||||||
association={props.association}
|
|
||||||
group={props.group}
|
|
||||||
onDismiss={this.profileHide}
|
|
||||||
hideAvatars={hideAvatars}
|
|
||||||
hideNicknames={props.hideNicknames}
|
|
||||||
history={props.history}
|
|
||||||
api={props.api}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{img}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,7 +10,10 @@ import Tiles from './components/tiles';
|
|||||||
import Tile from './components/tiles/tile';
|
import Tile from './components/tiles/tile';
|
||||||
import Welcome from './components/welcome';
|
import Welcome from './components/welcome';
|
||||||
import Groups from './components/Groups';
|
import Groups from './components/Groups';
|
||||||
|
import ModalButton from './components/ModalButton';
|
||||||
import { writeText } from '~/logic/lib/util';
|
import { writeText } from '~/logic/lib/util';
|
||||||
|
import { NewGroup } from "~/views/landscape/components/NewGroup";
|
||||||
|
import { JoinGroup } from "~/views/landscape/components/JoinGroup";
|
||||||
|
|
||||||
const ScrollbarLessBox = styled(Box)`
|
const ScrollbarLessBox = styled(Box)`
|
||||||
scrollbar-width: none !important;
|
scrollbar-width: none !important;
|
||||||
@ -39,19 +42,18 @@ export default function LaunchApp(props) {
|
|||||||
pt={0}
|
pt={0}
|
||||||
>
|
>
|
||||||
<Tile
|
<Tile
|
||||||
bg="transparent"
|
bg="white"
|
||||||
color="green"
|
color="scales.black20"
|
||||||
to="/~landscape/home"
|
to="/~landscape/home"
|
||||||
p={0}
|
p={0}
|
||||||
>
|
>
|
||||||
<Box p={2} height='100%' width='100%' bg='green'>
|
<Box p={2} height='100%' width='100%' bg='scales.black20'>
|
||||||
<Row alignItems='center'>
|
<Row alignItems='center'>
|
||||||
<Icon
|
<Icon
|
||||||
color="white"
|
color="black"
|
||||||
// fill="rgba(0,0,0,0)"
|
icon="Mail"
|
||||||
icon="Boot"
|
|
||||||
/>
|
/>
|
||||||
<Text ml="1" mt='1px' color="white">DMs + Drafts</Text>
|
<Text ml="1" mt='1px' color="black">DMs + Drafts</Text>
|
||||||
</Row>
|
</Row>
|
||||||
</Box>
|
</Box>
|
||||||
</Tile>
|
</Tile>
|
||||||
@ -62,7 +64,23 @@ export default function LaunchApp(props) {
|
|||||||
location={props.userLocation}
|
location={props.userLocation}
|
||||||
weather={props.weather}
|
weather={props.weather}
|
||||||
/>
|
/>
|
||||||
<Box display={["none", "block"]} width="100%" gridColumn="1 / -1"></Box>
|
<ModalButton
|
||||||
|
icon="Plus"
|
||||||
|
bg="blue"
|
||||||
|
color="#fff"
|
||||||
|
text="Join a Group"
|
||||||
|
style={{ gridColumnStart: 1 }}
|
||||||
|
>
|
||||||
|
<JoinGroup {...props} />
|
||||||
|
</ModalButton>
|
||||||
|
<ModalButton
|
||||||
|
icon="CreateGroup"
|
||||||
|
bg="green"
|
||||||
|
color="#fff"
|
||||||
|
text="Create a Group"
|
||||||
|
>
|
||||||
|
<NewGroup {...props} />
|
||||||
|
</ModalButton>
|
||||||
<Groups unreads={props.unreads} groups={props.groups} associations={props.associations} />
|
<Groups unreads={props.unreads} groups={props.groups} associations={props.associations} />
|
||||||
</Box>
|
</Box>
|
||||||
</ScrollbarLessBox>
|
</ScrollbarLessBox>
|
||||||
|
@ -34,7 +34,7 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{groups.map((group) => {
|
{groups.map((group, index) => {
|
||||||
const path = group?.["group-path"];
|
const path = group?.["group-path"];
|
||||||
const unreadCount = (["chat", "graph"] as const)
|
const unreadCount = (["chat", "graph"] as const)
|
||||||
.map(getUnreads(path))
|
.map(getUnreads(path))
|
||||||
@ -42,6 +42,7 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
|||||||
.reduce(f.add, 0);
|
.reduce(f.add, 0);
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group
|
||||||
|
first={index === 0}
|
||||||
unreads={unreadCount}
|
unreads={unreadCount}
|
||||||
path={group?.["group-path"]}
|
path={group?.["group-path"]}
|
||||||
title={group.metadata.title}
|
title={group.metadata.title}
|
||||||
@ -56,11 +57,12 @@ interface GroupProps {
|
|||||||
path: string;
|
path: string;
|
||||||
title: string;
|
title: string;
|
||||||
unreads: number;
|
unreads: number;
|
||||||
|
first: boolean;
|
||||||
}
|
}
|
||||||
function Group(props: GroupProps) {
|
function Group(props: GroupProps) {
|
||||||
const { path, title, unreads } = props;
|
const { path, title, unreads, first = false } = props;
|
||||||
return (
|
return (
|
||||||
<Tile to={`/~landscape${path}`}>
|
<Tile to={`/~landscape${path}`} gridColumnStart={first ? '1' : null}>
|
||||||
<Col height="100%" justifyContent="space-between">
|
<Col height="100%" justifyContent="space-between">
|
||||||
<Text>{title}</Text>
|
<Text>{title}</Text>
|
||||||
{unreads > 0 &&
|
{unreads > 0 &&
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
import React, { useState, useEffect } from "react"
|
||||||
|
import { Box, Button, Icon, Text } from "@tlon/indigo-react"
|
||||||
|
import { NewGroup } from "~/views/landscape/components/NewGroup";
|
||||||
|
import { JoinGroup } from "~/views/landscape/components/JoinGroup";
|
||||||
|
|
||||||
|
const ModalButton = (props) => {
|
||||||
|
const {
|
||||||
|
childen,
|
||||||
|
icon,
|
||||||
|
text,
|
||||||
|
bg,
|
||||||
|
color,
|
||||||
|
...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"
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
onClick={() => setModalShown(true)}
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
cursor="pointer"
|
||||||
|
bg={bg}
|
||||||
|
p={2}
|
||||||
|
borderRadius={2}
|
||||||
|
boxShadow="0 0 0px 1px inset"
|
||||||
|
color="scales.black20"
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<Icon icon={props.icon} mr={2} color={color}></Icon><Text color={color}>{props.text}</Text>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalButton;
|
@ -18,12 +18,13 @@ const SquareBox = styled(Box)`
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
position: relative;
|
||||||
`;
|
`;
|
||||||
const routeList = defaultApps.map(a => `/~${a}`);
|
const routeList = defaultApps.map(a => `/~${a}`);
|
||||||
|
|
||||||
export default class Tile extends React.Component {
|
export default class Tile extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const { bg, to, href, p, boxShadow, ...props } = this.props;
|
const { bg, to, href, p, boxShadow, gridColumnStart, ...props } = this.props;
|
||||||
|
|
||||||
let childElement = (
|
let childElement = (
|
||||||
<Box p={typeof p === 'undefined' ? 2 : p} width="100%" height="100%">
|
<Box p={typeof p === 'undefined' ? 2 : p} width="100%" height="100%">
|
||||||
@ -32,7 +33,7 @@ export default class Tile extends React.Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (to) {
|
if (to) {
|
||||||
if (routeList.indexOf(to) !== -1 || to === '/~landscape/home' || to === '/~profile' || to.startsWith('/~landscape/ship')) {
|
if (routeList.indexOf(to) !== -1 || to === '/~profile' || to.startsWith('/~landscape/')) {
|
||||||
childElement= (<Link to={to}>{childElement}</Link>);
|
childElement= (<Link to={to}>{childElement}</Link>);
|
||||||
} else {
|
} else {
|
||||||
childElement= (<a href={to}>{childElement}</a>);
|
childElement= (<a href={to}>{childElement}</a>);
|
||||||
@ -48,6 +49,7 @@ export default class Tile extends React.Component {
|
|||||||
bg={bg || "white"}
|
bg={bg || "white"}
|
||||||
color={props?.color || 'scales.black20'}
|
color={props?.color || 'scales.black20'}
|
||||||
boxShadow={boxShadow || '0 0 0px 1px inset'}
|
boxShadow={boxShadow || '0 0 0px 1px inset'}
|
||||||
|
style={{ gridColumnStart }}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -1,14 +1,43 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Box, Icon, Text, BaseAnchor, BaseInput } from '@tlon/indigo-react';
|
import { Box, Icon, Text, BaseAnchor, BaseInput } from '@tlon/indigo-react';
|
||||||
|
import ErrorBoundary from '~/views/components/ErrorBoundary';
|
||||||
|
|
||||||
import Tile from './tile';
|
import Tile from './tile';
|
||||||
|
|
||||||
|
export const weatherStyleMap = {
|
||||||
|
Sunny: 'rgba(67, 169, 255, 0.4)',
|
||||||
|
PartlyCloudy: 'rgba(178, 211, 255, 0.33)',
|
||||||
|
Cloudy: 'rgba(136, 153, 176, 0.43)',
|
||||||
|
VeryCloudy: 'rgba(78, 90, 106, 0.43)',
|
||||||
|
Fog: 'rgba(100, 119, 128, 0.12)',
|
||||||
|
LightShowers: 'rgba(121, 148, 185, 0.33)',
|
||||||
|
LightSleetShowers: 'rgba(114, 130, 153, 0.33)',
|
||||||
|
LightSleet: 'rgba(155, 164, 177, 0.33)',
|
||||||
|
ThunderyShowers: 'rgba(53, 77, 103, 0.33)',
|
||||||
|
LightSnow: 'rgba(179, 182, 200, 0.33)',
|
||||||
|
HeavySnow: 'rgba(179, 182, 200, 0.33)',
|
||||||
|
LightRain: 'rgba(58, 79, 107, 0.33)',
|
||||||
|
HeavyShowers: 'rgba(36, 54, 77, 0.33)',
|
||||||
|
HeavyRain: 'rgba(5, 9, 13, 0.39)',
|
||||||
|
LightSnowShowers: 'rgba(174, 184, 198, 0.33)',
|
||||||
|
HeavySnowShowers: 'rgba(55, 74, 107, 0.33)',
|
||||||
|
ThunderyHeavyRain: 'rgba(45, 56, 66, 0.61)',
|
||||||
|
ThunderySnowShowers: 'rgba(40, 54, 79, 0.46)',
|
||||||
|
default: 'transparent'
|
||||||
|
};
|
||||||
|
|
||||||
|
const imperialCountries = [
|
||||||
|
'United States of America',
|
||||||
|
'Myanmar',
|
||||||
|
'Liberia',
|
||||||
|
];
|
||||||
|
|
||||||
export default class WeatherTile extends React.Component {
|
export default class WeatherTile extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
latlong: '',
|
location: '',
|
||||||
manualEntry: false,
|
manualEntry: false,
|
||||||
error: false
|
error: false
|
||||||
};
|
};
|
||||||
@ -17,89 +46,45 @@ export default class WeatherTile extends React.Component {
|
|||||||
// geolocation and manual input functions
|
// geolocation and manual input functions
|
||||||
locationSubmit() {
|
locationSubmit() {
|
||||||
navigator.geolocation.getCurrentPosition((res) => {
|
navigator.geolocation.getCurrentPosition((res) => {
|
||||||
const latlng = `${res.coords.latitude},${res.coords.longitude}`;
|
const location = `${res.coords.latitude},${res.coords.longitude}`;
|
||||||
this.setState({
|
this.setState({
|
||||||
latlng
|
location
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}, { maximumAge: Infinity, timeout: 10000 });
|
}, { maximumAge: Infinity, timeout: 10000 });
|
||||||
this.props.api.launch.weather(latlng);
|
this.props.api.launch.weather(location);
|
||||||
this.setState({ manualEntry: !this.state.manualEntry });
|
this.setState({ manualEntry: !this.state.manualEntry });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
manualLocationSubmit() {
|
manualLocationSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const gpsInput = document.getElementById('gps');
|
const location = document.getElementById('location').value;
|
||||||
const latlngNoSpace = gpsInput.value.replace(/\s+/g, '');
|
this.setState({ location }, (err) => {
|
||||||
const latlngParse = /-?[0-9]+(?:\.[0-9]*)?,-?[0-9]+(?:\.[0-9]*)?/g;
|
|
||||||
if (latlngParse.test(latlngNoSpace)) {
|
|
||||||
const latlng = latlngNoSpace;
|
|
||||||
this.setState({ latlng }, (err) => {
|
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}, { maximumAge: Infinity, timeout: 10000 });
|
}, { maximumAge: Infinity, timeout: 10000 });
|
||||||
this.props.api.launch.weather(latlng);
|
this.props.api.launch.weather(location);
|
||||||
this.setState({ manualEntry: !this.state.manualEntry });
|
this.setState({ manualEntry: !this.state.manualEntry });
|
||||||
} else {
|
|
||||||
this.setState({ error: true });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// set appearance based on weather
|
|
||||||
setColors(data) {
|
|
||||||
let weatherStyle = {
|
|
||||||
bg: '',
|
|
||||||
text: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (data.currently.icon) {
|
// set appearance based on weather
|
||||||
case 'clear-day':
|
colorFromCondition(data) {
|
||||||
weatherStyle = { bg: '#E9F5FF', text: '#333' };
|
let weatherDesc = data['current-condition'][0].weatherDesc[0].value;
|
||||||
break;
|
return weatherStyleMap[weatherDesc] || weatherStyleMap.default;
|
||||||
case 'clear-night':
|
|
||||||
weatherStyle = { bg: '#14263C', text: '#fff' };
|
|
||||||
break;
|
|
||||||
case 'rain':
|
|
||||||
weatherStyle = { bg: '#2E1611', text: '#fff' };
|
|
||||||
break;
|
|
||||||
case 'snow':
|
|
||||||
weatherStyle = { bg: '#F9F9FB', text: '#333' };
|
|
||||||
break;
|
|
||||||
case 'sleet':
|
|
||||||
weatherStyle = { bg: '#EFF1F3', text: '#333' };
|
|
||||||
break;
|
|
||||||
case 'wind':
|
|
||||||
weatherStyle = { bg: '#F7FEF6', text: '#333' };
|
|
||||||
break;
|
|
||||||
case 'fog':
|
|
||||||
weatherStyle = { bg: '#504D44', text: '#fff' };
|
|
||||||
break;
|
|
||||||
case 'cloudy':
|
|
||||||
weatherStyle = { bg: '#EEF1F5', text: '#333' };
|
|
||||||
break;
|
|
||||||
case 'partly-cloudy-day':
|
|
||||||
weatherStyle = { bg: '#F3F6FA', text: '#333' };
|
|
||||||
break;
|
|
||||||
case 'partly-cloudy-night':
|
|
||||||
weatherStyle = { bg: '#283442', text: '#fff' };
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
weatherStyle = { bg: 'white', text: 'black' };
|
|
||||||
}
|
|
||||||
return weatherStyle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// all tile views
|
// all tile views
|
||||||
renderWrapper(child,
|
renderWrapper(child, backgroundColor = 'white') {
|
||||||
weatherStyle = { bg: 'white', text: 'black' }
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<Tile bg={weatherStyle.bg}>
|
<ErrorBoundary>
|
||||||
{child}
|
<Tile bg='white' backgroundColor={backgroundColor}>
|
||||||
</Tile>
|
{child}
|
||||||
|
</Tile>
|
||||||
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderManualEntry() {
|
renderManualEntry(data) {
|
||||||
let secureCheck;
|
let secureCheck;
|
||||||
let error;
|
let error;
|
||||||
if (this.state.error === true) {
|
if (this.state.error === true) {
|
||||||
@ -114,6 +99,10 @@ export default class WeatherTile extends React.Component {
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
let locationName;
|
||||||
|
if ('nearest-area' in data) {
|
||||||
|
locationName = data['nearest-area'][0].areaName[0].value;
|
||||||
|
}
|
||||||
return this.renderWrapper(
|
return this.renderWrapper(
|
||||||
<Box
|
<Box
|
||||||
display='flex'
|
display='flex'
|
||||||
@ -132,21 +121,13 @@ export default class WeatherTile extends React.Component {
|
|||||||
</Text>
|
</Text>
|
||||||
{secureCheck}
|
{secureCheck}
|
||||||
<Text pb={1} mb='auto'>
|
<Text pb={1} mb='auto'>
|
||||||
Please enter your{' '}
|
Please enter your location.
|
||||||
<BaseAnchor
|
{locationName ? ` Current location is near ${locationName}.` : ''}
|
||||||
borderBottom='1px solid'
|
|
||||||
color='black'
|
|
||||||
href="https://latitudeandlongitude.org/"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
latitude and longitude
|
|
||||||
</BaseAnchor>
|
|
||||||
.
|
|
||||||
</Text>
|
</Text>
|
||||||
{error}
|
{error}
|
||||||
<Box mt='auto' display='flex' marginBlockEnd='0'>
|
<Box mt='auto' display='flex' marginBlockEnd='0'>
|
||||||
<BaseInput
|
<BaseInput
|
||||||
id="gps"
|
id="location"
|
||||||
size="10"
|
size="10"
|
||||||
width='100%'
|
width='100%'
|
||||||
color='black'
|
color='black'
|
||||||
@ -154,11 +135,11 @@ export default class WeatherTile extends React.Component {
|
|||||||
backgroundColor='transparent'
|
backgroundColor='transparent'
|
||||||
border='0'
|
border='0'
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="29.55, -95.08"
|
autoFocus
|
||||||
|
placeholder="GPS, ZIP, City"
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
this.manualLocationSubmit(e);
|
||||||
this.manualLocationSubmit(e.target.value);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -171,7 +152,7 @@ export default class WeatherTile extends React.Component {
|
|||||||
fontSize='0'
|
fontSize='0'
|
||||||
border='0'
|
border='0'
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => this.manualLocationSubmit()}
|
onClick={this.manualLocationSubmit.bind(this)}
|
||||||
value="->"
|
value="->"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -182,7 +163,6 @@ export default class WeatherTile extends React.Component {
|
|||||||
renderNoData() {
|
renderNoData() {
|
||||||
return this.renderWrapper(
|
return this.renderWrapper(
|
||||||
<Box
|
<Box
|
||||||
bg='white'
|
|
||||||
display='flex'
|
display='flex'
|
||||||
flexDirection='column'
|
flexDirection='column'
|
||||||
justifyContent='space-between'
|
justifyContent='space-between'
|
||||||
@ -200,14 +180,16 @@ export default class WeatherTile extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderWithData(data, weatherStyle) {
|
renderWithData(data) {
|
||||||
const c = data.currently;
|
const locationName = data['nearest-area'][0].areaName[0].value;
|
||||||
const d = data.daily.data[0];
|
const c = data['current-condition'][0];
|
||||||
|
const d = data['weather'][0];
|
||||||
|
const bg = this.colorFromCondition(data);
|
||||||
|
|
||||||
const sunset = moment.unix(d.sunsetTime);
|
const sunset = moment(d.date + ' ' + d.astronomy[0].sunset, 'YYYY-MM-DD hh:mm A');
|
||||||
const sunsetDiff = sunset.diff(moment(), 'hours');
|
const sunsetDiff = sunset.diff(moment(), 'hours');
|
||||||
|
|
||||||
const sunrise = moment.unix(d.sunriseTime);
|
const sunrise = moment(d.date + ' ' + d.astronomy[0].sunrise, 'YYYY-MM-DD hh:mm A');
|
||||||
let sunriseDiff = sunrise.diff(moment(), 'hours');
|
let sunriseDiff = sunrise.diff(moment(), 'hours');
|
||||||
|
|
||||||
if (sunriseDiff > 24) {
|
if (sunriseDiff > 24) {
|
||||||
@ -220,6 +202,10 @@ export default class WeatherTile extends React.Component {
|
|||||||
? `Sun sets in ${sunsetDiff}h`
|
? `Sun sets in ${sunsetDiff}h`
|
||||||
: `Sun rises in ${sunriseDiff}h`;
|
: `Sun rises in ${sunriseDiff}h`;
|
||||||
|
|
||||||
|
const temp = data['nearest-area'] && imperialCountries.includes(data['nearest-area'][0].country[0].value)
|
||||||
|
? `${Math.round(c.temp_F)}℉`
|
||||||
|
: `${Math.round(c.temp_C)}℃`;
|
||||||
|
|
||||||
return this.renderWrapper(
|
return this.renderWrapper(
|
||||||
<Box
|
<Box
|
||||||
width='100%'
|
width='100%'
|
||||||
@ -227,12 +213,12 @@ export default class WeatherTile extends React.Component {
|
|||||||
display='flex'
|
display='flex'
|
||||||
flexDirection='column'
|
flexDirection='column'
|
||||||
alignItems='space-between'
|
alignItems='space-between'
|
||||||
|
title={`${locationName} Weather`}
|
||||||
>
|
>
|
||||||
<Text color={weatherStyle.text}>
|
<Text>
|
||||||
<Icon icon='Weather' color={weatherStyle.text} display='inline' style={{ position: 'relative', top: '.3em' }} />
|
<Icon icon='Weather' display='inline' style={{ position: 'relative', top: '.3em' }} />
|
||||||
Weather
|
Weather
|
||||||
<Text
|
<Text
|
||||||
color={weatherStyle.text}
|
|
||||||
cursor='pointer'
|
cursor='pointer'
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
this.setState({ manualEntry: !this.state.manualEntry })
|
this.setState({ manualEntry: !this.state.manualEntry })
|
||||||
@ -248,42 +234,56 @@ export default class WeatherTile extends React.Component {
|
|||||||
display="flex"
|
display="flex"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
>
|
>
|
||||||
<Text color={weatherStyle.text}>{c.summary}</Text>
|
<Text>{c.weatherDesc[0].value.replace(/([a-z])([A-Z])/g, '$1 $2')}</Text>
|
||||||
<Text color={weatherStyle.text}>{Math.round(c.temperature)}°</Text>
|
<Text>{temp}</Text>
|
||||||
<Text color={weatherStyle.text}>{nextSolarEvent}</Text>
|
<Text>{nextSolarEvent}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>, bg);
|
||||||
, weatherStyle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const data = this.props.weather ? this.props.weather : {};
|
const data = this.props.weather ? this.props.weather : {};
|
||||||
|
|
||||||
if (this.state.manualEntry === true) {
|
if (this.state.manualEntry === true) {
|
||||||
return this.renderManualEntry();
|
return this.renderManualEntry(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('currently' in data && 'daily' in data) {
|
if ('currently' in data) { // Old weather source
|
||||||
const weatherStyle = this.setColors(data);
|
this.props.api.launch.weather(this.props.location);
|
||||||
return this.renderWithData(data, weatherStyle);
|
}
|
||||||
|
|
||||||
|
if ('current-condition' in data && 'weather' in data) {
|
||||||
|
return this.renderWithData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.location) {
|
if (this.props.location) {
|
||||||
return this.renderWrapper((
|
return this.renderWrapper(
|
||||||
<Box
|
<Box
|
||||||
p='2'
|
|
||||||
width='100%'
|
width='100%'
|
||||||
height='100%'
|
height='100%'
|
||||||
backgroundColor='white'
|
backgroundColor='white'
|
||||||
color='black'
|
color='black'
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
justifyContent="flex-start"
|
||||||
>
|
>
|
||||||
<Icon icon='Weather' color='black' display='inline' style={{ position: 'relative', top: '.3em' }} />
|
<Text><Icon icon='Weather' color='black' display='inline' style={{ position: 'relative', top: '.3em' }} /> Weather</Text>
|
||||||
<Text>Weather</Text>
|
<Text width='100%' display='flex' flexDirection='column' mt={1}>
|
||||||
<Text pt='2' width='100%' display='flex' flexDirection='column'>
|
Loading, please check again later...
|
||||||
Loading, please check again later...
|
</Text>
|
||||||
|
<Text mt="auto">
|
||||||
|
Set new location{' '}
|
||||||
|
<Text
|
||||||
|
cursor='pointer'
|
||||||
|
onClick={() =>
|
||||||
|
this.setState({ manualEntry: !this.state.manualEntry })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
->
|
||||||
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
return this.renderNoData();
|
return this.renderNoData();
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Box, Row, Col, Center, LoadingSpinner } from "@tlon/indigo-react";
|
import { Box, Row, Col, Center, LoadingSpinner, Text } from "@tlon/indigo-react";
|
||||||
import { Switch, Route, Link } from "react-router-dom";
|
import { Switch, Route, Link } from "react-router-dom";
|
||||||
import bigInt from 'big-integer';
|
import bigInt from 'big-integer';
|
||||||
|
|
||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { StoreState } from "~/logic/store/type";
|
import { StoreState } from "~/logic/store/type";
|
||||||
import { uxToHex } from '~/logic/lib/util';
|
import { uxToHex } from '~/logic/lib/util';
|
||||||
import { Association, GraphNode } from "~/types";
|
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
|
|
||||||
import { LinkItem } from "./components/link-item";
|
import { LinkItem } from "./components/LinkItem";
|
||||||
import { LinkSubmit } from "./components/link-submit";
|
import { LinkSubmit } from "./components/link-submit";
|
||||||
import { LinkPreview } from "./components/link-preview";
|
import { LinkPreview } from "./components/link-preview";
|
||||||
import { Comments } from "~/views/components/comments";
|
import { Comments } from "~/views/components/comments";
|
||||||
@ -77,16 +76,18 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
const contact = contactDetails[node.post.author];
|
const contact = contactDetails[node.post.author];
|
||||||
return (
|
return (
|
||||||
<LinkItem
|
<LinkItem
|
||||||
|
contacts={contacts}
|
||||||
key={date.toString()}
|
key={date.toString()}
|
||||||
resource={resourcePath}
|
resource={resourcePath}
|
||||||
node={node}
|
node={node}
|
||||||
nickname={contact?.nickname}
|
|
||||||
hideAvatars={hideAvatars}
|
hideAvatars={hideAvatars}
|
||||||
hideNicknames={hideNicknames}
|
hideNicknames={hideNicknames}
|
||||||
|
remoteContentPolicy={remoteContentPolicy}
|
||||||
baseUrl={resourceUrl}
|
baseUrl={resourceUrl}
|
||||||
color={uxToHex(contact?.color || '0x0')}
|
|
||||||
group={group}
|
group={group}
|
||||||
|
path={resource["group-path"]}
|
||||||
api={api}
|
api={api}
|
||||||
|
mb={3}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -95,7 +96,7 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={relativePath("/:index/:commentId?")}
|
path={relativePath("/:index(\\d+)/:commentId?")}
|
||||||
render={(props) => {
|
render={(props) => {
|
||||||
const index = bigInt(props.match.params.index);
|
const index = bigInt(props.match.params.index);
|
||||||
const editCommentId = props.match.params.commentId || null;
|
const editCommentId = props.match.params.commentId || null;
|
||||||
@ -113,15 +114,21 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
const contact = contactDetails[node.post.author];
|
const contact = contactDetails[node.post.author];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col width="100%" p={3} maxWidth="640px">
|
<Col width="100%" p={3} maxWidth="768px">
|
||||||
<Link to={resourceUrl}>{"<- Back"}</Link>
|
<Link to={resourceUrl}><Text bold>{"<- Back"}</Text></Link>
|
||||||
<LinkPreview
|
<LinkItem
|
||||||
resourcePath={resourcePath}
|
contacts={contacts}
|
||||||
post={node.post}
|
key={node.post.index}
|
||||||
nickname={contact?.nickname}
|
resource={resourcePath}
|
||||||
|
node={node}
|
||||||
|
hideAvatars={hideAvatars}
|
||||||
hideNicknames={hideNicknames}
|
hideNicknames={hideNicknames}
|
||||||
commentNumber={node.children.size}
|
|
||||||
remoteContentPolicy={remoteContentPolicy}
|
remoteContentPolicy={remoteContentPolicy}
|
||||||
|
baseUrl={resourceUrl}
|
||||||
|
group={group}
|
||||||
|
path={resource["group-path"]}
|
||||||
|
api={api}
|
||||||
|
mt={3}
|
||||||
/>
|
/>
|
||||||
<Comments
|
<Comments
|
||||||
ship={ship}
|
ship={ship}
|
||||||
@ -136,6 +143,8 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
editCommentId={editCommentId}
|
editCommentId={editCommentId}
|
||||||
history={props.history}
|
history={props.history}
|
||||||
baseUrl={`${resourceUrl}/${props.match.params.index}`}
|
baseUrl={`${resourceUrl}/${props.match.params.index}`}
|
||||||
|
association={association}
|
||||||
|
group={group}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
|
164
pkg/interface/src/views/apps/links/components/LinkItem.tsx
Normal file
164
pkg/interface/src/views/apps/links/components/LinkItem.tsx
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Row, Col, Anchor, Box, Text, BaseImage, Icon, Action } from '@tlon/indigo-react';
|
||||||
|
|
||||||
|
import { Sigil } from '~/logic/lib/sigil';
|
||||||
|
import { writeText } from '~/logic/lib/util';
|
||||||
|
import Author from '~/views/components/Author';
|
||||||
|
|
||||||
|
import { roleForShip } from '~/logic/lib/group';
|
||||||
|
import { Contacts, GraphNode, Group, LocalUpdateRemoteContentPolicy, Rolodex } from '~/types';
|
||||||
|
import GlobalApi from '~/logic/api/global';
|
||||||
|
import { Dropdown } from '~/views/components/Dropdown';
|
||||||
|
import RemoteContent from '~/views/components/RemoteContent';
|
||||||
|
|
||||||
|
interface LinkItemProps {
|
||||||
|
node: GraphNode;
|
||||||
|
resource: string;
|
||||||
|
hideAvatars: boolean;
|
||||||
|
hideNicknames: boolean;
|
||||||
|
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
||||||
|
api: GlobalApi;
|
||||||
|
group: Group;
|
||||||
|
path: string;
|
||||||
|
contacts: Rolodex[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LinkItem = (props: LinkItemProps) => {
|
||||||
|
const {
|
||||||
|
node,
|
||||||
|
resource,
|
||||||
|
hideAvatars,
|
||||||
|
hideNicknames,
|
||||||
|
remoteContentPolicy,
|
||||||
|
api,
|
||||||
|
group,
|
||||||
|
path,
|
||||||
|
contacts,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const URLparser = new RegExp(
|
||||||
|
/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/
|
||||||
|
);
|
||||||
|
|
||||||
|
const author = node.post.author;
|
||||||
|
const index = node.post.index.split('/')[1];
|
||||||
|
const size = node.children ? node.children.size : 0;
|
||||||
|
const contents = node.post.contents;
|
||||||
|
const hostname = URLparser.exec(contents[1].url) ? URLparser.exec(contents[1].url)[4] : null;
|
||||||
|
|
||||||
|
const baseUrl = props.baseUrl || `/~404/${resource}`;
|
||||||
|
|
||||||
|
const ourRole = group ? roleForShip(group, window.ship) : undefined;
|
||||||
|
const [ship, name] = resource.split('/');
|
||||||
|
|
||||||
|
const [locationText, setLocationText] = useState('Copy Link Location');
|
||||||
|
|
||||||
|
const copyLocation = () => {
|
||||||
|
setLocationText('Copied');
|
||||||
|
writeText(contents[1].url);
|
||||||
|
setTimeout(() => {
|
||||||
|
setLocationText('Copy Link Location');
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteLink = () => {
|
||||||
|
if (confirm('Are you sure you want to delete this link?')) {
|
||||||
|
api.graph.removeNodes(`~${ship}`, name, [node.post.index]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box width="100%" {...rest}>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
lineHeight="tall"
|
||||||
|
display='flex'
|
||||||
|
flexDirection='column'
|
||||||
|
width="100%"
|
||||||
|
color='washedGray'
|
||||||
|
border={1}
|
||||||
|
borderRadius={2}
|
||||||
|
alignItems="flex-start"
|
||||||
|
overflow="hidden"
|
||||||
|
>
|
||||||
|
<RemoteContent
|
||||||
|
url={contents[1].url}
|
||||||
|
text={contents[0].text}
|
||||||
|
remoteContentPolicy={remoteContentPolicy}
|
||||||
|
unfold={true}
|
||||||
|
style={{ alignSelf: 'center' }}
|
||||||
|
oembedProps={{
|
||||||
|
p: 2,
|
||||||
|
className: 'links embed-container',
|
||||||
|
}}
|
||||||
|
imageProps={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
display: 'block'
|
||||||
|
}}
|
||||||
|
textProps={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
color: 'black',
|
||||||
|
display: 'block',
|
||||||
|
alignSelf: 'center',
|
||||||
|
style: { textOverflow: 'ellipsis', whiteSpace: 'pre', width: '100%' },
|
||||||
|
p: 2
|
||||||
|
}} />
|
||||||
|
<Text color="gray" p={2} flexShrink={0}>
|
||||||
|
<Anchor target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none' }} href={contents[1].url}>
|
||||||
|
<Box display='flex'>
|
||||||
|
<Icon icon='ArrowExternal' mr={1} />{hostname}
|
||||||
|
</Box>
|
||||||
|
</Anchor>
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
|
||||||
|
|
||||||
|
<Author
|
||||||
|
showImage
|
||||||
|
contacts={contacts[path]}
|
||||||
|
ship={author}
|
||||||
|
date={node.post['time-sent']}
|
||||||
|
hideAvatars={hideAvatars}
|
||||||
|
hideNicknames={hideNicknames}
|
||||||
|
remoteContentPolicy={remoteContentPolicy}
|
||||||
|
group={group}
|
||||||
|
api={api}
|
||||||
|
></Author>
|
||||||
|
|
||||||
|
<Box ml="auto" mr={1}>
|
||||||
|
<Link to={`${baseUrl}/${index}`}>
|
||||||
|
<Box display='flex'>
|
||||||
|
<Icon color='blue' icon='Chat' />
|
||||||
|
<Text color='blue' ml={1}>{node.children.size}</Text>
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
width="200px"
|
||||||
|
alignX="right"
|
||||||
|
alignY="top"
|
||||||
|
options={
|
||||||
|
<Col backgroundColor="white" border={1} borderRadius={1} borderColor="lightGray">
|
||||||
|
<Row alignItems="center" p={1}>
|
||||||
|
<Action bg="white" m={1} color="black" onClick={copyLocation}>{locationText}</Action>
|
||||||
|
</Row>
|
||||||
|
{(ourRole === 'admin' || node.post.author === window.ship) &&
|
||||||
|
<Row alignItems="center" p={1}>
|
||||||
|
<Action bg="white" m={1} color="red" destructive onClick={deleteLink}>Delete Link</Action>
|
||||||
|
</Row>
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon display="block" icon="Ellipsis" color="gray" />
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
|
</Row>
|
||||||
|
</Box>);
|
||||||
|
};
|
||||||
|
|
@ -1,76 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Row, Col, Anchor, Box, Text, BaseImage } from '@tlon/indigo-react';
|
|
||||||
|
|
||||||
import { Sigil } from '~/logic/lib/sigil';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { cite } from '~/logic/lib/util';
|
|
||||||
|
|
||||||
import { roleForShip } from '~/logic/lib/group';
|
|
||||||
|
|
||||||
export const LinkItem = (props) => {
|
|
||||||
const {
|
|
||||||
node,
|
|
||||||
nickname,
|
|
||||||
avatar,
|
|
||||||
resource,
|
|
||||||
hideAvatars,
|
|
||||||
hideNicknames,
|
|
||||||
api,
|
|
||||||
group
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const URLparser = new RegExp(
|
|
||||||
/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/
|
|
||||||
);
|
|
||||||
|
|
||||||
const author = node.post.author;
|
|
||||||
const index = node.post.index.split('/')[1];
|
|
||||||
const size = node.children ? node.children.size : 0;
|
|
||||||
const contents = node.post.contents;
|
|
||||||
const hostname = URLparser.exec(contents[1].url) ? URLparser.exec(contents[1].url)[4] : null;
|
|
||||||
|
|
||||||
const showAvatar = avatar && !hideAvatars;
|
|
||||||
const showNickname = nickname && !hideNicknames;
|
|
||||||
|
|
||||||
const img = showAvatar
|
|
||||||
? <BaseImage display='inline-block' src={props.avatar} height={36} width={36} />
|
|
||||||
: <Sigil ship={`~${author}`} size={36} color={'#' + props.color} />;
|
|
||||||
|
|
||||||
const baseUrl = props.baseUrl || `/~404/${resource}`;
|
|
||||||
|
|
||||||
const ourRole = group ? roleForShip(group, window.ship) : undefined;
|
|
||||||
const [ship, name] = resource.split('/');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row minWidth='0' flexShrink='0' width="100%" alignItems="center" py={3} bg="white">
|
|
||||||
{img}
|
|
||||||
<Col minWidth='0' height="100%" width='100%' justifyContent="space-between" ml={2}>
|
|
||||||
<Anchor
|
|
||||||
lineHeight="tall"
|
|
||||||
display='flex'
|
|
||||||
style={{ textDecoration: 'none' }}
|
|
||||||
href={contents[1].url}
|
|
||||||
width="100%"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Text display='inline-block' overflow='hidden' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{contents[0].text}</Text>
|
|
||||||
<Text ml="2" color="gray" display='inline-block' flexShrink='0'>{hostname} ↗</Text>
|
|
||||||
</Anchor>
|
|
||||||
<Box width="100%">
|
|
||||||
<Text
|
|
||||||
fontFamily={showNickname ? 'sans' : 'mono'} pr={2}
|
|
||||||
>
|
|
||||||
{showNickname ? nickname : cite(author) }
|
|
||||||
</Text>
|
|
||||||
<Link to={`${baseUrl}/${index}`}>
|
|
||||||
<Text color="gray">{size} comments</Text>
|
|
||||||
</Link>
|
|
||||||
{(ourRole === 'admin' || node.post.author === window.ship)
|
|
||||||
&& (<Text color='red' ml='2' cursor='pointer' onClick={() => api.graph.removeNodes(`~${ship}`, name, [node.post.index])}>Delete</Text>)}
|
|
||||||
</Box>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -42,8 +42,12 @@ function describeNotification(description: string, plural: boolean) {
|
|||||||
return `added ${pluralize("new link", plural)} to`;
|
return `added ${pluralize("new link", plural)} to`;
|
||||||
case "comment":
|
case "comment":
|
||||||
return `left ${pluralize("comment", plural)} on`;
|
return `left ${pluralize("comment", plural)} on`;
|
||||||
|
case "edit-comment":
|
||||||
|
return `updated ${pluralize("comment", plural)} on`;
|
||||||
case "note":
|
case "note":
|
||||||
return `posted ${pluralize("note", plural)} to`;
|
return `posted ${pluralize("note", plural)} to`;
|
||||||
|
case "edit-note":
|
||||||
|
return `updated ${pluralize("note", plural)} in`;
|
||||||
case "mention":
|
case "mention":
|
||||||
return "mentioned you on";
|
return "mentioned you on";
|
||||||
default:
|
default:
|
||||||
|
@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import { Route, Link, Switch } from "react-router-dom";
|
import { Route, Link, Switch } from "react-router-dom";
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import { Box, Text, Row, Col, Icon } from "@tlon/indigo-react";
|
import { Box, Text, Row, Col, Icon, BaseImage } from "@tlon/indigo-react";
|
||||||
|
|
||||||
import { Sigil } from "~/logic/lib/sigil";
|
import { Sigil } from "~/logic/lib/sigil";
|
||||||
import { uxToHex, MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
|
import { uxToHex, MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
|
||||||
@ -12,7 +12,16 @@ import { ContactCard } from "~/views/landscape/components/ContactCard";
|
|||||||
|
|
||||||
const SidebarItem = ({ children, view, current }) => {
|
const SidebarItem = ({ children, view, current }) => {
|
||||||
const selected = current === view;
|
const selected = current === view;
|
||||||
const color = selected ? "blue" : "black";
|
const icon = (view) => {
|
||||||
|
switch(view) {
|
||||||
|
case 'identity':
|
||||||
|
return 'Smiley';
|
||||||
|
case 'settings':
|
||||||
|
return 'Adjust';
|
||||||
|
default:
|
||||||
|
return 'Circle'
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Link to={`/~profile/${view}`}>
|
<Link to={`/~profile/${view}`}>
|
||||||
<Row
|
<Row
|
||||||
@ -20,10 +29,10 @@ const SidebarItem = ({ children, view, current }) => {
|
|||||||
verticalAlign="middle"
|
verticalAlign="middle"
|
||||||
py={1}
|
py={1}
|
||||||
px={3}
|
px={3}
|
||||||
backgroundColor={selected ? "washedBlue" : "white"}
|
backgroundColor={selected ? "washedGray" : "white"}
|
||||||
>
|
>
|
||||||
<Icon mr={2} display="inline-block" icon="Circle" color={color} />
|
<Icon mr={2} display="inline-block" icon={icon(view)} color='black' />
|
||||||
<Text color={color} fontSize={0}>
|
<Text color='black' fontSize={0}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
@ -56,6 +65,9 @@ export default function ProfileScreen(props: any) {
|
|||||||
history.replace("/~profile/identity");
|
history.replace("/~profile/identity");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const image = (!props?.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
|
||||||
@ -87,7 +99,7 @@ export default function ProfileScreen(props: any) {
|
|||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Sigil ship={`~${ship}`} size={80} color={sigilColor} />
|
{image}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width="100%" py={3} zIndex='2'>
|
<Box width="100%" py={3} zIndex='2'>
|
||||||
@ -104,6 +116,7 @@ export default function ProfileScreen(props: any) {
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
px={3}
|
px={3}
|
||||||
borderBottom={1}
|
borderBottom={1}
|
||||||
|
fontSize='0'
|
||||||
borderBottomColor="washedGray"
|
borderBottomColor="washedGray"
|
||||||
>
|
>
|
||||||
<Link to="/~profile">{"<- Back"}</Link>
|
<Link to="/~profile">{"<- Back"}</Link>
|
||||||
@ -119,6 +132,8 @@ export default function ProfileScreen(props: any) {
|
|||||||
path="/~/default"
|
path="/~/default"
|
||||||
api={props.api}
|
api={props.api}
|
||||||
s3={props.s3}
|
s3={props.s3}
|
||||||
|
hideAvatars={props.hideAvatars}
|
||||||
|
hideNicknames={props.hideNicknames}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -50,6 +50,8 @@ export function EditPost(props: EditPostProps & RouteComponentProps) {
|
|||||||
return (
|
return (
|
||||||
<PostForm
|
<PostForm
|
||||||
initial={initial}
|
initial={initial}
|
||||||
|
cancel
|
||||||
|
history={history}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
submitLabel="Update"
|
submitLabel="Update"
|
||||||
loadingText="Updating..."
|
loadingText="Updating..."
|
||||||
|
@ -9,7 +9,7 @@ import { Comments } from "~/views/components/Comments";
|
|||||||
import { NoteNavigation } from "./NoteNavigation";
|
import { NoteNavigation } from "./NoteNavigation";
|
||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { getLatestRevision, getComments } from '~/logic/lib/publish';
|
import { getLatestRevision, getComments } from '~/logic/lib/publish';
|
||||||
import { Author } from "./Author";
|
import Author from "~/views/components/Author";
|
||||||
import { Contacts, GraphNode, Graph, LocalUpdateRemoteContentPolicy } from "~/types";
|
import { Contacts, GraphNode, Graph, LocalUpdateRemoteContentPolicy } from "~/types";
|
||||||
|
|
||||||
interface NoteProps {
|
interface NoteProps {
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import * as Yup from "yup";
|
import * as Yup from 'yup';
|
||||||
import {
|
import {
|
||||||
Box,
|
|
||||||
ManagedTextInputField as Input,
|
ManagedTextInputField as Input,
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
} from "@tlon/indigo-react";
|
Button
|
||||||
import { AsyncButton } from "../../../components/AsyncButton";
|
} from '@tlon/indigo-react';
|
||||||
import { Formik, Form, FormikHelpers } from "formik";
|
import { AsyncButton } from '../../../components/AsyncButton';
|
||||||
import { MarkdownField } from "./MarkdownField";
|
import { Formik, Form, FormikHelpers } from 'formik';
|
||||||
|
import { MarkdownField } from './MarkdownField';
|
||||||
|
|
||||||
interface PostFormProps {
|
interface PostFormProps {
|
||||||
initial: PostFormSchema;
|
initial: PostFormSchema;
|
||||||
|
cancel?: boolean;
|
||||||
|
history?: any;
|
||||||
onSubmit: (
|
onSubmit: (
|
||||||
values: PostFormSchema,
|
values: PostFormSchema,
|
||||||
actions: FormikHelpers<PostFormSchema>
|
actions: FormikHelpers<PostFormSchema>
|
||||||
@ -21,8 +23,8 @@ interface PostFormProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formSchema = Yup.object({
|
const formSchema = Yup.object({
|
||||||
title: Yup.string().required("Title cannot be blank"),
|
title: Yup.string().required('Title cannot be blank'),
|
||||||
body: Yup.string().required("Post cannot be blank"),
|
body: Yup.string().required('Post cannot be blank')
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface PostFormSchema {
|
export interface PostFormSchema {
|
||||||
@ -31,7 +33,7 @@ export interface PostFormSchema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function PostForm(props: PostFormProps) {
|
export function PostForm(props: PostFormProps) {
|
||||||
const { initial, onSubmit, submitLabel, loadingText } = props;
|
const { initial, onSubmit, cancel, submitLabel, loadingText, history } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col width="100%" height="100%" p={[2, 4]}>
|
<Col width="100%" height="100%" p={[2, 4]}>
|
||||||
@ -41,18 +43,26 @@ export function PostForm(props: PostFormProps) {
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
validateOnBlur
|
validateOnBlur
|
||||||
>
|
>
|
||||||
<Form style={{ display: "contents"}}>
|
<Form style={{ display: 'contents' }}>
|
||||||
<Row flexShrink='0' flexDirection={["column-reverse", "row"]} mb={4} gapX={4} justifyContent='space-between'>
|
<Row flexShrink='0' flexDirection={['column-reverse', 'row']} mb={4} gapX={4} justifyContent='space-between'>
|
||||||
<Input maxWidth='40rem' width='100%' flexShrink={[0, 1]} placeholder="Post Title" id="title" />
|
<Input maxWidth='40rem' width='100%' flexShrink={[0, 1]} placeholder="Post Title" id="title" />
|
||||||
<AsyncButton
|
<Row flexDirection={['column', 'row']} mb={[4,0]}>
|
||||||
|
<AsyncButton
|
||||||
|
ml={[0,2]}
|
||||||
|
flexShrink={0}
|
||||||
|
primary
|
||||||
|
loadingText={loadingText}
|
||||||
|
>
|
||||||
|
{submitLabel}
|
||||||
|
</AsyncButton>
|
||||||
|
{cancel && <Button
|
||||||
ml={[0,2]}
|
ml={[0,2]}
|
||||||
mb={[4,0]}
|
mt={[2,0]}
|
||||||
flexShrink={0}
|
onClick={() => {
|
||||||
primary
|
history.goBack();
|
||||||
loadingText={loadingText}
|
}}
|
||||||
>
|
type="button">Cancel</Button>}
|
||||||
{submitLabel}
|
</Row>
|
||||||
</AsyncButton>
|
|
||||||
</Row>
|
</Row>
|
||||||
<MarkdownField flexGrow={1} id="body" />
|
<MarkdownField flexGrow={1} id="body" />
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,26 +1,29 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Col, Box } from "@tlon/indigo-react";
|
import { Link } from 'react-router-dom';
|
||||||
import { cite } from "~/logic/lib/util";
|
import styled from 'styled-components';
|
||||||
import { Note } from "~/types/publish-update";
|
import { Col, Row, Box, Text, Icon, Image } from '@tlon/indigo-react';
|
||||||
import { Contact } from "~/types/contact-update";
|
|
||||||
import ReactMarkdown from "react-markdown";
|
import Author from '~/views/components/Author';
|
||||||
import moment from "moment";
|
import { GraphNode } from '~/types/graph-update';
|
||||||
import { Link } from "react-router-dom";
|
import { Contacts, Group } from '~/types';
|
||||||
import styled from "styled-components";
|
|
||||||
import { GraphNode } from "~/types/graph-update";
|
|
||||||
import {
|
import {
|
||||||
getComments,
|
getComments,
|
||||||
getLatestRevision,
|
getLatestRevision,
|
||||||
getSnippet,
|
getSnippet
|
||||||
} from "~/logic/lib/publish";
|
} from '~/logic/lib/publish';
|
||||||
|
import GlobalApi from '~/logic/api/global';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
|
||||||
interface NotePreviewProps {
|
interface NotePreviewProps {
|
||||||
host: string;
|
host: string;
|
||||||
book: string;
|
book: string;
|
||||||
node: GraphNode;
|
node: GraphNode;
|
||||||
contact?: Contact;
|
hideAvatars?: boolean;
|
||||||
hideNicknames?: boolean;
|
hideNicknames?: boolean;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
|
contacts: Contacts;
|
||||||
|
api: GlobalApi;
|
||||||
|
group: Group;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WrappedBox = styled(Box)`
|
const WrappedBox = styled(Box)`
|
||||||
@ -28,29 +31,14 @@ const WrappedBox = styled(Box)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export function NotePreview(props: NotePreviewProps) {
|
export function NotePreview(props: NotePreviewProps) {
|
||||||
const { node, contact } = props;
|
const { node, contacts, hideAvatars, hideNicknames, group } = props;
|
||||||
const { post } = node;
|
const { post } = node;
|
||||||
if (!post) {
|
if (!post) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = post?.author;
|
|
||||||
if (contact && !props.hideNicknames) {
|
|
||||||
name = contact.nickname.length > 0 ? contact.nickname : post?.author;
|
|
||||||
}
|
|
||||||
if (name === post?.author) {
|
|
||||||
name = cite(post?.author);
|
|
||||||
}
|
|
||||||
|
|
||||||
const numComments = getComments(node).children.size;
|
const numComments = getComments(node).children.size;
|
||||||
const commentDesc =
|
const url = `${props.baseUrl}/note/${post.index.split('/')[1]}`;
|
||||||
numComments === 0
|
|
||||||
? "No Comments"
|
|
||||||
: numComments === 1
|
|
||||||
? "1 Comment"
|
|
||||||
: `${numComments} Comments`;
|
|
||||||
const date = moment(post["time-sent"]).fromNow();
|
|
||||||
const url = `${props.baseUrl}/note/${post.index.split("/")[1]}`;
|
|
||||||
|
|
||||||
// stubbing pending notification-store
|
// stubbing pending notification-store
|
||||||
const isRead = true;
|
const isRead = true;
|
||||||
@ -60,32 +48,53 @@ export function NotePreview(props: NotePreviewProps) {
|
|||||||
const snippet = getSnippet(body);
|
const snippet = getSnippet(body);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={url}>
|
<Box width='100%'>
|
||||||
<Col mb={4}>
|
<Link to={url}>
|
||||||
<WrappedBox mb={1}>{title}</WrappedBox>
|
<Col
|
||||||
<WrappedBox mb={1}>
|
lineHeight='tall'
|
||||||
<ReactMarkdown
|
width='100%'
|
||||||
unwrapDisallowed
|
color={isRead ? 'washedGray' : 'blue'}
|
||||||
allowedTypes={["text", "root", "break", "paragraph"]}
|
border={1}
|
||||||
source={snippet}
|
borderRadius={2}
|
||||||
/>
|
alignItems='flex-start'
|
||||||
</WrappedBox>
|
overflow='hidden'
|
||||||
<Box color="gray" display="flex">
|
p='2'
|
||||||
<Box
|
>
|
||||||
mr={3}
|
<WrappedBox mb={2}><Text bold fontSize='0'>{title}</Text></WrappedBox>
|
||||||
fontFamily={
|
<WrappedBox>
|
||||||
contact?.nickname && !props.hideNicknames ? "sans" : "mono"
|
<Text fontSize='14px'>
|
||||||
}
|
<ReactMarkdown
|
||||||
>
|
unwrapDisallowed
|
||||||
{name}
|
allowedTypes={['text', 'root', 'break', 'paragraph', 'image']}
|
||||||
</Box>
|
renderers={{
|
||||||
<Box color={isRead ? "gray" : "green"} mr={3}>
|
image: props => <Image src={props.src} maxHeight='300px' style={{ objectFit: 'cover' }} />
|
||||||
{date}
|
}}
|
||||||
</Box>
|
source={snippet}
|
||||||
<Box mr={3}>{commentDesc}</Box>
|
/>
|
||||||
<Box>{rev.valueOf() === 1 ? `1 Revision` : `${rev} Revisions`}</Box>
|
</Text>
|
||||||
|
</WrappedBox>
|
||||||
|
</Col>
|
||||||
|
</Link>
|
||||||
|
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
|
||||||
|
<Author
|
||||||
|
showImage
|
||||||
|
contacts={contacts}
|
||||||
|
ship={post?.author}
|
||||||
|
date={post?.['time-sent']}
|
||||||
|
hideAvatars={hideAvatars || false}
|
||||||
|
hideNicknames={hideNicknames || false}
|
||||||
|
group={group}
|
||||||
|
api={props.api}
|
||||||
|
/>
|
||||||
|
<Box ml="auto" mr={1}>
|
||||||
|
<Link to={url}>
|
||||||
|
<Box display='flex'>
|
||||||
|
<Icon color='blue' icon='Chat' />
|
||||||
|
<Text color='blue' ml={1}>{numComments}</Text>
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
</Col>
|
</Row>
|
||||||
</Link>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import { RouteComponentProps } from "react-router-dom";
|
|||||||
import Note from "./Note";
|
import Note from "./Note";
|
||||||
import { EditPost } from "./EditPost";
|
import { EditPost } from "./EditPost";
|
||||||
|
|
||||||
import { GraphNode, Graph, Contacts, LocalUpdateRemoteContentPolicy } from "~/types";
|
import { GraphNode, Graph, Contacts, LocalUpdateRemoteContentPolicy, Group } from "~/types";
|
||||||
|
|
||||||
interface NoteRoutesProps {
|
interface NoteRoutesProps {
|
||||||
ship: string;
|
ship: string;
|
||||||
@ -24,8 +24,6 @@ interface NoteRoutesProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function NoteRoutes(props: NoteRoutesProps & RouteComponentProps) {
|
export function NoteRoutes(props: NoteRoutesProps & RouteComponentProps) {
|
||||||
const { ship, book, noteId } = props;
|
|
||||||
|
|
||||||
const baseUrl = props.baseUrl || '/~404';
|
const baseUrl = props.baseUrl || '/~404';
|
||||||
const rootUrl = props.rootUrl || '/~404';
|
const rootUrl = props.rootUrl || '/~404';
|
||||||
|
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import React, { PureComponent } from "react";
|
import React from 'react';
|
||||||
import { Link, RouteComponentProps, Route, Switch } from "react-router-dom";
|
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||||
import { NotebookPosts } from "./NotebookPosts";
|
import { NotebookPosts } from './NotebookPosts';
|
||||||
import { roleForShip } from "~/logic/lib/group";
|
import { Box, Button, Text, Row, Col } from '@tlon/indigo-react';
|
||||||
import { Box, Button, Text, Row, Col } from "@tlon/indigo-react";
|
import { Groups } from '~/types/group-update';
|
||||||
import { Groups } from "~/types/group-update";
|
import { Contacts, Rolodex } from '~/types/contact-update';
|
||||||
import { Contacts, Rolodex } from "~/types/contact-update";
|
import GlobalApi from '~/logic/api/global';
|
||||||
import GlobalApi from "~/logic/api/global";
|
import { Associations, Graph, Association } from '~/types';
|
||||||
import styled from "styled-components";
|
|
||||||
import { Associations, Graph, Association } from "~/types";
|
|
||||||
import { deSig } from "~/logic/lib/util";
|
|
||||||
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
|
|
||||||
|
|
||||||
interface NotebookProps {
|
interface NotebookProps {
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
@ -22,9 +18,9 @@ interface NotebookProps {
|
|||||||
contacts: Rolodex;
|
contacts: Rolodex;
|
||||||
groups: Groups;
|
groups: Groups;
|
||||||
hideNicknames: boolean;
|
hideNicknames: boolean;
|
||||||
|
hideAvatars: boolean;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
rootUrl: string;
|
rootUrl: string;
|
||||||
associations: Associations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotebookState {
|
interface NotebookState {
|
||||||
@ -39,39 +35,42 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
|
|||||||
notebookContacts,
|
notebookContacts,
|
||||||
groups,
|
groups,
|
||||||
hideNicknames,
|
hideNicknames,
|
||||||
|
hideAvatars,
|
||||||
association,
|
association,
|
||||||
graph,
|
graph
|
||||||
} = props;
|
} = props;
|
||||||
const { metadata } = association;
|
const { metadata } = association;
|
||||||
|
|
||||||
const group = groups[association?.["group-path"]];
|
const group = groups[association?.['group-path']];
|
||||||
if (!group) return null; // Waitin on groups to populate
|
if (!group) {
|
||||||
|
return null; // Waitin on groups to populate
|
||||||
|
}
|
||||||
|
|
||||||
const relativePath = (p: string) => props.baseUrl + p;
|
const relativePath = (p: string) => props.baseUrl + p;
|
||||||
|
|
||||||
const contact = notebookContacts?.[ship];
|
const contact = notebookContacts?.[ship];
|
||||||
const role = group ? roleForShip(group, window.ship) : undefined;
|
|
||||||
const isOwn = `~${window.ship}` === ship;
|
const isOwn = `~${window.ship}` === ship;
|
||||||
|
let isWriter = true;
|
||||||
|
|
||||||
const isWriter =
|
if (group.tags?.publish?.[`writers-${book}`]) {
|
||||||
isOwn || group.tags?.publish?.[`writers-${book}`]?.has(window.ship);
|
isWriter = isOwn || group.tags?.publish?.[`writers-${book}`]?.has(window.ship);
|
||||||
|
}
|
||||||
|
|
||||||
const showNickname = contact?.nickname && !hideNicknames;
|
const showNickname = contact?.nickname && !hideNicknames;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col gapY="4" pt={4} mx="auto" px={3} maxWidth="500px">
|
<Col gapY="4" pt={4} mx="auto" px={3} maxWidth="768px">
|
||||||
<Row justifyContent="space-between">
|
<Row justifyContent="space-between">
|
||||||
<Box>
|
<Box>
|
||||||
<Text> {metadata?.title}</Text>
|
<Text display='block'>{metadata?.title}</Text>
|
||||||
<br />
|
|
||||||
<Text color="lightGray">by </Text>
|
<Text color="lightGray">by </Text>
|
||||||
<Text fontFamily={showNickname ? "sans" : "mono"}>
|
<Text fontFamily={showNickname ? 'sans' : 'mono'}>
|
||||||
{showNickname ? contact?.nickname : ship}
|
{showNickname ? contact?.nickname : ship}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{isWriter && (
|
{isWriter && (
|
||||||
<Link to={relativePath("/new")}>
|
<Link to={relativePath('/new')}>
|
||||||
<Button primary style={{ cursor: "pointer" }}>
|
<Button primary style={{ cursor: 'pointer' }}>
|
||||||
New Post
|
New Post
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
@ -82,9 +81,12 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
|
|||||||
graph={graph}
|
graph={graph}
|
||||||
host={ship}
|
host={ship}
|
||||||
book={book}
|
book={book}
|
||||||
contacts={!!notebookContacts ? notebookContacts : {}}
|
contacts={notebookContacts ? notebookContacts : {}}
|
||||||
hideNicknames={hideNicknames}
|
hideNicknames={hideNicknames}
|
||||||
|
hideAvatars={hideAvatars}
|
||||||
baseUrl={props.baseUrl}
|
baseUrl={props.baseUrl}
|
||||||
|
api={props.api}
|
||||||
|
group={group}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
import React, { Component } from "react";
|
import React from 'react';
|
||||||
import { Col } from "@tlon/indigo-react";
|
import { Col } from '@tlon/indigo-react';
|
||||||
import { NotePreview } from "./NotePreview";
|
import { NotePreview } from './NotePreview';
|
||||||
import { Contacts, Graph } from "~/types";
|
import { Contacts, Graph, Group } from '~/types';
|
||||||
|
import GlobalApi from '~/logic/api/global';
|
||||||
|
|
||||||
interface NotebookPostsProps {
|
interface NotebookPostsProps {
|
||||||
contacts: Contacts;
|
contacts: Contacts;
|
||||||
graph: Graph;
|
graph: Graph;
|
||||||
host: string;
|
host: string;
|
||||||
book: string;
|
book: string;
|
||||||
hideNicknames?: boolean;
|
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
|
hideAvatars?: boolean;
|
||||||
|
hideNicknames?: boolean;
|
||||||
|
api: GlobalApi;
|
||||||
|
group: Group;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NotebookPosts(props: NotebookPostsProps) {
|
export function NotebookPosts(props: NotebookPostsProps) {
|
||||||
@ -22,10 +26,11 @@ export function NotebookPosts(props: NotebookPostsProps) {
|
|||||||
key={date.toString()}
|
key={date.toString()}
|
||||||
host={props.host}
|
host={props.host}
|
||||||
book={props.book}
|
book={props.book}
|
||||||
contact={props.contacts[node.post.author]}
|
contacts={props.contacts}
|
||||||
node={node}
|
node={node}
|
||||||
hideNicknames={props.hideNicknames}
|
|
||||||
baseUrl={props.baseUrl}
|
baseUrl={props.baseUrl}
|
||||||
|
api={props.api}
|
||||||
|
group={props.group}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { RouteComponentProps, Route, Switch } from "react-router-dom";
|
import { RouteComponentProps, Route, Switch } from "react-router-dom";
|
||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import {
|
import {
|
||||||
Association,
|
Association,
|
||||||
Associations,
|
Associations,
|
||||||
Graphs,
|
Graphs,
|
||||||
@ -55,15 +55,18 @@ export function NotebookRoutes(
|
|||||||
<Route
|
<Route
|
||||||
path={baseUrl}
|
path={baseUrl}
|
||||||
exact
|
exact
|
||||||
render={(routeProps) => (
|
render={(routeProps) => {
|
||||||
<Notebook
|
if (!graph) {
|
||||||
|
return <Center height="100%"><LoadingSpinner /></Center>;
|
||||||
|
}
|
||||||
|
return <Notebook
|
||||||
{...props}
|
{...props}
|
||||||
graph={graph}
|
graph={graph}
|
||||||
contacts={notebookContacts}
|
contacts={notebookContacts}
|
||||||
association={props.association}
|
association={props.association}
|
||||||
rootUrl={rootUrl}
|
rootUrl={rootUrl}
|
||||||
baseUrl={baseUrl} />
|
baseUrl={baseUrl} />;
|
||||||
)}
|
}}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={relativePath("/new")}
|
path={relativePath("/new")}
|
||||||
@ -83,7 +86,7 @@ export function NotebookRoutes(
|
|||||||
path={relativePath("/note/:noteId")}
|
path={relativePath("/note/:noteId")}
|
||||||
render={(routeProps) => {
|
render={(routeProps) => {
|
||||||
const { noteId } = routeProps.match.params;
|
const { noteId } = routeProps.match.params;
|
||||||
const noteIdNum = bigInt(noteId)
|
const noteIdNum = bigInt(noteId);
|
||||||
|
|
||||||
if(!graph) {
|
if(!graph) {
|
||||||
return <Center height="100%"><LoadingSpinner /></Center>;
|
return <Center height="100%"><LoadingSpinner /></Center>;
|
||||||
|
@ -4,6 +4,7 @@ import { ShipSearch } from '~/views/components/ShipSearch';
|
|||||||
import { Formik, Form, FormikHelpers } from 'formik';
|
import { Formik, Form, FormikHelpers } from 'formik';
|
||||||
import { resourceFromPath } from '~/logic/lib/group';
|
import { resourceFromPath } from '~/logic/lib/group';
|
||||||
import { AsyncButton } from '~/views/components/AsyncButton';
|
import { AsyncButton } from '~/views/components/AsyncButton';
|
||||||
|
import { cite } from '~/logic/lib/util';
|
||||||
|
|
||||||
export class Writers extends Component {
|
export class Writers extends Component {
|
||||||
render() {
|
render() {
|
||||||
@ -27,6 +28,7 @@ export class Writers extends Component {
|
|||||||
actions.setStatus({ error: e.message });
|
actions.setStatus({ error: e.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const writers = Array.from(groups?.[association?.['group-path']]?.tags.publish?.[`writers-${name}`] || new Set()).map(e => cite(`~${e}`)).join(', ');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box maxWidth='512px'>
|
<Box maxWidth='512px'>
|
||||||
@ -49,6 +51,10 @@ export class Writers extends Component {
|
|||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
{writers.length > 0 && <>
|
||||||
|
<Text display='block' mt='2'>Current writers:</Text>
|
||||||
|
<Text mt='2' display='block' mono>{writers}</Text>
|
||||||
|
</>}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@
|
|||||||
color:var(--gray);
|
color:var(--gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
.md p {
|
.md {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
.md code, .md pre {
|
.md code, .md pre {
|
||||||
@ -201,7 +201,7 @@
|
|||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
md img {
|
.md img {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ import Helmet from 'react-helmet';
|
|||||||
import { History } from './components/history';
|
import { History } from './components/history';
|
||||||
import { Input } from './components/input';
|
import { Input } from './components/input';
|
||||||
|
|
||||||
|
import { Box, Col } from '@tlon/indigo-react';
|
||||||
|
|
||||||
import Api from './api';
|
import Api from './api';
|
||||||
import Store from './store';
|
import Store from './store';
|
||||||
import Subscription from './subscription';
|
import Subscription from './subscription';
|
||||||
@ -47,30 +49,30 @@ export default class TermApp extends Component {
|
|||||||
<Helmet>
|
<Helmet>
|
||||||
<title>OS1 - Terminal</title>
|
<title>OS1 - Terminal</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div
|
<Box
|
||||||
style={{ height: '100%' }}
|
height='100%'
|
||||||
>
|
>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path="/~term/"
|
path="/~term/"
|
||||||
render={(props) => {
|
render={(props) => {
|
||||||
return (
|
return (
|
||||||
<div className="w-100 h-100 flex-m flex-l flex-xl">
|
<Box
|
||||||
<div
|
width='100%'
|
||||||
className="db dn-m dn-l dn-xl inter dt w-100"
|
height='100%'
|
||||||
style={{ height: 40 }}
|
display='flex'
|
||||||
>
|
>
|
||||||
</div>
|
<Col
|
||||||
<div
|
p='3'
|
||||||
className={
|
backgroundColor='white'
|
||||||
'pa3 bg-white bg-gray0-d black white-d mono w-100 f8 relative' +
|
width='100%'
|
||||||
' h-100-m40-s b--gray2 br1 flex-auto flex flex-column ' +
|
minHeight='0'
|
||||||
'mh4-m mh4-l mh4-xl mb4-m mb4-l mb4-xl ba-m ba-l ba-xl'
|
color='washedGray'
|
||||||
}
|
borderRadius='2'
|
||||||
style={{
|
mx={['0','3']}
|
||||||
lineHeight: '1.4',
|
mb={['0','3']}
|
||||||
cursor: 'text'
|
border={['0','1']}
|
||||||
}}
|
cursor='text'
|
||||||
>
|
>
|
||||||
<History log={this.state.lines.slice(0, -1)} />
|
<History log={this.state.lines.slice(0, -1)} />
|
||||||
<Input
|
<Input
|
||||||
@ -80,12 +82,12 @@ export default class TermApp extends Component {
|
|||||||
store={this.store}
|
store={this.store}
|
||||||
line={this.state.lines.slice(-1)[0]}
|
line={this.state.lines.slice(-1)[0]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Col>
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { Box } from '@tlon/indigo-react';
|
||||||
|
|
||||||
import Line from './line';
|
import Line from './line';
|
||||||
|
|
||||||
@ -9,16 +10,22 @@ export class History extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div
|
<Box
|
||||||
className="h-100 relative flex flex-column-reverse overflow-container flex-auto"
|
height='100%'
|
||||||
|
minHeight='0'
|
||||||
|
display='flex'
|
||||||
|
flexDirection='column-reverse'
|
||||||
|
overflowY='scroll'
|
||||||
style={{ resize: 'none' }}
|
style={{ resize: 'none' }}
|
||||||
>
|
>
|
||||||
<div style={{ marginTop: 'auto' }}>
|
<Box
|
||||||
|
mt='auto'
|
||||||
|
>
|
||||||
{this.props.log.map((line, i) => {
|
{this.props.log.map((line, i) => {
|
||||||
return <Line key={i} line={line} />;
|
return <Line key={i} line={line} />;
|
||||||
})}
|
})}
|
||||||
</div>
|
</Box>
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Row, Box, BaseInput } from '@tlon/indigo-react';
|
import { Row, Box, BaseInput } from '@tlon/indigo-react';
|
||||||
import { cite } from '~/logic/lib/util';
|
|
||||||
import { Spinner } from '~/views/components/Spinner';
|
|
||||||
|
|
||||||
export class Input extends Component {
|
export class Input extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -85,15 +83,19 @@ export class Input extends Component {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Row flexGrow='1' position='relative'>
|
<Row flexGrow='1' position='relative'>
|
||||||
<Box flexShrink='0' className="w-100">
|
<Box flexShrink='0' width='100%' color='black' fontSize='0'>
|
||||||
<BaseInput
|
<BaseInput
|
||||||
autoFocus
|
autoFocus
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
|
color='black'
|
||||||
|
minHeight='0'
|
||||||
|
display='inline-block'
|
||||||
|
width='100%'
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
wrap="off"
|
wrap="off"
|
||||||
className="mono ml1 flex-auto dib w-100"
|
className="mono"
|
||||||
id="term"
|
id="term"
|
||||||
cursor={this.props.cursor}
|
cursor={this.props.cursor}
|
||||||
onKeyDown={this.keyPress}
|
onKeyDown={this.keyPress}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { Component, useMemo } from 'react';
|
import React from 'react';
|
||||||
import { Box, Text } from '@tlon/indigo-react';
|
import { Text } from '@tlon/indigo-react';
|
||||||
|
|
||||||
export default React.memo(({line}) => {
|
export default React.memo(({line}) => {
|
||||||
|
|
||||||
@ -59,11 +59,10 @@ export default React.memo(({line}) => {
|
|||||||
// render line
|
// render line
|
||||||
//
|
//
|
||||||
return (
|
return (
|
||||||
<Text mono display='flex' fontSize='14px'
|
<Text mono display='flex' fontSize='0'
|
||||||
style={{ overflowWrap: 'break-word', whiteSpace: 'pre-wrap' }}
|
style={{ overflowWrap: 'break-word', whiteSpace: 'pre-wrap' }}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -14,10 +14,3 @@ input#term {
|
|||||||
90% { opacity: 0; }
|
90% { opacity: 0; }
|
||||||
100% { opacity: 0; }
|
100% { opacity: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* responsive */
|
|
||||||
@media all and (max-width: 34.375em) {
|
|
||||||
.h-100-m40-s {
|
|
||||||
height: calc(100% - 40px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
import React, {ReactNode} from "react";
|
import React, {ReactNode} from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import { Row, Box } from "@tlon/indigo-react";
|
||||||
|
|
||||||
import { Sigil } from "~/logic/lib/sigil"
|
import { Sigil } from "~/logic/lib/sigil"
|
||||||
import { uxToHex, cite } from "~/logic/lib/util";
|
import { uxToHex, cite } from "~/logic/lib/util";
|
||||||
import { Contacts } from "~/types/contact-update";
|
import { Contacts, Rolodex } from "~/types/contact-update";
|
||||||
import { Row, Box } from "@tlon/indigo-react";
|
import OverlaySigil from "./OverlaySigil";
|
||||||
|
import { Group, Association } from "~/types";
|
||||||
|
import GlobalApi from "~/logic/api/global";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
interface AuthorProps {
|
interface AuthorProps {
|
||||||
contacts: Contacts;
|
contacts: Contacts;
|
||||||
@ -13,16 +18,18 @@ interface AuthorProps {
|
|||||||
hideAvatars: boolean;
|
hideAvatars: boolean;
|
||||||
hideNicknames: boolean;
|
hideNicknames: boolean;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
group: Group;
|
||||||
|
api: GlobalApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Author(props: AuthorProps) {
|
export default function Author(props: AuthorProps) {
|
||||||
const { contacts, ship = '', date, showImage } = props;
|
const { contacts, ship = '', date, showImage, hideAvatars, hideNicknames, group, api } = props;
|
||||||
let contact = null;
|
const history = useHistory();
|
||||||
|
let contact;
|
||||||
if (contacts) {
|
if (contacts) {
|
||||||
contact = ship in contacts ? contacts[ship] : null;
|
contact = ship in contacts ? contacts[ship] : null;
|
||||||
}
|
}
|
||||||
const color = contact?.color ? `#${uxToHex(contact?.color)}` : "#000000";
|
const color = contact?.color ? `#${uxToHex(contact?.color)}` : "#000000";
|
||||||
const showAvatar = !props.hideAvatars && contact?.avatar;
|
|
||||||
const showNickname = !props.hideNicknames && contact?.nickname;
|
const showNickname = !props.hideNicknames && contact?.nickname;
|
||||||
|
|
||||||
const name = showNickname ? contact?.nickname : cite(ship);
|
const name = showNickname ? contact?.nickname : cite(ship);
|
||||||
@ -31,18 +38,19 @@ export function Author(props: AuthorProps) {
|
|||||||
<Row alignItems="center" width="auto">
|
<Row alignItems="center" width="auto">
|
||||||
{showImage && (
|
{showImage && (
|
||||||
<Box>
|
<Box>
|
||||||
{showAvatar ? (
|
<OverlaySigil
|
||||||
<img src={contact?.avatar} height={16} width={16} className="dib" />
|
ship={ship}
|
||||||
) : (
|
contact={contact}
|
||||||
<Sigil
|
color={color}
|
||||||
ship={ship}
|
sigilClass={''}
|
||||||
size={16}
|
group={group}
|
||||||
icon
|
hideAvatars={hideAvatars}
|
||||||
padded
|
hideNicknames={hideNicknames}
|
||||||
color={color}
|
history={history}
|
||||||
classes={contact?.color ? '' : "mix-blend-diff"}
|
api={api}
|
||||||
/>
|
bg="white"
|
||||||
)}
|
className="fl v-top pt1"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box
|
<Box
|
@ -1,13 +1,13 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Contacts } from '~/types/contact-update';
|
import { Contacts } from '~/types/contact-update';
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { Box, Row, Text } from '@tlon/indigo-react';
|
import { Box, Row, Text } from '@tlon/indigo-react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Author } from '~/views/apps/publish/components/Author';
|
import Author from '~/views/components/Author';
|
||||||
import { GraphNode, TextContent } from '~/types/graph-update';
|
import { GraphNode, TextContent } from '~/types/graph-update';
|
||||||
import tokenizeMessage from '~/logic/lib/tokenizeMessage';
|
import tokenizeMessage from '~/logic/lib/tokenizeMessage';
|
||||||
import { LocalUpdateRemoteContentPolicy } from '~/types';
|
import { LocalUpdateRemoteContentPolicy, Group } from '~/types';
|
||||||
import { MentionText } from '~/views/components/MentionText';
|
import { MentionText } from '~/views/components/MentionText';
|
||||||
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
||||||
|
|
||||||
@ -27,10 +27,11 @@ interface CommentItemProps {
|
|||||||
hideNicknames: boolean;
|
hideNicknames: boolean;
|
||||||
hideAvatars: boolean;
|
hideAvatars: boolean;
|
||||||
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
||||||
|
group: Group;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CommentItem(props: CommentItemProps) {
|
export function CommentItem(props: CommentItemProps) {
|
||||||
const { ship, contacts, name, api, remoteContentPolicy, comment } = props;
|
const { ship, contacts, name, api, remoteContentPolicy, comment, group } = props;
|
||||||
const [revNum, post] = getLatestCommentRevision(comment);
|
const [revNum, post] = getLatestCommentRevision(comment);
|
||||||
const disabled = props.pending || window.ship !== post?.author;
|
const disabled = props.pending || window.ship !== post?.author;
|
||||||
|
|
||||||
@ -52,6 +53,9 @@ export function CommentItem(props: CommentItemProps) {
|
|||||||
date={post?.['time-sent']}
|
date={post?.['time-sent']}
|
||||||
hideAvatars={props.hideAvatars}
|
hideAvatars={props.hideAvatars}
|
||||||
hideNicknames={props.hideNicknames}
|
hideNicknames={props.hideNicknames}
|
||||||
|
remoteContentPolicy={remoteContentPolicy}
|
||||||
|
group={group}
|
||||||
|
api={api}
|
||||||
>
|
>
|
||||||
{!disabled && (
|
{!disabled && (
|
||||||
<Box display="inline-block" verticalAlign="middle">
|
<Box display="inline-block" verticalAlign="middle">
|
||||||
|
@ -9,7 +9,7 @@ import { FormikHelpers } from 'formik';
|
|||||||
import { GraphNode } from '~/types/graph-update';
|
import { GraphNode } from '~/types/graph-update';
|
||||||
import { createPost, createBlankNodeWithChildPost } from '~/logic/api/graph';
|
import { createPost, createBlankNodeWithChildPost } from '~/logic/api/graph';
|
||||||
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
||||||
import { LocalUpdateRemoteContentPolicy } from '~/types';
|
import { LocalUpdateRemoteContentPolicy, Group } from '~/types';
|
||||||
import { scanForMentions } from '~/logic/lib/graph';
|
import { scanForMentions } from '~/logic/lib/graph';
|
||||||
|
|
||||||
interface CommentsProps {
|
interface CommentsProps {
|
||||||
@ -23,10 +23,11 @@ interface CommentsProps {
|
|||||||
hideAvatars: boolean;
|
hideAvatars: boolean;
|
||||||
hideNicknames: boolean;
|
hideNicknames: boolean;
|
||||||
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
||||||
|
group: Group;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Comments(props: CommentsProps) {
|
export function Comments(props: CommentsProps) {
|
||||||
const { comments, ship, name, api, baseUrl, history} = props;
|
const { comments, ship, name, api, baseUrl, history, group } = props;
|
||||||
|
|
||||||
const onSubmit = async (
|
const onSubmit = async (
|
||||||
{ comment },
|
{ comment },
|
||||||
|
@ -35,6 +35,8 @@ interface DropdownSearchExtraProps<C> {
|
|||||||
onSelect: (c: C) => void;
|
onSelect: (c: C) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void;
|
||||||
|
onBlur?: (e: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type DropdownSearchProps<C> = PropFunc<typeof Box> &
|
type DropdownSearchProps<C> = PropFunc<typeof Box> &
|
||||||
@ -51,6 +53,8 @@ export function DropdownSearch<C>(props: DropdownSearchProps<C>) {
|
|||||||
renderCandidate,
|
renderCandidate,
|
||||||
disabled,
|
disabled,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
onChange = () => {},
|
||||||
|
onBlur = () => {},
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@ -101,8 +105,9 @@ export function DropdownSearch<C>(props: DropdownSearchProps<C>) {
|
|||||||
};
|
};
|
||||||
}, [textarea.current, next, back, onEnter]);
|
}, [textarea.current, next, back, onEnter]);
|
||||||
|
|
||||||
const onChange = useCallback(
|
const changeCallback = useCallback(
|
||||||
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
onChange(e);
|
||||||
search(e.target.value);
|
search(e.target.value);
|
||||||
setQuery(e.target.value);
|
setQuery(e.target.value);
|
||||||
},
|
},
|
||||||
@ -128,11 +133,12 @@ export function DropdownSearch<C>(props: DropdownSearchProps<C>) {
|
|||||||
<Box {...rest} position="relative" zIndex={9}>
|
<Box {...rest} position="relative" zIndex={9}>
|
||||||
<Input
|
<Input
|
||||||
ref={textarea}
|
ref={textarea}
|
||||||
onChange={onChange}
|
onChange={changeCallback}
|
||||||
value={query}
|
value={query}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
onBlur={onBlur}
|
||||||
/>
|
/>
|
||||||
{dropdown.length !== 0 && query.length !== 0 && (
|
{dropdown.length !== 0 && query.length !== 0 && (
|
||||||
<Box
|
<Box
|
||||||
|
141
pkg/interface/src/views/components/OverlaySigil.tsx
Normal file
141
pkg/interface/src/views/components/OverlaySigil.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import { Sigil } from '~/logic/lib/sigil';
|
||||||
|
import { Contact, Group } from '~/types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ProfileOverlay,
|
||||||
|
OVERLAY_HEIGHT
|
||||||
|
} from './ProfileOverlay';
|
||||||
|
|
||||||
|
import { Box, BaseImage, ColProps } from '@tlon/indigo-react';
|
||||||
|
|
||||||
|
type OverlaySigilProps = ColProps & {
|
||||||
|
ship: string;
|
||||||
|
contact?: Contact;
|
||||||
|
color: string;
|
||||||
|
sigilClass: string;
|
||||||
|
group?: Group;
|
||||||
|
hideAvatars: boolean;
|
||||||
|
hideNicknames: boolean;
|
||||||
|
scrollWindow?: HTMLElement;
|
||||||
|
history: any;
|
||||||
|
api: any;
|
||||||
|
className: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OverlaySigilState {
|
||||||
|
clicked: boolean;
|
||||||
|
topSpace: number | 'auto';
|
||||||
|
bottomSpace: number | 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
|
||||||
|
public containerRef: React.Ref<HTMLDivElement>;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
clicked: false,
|
||||||
|
topSpace: 0,
|
||||||
|
bottomSpace: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.containerRef = React.createRef();
|
||||||
|
|
||||||
|
this.profileShow = this.profileShow.bind(this);
|
||||||
|
this.profileHide = this.profileHide.bind(this);
|
||||||
|
this.updateContainerOffset = this.updateContainerOffset.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
profileShow() {
|
||||||
|
this.updateContainerOffset();
|
||||||
|
this.setState({ clicked: true });
|
||||||
|
this.props.scrollWindow?.addEventListener('scroll', this.updateContainerOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
profileHide() {
|
||||||
|
this.setState({ clicked: false });
|
||||||
|
this.props.scrollWindow?.removeEventListener('scroll', this.updateContainerOffset, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContainerOffset() {
|
||||||
|
if (this.containerRef && this.containerRef.current) {
|
||||||
|
const container = this.containerRef.current;
|
||||||
|
const scrollWindow = this.props.scrollWindow;
|
||||||
|
|
||||||
|
const bottomSpace = scrollWindow
|
||||||
|
? scrollWindow.scrollHeight - container.offsetTop - scrollWindow.scrollTop
|
||||||
|
: 'auto';
|
||||||
|
const topSpace = scrollWindow
|
||||||
|
? scrollWindow.offsetHeight - bottomSpace - OVERLAY_HEIGHT
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
topSpace,
|
||||||
|
bottomSpace
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.scrollWindow?.removeEventListener('scroll', this.updateContainerOffset, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
ship,
|
||||||
|
contact,
|
||||||
|
color,
|
||||||
|
group,
|
||||||
|
hideAvatars,
|
||||||
|
hideNicknames,
|
||||||
|
history,
|
||||||
|
api,
|
||||||
|
sigilClass,
|
||||||
|
...rest
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { state } = this;
|
||||||
|
|
||||||
|
const img = (contact && (contact.avatar !== null) && !hideAvatars)
|
||||||
|
? <BaseImage display='inline-block' src={contact.avatar} height={16} width={16} />
|
||||||
|
: <Sigil
|
||||||
|
ship={ship}
|
||||||
|
size={16}
|
||||||
|
color={color}
|
||||||
|
classes={sigilClass}
|
||||||
|
icon
|
||||||
|
padded
|
||||||
|
/>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
cursor='pointer'
|
||||||
|
position='relative'
|
||||||
|
onClick={this.profileShow}
|
||||||
|
ref={this.containerRef}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{state.clicked && (
|
||||||
|
<ProfileOverlay
|
||||||
|
ship={ship}
|
||||||
|
contact={contact}
|
||||||
|
color={color}
|
||||||
|
topSpace={state.topSpace}
|
||||||
|
bottomSpace={state.bottomSpace}
|
||||||
|
group={group}
|
||||||
|
onDismiss={this.profileHide}
|
||||||
|
hideAvatars={hideAvatars}
|
||||||
|
hideNicknames={hideNicknames}
|
||||||
|
history={history}
|
||||||
|
api={api}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{img}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,32 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import { Contact, Group } from '~/types';
|
||||||
import { cite } from '~/logic/lib/util';
|
import { cite } from '~/logic/lib/util';
|
||||||
import { Sigil } from '~/logic/lib/sigil';
|
import { Sigil } from '~/logic/lib/sigil';
|
||||||
|
|
||||||
import { Box, Col, Button, Text, BaseImage } from '@tlon/indigo-react';
|
import { Box, Col, Button, Text, BaseImage, ColProps } from '@tlon/indigo-react';
|
||||||
|
|
||||||
export const OVERLAY_HEIGHT = 250;
|
export const OVERLAY_HEIGHT = 250;
|
||||||
|
|
||||||
export class ProfileOverlay extends PureComponent {
|
type ProfileOverlayProps = ColProps & {
|
||||||
constructor() {
|
ship: string;
|
||||||
super();
|
contact?: Contact;
|
||||||
|
color: string;
|
||||||
|
topSpace: number | 'auto';
|
||||||
|
bottomSpace: number | 'auto';
|
||||||
|
group?: Group;
|
||||||
|
onDismiss(): void;
|
||||||
|
hideAvatars: boolean;
|
||||||
|
hideNicknames: boolean;
|
||||||
|
history: any;
|
||||||
|
api: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
||||||
|
public popoverRef: React.Ref<typeof Col>;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
this.popoverRef = React.createRef();
|
this.popoverRef = React.createRef();
|
||||||
this.onDocumentClick = this.onDocumentClick.bind(this);
|
this.onDocumentClick = this.onDocumentClick.bind(this);
|
||||||
@ -35,7 +53,19 @@ export class ProfileOverlay extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { contact, ship, color, topSpace, bottomSpace, group, hideNicknames, hideAvatars, history } = this.props;
|
const {
|
||||||
|
contact,
|
||||||
|
ship,
|
||||||
|
color,
|
||||||
|
topSpace,
|
||||||
|
bottomSpace,
|
||||||
|
group = false,
|
||||||
|
hideNicknames,
|
||||||
|
hideAvatars,
|
||||||
|
history,
|
||||||
|
onDismiss,
|
||||||
|
...rest
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
let top, bottom;
|
let top, bottom;
|
||||||
if (topSpace < OVERLAY_HEIGHT / 2) {
|
if (topSpace < OVERLAY_HEIGHT / 2) {
|
||||||
@ -66,7 +96,7 @@ export class ProfileOverlay extends PureComponent {
|
|||||||
/* if (!group.hidden) {
|
/* if (!group.hidden) {
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
const isHidden = group.hidden;
|
const isHidden = group ? group.hidden : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col
|
<Col
|
||||||
@ -77,6 +107,7 @@ export class ProfileOverlay extends PureComponent {
|
|||||||
zIndex='3'
|
zIndex='3'
|
||||||
fontSize='0'
|
fontSize='0'
|
||||||
style={containerStyle}
|
style={containerStyle}
|
||||||
|
{...rest}
|
||||||
>
|
>
|
||||||
<Box height='160px' width='160px'>
|
<Box height='160px' width='160px'>
|
||||||
{img}
|
{img}
|
@ -1,12 +1,13 @@
|
|||||||
import React, { PureComponent, Fragment } from 'react';
|
import React, { PureComponent, Fragment } from 'react';
|
||||||
import { LocalUpdateRemoteContentPolicy } from "~/types/local-update";
|
import { LocalUpdateRemoteContentPolicy } from "~/types/local-update";
|
||||||
import { BaseAnchor, BaseImage, Box, Button } from '@tlon/indigo-react';
|
import { BaseAnchor, BaseImage, Box, Button, Text } from '@tlon/indigo-react';
|
||||||
import { hasProvider } from 'oembed-parser';
|
import { hasProvider } from 'oembed-parser';
|
||||||
import EmbedContainer from 'react-oembed-container';
|
import EmbedContainer from 'react-oembed-container';
|
||||||
import { memoize } from 'lodash';
|
import { memoize } from 'lodash';
|
||||||
|
|
||||||
interface RemoteContentProps {
|
interface RemoteContentProps {
|
||||||
url: string;
|
url: string;
|
||||||
|
text?: string;
|
||||||
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
||||||
unfold?: boolean;
|
unfold?: boolean;
|
||||||
renderUrl?: boolean;
|
renderUrl?: boolean;
|
||||||
@ -14,6 +15,7 @@ interface RemoteContentProps {
|
|||||||
audioProps?: any;
|
audioProps?: any;
|
||||||
videoProps?: any;
|
videoProps?: any;
|
||||||
oembedProps?: any;
|
oembedProps?: any;
|
||||||
|
textProps?: any;
|
||||||
style?: any;
|
style?: any;
|
||||||
onLoad?(): void;
|
onLoad?(): void;
|
||||||
}
|
}
|
||||||
@ -27,8 +29,6 @@ const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i);
|
|||||||
const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg)$/i);
|
const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg)$/i);
|
||||||
const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i);
|
const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i);
|
||||||
|
|
||||||
const memoizedFetch = memoize(fetch);
|
|
||||||
|
|
||||||
export default class RemoteContent extends PureComponent<RemoteContentProps, RemoteContentState> {
|
export default class RemoteContent extends PureComponent<RemoteContentProps, RemoteContentState> {
|
||||||
private fetchController: AbortController | undefined;
|
private fetchController: AbortController | undefined;
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -48,7 +48,8 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unfoldEmbed() {
|
unfoldEmbed(event: Event) {
|
||||||
|
event.stopPropagation();
|
||||||
let unfoldState = this.state.unfold;
|
let unfoldState = this.state.unfold;
|
||||||
unfoldState = !unfoldState;
|
unfoldState = !unfoldState;
|
||||||
this.setState({ unfold: unfoldState });
|
this.setState({ unfold: unfoldState });
|
||||||
@ -57,7 +58,7 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
|||||||
|
|
||||||
loadOembed() {
|
loadOembed() {
|
||||||
this.fetchController = new AbortController();
|
this.fetchController = new AbortController();
|
||||||
memoizedFetch(`https://noembed.com/embed?url=${this.props.url}`, {
|
fetch(`https://noembed.com/embed?url=${this.props.url}`, {
|
||||||
signal: this.fetchController.signal
|
signal: this.fetchController.signal
|
||||||
})
|
})
|
||||||
.then(response => response.clone().json())
|
.then(response => response.clone().json())
|
||||||
@ -70,11 +71,13 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
|||||||
}
|
}
|
||||||
|
|
||||||
wrapInLink(contents) {
|
wrapInLink(contents) {
|
||||||
|
const { style } = this.props;
|
||||||
return (<BaseAnchor
|
return (<BaseAnchor
|
||||||
href={this.props.url}
|
href={this.props.url}
|
||||||
style={{ color: 'inherit', textDecoration: 'none' }}
|
style={{ color: 'inherit', textDecoration: 'none', ...style }}
|
||||||
className={`word-break-all ${(typeof contents === 'string') ? 'bb' : ''}`}
|
className={`word-break-all ${(typeof contents === 'string') ? 'bb' : ''}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
width="100%"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{contents}
|
{contents}
|
||||||
@ -85,12 +88,14 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
|||||||
const {
|
const {
|
||||||
remoteContentPolicy,
|
remoteContentPolicy,
|
||||||
url,
|
url,
|
||||||
|
text,
|
||||||
unfold = false,
|
unfold = false,
|
||||||
renderUrl = true,
|
renderUrl = true,
|
||||||
imageProps = {},
|
imageProps = {},
|
||||||
audioProps = {},
|
audioProps = {},
|
||||||
videoProps = {},
|
videoProps = {},
|
||||||
oembedProps = {},
|
oembedProps = {},
|
||||||
|
textProps = {},
|
||||||
style = {},
|
style = {},
|
||||||
onLoad = () => {},
|
onLoad = () => {},
|
||||||
...props
|
...props
|
||||||
@ -113,7 +118,9 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
|||||||
} else if (isAudio && remoteContentPolicy.audioShown) {
|
} else if (isAudio && remoteContentPolicy.audioShown) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderUrl ? this.wrapInLink(url) : null}
|
{renderUrl
|
||||||
|
? this.wrapInLink(<Text {...textProps}>{text || url}</Text>)
|
||||||
|
: null}
|
||||||
<audio
|
<audio
|
||||||
controls
|
controls
|
||||||
className="db"
|
className="db"
|
||||||
@ -127,7 +134,9 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
|||||||
} else if (isVideo && remoteContentPolicy.videoShown) {
|
} else if (isVideo && remoteContentPolicy.videoShown) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderUrl ? this.wrapInLink(url) : null}
|
{renderUrl
|
||||||
|
? this.wrapInLink(<Text {...textProps}>{text || url}</Text>)
|
||||||
|
: null}
|
||||||
<video
|
<video
|
||||||
controls
|
controls
|
||||||
className="db"
|
className="db"
|
||||||
@ -146,7 +155,11 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{renderUrl ? this.wrapInLink(this.state.embed && this.state.embed.title ? this.state.embed.title : url) : null}
|
{renderUrl
|
||||||
|
? this.wrapInLink(<Text {...textProps}>{(this.state.embed && this.state.embed.title)
|
||||||
|
? this.state.embed.title
|
||||||
|
: (text || url)}</Text>)
|
||||||
|
: null}
|
||||||
{this.state.embed !== 'error' && this.state.embed?.html && !unfold ? <Button
|
{this.state.embed !== 'error' && this.state.embed?.html && !unfold ? <Button
|
||||||
display='inline-flex'
|
display='inline-flex'
|
||||||
border={1}
|
border={1}
|
||||||
@ -176,7 +189,9 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return renderUrl ? this.wrapInLink(url) : null;
|
return renderUrl
|
||||||
|
? this.wrapInLink(<Text {...textProps}>{text || url}</Text>)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo, useCallback } from "react";
|
import React, { useMemo, useCallback, ChangeEvent, useState, SyntheticEvent, useEffect } from "react";
|
||||||
import { Box, Label, Icon, Text, Row, Col } from "@tlon/indigo-react";
|
import { Box, Label, Icon, Text, Row, Col, ErrorLabel } from "@tlon/indigo-react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import ob from "urbit-ob";
|
import ob from "urbit-ob";
|
||||||
import { useField } from "formik";
|
import { useField } from "formik";
|
||||||
@ -11,6 +11,8 @@ import { cite, deSig } from "~/logic/lib/util";
|
|||||||
import { Rolodex, Groups } from "~/types";
|
import { Rolodex, Groups } from "~/types";
|
||||||
import { HoverBox } from "./HoverBox";
|
import { HoverBox } from "./HoverBox";
|
||||||
|
|
||||||
|
const INVALID_SHIP_ERR = "Invalid ship";
|
||||||
|
|
||||||
interface InviteSearchProps {
|
interface InviteSearchProps {
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@ -45,25 +47,67 @@ const Candidate = ({ title, detail, selected, onClick }) => (
|
|||||||
|
|
||||||
export function ShipSearch(props: InviteSearchProps) {
|
export function ShipSearch(props: InviteSearchProps) {
|
||||||
const { id, label, caption } = props;
|
const { id, label, caption } = props;
|
||||||
const [{ value }, { error }, { setValue, setTouched }] = useField<string[]>(
|
const [{}, meta, { setValue, setTouched, setError: _setError }] = useField<string[]>({
|
||||||
props.id
|
name: id,
|
||||||
|
multiple: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const setError = _setError as unknown as (s: string | undefined) => void;
|
||||||
|
|
||||||
|
const { error, touched } = meta;
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState([] as string[]);
|
||||||
|
const [inputShip, setInputShip] = useState(undefined as string | undefined);
|
||||||
|
const [inputTouched, setInputTouched] = useState(false);
|
||||||
|
|
||||||
|
const checkInput = useCallback((valid: boolean, ship: string | undefined) => {
|
||||||
|
if(valid) {
|
||||||
|
setInputShip(ship);
|
||||||
|
setError(error === INVALID_SHIP_ERR ? undefined : error);
|
||||||
|
} else {
|
||||||
|
setError(INVALID_SHIP_ERR);
|
||||||
|
setInputTouched(false);
|
||||||
|
}
|
||||||
|
}, [setError, error, setInputTouched, setInputShip]);
|
||||||
|
|
||||||
|
const onChange = useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
let ship = `~${deSig(e.target.value) || ""}`;
|
||||||
|
if(ob.isValidPatp(ship)) {
|
||||||
|
checkInput(true, ship);
|
||||||
|
} else {
|
||||||
|
checkInput(ship.length !== 1, undefined)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[checkInput]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onBlur = useCallback(() => {
|
||||||
|
setInputTouched(true);
|
||||||
|
}, [setInputTouched]);
|
||||||
|
|
||||||
const onSelect = useCallback(
|
const onSelect = useCallback(
|
||||||
(s: string) => {
|
(s: string) => {
|
||||||
setTouched(true);
|
setTouched(true);
|
||||||
setValue([...value, s]);
|
checkInput(true, undefined);
|
||||||
|
s = `~${deSig(s)}`;
|
||||||
|
setSelected(v => _.uniq([...v, s]))
|
||||||
},
|
},
|
||||||
[setValue, value]
|
[setTouched, checkInput, setSelected]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onRemove = useCallback(
|
const onRemove = useCallback(
|
||||||
(s: string) => {
|
(s: string) => {
|
||||||
setValue(value.filter((v) => v !== s));
|
setSelected(ships => ships.filter(ship => ship !== s))
|
||||||
},
|
},
|
||||||
[setValue, value]
|
[setSelected]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const newValue = inputShip ? [...selected, inputShip] : selected;
|
||||||
|
setValue(newValue);
|
||||||
|
}, [inputShip, selected])
|
||||||
|
|
||||||
const [peers, nicknames] = useMemo(() => {
|
const [peers, nicknames] = useMemo(() => {
|
||||||
const peerSet = new Set<string>();
|
const peerSet = new Set<string>();
|
||||||
const contacts = new Map<string, string[]>();
|
const contacts = new Map<string, string[]>();
|
||||||
@ -125,20 +169,22 @@ export function ShipSearch(props: InviteSearchProps) {
|
|||||||
isExact={(s) => {
|
isExact={(s) => {
|
||||||
const ship = `~${deSig(s)}`;
|
const ship = `~${deSig(s)}`;
|
||||||
const result = ob.isValidPatp(ship);
|
const result = ob.isValidPatp(ship);
|
||||||
return result ? deSig(s) : 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 ? value.length >= props.maxLength : false}
|
disabled={props.maxLength ? selected.length >= props.maxLength : false}
|
||||||
search={(s: string, t: string) =>
|
search={(s: string, t: string) =>
|
||||||
t.toLowerCase().startsWith(s.toLowerCase())
|
t.toLowerCase().startsWith(s.toLowerCase())
|
||||||
}
|
}
|
||||||
getKey={(s: string) => s}
|
getKey={(s: string) => s}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
/>
|
/>
|
||||||
<Row minHeight="34px" flexWrap="wrap">
|
<Row minHeight="34px" flexWrap="wrap">
|
||||||
{value.map((s) => (
|
{selected.map((s) => (
|
||||||
<Row
|
<Row
|
||||||
fontFamily="mono"
|
fontFamily="mono"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
@ -161,6 +207,11 @@ export function ShipSearch(props: InviteSearchProps) {
|
|||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
|
<ErrorLabel
|
||||||
|
mt="3"
|
||||||
|
hasError={error === INVALID_SHIP_ERR ? inputTouched : !!(touched && error)}>
|
||||||
|
{error}
|
||||||
|
</ErrorLabel>
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export class OmniboxResult extends Component {
|
|||||||
} 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 />;
|
graphic = <Sigil color={sigilFill} classes='dib flex-shrink-0 v-mid mr2' ship={window.ship} size={16} icon padded />;
|
||||||
} else if (icon === 'home') {
|
} else if (icon === 'home') {
|
||||||
graphic = <Icon display='inline-block' verticalAlign='middle' icon='Boot' mr='2' size='16px' color={iconFill} />;
|
graphic = <Icon display='inline-block' verticalAlign='middle' icon='Mail' mr='2' size='16px' 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='16px' color={iconFill} />;
|
||||||
} else {
|
} else {
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Text,
|
Text,
|
||||||
Row,
|
Row,
|
||||||
|
BaseImage
|
||||||
} from "@tlon/indigo-react";
|
} from "@tlon/indigo-react";
|
||||||
import { Formik, FormikHelpers } from "formik";
|
import { Formik, FormikHelpers } from "formik";
|
||||||
import { Contact } from "~/types/contact-update";
|
import { Contact } from "~/types/contact-update";
|
||||||
@ -25,6 +26,8 @@ interface ContactCardProps {
|
|||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
s3: S3State;
|
s3: S3State;
|
||||||
rootIdentity: Contact;
|
rootIdentity: Contact;
|
||||||
|
hideAvatars: boolean;
|
||||||
|
hideNicknames: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formSchema = Yup.object({
|
const formSchema = Yup.object({
|
||||||
@ -71,12 +74,15 @@ const emptyContact = {
|
|||||||
export function ContactCard(props: ContactCardProps) {
|
export function ContactCard(props: ContactCardProps) {
|
||||||
const us = `~${window.ship}`;
|
const us = `~${window.ship}`;
|
||||||
const { contact, rootIdentity } = props;
|
const { contact, rootIdentity } = props;
|
||||||
const onSubmit = async (values: Contact, actions: FormikHelpers<Contact>) => {
|
const onSubmit = async (values: any, actions: FormikHelpers<Contact>) => {
|
||||||
try {
|
try {
|
||||||
if(!contact) {
|
if(!contact) {
|
||||||
const [,,ship] = props.path.split('/');
|
const [,,ship] = props.path.split('/');
|
||||||
values.color = uxToHex(values.color);
|
values.color = uxToHex(values.color);
|
||||||
await props.api.contacts.share(ship, props.path, us, values)
|
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 });
|
actions.setStatus({ success: null });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -108,6 +114,11 @@ export function ContactCard(props: ContactCardProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : "#000000";
|
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : "#000000";
|
||||||
|
const image = (!props?.hideAvatars && contact?.avatar)
|
||||||
|
? <BaseImage src={contact.avatar} width='100%' height='100%' style={{ objectFit: 'cover' }} />
|
||||||
|
: <Sigil ship={us} size={32} color={hexColor} />;
|
||||||
|
|
||||||
|
const nickname = (!props.hideNicknames && contact?.nickname) ? contact.nickname : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box p={4} height="100%" overflowY="auto">
|
<Box p={4} height="100%" overflowY="auto">
|
||||||
@ -129,9 +140,11 @@ export function ContactCard(props: ContactCardProps) {
|
|||||||
pb={3}
|
pb={3}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Sigil size={32} classes="" color={hexColor} ship={us} />
|
<Box height='32px' width='32px'>
|
||||||
|
{image}
|
||||||
|
</Box>
|
||||||
<Box ml={2}>
|
<Box ml={2}>
|
||||||
<Text fontFamily="mono">{us}</Text>
|
<Text mono={!Boolean(nickname)}>{nickname}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Row>
|
</Row>
|
||||||
<ImageInput id="avatar" label="Avatar" s3={props.s3} />
|
<ImageInput id="avatar" label="Avatar" s3={props.s3} />
|
||||||
|
@ -69,7 +69,7 @@ export const Content = (props) => {
|
|||||||
<Notifications {...props} />
|
<Notifications {...props} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<GraphApp {...props} />
|
<GraphApp path="/~graph" {...props} />
|
||||||
<Route
|
<Route
|
||||||
render={p => (
|
render={p => (
|
||||||
<ErrorComponent
|
<ErrorComponent
|
||||||
|
@ -34,10 +34,13 @@ function DeleteGroup(props: {
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const onDelete = async () => {
|
const onDelete = async () => {
|
||||||
const name = props.association['group-path'].split('/').pop();
|
const name = props.association['group-path'].split('/').pop();
|
||||||
if (prompt(`To confirm deleting this group, type ${name}`) === name) {
|
if (props.owner) {
|
||||||
await props.api.contacts.delete(props.association["group-path"]);
|
const shouldDelete = (prompt(`To confirm deleting this group, type ${name}`) === name);
|
||||||
history.push("/");
|
if (!shouldDelete) return;
|
||||||
}
|
}
|
||||||
|
const resource = resourceFromPath(props.association["group-path"])
|
||||||
|
await props.api.groups.removeGroup(resource);
|
||||||
|
history.push("/");
|
||||||
};
|
};
|
||||||
|
|
||||||
const action = props.owner ? "Delete" : "Leave";
|
const action = props.owner ? "Delete" : "Leave";
|
||||||
|
@ -118,7 +118,7 @@ export function GroupSwitcher(props: {
|
|||||||
mr={2}
|
mr={2}
|
||||||
color="gray"
|
color="gray"
|
||||||
display="block"
|
display="block"
|
||||||
icon="Boot"
|
icon="Mail"
|
||||||
/>
|
/>
|
||||||
<Text>DMs + Drafts</Text>
|
<Text>DMs + Drafts</Text>
|
||||||
</GroupSwitcherItem>}
|
</GroupSwitcherItem>}
|
||||||
@ -127,11 +127,11 @@ export function GroupSwitcher(props: {
|
|||||||
associations={props.associations}
|
associations={props.associations}
|
||||||
/>
|
/>
|
||||||
<GroupSwitcherItem to="/~landscape/new">
|
<GroupSwitcherItem to="/~landscape/new">
|
||||||
<Icon mr="2" color="gray" icon="Plus" />
|
<Icon mr="2" color="gray" icon="CreateGroup" />
|
||||||
<Text> New Group</Text>
|
<Text> New Group</Text>
|
||||||
</GroupSwitcherItem>
|
</GroupSwitcherItem>
|
||||||
<GroupSwitcherItem to="/~landscape/join">
|
<GroupSwitcherItem to="/~landscape/join">
|
||||||
<Icon mr="2" color="gray" icon="Boot" />
|
<Icon mr="2" color="gray" icon="Plus" />
|
||||||
<Text> Join Group</Text>
|
<Text> Join Group</Text>
|
||||||
</GroupSwitcherItem>
|
</GroupSwitcherItem>
|
||||||
{workspace.type === "group" && (
|
{workspace.type === "group" && (
|
||||||
|
@ -173,6 +173,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
{...routeProps}
|
{...routeProps}
|
||||||
api={api}
|
api={api}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
|
chatSynced={props.chatSynced}
|
||||||
associations={associations}
|
associations={associations}
|
||||||
groups={groups}
|
groups={groups}
|
||||||
group={groupPath}
|
group={groupPath}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import React, { useCallback, useRef, useMemo } from "react";
|
import React, { useCallback, useRef, useMemo } from "react";
|
||||||
import { Box, Text, Col, Button, Row } from "@tlon/indigo-react";
|
import { Switch, Route, useHistory } from "react-router-dom";
|
||||||
|
import { Formik, Form } from "formik";
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
import { Box, Text, Col, Button, Row } from "@tlon/indigo-react";
|
||||||
|
|
||||||
import { ShipSearch } from "~/views/components/ShipSearch";
|
import { ShipSearch } from "~/views/components/ShipSearch";
|
||||||
import { Association } from "~/types/metadata-update";
|
import { Association } from "~/types/metadata-update";
|
||||||
import { Switch, Route, useHistory } from "react-router-dom";
|
|
||||||
import { Formik, Form } from "formik";
|
|
||||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||||
import { useOutsideClick } from "~/logic/lib/useOutsideClick";
|
import { useOutsideClick } from "~/logic/lib/useOutsideClick";
|
||||||
import { FormError } from "~/views/components/FormError";
|
import { FormError } from "~/views/components/FormError";
|
||||||
import { resourceFromPath } from "~/logic/lib/group";
|
import { resourceFromPath } from "~/logic/lib/group";
|
||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { Groups, Rolodex, Workspace } from "~/types";
|
import { Groups, Rolodex, Workspace } from "~/types";
|
||||||
import { ChipInput } from "~/views/components/ChipInput";
|
import { deSig } from "~/logic/lib/util";
|
||||||
|
|
||||||
interface InvitePopoverProps {
|
interface InvitePopoverProps {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@ -30,7 +30,7 @@ interface FormSchema {
|
|||||||
|
|
||||||
const formSchema = Yup.object({
|
const formSchema = Yup.object({
|
||||||
emails: Yup.array(Yup.string().email("Invalid email")),
|
emails: Yup.array(Yup.string().email("Invalid email")),
|
||||||
ships: Yup.array(Yup.string())
|
ships: Yup.array(Yup.string()).min(1, "Must invite at least one ship")
|
||||||
});
|
});
|
||||||
|
|
||||||
export function InvitePopover(props: InvitePopoverProps) {
|
export function InvitePopover(props: InvitePopoverProps) {
|
||||||
@ -48,14 +48,14 @@ export function InvitePopover(props: InvitePopoverProps) {
|
|||||||
|
|
||||||
const onSubmit = async ({ ships, emails }: { ships: string[] }, actions) => {
|
const onSubmit = async ({ ships, emails }: { ships: string[] }, actions) => {
|
||||||
if(props.workspace.type === 'home') {
|
if(props.workspace.type === 'home') {
|
||||||
history.push(`/~landscape/dm/${ships[0]}`);
|
history.push(`/~landscape/dm/${deSig(ships[0])}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: how to invite via email?
|
// TODO: how to invite via email?
|
||||||
try {
|
try {
|
||||||
const resource = resourceFromPath(association["group-path"]);
|
const resource = resourceFromPath(association["group-path"]);
|
||||||
await ships.reduce(
|
await ships.reduce(
|
||||||
(acc, s) => acc.then(() => api.contacts.invite(resource, `~${s}`)),
|
(acc, s) => acc.then(() => api.contacts.invite(resource, `~${deSig(s)}`)),
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
);
|
);
|
||||||
actions.setStatus({ success: null });
|
actions.setStatus({ success: null });
|
||||||
@ -90,16 +90,19 @@ export function InvitePopover(props: InvitePopoverProps) {
|
|||||||
borderColor="washedGray"
|
borderColor="washedGray"
|
||||||
borderRadius={1}
|
borderRadius={1}
|
||||||
maxHeight="472px"
|
maxHeight="472px"
|
||||||
width="380px"
|
width="100%"
|
||||||
|
maxWidth="380px"
|
||||||
|
mx={[4,0]}
|
||||||
bg="white"
|
bg="white"
|
||||||
>
|
>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
validationSchema={formSchema}
|
validationSchema={formSchema}
|
||||||
|
validateOnBlur
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<Col gapY="3" p={3}>
|
<Col gapY="3" pt={3} px={3}>
|
||||||
<Box>
|
<Box>
|
||||||
<Text>Invite to </Text>
|
<Text>Invite to </Text>
|
||||||
<Text fontWeight="800">{title || "DM"}</Text>
|
<Text fontWeight="800">{title || "DM"}</Text>
|
||||||
@ -122,13 +125,12 @@ export function InvitePopover(props: InvitePopoverProps) {
|
|||||||
/> */}
|
/> */}
|
||||||
</Col>
|
</Col>
|
||||||
<Row
|
<Row
|
||||||
borderTop={1}
|
|
||||||
borderTopColor="washedGray"
|
|
||||||
justifyContent="flex-end"
|
justifyContent="flex-end"
|
||||||
|
p={3}
|
||||||
>
|
>
|
||||||
<AsyncButton
|
<AsyncButton
|
||||||
border={0}
|
border={0}
|
||||||
color="blue"
|
primary
|
||||||
loadingText="Inviting..."
|
loadingText="Inviting..."
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
|
@ -79,8 +79,8 @@ export function JoinGroup(props: JoinGroupProps & RouteComponentProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Body>
|
<>
|
||||||
<Col maxWidth="300px" overflowY="auto" p="3">
|
<Col overflowY="auto" p="3">
|
||||||
<Box mb={3}>
|
<Box mb={3}>
|
||||||
<Text fontWeight="bold">Join Group</Text>
|
<Text fontWeight="bold">Join Group</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@ -103,6 +103,6 @@ export function JoinGroup(props: JoinGroupProps & RouteComponentProps) {
|
|||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
</Col>
|
</Col>
|
||||||
</Body>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,174 +1,221 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
ManagedTextInputField as Input,
|
ManagedTextInputField as Input,
|
||||||
Col,
|
Col,
|
||||||
ManagedRadioButtonField as Radio,
|
ManagedRadioButtonField as Radio,
|
||||||
Text,
|
Text,
|
||||||
} from "@tlon/indigo-react";
|
Icon,
|
||||||
import { Formik, Form } from "formik";
|
Row
|
||||||
import * as Yup from "yup";
|
} from '@tlon/indigo-react';
|
||||||
import GlobalApi from "~/logic/api/global";
|
import { Formik, Form } from 'formik';
|
||||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
import * as Yup from 'yup';
|
||||||
import { FormError } from "~/views/components/FormError";
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
import { AsyncButton } from '~/views/components/AsyncButton';
|
||||||
import { stringToSymbol, parentPath } from "~/logic/lib/util";
|
import { FormError } from '~/views/components/FormError';
|
||||||
import GroupSearch from "~/views/components/GroupSearch";
|
import { RouteComponentProps } from 'react-router-dom';
|
||||||
import { Associations } from "~/types/metadata-update";
|
import { stringToSymbol, parentPath } from '~/logic/lib/util';
|
||||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
import { resourceFromPath } from '~/logic/lib/group';
|
||||||
import { Groups } from "~/types/group-update";
|
import { Associations } from '~/types/metadata-update';
|
||||||
import { ShipSearch } from "~/views/components/ShipSearch";
|
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
||||||
import { Rolodex, Workspace } from "~/types";
|
import { Groups } from '~/types/group-update';
|
||||||
|
import { ShipSearch } from '~/views/components/ShipSearch';
|
||||||
|
import { Rolodex, Workspace } from '~/types';
|
||||||
|
|
||||||
interface FormSchema {
|
interface FormSchema {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
ships: string[];
|
ships: string[];
|
||||||
moduleType: "chat" | "publish" | "link";
|
moduleType: 'chat' | 'publish' | 'link';
|
||||||
|
writers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const formSchema = Yup.object({
|
const formSchema = (group, groups) => Yup.object({
|
||||||
name: Yup.string().required('Channel must have a name'),
|
name: Yup.string().required('Channel must have a name'),
|
||||||
description: Yup.string(),
|
description: Yup.string(),
|
||||||
ships: Yup.array(Yup.string()),
|
ships: Yup.array(Yup.string()),
|
||||||
moduleType: Yup.string().required('Must choose channel type')
|
moduleType: Yup.string().required('Must choose channel type'),
|
||||||
|
writers: Yup.array(Yup.string().test('ingroup', 'Writers must be in group',
|
||||||
|
value => groups?.[group]?.members?.has(value)))
|
||||||
});
|
});
|
||||||
|
|
||||||
interface NewChannelProps {
|
interface NewChannelProps {
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
associations: Associations;
|
associations: Associations;
|
||||||
contacts: Rolodex;
|
contacts: Rolodex;
|
||||||
|
chatSynced: any;
|
||||||
groups: Groups;
|
groups: Groups;
|
||||||
group?: string;
|
group?: string;
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NewChannel(props: NewChannelProps & RouteComponentProps) {
|
export function NewChannel(props: NewChannelProps & RouteComponentProps) {
|
||||||
const { history, api, group, workspace } = props;
|
const { history, api, group, workspace, groups } = props;
|
||||||
|
|
||||||
const waiter = useWaitForProps(props, 5000);
|
const waiter = useWaitForProps(props, 5000);
|
||||||
|
|
||||||
const onSubmit = async (values: FormSchema, actions) => {
|
const onSubmit = async (values: FormSchema, actions) => {
|
||||||
const resId: string = stringToSymbol(values.name);
|
const resId: string = stringToSymbol(values.name)
|
||||||
|
+ ((workspace?.type !== 'home') ? `-${Math.floor(Math.random() * 10000)}`
|
||||||
|
: '');
|
||||||
try {
|
try {
|
||||||
const { name, description, moduleType, ships } = values;
|
const { name, description, moduleType, ships, writers } = values;
|
||||||
switch (moduleType) {
|
switch (moduleType) {
|
||||||
case 'chat':
|
case 'chat':
|
||||||
const appPath = `/~${window.ship}/${resId}`;
|
const appPath = `/~${window.ship}/${resId}`;
|
||||||
const groupPath = group || `/ship${appPath}`;
|
const groupPath = group || `/ship${appPath}`;
|
||||||
|
|
||||||
await api.chat.create(
|
await api.chat.create(
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
appPath,
|
appPath,
|
||||||
groupPath,
|
groupPath,
|
||||||
{ invite: { pending: ships.map(s => `~${s}`) } },
|
{ invite: { pending: ships.map(s => `~${s}`) } },
|
||||||
ships.map(s => `~${s}`),
|
ships.map(s => `~${s}`),
|
||||||
true,
|
true,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "publish":
|
case 'publish':
|
||||||
case "link":
|
if (writers.length > 0) {
|
||||||
if (group) {
|
const resource = resourceFromPath(group);
|
||||||
await api.graph.createManagedGraph(
|
await api.groups.addTag(
|
||||||
resId,
|
resource,
|
||||||
name,
|
{ app: 'publish', tag: `writers-${resId}` },
|
||||||
description,
|
writers.map(s => `~${s}`)
|
||||||
group,
|
);
|
||||||
moduleType
|
}
|
||||||
);
|
case 'link':
|
||||||
} else {
|
if (group) {
|
||||||
await api.graph.createUnmanagedGraph(
|
await api.graph.createManagedGraph(
|
||||||
resId,
|
resId,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
{ invite: { pending: ships.map((s) => `~${s}`) } },
|
group,
|
||||||
moduleType
|
moduleType
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
break;
|
await api.graph.createUnmanagedGraph(
|
||||||
default:
|
resId,
|
||||||
console.log('fallthrough');
|
name,
|
||||||
}
|
description,
|
||||||
|
{ invite: { pending: ships.map(s => `~${s}`) } },
|
||||||
|
moduleType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('fallthrough');
|
||||||
|
}
|
||||||
|
|
||||||
if (!group) {
|
if (!group) {
|
||||||
await waiter(p => Boolean(p?.groups?.[`/ship/~${window.ship}/${resId}`]));
|
await waiter(p => Boolean(p?.groups?.[`/ship/~${window.ship}/${resId}`]));
|
||||||
}
|
}
|
||||||
actions.setStatus({ success: null });
|
if (moduleType === 'chat') {
|
||||||
const resourceUrl = parentPath(location.pathname);
|
await waiter(p => Boolean(p?.chatSynced?.[`/~${window.ship}/${resId}`]));
|
||||||
history.push(
|
}
|
||||||
`${resourceUrl}/resource/${moduleType}` +
|
actions.setStatus({ success: null });
|
||||||
`${moduleType !== 'chat' ? '/ship' : ''}/~${window.ship}/${resId}`
|
const resourceUrl = parentPath(location.pathname);
|
||||||
);
|
history.push(
|
||||||
} catch (e) {
|
`${resourceUrl}/resource/${moduleType}` +
|
||||||
console.error(e);
|
`${moduleType !== 'chat' ? '/ship' : ''}/~${window.ship}/${resId}`
|
||||||
actions.setStatus({ error: 'Channel creation failed' });
|
);
|
||||||
}
|
} catch (e) {
|
||||||
};
|
console.error(e);
|
||||||
return (
|
actions.setStatus({ error: 'Channel creation failed' });
|
||||||
<Col overflowY="auto" p={3}>
|
}
|
||||||
<Box pb='3' display={['block', 'none']} onClick={() => history.push(props.baseUrl)}>
|
};
|
||||||
{'<- Back'}
|
return (
|
||||||
</Box>
|
<Col overflowY="auto" p={3}>
|
||||||
<Box fontWeight="bold" mb={4} color="black">
|
<Box pb='3' display={['block', 'none']} onClick={() => history.push(props.baseUrl)}>
|
||||||
New Channel
|
{'<- Back'}
|
||||||
</Box>
|
</Box>
|
||||||
<Formik
|
<Box fontWeight="bold" mb={4} color="black">
|
||||||
validationSchema={formSchema}
|
New Channel
|
||||||
initialValues={{
|
</Box>
|
||||||
moduleType: 'chat',
|
<Formik
|
||||||
name: '',
|
validationSchema={formSchema(group, groups)}
|
||||||
description: '',
|
initialValues={{
|
||||||
group: '',
|
moduleType: 'chat',
|
||||||
ships: []
|
name: '',
|
||||||
}}
|
description: '',
|
||||||
onSubmit={onSubmit}
|
group: '',
|
||||||
>
|
ships: [],
|
||||||
<Form>
|
writers: []
|
||||||
<Col
|
}}
|
||||||
maxWidth="348px"
|
onSubmit={onSubmit}
|
||||||
gapY="4"
|
>
|
||||||
>
|
{ ({ errors, values }) => <Form>
|
||||||
<Col gapY="2">
|
<Col
|
||||||
<Box color="black" mb={2}>Channel Type</Box>
|
maxWidth="348px"
|
||||||
<Radio label="Chat" id="chat" name="moduleType" />
|
gapY="4"
|
||||||
<Radio label="Notebook" id="publish" name="moduleType" />
|
>
|
||||||
<Radio label="Collection" id="link" name="moduleType" />
|
<Col gapY="2">
|
||||||
</Col>
|
<Box color="black" mb={2}>Channel Type</Box>
|
||||||
<Input
|
<Radio label="Chat" id="chat" name="moduleType" />
|
||||||
id="name"
|
<Radio label="Notebook" id="publish" name="moduleType" />
|
||||||
label="Name"
|
<Radio label="Collection" id="link" name="moduleType" />
|
||||||
caption="Provide a name for your channel"
|
</Col>
|
||||||
placeholder="eg. My Channel"
|
<Input
|
||||||
/>
|
id="name"
|
||||||
<Input
|
label="Name"
|
||||||
id="description"
|
caption="Provide a name for your channel"
|
||||||
label="Description"
|
placeholder="eg. My Channel"
|
||||||
caption="What's your channel about?"
|
/>
|
||||||
placeholder="Channel description"
|
<Input
|
||||||
/>
|
id="description"
|
||||||
{(workspace?.type === 'home') &&
|
label="Description"
|
||||||
<ShipSearch
|
caption="What's your channel about?"
|
||||||
groups={props.groups}
|
placeholder="Channel description"
|
||||||
contacts={props.contacts}
|
/>
|
||||||
id="ships"
|
{(workspace?.type === 'home') &&
|
||||||
label="Invitees"
|
<ShipSearch
|
||||||
/>}
|
groups={props.groups}
|
||||||
<Box justifySelf="start">
|
contacts={props.contacts}
|
||||||
<AsyncButton
|
id="ships"
|
||||||
primary
|
label="Invitees"
|
||||||
loadingText="Creating..."
|
/>}
|
||||||
type="submit"
|
{(workspace?.type !== 'home' && values.moduleType === 'publish') &&
|
||||||
border
|
<>
|
||||||
>
|
<ShipSearch
|
||||||
Create Channel
|
groups={props.groups}
|
||||||
</AsyncButton>
|
contacts={props.contacts}
|
||||||
</Box>
|
caption="Add writers to restrict who can write to this
|
||||||
<FormError message="Channel creation failed" />
|
notebook, or leave blank to allow all group members to write"
|
||||||
</Col>
|
id="writers"
|
||||||
</Form>
|
label="Writers"
|
||||||
</Formik>
|
/>
|
||||||
</Col>
|
{errors.writers &&
|
||||||
);
|
<>
|
||||||
}
|
<Row>
|
||||||
|
<Icon
|
||||||
|
color='white'
|
||||||
|
mr='2'
|
||||||
|
backgroundColor='red'
|
||||||
|
borderRadius='999px'
|
||||||
|
icon="ExclaimationMarkBold"
|
||||||
|
/>
|
||||||
|
<Text color='red'>
|
||||||
|
{Array.from(new Set([...errors.writers]))}
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</>}
|
||||||
|
<Box justifySelf="start">
|
||||||
|
<AsyncButton
|
||||||
|
primary
|
||||||
|
loadingText="Creating..."
|
||||||
|
type="submit"
|
||||||
|
border
|
||||||
|
>
|
||||||
|
Create Channel
|
||||||
|
</AsyncButton>
|
||||||
|
</Box>
|
||||||
|
<FormError message="Channel creation failed" />
|
||||||
|
</Col>
|
||||||
|
</Form>}
|
||||||
|
</Formik>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -78,8 +78,8 @@ export function NewGroup(props: NewGroupProps & RouteComponentProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Body>
|
<>
|
||||||
<Col maxWidth="300px" overflowY="auto" p="3">
|
<Col overflowY="auto" p="3">
|
||||||
<Box mb={3}>
|
<Box mb={3}>
|
||||||
<Text fontWeight="bold">New Group</Text>
|
<Text fontWeight="bold">New Group</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@ -112,6 +112,6 @@ export function NewGroup(props: NewGroupProps & RouteComponentProps) {
|
|||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
</Col>
|
</Col>
|
||||||
</Body>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -347,20 +347,20 @@ function Participant(props: {
|
|||||||
</Action>
|
</Action>
|
||||||
{props.role === 'admin' && (
|
{props.role === 'admin' && (
|
||||||
<>
|
<>
|
||||||
{!isInvite && (
|
{(!isInvite && contact.patp !== window.ship) && (
|
||||||
<StatelessAsyncAction onClick={onBan} bg="transparent">
|
<StatelessAsyncAction onClick={onBan} bg="transparent">
|
||||||
<Text color="red">Ban from {title}</Text>
|
<Text color="red">Ban from {title}</Text>
|
||||||
</StatelessAsyncAction>
|
</StatelessAsyncAction>
|
||||||
)}
|
)}
|
||||||
{role === 'admin' ? (
|
{role === 'admin' ? (
|
||||||
<StatelessAsyncAction onClick={onDemote} bg="transparent">
|
group?.tags?.role?.admin?.size > 1 && (<StatelessAsyncAction onClick={onDemote} bg="transparent">
|
||||||
Demote from Admin
|
Demote from Admin
|
||||||
</StatelessAsyncAction>
|
</StatelessAsyncAction>)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<StatelessAsyncAction onClick={onKick} bg="transparent">
|
{(contact.patp !== window.ship) && (<StatelessAsyncAction onClick={onKick} bg="transparent">
|
||||||
<Text color="red">Kick from {title}</Text>
|
<Text color="red">Kick from {title}</Text>
|
||||||
</StatelessAsyncAction>
|
</StatelessAsyncAction>)}
|
||||||
<StatelessAsyncAction onClick={onPromote} bg="transparent">
|
<StatelessAsyncAction onClick={onPromote} bg="transparent">
|
||||||
Promote to Admin
|
Promote to Admin
|
||||||
</StatelessAsyncAction>
|
</StatelessAsyncAction>
|
||||||
|
@ -144,6 +144,8 @@ export function PopoverRoutes(
|
|||||||
contact={props.contacts[window.ship]}
|
contact={props.contacts[window.ship]}
|
||||||
rootIdentity={props.rootIdentity}
|
rootIdentity={props.rootIdentity}
|
||||||
api={props.api}
|
api={props.api}
|
||||||
|
hideAvatars={props.hideAvatars}
|
||||||
|
hideNicknames={props.hideNicknames}
|
||||||
path={props.association["group-path"]}
|
path={props.association["group-path"]}
|
||||||
s3={props.s3}
|
s3={props.s3}
|
||||||
/>
|
/>
|
||||||
|
@ -12,6 +12,8 @@ import { NewGroup } from './components/NewGroup';
|
|||||||
import { JoinGroup } from './components/JoinGroup';
|
import { JoinGroup } from './components/JoinGroup';
|
||||||
|
|
||||||
import { cite } from '~/logic/lib/util';
|
import { cite } from '~/logic/lib/util';
|
||||||
|
import { Body } from '../components/Body';
|
||||||
|
import { Box } from '@tlon/indigo-react';
|
||||||
|
|
||||||
|
|
||||||
type LandscapeProps = StoreState & {
|
type LandscapeProps = StoreState & {
|
||||||
@ -95,12 +97,16 @@ export default class Landscape extends Component<LandscapeProps, {}> {
|
|||||||
<Route path="/~landscape/new"
|
<Route path="/~landscape/new"
|
||||||
render={routeProps=> {
|
render={routeProps=> {
|
||||||
return (
|
return (
|
||||||
<NewGroup
|
<Body>
|
||||||
groups={props.groups}
|
<Box maxWidth="300px">
|
||||||
contacts={props.contacts}
|
<NewGroup
|
||||||
api={props.api}
|
groups={props.groups}
|
||||||
{...routeProps}
|
contacts={props.contacts}
|
||||||
/>
|
api={props.api}
|
||||||
|
{...routeProps}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Body>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -115,13 +121,17 @@ export default class Landscape extends Component<LandscapeProps, {}> {
|
|||||||
const { ship, name } = routeProps.match.params;
|
const { ship, name } = routeProps.match.params;
|
||||||
const autojoin = ship && name ? `${ship}/${name}` : null;
|
const autojoin = ship && name ? `${ship}/${name}` : null;
|
||||||
return (
|
return (
|
||||||
<JoinGroup
|
<Body>
|
||||||
groups={props.groups}
|
<Box maxWidth="300px">
|
||||||
contacts={props.contacts}
|
<JoinGroup
|
||||||
api={props.api}
|
groups={props.groups}
|
||||||
autojoin={autojoin}
|
contacts={props.contacts}
|
||||||
{...routeProps}
|
api={props.api}
|
||||||
/>
|
autojoin={autojoin}
|
||||||
|
{...routeProps}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Body>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user