mirror of
https://github.com/filecoin-project/slate.git
synced 2024-12-23 09:01:56 +03:00
implementing api for multiple delete
This commit is contained in:
parent
aaab6fcb2c
commit
da5ccb8abc
@ -206,6 +206,13 @@ export const deleteBucketItem = async (data) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteBucketItems = async (data) => {
|
||||
return await returnJSON(`/api/data/remove-multiple`, {
|
||||
...DEFAULT_OPTIONS,
|
||||
body: JSON.stringify({ data }),
|
||||
});
|
||||
};
|
||||
|
||||
export const getSerializedSlate = async (data) => {
|
||||
return await returnJSON(`/api/slates/get-serialized`, {
|
||||
...DEFAULT_OPTIONS,
|
||||
|
@ -1,3 +1,6 @@
|
||||
import React from "react";
|
||||
import * as SVG from "~/common/svg";
|
||||
|
||||
export const values = {
|
||||
version: "1.0.0",
|
||||
sds: "0.1.0",
|
||||
@ -79,3 +82,27 @@ export const theme = {
|
||||
export const gateways = {
|
||||
ipfs: "https://slate.textile.io/ipfs",
|
||||
};
|
||||
|
||||
export function FileTypeIcon(props) {
|
||||
if (props.type && props.type.startsWith("image/")) {
|
||||
return <SVG.Image {...props} />;
|
||||
}
|
||||
|
||||
if (props.type && props.type.startsWith("video/")) {
|
||||
return <SVG.Video {...props} />;
|
||||
}
|
||||
|
||||
if (props.type && props.type.startsWith("audio/")) {
|
||||
return <SVG.Sound {...props} />;
|
||||
}
|
||||
|
||||
if (props.type && props.type.startsWith("application/epub")) {
|
||||
return <SVG.Book {...props} />;
|
||||
}
|
||||
|
||||
if (props.type && props.type.startsWith("application/pdf")) {
|
||||
return <SVG.Document {...props} />;
|
||||
}
|
||||
|
||||
return <SVG.Document {...props} />;
|
||||
}
|
||||
|
@ -593,6 +593,25 @@ export const Slate = (props) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Trash = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M3 6H5H21" />
|
||||
<path d="M8 6V4C8 3.46957 8.21071 2.96086 8.58579 2.58579C8.96086 2.21071 9.46957 2 10 2H14C14.5304 2 15.0391 2.21071 15.4142 2.58579C15.7893 2.96086 16 3.46957 16 4V6M19 6V20C19 20.5304 18.7893 21.0391 18.4142 21.4142C18.0391 21.7893 17.5304 22 17 22H7C6.46957 22 5.96086 21.7893 5.58579 21.4142C5.21071 21.0391 5 20.5304 5 20V6H19Z" />
|
||||
<path d="M10 11V17" />
|
||||
<path d="M14 11V17" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Folder = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -11,29 +11,22 @@ import { PopoverNavigation } from "~/components/system/components/PopoverNavigat
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
import { dispatchCustomEvent } from "~/common/custom-events";
|
||||
import { generateLayout } from "~/components/core/Slate";
|
||||
import { CheckBox } from "~/components/system/components/CheckBox";
|
||||
import { Table } from "~/components/core/Table";
|
||||
|
||||
import SlateMediaObject from "~/components/core/SlateMediaObject";
|
||||
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
|
||||
|
||||
const COLUMNS_SCHEMA = [
|
||||
{
|
||||
key: "name",
|
||||
name: <span style={{ fontSize: "0.9rem" }}>Name</span>,
|
||||
width: "100%",
|
||||
},
|
||||
{
|
||||
key: "size",
|
||||
name: <span style={{ fontSize: "0.9rem" }}>Size</span>,
|
||||
width: "104px",
|
||||
},
|
||||
{
|
||||
key: "more",
|
||||
name: <div />,
|
||||
width: "64px",
|
||||
},
|
||||
];
|
||||
const STYLES_CONTAINER_HOVER = css`
|
||||
display: flex;
|
||||
|
||||
:hover {
|
||||
color: ${Constants.system.brand};
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_LINK = css`
|
||||
display: inline;
|
||||
cursor: pointer;
|
||||
transition: 200ms ease all;
|
||||
font-size: 0.9rem;
|
||||
@ -41,10 +34,6 @@ const STYLES_LINK = css`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
:hover {
|
||||
color: ${Constants.system.brand};
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_VALUE = css`
|
||||
@ -55,18 +44,8 @@ const STYLES_VALUE = css`
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const STYLES_TABLE_VALUE = {
|
||||
fontFamily: Constants.font.medium,
|
||||
padding: "0px 24px",
|
||||
};
|
||||
|
||||
const STYLES_TABLE_CONTAINER = css`
|
||||
border: 1px solid rgba(229, 229, 229, 0.75);
|
||||
`;
|
||||
|
||||
const STYLES_ICON_BOX = css`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
@ -85,6 +64,7 @@ const STYLES_COPY_INPUT = css`
|
||||
const STYLES_IMAGE_GRID = css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -27px;
|
||||
`;
|
||||
@ -109,6 +89,7 @@ export default class DataView extends React.Component {
|
||||
|
||||
state = {
|
||||
menu: null,
|
||||
checked: {},
|
||||
loading: {},
|
||||
};
|
||||
|
||||
@ -385,6 +366,13 @@ export default class DataView extends React.Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: { message: "File successfully deleted!", status: "INFO" },
|
||||
},
|
||||
});
|
||||
|
||||
await this.props.onRehydrate();
|
||||
await this._handleUpdate();
|
||||
this._handleLoading({ cid });
|
||||
@ -419,16 +407,73 @@ export default class DataView extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
const columns = COLUMNS_SCHEMA;
|
||||
const columns = [
|
||||
{
|
||||
key: "checkbox",
|
||||
name: <span />,
|
||||
width: "24px",
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
name: <div style={{ fontSize: "0.9rem", padding: "18px 0" }}>Name</div>,
|
||||
width: "100%",
|
||||
},
|
||||
{
|
||||
key: "size",
|
||||
name: <div style={{ fontSize: "0.9rem", padding: "18px 0" }}>Size</div>,
|
||||
width: "104px",
|
||||
},
|
||||
{
|
||||
key: "more",
|
||||
name: <span />,
|
||||
width: "48px",
|
||||
},
|
||||
];
|
||||
const rows = this.props.items.map((each, index) => {
|
||||
const cid = each.ipfs.replace("/ipfs/", "");
|
||||
const isOnNetwork = each.networks && each.networks.includes("FILECOIN");
|
||||
|
||||
return {
|
||||
...each,
|
||||
checkbox: this.props.onCheckBox ? (
|
||||
<div
|
||||
style={{
|
||||
margin: "12px 0",
|
||||
opacity:
|
||||
Object.keys(this.props.checked).length > 0
|
||||
? 1
|
||||
: this.state.hover === index
|
||||
? 1
|
||||
: 0,
|
||||
}}
|
||||
>
|
||||
<CheckBox
|
||||
name={`checkbox-${this.props.startIndex + index}`}
|
||||
value={
|
||||
!!this.props.checked[
|
||||
`checkbox-${this.props.startIndex + index}`
|
||||
]
|
||||
}
|
||||
onChange={this.props.onCheckBox}
|
||||
boxStyle={{ height: 16, width: 16 }}
|
||||
style={{ position: "relative", right: 3 }}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
),
|
||||
name: (
|
||||
<div css={STYLES_LINK} onClick={() => this._handleSelect(index)}>
|
||||
{each.file || each.name}
|
||||
<div
|
||||
css={STYLES_CONTAINER_HOVER}
|
||||
onClick={() => this._handleSelect(index)}
|
||||
>
|
||||
<div
|
||||
css={STYLES_ICON_BOX}
|
||||
style={{ paddingLeft: 0, paddingRight: 18 }}
|
||||
>
|
||||
<Constants.FileTypeIcon type={each.type} height="24px" />
|
||||
</div>
|
||||
<div css={STYLES_LINK}>{each.file || each.name}</div>
|
||||
</div>
|
||||
),
|
||||
size: <div css={STYLES_VALUE}>{Strings.bytesToSize(each.size)}</div>,
|
||||
@ -496,19 +541,13 @@ export default class DataView extends React.Component {
|
||||
};
|
||||
|
||||
return (
|
||||
<div css={STYLES_TABLE_CONTAINER}>
|
||||
<System.Table
|
||||
topRowStyle={{
|
||||
...STYLES_TABLE_VALUE,
|
||||
backgroundColor: Constants.system.foreground,
|
||||
fontFamily: Constants.font.semiBold,
|
||||
padding: "12px 24px",
|
||||
}}
|
||||
<React.Fragment>
|
||||
<Table
|
||||
data={data}
|
||||
noColor
|
||||
rowStyle={STYLES_TABLE_VALUE}
|
||||
onAction={this.props.onAction}
|
||||
onNavigateTo={this.props.onNavigateTo}
|
||||
rowStyle={{ padding: "10px 16px" }}
|
||||
topRowStyle={{ padding: "0px 16px" }}
|
||||
onMouseEnter={(i) => this.setState({ hover: i })}
|
||||
onMouseLeave={() => this.setState({ hover: null })}
|
||||
/>
|
||||
<input
|
||||
ref={(c) => {
|
||||
@ -518,7 +557,7 @@ export default class DataView extends React.Component {
|
||||
value={this.state.copyValue}
|
||||
css={STYLES_COPY_INPUT}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -51,75 +51,21 @@ export default class SlateMediaObjectPreview extends React.Component {
|
||||
? this.props.title.substring(0, this.props.charCap) + "..."
|
||||
: this.props.title;
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("image/")) {
|
||||
return <img css={STYLES_IMAGE} style={this.props.imageStyle} src={url} />;
|
||||
}
|
||||
|
||||
let element = (
|
||||
<Constants.FileTypeIcon type={this.props.type} height="24px" />
|
||||
);
|
||||
|
||||
return (
|
||||
<article css={STYLES_ENTITY} style={this.props.style}>
|
||||
<div>
|
||||
<SVG.Document height="24px" />
|
||||
</div>
|
||||
<div>{element}</div>
|
||||
{this.props.title && !this.props.small ? (
|
||||
<div css={STYLES_TITLE}>{title}</div>
|
||||
) : null}
|
||||
</article>
|
||||
);
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("video/")) {
|
||||
element = (
|
||||
<article css={STYLES_ENTITY} style={this.props.style}>
|
||||
<div>
|
||||
<SVG.Video height="24px" />
|
||||
</div>
|
||||
{this.props.title && !this.props.small ? (
|
||||
<div css={STYLES_TITLE}>{title}</div>
|
||||
) : null}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("audio/")) {
|
||||
element = (
|
||||
<article css={STYLES_ENTITY} style={this.props.style}>
|
||||
<div>
|
||||
<SVG.Sound height="24px" />
|
||||
</div>
|
||||
{this.props.title && !this.props.small ? (
|
||||
<div css={STYLES_TITLE}>{title}</div>
|
||||
) : null}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("application/epub")) {
|
||||
element = (
|
||||
<article css={STYLES_ENTITY} style={this.props.style}>
|
||||
<div>
|
||||
<SVG.Book height="24px" />
|
||||
</div>
|
||||
{this.props.title && !this.props.small ? (
|
||||
<div css={STYLES_TITLE}>{title}</div>
|
||||
) : null}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("application/pdf")) {
|
||||
element = (
|
||||
<article css={STYLES_ENTITY} style={this.props.style}>
|
||||
<div>
|
||||
<SVG.Document height="24px" />
|
||||
</div>
|
||||
{this.props.title && !this.props.small ? (
|
||||
<div css={STYLES_TITLE}>{title}</div>
|
||||
) : null}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("image/")) {
|
||||
element = (
|
||||
<img css={STYLES_IMAGE} style={this.props.imageStyle} src={url} />
|
||||
);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ const STYLES_IMAGE_ROW = css`
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
height: 160px;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
@ -26,7 +27,7 @@ const STYLES_IMAGE_ROW = css`
|
||||
const STYLES_ITEM_BOX = css`
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
margin: 0px 18px;
|
||||
margin: 0px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
131
components/core/Table.js
Normal file
131
components/core/Table.js
Normal file
@ -0,0 +1,131 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as SubSystem from "~/components/system/components/fragments/TableComponents";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { P } from "~/components/system/components/Typography";
|
||||
import * as SVG from "~/common/svg";
|
||||
|
||||
const TABLE_COLUMN_WIDTH_DEFAULTS = {
|
||||
1: "100%",
|
||||
2: "50%",
|
||||
3: "33.333%",
|
||||
4: "25%",
|
||||
5: "20%",
|
||||
6: "16.666%",
|
||||
7: "14.28%",
|
||||
8: "12.5%",
|
||||
};
|
||||
|
||||
const STYLES_CONTAINER = css`
|
||||
border: 1px solid rgba(229, 229, 229, 0.75);
|
||||
`;
|
||||
|
||||
const STYLES_TABLE_ROW = css`
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid rgba(229, 229, 229, 0.75);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
transition: 200ms ease all;
|
||||
|
||||
:last-child {
|
||||
border: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_TABLE_TOP_ROW = css`
|
||||
box-sizing: border-box;
|
||||
font-family: ${Constants.font.semiBold};
|
||||
border-bottom: 1px solid rgba(229, 229, 229, 0.75);
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
background-color: ${Constants.system.foreground};
|
||||
`;
|
||||
|
||||
export class Table extends React.Component {
|
||||
render() {
|
||||
const { data } = this.props;
|
||||
|
||||
const ac = {};
|
||||
|
||||
if (!data || !data.rows || data.rows.length === 0) {
|
||||
return <P style={{ padding: 24 }}>No data.</P>;
|
||||
}
|
||||
|
||||
for (let x = 0; x < data.columns.length; x++) {
|
||||
ac[data.columns[x].key] = {
|
||||
...data.columns[x],
|
||||
index: x,
|
||||
};
|
||||
}
|
||||
|
||||
const width = TABLE_COLUMN_WIDTH_DEFAULTS[data.columns.length];
|
||||
return (
|
||||
<div css={STYLES_CONTAINER} onMouseLeave={this.props.onMouseLeave}>
|
||||
{this.props.noLabel ? null : (
|
||||
<div css={STYLES_TABLE_TOP_ROW} style={this.props.topRowStyle}>
|
||||
{data.columns.map((c, cIndex) => {
|
||||
let localWidth = c.width ? c.width : width;
|
||||
let flexShrink = c.width && c.width !== "100%" ? "0" : null;
|
||||
if (cIndex === 0 && !c.width) {
|
||||
localWidth = "100%";
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`table-top-${c.key}-${cIndex}`}
|
||||
style={{
|
||||
width: localWidth,
|
||||
flexShrink,
|
||||
}}
|
||||
>
|
||||
{c.name || ""}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.rows.map((r, i) => {
|
||||
return (
|
||||
<div
|
||||
key={`${r.id}-${i}`}
|
||||
css={STYLES_TABLE_ROW}
|
||||
style={this.props.rowStyle}
|
||||
onMouseEnter={() => this.props.onMouseEnter(i)}
|
||||
>
|
||||
{Object.keys(ac).map((each, cIndex) => {
|
||||
const field = ac[each];
|
||||
const content = r[each];
|
||||
|
||||
let localWidth = field.width ? field.width : width;
|
||||
let flexShrink =
|
||||
field.width && field.width !== "100%" ? "0" : null;
|
||||
if (cIndex === 0 && !field.width) {
|
||||
localWidth = "100%";
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`${each}-${i}-${cIndex}`}
|
||||
style={{
|
||||
width: localWidth,
|
||||
flexShrink,
|
||||
...this.props.contentStyle,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -25,8 +25,8 @@ const STYLES_CHECKBOX_FIGURE = css`
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
flex-shrink: 0;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
`;
|
||||
@ -74,7 +74,7 @@ export class CheckBox extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<label css={STYLES_CHECKBOX} style={this.props.style}>
|
||||
<figure css={STYLES_CHECKBOX_FIGURE}>
|
||||
<figure css={STYLES_CHECKBOX_FIGURE} style={this.props.boxStyle}>
|
||||
{this.props.value ? <SVG.CheckBox height="20px" /> : null}
|
||||
</figure>
|
||||
<input
|
||||
|
142
pages/api/data/remove-multiple.js
Normal file
142
pages/api/data/remove-multiple.js
Normal file
@ -0,0 +1,142 @@
|
||||
import * as Data from "~/node_common/data";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
import * as Strings from "~/common/strings";
|
||||
import { read } from "fs";
|
||||
|
||||
const generateLayout = (items) => {
|
||||
if (!items) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!items.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return items.map((item, i) => {
|
||||
var y = Math.ceil(Math.random() * 4) + 1;
|
||||
|
||||
return {
|
||||
x: (i * 2) % 10,
|
||||
y: 0,
|
||||
w: 2,
|
||||
h: 2,
|
||||
minW: 2,
|
||||
minH: 2,
|
||||
// NOTE(jim): Library quirk thats required.
|
||||
i: i.toString(),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export default async (req, res) => {
|
||||
if (!req.body.data || !req.body.data.cids || !req.body.data.cids.length) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_REMOVE_DATA_NO_CID", error: true });
|
||||
}
|
||||
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
if (!id) {
|
||||
return res
|
||||
.status(403)
|
||||
.send({ decorator: "SERVER_REMOVE_DATA_NOT_ALLOWED", error: true });
|
||||
}
|
||||
|
||||
const user = await Data.getUserById({
|
||||
id,
|
||||
});
|
||||
|
||||
const {
|
||||
buckets,
|
||||
bucketKey,
|
||||
bucketName,
|
||||
} = await Utilities.getBucketAPIFromUserToken(user.data.tokens.api);
|
||||
|
||||
const r = await buckets.list();
|
||||
const items = await buckets.listIpfsPath(r[0].path);
|
||||
|
||||
let entities = [];
|
||||
for (let i = 0; i < items.itemsList.length; i++) {
|
||||
if (req.body.data.cids.includes(items.itemsList[i].cid)) {
|
||||
entities.push(items.itemsList[i]);
|
||||
if (entities.length === items.itemsList.length) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entities.length) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_REMOVE_DATA_NO_CID", error: true });
|
||||
}
|
||||
|
||||
// remove from your bucket
|
||||
for (let entity of entities) {
|
||||
try {
|
||||
// NOTE(jim):
|
||||
// We use name instead of path because the second argument is for
|
||||
// a subpath, not the full path.
|
||||
bucketRemoval = await buckets.removePath(bucketKey, entity.name);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(jim):
|
||||
// Goes through all of your slates and removes all data references.
|
||||
const slates = await Data.getSlatesByUserId({ userId: id });
|
||||
for (let i = 0; i < slates.length; i++) {
|
||||
let slate = slates[i];
|
||||
|
||||
let removal = false;
|
||||
let objects = slate.data.objects.filter((o) => {
|
||||
for (let cid of req.body.data.cids) {
|
||||
if (o.url.includes(cid)) {
|
||||
removal = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (removal) {
|
||||
let layouts = await Data.updateSlateById({
|
||||
id: slate.id,
|
||||
updated_at: new Date(),
|
||||
data: {
|
||||
...slate.data,
|
||||
objects,
|
||||
layouts: { lg: generateLayout(objects) },
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(jim):
|
||||
// Removes the file reference from your library
|
||||
const response = await Data.updateUserById({
|
||||
id: user.id,
|
||||
data: {
|
||||
...user.data,
|
||||
library: [
|
||||
{
|
||||
...user.data.library[0],
|
||||
children: user.data.library[0].children.filter((o) => {
|
||||
for (let cid of req.body.data.cids) {
|
||||
if (o.ipfs.includes(cid)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
decorator: "SERVER_REMOVE_DATA",
|
||||
success: true,
|
||||
bucketItems: items.itemsList,
|
||||
});
|
||||
};
|
@ -6,7 +6,11 @@ import * as Constants from "~/common/constants";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { TabGroup } from "~/components/core/TabGroup";
|
||||
import { ButtonPrimary } from "~/components/system/components/Buttons";
|
||||
import {
|
||||
ButtonPrimary,
|
||||
ButtonWarning,
|
||||
} from "~/components/system/components/Buttons";
|
||||
import { dispatchCustomEvent } from "~/common/custom-events";
|
||||
|
||||
import ScenePage from "~/components/core/ScenePage";
|
||||
import DataView from "~/components/core/DataView";
|
||||
@ -33,8 +37,18 @@ const STYLES_HEADER_LINE = css`
|
||||
margin-bottom: 42px;
|
||||
`;
|
||||
|
||||
const STYLES_ARROWS = css`
|
||||
text-align: end;
|
||||
const STYLES_ACTION_ROW = css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const STYLES_LEFT = css`
|
||||
width: 100%;
|
||||
min-width: 10%;
|
||||
`;
|
||||
|
||||
const STYLES_RIGHT = css`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const STYLES_ICON_ELEMENT = css`
|
||||
@ -73,6 +87,7 @@ export default class SceneFilesFolder extends React.Component {
|
||||
state = {
|
||||
view: "grid",
|
||||
startIndex: 0,
|
||||
checked: {},
|
||||
};
|
||||
|
||||
loop = async () => {
|
||||
@ -134,6 +149,62 @@ export default class SceneFilesFolder extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
_handleCheckBox = (e) => {
|
||||
let checked = this.state.checked;
|
||||
if (e.target.value === false) {
|
||||
delete checked[e.target.name];
|
||||
this.setState({ checked });
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
checked: { ...this.state.checked, [e.target.name]: true },
|
||||
});
|
||||
};
|
||||
|
||||
_handleDeleteFiles = async (e) => {
|
||||
const message = `Are you sure you want to delete these ${
|
||||
Object.keys(this.state.checked).length
|
||||
} files? They will be deleted from your slates as well`;
|
||||
if (!window.confirm(message)) {
|
||||
return;
|
||||
}
|
||||
let cids = Object.keys(this.state.checked).map((id) => {
|
||||
let index = parseInt(id.replace("checkbox-", ""));
|
||||
return this.props.viewer.library[0].children[index].ipfs.replace(
|
||||
"/ipfs/",
|
||||
""
|
||||
);
|
||||
});
|
||||
console.log(cids);
|
||||
|
||||
const response = await Actions.deleteBucketItems({ cids });
|
||||
if (!response) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: {
|
||||
message:
|
||||
"We're having trouble connecting right now. Please try again later",
|
||||
},
|
||||
},
|
||||
});
|
||||
return null;
|
||||
}
|
||||
if (response.error) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: { alert: { decorator: response.decorator } },
|
||||
});
|
||||
return null;
|
||||
}
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: { message: "Files successfully deleted!", status: "INFO" },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ScenePage>
|
||||
@ -166,13 +237,13 @@ export default class SceneFilesFolder extends React.Component {
|
||||
<div
|
||||
css={STYLES_ICON_BOX}
|
||||
onClick={() => {
|
||||
this.setState({ view: "list" });
|
||||
this.setState({ view: "grid" });
|
||||
}}
|
||||
>
|
||||
<SVG.ListView
|
||||
<SVG.GridView
|
||||
style={{
|
||||
color:
|
||||
this.state.view === "list"
|
||||
this.state.view === "grid"
|
||||
? Constants.system.black
|
||||
: "rgba(0,0,0,0.25)",
|
||||
}}
|
||||
@ -182,13 +253,13 @@ export default class SceneFilesFolder extends React.Component {
|
||||
<div
|
||||
css={STYLES_ICON_BOX}
|
||||
onClick={() => {
|
||||
this.setState({ view: "grid" });
|
||||
this.setState({ view: "list" });
|
||||
}}
|
||||
>
|
||||
<SVG.GridView
|
||||
<SVG.ListView
|
||||
style={{
|
||||
color:
|
||||
this.state.view === "grid"
|
||||
this.state.view === "list"
|
||||
? Constants.system.black
|
||||
: "rgba(0,0,0,0.25)",
|
||||
}}
|
||||
@ -204,36 +275,57 @@ export default class SceneFilesFolder extends React.Component {
|
||||
this.state.startIndex,
|
||||
this.state.startIndex + VIEW_LIMIT
|
||||
)}
|
||||
onAction={this.props.onAction}
|
||||
startIndex={this.state.startIndex}
|
||||
checked={this.state.checked}
|
||||
onCheckBox={this._handleCheckBox}
|
||||
onRehydrate={this.props.onRehydrate}
|
||||
/>
|
||||
<div css={STYLES_ARROWS}>
|
||||
<span
|
||||
css={STYLES_ICON_ELEMENT}
|
||||
style={
|
||||
this.state.startIndex - VIEW_LIMIT >= 0
|
||||
? null
|
||||
: { cursor: "not-allowed", color: Constants.system.border }
|
||||
}
|
||||
onClick={() => this._increment(-1)}
|
||||
>
|
||||
<SVG.NavigationArrow
|
||||
height="24px"
|
||||
style={{ transform: `rotate(180deg)` }}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
css={STYLES_ICON_ELEMENT}
|
||||
style={
|
||||
this.state.startIndex + VIEW_LIMIT <
|
||||
this.props.viewer.library[0].children.length
|
||||
? null
|
||||
: { cursor: "not-allowed", color: Constants.system.border }
|
||||
}
|
||||
onClick={() => this._increment(1)}
|
||||
>
|
||||
<SVG.NavigationArrow height="24px" />
|
||||
</span>
|
||||
<div css={STYLES_ACTION_ROW}>
|
||||
<div css={STYLES_LEFT}>
|
||||
{Object.keys(this.state.checked).length ? (
|
||||
<ButtonWarning
|
||||
style={{ width: 160 }}
|
||||
onClick={this._handleDeleteFiles}
|
||||
>
|
||||
Delete {Object.keys(this.state.checked).length} file
|
||||
{Object.keys(this.state.checked).length > 1 ? "s" : ""}
|
||||
</ButtonWarning>
|
||||
) : null}
|
||||
</div>
|
||||
<div css={STYLES_RIGHT}>
|
||||
<span
|
||||
css={STYLES_ICON_ELEMENT}
|
||||
style={
|
||||
this.state.startIndex - VIEW_LIMIT >= 0
|
||||
? null
|
||||
: {
|
||||
cursor: "not-allowed",
|
||||
color: Constants.system.border,
|
||||
}
|
||||
}
|
||||
onClick={() => this._increment(-1)}
|
||||
>
|
||||
<SVG.NavigationArrow
|
||||
height="24px"
|
||||
style={{ transform: `rotate(180deg)` }}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
css={STYLES_ICON_ELEMENT}
|
||||
style={
|
||||
this.state.startIndex + VIEW_LIMIT <
|
||||
this.props.viewer.library[0].children.length
|
||||
? null
|
||||
: {
|
||||
cursor: "not-allowed",
|
||||
color: Constants.system.border,
|
||||
}
|
||||
}
|
||||
onClick={() => this._increment(1)}
|
||||
>
|
||||
<SVG.NavigationArrow height="24px" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
|
Loading…
Reference in New Issue
Block a user