garage-box/src/ObjectList.tsx

246 lines
7.2 KiB
TypeScript
Raw Permalink Normal View History

2022-02-28 16:08:49 +00:00
import React from 'react';
2022-02-28 20:52:20 +00:00
import { useParams } from 'react-router-dom';
import { S3Client, ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3';
2022-02-28 16:08:49 +00:00
2022-02-28 16:59:06 +00:00
import Alert from 'react-bootstrap/Alert';
import Breadcrumb from 'react-bootstrap/Breadcrumb';
2022-02-28 20:31:26 +00:00
import Button from 'react-bootstrap/Button';
import Container from 'react-bootstrap/Container';
2022-02-28 16:59:06 +00:00
import ListGroup from 'react-bootstrap/ListGroup';
2022-02-28 20:31:26 +00:00
import Stack from 'react-bootstrap/Stack';
2022-02-28 16:59:06 +00:00
import { LinkContainer } from 'react-router-bootstrap'
2022-02-28 20:31:26 +00:00
import ObjectInfo from './ObjectInfo';
import UploadFiles from './UploadFiles';
import downloadFile from './downloadFile';
2022-02-28 20:42:55 +00:00
import { BsInfoCircle, BsFolder, BsFileEarmarkText, BsDownload } from 'react-icons/bs';
import { AiOutlineDelete } from 'react-icons/ai';
2022-02-28 20:31:26 +00:00
2022-02-28 16:08:49 +00:00
type Props = {
client: S3Client;
bucket: string;
prefix: string;
};
type State = {
2022-02-28 16:15:39 +00:00
loaded: boolean;
2022-02-28 16:08:49 +00:00
folders: string[];
files: string[];
2022-02-28 20:31:26 +00:00
info: string | null;
iInfo: number;
iUpload: number;
2022-02-28 16:08:49 +00:00
};
2022-02-28 16:59:06 +00:00
var cache: { [path: string]: State; } = {};
2022-02-28 16:08:49 +00:00
class ObjectList extends React.Component<Props, State> {
state = {
2022-02-28 16:15:39 +00:00
loaded: false,
2022-02-28 16:08:49 +00:00
folders: [],
files: [],
2022-02-28 20:31:26 +00:00
info: null,
iInfo: 0,
iUpload: 0,
2022-02-28 16:08:49 +00:00
};
2022-02-28 20:31:26 +00:00
componentDidMount() {
//console.log(this.props);
2022-02-28 16:08:49 +00:00
2022-02-28 20:52:20 +00:00
if (cache[this.fullPathWithBucket()]) {
this.setState(cache[this.fullPathWithBucket()]);
2022-02-28 16:59:06 +00:00
}
2022-02-28 20:31:26 +00:00
this.loadEntries();
}
async loadEntries() {
2022-02-28 16:08:49 +00:00
let command = new ListObjectsV2Command({
Bucket: this.props.bucket,
Prefix: this.props.prefix,
Delimiter: '/',
});
try {
2022-02-28 16:15:39 +00:00
const pxlen = this.props.prefix.length;
2022-02-28 16:08:49 +00:00
const data = await this.props.client.send(command);
2022-02-28 20:31:26 +00:00
//console.log("ok", data);
2022-02-28 16:59:06 +00:00
const folders = (data.CommonPrefixes || []).map((cp) => cp.Prefix!.substring(pxlen));
const files = (data.Contents || []).map((obj) => obj.Key!.substring(pxlen));
2022-02-28 20:31:26 +00:00
folders.sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}));
files.sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}));
2022-02-28 16:59:06 +00:00
2022-02-28 16:08:49 +00:00
this.setState({
2022-02-28 16:15:39 +00:00
loaded: true,
2022-02-28 16:59:06 +00:00
folders: folders,
files: files,
2022-02-28 16:08:49 +00:00
});
2022-02-28 20:52:20 +00:00
cache[this.fullPathWithBucket()] = this.state;
2022-02-28 16:08:49 +00:00
} catch(error) {
console.log("err", error);
}
}
2022-02-28 20:52:20 +00:00
fullPathWithBucket() {
2022-02-28 16:59:06 +00:00
return this.props.bucket + "/" + this.props.prefix;
}
renderBreadcrumbs() {
2022-02-28 16:15:39 +00:00
let spl = this.props.prefix.split("/");
2022-02-28 16:59:06 +00:00
let items = [];
for (var i = 0; i < spl.length - 1; i++) {
2022-02-28 20:31:26 +00:00
let to = "/" + this.props.bucket + "/" + spl.slice(0, i+1).join("/") + "/" ;
2022-02-28 16:59:06 +00:00
if (i < spl.length - 2) {
items.push(
2022-02-28 20:31:26 +00:00
<LinkContainer to={to} key={to}>
2022-02-28 16:59:06 +00:00
<Breadcrumb.Item>{ spl[i] }</Breadcrumb.Item>
</LinkContainer>
);
} else {
items.push(
2022-02-28 20:31:26 +00:00
<Breadcrumb.Item active key={to}>{ spl[i] }</Breadcrumb.Item>
2022-02-28 16:59:06 +00:00
);
}
}
2022-02-28 16:15:39 +00:00
return (
2022-02-28 16:59:06 +00:00
<Breadcrumb>
2022-02-28 20:31:26 +00:00
<LinkContainer key="BUCKETS" to="/">
2022-02-28 16:59:06 +00:00
<Breadcrumb.Item>my buckets</Breadcrumb.Item>
</LinkContainer>
2022-02-28 20:52:20 +00:00
{ this.props.prefix === "" ?
2022-02-28 16:59:06 +00:00
<Breadcrumb.Item active>{ this.props.bucket }</Breadcrumb.Item>
:
2022-02-28 20:31:26 +00:00
<LinkContainer key="BUCKET" to={ "/" + this.props.bucket + "/" }>
2022-02-28 16:59:06 +00:00
<Breadcrumb.Item>{ this.props.bucket }</Breadcrumb.Item>
</LinkContainer>
}
{ items }
</Breadcrumb>
2022-02-28 16:15:39 +00:00
);
}
2022-02-28 20:31:26 +00:00
openInfo(f: string) {
this.setState({info: f, iInfo: this.state.iInfo + 1});
}
2022-02-28 20:42:55 +00:00
async deleteFile(f: string) {
if (window.confirm("Really delete file " + f + "?")) {
console.log("DELETING", f);
let command = new DeleteObjectCommand({
Bucket: this.props.bucket,
Key: this.props.prefix + f,
});
2022-02-28 20:52:20 +00:00
await this.props.client.send(command);
2022-02-28 20:42:55 +00:00
this.loadEntries();
}
}
2022-02-28 20:31:26 +00:00
openUpload() {
this.setState({iUpload: this.state.iUpload + 1});
}
onUploadComplete() {
this.loadEntries();
}
2022-02-28 16:15:39 +00:00
2022-02-28 20:31:26 +00:00
render() {
2022-02-28 16:08:49 +00:00
return (
2022-02-28 16:59:06 +00:00
<>
2022-02-28 20:31:26 +00:00
{ this.state.iUpload > 0 ?
<UploadFiles
key={ "upload" + this.state.iUpload }
client={this.props.client}
bucket={this.props.bucket}
prefix={this.props.prefix}
onUploadComplete={() => this.onUploadComplete()}
/>
: <></> }
{ this.state.info ?
<ObjectInfo
key={ "info" + this.state.iInfo }
client={ this.props.client }
bucket={this.props.bucket}
s3key={this.props.prefix + this.state.info}
filename={this.state.info} />
: <></> }
<Container className="pb-3">
<Stack direction="horizontal">
<div className="mt-1">
{ this.renderBreadcrumbs() }
</div>
<div className="ms-auto">
<Button size="sm" variant="info" onClick={(event) => this.openUpload()}>
Upload files
</Button>
</div>
</Stack>
</Container>
2022-02-28 20:45:43 +00:00
{ this.state.loaded ?
<ListGroup>
{ this.state.folders.map((f) =>
<LinkContainer key={f + "/"} to={ "/" + this.props.bucket + "/" + this.props.prefix + f }>
<ListGroup.Item action>
<BsFolder /> { f }
</ListGroup.Item>
</LinkContainer>
)}
{ this.state.files.map((f) =>
<ListGroup.Item key={f}>
<Stack direction="horizontal">
<div><BsFileEarmarkText /> { f }</div>
<div className="ms-auto">
<Button size="sm" className="mx-1" variant="primary" onClick={(event) => {
event.stopPropagation();
downloadFile(this.props.client, this.props.bucket, this.props.prefix + f, f);
}}>
<BsDownload />
</Button>
<Button size="sm" className="mx-1" variant="danger" onClick={(event) => {
event.stopPropagation();
this.deleteFile(f);
}}>
<AiOutlineDelete />
</Button>
<Button size="sm" className="mx-1" variant="secondary" onClick={(event) => {
event.stopPropagation();
this.openInfo(f);
}}>
<BsInfoCircle />
</Button>
</div>
</Stack>
</ListGroup.Item>
)}
</ListGroup>
:
<Alert variant="secondary">Loading...</Alert>
}
2022-02-28 16:59:06 +00:00
</>
2022-02-28 16:08:49 +00:00
);
}
}
interface IClient {
client: S3Client;
}
export const ObjectList1 = ({ client }: IClient) => {
const params = useParams();
const bucket = params["bucket"]!;
return <>
<ObjectList client={client} bucket={bucket} prefix="" key={bucket} />
</>;
};
export const ObjectList2 = ({ client }: IClient) => {
const params = useParams();
const bucket = params["bucket"]!;
const prefix = params["*"] || "";
const key = bucket + "/" + prefix;
return <>
<ObjectList client={client} bucket={bucket} prefix={prefix} key={key} />
</>;
};