diff --git a/common/actions.js b/common/actions.js
index 7fb01360..5ce4cf76 100644
--- a/common/actions.js
+++ b/common/actions.js
@@ -1,6 +1,5 @@
import "isomorphic-fetch";
-import * as State from "~/common/state";
import * as Strings from "~/common/strings";
const REQUEST_HEADERS = {
@@ -21,6 +20,13 @@ const returnJSON = async (route, options) => {
return json;
};
+export const health = async (data) => {
+ return await returnJSON(`/api/_`, {
+ ...DEFAULT_OPTIONS,
+ body: JSON.stringify({ data: { success: true } }),
+ });
+};
+
export const sendFilecoin = async (data) => {
if (Strings.isEmpty(data.source)) {
return null;
@@ -80,13 +86,6 @@ export const checkCIDStatus = async (data) => {
});
};
-export const health = async (data) => {
- return await returnJSON(`/api/_`, {
- ...DEFAULT_OPTIONS,
- body: JSON.stringify({ data: { success: true } }),
- });
-};
-
export const createSlate = async (data) => {
return await returnJSON(`/api/slates/create`, {
...DEFAULT_OPTIONS,
@@ -127,3 +126,10 @@ export const deleteAPIKey = async (data) => {
body: JSON.stringify({ data }),
});
};
+
+export const deleteBucketItem = async (data) => {
+ return await returnJSON(`/api/data/remove`, {
+ ...DEFAULT_OPTIONS,
+ body: JSON.stringify({ data }),
+ });
+};
diff --git a/components/core/DataView.js b/components/core/DataView.js
new file mode 100644
index 00000000..6d6071c7
--- /dev/null
+++ b/components/core/DataView.js
@@ -0,0 +1,198 @@
+import * as React from "react";
+import * as Constants from "~/common/constants";
+import * as Strings from "~/common/strings";
+import * as System from "~/components/system";
+import * as Actions from "~/common/actions";
+
+import { css } from "@emotion/react";
+
+import Section from "~/components/core/Section";
+import MediaObject from "~/components/core/MediaObject";
+
+const COLUMNS_SCHEMA = [
+ { key: "cid", name: "CID", width: "100%" },
+ {
+ key: "size",
+ name: "Size",
+ width: "84px",
+ },
+ { key: "type", name: "Type", type: "TEXT_TAG", width: "172px" },
+ {
+ key: "networks",
+ name: "Networks",
+ type: "NETWORK_TYPE",
+ },
+ {
+ key: "storage",
+ name: "Storage Deal Status",
+ width: "148px",
+ type: "STORAGE_DEAL_STATUS",
+ },
+];
+
+const STYLES_LINK = css`
+ font-family: ${Constants.font.semiBold};
+ font-weight: 400;
+ cursor: pointer;
+ transition: 200ms ease all;
+
+ :hover {
+ color: ${Constants.system.brand};
+ }
+`;
+
+const STYLES_LABEL = css`
+ letter-spacing: 0.1px;
+ font-size: 12px;
+ text-transform: uppercase;
+ font-family: ${Constants.font.semiBold};
+ font-weight: 400;
+ color: ${Constants.system.black};
+`;
+
+const STYLES_SECTION = css`
+ margin: 12px 0 16px 0;
+`;
+
+const delay = (ms) => new Promise((resolve) => window.setTimeout(resolve, ms));
+
+export default class DataView extends React.Component {
+ state = {
+ selectedRowId: null,
+ };
+
+ async componentDidMount() {
+ await this._handleUpdate();
+ }
+
+ _handleUpdate = async () => {
+ // NOTE(jim): Hack to handle some race conditions.
+ await delay(200);
+
+ System.dispatchCustomEvent({
+ name: "slate-global-create-carousel",
+ detail: {
+ slides: this.props.items.map((each) => {
+ const cid = each.ipfs.replace("/ipfs/", "");
+ return {
+ id: each.id,
+ cid,
+ component: ,
+ };
+ }),
+ },
+ });
+ };
+
+ _handleSelect = (index) => {
+ System.dispatchCustomEvent({
+ name: "slate-global-open-carousel",
+ detail: { index },
+ });
+ };
+
+ _handleMakeDeal = (data) => {
+ this.props.onAction({ type: "SIDEBAR", value: "SIDEBAR_FILE_STORAGE_DEAL", data });
+ };
+
+ _handleDelete = async (cid) => {
+ this.setState({ loading: true });
+ if (!window.confirm("Are you sure you want to delete this? It will be removed from your Slates too.")) {
+ this.setState({ loading: false });
+ return null;
+ }
+
+ const response = await Actions.deleteBucketItem({ cid });
+ console.log(response);
+
+ if (!response) {
+ this.setState({ loading: false });
+ alert("TODO: Broken response error");
+ return;
+ }
+
+ if (response.error) {
+ this.setState({ loading: false });
+ alert("TODO: Bucket delete error");
+ return;
+ }
+
+ await this.props.onRehydrate();
+ await this._handleUpdate();
+ this.setState({ loading: false });
+ };
+
+ _handleClick = (e) => {
+ this.setState({ [e.target.name]: e.target.value });
+ };
+
+ render() {
+ const columns = COLUMNS_SCHEMA;
+ const rows = this.props.items.map((each, index) => {
+ const cid = each.ipfs.replace("/ipfs/", "");
+ const isOnNetwork = each.networks && each.networks.includes("FILECOIN");
+
+ return {
+ ...each,
+ cid: (
+ this._handleSelect(index)}>
+ {cid}
+
+ ),
+ size: {Strings.bytesToSize(each.size)},
+ children: (
+
+
+ Actions
+
+ {this.state.loading || isOnNetwork ? null : (
+ this._handleMakeDeal(each)}>
+ Store on Filecoin
+
+ )}
+
+ this._handleDelete(cid)}>
+ Delete
+
+
+
+ {each.error ? (
+
+
+ Errors
+
+ {each.error}
+
+ ) : null}
+
+ ),
+ };
+ });
+
+ const data = {
+ columns,
+ rows,
+ };
+
+ return (
+
+ );
+ }
+}
diff --git a/components/system/components/GlobalCarousel.js b/components/system/components/GlobalCarousel.js
index e09c3fd4..edf4529b 100644
--- a/components/system/components/GlobalCarousel.js
+++ b/components/system/components/GlobalCarousel.js
@@ -11,6 +11,7 @@ const STYLES_BACKGROUND = css`
right: 0;
bottom: 0;
top: 0;
+ padding: 88px 24px 88px 24px;
width: 100%;
height: 100%;
display: flex;
@@ -60,6 +61,37 @@ const STYLES_BUTTON = css`
color: ${Constants.system.white};
cursor: pointer;
margin: auto;
+ text-decoration: none;
+
+ :hover {
+ background-color: ${Constants.system.black};
+ }
+`;
+
+const STYLES_LINK = css`
+ font-family: ${Constants.font.code};
+ font-size: 10px;
+ user-select: none;
+ height: 32px;
+ top: 8px;
+ left: 16px;
+ padding: 0 16px 0 16px;
+ border-radius: 32px;
+ position: absolute;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ z-index: ${Constants.zindex.modal};
+ background: ${Constants.system.pitchBlack};
+ transition: 200ms ease all;
+ color: ${Constants.system.white};
+ cursor: pointer;
+ margin: auto;
+ text-decoration: none;
+
+ :visited {
+ color: ${Constants.system.white};
+ }
:hover {
background-color: ${Constants.system.black};
@@ -110,13 +142,17 @@ export class GlobalCarousel extends React.Component {
_handleSetLoading = (e) => this.setState({ loading: e.detail.loading });
- _handleOpen = (e) => this.setState({ visible: true, index: e.detail.index || 0, loading: false });
+ _handleOpen = (e) => {
+ this.setState({ visible: true, index: e.detail.index || 0, loading: false });
+ };
_handleClose = () => this.setState({ visible: false, index: 0, loading: false });
_handleCreate = (e) => {
this.setState({
slides: e.detail.slides,
+ visible: false,
+ index: 0,
});
};
@@ -147,24 +183,25 @@ export class GlobalCarousel extends React.Component {
return (
+ {current.cid ? (
+
+ OPEN {current.cid}
+
+ ) : null}
{current.onDelete ? (
current.onDelete(current.id)} style={{ top: 56, right: 16 }}>
{this.state.loading ? : "Delete Object"}
) : null}
-
-
-
-
{current.component}
);
diff --git a/components/system/components/Table.js b/components/system/components/Table.js
index eb113ccf..dfaaa4ee 100644
--- a/components/system/components/Table.js
+++ b/components/system/components/Table.js
@@ -66,6 +66,7 @@ const STYLES_TABLE_ROW = css`
`;
const STYLES_TABLE_SELECTED_ROW = css`
+ background-color: ${Constants.system.foreground};
box-sizing: border-box;
display: block;
border-bottom: 1px solid ${Constants.system.gray};
diff --git a/components/system/components/fragments/TableComponents.js b/components/system/components/fragments/TableComponents.js
index 8b50c180..cccff065 100644
--- a/components/system/components/fragments/TableComponents.js
+++ b/components/system/components/fragments/TableComponents.js
@@ -70,9 +70,7 @@ const COMPONENTS_TRANSACTION_DIRECTION = {
const COMPONENTS_TRANSACTION_STATUS = {
"0": Qualified,
- "1": (
- Sealed On Filecoin
- ),
+ "1": Sealed On Filecoin,
"2": ,
};
@@ -170,10 +168,7 @@ export const TableColumn = (props) => {
) : null;
return (
-
+
{props.children}
@@ -186,14 +181,7 @@ export const TableColumn = (props) => {
// TODO(jim): We probably won't use this Table component for long.
// Once we have components for all the necessary flows. We will probably
// make bespoke components for each experience.
-export const TableContent = ({
- type,
- text,
- action,
- data = {},
- onNavigateTo,
- onAction,
-}) => {
+export const TableContent = ({ type, text, action, data = {}, onNavigateTo, onAction }) => {
const { status, online } = data;
if (text === null || text === undefined) {
@@ -202,23 +190,13 @@ export const TableContent = ({
switch (type) {
case "DEAL_CATEGORY":
- return (
- {text == 1 ? "Storage" : "Retrieval"}
- );
+ return {text == 1 ? "Storage" : "Retrieval"};
case "BUTTON":
- return (
- onAction({ type: "SIDEBAR", value: action, data })}
- >
- {text}
-
- );
+ return onAction({ type: "SIDEBAR", value: action, data })}>{text};
case "TRANSACTION_DIRECTION":
return COMPONENTS_TRANSACTION_DIRECTION[text];
case "TRANSACTION_STATUS":
- return (
- {COMPONENTS_TRANSACTION_STATUS[text]}
- );
+ return {COMPONENTS_TRANSACTION_STATUS[text]} ;
case "OBJECT_TYPE":
return COMPONENTS_OBJECT_TYPE(text);
case "ICON":
@@ -231,16 +209,12 @@ export const TableContent = ({
case "DEAL_STATUS_RETRIEVAL":
return RETRIEVAL_DEAL_STATES[`${text}`];
case "DEAL_STATUS":
- return data["deal_category"] === 1
- ? STORAGE_DEAL_STATES[`${text}`]
- : RETRIEVAL_DEAL_STATES[`${text}`];
+ return data["deal_category"] === 1 ? STORAGE_DEAL_STATES[`${text}`] : RETRIEVAL_DEAL_STATES[`${text}`];
case "STORAGE_DEAL_STATUS":
return (
{COMPONENTS_TRANSACTION_STATUS[`${text}`]}
- {data.error ? (
- Failed Deal
- ) : null}
+ {data.error ? Previously Failed : null}
);
case "BANDWIDTH_UPLOAD":
@@ -258,15 +232,9 @@ export const TableContent = ({
);
case "MINER_AVAILABILITY":
- return text == 1 ? (
- Online
- ) : null;
+ return text == 1 ? Online : null;
case "DEAL_AUTO_RENEW":
- return text == 1 ? (
- True
- ) : (
- False
- );
+ return text == 1 ? True : False;
case "NOTIFICATION_ERROR":
return (
@@ -275,18 +243,10 @@ export const TableContent = ({
);
case "NETWORK_TYPE":
return text.map((each) => {
- return (
-
- {each}
-
- );
+ return {each};
});
case "SLATE_PUBLIC_TEXT_TAG":
- return !text ? (
- Private
- ) : (
- Public
- );
+ return !text ? Private : Public;
case "TEXT_TAG":
return {text};
case "FILE_DATE":
@@ -306,9 +266,7 @@ export const TableContent = ({
return text;
}
- return (
- onNavigateTo({ id: data.id }, data)}>{text}
- );
+ return onNavigateTo({ id: data.id }, data)}>{text};
case "FILE_LINK":
if (!data) {
return text;
diff --git a/pages/api/data/get.js b/pages/api/data/get.js
index 197798fe..74b5997a 100644
--- a/pages/api/data/get.js
+++ b/pages/api/data/get.js
@@ -17,20 +17,14 @@ export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
- return res
- .status(500)
- .json({ decorator: "SERVER_DELETE_SLATE", error: true });
+ return res.status(500).json({ decorator: "SERVER_GET_BUCKET_DATA", error: true });
}
const user = await Data.getUserById({
id,
});
- const {
- buckets,
- bucketKey,
- bucketName,
- } = await Utilities.getBucketAPIFromUserToken(user.data.tokens.api);
+ const { buckets, bucketKey, bucketName } = await Utilities.getBucketAPIFromUserToken(user.data.tokens.api);
const r = await buckets.list();
const items = await buckets.listIpfsPath(r[0].path);
diff --git a/pages/api/data/remove.js b/pages/api/data/remove.js
new file mode 100644
index 00000000..5e3ea590
--- /dev/null
+++ b/pages/api/data/remove.js
@@ -0,0 +1,91 @@
+import * as MW from "~/node_common/middleware";
+import * as Data from "~/node_common/data";
+import * as Utilities from "~/node_common/utilities";
+import * as Strings from "~/common/strings";
+
+const initCORS = MW.init(MW.CORS);
+const initAuth = MW.init(MW.RequireCookieAuthentication);
+
+export default async (req, res) => {
+ initCORS(req, res);
+ initAuth(req, res);
+
+ if (Strings.isEmpty(req.body.data.cid)) {
+ return res.status(500).json({ decorator: "SERVER_REMOVE_DATA_NO_CID", error: true });
+ }
+
+ const id = Utilities.getIdFromCookie(req);
+ if (!id) {
+ return res.status(403).json({ 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 entity;
+ for (let i = 0; i < items.itemsList.length; i++) {
+ if (items.itemsList[i].cid === req.body.data.cid) {
+ entity = items.itemsList[i];
+ break;
+ }
+ }
+
+ if (!entity) {
+ return res.status(500).json({ decorator: "SERVER_REMOVE_DATA_NO_CID", error: true });
+ }
+
+ // remove from your bucket
+ let bucketRemoval;
+ 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);
+ return res.status(500).json({ decorator: "SERVER_REMOVE_DATA_NO_LINK", error: true });
+ }
+
+ // 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++) {
+ const slate = slates[i];
+
+ await Data.updateSlateById({
+ id: slate.id,
+ updated_at: new Date(),
+ data: {
+ ...slate.data,
+ objects: slate.data.objects.filter((o) => !o.url.includes(req.body.data.cid)),
+ },
+ });
+ }
+
+ // 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) => !o.ipfs.includes(req.body.data.cid)),
+ },
+ ],
+ },
+ });
+
+ return res.status(200).send({
+ decorator: "SERVER_REMOVE_DATA",
+ success: true,
+ bucketItems: items.itemsList,
+ });
+};
diff --git a/scenes/SceneFilesFolder.js b/scenes/SceneFilesFolder.js
index c3a307a2..92f60345 100644
--- a/scenes/SceneFilesFolder.js
+++ b/scenes/SceneFilesFolder.js
@@ -1,12 +1,11 @@
import * as React from "react";
import * as Actions from "~/common/actions";
import * as System from "~/components/system";
-import * as Strings from "~/common/strings";
import { css } from "@emotion/react";
-import Section from "~/components/core/Section";
import ScenePage from "~/components/core/ScenePage";
+import DataView from "~/components/core/DataView";
import DataMeter from "~/components/core/DataMeter";
const POLLING_INTERVAL = 10000;
@@ -62,50 +61,6 @@ export default class SceneFilesFolder extends React.Component {
}
render() {
- let rows = this.props.viewer.library[0].children.map((each) => {
- return {
- ...each,
- button: each.networks && each.networks.includes("FILECOIN") ? null : "Store on Filecoin",
- };
- });
-
- const data = {
- columns: [
- { key: "name", name: "Data", type: "FILE_LINK", width: "328px" },
- {
- key: "size",
- name: "Size",
- width: "84px",
- type: "FILE_SIZE",
- },
- { key: "type", name: "Type", type: "TEXT_TAG", width: "172px" },
- {
- key: "networks",
- name: "Networks",
- type: "NETWORK_TYPE",
- },
- {
- key: "storage",
- name: "Storage Deal Status",
- width: "148px",
- type: "STORAGE_DEAL_STATUS",
- },
- {
- key: "button",
- hideLabel: true,
- type: "BUTTON",
- action: "SIDEBAR_FILE_STORAGE_DEAL",
- width: "132px",
- },
- {
- key: "error",
- hideLabel: true,
- width: "188px",
- },
- ],
- rows,
- };
-
return (
{this.props.current.name}
-
+ ]}
+ viewer={this.props.viewer}
+ items={this.props.viewer.library[0].children}
+ onAction={this.props.onAction}
+ onRehydrate={this.props.onRehydrate}
+ />
);
}
diff --git a/scenes/SceneHome.js b/scenes/SceneHome.js
index 9afd5768..bf1d1aee 100644
--- a/scenes/SceneHome.js
+++ b/scenes/SceneHome.js
@@ -6,17 +6,9 @@ import { css } from "@emotion/react";
import Section from "~/components/core/Section";
import ScenePage from "~/components/core/ScenePage";
+import DataView from "~/components/core/DataView";
export default class SceneHome extends React.Component {
- state = {
- data: null,
- transaction: null,
- };
-
- _handleChange = (e) => {
- this.setState({ [e.target.name]: e.target.value });
- };
-
render() {
// TODO(jim): Refactor later.
const slates = {
@@ -46,55 +38,7 @@ export default class SceneHome extends React.Component {
};
// TODO(jim): Refactor later.
- const slateButtons = [
- { name: "Create slate", type: "SIDEBAR", value: "SIDEBAR_CREATE_SLATE" },
- ];
-
- // TODO(jim): Refactor later.
- const data = {
- columns: [
- { key: "name", name: "Data", type: "FILE_LINK", width: "328px" },
- {
- key: "size",
- name: "Size",
- width: "140px",
- type: "FILE_SIZE",
- },
- { key: "type", name: "Type", type: "TEXT_TAG", width: "172px" },
- {
- key: "date",
- name: "Date uploaded",
- width: "160px",
- type: "FILE_DATE",
- },
- {
- key: "networks",
- name: "Network",
- type: "NETWORK_TYPE",
- width: "100%",
- },
- ],
- rows: this.props.viewer.library[0].children.map((each) => {
- return {
- ...each,
- button: "Store on Filecoin",
- };
- }),
- };
-
- // TODO(jim): Refactor later.
- const dataButtons = [
- {
- name: "View files",
- type: "NAVIGATE",
- value: this.props.viewer.library[0].id,
- },
- {
- name: "Upload data",
- type: "SIDEBAR",
- value: "SIDEBAR_ADD_FILE_TO_BUCKET",
- },
- ];
+ const slateButtons = [{ name: "Create slate", type: "SIDEBAR", value: "SIDEBAR_CREATE_SLATE" }];
// TODO(jim): Refactor later.
const wallet = {
@@ -122,12 +66,9 @@ export default class SceneHome extends React.Component {
description="No! Consider this page just a functionality test. Home will have Filecoin network analytics and updates from the people you engage with."
/>
Home
+
{this.props.viewer.addresses[0] ? (
-
+
) : null}
-
+
{this.props.viewer.library[0] ? (
-
+ onRehydrate={this.props.onRehydrate}
+ />
) : null}
);