Ability to add and delete files
parent
236caa2616
commit
a95d37546f
@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { S3Client, ListObjectsV2Command, HeadObjectCommand } from '@aws-sdk/client-s3';
|
||||
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import { BsFileEarmarkText, BsDownload } from 'react-icons/bs';
|
||||
|
||||
import downloadFile from './downloadFile';
|
||||
|
||||
type Props = {
|
||||
client: S3Client;
|
||||
bucket: string;
|
||||
s3key: string;
|
||||
filename: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
show: boolean;
|
||||
loaded: boolean;
|
||||
size: number;
|
||||
contentType: string;
|
||||
};
|
||||
|
||||
class ObjectInfo extends React.Component<Props, State> {
|
||||
state = {
|
||||
show: true,
|
||||
loaded: false,
|
||||
size: 0,
|
||||
contentType: "",
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
let command = new HeadObjectCommand({
|
||||
Bucket: this.props.bucket,
|
||||
Key: this.props.s3key,
|
||||
});
|
||||
|
||||
const data = await this.props.client.send(command);
|
||||
|
||||
this.setState({
|
||||
loaded: true,
|
||||
size: data.ContentLength!,
|
||||
contentType: data.ContentType || "",
|
||||
});
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.setState({show: false});
|
||||
}
|
||||
|
||||
render() {
|
||||
return <>
|
||||
<Modal show={this.state.show} onHide={() => this.handleClose()}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title><BsFileEarmarkText /> { this.props.filename }</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p><strong>Bucket:</strong> { this.props.bucket }</p>
|
||||
<p><strong>Key:</strong> { this.props.s3key }</p>
|
||||
{ this.state.loaded ?
|
||||
<>
|
||||
<p><strong>Size:</strong> { this.state.size }</p>
|
||||
<p><strong>Content type:</strong> { this.state.contentType }</p>
|
||||
</>
|
||||
: <></> }
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="primary" onClick={() =>
|
||||
downloadFile(this.props.client, this.props.bucket, this.props.s3key, this.props.filename)
|
||||
}>
|
||||
<BsDownload /> Download
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>;
|
||||
}
|
||||
};
|
||||
|
||||
export default ObjectInfo;
|
@ -0,0 +1,163 @@
|
||||
import React from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { S3Client, ListObjectsV2Command, HeadObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
||||
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import ListGroup from 'react-bootstrap/ListGroup';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import Stack from 'react-bootstrap/Stack';
|
||||
|
||||
import { BsFileEarmarkText, BsDownload, BsHourglassSplit, BsCheckCircle } from 'react-icons/bs';
|
||||
import { AiOutlineDelete } from 'react-icons/ai';
|
||||
|
||||
import downloadFile from './downloadFile';
|
||||
import fileSizePretty from './fileSizePretty';
|
||||
|
||||
interface Props {
|
||||
client: S3Client;
|
||||
bucket: string;
|
||||
prefix: string;
|
||||
onUploadComplete: () => void;
|
||||
}
|
||||
|
||||
type State = {
|
||||
iFile: number;
|
||||
show: boolean;
|
||||
files: File[];
|
||||
processing: boolean;
|
||||
canceled: boolean;
|
||||
progressEach: number[];
|
||||
};
|
||||
|
||||
class UploadFiles extends React.Component<Props, State> {
|
||||
file_input: HTMLInputElement | null = null;
|
||||
|
||||
state = {
|
||||
iFile: 0,
|
||||
show: true,
|
||||
files: [],
|
||||
processing: false,
|
||||
canceled: false,
|
||||
progressEach: [],
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this.addFile();
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.setState({show: false});
|
||||
}
|
||||
|
||||
handleFileChange() {
|
||||
let infiles = this.file_input!.files || [];
|
||||
let files: File[] = this.state.files;
|
||||
|
||||
for (var i = 0; i < infiles.length; i++) {
|
||||
files.push(infiles[i]);
|
||||
}
|
||||
|
||||
this.setState({show: (files.length > 0), files: files, iFile: this.state.iFile + 1});
|
||||
}
|
||||
|
||||
addFile() {
|
||||
this.file_input!.click();
|
||||
}
|
||||
|
||||
removeFile(i: number) {
|
||||
let files: File[] = this.state.files;
|
||||
files.splice(i, 1);
|
||||
this.setState({files: files});
|
||||
}
|
||||
|
||||
async processUpload() {
|
||||
this.setState({canceled: false});
|
||||
|
||||
let progressEach: number[] = [];
|
||||
for (var i = 0; i < this.state.files.length; i++) {
|
||||
progressEach.push(0);
|
||||
}
|
||||
this.setState({canceled: false, processing: true, progressEach: progressEach});
|
||||
|
||||
for (var i = 0; i < this.state.files.length; i++) {
|
||||
let file: File = this.state.files[i];
|
||||
let req = new PutObjectCommand({
|
||||
Bucket: this.props.bucket,
|
||||
Key: this.props.prefix + file.name,
|
||||
ContentType: file.type,
|
||||
ContentLength: file.size,
|
||||
Body: file,
|
||||
});
|
||||
let resp = await this.props.client.send(req);
|
||||
progressEach[i] = 100;
|
||||
this.setState({progressEach: progressEach});
|
||||
}
|
||||
|
||||
this.setState({show: false});
|
||||
this.props.onUploadComplete();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <>
|
||||
<Modal show={this.state.show} onHide={() => this.handleClose()}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
Upload files
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<input
|
||||
key={ "file" + this.state.iFile }
|
||||
type="file" id="file_input"
|
||||
ref={input => this.file_input = input}
|
||||
onChange={() => this.handleFileChange()}
|
||||
multiple
|
||||
hidden />
|
||||
{ this.state.files.length == 0 ? <p>No files added yet, click "add file" below.</p> : <></> }
|
||||
<ListGroup>
|
||||
{ this.state.files.map((f: File, i: number) =>
|
||||
<ListGroup.Item>
|
||||
<Stack direction="horizontal">
|
||||
<div className="mt-1">
|
||||
<BsFileEarmarkText /> { f.name }
|
||||
</div>
|
||||
<div className="ms-auto">
|
||||
{ this.state.processing ?
|
||||
(this.state.progressEach[i] == 100 ? <BsCheckCircle />
|
||||
: this.state.progressEach[i] > 0 ? this.state.progressEach[i] + "%"
|
||||
: <BsHourglassSplit /> )
|
||||
:
|
||||
<Button variant="danger" size="sm" onClick={() => this.removeFile(i)}>
|
||||
<AiOutlineDelete />
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
</Stack>
|
||||
<Stack direction="horizontal">
|
||||
<div className="px-2"> </div>
|
||||
<div className="small">
|
||||
{ fileSizePretty(f.size) } - { f.type }
|
||||
</div>
|
||||
</Stack>
|
||||
</ListGroup.Item>
|
||||
)}
|
||||
</ListGroup>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="success" onClick={() => this.addFile()}>
|
||||
Add file
|
||||
</Button>
|
||||
<Button variant="primary" onClick={() => this.processUpload()} disabled={this.state.files.length == 0}>
|
||||
Upload
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>;
|
||||
}
|
||||
};
|
||||
|
||||
export default UploadFiles;
|
@ -0,0 +1,34 @@
|
||||
import StreamSaver from 'streamsaver';
|
||||
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
||||
|
||||
|
||||
async function downloadFile(client: S3Client, bucket: string, key: string, filename: string) {
|
||||
let command = new GetObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
});
|
||||
|
||||
const data = await client.send(command);
|
||||
|
||||
console.log("body:", data.Body);
|
||||
|
||||
const fileStream = StreamSaver.createWriteStream(filename);
|
||||
const writer = fileStream.getWriter();
|
||||
|
||||
const reader = (data.Body! as ReadableStream).getReader();
|
||||
|
||||
const pump = (): Promise<null|undefined> => reader.read()
|
||||
.then(({ value, done }) => {
|
||||
if (done) writer.close();
|
||||
else {
|
||||
writer.write(value);
|
||||
return writer.ready.then(pump);
|
||||
}
|
||||
});
|
||||
|
||||
await pump()
|
||||
.then(() => console.log('Closed the stream, Done writing'))
|
||||
.catch(err => console.log(err));
|
||||
}
|
||||
|
||||
export default downloadFile;
|
@ -0,0 +1,12 @@
|
||||
|
||||
function fileSizePretty(nBytes: number): string {
|
||||
let sOutput = nBytes + " bytes";
|
||||
// optional code for multiples approximation
|
||||
const aMultiples = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
||||
for (var nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
|
||||
sOutput = nApprox.toFixed(3) + " " + aMultiples[nMultiple] + " (" + nBytes + " bytes)";
|
||||
}
|
||||
return sOutput;
|
||||
}
|
||||
|
||||
export default fileSizePretty;
|
Loading…
Reference in New Issue