Merge branch 'odyssey-wip' (#1785)

* odyssey-wip: (31 commits)
  chat-cli: Add clarity
  drum: Boot with %chat-cli, without %hall & %talk
  chat-cli: Cosmetic improvements
  chat: Move eval logic out of /lib/chat-json
  chat-cli: Properly support deleting local chats
  chat-cli: Subscribe to /updates instead of /all
  chat: Move eval logic into lib
  chat: removed unnecessary cast
  chat: removed overly specific pattern match
  chat: style fix for a comment
  chat: remove poke-noun arms
  chat: fixed eval function to disable scry
  chat-cli: Simplify message command type & logic
  chat-cli: Implement permission management
  chat: /primary path provides truncated initial as well as updates
  chat: style fixes, removed some redirect bugs from chat
  chat-cli: Match store and hook's path handling
  chat-cli: Update prompt on-create
  chat: changed wire format and quitting subscription properly on ban
  chat-cli: Add debug poke for connecting to store
  ...

Signed-off-by: Jared Tobin <jared@tlon.io>
This commit is contained in:
Jared Tobin 2019-10-09 10:04:38 +04:00
commit 23f13877a0
No known key found for this signature in database
GPG Key ID: 0E4647D58F8A69E4
64 changed files with 6642 additions and 4097 deletions

1204
pkg/arvo/app/chat-cli.hoon Normal file

File diff suppressed because it is too large Load Diff

349
pkg/arvo/app/chat-hook.hoon Normal file
View File

@ -0,0 +1,349 @@
:: chat-hook:
:: mirror chat data from foreign to local based on read permissions
:: allow sending chat messages to foreign paths based on write perms
::
/- *permission-store, *chat-hook
/+ *chat-json
|%
+$ move [bone card]
::
+$ card
$% [%diff [%chat-update chat-update]]
[%quit ~]
[%poke wire dock poke]
[%pull wire dock ~]
[%peer wire dock path]
==
::
+$ state
$% [%0 state-zero]
==
::
+$ state-zero
$: synced=(map path ship)
boned=(map wire (list bone))
==
::
+$ poke
$% [%chat-action chat-action]
[%permission-action permission-action]
==
::
--
::
|_ [bol=bowl:gall state]
::
++ this .
::
++ prep
|= old=(unit state)
^- (quip move _this)
?~ old
:_ this
[ost.bol %peer /permissions [our.bol %permission-store] /updates]~
[~ this(+<+ u.old)]
::
++ poke-json
|= jon=json
^- (quip move _this)
(poke-chat-action (json-to-action jon))
::
++ poke-chat-action
|= act=chat-action
^- (quip move _this)
?> ?=(%message -.act)
:: local
:_ this
?: (team:title our.bol src.bol)
?. (~(has by synced) path.act)
~
=/ ship (~(got by synced) path.act)
=/ appl ?:(=(ship our.bol) %chat-store %chat-hook)
[ost.bol %poke / [ship appl] [%chat-action act]]~
:: foreign
=/ ship (~(get by synced) path.act)
?~ ship
~
?. =(u.ship our.bol)
~
:: scry permissions to check if write is permitted
?. (permitted-scry [(scot %p src.bol) %chat (weld path.act /write)])
~
=: author.envelope.act src.bol
when.envelope.act now.bol
==
[ost.bol %poke / [our.bol %chat-store] [%chat-action act]]~
::
++ poke-chat-hook-action
|= act=chat-hook-action
^- (quip move _this)
?- -.act
%add-owned
?> (team:title our.bol src.bol)
=/ chat-path [%mailbox path.act]
?: (~(has by synced) path.act)
[~ this]
=. synced (~(put by synced) path.act our.bol)
:_ (track-bone chat-path)
%+ weld
[ost.bol %peer chat-path [our.bol %chat-store] chat-path]~
(create-permission [%chat path.act] security.act)
::
%add-synced
?> (team:title our.bol src.bol)
=/ chat-path [%mailbox (scot %p ship.act) path.act]
?: (~(has by synced) [(scot %p ship.act) path.act])
[~ this]
=. synced (~(put by synced) [(scot %p ship.act) path.act] ship.act)
:_ (track-bone chat-path)
[ost.bol %peer chat-path [ship.act %chat-hook] chat-path]~
::
%remove
=/ ship (~(get by synced) path.act)
?~ ship
[~ this]
?: &(=(u.ship our.bol) (team:title our.bol src.bol))
:: delete one of our.bol own paths
:_ %_ this
synced (~(del by synced) path.act)
boned (~(del by boned) [%mailbox path.act])
==
%- zing
:~ (pull-wire [%mailbox path.act])
(delete-permission [%chat path.act])
^- (list move)
%+ turn (prey:pubsub:userlib [%mailbox path.act] bol)
|= [=bone *]
[bone %quit ~]
==
?. |(=(u.ship src.bol) (team:title our.bol src.bol))
:: if neither ship = source or source = us, do nothing
[~ this]
:: delete a foreign ship's path
:- (pull-wire [%mailbox path.act])
%_ this
synced (~(del by synced) path.act)
boned (~(del by boned) [%mailbox path.act])
==
==
::
++ peer-mailbox
|= pax=path
^- (quip move _this)
?> ?=([* ^] pax)
?> (~(has by synced) pax)
:: scry permissions to check if read is permitted
?> (permitted-scry [(scot %p src.bol) %chat (weld pax /read)])
=/ box (chat-scry pax)
?~ box !!
:_ this
[ost.bol %diff %chat-update [%create (slav %p i.pax) pax]]~
::
++ diff-permission-update
|= [wir=wire diff=permission-update]
^- (quip move _this)
:_ this
?- -.diff
%create ~
%delete ~
%add (handle-permissions [%add path.diff who.diff])
%remove (handle-permissions [%remove path.diff who.diff])
==
::
++ handle-permissions
|= [kind=?(%add %remove) pax=path who=(set ship)]
^- (list move)
?> ?=([* *] pax)
?. =(%chat i.pax) ~
:: check path to see if this is a %read permission
?. =(%read (snag (dec (lent pax)) `(list @t)`pax))
~
=/ sup
%- ~(gas by *(map [ship path] bone))
%+ turn ~(tap by sup.bol)
|=([=bone anchor=[ship path]] [anchor bone])
%- zing
%+ turn ~(tap in who)
|= check-ship=ship
?: (permitted-scry [(scot %p check-ship) pax])
~
:: if ship is not permitted, quit their subscription
=/ mail-path
(oust [(dec (lent t.pax)) (lent t.pax)] `(list @t)`t.pax)
=/ bne (~(get by sup) [check-ship [%mailbox mail-path]])
?~(bne ~ [u.bne %quit ~]~)
::
++ diff-chat-update
|= [wir=wire diff=chat-update]
^- (quip move _this)
?: (team:title our.bol src.bol)
(handle-local diff)
(handle-foreign diff)
::
++ handle-local
|= diff=chat-update
^- (quip move _this)
?- -.diff
%keys [~ this]
%config [~ this]
%create [~ this]
%read [~ this]
%delete
?. (~(has by synced) path.diff)
[~ this]
:_ this(synced (~(del by synced) path.diff))
[ost.bol %pull [%mailbox path.diff] [our.bol %chat-store] ~]~
::
%message
:_ this
%+ turn (prey:pubsub:userlib [%mailbox path.diff] bol)
|= [=bone *]
^- move
[bone %diff [%chat-update diff]]
==
::
++ handle-foreign
|= diff=chat-update
^- (quip move _this)
?- -.diff
%keys [~ this]
%config [~ this]
%read [~ this]
%create
:_ this
?> ?=([* ^] path.diff)
=/ shp (~(get by synced) path.diff)
?~ shp ~
?. =(src.bol u.shp) ~
[(chat-poke [%create ship.diff t.path.diff])]~
::
%delete
?> ?=([* ^] path.diff)
=/ shp (~(get by synced) path.diff)
?~ shp
[~ this]
?. =(u.shp src.bol)
[~ this]
:_ this(synced (~(del by synced) path.diff))
:- (chat-poke diff)
[ost.bol %pull [%mailbox path.diff] [src.bol %chat-hook] ~]~
::
%message
:_ this
?> ?=([* ^] path.diff)
=/ shp (~(get by synced) path.diff)
?~ shp ~
?. =(src.bol u.shp) ~
[(chat-poke diff)]~
==
::
++ quit
|= wir=wire
^- (quip move _this)
~& chat-hook-quit+wir
?: =(wir /permissions)
:_ this
[ost.bol %peer /permissions [our.bol %permission-store] /updates]~
?> ?=([* ^] wir)
?. (~(has by synced) t.wir)
:: no-op
[~ this]
~& %chat-hook-resubscribe
:_ (track-bone wir)
[ost.bol %peer wir [(slav %p i.t.wir) %chat-hook] wir]~
::
++ reap
|= [wir=wire saw=(unit tang)]
^- (quip move _this)
?~ saw
[~ this]
?> ?=(^ wir)
~& %chat-hook-reap
[((slog u.saw) ~) this(synced (~(del by synced) t.wir))]
::
++ chat-poke
|= act=chat-action
^- move
[ost.bol %poke / [our.bol %chat-store] [%chat-action act]]
::
++ permission-poke
|= act=permission-action
^- move
[ost.bol %poke / [our.bol %permission-store] [%permission-action act]]
::
++ create-permission
|= [pax=path sec=chat-security]
^- (list move)
=/ read-perm (weld pax /read)
=/ write-perm (weld pax /write)
?- sec
%channel
:~ (permission-poke (sec-to-perm read-perm %black))
(permission-poke (sec-to-perm write-perm %black))
==
::
%village
:~ (permission-poke (sec-to-perm read-perm %white))
(permission-poke (sec-to-perm write-perm %white))
==
::
%journal
:~ (permission-poke (sec-to-perm read-perm %black))
(permission-poke (sec-to-perm write-perm %white))
==
::
%mailbox
:~ (permission-poke (sec-to-perm read-perm %white))
(permission-poke (sec-to-perm write-perm %black))
==
==
::
++ delete-permission
|= pax=path
^- (list move)
=/ read-perm (weld pax /read)
=/ write-perm (weld pax /write)
:~ (permission-poke [%delete read-perm])
(permission-poke [%delete write-perm])
==
::
++ sec-to-perm
|= [pax=path =kind]
^- permission-action
[%create pax kind *(set ship)]
::
++ chat-scry
|= pax=path
^- (unit mailbox)
=. pax ;:(weld /=chat-store/(scot %da now.bol)/mailbox pax /noun)
.^((unit mailbox) %gx pax)
::
++ permitted-scry
|= pax=path
^- ?
.^(? %gx ;:(weld /=permission-store/(scot %da now.bol)/permitted pax /noun))
::
++ track-bone
|= wir=wire
^+ this
=/ bnd (~(get by boned) wir)
?^ bnd
this(boned (~(put by boned) wir (snoc u.bnd ost.bol)))
this(boned (~(put by boned) wir [ost.bol]~))
::
++ pull-wire
|= pax=path
^- (list move)
?> ?=(^ pax)
=/ bnd (~(get by boned) pax)
?~ bnd ~
=/ shp (~(get by synced) t.pax)
?~ shp ~
%+ turn u.bnd
|= =bone
^- move
?: =(u.shp our.bol)
[bone %pull pax [our.bol %chat-store] ~]
[bone %pull pax [u.shp %chat-hook] ~]
::
--

View File

@ -0,0 +1,237 @@
:: chat-store: data store that holds linear sequences of chat messages
::
/+ *chat-json, *chat-eval
|%
+$ move [bone card]
::
+$ card
$% [%diff diff]
[%quit ~]
==
::
+$ state
$% [%0 state-zero]
==
::
+$ state-zero
$: =inbox
==
::
+$ diff
$% [%chat-initial inbox]
[%chat-configs chat-configs]
[%chat-update chat-update]
==
--
::
|_ [bol=bowl:gall state]
::
++ this .
::
++ prep
|= old=(unit state)
^- (quip move _this)
[~ ?~(old this this(+<+ u.old))]
::
++ peek-x-all
|= pax=path
^- (unit (unit [%noun (map path mailbox)]))
[~ ~ %noun inbox]
::
++ peek-x-configs
|= pax=path
^- (unit (unit [%noun chat-configs]))
:^ ~ ~ %noun
(inbox-to-configs inbox)
::
++ peek-x-keys
|= pax=path
^- (unit (unit [%noun (set path)]))
[~ ~ %noun ~(key by inbox)]
::
++ peek-x-mailbox
|= pax=path
^- (unit (unit [%noun (unit mailbox)]))
?~ pax ~
=/ mailbox=(unit mailbox) (~(get by inbox) pax)
[~ ~ %noun mailbox]
::
++ peek-x-config
|= pax=path
^- (unit (unit [%noun config]))
?~ pax ~
=/ mailbox (~(get by inbox) pax)
?~ mailbox ~
:^ ~ ~ %noun
config.u.mailbox
::
++ peek-x-envelopes
|= pax=path
^- (unit (unit [%noun (list envelope)]))
?+ pax ~
[@ @ *]
=/ mail-path t.t.pax
=/ mailbox (~(get by inbox) mail-path)
?~ mailbox
[~ ~ %noun ~]
=* envelopes envelopes.u.mailbox
=/ sign-test=[?(%neg %pos) @]
%- need
%+ rush i.pax
;~ pose
%+ cook
|= n=@
[%neg n]
;~(pfix hep dem:ag)
::
%+ cook
|= n=@
[%pos n]
dem:ag
==
=* length length.config.u.mailbox
=* start +.sign-test
?: =(-.sign-test %neg)
?: (gth start length)
[~ ~ %noun envelopes]
[~ ~ %noun (swag [(sub length start) start] envelopes)]
::
=/ end (slav %ud i.t.pax)
?. (lte start end)
~
=. end ?:((lth end length) end length)
[~ ~ %noun (swag [start (sub end start)] envelopes)]
==
::
++ peer-keys
|= pax=path
^- (quip move _this)
?> (team:title our.bol src.bol)
:: we send the list of keys then send events when they change
:_ this
[ost.bol %diff %chat-update [%keys ~(key by inbox)]]~
::
++ peer-all
|= pax=path
^- (quip move _this)
?> (team:title our.bol src.bol)
:_ this
[ost.bol %diff %chat-initial inbox]~
::
++ peer-configs
|= pax=path
^- (quip move _this)
?> (team:title our.bol src.bol)
:_ this
[ost.bol %diff %chat-configs (inbox-to-configs inbox)]~
::
++ peer-updates
|= pax=path
^- (quip move _this)
?> (team:title our.bol src.bol)
:: we now proxy all events to this path
[~ this]
::
++ peer-mailbox
|= pax=path
^- (quip move _this)
?> (team:title our.bol src.bol)
?> (~(has by inbox) pax)
=^ =ship pax
?> ?=([* ^] pax)
[(slav %p i.pax) t.pax]
:_ this
[ost.bol %diff %chat-update [%create ship pax]]~
::
++ poke-json
|= jon=json
^- (quip move _this)
?> (team:title our.bol src.bol)
(poke-chat-action (json-to-action jon))
::
++ poke-chat-action
|= action=chat-action
^- (quip move _this)
?> (team:title our.bol src.bol)
?- -.action
%create (handle-create action)
%delete (handle-delete action)
%message (handle-message action)
%read (handle-read action)
==
::
++ handle-create
|= act=chat-action
^- (quip move _this)
?> ?=(%create -.act)
=/ pax [(scot %p ship.act) path.act]
?: (~(has by inbox) pax)
[~ this]
:- (send-diff pax act)
this(inbox (~(put by inbox) pax *mailbox))
::
++ handle-delete
|= act=chat-action
^- (quip move _this)
?> ?=(%delete -.act)
=/ mailbox=(unit mailbox) (~(get by inbox) path.act)
?~ mailbox
[~ this]
:- (send-diff path.act act)
this(inbox (~(del by inbox) path.act))
::
++ handle-message
|= act=chat-action
^- (quip move _this)
?> ?=(%message -.act)
=/ mailbox=(unit mailbox) (~(get by inbox) path.act)
?~ mailbox
[~ this]
=* letter letter.envelope.act
=? letter &(?=(%code -.letter) ?=(~ output.letter))
=/ =hoon (ream expression.letter)
letter(output (eval bol hoon))
=: length.config.u.mailbox +(length.config.u.mailbox)
number.envelope.act length.config.u.mailbox
envelopes.u.mailbox (snoc envelopes.u.mailbox envelope.act)
inbox (~(put by inbox) path.act u.mailbox)
==
:_ this(inbox inbox)
(send-diff path.act act)
::
++ handle-read
|= act=chat-action
^- (quip move _this)
?> ?=(%read -.act)
=/ mailbox=(unit mailbox) (~(get by inbox) path.act)
?~ mailbox
[~ this]
=: read.config.u.mailbox length.config.u.mailbox
inbox (~(put by inbox) path.act u.mailbox)
==
:_ this(inbox inbox)
(send-diff path.act act)
::
++ update-subscribers
|= [pax=path act=chat-action]
^- (list move)
%+ turn (prey:pubsub:userlib pax bol)
|= [=bone *]
[bone %diff %chat-update act]
::
++ send-diff
|= [pax=path act=chat-action]
^- (list move)
%- zing
:~ (update-subscribers /all act)
(update-subscribers /updates act)
(update-subscribers [%mailbox pax] act)
?. |(=(%read -.act) =(%message -.act))
~
(update-subscribers /configs act)
?. |(=(%create -.act) =(%delete -.act))
~
(update-subscribers /keys act)
==
::
--

320
pkg/arvo/app/chat-view.hoon Normal file
View File

@ -0,0 +1,320 @@
:: chat-view: sets up chat JS client, paginates data, and combines commands
:: into semantic actions for the UI
::
/- *permission-store, *group-store, *permission-group-hook, *chat-hook
/+ *server, *chat-json
/= index
/^ octs
/; as-octs:mimes:html
/: /===/app/chat/index
/| /html/
/~ ~
==
/= tile-js
/^ octs
/; as-octs:mimes:html
/: /===/app/chat/js/tile
/| /js/
/~ ~
==
/= script
/^ octs
/; as-octs:mimes:html
/: /===/app/chat/js/index
/| /js/
/~ ~
==
/= style
/^ octs
/; as-octs:mimes:html
/: /===/app/chat/css/index
/| /css/
/~ ~
==
/= chat-png
/^ (map knot @)
/: /===/app/chat/img /_ /png/
::
|%
::
+$ move [bone card]
::
+$ card
$% [%http-response =http-event:http]
[%connect wire binding:eyre term]
[%peer wire dock path]
[%poke wire dock poke]
[%diff %json json]
[%quit ~]
==
::
+$ poke
$% [%launch-action [@tas path @t]]
[%chat-action chat-action]
[%group-action group-action]
[%chat-hook-action chat-hook-action]
[%permission-group-hook-action permission-group-hook-action]
==
--
::
|_ [bol=bowl:gall ?]
::
++ this .
::
++ prep
|= old=(unit ?)
^- (quip move _this)
?~ old
:_ this
:~ [ost.bol %peer / [our.bol %chat-store] /updates]
[ost.bol %connect / [~ /'~chat'] %chat-view]
(launch-poke [/configs '/~chat/js/tile.js'])
==
[~ this]
::
++ bound
|= [wir=wire success=? binding=binding:eyre]
^- (quip move _this)
[~ this]
::
++ poke-handle-http-request
%- (require-authorization:app ost.bol move this)
|= =inbound-request:eyre
^- (quip move _this)
::
=+ url=(parse-request-line url.request.inbound-request)
=/ name=@t
=+ back-path=(flop site.url)
?~ back-path
''
i.back-path
?: =(name 'tile')
[[ost.bol %http-response (js-response:app tile-js)]~ this]
?+ site.url
:_ this
[ost.bol %http-response not-found:app]~
::
:: styling
::
[%'~chat' %css %index ~]
:_ this
[ost.bol %http-response (css-response:app style)]~
::
:: javascript
::
[%'~chat' %js %index ~]
:_ this
[ost.bol %http-response (js-response:app script)]~
::
:: images
::
[%'~chat' %img *]
=/ img (as-octs:mimes:html (~(got by chat-png) `@ta`name))
:_ this
[ost.bol %http-response (png-response:app img)]~
::
[%'~chat' %paginate @t @t *]
=/ start (need (rush i.t.t.site.url dem))
=/ end (need (rush i.t.t.t.site.url dem))
=/ pax t.t.t.t.site.url
=/ envelopes (envelope-scry [(scot %ud start) (scot %ud end) pax])
:_ this
:~
:+ ost.bol
%http-response
%- json-response:app
%- json-to-octs
%+ envelopes-update
envelopes
[start end pax]
==
::
:: inbox page
::
[%'~chat' *]
:_ this
[ost.bol %http-response (html-response:app index)]~
==
::
++ poke-json
|= jon=json
^- (quip move _this)
?. =(src.bol our.bol)
[~ this]
(poke-chat-view-action (json-to-view-action jon))
::
++ poke-chat-view-action
|= act=chat-view-action
^- (quip move _this)
?. =(src.bol our.bol)
[~ this]
?- -.act
%create
:: TODO: add invites
=/ pax [(scot %p our.bol) path.act]
=/ group-read=path [%chat (weld pax /read)]
=/ group-write=path [%chat (weld pax /write)]
:_ this
%+ weld
:~ (chat-poke [%create our.bol path.act])
(group-poke [%bundle group-read])
(group-poke [%bundle group-write])
(group-poke [%add read.act group-read])
(group-poke [%add write.act group-write])
(chat-hook-poke [%add-owned pax security.act])
==
(create-security [%chat pax] security.act)
::
%delete
=/ group-read [%chat (weld path.act /read)]
=/ group-write [%chat (weld path.act /write)]
:_ this
:~ (chat-hook-poke [%remove path.act])
(group-poke [%unbundle group-read])
(group-poke [%unbundle group-write])
(chat-poke [%delete path.act])
==
::
==
::
++ peer-primary
|= pax=path
^- (quip move _this)
?> (team:title our.bol src.bol)
:: create inbox with 100 messages max per mailbox and send that along
:: then quit the subscription
:_ this
[ost.bol %diff %json (inbox-to-json (truncate-inbox all-scry))]~
::
++ peer-configs
|= pax=path
^- (quip move _this)
?> (team:title our.bol src.bol)
:_ this
[ost.bol %diff %json *json]~
::
++ diff-chat-update
|= [wir=wire upd=chat-update]
^- (quip move _this)
=/ updates-json (update-to-json upd)
=/ configs-json (configs-to-json configs-scry)
:_ this
%+ weld
%+ turn (prey:pubsub:userlib /primary bol)
|= [=bone *]
[bone %diff %json updates-json]
%+ turn (prey:pubsub:userlib /configs bol)
|= [=bone *]
[bone %diff %json configs-json]
::
++ quit
|= wir=wire
^- (quip move _this)
:_ this
[ost.bol %peer / [our.bol %chat-store] /updates]~
::
:: +utilities
::
++ launch-poke
|= [=path =cord]
^- move
[ost.bol %poke / [our.bol %launch] [%launch-action %chat-view path cord]]
::
++ chat-poke
|= act=chat-action
^- move
[ost.bol %poke / [our.bol %chat-store] [%chat-action act]]
::
++ group-poke
|= act=group-action
^- move
[ost.bol %poke / [our.bol %group-store] [%group-action act]]
::
++ chat-hook-poke
|= act=chat-hook-action
^- move
[ost.bol %poke / [our.bol %chat-hook] [%chat-hook-action act]]
::
++ perm-group-hook-poke
|= act=permission-group-hook-action
^- move
=/ pok [%permission-group-hook-action act]
[ost.bol %poke / [our.bol %permission-group-hook] pok]
::
++ envelope-scry
|= pax=path
^- (list envelope)
=. pax ;:(weld /=chat-store/(scot %da now.bol)/envelopes pax /noun)
.^((list envelope) %gx pax)
::
++ all-scry
^- inbox
.^(inbox %gx /=chat-store/(scot %da now.bol)/all/noun)
::
++ configs-scry
^- chat-configs
.^(chat-configs %gx /=chat-store/(scot %da now.bol)/configs/noun)
::
++ create-security
|= [pax=path sec=chat-security]
^- (list move)
=/ read (weld pax /read)
=/ write (weld pax /write)
?- sec
%channel
:~ (perm-group-hook-poke [%associate read [[read %black] ~ ~]])
(perm-group-hook-poke [%associate write [[write %black] ~ ~]])
==
::
%village
:~ (perm-group-hook-poke [%associate read [[read %white] ~ ~]])
(perm-group-hook-poke [%associate write [[write %white] ~ ~]])
==
::
%journal
:~ (perm-group-hook-poke [%associate read [[read %black] ~ ~]])
(perm-group-hook-poke [%associate write [[write %white] ~ ~]])
==
::
%mailbox
:~ (perm-group-hook-poke [%associate read [[read %white] ~ ~]])
(perm-group-hook-poke [%associate write [[write %black] ~ ~]])
==
::
==
::
++ envelopes-update
|= [envelopes=(list envelope) start=@ud end=@ud pax=path]
^- json
=, enjs:format
%+ frond %chat-update
%- pairs
:~
:- %messages
%- pairs
:~ [%path (path pax)]
[%start (numb start)]
[%end (numb end)]
[%envelopes [%a (turn envelopes enve)]]
==
==
::
++ truncate-envelopes
|= envelopes=(list envelope)
^- (list envelope)
=/ length (lent envelopes)
?: (lth length 100)
envelopes
(swag [(sub length 100) 100] envelopes)
::
++ truncate-inbox
|= box=inbox
^- inbox
%- ~(run by box)
|= mail=mailbox
^- mailbox
:- config.mail
(truncate-envelopes envelopes.mail)
::
--

View File

@ -1,614 +0,0 @@
/- hall
/+ *server, chat, hall-json
/= index
/^ octs
/; as-octs:mimes:html
/: /===/app/chat/index
/| /html/
/~ ~
==
/= tile-js
/^ octs
/; as-octs:mimes:html
/: /===/app/chat/js/tile
/| /js/
/~ ~
==
/= script
/^ octs
/; as-octs:mimes:html
/: /===/app/chat/js/index
/| /js/
/~ ~
==
/= style
/^ octs
/; as-octs:mimes:html
/: /===/app/chat/css/index
/| /css/
/~ ~
==
/= chat-png
/^ (map knot @)
/: /===/app/chat/img /_ /png/
::
=, chat
::
|%
+$ state
$% [%0 str=streams]
==
::
+$ move [bone card]
::
+$ card
$% [%http-response =http-event:http]
[%connect wire binding:eyre term]
[%peer wire dock path]
[%quit ~]
[%poke wire dock poke]
[%peer wire dock path]
[%pull wire dock ~]
[%diff diff]
==
--
::
|_ [bol=bowl:gall state]
::
++ this .
::
:: +prep: set up the app, migrate the state
::
++ prep
|= old=(unit state)
^- (quip move _this)
?^ old
:_ this(+<+ u.old)
[(launch-poke [/chattile '/~chat/js/tile.js'])]~
::
=/ inbox-path /circle/inbox/config/group
::
:_ this
:* [ost.bol %connect / [~ /'~chat'] %chat]
(launch-poke [/chattile '/~chat/js/tile.js'])
(hall-peer /circle/(scot %p our.bol)/inbox/config/group inbox-path)
(hall-peer /circles/(scot %p our.bol) /circles/(scot %p our.bol))
(hall-source [our.bol %i])
?: =((clan:title our.bol) %czar)
~
?: =(our.bol ~marzod)
:- (hall-create %announcements 'Announcements from Tlon' %journal)
[(hall-source [~marzod %announcements])]~
?: =(our.bol ~dopzod)
:- (hall-create %urbit-dev 'Chat about developing on Urbit' %channel)
[(hall-create %urbit-help 'Help about Urbit' %channel)]~
:~ (hall-create %hall-internal-announcements '' %village)
(hall-source [our.bol %hall-internal-announcements])
(hall-source [~marzod %announcements])
==
==
::
:: +peer-chattile: subscribe to data necessary for chat tile
::
++ peer-chattile
|= wir=wire
^- (quip move _this)
:_ this
[ost.bol %diff %json (construct-tile-json str)]~
::
:: +peer-messages: subscribe to subset of messages and updates
::
++ peer-primary
|= wir=wire
^- (quip move _this)
=* messages messages.str
:_ this
:- [ost.bol %diff %chat-config str]
%+ murn ~(tap by messages)
|= [cir=circle:hall envelopes=(list envelope:hall)]
^- (unit move)
=/ length=@ (lent envelopes)
=/ start=@
?: (gte length 100)
(sub length 100)
0
=/ end=@ length
=/ offset=@ (sub end start)
:- ~
:* ost.bol
%diff
%chat-update
[%messages cir start end (swag [start offset] envelopes)]
==
::
:: +poke-chat: send a list of actions to hall
::
++ poke-chat-action
|= act=action:chat
^- (quip move _this)
:_ this
%+ turn lis.act
|= hac=action:hall
^- move
[ost.bol %poke /p/(scot %da now.bol) [our.bol %hall] [%hall-action hac]]
::
:: +diff-hall-prize: handle full state initially handed to us by hall
::
++ diff-hall-prize
|= [wir=wire piz=prize:hall]
^- (quip move _this)
?~ wir
(mean [leaf+"invalid wire for diff: {(spud wir)}"]~)
?+ i.wir
(mean [leaf+"invalid wire for diff: {(spud wir)}"]~)
::
%circles
?> ?=(%circles -.piz)
=. str str(circles cis.piz)
:_ this(str str)
(send-chat-update [[%circles cis.piz] str])
::
%circle
?> ?=(%circle -.piz)
?. =([our.bol &3:wir] [our.bol %inbox])
::
:: fill remote configs with message data
::
=* messages messages.str
=/ circle [`@p`(slav %p &2:wir) &3:wir]
=/ peers=(map circle:hall (set @p))
%- ~(rep by rem.pes.piz)
|= [[cir=circle:hall grp=group:hall] acc=(map circle:hall (set @p))]
^+ acc
(~(put by acc) cir (silt (turn ~(tap by grp) head)))
::
=. str
%= str
messages (~(put by messages) circle nes.piz)
peers
%- ~(uni by peers.str)
(~(put by peers) circle ~(key by loc.pes.piz))
==
:_ this(str str)
(send-chat-update [[%messages circle 0 (lent messages) nes.piz] str])
::
:: fill inbox config and remote configs with prize data
::
=/ circles=(list circle:hall) (turn ~(tap in src.loc.cos.piz) head)
::
=/ peers=(map circle:hall (set @p))
%- ~(rep by rem.pes.piz)
|= [[cir=circle:hall grp=group:hall] acc=(map circle:hall (set @p))]
^+ acc
(~(put by acc) cir (silt (turn ~(tap by grp) head)))
::
:-
%+ turn
%~ tap in
%- ~(del in (silt circles))
[our.bol %inbox]
|= cir=circle:hall
%+ hall-peer
/circle/(scot %p our.bol)/[nom.cir]/config/group
/circle/[nom.cir]/config/group
%= this
inbox.str loc.cos.piz
peers.str (~(put by peers) [our.bol %inbox] ~(key by loc.pes.piz))
::
configs.str
%- ~(uni by configs.str)
^- (map circle:hall (unit config:hall))
(~(run by rem.cos.piz) some)
::
messages.str
%- molt
%+ turn circles
|= cir=circle:hall
^- [circle:hall (list envelope:hall)]
[cir ~]
==
==
::
:: +diff-hall-rumor: handle updates to hall state
::
++ diff-hall-rumor
|= [wir=wire rum=rumor:hall]
^- (quip move _this)
?~ wir
(mean [leaf+"invalid wire for diff: {(spud wir)}"]~)
?+ i.wir
(mean [leaf+"invalid wire for diff: {(spud wir)}"]~)
::
%circles
(handle-rumor-circles rum)
::
%circle
(handle-rumor-circle wir rum)
::
==
::
:: +handle-rumor-circles
::
++ handle-rumor-circles
|= rum=rumor:hall
^- (quip move _this)
?> ?=(%circles -.rum)
=/ cis
?: add.rum
(~(put in circles.str) cir.rum)
(~(del in circles.str) cir.rum)
=. str
%= str
circles cis
peers
?: add.rum
(~(put by peers.str) [our.bol cir.rum] ~)
(~(del by peers.str) [our.bol cir.rum])
==
:_ this(str str)
(send-chat-update [[%circles cis] str])
::
++ handle-rumor-circle
|= [wir=wire rum=rumor:hall]
^- (quip move _this)
?> ?=(%circle -.rum)
?+ -.rum.rum
[~ this]
::
:: %gram: new message
::
%gram
(handle-rumor-circle-gram wir rum.rum)
::
:: status: status update
::
%status
(handle-rumor-circle-status rum.rum)
::
:: %config: config has changed
::
%config
?+ -.dif.rum.rum
[~ this]
::
:: %full: set all of config without side effects
::
%full
=* conf cof.dif.rum.rum
=. configs.str (~(put by configs.str) cir.rum.rum `conf)
:_ this(str str)
(send-chat-update [[%config cir.rum.rum conf] str])
::
:: %read: the read count of one of our configs has changed
::
%read
(handle-rumor-circle-config-read rum.rum)
::
:: %source: the sources of our inbox have changed
::
%source
(handle-rumor-circle-config-source rum.rum)
::
:: %remove: remove a circle
::
%remove
=. str
%= str
configs (~(del by configs.str) cir.rum.rum)
messages (~(del by messages.str) cir.rum.rum)
peers (~(del by peers.str) cir.rum.rum)
==
:_ this(str str)
(send-chat-update [[%delete cir.rum.rum] str])
::
==
==
::
++ handle-rumor-circle-gram
|= [wir=wire sto=rumor-story:hall]
^- (quip move _this)
?> ?=(%gram -.sto)
=* messages messages.str
=/ circle [`@p`(slav %p &2:wir) &3:wir]
=/ nes=(unit (list envelope:hall))
(~(get by messages) circle)
?~ nes
[~ this]
=. messages.str (~(put by messages) circle (snoc u.nes nev.sto))
:_ this(str str)
(send-chat-update [[%message circle nev.sto] str])
::
++ handle-rumor-circle-status
|= sto=rumor-story:hall
^- (quip move _this)
?> ?=(%status -.sto)
=/ upeers=(unit (set @p)) (~(get by peers.str) cir.sto)
?~ upeers
[~ this]
=/ peers=(set @p)
?: =(%remove -.dif.sto)
(~(del in u.upeers) who.sto)
(~(put in u.upeers) who.sto)
=. peers.str (~(put by peers.str) cir.sto peers)
:_ this(str str)
(send-chat-update [[%peers cir.sto peers] str])
::
++ handle-rumor-circle-config-read
|= sto=rumor-story:hall
^- (quip move _this)
?> ?=(%config -.sto)
?> ?=(%read -.dif.sto)
?: =(cir.sto [our.bol %inbox])
:: ignore when cir.sto is inbox
[~ this]
=/ conf=(unit config:hall) (~(got by configs.str) cir.sto)
?~ conf
[~ this]
=. red.u.conf red.dif.sto
=. configs.str (~(put by configs.str) cir.sto conf)
:_ this(str str)
(send-chat-update [[%config cir.sto u.conf] str])
::
:: +handle-rumor-circle-config-source: on source, subscribe and add to inbox
:: on remove source, send delete and remove data from state
::
++ handle-rumor-circle-config-source
|= sto=rumor-story:hall
^- (quip move _this)
?> ?=(%config -.sto)
?> ?=(%source -.dif.sto)
?. =(cir.sto [our.bol %inbox])
:: ignore when cir.sto is not inbox
[~ this]
=* circ cir.src.dif.sto
=/ wir /circle/(scot %p hos.circ)/[nom.circ]/grams/0/config/group
:: we've added a source to our inbox
::
?: add.dif.sto
=. str
%_ str
src.inbox (~(put in src.inbox.str) src.dif.sto)
::
configs
?: (~(has by configs.str) circ)
configs.str
(~(put by configs.str) circ ~)
==
::
=/ pax /circle/[nom.circ]/grams/0/config/group
:_ this(str str)
:- [ost.bol %peer wir [hos.circ %hall] pax]
(send-chat-update [[%inbox inbox.str] str])
::
=. src.inbox.str (~(del in src.inbox.str) src.dif.sto)
:: we've removed a source from our inbox
::
=. str
%= str
inbox inbox.str
::
configs (~(del by configs.str) circ)
messages (~(del by messages.str) circ)
peers (~(del by peers.str) circ)
==
=/ fake=circle:hall
[our.bol (crip (weld (trip 'hall-internal-') (trip nom.circ)))]
::
:_ this(str str)
;: weld
^- (list move)
:: just forward the delete to our clients
::
?~ (~(get by configs.str) fake)
[ost.bol %pull wir [hos.circ %hall] ~]~
:: if we get a delete from another ship, delete our fake circle copy
::
:- [ost.bol %pull wir [hos.circ %hall] ~]
[ost.bol %poke /f [our.bol %hall] [%hall-action [%delete nom.fake ~]]]~
::
(send-chat-update [[%inbox inbox.str] str])
(send-chat-update [[%delete circ] str])
==
::
:: +bound: lient tells us we successfully bound our server to the ~chat url
::
++ bound
|= [wir=wire success=? binding=binding:eyre]
^- (quip move _this)
[~ this]
::
:: +poke-handle-http-request: serve pages from file system based on URl path
::
++ poke-handle-http-request
%- (require-authorization:app ost.bol move this)
|= =inbound-request:eyre
^- (quip move _this)
::
=+ request-line=(parse-request-line url.request.inbound-request)
=/ name=@t
=+ back-path=(flop site.request-line)
?~ back-path
''
i.back-path
?: =(name 'tile')
[[ost.bol %http-response (js-response:app tile-js)]~ this]
?+ site.request-line
:_ this
[ost.bol %http-response not-found:app]~
::
:: styling
::
[%'~chat' %css %index ~]
:_ this
[ost.bol %http-response (css-response:app style)]~
::
:: javascript
::
[%'~chat' %js %index ~]
:_ this
[ost.bol %http-response (js-response:app script)]~
::
:: images
::
[%'~chat' %img *]
=/ img (as-octs:mimes:html (~(got by chat-png) `@ta`name))
:_ this
[ost.bol %http-response (png-response:app img)]~
::
:: paginated message data
::
[%'~chat' %scroll @t @t @t @t ~]
=/ cir [(slav %p &3:site.request-line) &4:site.request-line]
=/ start=@ud (need (rush &5:site.request-line dem))
=/ parsedend=@ud (need (rush &6:site.request-line dem))
=* messages messages.str
=/ envs=(unit (list envelope:hall)) (~(get by messages) cir)
?~ envs
[~ this]
?: (gte start (lent u.envs))
[~ this]
=/ end=@
?: (gte parsedend (lent u.envs))
(dec (lent u.envs))
parsedend
=/ offset (sub end start)
=/ jon
%- msg-to-json
:* %messages
cir
start
end
(swag [start offset] u.envs)
==
:_ this
[ost.bol %http-response (json-response:app (json-to-octs jon))]~
::
::
:: inbox page
::
[%'~chat' *]
:_ this
[ost.bol %http-response (html-response:app index)]~
==
::
::
:: +subscription-retry arms
::
::
:: +reap: recieve acknowledgement for peer, retry on failure
::
++ reap
|= [wir=wire err=(unit tang)]
^- (quip move _this)
?~ err
[~ this]
?~ wir
(mean [leaf+"invalid wire for diff: {(spud wir)}"]~)
?+ i.wir
(mean [leaf+"invalid wire for diff: {(spud wir)}"]~)
::
%circle
=/ shp=@p (slav %p &2:wir)
=/ pat /circle/[&3:wir]/config/group
?: =(&3:wir 'inbox')
:_ this
[ost.bol %peer wir [shp %hall] pat]~
?: (~(has in src.inbox.str) [[shp &3:wir] ~])
:_ this
[ost.bol %peer wir [shp %hall] pat]~
[~ this]
::
%circles
:_ this
[ost.bol %peer wir [our.bol %hall] wir]~
==
::
:: +quit: subscription failed/quit at some point, retry
::
++ quit
|= wir=wire
^- (quip move _this)
?~ wir
(mean [leaf+"invalid wire for diff: {(spud wir)}"]~)
?+ i.wir
(mean [leaf+"invalid wire for diff: {(spud wir)}"]~)
::
%circle
=/ shp=@p (slav %p &2:wir)
=/ pat /circle/[&3:wir]/config/group
?: =(&3:wir 'inbox')
:_ this
[ost.bol %peer wir [shp %hall] pat]~
?: (~(has in src.inbox.str) [[shp &3:wir] ~])
:_ this
[ost.bol %peer wir [shp %hall] pat]~
[~ this]
::
%circles
:_ this
[ost.bol %peer wir [our.bol %hall] wir]~
==
::
:: +utilities
::
::
:: +send-chat-update: utility func for sending updates to all our subscribers
::
++ send-chat-update
|= [upd=update str=streams]
^- (list move)
=/ jon-one (update-to-json upd)
=/ jon-two (construct-tile-json str)
::
%+ weld
^- (list move)
%+ turn (prey:pubsub:userlib /primary bol)
|= [=bone *]
[bone %diff %json jon-one]
^- (list move)
%+ turn (prey:pubsub:userlib /chattile bol)
|= [=bone *]
[bone %diff %json jon-two]
::
++ construct-tile-json
|= str=streams
^- json
:- %o
%- my
:~ ['config' (config-to-json str)]
::
:- 'numbers'
%- numbers-to-json
^- (list [circle:hall @ud])
%+ turn ~(tap by messages.str)
|= [cir=circle:hall lis=(list envelope:hall)]
^- [circle:hall @ud]
?~ lis
[cir 0]
=/ last (snag (dec (lent lis)) `(list envelope:hall)`lis)
[cir (add num.last 1)]
==
::
++ launch-poke
|= [=path =cord]
^- move
[ost.bol %poke /chat [our.bol %launch] [%launch-action %chat path cord]]
::
++ hall-peer
|= [wir=wire pat=path]
^- move
[ost.bol %peer wir [our.bol %hall] pat]
::
++ hall-create
|= [name=@tas description=@t =security:hall]
^- move
=/ poke [%hall-action [%create name description security]]
[ost.bol %poke /chat [our.bol %hall] poke]
::
++ hall-source
|= cir=circle:hall
^- move
=/ poke [%hall-action [%source %inbox %.y (silt [cir ~]~)]]
[ost.bol %poke /chat [our.bol %hall] poke]
::
--

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -39,7 +39,7 @@
^- (quip move _this)
?- -.act
%add
?. =(src.bol our.bol)
?. (team:title our.bol src.bol)
[~ this]
=/ group-path [%group path.act]
=/ group-wire [(scot %p ship.act) group-path]
@ -55,7 +55,7 @@
=/ ship (~(get by synced) path.act)
?~ ship
[~ this]
?: &(=(u.ship our.bol) =(our.bol src.bol))
?: &(=(u.ship our.bol) (team:title our.bol src.bol))
:: delete one of our own paths
=/ group-wire [(scot %p our.bol) %group path.act]
:_ this(synced (~(del by synced) path.act))
@ -66,22 +66,21 @@
|= [=bone *]
^- move
[bone %quit ~]
?: |(=(u.ship src.bol) =(our.bol src.bol))
?: |(=(u.ship src.bol) (team:title our.bol src.bol))
:: delete a foreign ship's path
=/ group-wire [(scot %p u.ship) %group path.act]
:_ this(synced (~(del by synced) path.act))
(pull-wire group-wire path.act)
:: don't allow
[~ this]
::
==
::
++ peer-group
|= pax=path
^- (quip move _this)
?~ pax !!
?. (~(has by synced) pax) !!
=/ grp=(unit group) (group-scry pax)
?> (~(has by synced) pax)
=/ grp (group-scry pax)
?~ grp !!
:_ this
[ost.bol %diff [%group-update [%path u.grp pax]]]~
@ -89,7 +88,7 @@
++ diff-group-update
|= [wir=wire diff=group-update]
^- (quip move _this)
?: =(src.bol our.bol)
?: (team:title our.bol src.bol)
(handle-local diff)
(handle-foreign diff)
::
@ -100,34 +99,17 @@
%keys [~ this]
%path [~ this]
%bundle [~ this]
%add
:_ this
%+ turn (prey:pubsub:userlib [%group pax.diff] bol)
|= [=bone *]
^- move
[bone %diff [%group-update diff]]
::
%remove
:_ this
%+ turn (prey:pubsub:userlib [%group pax.diff] bol)
|= [=bone *]
^- move
[bone %diff [%group-update diff]]
%add [(update-subscribers [%group pax.diff] diff) this]
%remove [(update-subscribers [%group pax.diff] diff) this]
::
%unbundle
:_ this(synced (~(del by synced) pax.diff))
%+ weld
(update-subscribers [%group pax.diff] diff)
^- (list move)
%+ turn (prey:pubsub:userlib [%group pax.diff] bol)
|= [=bone *]
^- move
[bone %diff [%group-update diff]]
^- (list move)
%+ turn (prey:pubsub:userlib [%group pax.diff] bol)
|= [=bone *]
^- move
[bone %quit ~]
::
==
::
++ handle-foreign
@ -138,42 +120,31 @@
%bundle [~ this]
::
%path
?~ pax.diff
[~ this]
=/ ship (~(get by synced) pax.diff)
?~ ship
[~ this]
?. =(src.bol u.ship)
[~ this]
:_ this
?~ pax.diff ~
=/ ship (~(get by synced) pax.diff)
?~ ship ~
?. =(src.bol u.ship) ~
:~ (group-poke pax.diff [%unbundle pax.diff])
(group-poke pax.diff [%bundle pax.diff])
(group-poke pax.diff [%add members.diff pax.diff])
==
::
%add
?~ pax.diff
[~ this]
=/ ship (~(get by synced) pax.diff)
?~ ship
[~ this]
?. =(src.bol u.ship)
[~ this]
:_ this
:~ (group-poke pax.diff diff)
==
?~ pax.diff ~
=/ ship (~(get by synced) pax.diff)
?~ ship ~
?. =(src.bol u.ship) ~
[(group-poke pax.diff diff)]~
::
%remove
?~ pax.diff
[~ this]
=/ ship (~(get by synced) pax.diff)
?~ ship
[~ this]
?. =(src.bol u.ship)
[~ this]
:_ this
:~ (group-poke pax.diff diff)
==
?~ pax.diff ~
=/ ship (~(get by synced) pax.diff)
?~ ship ~
?. =(src.bol u.ship) ~
[(group-poke pax.diff diff)]~
::
%unbundle
?~ pax.diff
@ -184,35 +155,30 @@
?. =(src.bol u.ship)
[~ this]
:_ this(synced (~(del by synced) pax.diff))
:~ (group-poke pax.diff diff)
==
::
[(group-poke pax.diff diff)]~
==
::
++ quit
|= wir=wire
^- (quip move _this)
=/ wir `(list @tas)`wir
=/ =ship (slav %p &1:wir)
=. wir ?^ wir t.wir ~
=. wir ?^ wir t.wir ~
?: (~(has by synced) wir)
=/ group-path [%group wir]
=/ group-wire [(scot %p ship) group-path]
:_ (track-bone group-wire)
[ost.bol %peer group-wire [ship %group-hook] group-path]~
:: no-op
[~ this]
=^ =ship wir
?> ?=([* ^] wir)
[(slav %p i.wir) t.t.wir]
?. (~(has by synced) wir)
[~ this]
=/ group-path [%group wir]
=/ group-wire [(scot %p ship) group-path]
:_ (track-bone group-wire)
[ost.bol %peer group-wire [ship %group-hook] group-path]~
::
++ reap
|= [wir=wire saw=(unit tang)]
^- (quip move _this)
?~ saw
[~ this]
=/ wir `(list @tas)`wir
=/ =ship (slav %p &1:wir)
=. wir ?^ wir t.wir ~
=. wir ?^ wir t.wir ~
=^ =ship wir
?> ?=([* ^] wir)
[(slav %p i.wir) t.t.wir]
~& %insufficient-permissions-for-group
[((slog u.saw) ~) this(synced (~(del by synced) wir))]
::
@ -224,12 +190,15 @@
++ group-scry
|= pax=path
^- (unit group)
=. pax ;: weld
`path`/=group-store/(scot %da now.bol)
pax
`path`/noun
==
.^((unit group) %gx pax)
.^((unit group) %gx ;:(weld /=group-store/(scot %da now.bol) pax /noun))
::
++ update-subscribers
|= [pax=path diff=group-update]
^- (list move)
%+ turn (prey:pubsub:userlib pax bol)
|= [=bone *]
^- move
[bone %diff [%group-update diff]]
::
++ track-bone
|= wir=wire

View File

@ -25,9 +25,7 @@
++ prep
|= old=(unit state)
^- (quip move _this)
?~ old
[~ this]
[~ this(+<+ u.old)]
[~ ?~(old this this(+<+ u.old))]
::
++ peek-x
|= pax=path
@ -40,7 +38,7 @@
++ peer-all
|= pax=path
^- (quip move _this)
?. =(src.bol our.bol) !!
?> (team:title our.bol src.bol)
:: we now proxy all events to this path
:_ this
[ost.bol %diff %group-initial groups]~
@ -48,7 +46,7 @@
++ peer-keys
|= pax=path
^- (quip move _this)
?. =(src.bol our.bol) !!
?> (team:title our.bol src.bol)
:: we send the list of keys then send events when they change
:_ this
[ost.bol %diff %group-update [%keys ~(key by groups)]]~
@ -56,17 +54,15 @@
++ peer-group
|= pax=path
^- (quip move _this)
?. =(src.bol our.bol) !!
=/ grp=(unit group) (~(get by groups) pax)
?~ grp !!
?> (team:title our.bol src.bol)
=/ grp (~(got by groups) pax)
:_ this
[ost.bol %diff %group-update [%path u.grp pax]]~
[ost.bol %diff %group-update [%path grp pax]]~
::
++ poke-group-action
|= action=group-action
^- (quip move _this)
?. =(src.bol our.bol)
[~ this]
?> (team:title our.bol src.bol)
?- -.action
%add (handle-add action)
%remove (handle-remove action)
@ -82,7 +78,7 @@
[~ this]
?. (~(has by groups) pax.act)
[~ this]
=/ members=group (~(got by groups) pax.act)
=/ members (~(got by groups) pax.act)
=. members (~(uni in members) members.act)
?: =(members (~(got by groups) pax.act))
[~ this]
@ -126,26 +122,22 @@
:- (send-diff pax.act act)
this(groups (~(del by groups) pax.act))
::
++ send-diff
|= [pax=path action=group-action]
++ update-subscribers
|= [pax=path act=group-action]
^- (list move)
;: weld
^- (list move)
%+ turn (prey:pubsub:userlib /all bol)
|= [=bone *]
[bone %diff %group-update action]
::
^- (list move)
%+ turn (prey:pubsub:userlib [%group pax] bol)
|= [=bone *]
[bone %diff %group-update action]
::
^- (list move)
?. |(=(%bundle -.action) =(%unbundle -.action))
~
%+ turn (prey:pubsub:userlib /keys bol)
|= [=bone *]
[bone %diff %group-update action]
%+ turn (prey:pubsub:userlib pax bol)
|= [=bone *]
[bone %diff %group-update act]
::
++ send-diff
|= [pax=path act=group-action]
^- (list move)
%- zing
:~ (update-subscribers /all act)
(update-subscribers [%group pax] act)
?. |(=(%bundle -.act) =(%unbundle -.act))
~
(update-subscribers /keys act)
==
::
--

View File

@ -38,22 +38,18 @@
++ prep
|= old=(unit state)
^- (quip move _this)
?~ old
[~ this]
[~ this(+<+ u.old)]
[~ ?~(old this this(+<+ u.old))]
::
++ poke-json
|= =json
^- (quip move _this)
?. =(src.bol our.bol)
[~ this]
?> (team:title our.bol src.bol)
(poke-permission-group-hook-action (json-to-perm-group-hook-action json))
::
++ poke-permission-group-hook-action
|= act=permission-group-hook-action
^- (quip move _this)
?. =(src.bol our.bol)
[~ this]
?> (team:title our.bol src.bol)
?- -.act
%associate (handle-associate group.act permissions.act)
%dissociate (handle-dissociate group.act permissions.act)
@ -64,25 +60,21 @@
^- (quip move _this)
=/ perms (~(get by relation) group)
:: if relation does not exist, create it and subscribe.
=/ permissions
%- silt
%+ turn ~(tap in permission-paths)
|= [=path =kind]
path
=/ permissions=(set path)
%- ~(run in permission-paths)
|=([=path =kind] path)
?~ perms
=/ group-path [%group group]
:_ this(relation (~(put by relation) group permissions))
[ost.bol %peer group-path [our.bol %group-store] group-path]~
::
=. u.perms (~(uni in u.perms) permissions)
:_ this(relation (~(put by relation) group u.perms))
%+ weld
%+ turn ~(tap in permissions)
|= =path
^- move
(permission-poke path [%delete path])
|=(=path (permission-poke path [%delete path]))
%+ turn ~(tap in permission-paths)
|= [=path =kind]
^- move
=/ pem *permission
=. kind.pem kind
(permission-poke path [%create path pem])
@ -93,13 +85,12 @@
=/ perms (~(get by relation) group)
?~ perms
[~ this]
::
=. permissions (~(del in u.perms) permissions)
?~ permissions
:_ this(relation (~(del by relation) group))
:~ (group-pull [%group group])
==
:- ~
this(relation (~(put by relation) group permissions))
[(group-pull [%group group])]~
[~ this(relation (~(put by relation) group permissions))]
::
++ diff-group-update
|= [wir=wire diff=group-update]
@ -117,7 +108,6 @@
:_ this
%+ turn ~(tap in u.perms)
|= =path
^- move
(permission-poke path [%add path members.diff])
::
%add
@ -128,7 +118,6 @@
:_ this
%+ turn ~(tap in u.perms)
|= =path
^- move
(permission-poke path [%add path members.diff])
::
%remove
@ -139,7 +128,6 @@
:_ this
%+ turn ~(tap in u.perms)
|= =path
^- move
(permission-poke path [%remove path members.diff])
::
%unbundle
@ -147,15 +135,12 @@
=/ perms (~(get by relation) pax.diff)
?~ perms
:_ this(relation (~(del by relation) pax.diff))
:~ (group-pull [%group pax.diff])
==
[(group-pull [%group pax.diff])]~
:_ this(relation (~(del by relation) pax.diff))
:- (group-pull [%group pax.diff])
%+ turn ~(tap in u.perms)
|= =path
^- move
(permission-poke path [%delete path])
::
==
::
++ quit
@ -183,14 +168,4 @@
^- move
[ost.bol %pull [%group path] [our.bol %group-store] ~]
::
++ permission-scry
|= pax=path
^- (unit permission)
=. pax ;: weld
`path`/=permission-store/(scot %da now.bol)/permission
pax
`path`/noun
==
.^((unit permission) %gx pax)
::
--

View File

@ -25,17 +25,24 @@
++ peer-all
|= =path
^- (quip move _this)
?. =(src.bol our.bol) !!
?> (team:title our.bol src.bol)
:: we now proxy all events to this path
:_ this
[ost.bol %diff %permission-initial permissions]~
::
++ peer-updates
|= =path
^- (quip move _this)
?> (team:title our.bol src.bol)
:: we now proxy all events to this path
[~ this]
::
++ peer-permission
|= =path
^- (quip move _this)
?~ path !!
?. =(src.bol our.bol) !!
?. (~(has by permissions) path) !!
?> (team:title our.bol src.bol)
?> (~(has by permissions) path)
:_ this
[ost.bol %diff %permission-update [%create path (~(got by permissions) path)]]~
::
@ -67,8 +74,7 @@
++ poke-permission-action
|= action=permission-action
^- (quip move _this)
?. =(src.bol our.bol)
[~ this]
?> (team:title our.bol src.bol)
?- -.action
%add (handle-add action)
%remove (handle-remove action)
@ -159,20 +165,20 @@
(handle-add [%add +.act])
(handle-remove [%remove +.act])
::
++ send-diff
|= [pax=path update=permission-update]
++ update-subscribers
|= [pax=path upd=permission-update]
^- (list move)
;: weld
^- (list move)
%+ turn (prey:pubsub:userlib /all bol)
|= [=bone *]
[bone %diff %permission-update update]
::
^- (list move)
%+ turn (prey:pubsub:userlib [%permission pax] bol)
|= [=bone *]
[bone %diff %permission-update update]
::
%+ turn (prey:pubsub:userlib pax bol)
|= [=bone *]
[bone %diff %permission-update upd]
::
++ send-diff
|= [pax=path upd=permission-update]
^- (list move)
%- zing
:~ (update-subscribers /all upd)
(update-subscribers /updates upd)
(update-subscribers [%permission pax] upd)
==
::
--

238
pkg/arvo/lib/chat-json.hoon Normal file
View File

@ -0,0 +1,238 @@
/- *chat-store, *chat-view
/+ chat-eval
|%
::
++ slan |=(mod/@tas |=(txt/@ta (need (slaw mod txt))))
::
++ seri ::: serial
=, dejs:format
^- $-(json serial)
(cu (slan %uv) so)
::
++ re :: recursive reparsers
|* {gar/* sef/_|.(fist:dejs-soft:format)}
|= jon/json
^- (unit _gar)
=- ~! gar ~! (need -) -
((sef) jon)
::
++ dank :: tank
^- $-(json (unit tank))
=, ^? dejs-soft:format
%+ re *tank |. ~+
%- of :~
leaf+sa
palm+(ot style+(ot mid+sa cap+sa open+sa close+sa ~) lines+(ar dank) ~)
rose+(ot style+(ot mid+sa open+sa close+sa ~) lines+(ar dank) ~)
==
::
++ eval ::: %exp speech
::: extract contents of an %exp speech, evaluating
::: the {exp} if there is no {res} yet.
::
|= a=json
^- [cord (list tank)]
=, ^? dejs-soft:format
=+ exp=((ot expression+so ~) a)
%- need
?~ exp [~ '' ~]
:+ ~ u.exp
::NOTE when sending, if output is an empty list, chat-store will evaluate
(fall ((ot output+(ar dank) ~) a) ~)
::
++ lett
|= =letter
^- json
=, enjs:format
?- -.letter
%text
(frond %text s+text.letter)
::
%url
(frond %url s+url.letter)
::
%code
%+ frond %code
%- pairs
:~ [%expression s+expression.letter]
[%output a+(turn output.letter tank)]
==
::
%me
(frond %me s+narrative.letter)
::
==
::
++ enve
|= =envelope
^- json
=, enjs:format
%- pairs
:~ [%uid s+(scot %uv uid.envelope)]
[%number (numb number.envelope)]
[%author (ship author.envelope)]
[%when (time when.envelope)]
[%letter (lett letter.envelope)]
==
::
++ conf
|= =config
^- json
=, enjs:format
%- pairs
:~ [%length (numb length.config)]
[%read (numb read.config)]
==
::
++ inbox-to-configs
|= =inbox
^- chat-configs
%- ~(run by inbox)
|= =mailbox
^- config
config.mailbox
::
++ configs-to-json
|= cfg=chat-configs
=, enjs:format
^- json
%+ frond %chat-configs
%- pairs
%+ turn ~(tap by cfg)
|= [pax=^path =config]
^- [cord json]
[(spat pax) (conf config)]
::
++ inbox-to-json
|= box=inbox
=, enjs:format
^- json
%+ frond %chat-initial
%- pairs
%+ turn ~(tap by box)
|= [pax=^path =mailbox]
^- [cord json]
:- (spat pax)
%- pairs
:~ [%envelopes [%a (turn envelopes.mailbox enve)]]
[%config (conf config.mailbox)]
==
::
++ update-to-json
|= upd=chat-update
=, enjs:format
^- json
%+ frond %chat-update
%- pairs
:~
?: =(%message -.upd)
?> ?=(%message -.upd)
:- %message
%- pairs
:~ [%path (path path.upd)]
[%envelope (enve envelope.upd)]
==
?: =(%read -.upd)
?> ?=(%read -.upd)
[%read (pairs [%path (path path.upd)]~)]
?: =(%create -.upd)
?> ?=(%create -.upd)
:- %create
%- pairs
:~ [%ship (ship ship.upd)]
[%path (path path.upd)]
==
?: =(%delete -.upd)
?> ?=(%delete -.upd)
[%delete (pairs [%path (path path.upd)]~)]
?: =(%config -.upd)
?> ?=(%config -.upd)
:- %config
%- pairs
:~ [%path (path path.upd)]
[%config (conf config.upd)]
==
[*@t *^json]
==
::
++ json-to-action
|= jon=json
^- chat-action
=, dejs:format
=< (parse-json jon)
|%
++ parse-json
%- of
:~ [%create create]
[%delete delete]
[%message message]
[%read read]
==
::
++ create
%- ot
:~ [%ship (su ;~(pfix sig fed:ag))]
[%path pa]
==
::
++ delete
(ot [%path pa]~)
::
++ message
%- ot
:~ [%path pa]
[%envelope envelope]
==
::
++ read
(ot [%path pa] ~)
::
++ envelope
%- ot
:~ [%uid seri]
[%number ni]
[%author (su ;~(pfix sig fed:ag))]
[%when di]
[%letter letter]
==
::
++ letter
%- of
:~ [%text so]
[%url so]
[%code eval]
[%me so]
==
::
--
::
++ json-to-view-action
|= jon=json
^- chat-view-action
=, dejs:format
=< (parse-json jon)
|%
++ parse-json
%- of
:~ [%create create]
[%delete delete]
==
::
++ create
%- ot
:~ [%path pa]
[%security sec]
[%read (as (su ;~(pfix sig fed:ag)))]
[%write (as (su ;~(pfix sig fed:ag)))]
==
::
++ delete
(ot [%path pa]~)
::
++ sec
=, dejs:format
^- $-(json chat-security)
(su (perk %channel %village %journal %mailbox ~))
--
--

View File

@ -0,0 +1,16 @@
|%
++ eval
|= [=bowl:gall =hoon]
^- (list tank)
=/ subj=[our=@p now=@da eny=@uvJ]
:+ our.bowl
now.bowl
(shaz (cat 3 (mix [now eny]:bowl) %eny))
::
;; (list tank)
=< +>
%+ mong
:- mute
|.([(sell (slap (slop !>(subj) !>(..zuse)) hoon))]~)
|=(^ ~)
--

View File

@ -76,52 +76,42 @@
|= [our/ship lit/?]
%- ~(gas in *(set well:gall))
^- (list well:gall)
?: lit
:~ [%home %dojo]
[%home %azimuth-tracker]
==
=+ myr=(clan:title our)
:: boot all default apps off the home desk
::
?: ?=($pawn myr)
:~ [%home %lens]
[%base %hall]
[%base %talk]
[%base %dojo]
[%base %modulo]
[%home %launch]
[%home %chat]
[%home %publish]
[%home %clock]
[%home %weather]
[%home %group-store]
[%home %group-hook]
[%home %permission-store]
[%home %permission-group-hook]
==
:~ [%home %lens]
[%home %acme]
[%home %dns]
[%home %dojo]
[%home %hall]
[%home %talk]
[%home %modulo]
[%home %launch]
[%home %chat]
[%home %publish]
[%home %clock]
[%home %weather]
[%home %group-store]
[%home %group-hook]
[%home %permission-store]
[%home %permission-group-hook]
[%home %azimuth-tracker]
=- (turn - |=(a=term home+a))
^- (list term)
?: lit
:~ %dojo
%azimuth-tracker
==
%+ welp
?: ?=(%pawn (clan:title our)) ~
:~ %acme
%dns
%azimuth-tracker
==
:~ %lens
%dojo
%modulo
%launch
%publish
%clock
%weather
%group-store
%group-hook
%permission-store
%permission-group-hook
%chat-store
%chat-hook
%chat-view
%chat-cli
==
::
++ deft-fish :: default connects
|= our/ship
%- ~(gas in *(set gill:gall))
^- (list gill:gall)
[[our %talk] [our %dojo] ~]
[[our %chat-cli] [our %dojo] ~]
::
++ make :: initial part
|= our/ship

View File

@ -1,58 +1,11 @@
::
::
/- chat, hall
/+ hall-json
::
|_ act=action:chat
++ grow
|%
++ tank !!
--
::
/+ *chat-json
=, dejs:format
|_ act=chat-action
++ grab
|%
++ noun action:chat
++ json
++ noun chat-action
++ json
|= jon=^json
=< (parse-chat-action jon)
|%
::
++ hall-action
=, dejs:hall-json
=, dejs-soft:format
|= a=^json
^- action:hall
=- (need ((of -) a))
:~ create+(ot nom+so des+so sec+secu ~)
design+(ot nom+so cof+conf ~)
delete+(ot nom+so why+(mu so) ~)
depict+(ot nom+so des+so ~)
filter+(ot nom+so fit+filt ~)
permit+(ot nom+so inv+bo sis+(as (su fed:ag)) ~)
source+(ot nom+so sub+bo srs+(as sorc) ~)
read+(ot nom+so red+ni ~)
usage+(ot nom+so add+bo tas+(as so) ~)
newdm+(ot sis+(as (su fed:ag)) ~)
::
convey+(ar thot)
phrase+(ot aud+audi ses+(ar spec:dejs:hall-json) ~)
::
notify+(ot aud+audi pes+(mu pres) ~)
naming+(ot aud+audi man+huma ~)
::
glyph+(ot gyf+so aud+audi bin+bo ~)
nick+(ot who+(su fed:ag) nic+so ~)
::
public+(ot add+bo cir+circ ~)
==
::
++ parse-chat-action
=, dejs:format
%- of
:~
[%actions (ot lis+(ar hall-action) ~)]
==
::
--
(json-to-action jon)
--
--

View File

@ -1,48 +1,14 @@
/+ *chat-json
|_ cfg=config
::
::
/? 309
::
/- chat, hall
/+ hall-json
::
|_ str=streams:chat
++ grow
|%
++ json
=, enjs:format
^- ^json
%+ frond %chat
%- pairs
:~
::
[%inbox (conf:enjs:hall-json inbox.str)]
::
:- %configs
%- pairs
%+ turn ~(tap by configs.str)
|= [cir=circle:hall con=(unit config:hall)]
^- [@t ^json]
:- (crip (circ:en-tape:hall-json cir))
?~(con ~ (conf:enjs:hall-json u.con))
::
:- %circles :- %a
%+ turn ~(tap in circles.str)
|= nom=name:hall
[%s nom]
::
:- %peers
%- pairs
%+ turn ~(tap by peers.str)
|= [cir=circle:hall per=(set @p)]
^- [@t ^json]
:- (crip (circ:en-tape:hall-json cir))
[%a (turn ~(tap in per) ship)]
::
==
++ json (conf cfg)
--
::
++ grab
|%
++ noun streams:chat
++ noun config
--
::
--

View File

@ -0,0 +1,14 @@
/+ *chat-json
|_ cfg=(map path config)
::
++ grow
|%
++ json (configs-to-json cfg)
--
::
++ grab
|%
++ noun chat-configs
--
::
--

View File

@ -0,0 +1,38 @@
/- *chat-hook
=, dejs:format
|_ act=chat-hook-action
++ grab
|%
++ noun chat-hook-action
++ json
|= jon=^json
=< (parse-chat-hook-action jon)
|%
++ parse-chat-hook-action
%- of
:~
[%add-owned add-owned]
[%add-synced add-synced]
[%remove pa]
==
::
++ add-owned
%- ot
:~ [%path pa]
[%security sec]
==
::
++ add-synced
%- ot
:~ [%ship (su ;~(pfix sig fed:ag))]
[%path pa]
==
::
++ sec
^- $-(^json chat-security)
(su (perk %channel %village %journal %mailbox ~))
::
--
--
--

View File

@ -0,0 +1,14 @@
/+ *chat-json
|_ box=inbox
::
++ grow
|%
++ json (inbox-to-json box)
--
::
++ grab
|%
++ noun inbox
--
::
--

View File

@ -1,96 +1,13 @@
::
::
/? 309
::
/- chat, hall
/+ hall-json
::
|_ upd=update:chat
/+ *chat-json
|_ upd=chat-update
++ grow
|%
++ json
=, enjs:format
^- ^json
%+ frond %update
%- pairs
:~
::
:: %inbox
?: =(%inbox -.upd)
?> ?=(%inbox -.upd)
[%inbox (conf:enjs:hall-json con.upd)]
::
:: %message
?: =(%message -.upd)
?> ?=(%message -.upd)
:- %message
%- pairs
:~
[%circle (circ:enjs:hall-json cir.upd)]
[%envelope (enve:enjs:hall-json env.upd)]
==
::
:: %messages
?: =(%messages -.upd)
?> ?=(%messages -.upd)
:- %messages
%- pairs
:~
[%circle (circ:enjs:hall-json cir.upd)]
[%start (numb start.upd)]
[%end (numb end.upd)]
[%envelopes [%a (turn env.upd enve:enjs:hall-json)]]
==
::
:: %config
?: =(%config -.upd)
?> ?=(%config -.upd)
:- %config
%- pairs
:~
[%circle (circ:enjs:hall-json cir.upd)]
[%config (conf:enjs:hall-json con.upd)]
==
::
:: %circles
?: =(%circles -.upd)
?> ?=(%circles -.upd)
:- %circles
%- pairs
:~
:- %circles
:- %a
%+ turn ~(tap in cir.upd)
|= nom=name:hall
[%s nom]
==
::
:: %peers
?: =(%peers -.upd)
?> ?=(%peers -.upd)
:- %peers
%- pairs
:~
[%circle (circ:enjs:hall-json cir.upd)]
[%peers [%a (turn ~(tap in per.upd) ship:enjs:format)]]
==
::
:: %delete
?: =(%delete -.upd)
?> ?=(%delete -.upd)
:- %delete
%- pairs
:~
[%circle (circ:enjs:hall-json cir.upd)]
==
::
:: %noop
[*@t *^json]
==
++ json (update-to-json upd)
--
::
++ grab
|%
++ noun update:chat
++ noun chat-update
--
::
--

View File

@ -0,0 +1,11 @@
/+ *chat-json
=, dejs:format
|_ act=chat-action
++ grab
|%
++ noun chat-view-action
++ json
|= jon=^json
(json-to-view-action jon)
--
--

View File

@ -0,0 +1,23 @@
|%
+$ chat-security
$? $channel :: black r, black w
$village :: white r, white w
$journal :: black r, white w
$mailbox :: white r, black w
==
::
+$ chat-hook-action
$% :: %add-owned: make a chatroom accessible to foreign ships
:: specified by the chat-security model
::
[%add-owned =path security=chat-security]
:: %add-synced: mirror a foreign chatroom to our chat-store
::
[%add-synced =ship =path]
:: %remove: stop mirroring a foreign chatroom or allowing a local
:: chatroom to be mirrored
::
[%remove =path]
==
--

View File

@ -0,0 +1,45 @@
|%
+$ serial @uvH
::
+$ letter
$% [%text text=cord]
[%url url=cord]
[%code expression=cord output=(list tank)]
[%me narrative=cord]
==
::
+$ envelope
$: uid=serial
number=@
author=ship
when=time
=letter
==
::
+$ config
$: length=@
read=@
==
::
+$ mailbox
$: =config
envelopes=(list envelope)
==
::
+$ inbox (map path mailbox)
::
+$ chat-configs (map path config)
::
+$ chat-action
$% [%create =ship =path] :: %create: create a mailbox at ~ship/path
[%delete =path] :: %delete: delete a mailbox at path
[%message =path =envelope] :: %message: append a message to mailbox
[%read =path] :: %read: set mailbox to read
==
::
+$ chat-update
$% [%keys keys=(set path)]
[%config =path =config]
chat-action
==
--

View File

@ -0,0 +1,13 @@
|%
+$ chat-security
$? $channel :: black r, black w
$village :: white r, white w
$journal :: black r, white w
$mailbox :: white r, black w
==
::
+$ chat-view-action
$% [%create =path security=chat-security read=(set ship) write=(set ship)]
[%delete =path]
==
--

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ p, h1, h2, h3, h4, h5, h6, a, input, textarea, button {
padding: 0;
}
textarea, select, input, button {
textarea, input, button {
outline: none;
-webkit-appearance: none;
border: none;
@ -144,4 +144,4 @@ h2 {
.label-small-mono.list-ship {
line-height: 29px;
}
}

View File

@ -9,13 +9,58 @@ class UrbitApi {
setAuthTokens(authTokens) {
this.authTokens = authTokens;
this.bindPaths = [];
this.groups = {
bundle: this.groupBundle.bind(this),
unbundle: this.groupUnbundle.bind(this),
add: this.groupAdd.bind(this),
remove: this.groupRemove.bind(this)
};
this.chat = {
create: this.chatCreate.bind(this),
delete: this.chatDelete.bind(this),
message: this.chatMessage.bind(this),
read: this.chatRead.bind(this)
};
this.chatView = {
create: this.chatViewCreate.bind(this),
delete: this.chatViewDelete.bind(this),
};
this.invite = {
create: this.inviteCreate.bind(this),
delete: this.inviteDelete.bind(this),
accept: this.inviteAccept.bind(this),
decline: this.inviteDecline.bind(this),
invite: this.inviteInvite.bind(this)
};
this.permissions = {
create: this.permissionCreate.bind(this),
delete: this.permissionDelete.bind(this),
add: this.permissionAdd.bind(this),
remove: this.permissionRemove.bind(this)
};
this.chatHook = {
addOwned: this.chatHookAddOwned.bind(this),
addSynced: this.chatHookAddSynced.bind(this),
remove: this.chatHookRemove.bind(this)
};
this.permissionGroupHook = {
associate: this.permissionGroupHookAssociate.bind(this),
dissociate: this.permissionGroupHookDissociate.bind(this)
};
}
// keep default bind to hall, since its bind procedure more complex for now AA
bind(path, method, ship = this.authTokens.ship, appl = "hall", success, fail) {
bind(path, method, ship = this.authTokens.ship, app, success, fail, quit) {
this.bindPaths = _.uniq([...this.bindPaths, path]);
window.subscriptionId = window.urb.subscribe(ship, appl, path,
window.subscriptionId = window.urb.subscribe(ship, app, path,
(err) => {
fail(err);
},
@ -28,35 +73,11 @@ class UrbitApi {
}
});
},
(err) => {
fail(err);
(qui) => {
quit(qui);
});
}
hall(data) {
this.action("hall", "json", data);
}
addPendingMessage(data) {
let pendingMap = store.state.pendingMessages;
if (pendingMap.has(data.aud[0])) {
pendingMap.get(data.aud[0]).push(data)
} else {
pendingMap.set(data.aud[0], [data])
}
store.setState({
pendingMessages: pendingMap
});
}
chat(lis) {
this.action("chat", "chat-action", {
actions: {
lis
}
});
}
action(appl, mark, data) {
return new Promise((resolve, reject) => {
window.urb.poke(ship, appl, mark, data,
@ -69,90 +90,228 @@ class UrbitApi {
});
}
notify(aud, bool) {
this.hall({
notify: {
aud,
pes: !!bool ? 'hear' : 'gone'
}
});
}
permit(cir, aud, message) {
this.hall({
permit: {
nom: cir,
sis: aud,
inv: true
}
});
if (message) {
this.invite(cir, aud);
addPendingMessage(msg) {
if (store.state.pendingMessages.has(msg.path)) {
store.state.pendingMessages.get(msg.path).push(msg.envelope);
} else {
store.state.pendingMessages.set(msg.path, [msg.envelope]);
}
store.setState({
pendingMessages: store.state.pendingMessages
});
}
unpermit(cir, ship) {
/*
* lol, never send an unpermit to yourself.
* it puts your ship into an infinite loop.
* */
if (ship === window.ship) {
return;
}
this.hall({
permit: {
nom: cir,
sis: [ship],
inv: false
groupsAction(data) {
this.action("group-store", "group-action", data);
}
groupBundle(path) {
this.groupsAction({ bundle: path });
}
groupUnbundle(path) {
this.groupsAction({ unbundle: path });
}
groupAdd(members, path) {
this.groupsAction({
add: {
members, path
}
});
}
invite(cir, aud) {
let audInboxes = aud.map((aud) => `~${aud}/i`);
let inviteMessage = {
aud: audInboxes,
ses: [{
inv: {
inv: true,
cir: `~${window.ship}/${cir}`
groupRemove(members, path) {
this.groupsAction({
remove: {
members, path
}
});
}
chatAction(data) {
this.action("chat-store", "json", data);
}
chatCreate(path, owner = `~${window.ship}`) {
this.chatAction({
create: {
path, owner
}
});
}
chatDelete(path) {
this.chatAction({ delete: { path } });
}
chatMessage(path, author, when, letter) {
let data = {
message: {
path,
envelope: {
uid: uuid(),
number: 0,
author,
when,
letter
}
}]
}
};
this.hall({
phrase: inviteMessage
this.chatHookAction(data, "json");
this.addPendingMessage(data.message);
}
chatRead(path, read) {
this.chatAction({ read: { path } });
}
chatViewAction(data) {
console.log(data);
this.action("chat-view", "json", data);
}
chatViewCreate(path, security, read, write) {
this.chatViewAction({
create: {
path, security, read, write
}
});
}
source(nom, sub) {
this.hall({
source: {
nom: "inbox",
sub: sub,
srs: [nom]
}
})
chatViewDelete(path) {
this.chatViewAction({ delete: { path } });
}
delete(nom) {
this.hall({
delete: {
nom,
why: ''
}
})
inviteAction(data) {
console.log(data);
this.action("invite-store", "json", data);
}
read(nom, red) {
this.hall({
read: {
nom,
red
}
})
inviteCreate(path) {
this.inviteAction({ create: { path } });
}
inviteDelete(path) {
this.inviteAction({ delete: { path } });
}
inviteAccept(path, uid) {
this.inviteAction({ accept: { path, uid } });
}
inviteDecline(path, uid) {
this.inviteAction({ decline: { path, uid } });
}
inviteInvite(path, peerPath, ship, app, text, recipient) {
this.inviteAction({
invite: {
path,
invite: {
'peer-path': peerPath,
dock: { ship, app },
text, recipient
},
uid: uuid()
}
});
}
chatHookAction(data, mark = "chat-hook-action") {
this.action("chat-hook", mark, data);
}
chatHookAddOwned(path, security) {
let data = {};
data['add-owned'] = {
path, security
};
this.chatHookAction(data);
}
chatHookRemove(path) {
this.chatHookAction({ remove: path });
}
chatHookAddSynced(ship, path) {
let data = {};
data['add-synced'] = {
ship, path
};
this.chatHookAction(data);
}
permissionAction(data) {
this.action("permission-store", "permission-action", data);
}
permissionCreate(path, kind, who) {
this.permissionAction({
create: {
path, kind, who
}
});
}
permissionDelete(path) {
this.permissionAction({ delete: { path } });
}
permissionAdd(path, who) {
this.permissionAction({
add: {
path, who
}
});
}
permissionRemove(path, who) {
this.permissionAction({
remove: {
path, who
}
});
}
permissionAllow(path, who) {
this.permissionAction({
allow: {
path, who
}
});
}
permissionDeny(path, who) {
this.permissionAction({
deny: {
path, who
}
});
}
permissionGroupHookAction(data) {
this.action("permission-group-hook", "permission-group-hook-action", data);
}
permissionGroupHookAssociate(group, permissions) {
this.permissionGroupHookAction({
associate: {
group, permissions
}
});
}
permissionGroupHookDissociate() {
this.permissionGroupHookAction({
dissociate: {
group, permissions
}
});
}
}
export let api = new UrbitApi();

View File

@ -5,6 +5,7 @@ import _ from 'lodash';
import { Message } from '/components/lib/message';
import { ChatTabBar } from '/components/lib/chat-tabbar';
import { ChatInput } from '/components/lib/chat-input';
import { deSig } from '/lib/util';
export class ChatScreen extends Component {
@ -12,16 +13,11 @@ export class ChatScreen extends Component {
super(props);
this.state = {
station: props.match.params.ship + "/" + props.match.params.station,
circle: props.match.params.station,
host: props.match.params.ship,
numPeople: 0,
station: `/${props.match.params.ship}/${props.match.params.station}`,
numPages: 1,
scrollLocked: false,
};
this.pendingQueue = props.pendingMessages;
this.hasAskedForMessages = false;
this.onScroll = this.onScroll.bind(this);
@ -32,7 +28,6 @@ export class ChatScreen extends Component {
}
componentDidMount() {
this.updateNumPeople();
this.updateReadNumber();
}
@ -46,22 +41,16 @@ export class ChatScreen extends Component {
componentDidUpdate(prevProps, prevState) {
const { props, state } = this;
if (prevProps.match.params.ship !== props.match.params.ship ||
prevProps.match.params.station !== props.match.params.station
) {
console.log('switched circle');
if ((prevProps.match.params.station !== props.match.params.station) ||
(prevProps.match.params.ship !== props.match.params.ship)) {
this.hasAskedForMessages = false;
clearInterval(this.updateReadInterval);
this.setState({
station: props.match.params.ship + "/" + props.match.params.station,
circle: props.match.params.station,
host: props.match.params.ship,
numPeople: 0,
station: `/${props.match.params.ship}/${props.match.params.station}`,
scrollLocked: false
}, () => {
this.updateNumPeople();
this.scrollToBottom();
this.updateReadInterval = setInterval(
this.updateReadNumber.bind(this),
@ -69,54 +58,40 @@ export class ChatScreen extends Component {
);
this.updateReadNumber();
});
} else if (!(state.station in props.configs)) {
} else if (Object.keys(props.inbox).length === 0) {
props.history.push('/~chat');
} else if (props.envelopes.length - prevProps.envelopes.length >= 200) {
this.hasAskedForMessages = false;
}
}
updateReadNumber() {
const { props, state } = this;
let internalCircle = 'hall-internal-' + state.circle;
let internalStation = `~${window.ship}/${internalCircle}`;
let internalConfig = props.configs[internalStation] || false;
let regularConfig = props.configs[state.station] || false;
let config = internalConfig || regularConfig;
let messages = props.messages;
let lastMsgNum = (messages.length > 0) ?
( messages[messages.length - 1].num + 1 ) : 0;
if (config && config.red < lastMsgNum) {
if (internalConfig) {
props.api.read(internalCircle, lastMsgNum);
} else {
props.api.read(state.circle, lastMsgNum);
}
if (props.read < props.envelopes.length) {
props.api.chat.read(state.station);
}
}
askForMessages() {
const { props, state } = this;
let messages = props.messages;
if (state.numPages * 50 < props.messages.length - 200 ||
if (state.numPages * 100 < props.envelopes.length - 400 ||
this.hasAskedForMessages) {
return;
}
if (messages.length > 0) {
let end = messages[0].num;
if (props.envelopes.length > 0) {
let end = props.envelopes[0].number;
if (end > 0) {
let start = ((end - 400) > 0) ? end - 400 : 0;
if (start === 0 && end === 1) {
return;
}
this.hasAskedForMessages = true;
console.log('fetching new messages');
props.subscription.fetchMessages(state.station, start, end - 1);
props.subscription.fetchMessages(start, end - 1, state.station);
}
}
}
@ -140,7 +115,7 @@ export class ChatScreen extends Component {
});
} else if (
(e.target.scrollHeight - Math.round(e.target.scrollTop)) ===
e.target.clientHeight
(e.target.clientHeight)
) {
this.setState({
numPages: 1,
@ -155,8 +130,8 @@ export class ChatScreen extends Component {
scrollLocked: false
});
} else if (
(e.target.scrollHeight + Math.round(e.target.scrollTop)) ===
e.target.clientHeight
(e.target.scrollHeight + Math.round(e.target.scrollTop)) <=
(e.target.clientHeight + 10)
) {
this.setState({
numPages: this.state.numPages + 1,
@ -170,104 +145,75 @@ export class ChatScreen extends Component {
}
}
updateNumPeople() {
let conf = this.props.configs[this.state.station] || {};
let sis = _.get(conf, 'con.sis');
let numPeople = !!sis ? sis.length : 0;
if (numPeople !== this.state.numPeople) {
this.setState({ numPeople });
}
}
render() {
const { props, state } = this;
let config = props.configs[state.station] || {};
let messages = props.messages.slice(0);
let messages = props.envelopes.slice(0);
// Pending messages get pinned to the bottom of the messages queue.
let pendingInRoom =
(this.pendingQueue.has(this.state.station))
? this.pendingQueue.get(this.state.station) : [];
pendingInRoom.map(function(value) {
return value.pending = true;
})
messages = messages.concat(pendingInRoom);
let lastMsgNum = (messages.length > 0) ?
messages[messages.length - 1].num : 0;
messages.length : 0;
if (messages.length > 50 * state.numPages) {
if (messages.length > 100 * state.numPages) {
messages = messages
.slice(messages.length - (50 * state.numPages), messages.length);
.slice(messages.length - (100 * state.numPages), messages.length);
}
let reversedMessages = messages.reverse();
let chatMessages = reversedMessages.map((msg, i) => {
let pendingMessages =
props.pendingMessages.has(state.station)
? props.pendingMessages.get(state.station) : [];
pendingMessages.map(function(value) {
return value.pending = true;
})
let reversedMessages = messages.concat(pendingMessages);
reversedMessages = reversedMessages.reverse();
reversedMessages = reversedMessages.map((msg, i) => {
// Render sigil if previous message is not by the same sender
let gamAut = ['gam', 'aut'];
// Local messages don't have a 'gam' prop, so look for the top level if it doesn't exist.
let aut = msg.aut ? msg.aut : null;
// No gamAut? Return top level author for the same sender check.
let aut = ['author'];
let renderSigil =
_.get(reversedMessages[i + 1], gamAut) !== _.get(msg, gamAut, aut);
// More padding top if previous message is not by the same sender
_.get(reversedMessages[i + 1], aut) !== _.get(msg, aut, msg.author);
let paddingTop = renderSigil;
// More padding bot if next message is not by the same sender
let paddingBot =
_.get(reversedMessages[i - 1], gamAut) !== _.get(msg, gamAut, aut);
// Non-local ships don't have pending props.
if (!msg.pending) {
var pending = false;
}
// Non-local ships don't have pending props.
if (!pending) {
var pending = false;
}
_.get(reversedMessages[i - 1], aut) !== _.get(msg, aut, msg.author);
return (
<Message
key={msg.gam ? msg.gam.uid : msg.uid}
msg={msg.gam ? msg.gam : msg}
key={msg.uid}
msg={msg}
renderSigil={renderSigil}
paddingTop={paddingTop}
paddingBot={paddingBot}
pending={!!pending}/>
paddingBot={paddingBot}
pending={!!msg.pending} />
);
});
let peers = props.peers[state.station] || [window.ship];
let group = Array.from(props.group.values());
return (
<div key={state.station}
className="h-100 w-100 overflow-hidden flex flex-column">
<div className='pl3 pt2 bb'>
<h2>{state.circle}</h2>
<h2>{state.station.substr(1)}</h2>
<ChatTabBar {...props}
station={state.station}
numPeers={peers.length} />
numPeers={group.length}
isOwner={deSig(props.match.params.ship) === window.ship} />
</div>
<div
className="overflow-y-scroll pt3 pb2 flex flex-column-reverse"
style={{ height: 'calc(100% - 157px)', resize: 'vertical' }}
onScroll={this.onScroll}>
<div ref={ el => { this.scrollElement = el; }}></div>
{chatMessages}
{reversedMessages}
</div>
<ChatInput
api={props.api}
numMsgs={lastMsgNum}
station={state.station}
circle={state.circle}
security={!!config ? config.con : {}}
owner={deSig(props.match.params.ship)}
permissions={props.permissions}
placeholder='Message...' />
</div>
)

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import classnames from 'classnames';
export class LandingScreen extends Component {
export class JoinScreen extends Component {
constructor(props) {
super(props);
@ -10,51 +10,29 @@ export class LandingScreen extends Component {
componentDidMount() {
const { props } = this;
let station = props.match.params.ship + '/' + props.match.params.station;
let station = `/${props.match.params.ship}/${props.match.params.station}`;
if (station in props.configs) {
props.history.push(`/~chat/${station}`);
if (station in props.inbox) {
props.history.push(`/~chat/room${station}`);
}
}
componentDidUpdate(prevProps, prevState) {
const { props } = this;
let station = props.match.params.ship + '/' + props.match.params.station;
let station = '/' + props.match.params.station;
if (station in props.configs) {
props.history.push(`/~chat/${station}`);
if (station in props.inbox) {
props.history.push(`/~chat/room${station}`);
}
}
onClickSubscribe() {
const { props } = this;
let station = props.match.params.ship + '/' + props.match.params.station;
let actions = [
{
create: {
nom: 'hall-internal-' + props.match.params.station,
des: "chatroom",
sec: "channel"
}
},
{
source: {
nom: "inbox",
sub: true,
srs: [station]
}
},
{
source: {
nom: "inbox",
sub: true,
srs: [`~${window.ship}/hall-internal-${props.match.params.station}`]
}
}
];
this.props.api.chat(actions);
let ship = props.match.params.ship;
let station = `/${props.match.params.station}`;
props.api.chatHook.addSynced(ship, station);
this.props.history.push('/~chat');
}

View File

@ -15,47 +15,8 @@ export class ChatInput extends Component {
constructor(props) {
super(props);
/*let closure = () => {
let aud, sep;
let wen = Date.now();
let aut = window.ship;
aud = [props.station];
sep = {
lin: {
msg: Date.now().toString(),
pat: false
}
}
let uid;
let message;
for (var i = 0; i < 40; i++) {
uid = uuid();
wen = Date.now();
message = {
uid,
aut,
wen,
aud,
sep,
};
props.api.hall({
convey: [message]
});
}
setTimeout(closure, 1000);
};
setTimeout(closure, 2000);*/
this.state = {
message: '',
messageType: 'lin',
clipboard: null
};
this.textareaRef = React.createRef();
@ -63,6 +24,22 @@ export class ChatInput extends Component {
this.messageSubmit = this.messageSubmit.bind(this);
this.messageChange = this.messageChange.bind(this);
// perf testing:
/*let closure = () => {
for (var i = 0; i < 30; i++) {
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
{
text: `${Date.now()}`
}
);
}
setTimeout(closure, 1000);
};
setTimeout(closure, 2000);*/
moment.updateLocale('en', {
relativeTime : {
past: function(input) {
@ -86,11 +63,7 @@ export class ChatInput extends Component {
}
});
}
componentDidMount() {
this.bindShortcuts();
}
bindShortcuts() {
Mousetrap(this.textareaRef.current).bind('enter', e => {
e.preventDefault();
@ -101,45 +74,45 @@ export class ChatInput extends Component {
}
messageChange(event) {
const input = event.target.value;
const previous = this.state.message;
//NOTE dumb hack to work around paste event flow oddities
const pasted = (previous.length === 0 && input.length > 1);
if (input !== this.state.clipboard) {
this.setState({
message: input,
messageType: this.getSpeechType(input),
clipboard: (pasted ? input : null)
});
}
this.setState({
message: event.target.value
});
}
getSpeechType(input) {
if (input[0] === '#') {
return 'exp';
} else if (input.indexOf('\n') >= 0) {
return 'fat';
} else if (input[0] === '@') {
return 'lin@';
} else if (this.isUrl(input)) {
return 'url';
getLetterType(letter) {
if (letter[0] === '#') {
letter = letter.slice(1);
// remove insignificant leading whitespace.
// aces might be relevant to style.
while (letter[0] === '\n') {
letter = letter.slice(1);
}
return {
code: {
expression: letter,
output: undefined
}
}
} else if (letter[0] === '@') {
letter = letter.slice(1);
// remove insignificant leading whitespace.
// aces might be relevant to style.
while (letter[0] === '\n') {
letter = letter.slice(1);
}
return {
me: letter
}
} else if (this.isUrl(letter)) {
return {
url: letter
}
} else {
return 'lin';
}
}
getSpeechStyle(type, clipboard) {
switch (type) {
case 'lin@':
return 'fs-italic';
case 'url':
return 'td-underline';
case 'exp':
return 'code';
case 'fat':
if (clipboard) return 'code';
default:
return '';
return {
text: letter
}
}
}
@ -156,93 +129,6 @@ export class ChatInput extends Component {
}
}
// turns select urls into arvo:// urls
//
// we detect app names from the url. if the app is known to handle requests
// for remote data (instead of serving only from the host) we transfor the
// url into a generic arvo:// one.
// the app name format is pretty distinct and rare to find in the non-urbit
// wild, but this could still result in false positives for older-school
// websites serving pages under /~user paths.
// we could match only on ship.arvo.network, but that would exclude those
// running on localhost or under a custom domain.
//
//
globalizeUrl(url) {
const urlObject = new URL(url);
const app = urlObject.pathname.split('/')[1];
if (app === '~chat' ||
app === '~publish') {
//TODO send proper url speeches once hall starts using a url type that
// supports non-http protocols.
return { lin: {
msg: 'arvo://' + url.slice(urlObject.origin.length),
pat: false
} };
} else {
return {url};
}
}
speechFromInput(content, type, clipboard) {
switch (type) {
case 'lin':
return { lin: {
msg: content,
pat: false
} };
//
case 'lin@':
return { lin: {
msg: content.slice(1),
pat: true
} };
//
case 'url':
return this.globalizeUrl(content);
//
case 'exp':
// remove leading #
content = content.slice(1);
// remove insignificant leading whitespace.
// aces might be relevant to style.
while (content[0] === '\n') {
content = content.slice(1);
}
return { exp: {
exp: content
} };
//
case 'fat':
// clipboard contents
if (clipboard !== null) {
return { fat: {
sep: { lin: { msg: '', pat: false } },
tac: { name: {
nom: 'clipboard',
tac: { text: content }
} }
} };
// long-form message
} else {
const lines = content.split('\n');
return { fat: {
sep: { lin: {
msg: lines[0],
pat: false
} },
tac: { name: {
nom: 'long-form',
tac: { text: lines.slice(1).join('\n') }
} },
} };
}
//
default:
throw new Error('Unimplemented speech type', type);
}
}
messageSubmit() {
const { props, state } = this;
@ -250,29 +136,17 @@ export class ChatInput extends Component {
return;
}
let message = {
uid: uuid(),
aut: window.ship,
wen: Date.now(),
aud: [props.station],
sep: this.speechFromInput(
state.message,
state.messageType,
state.clipboard
)
};
let letter = this.getLetterType(state.message);
props.api.addPendingMessage(message);
props.api.hall(
{
convey: [message]
}
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
letter
);
this.setState({
message: '',
messageType: 'lin'
});
}
@ -293,14 +167,11 @@ export class ChatInput extends Component {
);
}
render() {
writeAccessRender() {
const { props, state } = this;
if (props.security && props.security.sec !== 'channel' &&
!props.security.sis.includes(window.ship)) {
return this.readOnlyRender();
}
this.bindShortcuts();
return (
<div className="pa3 cf flex black bt b--black-30" style={{ flexGrow: 1 }}>
<div className="fl" style={{
@ -312,9 +183,7 @@ export class ChatInput extends Component {
</div>
<div className="fr h-100 flex" style={{ flexGrow: 1 }}>
<textarea
className={'ml2 mt2 mr2 bn ' +
this.getSpeechStyle(state.messageType, state.clipboard)
}
className={'ml2 mt2 mr2 bn'}
style={{ flexGrow: 1, height: 40, paddingTop: 3, resize: 'none' }}
ref={this.textareaRef}
placeholder={props.placeholder}
@ -329,4 +198,29 @@ export class ChatInput extends Component {
</div>
);
}
render() {
const { props, state } = this;
let writePermission = props.permissions[`/chat${props.station}/write`];
if (writePermission) {
if (writePermission.kind === 'black') {
// black
if (writePermission.who.has(window.ship)) {
return this.readOnlyRender();
} else {
return this.writeAccessRender();
}
} else if (writePermission.kind === 'white') {
// white
if (writePermission.who.has(window.ship)) {
return this.writeAccessRender();
} else {
return this.readOnlyRender();
}
}
} else {
return this.writeAccessRender();
}
}
}

View File

@ -6,7 +6,8 @@ import classnames from 'classnames';
export class ChatTabBar extends Component {
render() {
let toBaseLink = '/~chat/' + this.props.station;
let props = this.props;
let toBaseLink = '/~chat/room' + props.station;
let bbStream = '',
bbMembers = '',
@ -16,12 +17,12 @@ export class ChatTabBar extends Component {
memColor = '',
setColor = '';
if (this.props.location.pathname.includes('/settings')) {
if (props.location.pathname.includes('/settings')) {
bbSettings = ' bb';
strColor = 'gray';
memColor = 'gray';
setColor = 'black';
} else if (this.props.location.pathname.includes('/members')) {
} else if (props.location.pathname.includes('/members')) {
bbMembers = ' bb';
strColor = 'gray';
memColor = 'black';
@ -33,8 +34,8 @@ export class ChatTabBar extends Component {
setColor = 'gray';
}
let membersText = this.props.numPeers === 1
? '1 Member' : `${this.props.numPeers} Members`;
let membersText = props.numPeers === 1
? '1 Member' : `${props.numPeers} Members`;
return (
<div className="w-100" style={{ height:28 }}>
@ -43,11 +44,14 @@ export class ChatTabBar extends Component {
className={'no-underline label-regular v-mid ' + strColor}
to={toBaseLink}>Stream</Link>
</div>
<div className={"dib h-100" + bbMembers} style={{width:'160px'}}>
<Link
className={'no-underline label-regular v-mid ' + memColor}
to={toBaseLink + '/members'}>{membersText}</Link>
</div>
{ !!props.isOwner ? (
<div className={"dib h-100" + bbMembers} style={{width:'160px'}}>
<Link
className={'no-underline label-regular v-mid ' + memColor}
to={toBaseLink + '/members'}>{membersText}</Link>
</div>
) : <div className="dib" style={{width:0}}></div>
}
<div className={"dib h-100" + bbSettings} style={{width:'160px'}}>
<Link
className={'no-underline label-regular v-mid ' + setColor}

View File

@ -0,0 +1,121 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { Sigil } from '/components/lib/icons/sigil';
import { deSig } from '/lib/util';
import urbitOb from 'urbit-ob';
export class InviteElement extends Component {
constructor(props) {
super(props);
this.state = {
members: '',
error: false,
success: false
};
}
modifyMembers() {
const { props, state } = this;
let aud = [];
let isValid = true;
if (state.members.length > 2) {
aud = state.members
.split(',')
.map((mem) => `~${deSig(mem.trim())}`);
aud.forEach((mem) => {
if (!urbitOb.isValidPatp(mem)) {
isValid = false;
}
});
}
if (!isValid || (state.members.length > 0 && state.members.length < 3)) {
this.setState({
error: true,
success: false
});
return;
}
if (this.textarea) {
this.textarea.value = '';
}
this.setState({
error: false,
success: true,
members: ''
}, () => {
props.api.groups.add(aud, props.path);
aud.forEach((recipient) => {
props.api.invite.invite(
'/chat',
`/mailbox${props.station}`,
window.ship,
'chat-hook',
`You have been invited to join ~${window.ship}/mailbox${props.station}`,
recipient
);
});
});
}
modifyMembersChange(e) {
this.setState({
members: e.target.value
});
}
render() {
const { props, state} = this;
let errorElem = !!state.error ? (
<p className="pt2 nice-red label-regular">Invalid ship name.</p>
) : (
<div></div>
);
let successElem = !!state.success ? (
<p className="pt2 nice-green label-regular">Success!</p>
) : (
<div></div>
);
let modifyButtonClasses = "label-regular black underline btn-font pointer";
if (!state.error) {
modifyButtonClasses = modifyButtonClasses + ' black';
}
let buttonText = '';
if (props.permissions.kind === 'black') {
buttonText = '-> Ban';
} else if (props.permissions.kind === 'white') {
buttonText = '-> Invite';
}
return (
<div>
<textarea
ref={ e => { this.textarea = e; } }
className="w-90 db ba overflow-y-hidden mono gray mb2"
style={{
resize: 'none',
height: 150
}}
spellCheck="false"
onChange={this.modifyMembersChange.bind(this)}></textarea>
<button
onClick={this.modifyMembers.bind(this)}
className={modifyButtonClasses}>
{buttonText}
</button>
{errorElem}
{successElem}
</div>
);
}
}

View File

@ -7,24 +7,23 @@ export class MemberElement extends Component {
onRemove() {
const { props } = this;
props.api.unpermit(props.circle, props.ship);
props.api.groups.remove([`~${props.ship}`], props.path);
}
render() {
const { props } = this;
let actionElem;
if (`~${props.ship}` === props.host) {
if (props.ship === props.owner) {
actionElem = (
<p className="dib w-10 underline black label-small-mono label-regular">
<p className="dib w-20 underline black label-small-mono label-regular">
Host
</p>
);
} else if (window.ship !== props.ship &&
`~${window.ship}` === props.host) {
} else if (window.ship !== props.ship && window.ship === props.owner) {
actionElem = (
<a onClick={this.onRemove.bind(this)}
className="w-10 dib list-ship black underline label-small-mono pointer">
className="w-20 dib list-ship black underline label-small-mono pointer">
Remove
</a>
);
@ -39,7 +38,7 @@ export class MemberElement extends Component {
<Sigil ship={props.ship} size={32} />
<p
className={
"w-80 dib v-mid black ml2 nowrap label-small-mono list-ship label-regular"
"w-70 dib v-mid black ml2 nowrap label-small-mono list-ship label-regular"
}>
{props.ship}
</p>

View File

@ -4,181 +4,61 @@ import classnames from 'classnames';
import moment from 'moment';
import _ from 'lodash';
export class Message extends Component {
renderSpeech(speech) {
if (_.has(speech, 'lin')) {
return this.renderLin(speech.lin.msg, speech.lin.pat);
} else if (_.has(speech, 'url')) {
return this.renderUrl(speech.url);
} else if (_.has(speech, 'exp')) {
return this.renderExp(speech.exp.exp, speech.exp.res);
} else if (_.has(speech, 'ire')) {
return this.renderSpeech(speech.ire.sep);
} else if (_.has(speech, 'app')) {
return this.renderSpeech(speech.app.sep);
} else if (_.has(speech, 'fat')) {
return this.renderFat(speech.fat.sep, speech.fat.tac);
} else {
return this.renderUnknown();
}
}
renderUnknown() {
return this.renderLin('<unknown message type>')
}
renderLin(content, action = false) {
if (content === '') {
return null;
}
//TODO remove once arvo:// urls are supported in url speeches
if (content.indexOf('arvo://') === 0) {
return this.renderUrl(content);
}
return (
<p className={`body-regular-400 v-top ${action ? 'fs-italic' : ''}`}>
{content}
</p>
);
}
renderUrl(url) {
try {
let urlObject = new URL(url);
let imgMatch =
/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF|GIF|webp|WEBP|webm|WEBM)$/
.exec(
urlObject.pathname
);
if (imgMatch) {
return this.renderImageUrl(url);
} else {
let localUrl = this.localizeUrl(url);
return this.renderAnchor(localUrl, url);
}
} catch(e) {
console.error('url render error', e);
return this.renderAnchor(url);
}
}
renderImageUrl(url) {
return this.renderAnchor(url, (
<img
src={url}
style={{
width:"50%",
maxWidth: '250px'
}}
></img>
));
}
renderAnchor(href, content) {
content = content || href;
return (
<a className="body-regular"
href={href}
target="_blank">{content}</a>
);
}
renderExp(expression, result) {
return (<>
<p>
<pre className="clamp-attachment pa1 mt0 mb0 bg-light-gray">
{expression}
</pre>
<pre className="clamp-attachment pa1 mt0 mb0">
{result[0].join('\n')}
</pre>
</p>
</>);
}
renderFat(speech, attachment) {
return (<>
{this.renderSpeech(speech)}
{this.renderAttachment(attachment)}
</>);
}
renderAttachment(content, title = '') {
if (_.has(content, 'name')) {
return this.renderAttachment(content.name.tac, content.name.nom);
}
return (<details>
<summary className="inter fs-italic">{'Attached: ' + title}</summary>
{ _.has(content, 'text')
? (title === 'long-form')
? this.renderParagraphs(content.text.split('\n'))
: this.renderPlaintext(content.text)
: _.has(content, 'tank')
? this.renderPlaintext(content.tank.join('\n'))
: null
}
</details>);
}
renderParagraphs(paragraphs) {
return (<div className="clamp-attachment">
{paragraphs.map(p => (<p className="mt2">{p}</p>))}
</div>);
}
renderPlaintext(text) {
return (<pre className="clamp-attachment">{text}</pre>);
}
renderContent() {
const { props } = this;
let letter = props.msg.letter;
try {
if (!_.has(props.msg, 'sep')) {
return this.renderUnknown();
}
return this.renderSpeech(props.msg.sep);
} catch (e) {
console.error('speech rendering error', e);
return this.renderUnknown();
}
}
renderAuthor() {
const msg = this.props.msg;
const ship = '~' + msg.aut;
if (_.has(msg, 'sep.app.app')) {
return `:${msg.sep.app.app} (${ship})`;
if ('code' in letter) {
let outputElement =
(!!letter.code.output &&
letter.code.output.length && letter.code.output.length > 0) ?
(
<pre className="clamp-attachment pa1 mt0 mb0">
{letter.code.output[0].join('\n')}
</pre>
) : null;
return (
<span>
<pre className="clamp-attachment pa1 mt0 mb0 bg-light-gray">
{letter.code.expression}
</pre>
{outputElement}
</span>
);
} else if ('url' in letter) {
return (
<a className="body-regular-400 v-top" href={letter.url}>
{letter.url}
</a>
);
} else if ('me' in letter) {
return (
<p className='body-regular-400 v-top'>
{letter.me}
</p>
);
} else {
return ship;
}
}
//NOTE see also lib/chat-input's globalizeUrl
localizeUrl(url) {
if (typeof url !== 'string') { throw 'Only localize strings!'; }
const arvo = 'arvo://';
if (url.indexOf(arvo) === 0) {
// this application is being served by an urbit also, so /path will
// point to the arvo url as hosted by this same urbit.
return url.slice(arvo.length);
} else {
return url;
return (
<p className='body-regular-400 v-top'>
{letter.text}
</p>
);
}
}
render() {
const { props } = this;
let pending = !!props.msg.pending ? ' o-40' : '';
let datestamp = moment.unix(props.msg.wen / 1000).format('LL');
let datestamp = moment.unix(props.msg.when / 1000).format('LL');
let paddingTop = props.paddingTop ? 'pt3' : '';
let paddingBot = props.paddingBot ? 'pb2' : 'pb1';
if (props.renderSigil) {
let timestamp = moment.unix(props.msg.wen / 1000).format('hh:mm a');
let timestamp = moment.unix(props.msg.when / 1000).format('hh:mm a');
return (
<div className={"w-100 pl3 pr3 cf flex " + paddingTop + " " + paddingBot + pending}
@ -186,12 +66,12 @@ export class Message extends Component {
minHeight: 'min-content'
}}>
<div className="fl mr2">
<Sigil ship={props.msg.aut} size={36} />
<Sigil ship={props.msg.author} size={36} />
</div>
<div className="fr clamp-message" style={{ flexGrow: 1, marginTop: -8 }}>
<div className="hide-child">
<p className="v-top label-small-mono gray dib mr3">
{this.renderAuthor()}
{props.msg.author}
</p>
<p className="v-top label-small-mono gray dib">{timestamp}</p>
<p className="v-top label-small-mono ml2 gray dib child">
@ -203,7 +83,7 @@ export class Message extends Component {
</div>
);
} else {
let timestamp = moment.unix(props.msg.wen / 1000).format('hh:mm');
let timestamp = moment.unix(props.msg.when / 1000).format('hh:mm');
return (
<div className={"w-100 pr3 pb1 cf hide-child flex" + pending}

View File

@ -4,82 +4,6 @@ import _ from 'lodash';
export class SidebarInvite extends Component {
onAccept() {
const { props } = this;
let msg = props.msg;
let cir = _.get(props, 'msg.sep.inv.cir', false);
if (!cir) {
return;
}
this.updateInvite(msg.uid, cir, true);
}
onReject() {
const { props } = this;
let msg = props.msg;
let cir = _.get(props, 'msg.sep.inv.cir', false);
if (!cir) {
return;
}
this.updateInvite(msg.uid, cir, false);
}
updateInvite(uid, cir, resp) {
let tagstring = resp ? "Accept" : "Reject";
let hostName = cir.split('/')[0];
let circleName = cir.split('/')[1];
let actions = [
{
phrase: {
aud: [`~${window.ship}/i`],
ses: [{
ire: {
top: uid,
sep: {
lin: {
msg: `${tagstring} ${cir}`,
pat: false
}
}
}
}]
}
}
];
if (resp && hostName !== `~${window.ship}`) {
actions = actions.concat([
{
create: {
nom: 'hall-internal-' + circleName,
des: "chatroom",
sec: "channel"
}
},
{
source: {
nom: "inbox",
sub: true,
srs: [cir]
}
},
{
source: {
nom: "inbox",
sub: true,
srs: [`~${window.ship}/hall-internal-${circleName}`]
}
}
]);
}
this.props.api.chat(actions);
}
render() {
const { props } = this;

View File

@ -25,14 +25,26 @@ export class SidebarItem extends Component {
}
getTimeSinceNewestMessage() {
return !!this.props.wen ?
moment.unix(this.props.wen / 1000).from(moment.utc())
return !!this.props.when ?
moment.unix(this.props.when / 1000).from(moment.utc())
: '';
}
onClick() {
const { props } = this;
props.history.push('/~chat/' + props.cir);
props.history.push('/~chat/room' + props.box);
}
getLetter(lett) {
if ('text' in lett) {
return lett.text;
} else if ('url' in lett) {
return lett.url;
} else if ('code' in lett) {
return lett.code.expression;
} else {
return '';
}
}
render() {
@ -47,18 +59,20 @@ export class SidebarItem extends Component {
<div className="dib"></div>
);
let description = this.getLetter(props.description);
let selectedCss = !!props.selected ? 'bg-light-gray' : 'bg-white pointer';
return (
<div className={'pa3 ' + selectedCss} onClick={this.onClick.bind(this)}>
<div className='w-100 v-mid'>
{unreadElem}
<p className="dib body-regular lh-16">{props.title}</p>
<p className="dib body-regular lh-16">{props.title.substr(1)}</p>
</div>
<div className="w-100">
<p className='dib gray label-small-mono mr3 lh-16'>{props.ship}</p>
<p className='dib gray label-small-mono lh-16'>{state.timeSinceNewestMessage}</p>
</div>
<p className='label-small gray clamp-3 lh-16 pt1'>{props.description}</p>
<p className='label-small gray clamp-3 lh-16 pt1'>{description}</p>
</div>
)
}

View File

@ -5,130 +5,117 @@ import urbitOb from 'urbit-ob';
import { deSig } from '/lib/util';
import { ChatTabBar } from '/components/lib/chat-tabbar';
import { MemberElement } from '/components/lib/member-element';
import { InviteElement } from '/components/lib/invite-element';
export class MemberScreen extends Component {
constructor(props) {
super(props);
this.state = {
station: props.match.params.ship + "/" + props.match.params.station,
circle: props.match.params.station,
host: props.match.params.ship,
invMembers: '',
error: false,
success: false
station: `/${props.match.params.ship}/${props.match.params.station}`,
};
}
inviteMembers() {
const { props, state } = this;
let sis = state.invMembers.split(',')
.map((mem) => mem.trim())
.map(deSig);
let isValid = true;
sis.forEach((mem) => {
if (!urbitOb.isValidPatp(`~${mem}`)) {
isValid = false;
}
});
if (isValid) {
props.api.permit(state.circle, sis, true);
if (this.textarea) {
this.textarea.value = '';
}
this.setState({
error: false,
success: true,
invMembers: ''
});
} else {
this.setState({ error: true, success: false });
}
}
inviteMembersChange(e) {
this.setState({
invMembers: e.target.value
});
}
render() {
const { props, state } = this;
let peers = props.peers[state.station] || [window.ship];
let listMembers = peers.map((mem) => {
let writeGroup = Array.from(props.write.who.values());
let readGroup = Array.from(props.read.who.values());
let writeText = '';
let readText = '';
let modWriteText = '';
let modReadText = '';
if (props.write.kind === 'black') {
writeText = 'Everyone banned from writing to this chat.';
modWriteText = 'Ban someone from writing to this chat.';
} else if (props.write.kind === 'white') {
writeText = 'Everyone with permission to message this chat.';
modWriteText = 'Invite someone to write to this chat.';
}
if (props.read.kind === 'black') {
readText = 'Everyone banned from reading this chat.';
modReadText = 'Ban someone from reading this chat.';
} else if (props.read.kind === 'white') {
readText = 'Everyone with permission to read this chat.';
modReadText = 'Invite someone to read this chat.';
}
let writeListMembers = writeGroup.map((mem) => {
return (
<MemberElement
key={mem}
host={state.host}
owner={deSig(props.match.params.ship)}
ship={mem}
circle={state.circle}
path={`/chat${state.station}/write`}
kind={props.write.kind}
api={props.api} />
);
});
let readListMembers = readGroup.map((mem) => {
return (
<MemberElement
key={mem}
owner={deSig(props.match.params.ship)}
ship={mem}
path={`/chat${state.station}/read`}
kind={props.read.kind}
api={props.api} />
);
});
let errorElem = !!this.state.error ? (
<p className="pa2 nice-red label-regular">Invalid ship name.</p>
) : (
<div></div>
);
let successElem = !!this.state.success ? (
<p className="pa2 nice-green label-regular">Sent invites!</p>
) : (
<div></div>
);
let inviteButtonClasses = "label-regular black underline btn-font pointer";
if (!this.state.error) {
inviteButtonClasses = inviteButtonClasses + ' black';
}
return (
<div className="h-100 w-100 overflow-x-hidden flex flex-column">
<div className='pl3 pt2 bb mb3'>
<h2>{state.circle}</h2>
<h2>{state.station.substr(1)}</h2>
<ChatTabBar
{...props}
station={state.station}
numPeers={peers.length} />
numPeers={writeGroup.length}
isOwner={deSig(props.match.params.ship) === window.ship} />
</div>
<div className="w-100 cf">
<div className="w-50 fl pa2">
<p className="body-regular">Permitted Members</p>
<p className="label-regular gray mb3">
Everyone with permission to see this chat.
</p>
{listMembers}
<div className="w-50 fl pa2 pr3">
<p className="body-regular mb3">Members</p>
<p className="label-regular gray mb3">{writeText}</p>
{writeListMembers}
</div>
<div className="w-50 fr pa2 pl3">
<p className="body-regular mb3">Modify Permissions</p>
<p className="label-regular gray mb3">
{modWriteText}
</p>
{ window.ship === deSig(props.match.params.ship) ? (
<InviteElement
path={`/chat${state.station}/write`}
station={state.station}
permissions={props.write}
api={props.api} />
) : null }
</div>
</div>
<div className="w-100 cf mt2">
<div className="w-50 fl pa2 pr3">
<p className="label-regular gray mb3">{readText}</p>
{readListMembers}
</div>
<div className="w-50 fr pa2 pl3">
<p className="label-regular gray mb3">
{modReadText}
</p>
{ window.ship === deSig(props.match.params.ship) ?
( <InviteElement
path={`/chat${state.station}/read`}
permissions={props.read}
api={props.api}/>
) : null
}
</div>
{ `~${window.ship}` === state.host ? (
<div className="w-50 fr pa2">
<p className="body-regular">Invite</p>
<p className="label-regular gray mb3">
Invite new participants to this chat.
</p>
<textarea
ref={ e => { this.textarea = e; } }
className="w-80 db ba overflow-y-hidden mono gray mb2"
style={{
resize: 'none',
height: 150
}}
spellCheck="false"
onChange={this.inviteMembersChange.bind(this)}></textarea>
<button
onClick={this.inviteMembers.bind(this)}
className={inviteButtonClasses}>
-> Invite
</button>
{errorElem}
{successElem}
</div>
) : null }
</div>
</div>
)

View File

@ -12,21 +12,23 @@ export class NewScreen extends Component {
this.state = {
idName: '',
invites: '',
security: 'village',
idError: false,
inviteError: false
};
this.idChange = this.idChange.bind(this);
this.invChange = this.invChange.bind(this);
this.securityChange = this.securityChange.bind(this);
}
componentDidUpdate(prevProps, prevState) {
const { props, state } = this;
if (prevProps.circles !== props.circles) {
let station = `~${window.ship}/${state.idName}`;
if (props.circles.includes(station)) {
props.history.push('/~chat/' + station);
if (prevProps !== props) {
let station = `/~${window.ship}/${state.idName}`;
if (station in props.inbox) {
props.history.push('/~chat/room' + station);
}
}
}
@ -41,6 +43,10 @@ export class NewScreen extends Component {
this.setState({invites: event.target.value});
}
securityChange(event) {
this.setState({security: event.target.value});
}
onClickCreate() {
const { props, state } = this;
if (!state.idName) {
@ -51,91 +57,86 @@ export class NewScreen extends Component {
return;
}
let station = `~${window.ship}/${state.idName}`;
let actions = [
{
create: {
nom: state.idName,
des: "chatroom",
sec: "village"
}
},
{
source: {
nom: 'inbox',
sub: true,
srs: [station]
}
}
];
let station = `/${state.idName}`;
if (station in props.inbox) {
this.setState({
inviteError: false,
idError: true,
success: false
});
return;
}
if (state.invites.length > 0) {
// TODO: send invites
let aud = [];
let isValid = true;
if (state.invites.length > 2) {
aud = state.invites.split(',')
.map((mem) => `~${deSig(mem.trim())}`);
let aud = state.invites.split(',')
.map((mem) => mem.trim())
.map(deSig);
let isValid = true;
aud.forEach((mem) => {
if (!urbitOb.isValidPatp(`~${mem}`)) {
if (!urbitOb.isValidPatp(mem)) {
isValid = false;
}
});
if (isValid) {
actions.push({
permit: {
nom: state.idName,
sis: aud,
inv: true
}
});
actions.push({
phrase: {
aud: aud.map((aud) => `~${aud}/i`),
ses: [{
inv: {
inv: true,
cir: station
}
}]
}
});
if (this.textarea) {
this.textarea.value = '';
}
this.setState({
inviteError: false,
idError: false,
success: true,
invites: ''
}, () => {
props.setSpinner(true);
props.api.chat(actions);
});
} else {
this.setState({
inviteError: true,
idError: false,
success: false
});
}
} else {
this.setState({
error: false,
success: true,
invites: ''
}, () => {
props.setSpinner(true);
props.api.chat(actions);
});
}
if (!isValid) {
this.setState({
inviteError: true,
idError: false,
success: false
});
return;
}
if (this.textarea) {
this.textarea.value = '';
}
// TODO: don't do this, it's shitty
let writeAud;
let readAud;
if (state.security === 'village') {
aud.push(`~${window.ship}`);
readAud = aud.slice(); // white list
writeAud = aud.slice(); // white list
} else if (state.security === 'channel') {
readAud = []; // black list
writeAud = []; // black list
} else if (state.security === 'journal') {
aud.push(`~${window.ship}`);
readAud = []; // black list
writeAud = aud.slice(); // white list
} else if (state.security === 'mailbox') {
aud.push(`~${window.ship}`);
readAud = aud.slice(); // white list
writeAud = []; // black list
}
this.setState({
error: false,
success: true,
invites: ''
}, () => {
props.setSpinner(true);
props.api.chatView.create(station, state.security, readAud, writeAud);
props.api.invite.create('/chat');
aud.forEach((recipient) => {
props.api.invite.invite(
'/chat',
`/mailbox${station}`,
window.ship,
'chat-hook',
`You have been invited to join ~${window.ship}/mailbox${station}`,
recipient
);
});
});
}
render() {
@ -194,6 +195,14 @@ export class NewScreen extends Component {
}}
onChange={this.invChange} />
{invErrElem}
<select
value={this.state.securityValue}
onChange={this.securityChange}>
<option value="village">Village</option>
<option value="channel">Channel</option>
<option value="journal">Journal</option>
<option value="mailbox">Mailbox</option>
</select>
<button
onClick={this.onClickCreate.bind(this)}
className={createClasses}

View File

@ -12,7 +12,7 @@ import { ChatScreen } from '/components/chat';
import { MemberScreen } from '/components/member';
import { SettingsScreen } from '/components/settings';
import { NewScreen } from '/components/new';
import { LandingScreen } from '/components/landing';
import { JoinScreen } from '/components/join';
export class Root extends Component {
@ -33,75 +33,27 @@ export class Root extends Component {
render() {
const { props, state } = this;
let configs = !!state.configs ? state.configs : {};
let circles = Object.keys(configs).filter((conf) => {
return configs[conf] !== undefined && conf.split('/')[1] !== 'i';
});
let messages = _.get(state, 'messages', {});
let messagePreviews = {};
Object.keys(messages).forEach((stat) => {
let arr = messages[stat];
if (arr.length === 0) {
let unreads = {};
Object.keys(state.inbox).forEach((stat) => {
let envelopes = state.inbox[stat].envelopes;
if (envelopes.length === 0) {
messagePreviews[stat] = false;
} else {
messagePreviews[stat] = arr[arr.length - 1];
messagePreviews[stat] = envelopes[envelopes.length - 1];
}
unreads[stat] = envelopes.length > state.inbox[stat].config.read;
});
let unreads = {};
circles.forEach((cir) => {
if (cir in messages) {
if (messages[cir].length === 0) {
unreads[cir] = false;
} else {
let host = `~${window.ship}`;
let circle = cir.split('/')[1];
let internalStation = host + '/hall-internal-' + circle;
if (internalStation in state.configs) {
if (!!state.configs[internalStation]) {
unreads[cir] =
state.configs[internalStation].red <=
messages[cir][messages[cir].length - 1].num;
} else {
unreads[cir] = false;
}
} else if (cir in state.configs) {
if (!!state.configs[cir]) {
unreads[cir] =
state.configs[cir].red <=
messages[cir][messages[cir].length - 1].num;
} else {
unreads[cir] = false;
}
} else {
unreads[cir] = false;
}
}
} else {
unreads[cir] = false;
}
});
let invites = _.get(state, 'messages', {});
if (`~${window.ship}/i` in invites) {
invites = invites[`~${window.ship}/i`];
} else {
invites = [];
}
let inviteConfig = false;
if (`~${window.ship}/i` in configs) {
inviteConfig = configs[`~${window.ship}/i`];
}
const renderChannelSidebar = (props) => (
<Sidebar
circles={circles}
inbox={state.inbox}
messagePreviews={messagePreviews}
invites={invites}
invites={[]}
unreads={unreads}
api={api}
inviteConfig={inviteConfig}
@ -115,8 +67,7 @@ export class Root extends Component {
<Route exact path="/~chat"
render={ (props) => {
return (
<Skeleton
sidebar={renderChannelSidebar(props)}>
<Skeleton sidebar={renderChannelSidebar(props)}>
<div className="h-100 w-100 overflow-x-hidden flex flex-column">
<div className="pl3 pr3 pt2 pb3">
<h2>Home</h2>
@ -138,7 +89,7 @@ export class Root extends Component {
<NewScreen
setSpinner={this.setSpinner}
api={api}
circles={circles}
inbox={state.inbox || {}}
{...props}
/>
</Skeleton>
@ -147,53 +98,76 @@ export class Root extends Component {
<Route exact path="/~chat/join/:ship/:station"
render={ (props) => {
return (
<Skeleton
sidebar={renderChannelSidebar(props)}>
<LandingScreen
<Skeleton sidebar={renderChannelSidebar(props)}>
<JoinScreen
api={api}
configs={configs}
inbox={state.inbox}
{...props}
/>
</Skeleton>
);
}} />
<Route exact path="/~chat/:ship/:station"
<Route exact path="/~chat/room/:ship/:station"
render={ (props) => {
let station =
props.match.params.ship
+ "/" +
props.match.params.station;
let messages = state.messages[station] || [];
`/${props.match.params.ship}/${props.match.params.station}`;
let mailbox = state.inbox[station] || {
config: {
read: -1,
length: 0
},
envelopes: []
};
let write = state.groups[`/chat${station}/write`] || new Set([]);
return (
<Skeleton
sidebar={renderChannelSidebar(props) }>
<Skeleton sidebar={renderChannelSidebar(props) }>
<ChatScreen
api={api}
configs={configs}
messages={messages}
pendingMessages={state.pendingMessages}
peers={state.peers}
subscription={subscription}
read={mailbox.config.read}
envelopes={mailbox.envelopes}
inbox={state.inbox}
group={write}
permissions={state.permissions}
pendingMessages={state.pendingMessages}
{...props}
/>
</Skeleton>
);
}} />
<Route exact path="/~chat/:ship/:station/members"
<Route exact path="/~chat/room/:ship/:station/members"
render={ (props) => {
let station =
`/${props.match.params.ship}/${props.match.params.station}`;
let read = state.permissions[`/chat${station}/read`] || {
kind: '',
who: new Set([])
};
let write = state.permissions[`/chat${station}/write`] || {
kind: '',
who: new Set([])
};
return (
<Skeleton
sidebar={renderChannelSidebar(props) }>
<Skeleton sidebar={renderChannelSidebar(props) }>
<MemberScreen
{...props}
api={api}
peers={state.peers}
read={read}
write={write}
permissions={state.permissions}
/>
</Skeleton>
);
}} />
<Route exact path="/~chat/:ship/:station/settings"
<Route exact path="/~chat/room/:ship/:station/settings"
render={ (props) => {
let station =
`/${props.match.params.ship}/${props.match.params.station}`;
let write = state.groups[`/chat${station}/write`] || new Set([]);
return (
<Skeleton
spinner={this.state.spinner}
@ -202,8 +176,8 @@ export class Root extends Component {
{...props}
setSpinner={this.setSpinner}
api={api}
peers={state.peers}
circles={state.circles}
group={write}
inbox={state.inbox}
/>
</Skeleton>
);

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { deSig } from '/lib/util';
import { ChatTabBar } from '/components/lib/chat-tabbar';
@ -9,9 +10,7 @@ export class SettingsScreen extends Component {
super(props);
this.state = {
station: props.match.params.ship + "/" + props.match.params.station,
circle: props.match.params.station,
host: props.match.params.ship,
station: `/${props.match.params.ship}/${props.match.params.station}`,
isLoading: false
};
@ -20,10 +19,11 @@ export class SettingsScreen extends Component {
componentDidUpdate(prevProps, prevState) {
const { props, state } = this;
if (!!state.isLoading && !props.circles.includes(state.station)) {
if (!!state.isLoading && !(state.station in props.inbox)) {
this.setState({
isLoading: false
}, () => {
props.setSpinner(false);
props.history.push('/~chat');
});
}
@ -31,29 +31,10 @@ export class SettingsScreen extends Component {
deleteChat() {
const { props, state } = this;
if (state.host === `~${window.ship}`) {
props.api.delete(state.circle);
} else {
let internalCircle = 'hall-internal-' + state.circle;
props.api.chat([
{
source: {
nom: 'inbox',
sub: false,
srs: [state.station]
}
},
{
delete: {
nom: internalCircle,
why: ''
}
}
]);
}
props.api.chatView.delete(state.station);
props.setSpinner(true);
this.setState({
isLoading: true
});
@ -61,11 +42,12 @@ export class SettingsScreen extends Component {
renderDelete() {
const { props, state } = this;
let titleText = "Delete Chat";
let descriptionText = "Permanently delete this chat.";
let buttonText = "-> Delete";
if (state.host !== `~${window.ship}`) {
if (deSig(props.match.params.ship) !== window.ship) {
titleText = "Leave Chat"
descriptionText = "You will no longer have access to this chat."
buttonText = "-> Leave";
@ -81,25 +63,25 @@ export class SettingsScreen extends Component {
);
}
render() {
const { props, state } = this;
let peers = props.peers[state.station] || [window.ship];
let writeGroup = Array.from(props.group.values());
if (!!state.isLoading) {
let text = "Deleting...";
if (state.host === `~${window.ship}`) {
if (deSig(props.match.params.ship) !== window.ship) {
text = "Leaving...";
}
return (
<div className="h-100 w-100 overflow-x-hidden flex flex-column">
<div className='pl3 pt2 bb mb3'>
<h2>{state.circle}</h2>
<h2>{state.station.substr(1)}</h2>
<ChatTabBar
{...props}
station={state.station}
numPeers={peers.length} />
numPeers={writeGroup.length} />
</div>
<div className="w-100 cf pa3">
<h2>{text}</h2>
@ -111,11 +93,12 @@ export class SettingsScreen extends Component {
return (
<div className="h-100 w-100 overflow-x-hidden flex flex-column">
<div className='pl3 pt2 bb mb3'>
<h2>{state.circle}</h2>
<h2>{state.station.substr(1)}</h2>
<ChatTabBar
{...props}
station={state.station}
numPeers={peers.length} />
numPeers={writeGroup.length}
isOwner={deSig(props.match.params.ship) === window.ship} />
</div>
<div className="w-100 cf pa3">
<h2>Settings</h2>

View File

@ -3,55 +3,10 @@ import classnames from 'classnames';
import _ from 'lodash';
import { SidebarItem } from '/components/lib/sidebar-item';
import { SidebarInvite } from '/components/lib/sidebar-invite';
export class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {
invites: []
};
this.setInvitesToReadInterval = setInterval(
this.setInvitesToRead.bind(this),
1000
);
}
componentDidMount() {
this.filterInvites();
}
componentDidUpdate(prevProps, prevState) {
if (prevProps !== this.props) {
this.filterInvites();
}
}
filterInvites() {
const { props } = this;
let invites = [];
let filterInvites = {};
props.invites.forEach((msg) => {
let uid = _.get(msg, 'gam.sep.ire.top', false);
if (!uid) {
invites.push(msg.gam);
} else {
filterInvites[uid] = true;
}
});
invites = invites.filter((msg) => {
return !(msg.uid in filterInvites);
})
this.setState({ invites });
}
componentWillUnmount() {
if (this.setInvitesToReadInterval) {
clearInterval(this.setInvitesToReadInterval);
@ -59,93 +14,46 @@ export class Sidebar extends Component {
}
}
setInvitesToRead() {
const { props, state } = this;
if (
props.inviteConfig &&
'red' in props.inviteConfig &&
props.invites.length > 0
) {
let invNum = (props.invites[props.invites.length - 1].num + 1);
if (
props.inviteConfig.red < invNum &&
(invNum - props.inviteConfig.red) > state.invites.length
) {
props.api.read('i', invNum - state.invites.length);
}
}
}
onClickNew() {
this.props.history.push('/~chat/new');
}
summarizeMessage(speech) {
const fallback = '...';
if (_.has(speech, 'lin')) {
return speech.lin.msg;
} else if (_.has(speech, 'url')) {
return speech.url;
} else if (_.has(speech, 'exp')) {
return '# ' + speech.exp.exp;
} else if (_.has(speech, 'ire')) {
return this.summarizeMessage(speech.ire.sep);
} else if (_.has(speech, 'app')) {
return this.summarizeMessage(speech.app.sep);
} else if (_.has(speech, 'fat')) {
const msg = this.summarizeMessage(speech.fat.sep);
if (msg !== '' && msg !== fallback) return msg;
return 'Attachment' +
(_.has(speech, 'fat.tac.name.nom')
? ': ' + speech.fat.tac.name.nom
: '');
} else {
return fallback;
}
}
render() {
const { props, state } = this;
let station = props.match.params.ship + '/' + props.match.params.station;
let station = props.match.params.ship + props.match.params.station;
console.log(props.circles);
let sidebarItems = props.circles
.filter((cir) => {
return !cir.includes('hall-internal-');
})
.map((cir) => {
let msg = props.messagePreviews[cir];
let content = _.has(msg, 'gam.sep')
? this.summarizeMessage(msg.gam.sep)
: 'No messages yet';
let aut = !!msg ? msg.gam.aut : '';
let wen = !!msg ? msg.gam.wen : 0;
let sidebarItems = Object.keys(props.inbox)
.map((box) => {
let msg = props.messagePreviews[box];
let letter = _.has(msg, 'letter')
? msg.letter
: {text: 'No messages yet'};
let author = !!msg ? msg.author : '';
let when = !!msg ? msg.when : 0;
return {
msg,
wen,
aut,
content,
cir,
title: cir.split('/')[1],
selected: station === cir
when,
author,
letter,
box,
title: box,
selected: station === box
};
})
.sort((a, b) => {
return b.wen - a.wen;
})
.map((obj) => {
let unread = props.unreads[obj.cir];
let unread = props.unreads[obj.box];
return (
<SidebarItem
key={obj.cir + '/' + obj.wen}
key={obj.box + '/' + obj.when}
title={obj.title}
description={obj.content}
cir={obj.cir}
wen={obj.wen}
ship={obj.aut}
description={obj.letter}
box={obj.box}
when={obj.when}
ship={obj.author}
selected={obj.selected}
unread={unread}
history={props.history}
@ -153,17 +61,6 @@ export class Sidebar extends Component {
);
});
let inviteItems = state.invites.map((inv) => {
return (
<SidebarInvite
key={inv.uid}
msg={inv}
api={props.api}
config={props.inviteConfig}
/>
);
});
return (
<div className="h-100 w-100 overflow-x-hidden flex flex-column">
<div className="pl3 pr3 pt2 pb3 cf bb b--black-30" style={{height: '88px'}}>
@ -175,7 +72,6 @@ export class Sidebar extends Component {
<div className="overflow-y-auto" style={{
height: 'calc(100vh - 60px - 48px)'
}}>
{inviteItems}
{sidebarItems}
</div>
</div>

View File

@ -0,0 +1,78 @@
import _ from 'lodash';
export class ChatUpdateReducer {
reduce(json, state) {
let data = _.get(json, 'chat-update', false);
if (data) {
this.pending(data, state);
this.message(data, state);
this.messages(data, state);
this.read(data, state);
this.create(data, state);
this.delete(data, state);
}
}
message(json, state) {
let data = _.get(json, 'message', false);
if (data) {
state.inbox[data.path].envelopes.push(data.envelope);
}
}
messages(json, state) {
let data = _.get(json, 'messages', false);
if (data) {
console.log(data);
state.inbox[data.path].envelopes =
data.envelopes.concat(state.inbox[data.path].envelopes);
}
}
read(json, state) {
let data = _.get(json, 'read', false);
if (data) {
state.inbox[data.path].config.read =
state.inbox[data.path].envelopes.length;
}
}
create(json, state) {
let data = _.get(json, 'create', false);
if (data) {
state.inbox[`/~${data.ship}${data.path}`] = {
envelopes: [],
config: {
read:0,
length: 0,
}
};
}
}
delete(json, state) {
let data = _.get(json, 'delete', false);
if (data) {
delete state.inbox[data.path];
}
}
pending(json, state) {
let msg = _.get(json, 'message', false);
if (!msg || !state.pendingMessages.has(msg.path)) {
return;
}
let mailbox = state.pendingMessages.get(msg.path);
for (let pendingMsg of mailbox) {
if (msg.envelope.uid === pendingMsg.uid) {
let index = mailbox.indexOf(pendingMsg);
state.pendingMessages.get(msg.path).splice(index, 1);
}
}
}
}

View File

@ -1,16 +0,0 @@
import _ from 'lodash';
export class ConfigReducer {
reduce(json, state) {
let data = _.get(json, 'chat', false);
if (data) {
state.inbox = data.inbox;
state.configs = data.configs;
state.circles = data.circles;
state.peers = data.peers;
state.messages = state.messages || {};
}
}
}

View File

@ -0,0 +1,65 @@
import _ from 'lodash';
export class GroupUpdateReducer {
reduce(json, state) {
let data = _.get(json, 'group-update', false);
if (data) {
this.add(data, state);
this.remove(data, state);
this.bundle(data, state);
this.unbundle(data, state);
this.keys(data, state);
this.path(data, state);
}
}
add(json, state) {
let data = _.get(json, 'add', false);
if (data) {
for (let member of data.members) {
state.groups[data.path].add(member);
}
}
}
remove(json, state) {
let data = _.get(json, 'remove', false);
if (data) {
for (let member of data.members) {
state.groups[data.path].delete(member);
}
}
}
bundle(json, state) {
let data = _.get(json, 'bundle', false);
if (data) {
state.groups[data.path] = new Set();
}
}
unbundle(json, state) {
let data = _.get(json, 'unbundle', false);
if (data) {
delete state.groups[data.path];
}
}
keys(json, state) {
let data = _.get(json, 'keys', false);
if (data) {
state.groupKeys = new Set(data.keys);
}
}
path(json, state) {
let data = _.get(json, 'path', false);
if (data) {
state.groups[data.path] = new Set([data.members]);
}
}
}

View File

@ -3,13 +3,31 @@ import _ from 'lodash';
export class InitialReducer {
reduce(json, state) {
let data = _.get(json, 'initial', false);
let data = _.get(json, 'chat-initial', false);
if (data) {
state.messages = data.messages;
state.inbox = data.inbox;
state.configs = data.configs;
state.circles = data.circles;
state.peers = data.peers;
state.inbox = data;
}
data = _.get(json, 'group-initial', false);
if (data) {
for (let group in data) {
state.groups[group] = new Set(data[group]);
}
}
data = _.get(json, 'permission-initial', false);
if (data) {
for (let perm in data) {
state.permissions[perm] = {
who: new Set(data[perm].who),
kind: data[perm].kind
}
}
}
data = _.get(json, 'invite-initial', false);
if (data) {
state.invites = data;
}
}
}

View File

@ -0,0 +1,52 @@
import _ from 'lodash';
export class InviteUpdateReducer {
reduce(json, state) {
let data = _.get(json, 'invite-update', false);
if (data) {
this.create(data, state);
this.delete(data, state);
this.invite(data, state);
this.accept(data, state);
this.decline(data, state);
}
}
create(json, state) {
let data = _.get(json, 'create', false);
if (data) {
state.invites[data.path] = {};
}
}
delete(json, state) {
let data = _.get(json, 'delete', false);
if (data) {
delete state.invites[data.path];
}
}
invite(json, state) {
let data = _.get(json, 'invite', false);
if (data) {
state.invites[data.path][data.uid] = data.invite;
}
}
accept(json, state) {
let data = _.get(json, 'accept', false);
if (data) {
delete state.invites[data.path][data.uid];
}
}
decline(json, state) {
let data = _.get(json, 'decline', false);
if (data) {
delete state.invites[data.path][data.uid];
}
}
}

View File

@ -0,0 +1,51 @@
import _ from 'lodash';
export class PermissionUpdateReducer {
reduce(json, state) {
let data = _.get(json, 'permission-update', false);
if (data) {
this.create(data, state);
this.delete(data, state);
this.add(data, state);
this.remove(data, state);
}
}
create(json, state) {
let data = _.get(json, 'create', false);
if (data) {
state.permissions[data.path] = {
kind: data.kind,
who: new Set(data.who)
};
}
}
delete(json, state) {
let data = _.get(json, 'delete', false);
if (data) {
delete state.permissions[data.path];
}
}
add(json, state) {
let data = _.get(json, 'add', false);
if (data) {
for (let member of data.who) {
state.permissions[data.path].who.add(member);
}
}
}
remove(json, state) {
let data = _.get(json, 'remove', false);
if (data) {
for (let member of data.who) {
state.permissions[data.path].who.delete(member);
}
}
}
}

View File

@ -1,101 +0,0 @@
import _ from 'lodash';
export class UpdateReducer {
reduce(json, state) {
let data = _.get(json, 'update', false);
if (data) {
this.reduceInbox(_.get(data, 'inbox', false), state);
this.reducePending(_.get(data, 'message', false), state);
this.reduceMessage(_.get(data, 'message', false), state);
this.reduceMessages(_.get(data, 'messages', false), state);
this.reduceConfig(_.get(data, 'config', false), state);
this.reduceCircles(_.get(data, 'circles', false), state);
this.reducePeers(_.get(data, 'peers', false), state);
this.reduceDelete(_.get(data, 'delete', false), state);
}
}
reduceInbox(inbox, state) {
if (inbox) {
state.inbox = inbox;
}
}
reduceMessage(message, state) {
if (message && message.circle in state.messages) {
state.messages[message.circle].push(message.envelope);
} else {
state.messages[message.circle] = [message.envelope];
}
}
reducePending(message, state) {
if (message && (state.pendingMessages.has(message.envelope.gam.aud[0]))) {
for (let pendingMessage of state.pendingMessages.get(message.envelope.gam.aud[0])) {
// Does the incoming message match a pending one?
if (message.envelope.gam.uid === pendingMessage.uid) {
// Mutating the pendingMessages array.
let pendingMessageMap = state.pendingMessages;
let matchedMessage = pendingMessageMap.get(pendingMessage.aud[0]).indexOf(pendingMessage);
pendingMessageMap.get(pendingMessage.aud[0]).splice(matchedMessage, 1);
state.pendingMessages = pendingMessageMap;
}
}
}
}
reduceMessages(msgs, state) {
if (msgs) {
let staMsgs = state.messages[msgs.circle];
if (msgs.circle in state.messages) {
console.log('new messages object: ', msgs);
console.log('lowest num in store: ', staMsgs[0].num);
console.log('highest num in store: ', staMsgs[staMsgs.length - 1].num);
if (staMsgs.length > 0 && staMsgs[0].num - 1 === msgs.end) {
state.messages[msgs.circle] = msgs.envelopes.concat(staMsgs);
} else if (staMsgs.length === 0) {
state.messages[msgs.circle] = msgs.envelopes;
} else {
console.error('%messages has inconsistent indices');
}
} else {
state.messages[msgs.circle] = msgs.envelopes;
}
}
}
reduceConfig(config, state) {
if (config) {
state.configs[config.circle] = config.config;
}
}
reduceCircles(circles, state) {
if (circles) {
state.circles = circles.circles;
}
}
reducePeers(peers, state) {
if (peers) {
state.peers[peers.circle] = peers.peers;
}
}
reduceDelete(del, state) {
if (del) {
delete state.configs[del.circle];
delete state.messages[del.circle];
delete state.peers[del.circle];
}
}
}

View File

@ -1,23 +1,26 @@
import { InitialReducer } from '/reducers/initial';
import { ConfigReducer } from '/reducers/config';
import { UpdateReducer } from '/reducers/update';
import { GroupUpdateReducer } from '/reducers/group-update';
import { ChatUpdateReducer } from '/reducers/chat-update';
import { InviteUpdateReducer } from '/reducers/invite-update';
import { PermissionUpdateReducer } from '/reducers/permission-update';
class Store {
constructor() {
this.state = {
inbox: {},
messages: {},
configs: {},
circles: [],
peers: {},
groups: {},
permissions: {},
invites: {},
spinner: false,
pendingMessages: new Map([])
};
this.initialReducer = new InitialReducer();
this.configReducer = new ConfigReducer();
this.updateReducer = new UpdateReducer();
this.groupUpdateReducer = new GroupUpdateReducer();
this.permissionUpdateReducer = new PermissionUpdateReducer();
this.chatUpdateReducer = new ChatUpdateReducer();
this.inviteUpdateReducer = new InviteUpdateReducer();
this.setState = () => {};
}
@ -30,8 +33,10 @@ class Store {
console.log(json);
this.initialReducer.reduce(json, this.state);
this.configReducer.reduce(json, this.state);
this.updateReducer.reduce(json, this.state);
this.groupUpdateReducer.reduce(json, this.state);
this.permissionUpdateReducer.reduce(json, this.state);
this.chatUpdateReducer.reduce(json, this.state);
this.inviteUpdateReducer.reduce(json, this.state);
this.setState(this.state);
}

View File

@ -14,19 +14,18 @@ export class Subscription {
}
initializeChat() {
api.bind('/primary', 'PUT', api.authTokens.ship, 'chat',
api.bind('/primary', 'PUT', api.authTokens.ship, 'chat-view',
this.handleEvent.bind(this),
this.handleError.bind(this));
}
fetchMessages(circle, start, end) {
fetch(`/~chat/scroll/${circle}/${start}/${end}`)
.then((response) => response.json())
.then((json) => {
store.handleEvent({
data: json
});
});
this.handleError.bind(this),
this.handleQuitAndResubscribe.bind(this));
api.bind('/all', 'PUT', api.authTokens.ship, 'group-store',
this.handleEvent.bind(this),
this.handleError.bind(this),
this.handleQuitAndResubscribe.bind(this));
api.bind('/all', 'PUT', api.authTokens.ship, 'permission-store',
this.handleEvent.bind(this),
this.handleError.bind(this),
this.handleQuitAndResubscribe.bind(this));
}
handleEvent(diff) {
@ -35,10 +34,27 @@ export class Subscription {
handleError(err) {
console.error(err);
api.bind('/primary', 'PUT', api.authTokens.ship, 'chat',
this.handleEvent.bind(this),
this.handleError.bind(this));
}
handleQuitSilently(quit) {
// no-op
}
handleQuitAndResubscribe(quit) {
// TODO: resubscribe
}
fetchMessages(start, end, path) {
console.log(start, end, path);
fetch(`/~chat/paginate/${start}/${end}${path}`)
.then((response) => response.json())
.then((json) => {
store.handleEvent({
data: json
});
});
}
}
export let subscription = new Subscription();

View File

@ -7,40 +7,17 @@ export default class ChatTile extends Component {
render() {
const { props } = this;
let data = _.get(props.data, 'chat-configs', false);
let inviteNum = 0;
let msgNum = 0;
let inviteCircle = `~${window.ship}/i`;
let propNumbers = _.get(props, 'data.numbers.chat.numbers', false);
let propConfigs = _.get(props, 'data.config.chat.configs', false);
if (propNumbers && propConfigs) {
let numbers = {};
for (let i = 0; i < propNumbers.length; i++) {
let num = propNumbers[i];
numbers[num.circle] = num.length;
}
let configs = Object.keys(propConfigs);
for (let i = 0; i < configs.length; i++) {
let key = configs[i];
let host = key.split('/')[0];
if (!propConfigs[key]) { break; }
if (!(key in numbers)) { break; }
let red = propConfigs[key].red;
if (key === inviteCircle) {
inviteNum = inviteNum - red + numbers[key];
} else if (host === `~${window.ship}`) {
msgNum = msgNum - red + numbers[key];
} else {
msgNum = msgNum + numbers[key];
}
}
if (data) {
Object.keys(data).forEach((conf) => {
console.log(conf);
msgNum = msgNum + data[conf].length - data[conf].read;
});
}
let invSuffix = (inviteNum === 1) ? (
@ -106,4 +83,4 @@ export default class ChatTile extends Component {
}
window.chatTile = ChatTile;
window['chat-viewTile'] = ChatTile;

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 urbit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,10 +0,0 @@
# Clock tile for Urbit
To install this on your Urbit planet:
1. In your Urbit's Dojo, run |mount %
2. Write in the filepath to your Urbit's pier in the urbitrc-sample file in this repository, then copy it to .urbitrc in this directory.
3. Run `npm install` in terminal in this directory.
4. Run `gulp default` in terminal in this directory.
5. Run |start %clock in your Urbit's Dojo.
To see it, navigate to your Urbit's url and add /~home to the URL path.

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
module.exports = {
URBIT_PIERS: [
"/Users/logan/Dev/light-urbit/build/zod-chess-9/home",
]
};

View File

@ -461,11 +461,6 @@
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
"dev": true
},
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
},
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@ -1760,25 +1755,28 @@
"dependencies": {
"abbrev": {
"version": "1.1.1",
"bundled": true,
"resolved": false,
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true,
"optional": true
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"aproba": {
"version": "1.2.0",
"bundled": true,
"resolved": false,
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true,
"optional": true
},
"are-we-there-yet": {
"version": "1.1.5",
"bundled": true,
"resolved": false,
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"dev": true,
"optional": true,
"requires": {
@ -1788,15 +1786,15 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"resolved": false,
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -1804,37 +1802,40 @@
},
"chownr": {
"version": "1.1.1",
"bundled": true,
"resolved": false,
"integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==",
"dev": true,
"optional": true
},
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
},
"core-util-is": {
"version": "1.0.2",
"bundled": true,
"resolved": false,
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true,
"optional": true
},
"debug": {
"version": "4.1.1",
"bundled": true,
"resolved": false,
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"optional": true,
"requires": {
@ -1843,25 +1844,29 @@
},
"deep-extend": {
"version": "0.6.0",
"bundled": true,
"resolved": false,
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true,
"optional": true
},
"delegates": {
"version": "1.0.0",
"bundled": true,
"resolved": false,
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"dev": true,
"optional": true
},
"detect-libc": {
"version": "1.0.3",
"bundled": true,
"resolved": false,
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
"dev": true,
"optional": true
},
"fs-minipass": {
"version": "1.2.5",
"bundled": true,
"resolved": false,
"integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
"dev": true,
"optional": true,
"requires": {
@ -1870,13 +1875,15 @@
},
"fs.realpath": {
"version": "1.0.0",
"bundled": true,
"resolved": false,
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true,
"optional": true
},
"gauge": {
"version": "2.7.4",
"bundled": true,
"resolved": false,
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"dev": true,
"optional": true,
"requires": {
@ -1892,7 +1899,8 @@
},
"glob": {
"version": "7.1.3",
"bundled": true,
"resolved": false,
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"optional": true,
"requires": {
@ -1906,13 +1914,15 @@
},
"has-unicode": {
"version": "2.0.1",
"bundled": true,
"resolved": false,
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"dev": true,
"optional": true
},
"iconv-lite": {
"version": "0.4.24",
"bundled": true,
"resolved": false,
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"optional": true,
"requires": {
@ -1921,7 +1931,8 @@
},
"ignore-walk": {
"version": "3.0.1",
"bundled": true,
"resolved": false,
"integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
"dev": true,
"optional": true,
"requires": {
@ -1930,7 +1941,8 @@
},
"inflight": {
"version": "1.0.6",
"bundled": true,
"resolved": false,
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"optional": true,
"requires": {
@ -1940,51 +1952,53 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"ini": {
"version": "1.3.5",
"bundled": true,
"resolved": false,
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"resolved": false,
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
},
"isarray": {
"version": "1.0.0",
"bundled": true,
"resolved": false,
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true,
"optional": true
},
"minimatch": {
"version": "3.0.4",
"bundled": true,
"resolved": false,
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"resolved": false,
"integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -1992,7 +2006,8 @@
},
"minizlib": {
"version": "1.2.1",
"bundled": true,
"resolved": false,
"integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
"dev": true,
"optional": true,
"requires": {
@ -2001,22 +2016,24 @@
},
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"resolved": false,
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
},
"ms": {
"version": "2.1.1",
"bundled": true,
"resolved": false,
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true,
"optional": true
},
"needle": {
"version": "2.3.0",
"bundled": true,
"resolved": false,
"integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==",
"dev": true,
"optional": true,
"requires": {
@ -2027,7 +2044,8 @@
},
"node-pre-gyp": {
"version": "0.12.0",
"bundled": true,
"resolved": false,
"integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==",
"dev": true,
"optional": true,
"requires": {
@ -2045,7 +2063,8 @@
},
"nopt": {
"version": "4.0.1",
"bundled": true,
"resolved": false,
"integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
"dev": true,
"optional": true,
"requires": {
@ -2055,13 +2074,15 @@
},
"npm-bundled": {
"version": "1.0.6",
"bundled": true,
"resolved": false,
"integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==",
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.4.1",
"bundled": true,
"resolved": false,
"integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==",
"dev": true,
"optional": true,
"requires": {
@ -2071,7 +2092,8 @@
},
"npmlog": {
"version": "4.1.2",
"bundled": true,
"resolved": false,
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
"optional": true,
"requires": {
@ -2083,40 +2105,44 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
},
"object-assign": {
"version": "4.1.1",
"bundled": true,
"resolved": false,
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true,
"optional": true
},
"once": {
"version": "1.4.0",
"bundled": true,
"resolved": false,
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
},
"os-homedir": {
"version": "1.0.2",
"bundled": true,
"resolved": false,
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true,
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
"bundled": true,
"resolved": false,
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true,
"optional": true
},
"osenv": {
"version": "0.1.5",
"bundled": true,
"resolved": false,
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"dev": true,
"optional": true,
"requires": {
@ -2126,19 +2152,22 @@
},
"path-is-absolute": {
"version": "1.0.1",
"bundled": true,
"resolved": false,
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true,
"optional": true
},
"process-nextick-args": {
"version": "2.0.0",
"bundled": true,
"resolved": false,
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
"dev": true,
"optional": true
},
"rc": {
"version": "1.2.8",
"bundled": true,
"resolved": false,
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dev": true,
"optional": true,
"requires": {
@ -2150,7 +2179,8 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"bundled": true,
"resolved": false,
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true,
"optional": true
}
@ -2158,7 +2188,8 @@
},
"readable-stream": {
"version": "2.3.6",
"bundled": true,
"resolved": false,
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"optional": true,
"requires": {
@ -2173,7 +2204,8 @@
},
"rimraf": {
"version": "2.6.3",
"bundled": true,
"resolved": false,
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"optional": true,
"requires": {
@ -2182,45 +2214,50 @@
},
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
"bundled": true,
"resolved": false,
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"optional": true
},
"sax": {
"version": "1.2.4",
"bundled": true,
"resolved": false,
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"dev": true,
"optional": true
},
"semver": {
"version": "5.7.0",
"bundled": true,
"resolved": false,
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
"dev": true,
"optional": true
},
"set-blocking": {
"version": "2.0.0",
"bundled": true,
"resolved": false,
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true,
"optional": true
},
"signal-exit": {
"version": "3.0.2",
"bundled": true,
"resolved": false,
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true,
"optional": true
},
"string-width": {
"version": "1.0.2",
"bundled": true,
"resolved": false,
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -2229,7 +2266,8 @@
},
"string_decoder": {
"version": "1.1.1",
"bundled": true,
"resolved": false,
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"optional": true,
"requires": {
@ -2238,22 +2276,24 @@
},
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"resolved": false,
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"strip-json-comments": {
"version": "2.0.1",
"bundled": true,
"resolved": false,
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"dev": true,
"optional": true
},
"tar": {
"version": "4.4.8",
"bundled": true,
"resolved": false,
"integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
"dev": true,
"optional": true,
"requires": {
@ -2268,13 +2308,15 @@
},
"util-deprecate": {
"version": "1.0.2",
"bundled": true,
"resolved": false,
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true,
"optional": true
},
"wide-align": {
"version": "1.1.3",
"bundled": true,
"resolved": false,
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"dev": true,
"optional": true,
"requires": {
@ -2283,15 +2325,15 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true,
"optional": true
"resolved": false,
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
"dev": true
}
}
},
@ -5659,15 +5701,6 @@
"integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==",
"dev": true
},
"urbit-ob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/urbit-ob/-/urbit-ob-3.2.0.tgz",
"integrity": "sha512-nsDvWp4zILQ7VxnZnMtQgImMtgZiUbZTrs+bVh17VbXkYVmA+vxf3wEcgiEVsswGZKYAoG+7WQDLKd6t5Fcm5w==",
"requires": {
"bn.js": "^4.11.8",
"lodash": "^4.17.11"
}
},
"urix": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 urbit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,10 +0,0 @@
# Weather tile for Urbit
To install this on your Urbit planet:
1. In your Urbit's Dojo, run |mount %
2. Write in the filepath to your Urbit's pier in the urbitrc-sample file in this repository, then copy it to .urbitrc in this directory.
3. Run `npm install` in terminal in this directory.
4. Run `gulp default` in terminal in this directory.
5. Run |start %weather in your Urbit's Dojo.
To see it, navigate to your Urbit's url and add /~home to the URL path.

File diff suppressed because it is too large Load Diff

View File

@ -31,11 +31,7 @@
"classnames": "^2.2.6",
"lodash": "^4.17.11",
"moment": "^2.20.1",
"mousetrap": "^1.6.1",
"react": "^16.5.2",
"react-custom-scrollbars": "^4.2.1",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.0"
"react": "^16.5.2"
},
"resolutions": {
"natives": "1.1.3"

View File

@ -1,5 +0,0 @@
module.exports = {
URBIT_PIERS: [
"/Users/logan/Dev/light-urbit/build/zod-chess-9/home",
]
};