mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 22:33:06 +03:00
commit
6e2ce697f9
@ -6,6 +6,7 @@ import InviteReducer from '../reducers/invite-update';
|
||||
import LinkReducer from '../reducers/link-update';
|
||||
import ListenReducer from '../reducers/listen-update';
|
||||
import LocalReducer from '../reducers/local';
|
||||
import S3Reducer from '../reducers/s3-update';
|
||||
|
||||
import BaseStore from './base';
|
||||
|
||||
@ -21,6 +22,7 @@ export default class LinksStore extends BaseStore {
|
||||
this.localReducer = new LocalReducer();
|
||||
this.linkReducer = new LinkReducer();
|
||||
this.listenReducer = new ListenReducer();
|
||||
this.s3Reducer = new S3Reducer();
|
||||
}
|
||||
|
||||
initialState() {
|
||||
@ -37,6 +39,7 @@ export default class LinksStore extends BaseStore {
|
||||
comments: {},
|
||||
seen: {},
|
||||
permissions: {},
|
||||
s3: {},
|
||||
sidebarShown: true
|
||||
};
|
||||
}
|
||||
@ -50,6 +53,7 @@ export default class LinksStore extends BaseStore {
|
||||
this.localReducer.reduce(data, this.state);
|
||||
this.linkReducer.reduce(data, this.state);
|
||||
this.listenReducer.reduce(data, this.state);
|
||||
this.s3Reducer.reduce(data, this.state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import ChatEditor from './chat-editor';
|
||||
import { S3Upload } from './s3-upload'
|
||||
import { S3Upload } from '~/views/components/s3-upload'
|
||||
;
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
@ -221,10 +221,10 @@ export class ChatInput extends Component {
|
||||
style={{ flexGrow: 1 }}>
|
||||
<div className="fl"
|
||||
style={{
|
||||
marginTop: 6,
|
||||
flexBasis: 24,
|
||||
height: 24
|
||||
}}>
|
||||
marginTop: 6,
|
||||
flexBasis: 24,
|
||||
height: 24
|
||||
}}>
|
||||
{avatar}
|
||||
</div>
|
||||
<ChatEditor
|
||||
@ -235,32 +235,40 @@ export class ChatInput extends Component {
|
||||
placeholder='Message...' />
|
||||
<div className="ml2 mr2"
|
||||
style={{
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
flexBasis: 16,
|
||||
marginTop: 10
|
||||
}}>
|
||||
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)}
|
||||
/>
|
||||
accept="image/*"
|
||||
>
|
||||
<img
|
||||
className="invert-d"
|
||||
src="/~chat/img/ImageUpload.png"
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
</S3Upload>
|
||||
</div>
|
||||
<div style={{
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
flexBasis: 16,
|
||||
marginTop: 10
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
flexBasis: 16,
|
||||
marginTop: 10
|
||||
}}>
|
||||
<img style={{
|
||||
filter: state.inCodeMode && 'invert(100%)',
|
||||
height: '14px',
|
||||
width: '14px',
|
||||
}}
|
||||
onClick={this.toggleCode}
|
||||
src="/~chat/img/CodeEval.png"
|
||||
className="contrast-10-d bg-white bg-none-d ba b--gray1-d br1" />
|
||||
filter: state.inCodeMode && 'invert(100%)',
|
||||
height: '14px',
|
||||
width: '14px',
|
||||
}}
|
||||
onClick={this.toggleCode}
|
||||
src="/~chat/img/CodeEval.png"
|
||||
className="contrast-10-d bg-white bg-none-d ba b--gray1-d br1" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,105 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import S3Client from '~/logic/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 (
|
||||
Boolean(creds) &&
|
||||
'endpoint' in creds &&
|
||||
'accessKeyId' in creds &&
|
||||
'secretAccessKey' in creds &&
|
||||
creds.endpoint !== '' &&
|
||||
creds.accessKeyId !== '' &&
|
||||
creds.secretAccessKey !== '' &&
|
||||
Boolean(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;
|
||||
}
|
||||
const files = this.inputRef.current.files;
|
||||
if (files.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = files.item(0);
|
||||
const 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 {
|
||||
const 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { Link } from 'react-router-dom';
|
||||
import { EditElement } from './edit-element';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { S3Upload } from './s3-upload';
|
||||
import { S3Upload } from '~/views/components/s3-upload';
|
||||
|
||||
export class ContactCard extends Component {
|
||||
constructor(props) {
|
||||
@ -492,7 +492,15 @@ export class ContactCard extends Component {
|
||||
credentials={props.s3.credentials}
|
||||
uploadSuccess={this.uploadSuccess.bind(this)}
|
||||
uploadError={this.uploadError.bind(this)}
|
||||
/>
|
||||
accept="image/*"
|
||||
>
|
||||
<img
|
||||
className="invert-d"
|
||||
src="/~chat/img/ImageUpload.png"
|
||||
width="32"
|
||||
height="32"
|
||||
/>
|
||||
</S3Upload>
|
||||
</span>
|
||||
<EditElement
|
||||
className="fr w-80"
|
||||
|
@ -59,7 +59,7 @@ export class LinksApp extends Component {
|
||||
props.invites : {};
|
||||
|
||||
const listening = props.linkListening;
|
||||
const { api, sidebarShown, hideAvatars, hideNicknames } = this.props;
|
||||
const { api, sidebarShown, hideAvatars, hideNicknames, s3 } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -256,6 +256,7 @@ export class LinksApp extends Component {
|
||||
api={api}
|
||||
hideNicknames={hideNicknames}
|
||||
hideAvatars={hideAvatars}
|
||||
s3={s3}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
|
@ -1,5 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { S3Upload } from '~/views/components/s3-upload';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { Icon } from "@tlon/indigo-react";
|
||||
|
||||
export class LinkSubmit extends Component {
|
||||
constructor() {
|
||||
@ -60,23 +63,29 @@ export class LinkSubmit extends Component {
|
||||
this.setState({ linkTitle: event.target.value });
|
||||
}
|
||||
|
||||
uploadSuccess(url) {
|
||||
this.setState({ linkValue: url });
|
||||
this.setLinkValid(url);
|
||||
}
|
||||
|
||||
uploadError(error) {
|
||||
// no-op for now
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log('s3', this.props.s3);
|
||||
const activeClasses = (this.state.linkValid && !this.state.disabled)
|
||||
? 'green2 pointer' : 'gray2';
|
||||
|
||||
const focus = (this.state.submitFocus)
|
||||
? 'b--black b--white-d'
|
||||
: 'b--gray4 b--gray2-d';
|
||||
|
||||
|
||||
return (
|
||||
<div className={'relative ba br1 w-100 mb6 ' + focus}>
|
||||
<textarea
|
||||
className="pl2 bg-gray0-d white-d w-100 f8"
|
||||
style={{
|
||||
resize: 'none',
|
||||
height: 40,
|
||||
paddingTop: 10
|
||||
}}
|
||||
<input
|
||||
type="url"
|
||||
className="pl2 bg-gray0-d white-d w-100 f8 pt2"
|
||||
placeholder="Paste link here"
|
||||
onChange={this.setLinkValue}
|
||||
onBlur={() => this.setState({ submitFocus: false })}
|
||||
@ -91,41 +100,51 @@ export class LinkSubmit extends Component {
|
||||
}}
|
||||
value={this.state.linkValue}
|
||||
/>
|
||||
<textarea
|
||||
className="pl2 bg-gray0-d white-d w-100 f8"
|
||||
style={{
|
||||
resize: 'none',
|
||||
height: 40,
|
||||
paddingTop: 16
|
||||
}}
|
||||
placeholder="Enter title"
|
||||
onChange={this.setLinkTitle}
|
||||
onBlur={() => this.setState({ submitFocus: false })}
|
||||
onFocus={() => this.setState({ submitFocus: true })}
|
||||
spellCheck="false"
|
||||
rows={1}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.onClickPost();
|
||||
<div className="flex flex-auto mt2 pt2">
|
||||
<input
|
||||
type="text"
|
||||
className="pl2 bg-gray0-d white-d w-100 f8"
|
||||
style={{
|
||||
resize: 'none',
|
||||
height: 40
|
||||
}}
|
||||
placeholder="Enter title"
|
||||
onChange={this.setLinkTitle}
|
||||
onBlur={() => this.setState({ submitFocus: false })}
|
||||
onFocus={() => this.setState({ submitFocus: true })}
|
||||
spellCheck="false"
|
||||
rows={1}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.onClickPost();
|
||||
}
|
||||
}}
|
||||
value={this.state.linkTitle}
|
||||
/>
|
||||
{!this.state.disabled ? <S3Upload
|
||||
configuration={this.props.s3.configuration}
|
||||
credentials={this.props.s3.credentials}
|
||||
uploadSuccess={this.uploadSuccess.bind(this)}
|
||||
uploadError={this.uploadError.bind(this)}
|
||||
className="nowrap flex items-center pr2 h-100"
|
||||
><span className="green2 f8">Upload File</span></S3Upload> : null}
|
||||
{!this.state.disabled ? <button
|
||||
className={
|
||||
'bg-gray0-d f8 flex-shrink-0 pr2 ' + activeClasses
|
||||
}
|
||||
}}
|
||||
value={this.state.linkTitle}
|
||||
/>
|
||||
<button
|
||||
className={
|
||||
'absolute bg-gray0-d f8 ml2 flex-shrink-0 ' + activeClasses
|
||||
}
|
||||
disabled={!this.state.linkValid || this.state.disabled}
|
||||
onClick={this.onClickPost.bind(this)}
|
||||
style={{
|
||||
bottom: 12,
|
||||
right: 8
|
||||
}}
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
<Spinner awaiting={this.state.disabled} classes="mt3 absolute right-0" text="Posting to collection..." />
|
||||
disabled={!this.state.linkValid || this.state.disabled}
|
||||
onClick={this.onClickPost.bind(this)}
|
||||
style={{
|
||||
bottom: 12,
|
||||
right: 8
|
||||
}}
|
||||
>
|
||||
Post
|
||||
</button> : null}
|
||||
<Spinner awaiting={this.state.disabled} classes="nowrap flex items-center pr2" style={{flex: '1 1 14rem'}} text="Posting to collection..." />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
) ;
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ export class Links extends Component {
|
||||
<div className="w-100 mt6 flex justify-center overflow-y-scroll ph4 pb4">
|
||||
<div className="w-100 mw7">
|
||||
<div className="flex">
|
||||
<LinkSubmit resourcePath={props.resourcePath} api={this.props.api} />
|
||||
<LinkSubmit resourcePath={props.resourcePath} api={this.props.api} s3={props.s3} />
|
||||
</div>
|
||||
<div className="pb4">
|
||||
{LinkList}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { InviteSearch } from '~/views/components/InviteSearch';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { makeRoutePath, deSig } from '~/logic/lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
|
||||
export class NewScreen extends Component {
|
||||
constructor(props) {
|
||||
|
@ -8,7 +8,7 @@ export class Spinner extends Component {
|
||||
|
||||
if (awaiting) {
|
||||
return (
|
||||
<div className={classes + ' z-2 bg-white bg-gray0-d white-d'}>
|
||||
<div className={classes + ' z-2 bg-white bg-gray0-d white-d flex'}>
|
||||
<img className="invert-d spin-active v-mid"
|
||||
src="/~landscape/img/Spinner.png"
|
||||
width={16}
|
||||
|
@ -1,10 +1,16 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Icon } from "@tlon/indigo-react";
|
||||
|
||||
import S3Client from '~/logic/lib/s3';
|
||||
import { Spinner } from './Spinner';
|
||||
|
||||
export class S3Upload extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isUploading: false
|
||||
};
|
||||
this.s3 = new S3Client();
|
||||
this.setCredentials(props.credentials, props.configuration);
|
||||
this.inputRef = React.createRef();
|
||||
@ -52,15 +58,22 @@ export class S3Upload extends Component {
|
||||
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);
|
||||
});
|
||||
this.setState({ isUploading: true });
|
||||
|
||||
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);
|
||||
})
|
||||
.finally(() => {
|
||||
this.setState({ isUploading: false });
|
||||
});
|
||||
}
|
||||
|
||||
onClick() {
|
||||
@ -69,25 +82,34 @@ export class S3Upload extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
if (!this.isReady(props.credentials, props.configuration)) {
|
||||
const {
|
||||
credentials,
|
||||
configuration,
|
||||
className,
|
||||
accept = '*',
|
||||
children = false
|
||||
} = this.props;
|
||||
if (!this.isReady(credentials, configuration)) {
|
||||
return <div></div>;
|
||||
} else {
|
||||
let classes = !!props.className ?
|
||||
"pointer " + props.className : "pointer";
|
||||
let classes = !!className
|
||||
? "pointer " + className
|
||||
: "pointer";
|
||||
const display = children || <Icon icon='ArrowNorth' />;
|
||||
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="/~landscape/img/ImageUpload.png"
|
||||
width="32"
|
||||
height="32"
|
||||
onClick={this.onClick.bind(this)} />
|
||||
<div>
|
||||
<input
|
||||
className="dn"
|
||||
type="file"
|
||||
id="fileElement"
|
||||
ref={this.inputRef}
|
||||
accept={accept}
|
||||
onChange={this.onChange.bind(this)} />
|
||||
{this.state.isUploading
|
||||
? <Spinner awaiting={true} classes={className} />
|
||||
: <span className={`pointer ${className}`} onClick={this.onClick.bind(this)}>{display}</span>
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user