mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-05 22:03:50 +03:00
commit
03d80b8c63
Binary file not shown.
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 596 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="/~channel/channel.js"></script>
|
||||||
<script src="/~modulo/session.js"></script>
|
<script src="/~modulo/session.js"></script>
|
||||||
<script src="/~chat/js/index.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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
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="/~channel/channel.js"></script>
|
||||||
<script src="/~modulo/session.js"></script>
|
<script src="/~modulo/session.js"></script>
|
||||||
<script src="/~groups/js/index.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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -43,9 +43,9 @@
|
|||||||
!:
|
!:
|
||||||
=> |% ::
|
=> |% ::
|
||||||
++ hood-old :: unified old-state
|
++ 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
|
++ hood-1 :: unified state
|
||||||
{$3 lac/(map @tas hood-part)} ::
|
{$4 lac/(map @tas hood-part)} ::
|
||||||
++ hood-good :: extract specific
|
++ hood-good :: extract specific
|
||||||
=+ hed=$:hood-head
|
=+ hed=$:hood-head
|
||||||
|@ ++ $
|
|@ ++ $
|
||||||
@ -140,7 +140,7 @@
|
|||||||
`..on-init
|
`..on-init
|
||||||
::
|
::
|
||||||
++ on-save
|
++ on-save
|
||||||
!>([%3 lac])
|
!>([%4 lac])
|
||||||
::
|
::
|
||||||
++ on-load
|
++ on-load
|
||||||
|= =old-state=vase
|
|= =old-state=vase
|
||||||
@ -150,7 +150,8 @@
|
|||||||
?- -.old-state
|
?- -.old-state
|
||||||
%1 ((wrap on-load):from-drum:(help hid) %1)
|
%1 ((wrap on-load):from-drum:(help hid) %1)
|
||||||
%2 ((wrap on-load):from-drum:(help hid) %2)
|
%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]
|
[cards ..on-init]
|
||||||
::
|
::
|
||||||
|
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
|
||||||
|
--
|
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
|
%link-view
|
||||||
%metadata-store
|
%metadata-store
|
||||||
%metadata-hook
|
%metadata-hook
|
||||||
|
%s3-store
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ deft-fish :: default connects
|
++ deft-fish :: default connects
|
||||||
@ -223,7 +224,7 @@
|
|||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ on-load
|
++ on-load
|
||||||
|= ver=?(%1 %2)
|
|= ver=?(%1 %2 %3)
|
||||||
?- ver
|
?- ver
|
||||||
%1
|
%1
|
||||||
=< se-abet =< se-view
|
=< se-abet =< se-view
|
||||||
@ -237,7 +238,8 @@
|
|||||||
=< (se-born %home %link-store)
|
=< (se-born %home %link-store)
|
||||||
=< (se-born %home %link-proxy-hook)
|
=< (se-born %home %link-proxy-hook)
|
||||||
=< (se-born %home %link-listen-hook)
|
=< (se-born %home %link-listen-hook)
|
||||||
(se-born %home %link-view)
|
=< (se-born %home %link-view)
|
||||||
|
(se-born %home %s3-store)
|
||||||
::
|
::
|
||||||
%2
|
%2
|
||||||
=< se-abet =< se-view
|
=< se-abet =< se-view
|
||||||
@ -250,7 +252,22 @@
|
|||||||
=< (se-born %home %link-store)
|
=< (se-born %home %link-store)
|
||||||
=< (se-born %home %link-proxy-hook)
|
=< (se-born %home %link-proxy-hook)
|
||||||
=< (se-born %home %link-listen-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
|
++ reap-phat :: ack connect
|
||||||
|
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
|
||||||
|
==
|
||||||
|
--
|
@ -465,6 +465,7 @@ export class ChatScreen extends Component {
|
|||||||
ownerContact={ownerContact}
|
ownerContact={ownerContact}
|
||||||
envelopes={props.envelopes}
|
envelopes={props.envelopes}
|
||||||
contacts={props.contacts}
|
contacts={props.contacts}
|
||||||
|
s3={props.s3}
|
||||||
placeholder="Message..."
|
placeholder="Message..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +9,7 @@ import 'codemirror/addon/display/placeholder';
|
|||||||
|
|
||||||
import { Sigil } from '/components/lib/icons/sigil';
|
import { Sigil } from '/components/lib/icons/sigil';
|
||||||
import { ShipSearch } from '/components/lib/ship-search';
|
import { ShipSearch } from '/components/lib/ship-search';
|
||||||
|
import { S3Upload } from '/components/lib/s3-upload';
|
||||||
|
|
||||||
import { uxToHex } from '/lib/util';
|
import { uxToHex } from '/lib/util';
|
||||||
|
|
||||||
@ -266,6 +267,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() {
|
render() {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
|
|
||||||
@ -338,7 +353,7 @@ export class ChatInput extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="fr h-100 flex bg-gray0-d lh-copy pl2 w-100 items-center"
|
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
|
<CodeEditor
|
||||||
options={options}
|
options={options}
|
||||||
@ -353,16 +368,23 @@ export class ChatInput extends Component {
|
|||||||
onChange={(e, d, v) => this.messageChange(e, d, v)}
|
onChange={(e, d, v) => this.messageChange(e, d, v)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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
|
<img
|
||||||
style={{ filter: state.code && 'invert(100%)', height: '100%', width: '100%' }}
|
style={{ filter: state.code && 'invert(100%)', height: '100%', width: '100%' }}
|
||||||
onClick={this.toggleCode}
|
onClick={this.toggleCode}
|
||||||
src="/~chat/img/CodeEval.png"
|
src="/~chat/img/CodeEval.png"
|
||||||
className="contrast-10-d bg-white bg-none-d"
|
className="contrast-10-d bg-white bg-none-d"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -60,6 +60,7 @@ export class Root extends Component {
|
|||||||
|
|
||||||
let contacts = !!state.contacts ? state.contacts : {};
|
let contacts = !!state.contacts ? state.contacts : {};
|
||||||
let associations = !!state.associations ? state.associations : {chat: {}, contacts: {}};
|
let associations = !!state.associations ? state.associations : {chat: {}, contacts: {}};
|
||||||
|
let s3 = !!state.s3 ? state.s3 : {};
|
||||||
|
|
||||||
const renderChannelSidebar = (props, station) => (
|
const renderChannelSidebar = (props, station) => (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
@ -244,6 +245,7 @@ export class Root extends Component {
|
|||||||
inbox={state.inbox}
|
inbox={state.inbox}
|
||||||
contacts={roomContacts}
|
contacts={roomContacts}
|
||||||
permission={permission}
|
permission={permission}
|
||||||
|
s3={s3}
|
||||||
pendingMessages={state.pendingMessages}
|
pendingMessages={state.pendingMessages}
|
||||||
popout={popout}
|
popout={popout}
|
||||||
sidebarShown={state.sidebarShown}
|
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 { InviteUpdateReducer } from '/reducers/invite-update';
|
||||||
import { PermissionUpdateReducer } from '/reducers/permission-update';
|
import { PermissionUpdateReducer } from '/reducers/permission-update';
|
||||||
import { MetadataReducer } from '/reducers/metadata-update.js';
|
import { MetadataReducer } from '/reducers/metadata-update.js';
|
||||||
|
import { S3Reducer } from '/reducers/s3.js';
|
||||||
import { LocalReducer } from '/reducers/local.js';
|
import { LocalReducer } from '/reducers/local.js';
|
||||||
|
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ class Store {
|
|||||||
this.chatUpdateReducer = new ChatUpdateReducer();
|
this.chatUpdateReducer = new ChatUpdateReducer();
|
||||||
this.inviteUpdateReducer = new InviteUpdateReducer();
|
this.inviteUpdateReducer = new InviteUpdateReducer();
|
||||||
this.metadataReducer = new MetadataReducer();
|
this.metadataReducer = new MetadataReducer();
|
||||||
|
this.s3Reducer = new S3Reducer();
|
||||||
this.localReducer = new LocalReducer();
|
this.localReducer = new LocalReducer();
|
||||||
this.setState = () => {};
|
this.setState = () => {};
|
||||||
}
|
}
|
||||||
@ -32,6 +34,7 @@ class Store {
|
|||||||
chat: {},
|
chat: {},
|
||||||
contacts: {}
|
contacts: {}
|
||||||
},
|
},
|
||||||
|
s3: {},
|
||||||
selectedGroups: [],
|
selectedGroups: [],
|
||||||
sidebarShown: true,
|
sidebarShown: true,
|
||||||
pendingMessages: new Map([]),
|
pendingMessages: new Map([]),
|
||||||
@ -58,6 +61,7 @@ class Store {
|
|||||||
this.chatUpdateReducer.reduce(json, this.state);
|
this.chatUpdateReducer.reduce(json, this.state);
|
||||||
this.inviteUpdateReducer.reduce(json, this.state);
|
this.inviteUpdateReducer.reduce(json, this.state);
|
||||||
this.metadataReducer.reduce(json, this.state);
|
this.metadataReducer.reduce(json, this.state);
|
||||||
|
this.s3Reducer.reduce(json, this.state);
|
||||||
this.localReducer.reduce(json, this.state);
|
this.localReducer.reduce(json, this.state);
|
||||||
|
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
|
@ -54,6 +54,7 @@ export class Subscription {
|
|||||||
this.subscribe('/primary', 'contact-view');
|
this.subscribe('/primary', 'contact-view');
|
||||||
this.subscribe('/app-name/chat', 'metadata-store');
|
this.subscribe('/app-name/chat', 'metadata-store');
|
||||||
this.subscribe('/app-name/contacts', 'metadata-store');
|
this.subscribe('/app-name/contacts', 'metadata-store');
|
||||||
|
this.subscribe('/all', 's3-store');
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEvent(diff) {
|
handleEvent(diff) {
|
||||||
|
@ -6,6 +6,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import { EditElement } from '/components/lib/edit-element';
|
import { EditElement } from '/components/lib/edit-element';
|
||||||
import { Spinner } from './icons/icon-spinner';
|
import { Spinner } from './icons/icon-spinner';
|
||||||
import { uxToHex } from '/lib/util';
|
import { uxToHex } from '/lib/util';
|
||||||
|
import { S3Upload } from '/components/lib/s3-upload';
|
||||||
|
|
||||||
export class ContactCard extends Component {
|
export class ContactCard extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -117,8 +118,7 @@ export class ContactCard extends Component {
|
|||||||
(state.avatarToSet === '') ||
|
(state.avatarToSet === '') ||
|
||||||
(
|
(
|
||||||
Boolean(props.contact.avatar) &&
|
Boolean(props.contact.avatar) &&
|
||||||
'url' in props.contact.avatar &&
|
state.avatarToSet === props.contact.avatar
|
||||||
state.avatarToSet === props.contact.avatar.url
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
@ -129,6 +129,7 @@ export class ContactCard extends Component {
|
|||||||
awaiting: true,
|
awaiting: true,
|
||||||
type: 'Saving to group'
|
type: 'Saving to group'
|
||||||
}, (() => {
|
}, (() => {
|
||||||
|
console.log(state.avatarToSet);
|
||||||
api.contactEdit(props.path, ship, {
|
api.contactEdit(props.path, ship, {
|
||||||
avatar: {
|
avatar: {
|
||||||
url: state.avatarToSet
|
url: state.avatarToSet
|
||||||
@ -304,7 +305,7 @@ export class ContactCard extends Component {
|
|||||||
email: props.rootIdentity.email,
|
email: props.rootIdentity.email,
|
||||||
phone: props.rootIdentity.phone,
|
phone: props.rootIdentity.phone,
|
||||||
website: props.rootIdentity.website,
|
website: props.rootIdentity.website,
|
||||||
avatar: { url: props.rootIdentity.avatar },
|
avatar: !!props.rootIdentity.avatar ? { url: props.rootIdentity.avatar } : null,
|
||||||
notes: props.rootIdentity.notes,
|
notes: props.rootIdentity.notes,
|
||||||
color: uxToHex(props.rootIdentity.color)
|
color: uxToHex(props.rootIdentity.color)
|
||||||
} : {
|
} : {
|
||||||
@ -312,7 +313,7 @@ export class ContactCard extends Component {
|
|||||||
email: props.contact.email,
|
email: props.contact.email,
|
||||||
phone: props.contact.phone,
|
phone: props.contact.phone,
|
||||||
website: props.contact.website,
|
website: props.contact.website,
|
||||||
avatar: { url: props.contact.avatar },
|
avatar: !!props.contact.avatar ? { url: props.contact.avatar } : null,
|
||||||
notes: props.contact.notes,
|
notes: props.contact.notes,
|
||||||
color: props.contact.color
|
color: props.contact.color
|
||||||
};
|
};
|
||||||
@ -324,8 +325,12 @@ export class ContactCard extends Component {
|
|||||||
website: this.pickFunction(state.websiteToSet, defaultVal.website),
|
website: this.pickFunction(state.websiteToSet, defaultVal.website),
|
||||||
notes: this.pickFunction(state.notesToSet, defaultVal.notes),
|
notes: this.pickFunction(state.notesToSet, defaultVal.notes),
|
||||||
color: this.pickFunction(state.colorToSet, defaultVal.color),
|
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' }, (() => {
|
this.setState({ awaiting: true, type: 'Sharing with group' }, (() => {
|
||||||
api.contactView.share(
|
api.contactView.share(
|
||||||
`~${props.ship}`, props.path, `~${window.ship}`, contact
|
`~${props.ship}`, props.path, `~${window.ship}`, contact
|
||||||
@ -363,6 +368,18 @@ export class ContactCard extends Component {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadSuccess(url) {
|
||||||
|
this.setState({
|
||||||
|
avatarToSet: url
|
||||||
|
}, () => {
|
||||||
|
this.setField('avatar');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadError(error) {
|
||||||
|
// no-op for now
|
||||||
|
}
|
||||||
|
|
||||||
renderEditCard() {
|
renderEditCard() {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
// if this is our first edit in a new group, propagate from root identity
|
// if this is our first edit in a new group, propagate from root identity
|
||||||
@ -391,17 +408,28 @@ export class ContactCard extends Component {
|
|||||||
let currentColor = state.colorToSet ? state.colorToSet : defaultColor;
|
let currentColor = state.colorToSet ? state.colorToSet : defaultColor;
|
||||||
currentColor = uxToHex(currentColor);
|
currentColor = uxToHex(currentColor);
|
||||||
|
|
||||||
const hasAvatar =
|
const avatar = ('avatar' in props.contact && props.contact.avatar !== null)
|
||||||
'avatar' in props.contact && props.contact.avatar !== null;
|
? <img className="dib h-auto"
|
||||||
|
|
||||||
const avatar = (hasAvatar)
|
|
||||||
? <span>
|
|
||||||
<img className="dib h-auto"
|
|
||||||
width={128}
|
width={128}
|
||||||
src={props.contact.avatar}
|
src={props.contact.avatar}
|
||||||
/>
|
/>
|
||||||
|
: <span className="dn"></span>;
|
||||||
|
|
||||||
|
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
|
<EditElement
|
||||||
title="Avatar Image URL"
|
className="fr w-80"
|
||||||
defaultValue={defaultValue.avatar}
|
defaultValue={defaultValue.avatar}
|
||||||
onChange={this.avatarToSet}
|
onChange={this.avatarToSet}
|
||||||
onDeleteClick={() => this.setField('removeAvatar')}
|
onDeleteClick={() => this.setField('removeAvatar')}
|
||||||
@ -409,21 +437,14 @@ export class ContactCard extends Component {
|
|||||||
showButtons={!props.share}
|
showButtons={!props.share}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
: <span>
|
</span>
|
||||||
<EditElement
|
) : (<span className="dn"></span>);
|
||||||
title="Avatar Image URL"
|
|
||||||
defaultValue={''}
|
|
||||||
onChange={this.avatarToSet}
|
|
||||||
onDeleteClick={() => this.setField('removeAvatar')}
|
|
||||||
onSaveClick={() => this.setField('avatar')}
|
|
||||||
showButtons={!props.share}
|
|
||||||
/>
|
|
||||||
</span>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-100 mt8 flex justify-center pa4 pt8 pt0-l pa0-xl pt4-xl pb8">
|
<div className="w-100 mt8 flex justify-center pa4 pt8 pt0-l pa0-xl pt4-xl pb8">
|
||||||
<div className="w-100 mw6 tc">
|
<div className="w-100 mw6 tc">
|
||||||
{avatar}
|
{avatar}
|
||||||
|
{imageSetter}
|
||||||
<Sigil
|
<Sigil
|
||||||
ship={props.ship}
|
ship={props.ship}
|
||||||
size={128}
|
size={128}
|
||||||
|
@ -23,8 +23,10 @@ export class EditElement extends Component {
|
|||||||
? { resize: "vertical", height: 40, paddingTop: 10 }
|
? { resize: "vertical", height: 40, paddingTop: 10 }
|
||||||
: { resize: "none", height: 40, paddingTop: 10 }
|
: { resize: "none", height: 40, paddingTop: 10 }
|
||||||
|
|
||||||
|
let classes = !!props.className ? "pb4 " + props.className : "pb4";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pb4">
|
<div className={classes}>
|
||||||
<p className="f9 gray2">{props.title}</p>
|
<p className="f9 gray2">{props.title}</p>
|
||||||
<div className="w-100 flex">
|
<div className="w-100 flex">
|
||||||
<textarea
|
<textarea
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,9 @@ export class Root extends Component {
|
|||||||
let defaultContacts =
|
let defaultContacts =
|
||||||
(!!state.contacts && '/~/default' in state.contacts) ?
|
(!!state.contacts && '/~/default' in state.contacts) ?
|
||||||
state.contacts['/~/default'] : {};
|
state.contacts['/~/default'] : {};
|
||||||
|
|
||||||
let groups = !!state.groups ? state.groups : {};
|
let groups = !!state.groups ? state.groups : {};
|
||||||
|
let s3 = !!state.s3 ? state.s3 : {};
|
||||||
|
|
||||||
let invites =
|
let invites =
|
||||||
(!!state.invites && '/contacts' in state.invites) ?
|
(!!state.invites && '/contacts' in state.invites) ?
|
||||||
@ -211,6 +213,7 @@ export class Root extends Component {
|
|||||||
ship={window.ship}
|
ship={window.ship}
|
||||||
share={true}
|
share={true}
|
||||||
rootIdentity={rootIdentity}
|
rootIdentity={rootIdentity}
|
||||||
|
s3={s3}
|
||||||
/>
|
/>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
);
|
);
|
||||||
@ -259,6 +262,7 @@ export class Root extends Component {
|
|||||||
path={groupPath}
|
path={groupPath}
|
||||||
ship={props.match.params.contact}
|
ship={props.match.params.contact}
|
||||||
rootIdentity={rootIdentity}
|
rootIdentity={rootIdentity}
|
||||||
|
s3={s3}
|
||||||
/>
|
/>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
);
|
);
|
||||||
@ -283,6 +287,7 @@ export class Root extends Component {
|
|||||||
path="/~/default"
|
path="/~/default"
|
||||||
contact={me}
|
contact={me}
|
||||||
ship={window.ship}
|
ship={window.ship}
|
||||||
|
s3={s3}
|
||||||
/>
|
/>
|
||||||
</Skeleton>
|
</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 { InviteUpdateReducer } from '/reducers/invite-update';
|
||||||
import { PermissionUpdateReducer } from '/reducers/permission-update';
|
import { PermissionUpdateReducer } from '/reducers/permission-update';
|
||||||
import { MetadataReducer } from '/reducers/metadata-update.js';
|
import { MetadataReducer } from '/reducers/metadata-update.js';
|
||||||
|
import { S3Reducer } from '/reducers/s3.js';
|
||||||
import { LocalReducer } from '/reducers/local.js';
|
import { LocalReducer } from '/reducers/local.js';
|
||||||
|
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ class Store {
|
|||||||
this.contactUpdateReducer = new ContactUpdateReducer();
|
this.contactUpdateReducer = new ContactUpdateReducer();
|
||||||
this.inviteUpdateReducer = new InviteUpdateReducer();
|
this.inviteUpdateReducer = new InviteUpdateReducer();
|
||||||
this.metadataReducer = new MetadataReducer();
|
this.metadataReducer = new MetadataReducer();
|
||||||
|
this.s3Reducer = new S3Reducer();
|
||||||
this.localReducer = new LocalReducer();
|
this.localReducer = new LocalReducer();
|
||||||
this.setState = () => {};
|
this.setState = () => {};
|
||||||
}
|
}
|
||||||
@ -28,6 +30,7 @@ class Store {
|
|||||||
associations: {},
|
associations: {},
|
||||||
permissions: {},
|
permissions: {},
|
||||||
invites: {},
|
invites: {},
|
||||||
|
s3: {},
|
||||||
selectedGroups: []
|
selectedGroups: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -51,6 +54,7 @@ class Store {
|
|||||||
this.contactUpdateReducer.reduce(json, this.state);
|
this.contactUpdateReducer.reduce(json, this.state);
|
||||||
this.inviteUpdateReducer.reduce(json, this.state);
|
this.inviteUpdateReducer.reduce(json, this.state);
|
||||||
this.metadataReducer.reduce(json, this.state);
|
this.metadataReducer.reduce(json, this.state);
|
||||||
|
this.s3Reducer.reduce(json, this.state);
|
||||||
this.localReducer.reduce(json, this.state);
|
this.localReducer.reduce(json, this.state);
|
||||||
|
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
|
@ -7,12 +7,13 @@ import urbitOb from 'urbit-ob';
|
|||||||
export class Subscription {
|
export class Subscription {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.firstRoundSubscriptionComplete = false;
|
this.firstRoundComplete = false;
|
||||||
|
this.secondRoundComplete = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if (api.authTokens) {
|
if (api.authTokens) {
|
||||||
this.firstRoundSubscription();
|
this.firstRound();
|
||||||
window.urb.setOnChannelError(this.onChannelError.bind(this));
|
window.urb.setOnChannelError(this.onChannelError.bind(this));
|
||||||
} else {
|
} else {
|
||||||
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
|
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
|
||||||
@ -22,7 +23,8 @@ export class Subscription {
|
|||||||
onChannelError(err) {
|
onChannelError(err) {
|
||||||
console.error('event source error: ', err);
|
console.error('event source error: ', err);
|
||||||
console.log('initiating new channel');
|
console.log('initiating new channel');
|
||||||
this.firstRoundSubscriptionComplete = false;
|
this.firstRoundComplete = false;
|
||||||
|
this.secondRoundComplete = false;
|
||||||
setTimeout(2000, () => {
|
setTimeout(2000, () => {
|
||||||
store.handleEvent({
|
store.handleEvent({
|
||||||
data: { clear : true}
|
data: { clear : true}
|
||||||
@ -43,21 +45,28 @@ export class Subscription {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
firstRoundSubscription() {
|
firstRound() {
|
||||||
this.subscribe('/primary', 'contact-view');
|
this.subscribe('/primary', 'contact-view');
|
||||||
}
|
}
|
||||||
|
|
||||||
secondRoundSubscriptions() {
|
secondRound() {
|
||||||
this.subscribe('/synced', 'contact-hook');
|
|
||||||
this.subscribe('/primary', 'invite-view');
|
|
||||||
this.subscribe('/all', 'group-store');
|
this.subscribe('/all', 'group-store');
|
||||||
this.subscribe('/all', 'metadata-store');
|
this.subscribe('/all', 'metadata-store');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thirdRound() {
|
||||||
|
this.subscribe('/synced', 'contact-hook');
|
||||||
|
this.subscribe('/primary', 'invite-view');
|
||||||
|
this.subscribe('/all', 's3-store');
|
||||||
|
}
|
||||||
|
|
||||||
handleEvent(diff) {
|
handleEvent(diff) {
|
||||||
if (!this.firstRoundSubscriptionComplete) {
|
if (!this.firstRoundComplete) {
|
||||||
this.firstRoundSubscriptionComplete = true;
|
this.firstRoundComplete = true;
|
||||||
this.secondRoundSubscriptions();
|
this.secondRound();
|
||||||
|
} else if (!this.secondRoundComplete) {
|
||||||
|
this.secondRoundComplete = true;
|
||||||
|
this.thirdRound();
|
||||||
}
|
}
|
||||||
store.handleEvent(diff);
|
store.handleEvent(diff);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user