Skip to content

Commit

Permalink
File upload MVP
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxwellBo committed May 4, 2018
1 parent 7f7023c commit b0d0d3f
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 27 deletions.
7 changes: 7 additions & 0 deletions cors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"origin": ["*"],
"method": ["GET"],
"maxAgeSeconds": 3600
}
]
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/file-saver": "^1.3.0",
"@types/lodash": "^4.14.106",
"@types/react-router-dom": "^4.2.0",
"file-saver": "^1.3.8",
"firebase": "^4.4.0",
"lodash": "^4.17.5",
"react": "^16.0.0",
Expand Down
3 changes: 2 additions & 1 deletion src/components/Committee.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface CommitteeData {
caucuses?: Map<CaucusID, CaucusData>;
resolutions?: Map<ResolutionID, ResolutionData>;
motions?: Map<MotionID, MotionData>;
files?: Map<string, string>;
timer: TimerData;
notes: string;
settings: SettingsData;
Expand Down Expand Up @@ -229,7 +230,7 @@ export default class Committee extends React.Component<Props, State> {
</Menu.Menu>
</Menu.Item>
{makeMenuItem('Notes', 'sticky note outline')}
{makeMenuItem('Files', 'file pdf outline')}
{makeMenuItem('Files', 'file outline')}
{makeMenuItem('Stats', 'bar chart')}
{makeMenuItem('Settings', 'settings')}
{makeMenuItem('Help', 'help')}
Expand Down
108 changes: 83 additions & 25 deletions src/components/Files.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,72 @@
import * as React from 'react';
import * as firebase from 'firebase';
import * as FileSaver from 'file-saver';
import { CommitteeData } from './Committee';
import { RouteComponentProps } from 'react-router';
import { URLParameters } from '../types';
import { TextArea, Segment, Form, Button, Popup, InputOnChangeData, Progress } from 'semantic-ui-react';
import { TextArea, Segment, Form, Button, Popup, InputOnChangeData, Progress, List } from 'semantic-ui-react';
import { textAreaHandler } from '../actions/handlers';

interface Props extends RouteComponentProps<URLParameters> {
}

interface State {
committee?: CommitteeData;
committeeFref: firebase.database.Reference;
progress?: number;
file?: any;
state?: firebase.storage.TaskState;
errorCode?: string
}

export default class Files extends React.Component<Props, State> {
constructor(props: Props) {
super(props);

const { match } = props;

this.state = {
committeeFref: firebase.database().ref('committees')
.child(match.params.committeeID)
};
}

handleUploadError = (error: any) => {
firebaseCallback = (committee: firebase.database.DataSnapshot | null) => {
if (committee) {
this.setState({ committee: committee.val() });
}
}

componentDidMount() {
this.state.committeeFref.on('value', this.firebaseCallback);
}

componentWillUnmount() {
this.state.committeeFref.off('value', this.firebaseCallback);
}

handleError = (error: any) => {
// A full list of error codes is available at
// https://firebase.google.com/docs/storage/web/handle-errors
switch (error.code) {
case 'storage/unauthorized':
// User doesn't have permission to access the object
break;
case 'storage/canceled':
// User canceled the upload
break;
case 'storage/unknown':
// Unknown error occurred, inspect error.serverResponse
break;
default:
return
}
this.setState({ errorCode: error.code });
}

handleSnapshot = (snapshot: any) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
this.setState({progress: progress, state: snapshot.state});
}

handleComplete = () => {
// downloadURL = uploadTask.snapshot.downloadURL;
handleComplete = (uploadTask: firebase.storage.UploadTask) => () => {
this.state.committeeFref.child('files').push().set(uploadTask.snapshot.ref.name);
this.setState({ state: uploadTask.snapshot.state });
}

onFileChange = (event: any) => {
this.setState({ file: event.target.files[0] });
}

triggerUpload = () => {
const { handleSnapshot, handleUploadError, handleComplete } = this;
const { handleSnapshot, handleError, handleComplete } = this;
const { file } = this.state;

const { committeeID } = this.props.match.params;
Expand All @@ -71,22 +82,69 @@ export default class Files extends React.Component<Props, State> {
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
handleSnapshot,
handleUploadError,
handleComplete
handleError,
handleComplete(uploadTask)
);
}

download = (fileName: string) => () => {
const { committeeID } = this.props.match.params;

const storageRef = firebase.storage().ref();
const pathReference = storageRef.child('committees').child(committeeID).child(fileName);

pathReference.getDownloadURL().then((url) => {
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = (event) => {
const blob = xhr.response;
FileSaver.saveAs(blob, fileName);
};
xhr.open('GET', url);
xhr.send();
});
}

renderFile = (dbId: string, fileName: string) => {
const { download } = this;

return (
<List.Item key={dbId}>
<List.Icon name="file outline" verticalAlign="middle"/>
<List.Content>
<List.Header as="a" onClick={download(fileName)}>{fileName}</List.Header>
{/* <List.Description as="a">Updated 10 mins ago</List.Description> */}
</List.Content>
</List.Item>
);
}

render() {
const { progress, state } = this.state;
const { renderFile } = this;
const { progress, state, errorCode, committee } = this.state;

const files = committee ? (committee.files || {}) : {};

return (
<div>
{state}
<Progress percent={progress || 0} progress active={true} indicating={true} />
<Progress
percent={Math.round(progress || 0 )}
progress
warning={state === firebase.storage.TaskState.PAUSED}
success={state === firebase.storage.TaskState.SUCCESS}
error={!!errorCode}
active={true}
label={errorCode}
/>
<Form onSubmit={this.triggerUpload}>
<input type="file" onChange={this.onFileChange} />
<Button type="submit">Upload</Button>
<Form.Group>
<input type="file" onChange={this.onFileChange} />
<Button type="submit">Upload</Button>
</Form.Group>
</Form>
<List divided relaxed>
{Object.keys(files).reverse().map(key => renderFile(key, files[key]))}
</List>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface State {
timerId?: NodeJS.Timer;
}

const CLIENT_VERSION = 'v2.4.0';
const CLIENT_VERSION = 'v2.5.0';
const RELEASES_LATEST = 'https://api.github.com/repos/MaxwellBo/Muncoordinated-2/releases/latest';

export default class Footer extends React.PureComponent<{}, State> {
Expand Down
1 change: 1 addition & 0 deletions src/components/Homepage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ export default class Homepage extends React.Component<{}, {}> {
/>
<List.Item as="li">Resolution amendments</List.Item>
<List.Item as="li">Roll-call voting</List.Item>
<List.Item as="li">File uploads</List.Item>
<List.Item as="li">Notes</List.Item>
</List>
</p>
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
esutils "^2.0.2"
js-tokens "^3.0.0"

"@types/file-saver@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-1.3.0.tgz#0ef213077e704fc3f4e7a86cfd31c9de4f4f47a7"

"@types/history@*":
version "4.6.1"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.1.tgz#0dbaef4febad5d074d06dbdfae6b027d1d701d25"
Expand Down Expand Up @@ -2686,6 +2690,10 @@ [email protected]:
dependencies:
loader-utils "^1.0.2"

file-saver@^1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8"

filename-regex@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
Expand Down

0 comments on commit b0d0d3f

Please sign in to comment.