mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-02 20:15:27 +03:00
Merge branch 'master' into lf/chat-type-namespacing
This commit is contained in:
commit
cf8e8f8dc4
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:418f7512f9ff24de9c222cb4f666ef892c35376c7ee50f9f9e389c7ffa4711fc
|
||||
size 10010102
|
||||
oid sha256:801eb8574daff9f0ac88e2e40dab09d95bd8d667df953e971501a1f8db4fd039
|
||||
size 10394205
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ada435ce0c31b643530b81a3e2b5e2509aeb6407eb6dfe929fe073531fc6d220
|
||||
size 12479615
|
||||
oid sha256:ecfe53c9e00d8cba244c85eb0ec0ed3b4e8ac07737bb0c6a7aa57d6c52f52c85
|
||||
size 15993675
|
||||
|
@ -226,8 +226,6 @@
|
||||
::
|
||||
++ catch-up
|
||||
^- (quip card _state)
|
||||
?. .^(? %gu /(scot %p our.bowl)/chat-store/(scot %da now.bowl))
|
||||
[~ state]
|
||||
=/ =inbox:store
|
||||
(scry-for inbox:store %chat-store /all)
|
||||
|- ^- (quip card _state)
|
||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 611 B |
BIN
pkg/arvo/app/chat/img/ImageUpload.png
Normal file
BIN
pkg/arvo/app/chat/img/ImageUpload.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 865 B |
@ -26,5 +26,6 @@
|
||||
<script src="/~channel/channel.js"></script>
|
||||
<script src="/~modulo/session.js"></script>
|
||||
<script src="/~chat/js/index.js"></script>
|
||||
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.12.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
File diff suppressed because one or more lines are too long
@ -417,14 +417,17 @@
|
||||
:* to
|
||||
(mul windup-years yer:yo)
|
||||
stars
|
||||
(div (mul unlock-years yer:yo) stars)
|
||||
1
|
||||
(div (mul unlock-years yer:yo) stars)
|
||||
==
|
||||
::
|
||||
++ register-conditional
|
||||
|= [to=address [b1=@ud b2=@ud b3=@ud] unlock-years-per-batch=@ud]
|
||||
%- register-conditional:dat
|
||||
=- [`address`to b1 b2 b3 `@ud`- 1]
|
||||
(div (mul unlock-years-per-batch yer:yo) :(add b1 b2 b3))
|
||||
:* to
|
||||
b1 b2 b3
|
||||
1
|
||||
(div (mul unlock-years-per-batch yer:yo) :(add b1 b2 b3))
|
||||
==
|
||||
::
|
||||
--
|
||||
--
|
||||
|
BIN
pkg/arvo/app/contacts/img/ImageUpload.png
Normal file
BIN
pkg/arvo/app/contacts/img/ImageUpload.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 865 B |
@ -13,5 +13,6 @@
|
||||
<script src="/~channel/channel.js"></script>
|
||||
<script src="/~modulo/session.js"></script>
|
||||
<script src="/~groups/js/index.js"></script>
|
||||
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.12.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
File diff suppressed because one or more lines are too long
@ -215,7 +215,9 @@
|
||||
=/ ship (~(get by synced.state) pax.diff)
|
||||
?~ ship [~ state]
|
||||
?. =(src.bol u.ship) [~ state]
|
||||
?. (~(has in members.diff) our.bol) [~ state]
|
||||
?. (~(has in members.diff) our.bol)
|
||||
:_ state
|
||||
[(group-poke pax.diff diff)]~
|
||||
=/ changes (poke-group-hook-action [%remove pax.diff])
|
||||
:_ +.changes
|
||||
%+ welp -.changes
|
||||
|
@ -43,9 +43,9 @@
|
||||
!:
|
||||
=> |% ::
|
||||
++ hood-old :: unified old-state
|
||||
{?($1 $2 $3) lac/(map @tas hood-part-old)} ::
|
||||
{?($1 $2 $3 $4) lac/(map @tas hood-part-old)} ::
|
||||
++ hood-1 :: unified state
|
||||
{$3 lac/(map @tas hood-part)} ::
|
||||
{$4 lac/(map @tas hood-part)} ::
|
||||
++ hood-good :: extract specific
|
||||
=+ hed=$:hood-head
|
||||
|@ ++ $
|
||||
@ -140,7 +140,7 @@
|
||||
`..on-init
|
||||
::
|
||||
++ on-save
|
||||
!>([%3 lac])
|
||||
!>([%4 lac])
|
||||
::
|
||||
++ on-load
|
||||
|= =old-state=vase
|
||||
@ -150,7 +150,8 @@
|
||||
?- -.old-state
|
||||
%1 ((wrap on-load):from-drum:(help hid) %1)
|
||||
%2 ((wrap on-load):from-drum:(help hid) %2)
|
||||
%3 `lac
|
||||
%3 ((wrap on-load):from-drum:(help hid) %3)
|
||||
%4 `lac
|
||||
==
|
||||
[cards ..on-init]
|
||||
::
|
||||
|
@ -178,8 +178,11 @@ class Channel {
|
||||
this.lastEventId = e.lastEventId;
|
||||
|
||||
let obj = JSON.parse(e.data);
|
||||
if (obj.response == "poke") {
|
||||
let funcs = this.outstandingPokes.get(obj.id);
|
||||
let pokeFuncs = this.outstandingPokes.get(obj.id);
|
||||
let subFuncs = this.outstandingSubscriptions.get(obj.id);
|
||||
|
||||
if (obj.response == "poke" && !!pokeFuncs) {
|
||||
let funcs = pokeFuncs;
|
||||
if (obj.hasOwnProperty("ok")) {
|
||||
funcs["success"]();
|
||||
} else if (obj.hasOwnProperty("err")) {
|
||||
@ -189,19 +192,20 @@ class Channel {
|
||||
}
|
||||
this.outstandingPokes.delete(obj.id);
|
||||
|
||||
} else if (obj.response == "subscribe") {
|
||||
} else if (obj.response == "subscribe" ||
|
||||
(obj.response == "poke" && !!subFuncs)) {
|
||||
let funcs = subFuncs;
|
||||
// on a response to a subscribe, we only notify the caller on err
|
||||
//
|
||||
let funcs = this.outstandingSubscriptions.get(obj.id);
|
||||
if (obj.hasOwnProperty("err")) {
|
||||
funcs["err"](obj.err);
|
||||
this.outstandingSubscriptions.delete(obj.id);
|
||||
}
|
||||
} else if (obj.response == "diff") {
|
||||
let funcs = this.outstandingSubscriptions.get(obj.id);
|
||||
let funcs = subFuncs;
|
||||
funcs["event"](obj.json);
|
||||
} else if (obj.response == "quit") {
|
||||
let funcs = this.outstandingSubscriptions.get(obj.id);
|
||||
let funcs = subFuncs;
|
||||
funcs["quit"](obj);
|
||||
this.outstandingSubscriptions.delete(obj.id);
|
||||
} else {
|
||||
|
File diff suppressed because one or more lines are too long
@ -114,8 +114,6 @@
|
||||
`t.t.path
|
||||
~
|
||||
?~ target |
|
||||
~? !.^(? %gu (scot %p our.bowl) %metadata-store (scot %da now.bowl) ~)
|
||||
%woah-md-s-not-booted ::TODO fallback if needed
|
||||
%+ lien (groups-from-resource:md %link u.target)
|
||||
|= =group-path
|
||||
^- ?
|
||||
|
File diff suppressed because one or more lines are too long
@ -331,9 +331,16 @@
|
||||
?+ mar (on-poke:def mar vas)
|
||||
::
|
||||
%noun
|
||||
?: =(q.vas %flush-limbo)
|
||||
[~ this(limbo [~ ~])]
|
||||
[~ this]
|
||||
?+ q.vas
|
||||
[~ this]
|
||||
::
|
||||
%flush-limbo [~ this(limbo [~ ~])]
|
||||
::
|
||||
%reset-warp
|
||||
=/ rav [%sing %t [%da now.bol] /app/publish/notebooks]
|
||||
:_ this
|
||||
[%pass /read/paths %arvo %c %warp our.bol q.byk.bol `rav]~
|
||||
==
|
||||
::
|
||||
%handle-http-request
|
||||
=+ !<([id=@ta req=inbound-request:eyre] vas)
|
||||
@ -1857,8 +1864,9 @@
|
||||
::
|
||||
%remove
|
||||
=/ app-path [(scot %p author.del) /[book.del]]
|
||||
=/ group-path (group-from-book app-path)
|
||||
[(metadata-poke [%remove group-path [%publish app-path]])]~
|
||||
=/ group-path=(unit path) (group-from-book app-path)
|
||||
?~ group-path ~
|
||||
[(metadata-poke [%remove u.group-path [%publish app-path]])]~
|
||||
==
|
||||
::
|
||||
++ add
|
||||
@ -1884,13 +1892,12 @@
|
||||
::
|
||||
++ group-from-book
|
||||
|= app-path=path
|
||||
^- path
|
||||
^- (unit path)
|
||||
?. .^(? %gu (scot %p our.bol) %metadata-store (scot %da now.bol) ~)
|
||||
?: ?=([@ ^] app-path)
|
||||
~& [%assuming-ported-legacy-publish app-path]
|
||||
[%'~' app-path]
|
||||
~| [%weird-publish app-path]
|
||||
!!
|
||||
`[%'~' app-path]
|
||||
~&([%weird-publish app-path] ~)
|
||||
=/ resource-indices
|
||||
.^ (jug resource group-path)
|
||||
%gy
|
||||
@ -1899,8 +1906,12 @@
|
||||
(scot %da now.bol)
|
||||
/resource-indices
|
||||
==
|
||||
=/ groups=(set path) (~(got by resource-indices) [%publish app-path])
|
||||
(snag 0 ~(tap in groups))
|
||||
=/ groups=(unit (set path))
|
||||
(~(get by resource-indices) [%publish app-path])
|
||||
?~ groups ~
|
||||
=/ group-paths ~(tap in u.groups)
|
||||
?~ group-paths ~
|
||||
`i.group-paths
|
||||
--
|
||||
::
|
||||
++ metadata-hook-poke
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
96
pkg/arvo/app/s3-store.hoon
Normal file
96
pkg/arvo/app/s3-store.hoon
Normal file
@ -0,0 +1,96 @@
|
||||
/- *s3
|
||||
/+ s3-json, default-agent, verb, dbug
|
||||
~% %s3-top ..is ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
+$ versioned-state
|
||||
$% state-zero
|
||||
==
|
||||
::
|
||||
+$ state-zero [%0 =credentials =configuration]
|
||||
--
|
||||
::
|
||||
=| state-zero
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
~% %s3-agent-core ..card ~
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
[~ this(state !<(state-zero old-vase))]
|
||||
::
|
||||
++ on-poke
|
||||
~/ %s3-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
|^
|
||||
?> (team:title our.bowl src.bowl)
|
||||
=^ cards state
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%s3-action (poke-action !<(action vase))
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ poke-action
|
||||
|= act=action
|
||||
^- (quip card _state)
|
||||
:- [%give %fact [/all]~ %s3-update !>(act)]~
|
||||
?- -.act
|
||||
%set-endpoint
|
||||
state(endpoint.credentials endpoint.act)
|
||||
::
|
||||
%set-access-key-id
|
||||
state(access-key-id.credentials access-key-id.act)
|
||||
::
|
||||
%set-secret-access-key
|
||||
state(secret-access-key.credentials secret-access-key.act)
|
||||
::
|
||||
%set-current-bucket
|
||||
%_ state
|
||||
current-bucket.configuration bucket.act
|
||||
buckets.configuration (~(put in buckets.configuration) bucket.act)
|
||||
==
|
||||
::
|
||||
%add-bucket
|
||||
state(buckets.configuration (~(put in buckets.configuration) bucket.act))
|
||||
::
|
||||
%remove-bucket
|
||||
state(buckets.configuration (~(del in buckets.configuration) bucket.act))
|
||||
==
|
||||
--
|
||||
::
|
||||
++ on-watch
|
||||
~/ %s3-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
|^
|
||||
?> (team:title our.bowl src.bowl)
|
||||
=/ cards=(list card)
|
||||
?+ path (on-watch:def path)
|
||||
[%all ~]
|
||||
:~ (give %s3-update !>([%credentials credentials]))
|
||||
(give %s3-update !>([%configuration configuration]))
|
||||
==
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ give
|
||||
|= =cage
|
||||
^- card
|
||||
[%give %fact ~ cage]
|
||||
--
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
File diff suppressed because one or more lines are too long
10
pkg/arvo/gen/s3-store/add-bucket.hoon
Normal file
10
pkg/arvo/gen/s3-store/add-bucket.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: s3-store|add-bucket: add new bucket to S3 store
|
||||
::
|
||||
/- *s3
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[bucket=@t ~] ~]
|
||||
==
|
||||
:- %s3-action
|
||||
^- action
|
||||
[%add-bucket bucket]
|
10
pkg/arvo/gen/s3-store/remove-bucket.hoon
Normal file
10
pkg/arvo/gen/s3-store/remove-bucket.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: s3-store|remove-bucket: remove bucket from S3 store
|
||||
::
|
||||
/- *s3
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[bucket=@t ~] ~]
|
||||
==
|
||||
:- %s3-action
|
||||
^- action
|
||||
[%remove-bucket bucket]
|
10
pkg/arvo/gen/s3-store/set-access-key-id.hoon
Normal file
10
pkg/arvo/gen/s3-store/set-access-key-id.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: s3-store|set-access-key-id: set S3 access key ID
|
||||
::
|
||||
/- *s3
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[access-key-id=@t ~] ~]
|
||||
==
|
||||
:- %s3-action
|
||||
^- action
|
||||
[%set-access-key-id access-key-id]
|
10
pkg/arvo/gen/s3-store/set-current-bucket.hoon
Normal file
10
pkg/arvo/gen/s3-store/set-current-bucket.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: s3-store|set-current-bucket: set current bucket for S3
|
||||
::
|
||||
/- *s3
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[bucket=@t ~] ~]
|
||||
==
|
||||
:- %s3-action
|
||||
^- action
|
||||
[%set-current-bucket bucket]
|
10
pkg/arvo/gen/s3-store/set-endpoint.hoon
Normal file
10
pkg/arvo/gen/s3-store/set-endpoint.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: s3-store|set-endpoint: set S3 endpoint
|
||||
::
|
||||
/- *s3
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[endpoint=@t ~] ~]
|
||||
==
|
||||
:- %s3-action
|
||||
^- action
|
||||
[%set-endpoint endpoint]
|
10
pkg/arvo/gen/s3-store/set-secret-access-key.hoon
Normal file
10
pkg/arvo/gen/s3-store/set-secret-access-key.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: s3-store|set-secret-access-key: set S3 secret access key
|
||||
::
|
||||
/- *s3
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[secret-access-key=@t ~] ~]
|
||||
==
|
||||
:- %s3-action
|
||||
^- action
|
||||
[%set-secret-access-key secret-access-key]
|
@ -118,6 +118,7 @@
|
||||
%link-view
|
||||
%metadata-store
|
||||
%metadata-hook
|
||||
%s3-store
|
||||
==
|
||||
::
|
||||
++ deft-fish :: default connects
|
||||
@ -155,6 +156,7 @@
|
||||
=+ (~(gut by bin) ost *source)
|
||||
=* dev -
|
||||
|_ {moz/(list card:agent:gall) biz/(list dill-blit:dill)}
|
||||
+* this .
|
||||
++ diff-sole-effect-phat :: app event
|
||||
|= {way/wire fec/sole-effect}
|
||||
=< se-abet =< se-view
|
||||
@ -223,7 +225,7 @@
|
||||
==
|
||||
::
|
||||
++ on-load
|
||||
|= ver=?(%1 %2)
|
||||
|= ver=?(%1 %2 %3)
|
||||
?- ver
|
||||
%1
|
||||
=< se-abet =< se-view
|
||||
@ -237,7 +239,8 @@
|
||||
=< (se-born %home %link-store)
|
||||
=< (se-born %home %link-proxy-hook)
|
||||
=< (se-born %home %link-listen-hook)
|
||||
(se-born %home %link-view)
|
||||
=< (se-born %home %link-view)
|
||||
(se-born %home %s3-store)
|
||||
::
|
||||
%2
|
||||
=< se-abet =< se-view
|
||||
@ -250,7 +253,22 @@
|
||||
=< (se-born %home %link-store)
|
||||
=< (se-born %home %link-proxy-hook)
|
||||
=< (se-born %home %link-listen-hook)
|
||||
(se-born %home %link-view)
|
||||
=< (se-born %home %link-view)
|
||||
(se-born %home %s3-store)
|
||||
::
|
||||
%3
|
||||
=< se-abet =< se-view
|
||||
=< (se-emit %pass /kiln %arvo %g %sear ~wisrut-nocsub)
|
||||
=< (se-born %home %metadata-store)
|
||||
=< (se-born %home %metadata-hook)
|
||||
=< (se-born %home %contact-store)
|
||||
=< (se-born %home %contact-hook)
|
||||
=< (se-born %home %contact-view)
|
||||
=< (se-born %home %link-store)
|
||||
=< (se-born %home %link-proxy-hook)
|
||||
=< (se-born %home %link-listen-hook)
|
||||
=< (se-born %home %link-view)
|
||||
(se-born %home %s3-store)
|
||||
==
|
||||
::
|
||||
++ reap-phat :: ack connect
|
||||
@ -329,23 +347,70 @@
|
||||
[%give %fact ~[/drum] %dill-blit !>(dill-blit)]
|
||||
::
|
||||
++ se-adit :: update servers
|
||||
^+ .
|
||||
:: ensure dojo connects after talk
|
||||
=* dojo-on-top |=([a=* b=*] |(=(%dojo a) &(!=(%dojo b) (aor a b))))
|
||||
%+ roll (sort ~(tap in ray) dojo-on-top)
|
||||
=< .(con +>)
|
||||
|: $:{wel/well:gall con/_..se-adit} ^+ con
|
||||
=. +>.$ con
|
||||
=+ hig=(~(get by fur) q.wel)
|
||||
?: &(?=(^ hig) |(?=(~ u.hig) =(p.wel syd.u.u.hig))) +>.$
|
||||
=. +>.$ (se-text "activated app {(trip p.wel)}/{(trip q.wel)}")
|
||||
%- se-emit(fur (~(put by fur) q.wel ~))
|
||||
^+ this
|
||||
|^
|
||||
=/ servers=(list well:gall)
|
||||
(sort ~(tap in ray) sort-by-priorities)
|
||||
|-
|
||||
?~ servers
|
||||
this
|
||||
=/ wel=well:gall
|
||||
i.servers
|
||||
=/ =wire [%drum p.wel q.wel ~]
|
||||
[%pass wire %arvo %g %conf [our.hid q.wel] our.hid p.wel]
|
||||
=/ hig=(unit (unit server))
|
||||
(~(get by fur) q.wel)
|
||||
?: &(?=(^ hig) |(?=(~ u.hig) =(p.wel syd.u.u.hig)))
|
||||
$(servers t.servers)
|
||||
=. fur
|
||||
(~(put by fur) q.wel ~)
|
||||
=. this
|
||||
(se-text "activated app {(trip p.wel)}/{(trip q.wel)}")
|
||||
=. this
|
||||
%- se-emit
|
||||
[%pass wire %arvo %g %conf [our.hid q.wel] our.hid p.wel]
|
||||
$(servers t.servers)
|
||||
::
|
||||
++ priorities
|
||||
^- (list (set @))
|
||||
:~
|
||||
:: set up stores with priority: depended on, but never depending
|
||||
%- sy
|
||||
:~ %permission-store
|
||||
%chat-store
|
||||
%contact-store
|
||||
%group-store
|
||||
%link-store
|
||||
%invite-store
|
||||
%metadata-store
|
||||
==
|
||||
:: ensure chat-cli can sub to invites
|
||||
(sy ~[%chat-hook])
|
||||
==
|
||||
++ sort-by-priorities
|
||||
=/ priorities priorities
|
||||
|= [[desk a=term] [desk b=term]]
|
||||
^- ?
|
||||
?~ priorities
|
||||
(aor a b)
|
||||
=* priority i.priorities
|
||||
?: &((~(has in priority) a) (~(has in priority) b))
|
||||
(aor a b)
|
||||
?: (~(has in priority) a)
|
||||
%.y
|
||||
?: (~(has in priority) b)
|
||||
%.n
|
||||
$(priorities t.priorities)
|
||||
--
|
||||
::
|
||||
++ se-adze :: update connections
|
||||
^+ .
|
||||
%+ roll ~(tap in eel)
|
||||
%+ roll
|
||||
%+ sort
|
||||
~(tap in eel)
|
||||
|= [[@ a=term] [@ b=term]]
|
||||
?: =(a %dojo) %.n
|
||||
?: =(b %dojo) %.y
|
||||
(aor a b)
|
||||
=< .(con +>)
|
||||
|: $:{gil/gill:gall con/_.} ^+ con
|
||||
=. +>.$ con
|
||||
|
50
pkg/arvo/lib/s3-json.hoon
Normal file
50
pkg/arvo/lib/s3-json.hoon
Normal file
@ -0,0 +1,50 @@
|
||||
/- *s3
|
||||
|%
|
||||
++ json-to-action
|
||||
|= =json
|
||||
^- action
|
||||
=, format
|
||||
|^ (parse-json json)
|
||||
++ parse-json
|
||||
%- of:dejs
|
||||
:~ [%set-endpoint so:dejs]
|
||||
[%set-access-key-id so:dejs]
|
||||
[%set-secret-access-key so:dejs]
|
||||
[%add-bucket so:dejs]
|
||||
[%remove-bucket so:dejs]
|
||||
[%set-current-bucket so:dejs]
|
||||
==
|
||||
--
|
||||
::
|
||||
++ update-to-json
|
||||
|= upd=update
|
||||
^- json
|
||||
=, format
|
||||
%+ frond:enjs %s3-update
|
||||
%- pairs:enjs
|
||||
:~ ?- -.upd
|
||||
%set-current-bucket [%'setCurrentBucket' s+bucket.upd]
|
||||
%add-bucket [%'addBucket' s+bucket.upd]
|
||||
%remove-bucket [%'removeBucket' s+bucket.upd]
|
||||
%set-endpoint [%'setEndpoint' s+endpoint.upd]
|
||||
%set-access-key-id [%'setAccessKeyId' s+access-key-id.upd]
|
||||
%set-secret-access-key
|
||||
[%'setSecretAccessKey' s+secret-access-key.upd]
|
||||
::
|
||||
%credentials
|
||||
:- %credentials
|
||||
%- pairs:enjs
|
||||
:~ [%endpoint s+endpoint.credentials.upd]
|
||||
[%'accessKeyId' s+access-key-id.credentials.upd]
|
||||
[%'secretAccessKey' s+secret-access-key.credentials.upd]
|
||||
==
|
||||
::
|
||||
%configuration
|
||||
:- %configuration
|
||||
%- pairs:enjs
|
||||
:~ [%buckets a+(turn ~(tap in buckets.configuration.upd) |=(a=@t s+a))]
|
||||
[%'currentBucket' s+current-bucket.configuration.upd]
|
||||
==
|
||||
==
|
||||
==
|
||||
--
|
8
pkg/arvo/mar/s3/action.hoon
Normal file
8
pkg/arvo/mar/s3/action.hoon
Normal file
@ -0,0 +1,8 @@
|
||||
/+ *s3-json
|
||||
|_ act=action
|
||||
++ grab
|
||||
|%
|
||||
++ noun action
|
||||
++ json json-to-action
|
||||
--
|
||||
--
|
12
pkg/arvo/mar/s3/update.hoon
Normal file
12
pkg/arvo/mar/s3/update.hoon
Normal file
@ -0,0 +1,12 @@
|
||||
/+ *s3-json
|
||||
|_ upd=update
|
||||
++ grow
|
||||
|%
|
||||
++ json (update-to-json upd)
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
++ noun update
|
||||
--
|
||||
--
|
27
pkg/arvo/sur/s3.hoon
Normal file
27
pkg/arvo/sur/s3.hoon
Normal file
@ -0,0 +1,27 @@
|
||||
|%
|
||||
+$ credentials
|
||||
$: endpoint=@t
|
||||
access-key-id=@t
|
||||
secret-access-key=@t
|
||||
==
|
||||
::
|
||||
+$ configuration
|
||||
$: buckets=(set @t)
|
||||
current-bucket=@t
|
||||
==
|
||||
::
|
||||
+$ action
|
||||
$% [%set-endpoint endpoint=@t]
|
||||
[%set-access-key-id access-key-id=@t]
|
||||
[%set-secret-access-key secret-access-key=@t]
|
||||
[%add-bucket bucket=@t]
|
||||
[%remove-bucket bucket=@t]
|
||||
[%set-current-bucket bucket=@t]
|
||||
==
|
||||
::
|
||||
+$ update
|
||||
$% [%credentials =credentials]
|
||||
[%configuration =configuration]
|
||||
action
|
||||
==
|
||||
--
|
@ -78,7 +78,7 @@ instance FromNoun H.StdMethod where
|
||||
|
||||
-- Http Server Configuration ---------------------------------------------------
|
||||
|
||||
newtype PEM = PEM { unPEM :: Cord }
|
||||
newtype PEM = PEM { unPEM :: Wain }
|
||||
deriving newtype (Eq, Ord, Show, ToNoun, FromNoun)
|
||||
|
||||
type Key = PEM
|
||||
|
@ -24,6 +24,7 @@ type Life = Word -- Number of Azimoth key revs.
|
||||
type Bloq = Atom -- TODO
|
||||
type Oath = Atom -- Signature
|
||||
|
||||
|
||||
-- Parsed URLs -----------------------------------------------------------------
|
||||
|
||||
type Host = Each Turf Ipv4
|
||||
@ -169,7 +170,7 @@ data HttpServerReq = HttpServerReq
|
||||
data HttpClientEv
|
||||
= HttpClientEvReceive (KingId, ()) ServerId HttpEvent
|
||||
| HttpClientEvBorn (KingId, ()) ()
|
||||
| HttpClientEvCrud Path Cord Tang
|
||||
| HttpClientEvCrud Path Noun
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
data HttpServerEv
|
||||
@ -178,7 +179,7 @@ data HttpServerEv
|
||||
| HttpServerEvRequestLocal (ServId, UD, UD, ()) HttpServerReq
|
||||
| HttpServerEvLive (ServId, ()) Port (Maybe Port)
|
||||
| HttpServerEvBorn (KingId, ()) ()
|
||||
| HttpServerEvCrud Path Cord Tang
|
||||
| HttpServerEvCrud Path Noun
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
deriveNoun ''Address
|
||||
@ -193,7 +194,7 @@ deriveNoun ''HttpServerReq
|
||||
data AmesEv
|
||||
= AmesEvHear () AmesDest Bytes
|
||||
| AmesEvHole () AmesDest Bytes
|
||||
| AmesEvCrud Path Cord Tang
|
||||
| AmesEvCrud Path Noun
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
deriveNoun ''AmesEv
|
||||
@ -202,10 +203,10 @@ deriveNoun ''AmesEv
|
||||
-- Arvo Events -----------------------------------------------------------------
|
||||
|
||||
data ArvoEv
|
||||
= ArvoEvWhom () Ship
|
||||
| ArvoEvWack () Word512
|
||||
= ArvoEvWhom () Ship
|
||||
| ArvoEvWack () Word512
|
||||
| ArvoEvWarn Path Noun
|
||||
| ArvoEvCrud Path Cord Tang
|
||||
| ArvoEvCrud Path Noun
|
||||
| ArvoEvVeer Atom Noun
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
@ -216,7 +217,7 @@ deriveNoun ''ArvoEv
|
||||
|
||||
data BoatEv
|
||||
= BoatEvBoat () ()
|
||||
| BoatEvCrud Path Cord Tang
|
||||
| BoatEvCrud Path Noun
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
deriveNoun ''BoatEv
|
||||
@ -227,7 +228,7 @@ deriveNoun ''BoatEv
|
||||
data BehnEv
|
||||
= BehnEvWake () ()
|
||||
| BehnEvBorn (KingId, ()) ()
|
||||
| BehnEvCrud Path Cord Tang
|
||||
| BehnEvCrud Path Noun
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
deriveNoun ''BehnEv
|
||||
@ -237,7 +238,7 @@ deriveNoun ''BehnEv
|
||||
|
||||
data NewtEv
|
||||
= NewtEvBorn (KingId, ()) ()
|
||||
| NewtEvCrud Path Cord Tang
|
||||
| NewtEvCrud Path Noun
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
deriveNoun ''NewtEv
|
||||
@ -247,7 +248,7 @@ deriveNoun ''NewtEv
|
||||
|
||||
data SyncEv
|
||||
= SyncEvInto (Nullable (KingId, ())) Desk Bool [(Path, Maybe Mime)]
|
||||
| SyncEvCrud Path Cord Tang
|
||||
| SyncEvCrud Path Noun
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
deriveNoun ''SyncEv
|
||||
@ -278,7 +279,7 @@ data TermEv
|
||||
| TermEvBlew (UD, ()) Word Word
|
||||
| TermEvBoot (UD, ()) Bool LegacyBootEvent
|
||||
| TermEvHail (UD, ()) ()
|
||||
| TermEvCrud Path Cord Tang
|
||||
| TermEvCrud Path Noun
|
||||
deriving (Eq, Show)
|
||||
|
||||
deriveNoun ''LegacyBootEvent
|
||||
|
@ -8,7 +8,7 @@ module Urbit.Noun.Conversions
|
||||
, Bytes(..), Octs(..), File(..)
|
||||
, Cord(..), Knot(..), Term(..), Tape(..), Tour(..)
|
||||
, BigTape(..), BigCord(..)
|
||||
, Wall, Each(..)
|
||||
, Wain(..), Wall, Each(..)
|
||||
, UD(..), UV(..), UW(..), cordToUW
|
||||
, Mug(..), Path(..), EvilPath(..), Ship(..)
|
||||
, Lenient(..), pathToFilePath, filePathToPath
|
||||
@ -442,6 +442,20 @@ instance FromNoun Tape where
|
||||
Right tx -> pure (Tape tx)
|
||||
|
||||
|
||||
-- Wain -- List of Lines -------------------------------------------------------
|
||||
|
||||
newtype Wain = Wain { unWain :: Text }
|
||||
deriving newtype (Eq, Ord, Show, IsString, NFData)
|
||||
|
||||
instance ToNoun Wain where
|
||||
toNoun (Wain t) = toNoun (Cord <$> lines t)
|
||||
|
||||
instance FromNoun Wain where
|
||||
parseNoun n = named "Wain" $ do
|
||||
tx :: [Cord] <- parseNoun n
|
||||
pure $ Wain $ unlines (unCord <$> tx)
|
||||
|
||||
|
||||
-- Wall -- Text Lines ----------------------------------------------------------
|
||||
|
||||
type Wall = [Tape]
|
||||
|
@ -33,6 +33,7 @@ import Urbit.Vere.Pier.Types
|
||||
|
||||
import Data.Binary.Builder (Builder, fromByteString)
|
||||
import Data.Bits (shiftL, (.|.))
|
||||
import Data.PEM (pemParseBS, pemWriteBS)
|
||||
import Network.Socket (SockAddr(..))
|
||||
import System.Directory (doesFileExist, removeFile)
|
||||
import System.Random (randomIO)
|
||||
@ -216,6 +217,9 @@ writePortsFile f = writeFile f . encodeUtf8 . portsFileText
|
||||
cordBytes :: Cord -> ByteString
|
||||
cordBytes = encodeUtf8 . unCord
|
||||
|
||||
wainBytes :: Wain -> ByteString
|
||||
wainBytes = encodeUtf8 . unWain
|
||||
|
||||
pass :: Monad m => m ()
|
||||
pass = pure ()
|
||||
|
||||
@ -499,14 +503,22 @@ httpServerPorts fak = do
|
||||
|
||||
pure (PortsToTry { .. })
|
||||
|
||||
parseCerts :: ByteString -> Maybe (ByteString, [ByteString])
|
||||
parseCerts bs = do
|
||||
pems <- pemParseBS bs & either (const Nothing) Just
|
||||
case pems of
|
||||
[] -> Nothing
|
||||
p:ps -> pure (pemWriteBS p, pemWriteBS <$> ps)
|
||||
|
||||
startServ :: (HasPierConfig e, HasLogFunc e, HasNetworkConfig e)
|
||||
=> Bool -> HttpServerConf -> (Ev -> STM ())
|
||||
-> RIO e Serv
|
||||
startServ isFake conf plan = do
|
||||
logDebug "startServ"
|
||||
|
||||
let tls = hscSecure conf <&> \(PEM key, PEM cert) ->
|
||||
(W.tlsSettingsMemory (cordBytes cert) (cordBytes key))
|
||||
let tls = do (PEM key, PEM certs) <- hscSecure conf
|
||||
(cert, chain) <- parseCerts (wainBytes certs)
|
||||
pure $ W.tlsSettingsChainMemory cert chain $ wainBytes key
|
||||
|
||||
sId <- io $ ServId . UV . fromIntegral <$> (randomIO :: IO Word32)
|
||||
liv <- newTVarIO emptyLiveReqs
|
||||
|
@ -67,6 +67,7 @@ dependencies:
|
||||
- network
|
||||
- optparse-applicative
|
||||
- para
|
||||
- pem
|
||||
- pretty-show
|
||||
- primitive
|
||||
- process
|
||||
|
@ -71,7 +71,7 @@ h2 {
|
||||
}
|
||||
|
||||
.clamp-attachment {
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
max-height: 10em;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ class UrbitApi {
|
||||
|
||||
addPendingMessage(msg) {
|
||||
if (store.state.pendingMessages.has(msg.path)) {
|
||||
store.state.pendingMessages.get(msg.path).push(msg.envelope);
|
||||
store.state.pendingMessages.get(msg.path).unshift(msg.envelope);
|
||||
} else {
|
||||
store.state.pendingMessages.set(msg.path, [msg.envelope]);
|
||||
}
|
||||
|
@ -22,6 +22,37 @@ function getNumPending(props) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const ACTIVITY_TIMEOUT = 60000; // a minute
|
||||
const DEFAULT_BACKLOG_SIZE = 300;
|
||||
|
||||
function scrollIsAtTop(container) {
|
||||
if ((navigator.userAgent.includes("Safari") &&
|
||||
navigator.userAgent.includes("Chrome")) ||
|
||||
navigator.userAgent.includes("Firefox")
|
||||
) {
|
||||
return container.scrollTop === 0;
|
||||
} else if (navigator.userAgent.includes("Safari")) {
|
||||
return container.scrollHeight + Math.round(container.scrollTop) <=
|
||||
container.clientHeight + 10;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function scrollIsAtBottom(container) {
|
||||
if ((navigator.userAgent.includes("Safari") &&
|
||||
navigator.userAgent.includes("Chrome")) ||
|
||||
navigator.userAgent.includes("Firefox")
|
||||
) {
|
||||
return container.scrollHeight - Math.round(container.scrollTop) <=
|
||||
container.clientHeight + 10;
|
||||
} else if (navigator.userAgent.includes("Safari")) {
|
||||
return container.scrollTop === 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -29,9 +60,10 @@ export class ChatScreen extends Component {
|
||||
this.state = {
|
||||
numPages: 1,
|
||||
scrollLocked: false,
|
||||
read: props.read,
|
||||
active: true,
|
||||
// only for FF
|
||||
lastScrollHeight: null,
|
||||
scrollBottom: true
|
||||
};
|
||||
|
||||
this.hasAskedForMessages = false;
|
||||
@ -41,6 +73,12 @@ export class ChatScreen extends Component {
|
||||
this.onScroll = this.onScroll.bind(this);
|
||||
|
||||
this.unreadMarker = null;
|
||||
this.scrolledToMarker = false;
|
||||
this.setUnreadMarker = this.setUnreadMarker.bind(this);
|
||||
|
||||
this.activityTimeout = true;
|
||||
this.handleActivity = this.handleActivity.bind(this);
|
||||
this.setInactive = this.setInactive.bind(this);
|
||||
|
||||
moment.updateLocale('en', {
|
||||
calendar: {
|
||||
@ -56,10 +94,72 @@ export class ChatScreen extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.askForMessages();
|
||||
this.scrollToBottom();
|
||||
document.addEventListener("mousemove", this.handleActivity, false);
|
||||
document.addEventListener("mousedown", this.handleActivity, false);
|
||||
document.addEventListener("keypress", this.handleActivity, false);
|
||||
document.addEventListener("touchmove", this.handleActivity, false);
|
||||
this.activityTimeout = setTimeout(this.setInactive, ACTIVITY_TIMEOUT);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("mousemove", this.handleActivity, false);
|
||||
document.removeEventListener("mousedown", this.handleActivity, false);
|
||||
document.removeEventListener("keypress", this.handleActivity, false);
|
||||
document.removeEventListener("touchmove", this.handleActivity, false);
|
||||
if(this.activityTimeout) {
|
||||
clearTimeout(this.activityTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
handleActivity() {
|
||||
if(!this.state.active) {
|
||||
this.setState({ active: true });
|
||||
}
|
||||
|
||||
if(this.activityTimeout) {
|
||||
clearTimeout(this.activityTimeout);
|
||||
}
|
||||
|
||||
this.activityTimeout = setTimeout(this.setInactive, ACTIVITY_TIMEOUT);
|
||||
}
|
||||
|
||||
setInactive() {
|
||||
this.activityTimeout = null;
|
||||
this.setState({ active: false, scrollLocked: true });
|
||||
}
|
||||
|
||||
receivedNewChat() {
|
||||
const { props } = this;
|
||||
this.hasAskedForMessages = false;
|
||||
|
||||
this.unreadMarker = null;
|
||||
this.scrolledToMarker = false;
|
||||
|
||||
this.setState({ read: props.read });
|
||||
|
||||
const unread = props.length - props.read;
|
||||
const unreadUnloaded = unread - props.envelopes.length;
|
||||
|
||||
if(unreadUnloaded + 20 > DEFAULT_BACKLOG_SIZE) {
|
||||
this.askForMessages(unreadUnloaded + 20);
|
||||
} else {
|
||||
this.askForMessages(DEFAULT_BACKLOG_SIZE);
|
||||
}
|
||||
|
||||
if(props.read === props.length){
|
||||
this.scrolledToMarker = true;
|
||||
this.setState(
|
||||
{
|
||||
scrollLocked: false,
|
||||
},
|
||||
() => {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.setState({ scrollLocked: true, numPages: Math.ceil(unread/100) });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { props, state } = this;
|
||||
@ -68,18 +168,7 @@ export class ChatScreen extends Component {
|
||||
prevProps.match.params.station !== props.match.params.station ||
|
||||
prevProps.match.params.ship !== props.match.params.ship
|
||||
) {
|
||||
this.hasAskedForMessages = false;
|
||||
|
||||
if (props.envelopes.length < 100) {
|
||||
this.askForMessages();
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{ scrollLocked: false },
|
||||
() => {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
);
|
||||
this.receivedNewChat();
|
||||
} else if (props.chatInitialized &&
|
||||
!(props.station in props.inbox) &&
|
||||
(!!props.chatSynced && !(props.station in props.chatSynced))) {
|
||||
@ -89,21 +178,26 @@ export class ChatScreen extends Component {
|
||||
props.envelopes.length >= prevProps.envelopes.length + 10
|
||||
) {
|
||||
this.hasAskedForMessages = false;
|
||||
} else if(props.length !== prevProps.length &&
|
||||
prevProps.length === prevState.read &&
|
||||
state.active
|
||||
) {
|
||||
this.setState({ read: props.length });
|
||||
this.props.api.chat.read(this.props.station);
|
||||
}
|
||||
|
||||
if(!prevProps.chatInitialized && props.chatInitialized) {
|
||||
this.receivedNewChat();
|
||||
}
|
||||
|
||||
// FF logic
|
||||
if (
|
||||
navigator.userAgent.includes("Firefox") &&
|
||||
(props.length !== prevProps.length ||
|
||||
props.envelopes.length !== prevProps.envelopes.length ||
|
||||
getNumPending(props) !== this.lastNumPending ||
|
||||
state.numPages !== prevState.numPages)
|
||||
) {
|
||||
if(state.scrollBottom) {
|
||||
setTimeout(() => {
|
||||
this.scrollToBottom();
|
||||
})
|
||||
} else {
|
||||
this.scrollToBottom();
|
||||
if(navigator.userAgent.includes("Firefox")) {
|
||||
this.recalculateScrollTop();
|
||||
}
|
||||
|
||||
@ -111,16 +205,9 @@ export class ChatScreen extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
askForMessages() {
|
||||
askForMessages(size) {
|
||||
const { props, state } = this;
|
||||
|
||||
if (props.envelopes.length === 0) {
|
||||
setTimeout(() => {
|
||||
this.askForMessages();
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
props.envelopes.length >= props.length ||
|
||||
this.hasAskedForMessages ||
|
||||
@ -132,7 +219,7 @@ export class ChatScreen extends Component {
|
||||
let start =
|
||||
props.length - props.envelopes[props.envelopes.length - 1].number;
|
||||
if (start > 0) {
|
||||
let end = start + 300 < props.length ? start + 300 : props.length;
|
||||
const end = start + size < props.length ? start + size : props.length;
|
||||
this.hasAskedForMessages = true;
|
||||
props.subscription.fetchMessages(start + 1, end, props.station);
|
||||
}
|
||||
@ -161,80 +248,51 @@ export class ChatScreen extends Component {
|
||||
}
|
||||
|
||||
onScroll(e) {
|
||||
if (
|
||||
(navigator.userAgent.includes("Safari") &&
|
||||
navigator.userAgent.includes("Chrome")) ||
|
||||
navigator.userAgent.includes("Firefox")
|
||||
) {
|
||||
// Google Chrome and Firefox
|
||||
if (e.target.scrollTop === 0) {
|
||||
|
||||
// Save scroll position for FF
|
||||
if (navigator.userAgent.includes('Firefox')) {
|
||||
|
||||
this.setState({
|
||||
lastScrollHeight: e.target.scrollHeight
|
||||
})
|
||||
if(scrollIsAtTop(e.target)) {
|
||||
// Save scroll position for FF
|
||||
if (navigator.userAgent.includes('Firefox')) {
|
||||
this.setState({
|
||||
lastScrollHeight: e.target.scrollHeight
|
||||
});
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
numPages: this.state.numPages + 1,
|
||||
scrollLocked: true
|
||||
},
|
||||
() => {
|
||||
this.askForMessages(DEFAULT_BACKLOG_SIZE);
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
numPages: this.state.numPages + 1,
|
||||
scrollLocked: true
|
||||
},
|
||||
() => {
|
||||
this.askForMessages();
|
||||
}
|
||||
);
|
||||
} else if (
|
||||
e.target.scrollHeight - Math.round(e.target.scrollTop) ===
|
||||
e.target.clientHeight
|
||||
) {
|
||||
this.setState({
|
||||
numPages: 1,
|
||||
scrollLocked: false,
|
||||
scrollBottom: true
|
||||
});
|
||||
} else if (navigator.userAgent.includes('Firefox')) {
|
||||
this.setState({ scrollBottom: false });
|
||||
}
|
||||
} else if (navigator.userAgent.includes("Safari")) {
|
||||
// Safari
|
||||
if (e.target.scrollTop === 0) {
|
||||
this.setState({
|
||||
numPages: 1,
|
||||
scrollLocked: false
|
||||
});
|
||||
} else if (
|
||||
e.target.scrollHeight + Math.round(e.target.scrollTop) <=
|
||||
e.target.clientHeight + 10
|
||||
) {
|
||||
this.setState(
|
||||
{
|
||||
numPages: this.state.numPages + 1,
|
||||
scrollLocked: true
|
||||
},
|
||||
() => {
|
||||
this.askForMessages();
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log("Your browser is not supported.");
|
||||
);
|
||||
} else if (scrollIsAtBottom(e.target)) {
|
||||
this.dismissUnread();
|
||||
this.setState({
|
||||
numPages: 1,
|
||||
scrollLocked: false
|
||||
});
|
||||
}
|
||||
if(!!this.unreadMarker) {
|
||||
if(
|
||||
!navigator.userAgent.includes('Firefox') &&
|
||||
e.target.scrollHeight - e.target.scrollTop - (e.target.clientHeight * 1.5) + this.unreadMarker.offsetTop > 50
|
||||
) {
|
||||
this.props.api.chat.read(this.props.station);
|
||||
} else if(navigator.userAgent.includes('Firefox') &&
|
||||
this.unreadMarker.offsetTop - e.target.scrollTop - (e.target.clientHeight / 2) > 0
|
||||
) {
|
||||
this.props.api.chat.read(this.props.station);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setUnreadMarker(ref) {
|
||||
if(ref && !this.scrolledToMarker) {
|
||||
this.setState({ scrollLocked: true }, () => {
|
||||
ref.scrollIntoView({ block: 'center' });
|
||||
if(ref.offsetParent &&
|
||||
scrollIsAtBottom(ref.offsetParent)) {
|
||||
this.dismissUnread();
|
||||
this.setState({
|
||||
numPages: 1,
|
||||
scrollLocked: false
|
||||
});
|
||||
}
|
||||
});
|
||||
this.scrolledToMarker = true;
|
||||
}
|
||||
this.unreadMarker = ref;
|
||||
}
|
||||
|
||||
dismissUnread() {
|
||||
this.props.api.chat.read(this.props.station);
|
||||
}
|
||||
|
||||
chatWindow(unread) {
|
||||
@ -291,12 +349,12 @@ export class ChatScreen extends Component {
|
||||
group={props.association}
|
||||
/>
|
||||
);
|
||||
if(unread > 0 && i === unread) {
|
||||
if(unread > 0 && i === unread - 1) {
|
||||
return (
|
||||
<>
|
||||
{messageElem}
|
||||
<div key={'unreads'+ msg.uid} ref={ref => (this.unreadMarker = ref)} className="mv2 green2 flex items-center f9">
|
||||
<hr className="ma0 w2 b--green2 bt-0" />
|
||||
<div key={'unreads'+ msg.uid} ref={this.setUnreadMarker} className="mv2 green2 flex items-center f9">
|
||||
<hr className="dn-s ma0 w2 b--green2 bt-0" />
|
||||
<p className="mh4">
|
||||
New messages below
|
||||
</p>
|
||||
@ -410,10 +468,13 @@ export class ChatScreen extends Component {
|
||||
: props.station.substr(1);
|
||||
}
|
||||
|
||||
const unread = props.length - props.read;
|
||||
const unread = props.length - state.read;
|
||||
|
||||
const unreadMsg = unread > 0 && messages[unread - 1];
|
||||
|
||||
|
||||
const showUnreadNotice = props.length !== props.read && props.read === state.read;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={props.station}
|
||||
@ -449,11 +510,11 @@ export class ChatScreen extends Component {
|
||||
api={props.api}
|
||||
/>
|
||||
</div>
|
||||
{ !!unreadMsg && (
|
||||
{ !!unreadMsg && showUnreadNotice && (
|
||||
<UnreadNotice
|
||||
unread={unread}
|
||||
unreadMsg={unreadMsg}
|
||||
onRead={() => props.api.chat.read(props.station)}
|
||||
onRead={() => this.dismissUnread()}
|
||||
/>
|
||||
) }
|
||||
{this.chatWindow(unread)}
|
||||
@ -465,6 +526,8 @@ export class ChatScreen extends Component {
|
||||
ownerContact={ownerContact}
|
||||
envelopes={props.envelopes}
|
||||
contacts={props.contacts}
|
||||
onEnter={() => this.setState({ scrollLocked: false })}
|
||||
s3={props.s3}
|
||||
placeholder="Message..."
|
||||
/>
|
||||
</div>
|
||||
|
@ -2,12 +2,14 @@ import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { UnControlled as CodeEditor } from 'react-codemirror2';
|
||||
import CodeMirror from 'codemirror';
|
||||
|
||||
import 'codemirror/mode/markdown/markdown';
|
||||
import 'codemirror/addon/display/placeholder';
|
||||
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
import { ShipSearch } from '/components/lib/ship-search';
|
||||
import { S3Upload } from '/components/lib/s3-upload';
|
||||
|
||||
import { uxToHex } from '/lib/util';
|
||||
|
||||
@ -149,8 +151,8 @@ export class ChatInput extends Component {
|
||||
}
|
||||
|
||||
getLetterType(letter) {
|
||||
if (letter.startsWith('/me')) {
|
||||
letter = letter.slice(3);
|
||||
if (letter.startsWith('/me ')) {
|
||||
letter = letter.slice(4);
|
||||
// remove insignificant leading whitespace.
|
||||
// aces might be relevant to style.
|
||||
while (letter[0] === '\n') {
|
||||
@ -173,7 +175,7 @@ export class ChatInput extends Component {
|
||||
|
||||
isUrl(string) {
|
||||
try {
|
||||
const websiteTest = new RegExp(String(/((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source)
|
||||
const websiteTest = new RegExp(String(/^((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source)
|
||||
);
|
||||
return websiteTest.test(string);
|
||||
} catch (e) {
|
||||
@ -192,6 +194,8 @@ export class ChatInput extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
props.onEnter();
|
||||
|
||||
if(state.code) {
|
||||
props.api.chat.message(props.station, `~${window.ship}`, Date.now(), {
|
||||
code: {
|
||||
@ -265,6 +269,20 @@ export class ChatInput extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
uploadSuccess(url) {
|
||||
const { props } = this;
|
||||
props.api.chat.message(
|
||||
props.station,
|
||||
`~${window.ship}`,
|
||||
Date.now(),
|
||||
{ url }
|
||||
);
|
||||
}
|
||||
|
||||
uploadError(error) {
|
||||
// no-op for now
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
@ -303,10 +321,16 @@ export class ChatInput extends Component {
|
||||
extraKeys: {
|
||||
Tab: cm =>
|
||||
this.patpAutocomplete(cm.getValue(), true),
|
||||
'Enter': cm =>
|
||||
this.messageSubmit(),
|
||||
'Enter': () => {
|
||||
this.messageSubmit();
|
||||
if (this.state.code) {
|
||||
this.toggleCode();
|
||||
}
|
||||
},
|
||||
'Shift-3': cm =>
|
||||
this.toggleCode()
|
||||
cm.getValue().length === 0
|
||||
? this.toggleCode()
|
||||
: CodeMirror.Pass
|
||||
}
|
||||
};
|
||||
|
||||
@ -335,26 +359,38 @@ export class ChatInput extends Component {
|
||||
</div>
|
||||
<div
|
||||
className="fr h-100 flex bg-gray0-d lh-copy pl2 w-100 items-center"
|
||||
style={{ flexGrow: 1, maxHeight: '224px', width: 'calc(100% - 48px)' }}
|
||||
style={{ flexGrow: 1, maxHeight: '224px', width: 'calc(100% - 72px)' }}
|
||||
>
|
||||
<CodeEditor
|
||||
options={options}
|
||||
editorDidMount={(editor) => {
|
||||
this.editor = editor;
|
||||
}}
|
||||
this.editor = editor;
|
||||
if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(
|
||||
navigator.userAgent
|
||||
)) {
|
||||
editor.focus();
|
||||
}
|
||||
}}
|
||||
onChange={(e, d, v) => this.messageChange(e, d, v)}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ height: '24px', width: '24px', flexBasis: 24, marginTop: 6 }}>
|
||||
<div className="ml2 mr2"
|
||||
style={{ height: '16px', width: '16px', flexBasis: 16, marginTop: 10 }}>
|
||||
<S3Upload
|
||||
configuration={props.s3.configuration}
|
||||
credentials={props.s3.credentials}
|
||||
uploadSuccess={this.uploadSuccess.bind(this)}
|
||||
uploadError={this.uploadError.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ height: '16px', width: '16px', flexBasis: 16, marginTop: 10 }}>
|
||||
<img
|
||||
style={{ filter: state.code && 'invert(100%)', height: '100%', width: '100%' }}
|
||||
onClick={this.toggleCode}
|
||||
src="/~chat/img/CodeEval.png"
|
||||
className="contrast-10-d bg-white bg-none-d"
|
||||
className="contrast-10-d bg-white bg-none-d ba b--gray1-d br1"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ export class Sigil extends Component {
|
||||
return (
|
||||
<div
|
||||
className={'dib ' + classes}
|
||||
style={{ flexBasis: 32, backgroundColor: props.color }}
|
||||
style={{ flexBasis: props.size, backgroundColor: props.color }}
|
||||
>
|
||||
{sigil({
|
||||
patp: props.ship,
|
||||
|
@ -65,21 +65,21 @@ export class Message extends Component {
|
||||
(!!letter.code.output &&
|
||||
letter.code.output.length && letter.code.output.length > 0) ?
|
||||
(
|
||||
<pre className="f7 clamp-attachment pa1 mt0 mb0">
|
||||
<pre className="f7 clamp-attachment pa1 mt0 mb0 b--gray4 b--gray1-d bl br bb">
|
||||
{letter.code.output[0].join('\n')}
|
||||
</pre>
|
||||
) : null;
|
||||
return (
|
||||
<span>
|
||||
<pre className="f7 clamp-attachment pa1 mt0 mb0 bg-light-gray">
|
||||
<div className="mv2">
|
||||
<pre className="f7 clamp-attachment pa1 mt0 mb0 bg-light-gray b--gray4 b--gray1-d ba">
|
||||
{letter.code.expression}
|
||||
</pre>
|
||||
{outputElement}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
} else if ('url' in letter) {
|
||||
let imgMatch =
|
||||
/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF|GIF|webp|WEBP|webm|WEBM)$/
|
||||
/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF|GIF|webp|WEBP|webm|WEBM|svg|SVG)$/
|
||||
.exec(letter.url);
|
||||
let youTubeRegex = new RegExp(''
|
||||
+ /(?:https?:\/\/(?:[a-z]+.)?)/.source // protocol
|
||||
|
96
pkg/interface/chat/src/js/components/lib/s3-upload.js
Normal file
96
pkg/interface/chat/src/js/components/lib/s3-upload.js
Normal file
@ -0,0 +1,96 @@
|
||||
import React, { Component } from 'react'
|
||||
import S3Client from '/lib/s3';
|
||||
|
||||
export class S3Upload extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.s3 = new S3Client();
|
||||
this.setCredentials(props.credentials, props.configuration);
|
||||
this.inputRef = React.createRef();
|
||||
}
|
||||
|
||||
isReady(creds, config) {
|
||||
return (
|
||||
!!creds &&
|
||||
'endpoint' in creds &&
|
||||
'accessKeyId' in creds &&
|
||||
'secretAccessKey' in creds &&
|
||||
creds.endpoint !== '' &&
|
||||
creds.accessKeyId !== '' &&
|
||||
creds.secretAccessKey !== '' &&
|
||||
!!config &&
|
||||
'currentBucket' in config &&
|
||||
config.currentBucket !== ''
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props } = this;
|
||||
this.setCredentials(props.credentials, props.configuration);
|
||||
}
|
||||
|
||||
setCredentials(credentials, configuration) {
|
||||
if (!this.isReady(credentials, configuration)) { return; }
|
||||
this.s3.setCredentials(
|
||||
credentials.endpoint,
|
||||
credentials.accessKeyId,
|
||||
credentials.secretAccessKey
|
||||
);
|
||||
}
|
||||
|
||||
getFileUrl(endpoint, filename) {
|
||||
return endpoint + '/' + filename;
|
||||
}
|
||||
|
||||
onChange() {
|
||||
const { props } = this;
|
||||
if (!this.inputRef.current) { return; }
|
||||
let files = this.inputRef.current.files;
|
||||
if (files.length <= 0) { return; }
|
||||
|
||||
let file = files.item(0);
|
||||
let bucket = props.configuration.currentBucket;
|
||||
|
||||
this.s3.upload(bucket, file.name, file).then((data) => {
|
||||
if (!data || !('Location' in data)) {
|
||||
return;
|
||||
}
|
||||
this.props.uploadSuccess(data.Location);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
this.props.uploadError(err);
|
||||
});
|
||||
}
|
||||
|
||||
onClick() {
|
||||
if (!this.inputRef.current) { return; }
|
||||
this.inputRef.current.click();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
if (!this.isReady(props.credentials, props.configuration)) {
|
||||
return <div></div>;
|
||||
} else {
|
||||
let classes = !!props.className ?
|
||||
"pointer " + props.className : "pointer";
|
||||
return (
|
||||
<div className={classes}>
|
||||
<input className="dn"
|
||||
type="file"
|
||||
id="fileElement"
|
||||
ref={this.inputRef}
|
||||
accept="image/*"
|
||||
onChange={this.onChange.bind(this)} />
|
||||
<img className="invert-d"
|
||||
src="/~chat/img/ImageUpload.png"
|
||||
width="16"
|
||||
height="16"
|
||||
onClick={this.onClick.bind(this)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ function ShipSearchItem({ ship, contacts, selected, onSelect }) {
|
||||
nameStyle.color = hexToRgba(hex, 0.7);
|
||||
nameStyle.textShadow = '0px 0px 0px #000';
|
||||
nameStyle.filter = 'contrast(1.3) saturate(1.5)';
|
||||
nameStyle.maxWidth = '200px';
|
||||
sigilClass = 'v-mid';
|
||||
nickname = contact.nickname;
|
||||
}
|
||||
@ -28,7 +29,7 @@ function ShipSearchItem({ ship, contacts, selected, onSelect }) {
|
||||
<div
|
||||
onClick={() => onSelect(ship)}
|
||||
className={cn(
|
||||
'f8 pv1 ph3 pointer hover-bg-gray1-d hover-bg-gray4 relative flex items-center',
|
||||
'f9 pv1 ph3 pointer hover-bg-gray1-d hover-bg-gray4 relative flex items-center',
|
||||
{
|
||||
'white-d bg-gray0-d bg-white': !isSelected,
|
||||
'black-d bg-gray1-d bg-gray4': isSelected
|
||||
@ -38,7 +39,7 @@ function ShipSearchItem({ ship, contacts, selected, onSelect }) {
|
||||
>
|
||||
<Sigil ship={'~' + ship} size={24} color={color} classes={sigilClass} />
|
||||
{nickname && (
|
||||
<p style={nameStyle} className="dib ml4 b">
|
||||
<p style={nameStyle} className="dib ml4 b truncate">
|
||||
{nickname}
|
||||
</p>
|
||||
)}
|
||||
@ -232,7 +233,7 @@ export class ShipSearch extends Component {
|
||||
let idx = suggestions.findIndex(s => s === this.state.selected);
|
||||
|
||||
idx = backward ? idx - 1 : idx + 1;
|
||||
idx = idx % suggestions.length;
|
||||
idx = idx % Math.min(suggestions.length, 5);
|
||||
if (idx < 0) {
|
||||
idx = suggestions.length - 1;
|
||||
}
|
||||
|
@ -11,13 +11,10 @@ export class NewDmScreen extends Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
ship: null,
|
||||
idError: false,
|
||||
inviteError: false,
|
||||
allowHistory: true,
|
||||
station: null,
|
||||
awaiting: false
|
||||
};
|
||||
|
||||
this.setInvite = this.setInvite.bind(this);
|
||||
this.onClickCreate = this.onClickCreate.bind(this);
|
||||
}
|
||||
|
||||
@ -26,8 +23,6 @@ export class NewDmScreen extends Component {
|
||||
if (props.autoCreate && urbitOb.isValidPatp(props.autoCreate)) {
|
||||
this.setState(
|
||||
{
|
||||
error: false,
|
||||
success: true,
|
||||
ship: props.autoCreate.slice(1),
|
||||
awaiting: true
|
||||
},
|
||||
@ -40,20 +35,14 @@ export class NewDmScreen extends Component {
|
||||
const { props, state } = this;
|
||||
|
||||
if (prevProps !== props) {
|
||||
let station = `/~${window.ship}/${state.idName}`;
|
||||
if (station in props.inbox) {
|
||||
props.history.push("/~chat/room" + station);
|
||||
const { station } = this.state;
|
||||
if (station && station in props.inbox) {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push(`/~chat/room${station}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setInvite(value) {
|
||||
this.setState({
|
||||
groups: [],
|
||||
ship: value.ships[0]
|
||||
});
|
||||
}
|
||||
|
||||
onClickCreate() {
|
||||
const { props, state } = this;
|
||||
|
||||
@ -73,15 +62,11 @@ export class NewDmScreen extends Component {
|
||||
|
||||
this.setState(
|
||||
{
|
||||
error: false,
|
||||
success: true,
|
||||
group: [],
|
||||
ship: [],
|
||||
awaiting: true
|
||||
station
|
||||
},
|
||||
() => {
|
||||
let groupPath = station;
|
||||
let submit = props.api.chatView.create(
|
||||
props.api.chatView.create(
|
||||
`~${window.ship} <-> ~${state.ship}`,
|
||||
"",
|
||||
station,
|
||||
@ -90,10 +75,6 @@ export class NewDmScreen extends Component {
|
||||
state.ship !== window.ship ? [`~${state.ship}`] : [],
|
||||
true
|
||||
);
|
||||
submit.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push(`/~chat/room${station}`);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ export class Root extends Component {
|
||||
|
||||
let contacts = !!state.contacts ? state.contacts : {};
|
||||
let associations = !!state.associations ? state.associations : {chat: {}, contacts: {}};
|
||||
let s3 = !!state.s3 ? state.s3 : {};
|
||||
|
||||
const renderChannelSidebar = (props, station) => (
|
||||
<Sidebar
|
||||
@ -244,6 +245,7 @@ export class Root extends Component {
|
||||
inbox={state.inbox}
|
||||
contacts={roomContacts}
|
||||
permission={permission}
|
||||
s3={s3}
|
||||
pendingMessages={state.pendingMessages}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
|
53
pkg/interface/chat/src/js/lib/s3.js
Normal file
53
pkg/interface/chat/src/js/lib/s3.js
Normal file
@ -0,0 +1,53 @@
|
||||
export default class S3Client {
|
||||
constructor() {
|
||||
this.s3 = null;
|
||||
|
||||
this.endpoint = "";
|
||||
this.accessKeyId = "";
|
||||
this.secretAccesskey = "";
|
||||
}
|
||||
|
||||
setCredentials(endpoint, accessKeyId, secretAccessKey) {
|
||||
if (!window.AWS) {
|
||||
setTimeout(() => {
|
||||
this.setCredentials(endpoint, accessKeyId, secretAccessKey);
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
this.endpoint = new window.AWS.Endpoint(endpoint);
|
||||
this.accessKeyId = accessKeyId;
|
||||
this.secretAccessKey = secretAccessKey;
|
||||
|
||||
this.s3 =
|
||||
new window.AWS.S3({
|
||||
endpoint: this.endpoint,
|
||||
credentials: new window.AWS.Credentials({
|
||||
accessKeyId: this.accessKeyId,
|
||||
secretAccessKey: this.secretAccessKey
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
upload(bucket, filename, buffer) {
|
||||
let params = {
|
||||
Bucket: bucket,
|
||||
Key: filename,
|
||||
Body: buffer,
|
||||
ACL: 'public-read'
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.s3) {
|
||||
reject({ error: 'S3 not initialized!' });
|
||||
return;
|
||||
}
|
||||
this.s3.upload(params, (error, data) => {
|
||||
if (error) {
|
||||
reject({ error });
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
81
pkg/interface/chat/src/js/reducers/s3.js
Normal file
81
pkg/interface/chat/src/js/reducers/s3.js
Normal file
@ -0,0 +1,81 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class S3Reducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 's3-update', false);
|
||||
if (data) {
|
||||
this.credentials(data, state);
|
||||
this.configuration(data, state);
|
||||
this.currentBucket(data, state);
|
||||
this.addBucket(data, state);
|
||||
this.removeBucket(data, state);
|
||||
this.endpoint(data, state);
|
||||
this.accessKeyId(data, state);
|
||||
this.secretAccessKey(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
credentials(json, state) {
|
||||
let data = _.get(json, 'credentials', false);
|
||||
if (data) {
|
||||
state.s3.credentials = data;
|
||||
}
|
||||
}
|
||||
|
||||
configuration(json, state) {
|
||||
let data = _.get(json, 'configuration', false);
|
||||
if (data) {
|
||||
state.s3.configuration = {
|
||||
buckets: new Set(data.buckets),
|
||||
currentBucket: data.currentBucket
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
currentBucket(json, state) {
|
||||
let data = _.get(json, 'setCurrentBucket', false);
|
||||
if (data) {
|
||||
state.s3.configuration.currentBucket = data;
|
||||
}
|
||||
}
|
||||
|
||||
addBucket(json, state) {
|
||||
let data = _.get(json, 'addBucket', false);
|
||||
if (data) {
|
||||
state.s3.configuration.buckets =
|
||||
state.s3.configuration.buckets.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
removeBucket(json, state) {
|
||||
let data = _.get(json, 'removeBucket', false);
|
||||
if (data) {
|
||||
state.s3.configuration.buckets =
|
||||
state.s3.configuration.buckets.delete(data);
|
||||
}
|
||||
}
|
||||
|
||||
endpoint(json, state) {
|
||||
let data = _.get(json, 'setEndpoint', false);
|
||||
if (data) {
|
||||
state.s3.credentials.endpoint = data;
|
||||
}
|
||||
}
|
||||
|
||||
accessKeyId(json, state) {
|
||||
let data = _.get(json, 'setAccessKeyId', false);
|
||||
if (data) {
|
||||
state.s3.credentials.accessKeyId = data;
|
||||
}
|
||||
}
|
||||
|
||||
secretAccessKey(json, state) {
|
||||
let data = _.get(json, 'setSecretAccessKey', false);
|
||||
if (data) {
|
||||
state.s3.credentials.secretAccessKey = data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { ChatUpdateReducer } from '/reducers/chat-update';
|
||||
import { InviteUpdateReducer } from '/reducers/invite-update';
|
||||
import { PermissionUpdateReducer } from '/reducers/permission-update';
|
||||
import { MetadataReducer } from '/reducers/metadata-update.js';
|
||||
import { S3Reducer } from '/reducers/s3.js';
|
||||
import { LocalReducer } from '/reducers/local.js';
|
||||
|
||||
|
||||
@ -17,6 +18,7 @@ class Store {
|
||||
this.chatUpdateReducer = new ChatUpdateReducer();
|
||||
this.inviteUpdateReducer = new InviteUpdateReducer();
|
||||
this.metadataReducer = new MetadataReducer();
|
||||
this.s3Reducer = new S3Reducer();
|
||||
this.localReducer = new LocalReducer();
|
||||
this.setState = () => {};
|
||||
}
|
||||
@ -32,6 +34,7 @@ class Store {
|
||||
chat: {},
|
||||
contacts: {}
|
||||
},
|
||||
s3: {},
|
||||
selectedGroups: [],
|
||||
sidebarShown: true,
|
||||
pendingMessages: new Map([]),
|
||||
@ -58,6 +61,7 @@ class Store {
|
||||
this.chatUpdateReducer.reduce(json, this.state);
|
||||
this.inviteUpdateReducer.reduce(json, this.state);
|
||||
this.metadataReducer.reduce(json, this.state);
|
||||
this.s3Reducer.reduce(json, this.state);
|
||||
this.localReducer.reduce(json, this.state);
|
||||
|
||||
this.setState(this.state);
|
||||
|
@ -54,6 +54,7 @@ export class Subscription {
|
||||
this.subscribe('/primary', 'contact-view');
|
||||
this.subscribe('/app-name/chat', 'metadata-store');
|
||||
this.subscribe('/app-name/contacts', 'metadata-store');
|
||||
this.subscribe('/all', 's3-store');
|
||||
}
|
||||
|
||||
handleEvent(diff) {
|
||||
|
@ -17,6 +17,7 @@ class UrbitApi {
|
||||
this.contactView = {
|
||||
create: this.contactCreate.bind(this),
|
||||
delete: this.contactDelete.bind(this),
|
||||
remove: this.contactRemove.bind(this),
|
||||
share: this.contactShare.bind(this)
|
||||
};
|
||||
|
||||
@ -103,6 +104,10 @@ class UrbitApi {
|
||||
return this.contactViewAction({ delete: { path }});
|
||||
}
|
||||
|
||||
contactRemove(path, ship) {
|
||||
return this.contactViewAction({ remove: { path, ship } });
|
||||
}
|
||||
|
||||
contactHookAction(data) {
|
||||
return this.action("contact-hook", "contact-action", data);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { Link } from 'react-router-dom';
|
||||
import { EditElement } from '/components/lib/edit-element';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import { uxToHex } from '/lib/util';
|
||||
import { S3Upload } from '/components/lib/s3-upload';
|
||||
|
||||
export class ContactCard extends Component {
|
||||
constructor(props) {
|
||||
@ -32,7 +33,8 @@ export class ContactCard extends Component {
|
||||
this.notesToSet = this.notesToSet.bind(this);
|
||||
this.setField = this.setField.bind(this);
|
||||
this.shareWithGroup = this.shareWithGroup.bind(this);
|
||||
this.removeFromGroup = this.removeFromGroup.bind(this);
|
||||
this.removeSelfFromGroup = this.removeSelfFromGroup.bind(this);
|
||||
this.removeOtherFromGroup = this.removeOtherFromGroup.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@ -117,8 +119,7 @@ export class ContactCard extends Component {
|
||||
(state.avatarToSet === '') ||
|
||||
(
|
||||
Boolean(props.contact.avatar) &&
|
||||
'url' in props.contact.avatar &&
|
||||
state.avatarToSet === props.contact.avatar.url
|
||||
state.avatarToSet === props.contact.avatar
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
@ -129,6 +130,7 @@ export class ContactCard extends Component {
|
||||
awaiting: true,
|
||||
type: 'Saving to group'
|
||||
}, (() => {
|
||||
console.log(state.avatarToSet);
|
||||
api.contactEdit(props.path, ship, {
|
||||
avatar: {
|
||||
url: state.avatarToSet
|
||||
@ -304,7 +306,7 @@ export class ContactCard extends Component {
|
||||
email: props.rootIdentity.email,
|
||||
phone: props.rootIdentity.phone,
|
||||
website: props.rootIdentity.website,
|
||||
avatar: { url: props.rootIdentity.avatar },
|
||||
avatar: !!props.rootIdentity.avatar ? { url: props.rootIdentity.avatar } : null,
|
||||
notes: props.rootIdentity.notes,
|
||||
color: uxToHex(props.rootIdentity.color)
|
||||
} : {
|
||||
@ -312,11 +314,11 @@ export class ContactCard extends Component {
|
||||
email: props.contact.email,
|
||||
phone: props.contact.phone,
|
||||
website: props.contact.website,
|
||||
avatar: { url: props.contact.avatar },
|
||||
avatar: !!props.contact.avatar ? { url: props.contact.avatar } : null,
|
||||
notes: props.contact.notes,
|
||||
color: props.contact.color
|
||||
};
|
||||
|
||||
|
||||
const contact = {
|
||||
nickname: this.pickFunction(state.nickNameToSet, defaultVal.nickname),
|
||||
email: this.pickFunction(state.emailToSet, defaultVal.email),
|
||||
@ -324,8 +326,12 @@ export class ContactCard extends Component {
|
||||
website: this.pickFunction(state.websiteToSet, defaultVal.website),
|
||||
notes: this.pickFunction(state.notesToSet, defaultVal.notes),
|
||||
color: this.pickFunction(state.colorToSet, defaultVal.color),
|
||||
avatar: this.pickFunction({ url: state.avatarToSet }, defaultVal.avatar)
|
||||
avatar: this.pickFunction(
|
||||
!!state.avatarToSet ? { url: state.avatarToSet } : null,
|
||||
defaultVal.avatar
|
||||
)
|
||||
};
|
||||
|
||||
this.setState({ awaiting: true, type: 'Sharing with group' }, (() => {
|
||||
api.contactView.share(
|
||||
`~${props.ship}`, props.path, `~${window.ship}`, contact
|
||||
@ -335,7 +341,7 @@ export class ContactCard extends Component {
|
||||
}));
|
||||
}
|
||||
|
||||
removeFromGroup() {
|
||||
removeSelfFromGroup() {
|
||||
const { props } = this;
|
||||
// share empty contact so that we can remove ourselves from group
|
||||
// if we haven't shared yet
|
||||
@ -355,14 +361,35 @@ export class ContactCard extends Component {
|
||||
|
||||
this.setState({ awaiting: true, type: 'Removing from group' }, (() => {
|
||||
api.contactView.delete(props.path).then(() => {
|
||||
const destination = (props.ship === window.ship)
|
||||
? '' : props.path;
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push(`/~groups${destination}`);
|
||||
props.history.push(`/~groups`);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
removeOtherFromGroup() {
|
||||
const { props } = this;
|
||||
|
||||
this.setState({ awaiting: true, type: 'Removing from group' }, (() => {
|
||||
api.contactView.remove(props.path, `~${props.ship}`).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push(`/~groups${props.path}`);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
uploadSuccess(url) {
|
||||
this.setState({
|
||||
avatarToSet: url
|
||||
}, () => {
|
||||
this.setField('avatar');
|
||||
});
|
||||
}
|
||||
|
||||
uploadError(error) {
|
||||
// no-op for now
|
||||
}
|
||||
|
||||
renderEditCard() {
|
||||
const { props, state } = this;
|
||||
// if this is our first edit in a new group, propagate from root identity
|
||||
@ -391,17 +418,28 @@ export class ContactCard extends Component {
|
||||
let currentColor = state.colorToSet ? state.colorToSet : defaultColor;
|
||||
currentColor = uxToHex(currentColor);
|
||||
|
||||
const hasAvatar =
|
||||
'avatar' in props.contact && props.contact.avatar !== null;
|
||||
const avatar = ('avatar' in props.contact && props.contact.avatar !== null)
|
||||
? <img className="dib h-auto"
|
||||
width={128}
|
||||
src={props.contact.avatar}
|
||||
/>
|
||||
: <span className="dn"></span>;
|
||||
|
||||
const avatar = (hasAvatar)
|
||||
? <span>
|
||||
<img className="dib h-auto"
|
||||
width={128}
|
||||
src={props.contact.avatar}
|
||||
/>
|
||||
const imageSetter = (!props.share) ? (
|
||||
<span className="db">
|
||||
<p className="f9 gray2 db pb1">Avatar image url</p>
|
||||
<span className="cf db">
|
||||
<span className="w-20 fl pt1">
|
||||
<S3Upload
|
||||
className="fr pr3"
|
||||
configuration={props.s3.configuration}
|
||||
credentials={props.s3.credentials}
|
||||
uploadSuccess={this.uploadSuccess.bind(this)}
|
||||
uploadError={this.uploadError.bind(this)}
|
||||
/>
|
||||
</span>
|
||||
<EditElement
|
||||
title="Avatar Image URL"
|
||||
className="fr w-80"
|
||||
defaultValue={defaultValue.avatar}
|
||||
onChange={this.avatarToSet}
|
||||
onDeleteClick={() => this.setField('removeAvatar')}
|
||||
@ -409,21 +447,14 @@ export class ContactCard extends Component {
|
||||
showButtons={!props.share}
|
||||
/>
|
||||
</span>
|
||||
: <span>
|
||||
<EditElement
|
||||
title="Avatar Image URL"
|
||||
defaultValue={''}
|
||||
onChange={this.avatarToSet}
|
||||
onDeleteClick={() => this.setField('removeAvatar')}
|
||||
onSaveClick={() => this.setField('avatar')}
|
||||
showButtons={!props.share}
|
||||
/>
|
||||
</span>;
|
||||
</span>
|
||||
) : (<span className="dn"></span>);
|
||||
|
||||
return (
|
||||
<div className="w-100 mt8 flex justify-center pa4 pt8 pt0-l pa0-xl pt4-xl pb8">
|
||||
<div className="w-100 mw6 tc">
|
||||
{avatar}
|
||||
{imageSetter}
|
||||
<Sigil
|
||||
ship={props.ship}
|
||||
size={128}
|
||||
@ -638,7 +669,7 @@ export class ContactCard extends Component {
|
||||
className={
|
||||
'bg-gray0-d mv4 mh3 pa1 f9 red2 pointer flex-shrink-0 ' + adminOpt
|
||||
}
|
||||
onClick={this.removeFromGroup}
|
||||
onClick={props.ship === window.ship ? this.removeSelfFromGroup : this.removeOtherFromGroup}
|
||||
>
|
||||
{props.ship === window.ship
|
||||
? 'Leave Group'
|
||||
|
@ -31,6 +31,7 @@ export class ContactSidebar extends Component {
|
||||
( <ShareSheet
|
||||
ship={window.ship}
|
||||
nickname={me.nickname}
|
||||
avatar={me.avatar}
|
||||
color={me.color}
|
||||
path={props.path}
|
||||
selected={props.path + '/' + window.ship === props.selectedContact}
|
||||
|
@ -23,8 +23,10 @@ export class EditElement extends Component {
|
||||
? { resize: "vertical", height: 40, paddingTop: 10 }
|
||||
: { resize: "none", height: 40, paddingTop: 10 }
|
||||
|
||||
let classes = !!props.className ? "pb4 " + props.className : "pb4";
|
||||
|
||||
return (
|
||||
<div className="pb4">
|
||||
<div className={classes}>
|
||||
<p className="f9 gray2">{props.title}</p>
|
||||
<div className="w-100 flex">
|
||||
<textarea
|
||||
|
@ -32,7 +32,7 @@ export class Sigil extends Component {
|
||||
return (
|
||||
<div
|
||||
className={'dib ' + classes}
|
||||
style={{ flexBasis: 32, backgroundColor: props.color }}
|
||||
style={{ flexBasis: props.size, backgroundColor: props.color }}
|
||||
>
|
||||
{sigil({
|
||||
patp: props.ship,
|
||||
|
96
pkg/interface/groups/src/js/components/lib/s3-upload.js
Normal file
96
pkg/interface/groups/src/js/components/lib/s3-upload.js
Normal file
@ -0,0 +1,96 @@
|
||||
import React, { Component } from 'react'
|
||||
import S3Client from '/lib/s3';
|
||||
|
||||
export class S3Upload extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.s3 = new S3Client();
|
||||
this.setCredentials(props.credentials, props.configuration);
|
||||
this.inputRef = React.createRef();
|
||||
}
|
||||
|
||||
isReady(creds, config) {
|
||||
return (
|
||||
!!creds &&
|
||||
'endpoint' in creds &&
|
||||
'accessKeyId' in creds &&
|
||||
'secretAccessKey' in creds &&
|
||||
creds.endpoint !== '' &&
|
||||
creds.accessKeyId !== '' &&
|
||||
creds.secretAccessKey !== '' &&
|
||||
!!config &&
|
||||
'currentBucket' in config &&
|
||||
config.currentBucket !== ''
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props } = this;
|
||||
this.setCredentials(props.credentials, props.configuration);
|
||||
}
|
||||
|
||||
setCredentials(credentials, configuration) {
|
||||
if (!this.isReady(credentials, configuration)) { return; }
|
||||
this.s3.setCredentials(
|
||||
credentials.endpoint,
|
||||
credentials.accessKeyId,
|
||||
credentials.secretAccessKey
|
||||
);
|
||||
}
|
||||
|
||||
getFileUrl(endpoint, filename) {
|
||||
return endpoint + '/' + filename;
|
||||
}
|
||||
|
||||
onChange() {
|
||||
const { props } = this;
|
||||
if (!this.inputRef.current) { return; }
|
||||
let files = this.inputRef.current.files;
|
||||
if (files.length <= 0) { return; }
|
||||
|
||||
let file = files.item(0);
|
||||
let bucket = props.configuration.currentBucket;
|
||||
|
||||
this.s3.upload(bucket, file.name, file).then((data) => {
|
||||
if (!data || !('Location' in data)) {
|
||||
return;
|
||||
}
|
||||
this.props.uploadSuccess(data.Location);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
this.props.uploadError(err);
|
||||
});
|
||||
}
|
||||
|
||||
onClick() {
|
||||
if (!this.inputRef.current) { return; }
|
||||
this.inputRef.current.click();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
if (!this.isReady(props.credentials, props.configuration)) {
|
||||
return <div></div>;
|
||||
} else {
|
||||
let classes = !!props.className ?
|
||||
"pointer " + props.className : "pointer";
|
||||
return (
|
||||
<div className={classes}>
|
||||
<input className="dn"
|
||||
type="file"
|
||||
id="fileElement"
|
||||
ref={this.inputRef}
|
||||
accept="image/*"
|
||||
onChange={this.onChange.bind(this)} />
|
||||
<img className="invert-d"
|
||||
src="/~groups/img/ImageUpload.png"
|
||||
width="32"
|
||||
height="32"
|
||||
onClick={this.onClick.bind(this)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ export class ShareSheet extends Component {
|
||||
<p className="pt4 pb2 pl4 pr4 f8 gray2 f9">Group Identity</p>
|
||||
<ContactItem
|
||||
key={props.ship}
|
||||
avatar={props.avatar}
|
||||
ship={props.ship}
|
||||
nickname={props.nickname}
|
||||
color={props.color}
|
||||
|
@ -33,7 +33,9 @@ export class Root extends Component {
|
||||
let defaultContacts =
|
||||
(!!state.contacts && '/~/default' in state.contacts) ?
|
||||
state.contacts['/~/default'] : {};
|
||||
|
||||
let groups = !!state.groups ? state.groups : {};
|
||||
let s3 = !!state.s3 ? state.s3 : {};
|
||||
|
||||
let invites =
|
||||
(!!state.invites && '/contacts' in state.invites) ?
|
||||
@ -211,6 +213,7 @@ export class Root extends Component {
|
||||
ship={window.ship}
|
||||
share={true}
|
||||
rootIdentity={rootIdentity}
|
||||
s3={s3}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
@ -259,6 +262,7 @@ export class Root extends Component {
|
||||
path={groupPath}
|
||||
ship={props.match.params.contact}
|
||||
rootIdentity={rootIdentity}
|
||||
s3={s3}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
@ -283,6 +287,7 @@ export class Root extends Component {
|
||||
path="/~/default"
|
||||
contact={me}
|
||||
ship={window.ship}
|
||||
s3={s3}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
|
53
pkg/interface/groups/src/js/lib/s3.js
Normal file
53
pkg/interface/groups/src/js/lib/s3.js
Normal file
@ -0,0 +1,53 @@
|
||||
export default class S3Client {
|
||||
constructor() {
|
||||
this.s3 = null;
|
||||
|
||||
this.endpoint = "";
|
||||
this.accessKeyId = "";
|
||||
this.secretAccesskey = "";
|
||||
}
|
||||
|
||||
setCredentials(endpoint, accessKeyId, secretAccessKey) {
|
||||
if (!window.AWS) {
|
||||
setTimeout(() => {
|
||||
this.setCredentials(endpoint, accessKeyId, secretAccessKey);
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
this.endpoint = new window.AWS.Endpoint(endpoint);
|
||||
this.accessKeyId = accessKeyId;
|
||||
this.secretAccessKey = secretAccessKey;
|
||||
|
||||
this.s3 =
|
||||
new window.AWS.S3({
|
||||
endpoint: this.endpoint,
|
||||
credentials: new window.AWS.Credentials({
|
||||
accessKeyId: this.accessKeyId,
|
||||
secretAccessKey: this.secretAccessKey
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
upload(bucket, filename, buffer) {
|
||||
let params = {
|
||||
Bucket: bucket,
|
||||
Key: filename,
|
||||
Body: buffer,
|
||||
ACL: 'public-read'
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.s3) {
|
||||
reject({ error: 'S3 not initialized!' });
|
||||
return;
|
||||
}
|
||||
this.s3.upload(params, (error, data) => {
|
||||
if (error) {
|
||||
reject({ error });
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
81
pkg/interface/groups/src/js/reducers/s3.js
Normal file
81
pkg/interface/groups/src/js/reducers/s3.js
Normal file
@ -0,0 +1,81 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class S3Reducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 's3-update', false);
|
||||
if (data) {
|
||||
this.credentials(data, state);
|
||||
this.configuration(data, state);
|
||||
this.currentBucket(data, state);
|
||||
this.addBucket(data, state);
|
||||
this.removeBucket(data, state);
|
||||
this.endpoint(data, state);
|
||||
this.accessKeyId(data, state);
|
||||
this.secretAccessKey(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
credentials(json, state) {
|
||||
let data = _.get(json, 'credentials', false);
|
||||
if (data) {
|
||||
state.s3.credentials = data;
|
||||
}
|
||||
}
|
||||
|
||||
configuration(json, state) {
|
||||
let data = _.get(json, 'configuration', false);
|
||||
if (data) {
|
||||
state.s3.configuration = {
|
||||
buckets: new Set(data.buckets),
|
||||
currentBucket: data.currentBucket
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
currentBucket(json, state) {
|
||||
let data = _.get(json, 'setCurrentBucket', false);
|
||||
if (data) {
|
||||
state.s3.configuration.currentBucket = data;
|
||||
}
|
||||
}
|
||||
|
||||
addBucket(json, state) {
|
||||
let data = _.get(json, 'addBucket', false);
|
||||
if (data) {
|
||||
state.s3.configuration.buckets =
|
||||
state.s3.configuration.buckets.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
removeBucket(json, state) {
|
||||
let data = _.get(json, 'removeBucket', false);
|
||||
if (data) {
|
||||
state.s3.configuration.buckets =
|
||||
state.s3.configuration.buckets.delete(data);
|
||||
}
|
||||
}
|
||||
|
||||
endpoint(json, state) {
|
||||
let data = _.get(json, 'setEndpoint', false);
|
||||
if (data) {
|
||||
state.s3.credentials.endpoint = data;
|
||||
}
|
||||
}
|
||||
|
||||
accessKeyId(json, state) {
|
||||
let data = _.get(json, 'setAccessKeyId', false);
|
||||
if (data) {
|
||||
state.s3.credentials.accessKeyId = data;
|
||||
}
|
||||
}
|
||||
|
||||
secretAccessKey(json, state) {
|
||||
let data = _.get(json, 'setSecretAccessKey', false);
|
||||
if (data) {
|
||||
state.s3.credentials.secretAccessKey = data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { GroupUpdateReducer } from '/reducers/group-update';
|
||||
import { InviteUpdateReducer } from '/reducers/invite-update';
|
||||
import { PermissionUpdateReducer } from '/reducers/permission-update';
|
||||
import { MetadataReducer } from '/reducers/metadata-update.js';
|
||||
import { S3Reducer } from '/reducers/s3.js';
|
||||
import { LocalReducer } from '/reducers/local.js';
|
||||
|
||||
|
||||
@ -17,6 +18,7 @@ class Store {
|
||||
this.contactUpdateReducer = new ContactUpdateReducer();
|
||||
this.inviteUpdateReducer = new InviteUpdateReducer();
|
||||
this.metadataReducer = new MetadataReducer();
|
||||
this.s3Reducer = new S3Reducer();
|
||||
this.localReducer = new LocalReducer();
|
||||
this.setState = () => {};
|
||||
}
|
||||
@ -28,6 +30,7 @@ class Store {
|
||||
associations: {},
|
||||
permissions: {},
|
||||
invites: {},
|
||||
s3: {},
|
||||
selectedGroups: []
|
||||
};
|
||||
}
|
||||
@ -51,6 +54,7 @@ class Store {
|
||||
this.contactUpdateReducer.reduce(json, this.state);
|
||||
this.inviteUpdateReducer.reduce(json, this.state);
|
||||
this.metadataReducer.reduce(json, this.state);
|
||||
this.s3Reducer.reduce(json, this.state);
|
||||
this.localReducer.reduce(json, this.state);
|
||||
|
||||
this.setState(this.state);
|
||||
|
@ -7,12 +7,13 @@ import urbitOb from 'urbit-ob';
|
||||
export class Subscription {
|
||||
|
||||
constructor() {
|
||||
this.firstRoundSubscriptionComplete = false;
|
||||
this.firstRoundComplete = false;
|
||||
this.secondRoundComplete = false;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (api.authTokens) {
|
||||
this.firstRoundSubscription();
|
||||
this.firstRound();
|
||||
window.urb.setOnChannelError(this.onChannelError.bind(this));
|
||||
} else {
|
||||
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
|
||||
@ -22,7 +23,8 @@ export class Subscription {
|
||||
onChannelError(err) {
|
||||
console.error('event source error: ', err);
|
||||
console.log('initiating new channel');
|
||||
this.firstRoundSubscriptionComplete = false;
|
||||
this.firstRoundComplete = false;
|
||||
this.secondRoundComplete = false;
|
||||
setTimeout(2000, () => {
|
||||
store.handleEvent({
|
||||
data: { clear : true}
|
||||
@ -43,21 +45,28 @@ export class Subscription {
|
||||
});
|
||||
}
|
||||
|
||||
firstRoundSubscription() {
|
||||
firstRound() {
|
||||
this.subscribe('/primary', 'contact-view');
|
||||
}
|
||||
|
||||
secondRoundSubscriptions() {
|
||||
this.subscribe('/synced', 'contact-hook');
|
||||
this.subscribe('/primary', 'invite-view');
|
||||
secondRound() {
|
||||
this.subscribe('/all', 'group-store');
|
||||
this.subscribe('/all', 'metadata-store');
|
||||
}
|
||||
|
||||
thirdRound() {
|
||||
this.subscribe('/synced', 'contact-hook');
|
||||
this.subscribe('/primary', 'invite-view');
|
||||
this.subscribe('/all', 's3-store');
|
||||
}
|
||||
|
||||
handleEvent(diff) {
|
||||
if (!this.firstRoundSubscriptionComplete) {
|
||||
this.firstRoundSubscriptionComplete = true;
|
||||
this.secondRoundSubscriptions();
|
||||
if (!this.firstRoundComplete) {
|
||||
this.firstRoundComplete = true;
|
||||
this.secondRound();
|
||||
} else if (!this.secondRoundComplete) {
|
||||
this.secondRoundComplete = true;
|
||||
this.thirdRound();
|
||||
}
|
||||
store.handleEvent(diff);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export class Sigil extends Component {
|
||||
return (
|
||||
<div
|
||||
className={"dib " + classes}
|
||||
style={{ flexBasis: 16, backgroundColor: props.color }}>
|
||||
style={{ flexBasis: props.size, backgroundColor: props.color }}>
|
||||
{sigil({
|
||||
patp: props.ship,
|
||||
renderer: reactRenderer,
|
||||
|
@ -17,7 +17,7 @@ export class Sigil extends Component {
|
||||
return (
|
||||
<div
|
||||
className={"dib " + classes}
|
||||
style={{ flexBasis: 32, backgroundColor: props.color }}>
|
||||
style={{ flexBasis: props.size, backgroundColor: props.color }}>
|
||||
{sigil({
|
||||
patp: props.ship,
|
||||
renderer: reactRenderer,
|
||||
|
Loading…
Reference in New Issue
Block a user