mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-27 10:52:41 +03:00
Merge pull request #378 from filecoin-project/@martinalong/new-grid-layout
@martinalong/new grid layout
This commit is contained in:
commit
516035a4ba
@ -126,6 +126,13 @@ export const search = async (data) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const createPendingFiles = async (data) => {
|
||||
return await returnJSON(`/api/data/create-pending`, {
|
||||
...DEFAULT_OPTIONS,
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
};
|
||||
|
||||
export const processPendingFiles = async (data) => {
|
||||
return await returnJSON(`/api/data/process-pending`, {
|
||||
...DEFAULT_OPTIONS,
|
||||
@ -221,15 +228,15 @@ export const deleteAPIKey = async (data) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteBucketItem = async (data) => {
|
||||
return await returnJSON(`/api/data/remove`, {
|
||||
export const addCIDToData = async (data) => {
|
||||
return await returnJSON(`/api/data/add`, {
|
||||
...DEFAULT_OPTIONS,
|
||||
body: JSON.stringify({ data }),
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteBucketItems = async (data) => {
|
||||
return await returnJSON(`/api/data/remove-multiple`, {
|
||||
return await returnJSON(`/api/data/remove`, {
|
||||
...DEFAULT_OPTIONS,
|
||||
body: JSON.stringify({ data }),
|
||||
});
|
||||
|
@ -16,7 +16,7 @@ export const sizes = {
|
||||
export const system = {
|
||||
white: "#ffffff",
|
||||
foreground: "#f8f8f8",
|
||||
gray: "#e0e0e0",
|
||||
gray: "#e5e5e5",
|
||||
lightBorder: "#ececec",
|
||||
border: "#d8d8d8",
|
||||
darkGray: "#b2b2b2",
|
||||
@ -42,6 +42,7 @@ export const system = {
|
||||
|
||||
export const zindex = {
|
||||
navigation: 1,
|
||||
body: 2,
|
||||
sidebar: 5,
|
||||
alert: 3,
|
||||
header: 4,
|
||||
|
@ -1,10 +1,36 @@
|
||||
import * as Actions from "~/common/actions";
|
||||
import * as Store from "~/common/store";
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
import { dispatchCustomEvent } from "~/common/custom-events";
|
||||
import { encode } from "blurhash";
|
||||
|
||||
const STAGING_DEAL_BUCKET = "stage-deal";
|
||||
|
||||
const loadImage = async (src) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.crossOrigin = "Anonymous";
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = (...args) => reject(args);
|
||||
img.src = src;
|
||||
});
|
||||
|
||||
const getImageData = (image) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
const context = canvas.getContext("2d");
|
||||
context.drawImage(image, 0, 0);
|
||||
return context.getImageData(0, 0, image.width, image.height);
|
||||
};
|
||||
|
||||
const encodeImageToBlurhash = async (imageUrl) => {
|
||||
const image = await loadImage(imageUrl);
|
||||
const imageData = getImageData(image);
|
||||
return encode(imageData.data, imageData.width, imageData.height, 4, 4);
|
||||
};
|
||||
|
||||
export const upload = async ({ file, context, bucketName }) => {
|
||||
let formData = new FormData();
|
||||
const HEIC2ANY = require("heic2any");
|
||||
@ -91,15 +117,15 @@ export const upload = async ({ file, context, bucketName }) => {
|
||||
XHR.send(formData);
|
||||
});
|
||||
|
||||
let json;
|
||||
let res;
|
||||
// TODO(jim): Make this smarter.
|
||||
if (bucketName && bucketName === STAGING_DEAL_BUCKET) {
|
||||
json = await _privateUploadMethod(`/api/data/deal/${file.name}`, file);
|
||||
res = await _privateUploadMethod(`/api/data/deal/${file.name}`, file);
|
||||
} else {
|
||||
json = await _privateUploadMethod(`/api/data/${file.name}`, file);
|
||||
res = await _privateUploadMethod(`/api/data/${file.name}`, file);
|
||||
}
|
||||
|
||||
if (!json || json.error || !json.data) {
|
||||
if (!res || res.error || !res.data) {
|
||||
if (context) {
|
||||
context.setState({
|
||||
fileLoading: {
|
||||
@ -118,10 +144,20 @@ export const upload = async ({ file, context, bucketName }) => {
|
||||
},
|
||||
});
|
||||
|
||||
return !json ? { error: "NO_RESPONSE" } : json;
|
||||
return !res ? { error: "NO_RESPONSE" } : res;
|
||||
}
|
||||
|
||||
return { file, json };
|
||||
if (res.data.data.type.startsWith("image/")) {
|
||||
let url = `${Constants.gateways.ipfs}/${res.data.data.cid}`;
|
||||
let blurhash = await encodeImageToBlurhash(url);
|
||||
res.data.data.blurhash = blurhash;
|
||||
}
|
||||
|
||||
await Actions.createPendingFiles({ data: res.data });
|
||||
|
||||
res.data = res.data.data;
|
||||
|
||||
return { file, json: res };
|
||||
};
|
||||
|
||||
export const uploadToSlate = async ({ responses, slate }) => {
|
||||
|
@ -114,6 +114,8 @@ export const error = {
|
||||
"We can't seem to find your API key right now. Please try again later",
|
||||
CREATE_PENDING_DATA:
|
||||
"We ran into issues while uploading your data, please try again later",
|
||||
CREATE_PENDING_ERROR:
|
||||
"We ran into issues while uploading your data, please try again later",
|
||||
PROCESS_PENDING_ERROR:
|
||||
"We ran into an error while updating your uploaded data. Please try again later",
|
||||
|
||||
|
@ -175,6 +175,15 @@ export const getRemainingTime = (seconds) => {
|
||||
return `${value} ${unit} remaining`;
|
||||
};
|
||||
|
||||
export const urlToCid = (url) => {
|
||||
return url
|
||||
.replace(`${Constants.gateways.ipfs}/`, "")
|
||||
.replace("https://undefined", "")
|
||||
.replace("https://", "")
|
||||
.replace(".ipfs.slate.textile.io", "")
|
||||
.replace("hub.textile.io/ipfs/", "");
|
||||
};
|
||||
|
||||
export const hexToRGBA = (hex, alpha = 1) => {
|
||||
hex = hex.replace("#", "");
|
||||
var r = parseInt(hex.length == 3 ? hex.slice(0, 1).repeat(2) : hex.slice(0, 2), 16);
|
||||
|
300
common/svg.js
300
common/svg.js
@ -37,6 +37,63 @@ export const Directory = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const ResizeHandle = (props) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
>
|
||||
<path d="M21 3L3 21" />
|
||||
<path d="M21 11L11 21" />
|
||||
<path d="M21 19L19 21" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const Undo = (props) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
>
|
||||
<path d="M1 4V10H7" />
|
||||
<path d="M3.51 15C4.15839 16.8404 5.38734 18.4202 7.01166 19.5014C8.63598 20.5826 10.5677 21.1066 12.5157 20.9945C14.4637 20.8824 16.3226 20.1402 17.8121 18.8798C19.3017 17.6193 20.3413 15.909 20.7742 14.0064C21.2072 12.1037 21.0101 10.112 20.2126 8.3311C19.4152 6.55025 18.0605 5.0768 16.3528 4.13277C14.6451 3.18874 12.6769 2.82527 10.7447 3.09712C8.81245 3.36897 7.02091 4.26142 5.64 5.64L1 10" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const Edit = (props) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
>
|
||||
<path d="M17 3C17.2626 2.73735 17.5744 2.52901 17.9176 2.38687C18.2608 2.24473 18.6286 2.17157 19 2.17157C19.3714 2.17157 19.7392 2.24473 20.0824 2.38687C20.4256 2.52901 20.7374 2.73735 21 3C21.2626 3.26264 21.471 3.57444 21.6131 3.9176C21.7553 4.26077 21.8284 4.62856 21.8284 5C21.8284 5.37143 21.7553 5.73923 21.6131 6.08239C21.471 6.42555 21.2626 6.73735 21 7L7.5 20.5L2 22L3.5 16.5L17 3Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const PlusCircle = (props) => {
|
||||
return (
|
||||
<svg
|
||||
@ -168,12 +225,7 @@ export const SettingsDeveloper = (props) => {
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m18.25 2.251c-1.061 1.061-3.04.96-4.75-.75l-1.5 1.499 8 8 3.5-3.5z" />
|
||||
<path d="m11.939 9.94-3.793-3.792.00000007-.00000015c.910118-2.01295.0160937-4.38257-1.99686-5.29269-1.04732-.473526-2.24773-.473639-3.29514-.00031106l2.146 2.145v2h-2l-2.146-2.145.00000008-.00000019c-.910118 2.01295-.0160941 4.38257 1.99686 5.29269 1.04732.473526 2.24773.47364 3.29514.00031136l3.793 3.792" />
|
||||
<path d="m12.061 14.062 3.793 3.793.00000008-.00000019c-.910118 2.01295-.0160941 4.38257 1.99686 5.29269 1.04732.473526 2.24773.47364 3.29514.00031136l-2.146-2.148v-2h2l2.146 2.147.00000007-.00000015c.910118-2.01295.0160937-4.38257-1.99686-5.29269-1.04732-.473526-2.24773-.473639-3.29514-.00031106l-3.793-3.793" />
|
||||
@ -209,12 +261,7 @@ export const ProfileUser = (props) => {
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m1.9 17.5h10.12" />
|
||||
<path d="m2.51 5.5h18.99" />
|
||||
<path d="m12 11.5h-11.49" />
|
||||
@ -235,12 +282,7 @@ export const Slates = (props) => (
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m17.42 17.27-2.787-8.02-3.42 6-2.138-3-2.476 5.048" />
|
||||
<path d="m10.7071 8.29289c.390524.390524.390524 1.02369 0 1.41421-.390524.390524-1.02369.390524-1.41421 0-.390524-.390524-.390524-1.02369 0-1.41421.390524-.390524 1.02369-.390524 1.41421 0" />
|
||||
<path d="m1.5 6.5h-.00000004c-.552285-.00000002-1-.447715-1-1v-1 .00000015c-.00000008-.552285.447715-1 1-1h2v-2 .00000015c-.00000008-.552285.447715-1 1-1h1-.00000004c.552285-.00000002 1 .447715 1 1v2h13-.00000004c.552285-.00000002 1 .447715 1 1v13h2-.00000004c.552285-.00000002 1 .447715 1 1v1c0 .552285-.447715 1-1 1h-2v2c0 .552285-.447715 1-1 1h-1-.00000004c-.552285-.00000002-1-.447715-1-1v-2h-13-.00000004c-.552285-.00000002-1-.447715-1-1v-13z" />
|
||||
@ -413,12 +455,7 @@ export const Home = (props) => (
|
||||
|
||||
export const Channels = (props) => (
|
||||
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="16.004" cy="8" r="7.5" />
|
||||
<path d="m8.53 8.526a7.5 7.5 0 1 0 6.948 6.948" />
|
||||
<path d="m7.504 13.5v-1" />
|
||||
@ -433,12 +470,7 @@ export const Channels = (props) => (
|
||||
|
||||
export const Peers = (props) => (
|
||||
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m17 7.02 3.11-3.09" />
|
||||
<path d="m22.9142 1.08579c.781049.781049.781049 2.04738 0 2.82843-.781049.781049-2.04738.781049-2.82843 0-.781049-.781049-.781049-2.04738 0-2.82843.781049-.781049 2.04738-.781049 2.82843 0" />
|
||||
<path d="m17.96 17.98 2.12 2.13" />
|
||||
@ -478,12 +510,7 @@ export const Deals = (props) => (
|
||||
|
||||
export const DataTransfer = (props) => (
|
||||
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m20.5 14.406a4.311 4.311 0 0 0 2.5-4.049 4.711 4.711 0 0 0 -4.954-4.635 6.706 6.706 0 0 0 -6.046-3.722 6.605 6.605 0 0 0 -6.675 6.109 3.561 3.561 0 0 0 -4.325 3.409 3.186 3.186 0 0 0 2.5 3.282" />
|
||||
<path d="m6 19 3 3 3-3" />
|
||||
<path d="m9 22v-9" />
|
||||
@ -507,12 +534,7 @@ export const Stats = (props) => (
|
||||
|
||||
export const Logs = (props) => (
|
||||
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m8.5 20.5h-7a1 1 0 0 1 -1-1v-16a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5" />
|
||||
<path d="m4.5 4.5v-4" />
|
||||
<path d="m8.5 4.5v-4" />
|
||||
@ -526,12 +548,7 @@ export const Logs = (props) => (
|
||||
|
||||
export const Status = (props) => (
|
||||
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m14.061 5.243a1.5 1.5 0 0 1 0 2.121" />
|
||||
<path d="m16.182 3.121a4.5 4.5 0 0 1 0 6.364" />
|
||||
<path d="m16.182 3.121a4.5 4.5 0 0 1 0 6.364" />
|
||||
@ -570,12 +587,7 @@ export const Miners = (props) => (
|
||||
|
||||
export const StorageMarket = (props) => (
|
||||
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m23.5 22h-22.5a.5.5 0 0 1 -.5-.5v-19.5" />
|
||||
<path d="m12.872 15.523c.182 1 .458 3.477 3.128 3.477" />
|
||||
<path d="m3 19a3 3 0 0 0 2.947-2.46l1.2-6.571a2.4 2.4 0 0 1 3.8-1.487" />
|
||||
@ -655,7 +667,7 @@ export const Trash = (props) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Download = (props) => (
|
||||
export const DownloadCircle = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height={props.height}
|
||||
@ -937,12 +949,7 @@ export const FileImage = (props) => (
|
||||
style={props.style}
|
||||
{...props}
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m21.207 4.5-.00000002-.00000002c.187549.187493.292943.441805.293.707v17.293c0 .552285-.447715 1-1 1h-17-.00000004c-.552285-.00000002-1-.447715-1-1v-21 .00000015c-.00000008-.552285.447715-1 1-1h13.293.00000001c.265195.00005664.519507.105451.707.293z" />
|
||||
<path d="m12.826 12.366-2.8-3.74.00000001.00000002c-.165798-.22083-.479221-.265442-.700051-.0996437-.0578698.0434484-.105619.0989405-.139949.162644l-3.276 6.074.00000001-.00000002c-.130892.24315-.0398879.546371.203262.677262.0727636.0391698.154101.0596942.236738.0597376h4.181" />
|
||||
<path d="m17.3284 13.1716c1.5621 1.5621 1.5621 4.09476 0 5.65685-1.5621 1.5621-4.09476 1.5621-5.65685 0-1.5621-1.5621-1.5621-4.09476 0-5.65685 1.5621-1.5621 4.09476-1.5621 5.65685 0" />
|
||||
@ -1302,7 +1309,27 @@ export const GridView = (props) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ListView = (props) => (
|
||||
export const TableView = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M8 6H21" />
|
||||
<path d="M8 12H21" />
|
||||
<path d="M8 18H21" />
|
||||
<path d="M3 6H3.01" />
|
||||
<path d="M3 12H3.01" />
|
||||
<path d="M3 18H3.01" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const FeedView = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
@ -1317,3 +1344,152 @@ export const ListView = (props) => (
|
||||
<path d="M21 14H3V21H21V14Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Upload = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M21 15V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V15" />
|
||||
<path d="M17 8L12 3L7 8" />
|
||||
<path d="M12 3V15" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Download = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M21 15V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V15" />
|
||||
<path d="M7 10L12 15L17 10" />
|
||||
<path d="M12 15V3" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Save = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H16L21 8V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21Z" />
|
||||
<path d="M17 21V13H7V21" />
|
||||
<path d="M7 3V8H15" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const DismissCircle = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" />
|
||||
<path d="M15 9L9 15" />
|
||||
<path d="M9 9L15 15" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const DragHandle = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M15.5 21H19C19.5304 21 20.0391 20.7893 20.4142 20.4142C20.7893 20.0391 21 19.5304 21 19V15.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const FileNotFound = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M13 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V9L13 2Z" />
|
||||
<path d="M13 2V9H20" />
|
||||
<path d="M14 16C14 16 13.25 15 12 15C10.75 15 10 16 10 16" strokeWidth="1" />
|
||||
<path d="M10.5 12.5H10.505" strokeWidth="1" />
|
||||
<path d="M13.5 12.5H13.505" strokeWidth="1" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Desktop = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.3333"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M13.3334 2H2.66671C1.93033 2 1.33337 2.59695 1.33337 3.33333V10C1.33337 10.7364 1.93033 11.3333 2.66671 11.3333H13.3334C14.0698 11.3333 14.6667 10.7364 14.6667 10V3.33333C14.6667 2.59695 14.0698 2 13.3334 2Z" />
|
||||
<path d="M5.33337 14H10.6667" />
|
||||
<path d="M8 11.3335V14.0002" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const DesktopEye = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
// stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.3333"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M13.3334 2H2.66671C1.93033 2 1.33337 2.59695 1.33337 3.33333V10C1.33337 10.7364 1.93033 11.3333 2.66671 11.3333H13.3334C14.0698 11.3333 14.6667 10.7364 14.6667 10V3.33333C14.6667 2.59695 14.0698 2 13.3334 2Z"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
<path d="M5.33337 14H10.6667" stroke="currentColor" />
|
||||
<path d="M8 11.3335V14.0002" stroke="currentColor" stroke-width="1.33333" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.00004 4.0835C5.66671 4.0835 4.33337 6.75016 4.33337 6.75016C4.33337 6.75016 5.66671 9.41683 8.00004 9.41683C10.3334 9.41683 11.6667 6.75016 11.6667 6.75016C11.6667 6.75016 10.3334 4.0835 8.00004 4.0835ZM7.5 6.75C7.5 6.47386 7.72386 6.25 8 6.25C8.27614 6.25 8.5 6.47386 8.5 6.75C8.5 7.02614 8.27614 7.25 8 7.25C7.72386 7.25 7.5 7.02614 7.5 6.75ZM8 5.25C7.17157 5.25 6.5 5.92157 6.5 6.75C6.5 7.57843 7.17157 8.25 8 8.25C8.82843 8.25 9.5 7.57843 9.5 6.75C9.5 5.92157 8.82843 5.25 8 5.25Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M4.33337 6.75016L4.10977 6.63836C4.07458 6.70874 4.07458 6.79158 4.10977 6.86197L4.33337 6.75016ZM11.6667 6.75016L11.8903 6.86197C11.9255 6.79158 11.9255 6.70874 11.8903 6.63836L11.6667 6.75016ZM4.33337 6.75016C4.55698 6.86197 4.55695 6.86203 4.55692 6.86209C4.55692 6.86209 4.55689 6.86214 4.55689 6.86215C4.55688 6.86217 4.55689 6.86215 4.55692 6.8621C4.55698 6.86198 4.55711 6.8617 4.55733 6.86127C4.55777 6.86041 4.55853 6.85892 4.55961 6.85682C4.56176 6.85263 4.56519 6.84602 4.56989 6.83714C4.57928 6.8194 4.59372 6.79264 4.61312 6.75814C4.65194 6.68913 4.71052 6.58937 4.78818 6.46913C4.9438 6.22817 5.17437 5.90751 5.47409 5.58781C6.07809 4.94355 6.93325 4.3335 8.00004 4.3335V3.8335C6.7335 3.8335 5.75533 4.55677 5.10932 5.24584C4.78404 5.59281 4.53545 5.93882 4.36816 6.19786C4.28435 6.32763 4.22054 6.4362 4.17733 6.51301C4.15572 6.55144 4.13923 6.58197 4.12795 6.60329C4.12231 6.61395 4.11797 6.62231 4.11493 6.62821C4.11342 6.63116 4.11223 6.63349 4.11137 6.63519C4.11093 6.63604 4.11059 6.63673 4.11032 6.63726C4.11019 6.63752 4.11007 6.63775 4.10998 6.63793C4.10994 6.63802 4.10988 6.63813 4.10986 6.63818C4.10981 6.63827 4.10977 6.63836 4.33337 6.75016ZM8.00004 9.16683C6.93325 9.16683 6.07809 8.55677 5.47409 7.91251C5.17437 7.59281 4.9438 7.27216 4.78818 7.0312C4.71052 6.91096 4.65194 6.8112 4.61312 6.74218C4.59372 6.70769 4.57928 6.68093 4.56989 6.66318C4.56519 6.65431 4.56176 6.6477 4.55961 6.64351C4.55853 6.64141 4.55777 6.63992 4.55733 6.63906C4.55711 6.63862 4.55698 6.63835 4.55692 6.63823C4.55689 6.63817 4.55688 6.63815 4.55689 6.63817C4.55689 6.63819 4.55692 6.63823 4.55692 6.63824C4.55695 6.63829 4.55698 6.63836 4.33337 6.75016C4.10977 6.86197 4.10981 6.86205 4.10986 6.86215C4.10988 6.8622 4.10994 6.8623 4.10998 6.86239C4.11007 6.86258 4.11019 6.8628 4.11032 6.86307C4.11059 6.8636 4.11093 6.86429 4.11137 6.86514C4.11223 6.86683 4.11342 6.86917 4.11493 6.87212C4.11797 6.87802 4.12231 6.88638 4.12795 6.89704C4.13923 6.91836 4.15572 6.94889 4.17733 6.98731C4.22054 7.06413 4.28435 7.1727 4.36816 7.30246C4.53545 7.5615 4.78404 7.90751 5.10932 8.25448C5.75533 8.94355 6.7335 9.66683 8.00004 9.66683V9.16683ZM11.6667 6.75016C11.4431 6.63836 11.4431 6.63829 11.4432 6.63824C11.4432 6.63823 11.4432 6.63819 11.4432 6.63817C11.4432 6.63815 11.4432 6.63817 11.4432 6.63823C11.4431 6.63835 11.443 6.63862 11.4427 6.63906C11.4423 6.63992 11.4416 6.64141 11.4405 6.64351C11.4383 6.6477 11.4349 6.65431 11.4302 6.66318C11.4208 6.68093 11.4064 6.70769 11.387 6.74218C11.3481 6.8112 11.2896 6.91096 11.2119 7.0312C11.0563 7.27216 10.8257 7.59281 10.526 7.91251C9.92199 8.55677 9.06683 9.16683 8.00004 9.16683V9.66683C9.26658 9.66683 10.2448 8.94355 10.8908 8.25448C11.216 7.90751 11.4646 7.5615 11.6319 7.30246C11.7157 7.1727 11.7795 7.06413 11.8228 6.98731C11.8444 6.94889 11.8609 6.91836 11.8721 6.89704C11.8778 6.88638 11.8821 6.87802 11.8851 6.87212C11.8867 6.86917 11.8879 6.86683 11.8887 6.86514C11.8891 6.86429 11.8895 6.8636 11.8898 6.86307C11.8899 6.8628 11.89 6.86258 11.8901 6.86239C11.8901 6.8623 11.8902 6.8622 11.8902 6.86215C11.8903 6.86205 11.8903 6.86197 11.6667 6.75016ZM8.00004 4.3335C9.06683 4.3335 9.92199 4.94355 10.526 5.58781C10.8257 5.90751 11.0563 6.22817 11.2119 6.46913C11.2896 6.58937 11.3481 6.68913 11.387 6.75814C11.4064 6.79264 11.4208 6.8194 11.4302 6.83714C11.4349 6.84602 11.4383 6.85263 11.4405 6.85682C11.4416 6.85892 11.4423 6.86041 11.4427 6.86127C11.443 6.8617 11.4431 6.86198 11.4432 6.8621C11.4432 6.86215 11.4432 6.86217 11.4432 6.86215C11.4432 6.86214 11.4432 6.86209 11.4432 6.86209C11.4431 6.86203 11.4431 6.86197 11.6667 6.75016C11.8903 6.63836 11.8903 6.63827 11.8902 6.63818C11.8902 6.63813 11.8901 6.63802 11.8901 6.63793C11.89 6.63775 11.8899 6.63752 11.8898 6.63726C11.8895 6.63673 11.8891 6.63604 11.8887 6.63519C11.8879 6.63349 11.8867 6.63116 11.8851 6.62821C11.8821 6.62231 11.8778 6.61395 11.8721 6.60329C11.8609 6.58197 11.8444 6.55144 11.8228 6.51301C11.7795 6.4362 11.7157 6.32763 11.6319 6.19786C11.4646 5.93882 11.216 5.59281 10.8908 5.24584C10.2448 4.55677 9.26658 3.8335 8.00004 3.8335V4.3335ZM8 6C7.58579 6 7.25 6.33579 7.25 6.75H7.75C7.75 6.61193 7.86193 6.5 8 6.5V6ZM8.75 6.75C8.75 6.33579 8.41421 6 8 6V6.5C8.13807 6.5 8.25 6.61193 8.25 6.75H8.75ZM8 7.5C8.41421 7.5 8.75 7.16421 8.75 6.75H8.25C8.25 6.88807 8.13807 7 8 7V7.5ZM7.25 6.75C7.25 7.16421 7.58579 7.5 8 7.5V7C7.86193 7 7.75 6.88807 7.75 6.75H7.25ZM6.75 6.75C6.75 6.05964 7.30964 5.5 8 5.5V5C7.0335 5 6.25 5.7835 6.25 6.75H6.75ZM8 8C7.30964 8 6.75 7.44036 6.75 6.75H6.25C6.25 7.7165 7.0335 8.5 8 8.5V8ZM9.25 6.75C9.25 7.44036 8.69036 8 8 8V8.5C8.9665 8.5 9.75 7.7165 9.75 6.75H9.25ZM8 5.5C8.69036 5.5 9.25 6.05964 9.25 6.75H9.75C9.75 5.7835 8.9665 5 8 5V5.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
@ -53,12 +53,10 @@ const REJECT_LIST = [
|
||||
"please-dont-use-timeout",
|
||||
];
|
||||
|
||||
// TODO(martina): Make sure you catch cases where this isn't passed in to be safe.
|
||||
export const onMobile = () => {
|
||||
return false; //TODO(martina): make a function that works
|
||||
if (!navigator) return;
|
||||
export const onMobile = (userAgent) => {
|
||||
if (!userAgent) return;
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
navigator.userAgent
|
||||
userAgent
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,7 @@ import * as Actions from "~/common/actions";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as State from "~/common/state";
|
||||
import * as Credentials from "~/common/credentials";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as Validations from "~/common/validations";
|
||||
import * as FileUtilities from "~/common/file-utilities";
|
||||
import * as System from "~/components/system";
|
||||
@ -316,6 +317,7 @@ export default class ApplicationPage extends React.Component {
|
||||
return res.status === "fulfilled" && res.value && !res.value.error;
|
||||
})
|
||||
.map((res) => res.value);
|
||||
console.log(succeeded);
|
||||
if (slate && slate.id) {
|
||||
await FileUtilities.uploadToSlate({ responses: succeeded, slate });
|
||||
}
|
||||
@ -764,6 +766,7 @@ export default class ApplicationPage extends React.Component {
|
||||
navigation={navigation}
|
||||
onAction={this._handleAction}
|
||||
onSignOut={this._handleSignOut}
|
||||
mobile={this.props.mobile}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -778,6 +781,7 @@ export default class ApplicationPage extends React.Component {
|
||||
onBack={this._handleBack}
|
||||
onForward={this._handleForward}
|
||||
onSignOut={this._handleSignOut}
|
||||
mobile={this.props.mobile}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -795,6 +799,7 @@ export default class ApplicationPage extends React.Component {
|
||||
onForward: this._handleForward,
|
||||
onRehydrate: this.rehydrate,
|
||||
sceneId: current.target.id,
|
||||
mobile: this.props.mobile,
|
||||
});
|
||||
|
||||
let sidebarElement;
|
||||
@ -833,11 +838,16 @@ export default class ApplicationPage extends React.Component {
|
||||
onDismissSidebar={this._handleDismissSidebar}
|
||||
fileLoading={this.state.fileLoading}
|
||||
filecoin={current.target.filecoin}
|
||||
mobile={this.props.mobile}
|
||||
>
|
||||
{scene}
|
||||
</ApplicationLayout>
|
||||
<GlobalViewerCID onRehydrate={this.rehydrate} onAction={this._handleAction} />
|
||||
<System.GlobalCarousel />
|
||||
<GlobalViewerCID
|
||||
onRehydrate={this.rehydrate}
|
||||
onAction={this._handleAction}
|
||||
mobile={this.props.mobile}
|
||||
/>
|
||||
<System.GlobalCarousel mobile={this.props.mobile} />
|
||||
<System.GlobalModal />
|
||||
</WebsitePrototypeWrapper>
|
||||
</React.Fragment>
|
||||
|
@ -110,6 +110,7 @@ const STYLES_NAVIGATION = css`
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
z-index: ${Constants.zindex.header};
|
||||
}
|
||||
`;
|
||||
|
||||
@ -223,10 +224,10 @@ export default class ApplicationLayout extends React.Component {
|
||||
if (this.props.sidebar) {
|
||||
sidebarElements = (
|
||||
<React.Fragment>
|
||||
<GlobalTooltip
|
||||
{/* <GlobalTooltip
|
||||
elementRef={this._sidebar}
|
||||
allowedTypes={["sidebar"]}
|
||||
/>
|
||||
/> */}
|
||||
<div css={STYLES_SIDEBAR_HEADER}>
|
||||
<div css={STYLES_BLOCK} onClick={this._handleDismiss}>
|
||||
<SVG.Dismiss height="24px" />
|
||||
@ -244,16 +245,16 @@ export default class ApplicationLayout extends React.Component {
|
||||
this._navigation = c;
|
||||
}}
|
||||
>
|
||||
<GlobalTooltip
|
||||
{/* <GlobalTooltip
|
||||
elementRef={this._navigation}
|
||||
allowedTypes={["navigation"]}
|
||||
/>
|
||||
/> */}
|
||||
{this.props.navigation}
|
||||
</div>
|
||||
|
||||
<div css={STYLES_CONTENT}>
|
||||
{/* <GlobalTooltip elementRef={this._body} allowedTypes={["body"]} /> */}
|
||||
<GlobalTooltip allowedTypes={["body"]} />
|
||||
<GlobalTooltip />
|
||||
<span css={STYLES_MOBILE_HIDDEN}>
|
||||
<div css={STYLES_HEADER}>{this.props.header}</div>
|
||||
</span>
|
||||
|
@ -139,29 +139,17 @@ export default class ApplicationUserControls extends React.Component {
|
||||
state = { visible: false };
|
||||
|
||||
_handleClick = (e) => {
|
||||
console.log("click");
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this.state.visible) {
|
||||
this._handleHide();
|
||||
return;
|
||||
}
|
||||
this.setState({ visible: true });
|
||||
// dispatchCustomEvent({
|
||||
// name: "show-tooltip",
|
||||
// detail: {
|
||||
// id: APPLICATION_CONTROL_MENU_ID,
|
||||
// },
|
||||
// });
|
||||
};
|
||||
|
||||
_handleHide = () => {
|
||||
this.setState({ visible: false });
|
||||
// return dispatchCustomEvent({
|
||||
// name: "hide-tooltip",
|
||||
// detail: {
|
||||
// id: APPLICATION_CONTROL_MENU_ID,
|
||||
// },
|
||||
// });
|
||||
};
|
||||
|
||||
_handleAction = (e, data) => {
|
||||
@ -214,7 +202,14 @@ export default class ApplicationUserControls extends React.Component {
|
||||
value: "SIDEBAR_HELP",
|
||||
}),
|
||||
},
|
||||
{ text: "Sign out", onClick: this._handleSignOut },
|
||||
{
|
||||
text: "Sign out",
|
||||
onClick: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this._handleSignOut(e);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@ -229,7 +224,14 @@ export default class ApplicationUserControls extends React.Component {
|
||||
value: "V1_NAVIGATION_PROFILE_EDIT",
|
||||
}),
|
||||
},
|
||||
{ text: "Sign out", onClick: this._handleSignOut },
|
||||
{
|
||||
text: "Sign out",
|
||||
onClick: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this._handleSignOut(e);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
@ -10,7 +10,6 @@ import { Boundary } from "~/components/system/components/fragments/Boundary";
|
||||
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
|
||||
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 { FileTypeIcon } from "~/components/core/FileTypeIcon";
|
||||
@ -274,15 +273,20 @@ export default class DataView extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
_handleDeleteFiles = async (e) => {
|
||||
_handleDelete = async (cid) => {
|
||||
const message = `Are you sure you want to delete these 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);
|
||||
return this.props.viewer.library[0].children[index].ipfs.replace("/ipfs/", "");
|
||||
});
|
||||
let cids;
|
||||
if (cid) {
|
||||
cids = [cid];
|
||||
} else {
|
||||
cids = Object.keys(this.state.checked).map((id) => {
|
||||
let index = parseInt(id);
|
||||
return this.props.viewer.library[0].children[index].ipfs.replace("/ipfs/", "");
|
||||
});
|
||||
}
|
||||
this._handleLoading({ cids });
|
||||
|
||||
const response = await Actions.deleteBucketItems({ cids });
|
||||
@ -318,53 +322,6 @@ export default class DataView extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
_handleDelete = async (cid) => {
|
||||
this._handleLoading({ cids: [cid] });
|
||||
|
||||
if (
|
||||
!window.confirm(
|
||||
"Are you sure you want to delete this? It will be removed from your Slates too."
|
||||
)
|
||||
) {
|
||||
this._handleLoading({ cids: [cid] });
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await Actions.deleteBucketItem({ cid });
|
||||
|
||||
if (!response) {
|
||||
this._handleLoading({ cids: [cid] });
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: {
|
||||
message: "We're having trouble connecting right now. Please try again later",
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
this._handleLoading({ cids: [cid] });
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: { alert: { decorator: response.decorator } },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.props.onRehydrate();
|
||||
await this._handleUpdate();
|
||||
this._handleLoading({ cids: [cid] });
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: { message: "File successfully deleted!", status: "INFO" },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
_handleUpdate = async () => {
|
||||
// NOTE(jim): Hack to handle some race conditions.
|
||||
await delay(200);
|
||||
@ -487,15 +444,10 @@ export default class DataView extends React.Component {
|
||||
return o.id !== id;
|
||||
});
|
||||
|
||||
// TODO(jim): This is a brute force way to handle this.
|
||||
const layouts = { lg: generateLayout(objects) };
|
||||
|
||||
const response = await Actions.updateSlate({
|
||||
id: slate.id,
|
||||
data: {
|
||||
name: slate.data.name,
|
||||
objects,
|
||||
layouts,
|
||||
},
|
||||
});
|
||||
|
||||
@ -615,7 +567,7 @@ export default class DataView extends React.Component {
|
||||
this.setState({ view: "list", menu: null });
|
||||
}}
|
||||
>
|
||||
<SVG.ListView
|
||||
<SVG.TableView
|
||||
style={{
|
||||
color: this.state.view === "list" ? Constants.system.black : "rgba(0,0,0,0.25)",
|
||||
}}
|
||||
@ -671,7 +623,7 @@ export default class DataView extends React.Component {
|
||||
<ButtonWarning
|
||||
transparent
|
||||
style={{ marginLeft: 8 }}
|
||||
onClick={this._handleDeleteFiles}
|
||||
onClick={() => this._handleDelete()}
|
||||
loading={
|
||||
this.state.loading &&
|
||||
Object.values(this.state.loading).some((elem) => {
|
||||
@ -707,6 +659,7 @@ export default class DataView extends React.Component {
|
||||
onMouseLeave={() => this.setState({ hover: null })}
|
||||
>
|
||||
<SlateMediaObjectPreview
|
||||
blurhash={each.blurhash}
|
||||
url={`${Constants.gateways.ipfs}/${each.ipfs.replace("/ipfs/", "")}`}
|
||||
title={each.file || each.name}
|
||||
type={each.type || each.icon}
|
||||
|
33
components/core/DynamicIcon.js
Normal file
33
components/core/DynamicIcon.js
Normal file
@ -0,0 +1,33 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
export class DynamicIcon extends React.Component {
|
||||
state = {
|
||||
clicked: false,
|
||||
};
|
||||
|
||||
_handleClick = (e) => {
|
||||
this.props.onClick(e);
|
||||
this.setState({ clicked: true });
|
||||
setTimeout(
|
||||
() => this.setState({ clicked: false }),
|
||||
this.props.timeout || 1000
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
style={{ cursor: "pointer", ...this.props.style }}
|
||||
onClick={this._handleClick}
|
||||
onMouseUp={this.props.onMouseUp}
|
||||
onMouseDown={this.props.onMouseDown}
|
||||
onMouseEnter={this.props.onMouseEnter}
|
||||
onMouseLeave={this.props.onMouseLeave}
|
||||
css={this.props.css}
|
||||
>
|
||||
{this.state.clicked ? this.props.successState : this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -74,6 +74,7 @@ export default class Profile extends React.Component {
|
||||
<br />
|
||||
{data.slates && data.slates.length ? (
|
||||
<SlatePreviewBlocks
|
||||
isOwner={this.props.isOwner}
|
||||
external={this.props.onAction ? false : true}
|
||||
slates={data.slates}
|
||||
username={data.username}
|
||||
@ -98,7 +99,7 @@ export default class Profile extends React.Component {
|
||||
<SlatePreviewBlock
|
||||
slate={slate}
|
||||
username={data.username}
|
||||
editing={this.props.editing}
|
||||
isOwner={this.props.isOwner}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -33,7 +33,7 @@ const STYLES_HEADER = css`
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 688px;
|
||||
max-width: 800px;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
@ -50,7 +50,7 @@ const STYLES_DESCRIPTION = css`
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 688px;
|
||||
max-width: 800px;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
`;
|
||||
|
@ -5,7 +5,6 @@ import * as Strings from "~/common/strings";
|
||||
import * as Actions from "~/common/actions";
|
||||
|
||||
import MiniSearch from "minisearch";
|
||||
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { SearchDropdown } from "~/components/core/SearchDropdown";
|
||||
|
@ -1,284 +0,0 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/common/svg";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Validations from "~/common/validations";
|
||||
import * as Actions from "~/common/actions";
|
||||
|
||||
import { Responsive, WidthProvider } from "react-grid-layout";
|
||||
import { css } from "@emotion/react";
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
import { dispatchCustomEvent } from "~/common/custom-events";
|
||||
|
||||
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
|
||||
import CircleButtonGray from "~/components/core/CircleButtonGray";
|
||||
|
||||
// NOTE(jim): I broke my own rules to do this. Sorry.
|
||||
const STYLES_ITEM = css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:hover {
|
||||
figure {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
img,
|
||||
article {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_CONTAINER = css`
|
||||
padding: 24px;
|
||||
`;
|
||||
|
||||
const STYLES_ACTIONS = css`
|
||||
z-index: ${Constants.zindex.navigation};
|
||||
bottom: 16px;
|
||||
right: 8px;
|
||||
position: fixed;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const STYLES_BUTTON = css`
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: 200ms ease all;
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const STYLES_ACTION_BUTTON = css`
|
||||
font-family: ${Constants.font.code};
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
user-select: none;
|
||||
height: 32px;
|
||||
padding: 0 16px 0 16px;
|
||||
border-radius: 32px;
|
||||
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;
|
||||
margin: 4px 16px 4px 16px;
|
||||
flex-shrink: 0;
|
||||
text-decoration: none;
|
||||
|
||||
:hover {
|
||||
background-color: ${Constants.system.black};
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_MOBILE_HIDDEN = css`
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const ResponsiveReactGridLayout = WidthProvider(Responsive);
|
||||
|
||||
const COLUMN_MAP = { lg: 12, md: 8, sm: 6, xs: 4, xxs: 2 };
|
||||
|
||||
export 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 class Slate extends React.Component {
|
||||
static defaultProps = {
|
||||
onLayoutChange: () => {},
|
||||
};
|
||||
|
||||
state = {
|
||||
currentBreakpoint: "lg",
|
||||
compactType: "vertical",
|
||||
};
|
||||
|
||||
_handleResetLayout = () => {
|
||||
if (!this.props.editing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!window.confirm("Are you sure you want to reset your layout?")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const layouts = { lg: generateLayout(this.props.items) };
|
||||
this.props.onLayoutChange(null, layouts);
|
||||
};
|
||||
|
||||
_handleSaveLayout = async () => {
|
||||
if (!this.props.editing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await this.props.onLayoutSave();
|
||||
};
|
||||
|
||||
_handleSelect = (e, index) => {
|
||||
// TODO(jim): Test this again in React 17
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSelect(index);
|
||||
};
|
||||
|
||||
_handleDeepLink = async (e, object) => {
|
||||
// TODO(jim): Test this again in React 17
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let slug = object.deeplink
|
||||
.split("/")
|
||||
.map((string) => Strings.createSlug(string, ""))
|
||||
.join("/");
|
||||
//TODO(martina): moved this cleanup here rather than when entering the info b/c it doesn't allow you to enter "-"'s if used during input. Switch to a dropdown / search later
|
||||
if (!object.deeplink.startsWith("/")) {
|
||||
slug = "/" + slug;
|
||||
}
|
||||
return window.open(slug);
|
||||
};
|
||||
|
||||
generateDOM = () => {
|
||||
return this.props.layouts.lg.map((each, index) => {
|
||||
const data = this.props.items[each.i];
|
||||
if (!data) {
|
||||
return <div key={index} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
css={STYLES_ITEM}
|
||||
onClick={
|
||||
Validations.onMobile()
|
||||
? (e) => this._handleSelect(e, index)
|
||||
: () => {}
|
||||
}
|
||||
>
|
||||
<SlateMediaObjectPreview
|
||||
charCap={70}
|
||||
type={data.type}
|
||||
url={data.url}
|
||||
title={data.title || data.name}
|
||||
/>
|
||||
<span css={STYLES_MOBILE_HIDDEN}>
|
||||
<figure css={STYLES_BUTTON}>
|
||||
<CircleButtonGray
|
||||
style={{ margin: 8, cursor: "pointer" }}
|
||||
onMouseUp={(e) => this._handleSelect(e, index)}
|
||||
onTouchEnd={(e) => this._handleSelect(e, index)}
|
||||
>
|
||||
<SVG.Eye height="16px" />
|
||||
</CircleButtonGray>
|
||||
|
||||
{data.deeplink ? (
|
||||
<CircleButtonGray
|
||||
style={{ margin: 8 }}
|
||||
onMouseUp={(e) => this._handleDeepLink(e, data)}
|
||||
onTouchEnd={(e) => this._handleDeepLink(e, data)}
|
||||
>
|
||||
<SVG.DeepLink height="16px" />
|
||||
</CircleButtonGray>
|
||||
) : null}
|
||||
</figure>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
onBreakpointChange = (breakpoint) => {
|
||||
this.setState({
|
||||
currentBreakpoint: breakpoint,
|
||||
});
|
||||
};
|
||||
|
||||
onLayoutChange = (layout, layouts) => {
|
||||
this.props.onLayoutChange(layout, layouts);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div css={STYLES_CONTAINER}>
|
||||
<ResponsiveReactGridLayout
|
||||
columns={COLUMN_MAP}
|
||||
layouts={this.props.layouts}
|
||||
isDraggable={!!this.props.editing}
|
||||
isResizable={!!this.props.editing}
|
||||
onBreakpointChange={this.onBreakpointChange}
|
||||
onLayoutChange={this.onLayoutChange}
|
||||
measureBeforeMount={false}
|
||||
useCSSTransforms={false}
|
||||
compactType={this.state.compactType}
|
||||
preventCollision={false}
|
||||
margin={[24, 24]}
|
||||
>
|
||||
{this.generateDOM()}
|
||||
</ResponsiveReactGridLayout>
|
||||
|
||||
{this.props.editing ? (
|
||||
<div css={STYLES_ACTIONS}>
|
||||
<span css={STYLES_ACTION_BUTTON} onClick={this._handleResetLayout}>
|
||||
Reset Layout
|
||||
</span>
|
||||
<span
|
||||
css={STYLES_ACTION_BUTTON}
|
||||
onClick={this._handleSaveLayout}
|
||||
style={{
|
||||
backgroundColor:
|
||||
this.props.saving === "IDLE" ? Constants.system.brand : null,
|
||||
}}
|
||||
>
|
||||
{this.props.saving === "SAVING" ? (
|
||||
<LoaderSpinner style={{ height: 16, width: 16 }} />
|
||||
) : this.props.saving === "IDLE" ? (
|
||||
"Save"
|
||||
) : (
|
||||
"Saved"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
1694
components/core/SlateLayout.js
Normal file
1694
components/core/SlateLayout.js
Normal file
File diff suppressed because it is too large
Load Diff
102
components/core/SlateLayoutMobile.js
Normal file
102
components/core/SlateLayoutMobile.js
Normal file
@ -0,0 +1,102 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/common/svg";
|
||||
|
||||
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
const TAG_HEIGHT = 20;
|
||||
|
||||
const STYLES_LAYOUT = css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 48px;
|
||||
`;
|
||||
|
||||
const STYLES_FILE_TAG = css`
|
||||
font-family: ${Constants.font.medium};
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
width: 100%;
|
||||
background: ${Constants.system.white};
|
||||
font-size: ${Constants.typescale.lvl1};
|
||||
`;
|
||||
|
||||
const STYLES_FILE_NAME = css`
|
||||
width: 100%;
|
||||
min-width: 10%;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
const STYLES_FILE_TYPE = css`
|
||||
color: ${Constants.system.grayBlack};
|
||||
text-transform: uppercase;
|
||||
flex-shrink: 0;
|
||||
margin-left: 16px;
|
||||
text-align: right;
|
||||
`;
|
||||
|
||||
const STYLES_IMAGE_CONTAINER = css`
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export class SlateLayoutMobile extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div css={STYLES_LAYOUT}>
|
||||
{this.props.items.map((item, i) => (
|
||||
<div
|
||||
key={item.id}
|
||||
css={STYLES_IMAGE_CONTAINER}
|
||||
style={{
|
||||
width: `calc(100vw - 48px)`,
|
||||
}}
|
||||
onClick={() => this.props.onSelect(i)}
|
||||
>
|
||||
<SlateMediaObjectPreview
|
||||
blurhash={item.blurhash}
|
||||
iconOnly={this.props.fileNames}
|
||||
charCap={70}
|
||||
type={item.type}
|
||||
url={item.url}
|
||||
title={item.title || item.name}
|
||||
style={{
|
||||
height: `calc(100vw - 48px)`,
|
||||
width: `calc(100vw - 48px)`,
|
||||
background: Constants.system.white,
|
||||
}}
|
||||
imageStyle={{
|
||||
maxWidth: `calc(100vw - 48px)`,
|
||||
maxHeight: `calc(100vw - 48px)`,
|
||||
}}
|
||||
/>
|
||||
{/* {this.props.fileNames ? (
|
||||
<div
|
||||
css={STYLES_FILE_TAG}
|
||||
style={{
|
||||
height: `${TAG_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
<span css={STYLES_FILE_NAME}>{item.title || item.name}</span>
|
||||
<span css={STYLES_FILE_TYPE}>
|
||||
{item.name.lastIndexOf(".") !== -1
|
||||
? item.name.slice(item.name.lastIndexOf("."))
|
||||
: ""}
|
||||
</span>
|
||||
</div>
|
||||
) : null} */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ import * as SVG from "~/common/svg";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { FileTypeIcon } from "~/components/core/FileTypeIcon";
|
||||
import { Blurhash } from "react-blurhash";
|
||||
import { isBlurhashValid } from "blurhash";
|
||||
|
||||
const STYLES_IMAGE_CONTAINER = css`
|
||||
background-color: ${Constants.system.foreground};
|
||||
@ -17,7 +19,6 @@ const STYLES_IMAGE = css`
|
||||
background-color: ${Constants.system.foreground};
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
pointer-events: none;
|
||||
transition: 200ms ease all;
|
||||
`;
|
||||
@ -25,7 +26,8 @@ const STYLES_IMAGE = css`
|
||||
const STYLES_ENTITY = css`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 1px solid ${Constants.system.lightBorder};
|
||||
border: 1px solid ${Constants.system.gray};
|
||||
background-color: ${Constants.system.white};
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -46,30 +48,140 @@ const STYLES_TITLE = css`
|
||||
text-overflow: break-word;
|
||||
`;
|
||||
|
||||
const STYLES_BLUR_CONTAINER = css`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
let preload = (url) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = resolve(img);
|
||||
img.onerror = reject;
|
||||
img.src = url.replace("https://undefined", "https://");
|
||||
});
|
||||
|
||||
export default class SlateMediaObjectPreview extends React.Component {
|
||||
static defaultProps = {
|
||||
charCap: 30,
|
||||
};
|
||||
|
||||
state = {
|
||||
showImage: false,
|
||||
error: false,
|
||||
};
|
||||
|
||||
componentDidMount = async () => {
|
||||
if (this.props.type && this.props.type.startsWith("image/")) {
|
||||
try {
|
||||
let img = await preload(this.props.url);
|
||||
if (!img.height && !img.width) {
|
||||
this.setState({ showImage: false, error: true });
|
||||
}
|
||||
this.setState({ showImage: true, error: false });
|
||||
} catch (error) {
|
||||
this.setState({ showImage: false, error: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
// NOTE(jim):
|
||||
// This is a hack to catch this undefined case I don't want to track down yet.
|
||||
const url = this.props.url.replace("https://undefined", "https://");
|
||||
|
||||
const title =
|
||||
this.props.title && this.props.title.length > this.props.charCap
|
||||
? this.props.title.substring(0, this.props.charCap) + "..."
|
||||
: this.props.title;
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("image/")) {
|
||||
let blurhash =
|
||||
this.props.blurhash && isBlurhashValid(this.props.blurhash);
|
||||
if (this.props.centeredImage) {
|
||||
return (
|
||||
<div
|
||||
css={STYLES_IMAGE_CONTAINER}
|
||||
style={{ backgroundImage: `url(${url})`, ...this.props.imageStyle }}
|
||||
/>
|
||||
<React.Fragment>
|
||||
{this.state.error ? (
|
||||
<div
|
||||
css={STYLES_ENTITY}
|
||||
style={{
|
||||
...this.props.imageStyle,
|
||||
backgroundColor: Constants.system.foreground,
|
||||
}}
|
||||
>
|
||||
<SVG.FileNotFound height="24px" />
|
||||
{this.props.iconOnly ? null : (
|
||||
<div css={STYLES_TITLE}>File not found</div>
|
||||
)}
|
||||
</div>
|
||||
) : this.state.showImage ? (
|
||||
<div
|
||||
css={STYLES_IMAGE_CONTAINER}
|
||||
style={{
|
||||
backgroundImage: `url(${url})`,
|
||||
...this.props.imageStyle,
|
||||
}}
|
||||
/>
|
||||
) : blurhash ? (
|
||||
<div css={STYLES_BLUR_CONTAINER}>
|
||||
<Blurhash
|
||||
hash={this.props.blurhash}
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
...this.props.imageStyle,
|
||||
}}
|
||||
resolutionX={32}
|
||||
resolutionY={32}
|
||||
punch={1}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div css={STYLES_IMAGE_CONTAINER} style={this.props.imageStyle} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
return <img css={STYLES_IMAGE} style={this.props.imageStyle} src={url} />;
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.state.error ? (
|
||||
<div
|
||||
css={STYLES_ENTITY}
|
||||
style={{ ...this.props.imageStyle, backgroundColor: "#F2F2F2" }}
|
||||
>
|
||||
<SVG.FileNotFound height="24px" />
|
||||
{this.props.iconOnly ? null : (
|
||||
<div css={STYLES_TITLE}>File not found</div>
|
||||
)}
|
||||
</div>
|
||||
) : this.state.showImage ? (
|
||||
<img
|
||||
css={STYLES_IMAGE}
|
||||
style={{ maxHeight: "100%", ...this.props.imageStyle }}
|
||||
src={url}
|
||||
/>
|
||||
) : blurhash ? (
|
||||
<Blurhash
|
||||
hash={this.props.blurhash}
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
...this.props.imageStyle,
|
||||
}}
|
||||
resolutionX={32}
|
||||
resolutionY={32}
|
||||
punch={1}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
css={STYLES_IMAGE}
|
||||
style={{ maxHeight: "100%", ...this.props.imageStyle }}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
let element = <FileTypeIcon type={this.props.type} height="24px" />;
|
||||
@ -77,7 +189,7 @@ export default class SlateMediaObjectPreview extends React.Component {
|
||||
return (
|
||||
<article css={STYLES_ENTITY} style={this.props.style}>
|
||||
<div>{element}</div>
|
||||
{this.props.title && !this.props.small ? (
|
||||
{this.props.title && !this.props.iconOnly ? (
|
||||
<div css={STYLES_TITLE}>{title}</div>
|
||||
) : null}
|
||||
</article>
|
||||
|
@ -160,7 +160,7 @@ export default class SlateMediaObjectSidebar extends React.Component {
|
||||
const elements = [];
|
||||
|
||||
if (this.props.data) {
|
||||
if (this.props.editing) {
|
||||
if (this.props.isOwner) {
|
||||
elements.push(
|
||||
<React.Fragment key="sidebar-media-object-info">
|
||||
<div css={STYLES_SIDEBAR_SECTION}>
|
||||
@ -307,7 +307,7 @@ export default class SlateMediaObjectSidebar extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.onDelete && this.props.editing) {
|
||||
if (this.props.onDelete && this.props.isOwner) {
|
||||
elements.push(
|
||||
<a
|
||||
key="sidebar-media-object-preview"
|
||||
|
@ -141,12 +141,13 @@ export class SlatePreviewRow extends React.Component {
|
||||
}}
|
||||
>
|
||||
<SlateMediaObjectPreview
|
||||
blurhash={each.blurhash}
|
||||
charCap={30}
|
||||
type={each.type}
|
||||
url={each.url}
|
||||
style={{ border: "none", ...this.props.previewStyle }}
|
||||
style={this.props.previewStyle}
|
||||
title={each.title || each.name}
|
||||
small={this.props.small}
|
||||
iconOnly={this.props.small}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
@ -176,8 +177,7 @@ export class SlatePreviewRow extends React.Component {
|
||||
}
|
||||
|
||||
const STYLES_BLOCK = css`
|
||||
box-shadow: 0 0 0 1px ${Constants.system.lightBorder} inset,
|
||||
0 0 40px 0 ${Constants.system.shadow};
|
||||
box-shadow: 0 0 0 1px ${Constants.system.lightBorder} inset, 0 0 40px 0 ${Constants.system.shadow};
|
||||
border-radius: 8px;
|
||||
padding: 32px 40px;
|
||||
font-size: 12px;
|
||||
@ -315,12 +315,10 @@ export class SlatePreviewBlock extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.editing && !this.props.slate.data.objects.length) {
|
||||
if (!this.props.isOwner && !this.props.slate.data.objects.length) {
|
||||
return null;
|
||||
}
|
||||
let first = this.props.slate.data.objects
|
||||
? this.props.slate.data.objects[0]
|
||||
: null;
|
||||
let first = this.props.slate.data.objects ? this.props.slate.data.objects[0] : null;
|
||||
let contextMenu = (
|
||||
<React.Fragment>
|
||||
<Boundary
|
||||
@ -335,14 +333,14 @@ export class SlatePreviewBlock extends React.Component {
|
||||
right: "-12px",
|
||||
}}
|
||||
navigation={
|
||||
this.props.editing
|
||||
this.props.isOwner
|
||||
? [
|
||||
{
|
||||
text: "Copy URL",
|
||||
onClick: (e) =>
|
||||
this._handleCopy(
|
||||
e,
|
||||
`https://slate.host/${this.props.username}/${this.props.slate.slatename}`
|
||||
`${window.location.hostname}/${this.props.username}/${this.props.slate.slatename}`
|
||||
),
|
||||
},
|
||||
{
|
||||
@ -356,7 +354,7 @@ export class SlatePreviewBlock extends React.Component {
|
||||
onClick: (e) =>
|
||||
this._handleCopy(
|
||||
e,
|
||||
`https://slate.host/${this.props.username}/${this.props.slate.slatename}`
|
||||
`${window.location.hostname}/${this.props.username}/${this.props.slate.slatename}`
|
||||
),
|
||||
},
|
||||
]
|
||||
@ -378,14 +376,12 @@ export class SlatePreviewBlock extends React.Component {
|
||||
<div
|
||||
css={STYLES_BLOCK}
|
||||
style={
|
||||
this.props.external
|
||||
? { backgroundColor: Constants.system.white, boxShadow: "none" }
|
||||
: {}
|
||||
this.props.external ? { backgroundColor: Constants.system.white, boxShadow: "none" } : {}
|
||||
}
|
||||
>
|
||||
<div css={STYLES_TITLE_LINE}>
|
||||
<div css={STYLES_TITLE}>{this.props.slate.data.name}</div>
|
||||
{this.props.editing ? (
|
||||
{this.props.isOwner ? (
|
||||
this.props.slate.data.public ? (
|
||||
<div
|
||||
css={STYLES_TAG}
|
||||
@ -426,9 +422,7 @@ export class SlatePreviewBlock extends React.Component {
|
||||
> */}
|
||||
<div css={STYLES_ICON_BOX} onClick={this._handleClick}>
|
||||
<SVG.MoreHorizontal height="24px" />
|
||||
{this.state.showMenu ? (
|
||||
<div css={STYLES_CONTEXT_MENU}>{contextMenu}</div>
|
||||
) : null}
|
||||
{this.state.showMenu ? <div css={STYLES_CONTEXT_MENU}>{contextMenu}</div> : null}
|
||||
</div>
|
||||
{/* </TooltipWrapper> */}
|
||||
</div>
|
||||
@ -442,10 +436,7 @@ export class SlatePreviewBlock extends React.Component {
|
||||
<div style={{ height: "8px" }} />
|
||||
)}
|
||||
<span css={STYLES_MOBILE_ONLY}>
|
||||
<div
|
||||
css={STYLES_TITLE}
|
||||
style={{ marginBottom: 8, fontSize: Constants.typescale.lvl1 }}
|
||||
>
|
||||
<div css={STYLES_TITLE} style={{ marginBottom: 8, fontSize: Constants.typescale.lvl1 }}>
|
||||
{this.props.slate.data.name}
|
||||
</div>
|
||||
<div style={{ marginBottom: 16, fontSize: 12 }}>
|
||||
@ -460,6 +451,7 @@ export class SlatePreviewBlock extends React.Component {
|
||||
>
|
||||
{first ? (
|
||||
<SlateMediaObjectPreview
|
||||
blurhash={first.blurhash}
|
||||
centeredImage
|
||||
charCap={30}
|
||||
type={first.type}
|
||||
@ -542,11 +534,7 @@ export default class SlatePreviewBlocks extends React.Component {
|
||||
render() {
|
||||
if (this.props.external) {
|
||||
return this.props.slates.map((slate) => (
|
||||
<a
|
||||
key={slate.id}
|
||||
href={`/${this.props.username}/${slate.slatename}`}
|
||||
css={STYLES_LINK}
|
||||
>
|
||||
<a key={slate.id} href={`/${this.props.username}/${slate.slatename}`} css={STYLES_LINK}>
|
||||
<SlatePreviewBlock
|
||||
external
|
||||
imageSize={this.state.imageSize}
|
||||
@ -568,7 +556,7 @@ export default class SlatePreviewBlocks extends React.Component {
|
||||
}
|
||||
>
|
||||
<SlatePreviewBlock
|
||||
editing={this.props.editing}
|
||||
isOwner={this.props.isOwner}
|
||||
username={this.props.username}
|
||||
imageSize={this.state.imageSize}
|
||||
slate={slate}
|
||||
|
40
components/core/Tooltip.js
Normal file
40
components/core/Tooltip.js
Normal file
@ -0,0 +1,40 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
const STYLES_TOOLTIP = `
|
||||
font-family: ${Constants.font.text};
|
||||
font-size: ${Constants.typescale.lvl0};
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
-webkit-backdrop-filter: blur(25px);
|
||||
backdrop-filter: blur(25px);
|
||||
line-height: 1.2;
|
||||
transition: 100ms ease all;
|
||||
`;
|
||||
|
||||
const STYLES_TOOLTIP_LIGHT = css`
|
||||
${STYLES_TOOLTIP}
|
||||
background-color: rgba(248, 248, 248, 0.6);
|
||||
color: #4b4a4d;
|
||||
`;
|
||||
|
||||
const STYLES_TOOLTIP_DARK = css`
|
||||
${STYLES_TOOLTIP}
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
color: ${Constants.system.white};
|
||||
`;
|
||||
|
||||
export const Tooltip = (props) => {
|
||||
return (
|
||||
<div style={{ maxWidth: 400 }}>
|
||||
<span
|
||||
css={props.light ? STYLES_TOOLTIP_LIGHT : STYLES_TOOLTIP_DARK}
|
||||
style={props.style}
|
||||
>
|
||||
{props.children}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -172,7 +172,7 @@ export default class GlobalViewerCIDSidebarSlates extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await Actions.deleteBucketItem({ cid });
|
||||
const response = await Actions.deleteBucketItems({ cids: [cid] });
|
||||
if (!response) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
@ -242,7 +242,7 @@ export default class GlobalViewerCIDSidebarSlates extends React.Component {
|
||||
</span>
|
||||
</div>
|
||||
{/* <div css={STYLES_ACTION} onClick={this.props.onDownload}>
|
||||
<SVG.Download height="24px" />
|
||||
<SVG.DownloadCircle height="24px" />
|
||||
<span style={{ marginLeft: 16 }}>Download</span>
|
||||
</div> */}
|
||||
<div css={STYLES_ACTION} onClick={() => this._handleDelete(cid)}>
|
||||
|
@ -22,7 +22,6 @@ const STYLES_SLATE_NAME = css`
|
||||
|
||||
const STYLES_HEADER = css`
|
||||
font-family: ${Constants.font.semiBold};
|
||||
font-size: 18px;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
@ -92,7 +91,11 @@ export default class SidebarAddFileToSlate extends React.Component {
|
||||
});
|
||||
for (let slate of Object.values(this.state.selected)) {
|
||||
if (!slate) continue;
|
||||
const addResponse = await Actions.addFileToSlate({ slate, data });
|
||||
const addResponse = await Actions.addFileToSlate({
|
||||
slate,
|
||||
data,
|
||||
fromSlate: this.props.sidebarData.fromSlate,
|
||||
});
|
||||
|
||||
if (!addResponse) {
|
||||
dispatchCustomEvent({
|
||||
|
@ -22,7 +22,6 @@ const STYLES_GROUP = css`
|
||||
|
||||
const STYLES_HEADER = css`
|
||||
font-family: ${Constants.font.semiBold};
|
||||
font-size: 18px;
|
||||
margin-top: 32px;
|
||||
`;
|
||||
|
||||
@ -88,17 +87,14 @@ export default class SidebarCreateSlate extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.props.sidebarData &&
|
||||
this.props.sidebarData.files &&
|
||||
this.props.sidebarData.files[0].decorator === "FILE"
|
||||
) {
|
||||
if (this.props.sidebarData && this.props.sidebarData.files) {
|
||||
let data = this.props.sidebarData.files.map((file) => {
|
||||
return { title: file.name, ...file };
|
||||
});
|
||||
const addResponse = await Actions.addFileToSlate({
|
||||
slate: response.slate,
|
||||
data,
|
||||
repost: this.props.sidebarData.repost,
|
||||
});
|
||||
|
||||
if (!addResponse) {
|
||||
@ -204,7 +200,7 @@ export default class SidebarCreateSlate extends React.Component {
|
||||
style={{ marginTop: 12 }}
|
||||
name="body"
|
||||
value={this.state.body}
|
||||
placeholder="A slate."
|
||||
placeholder="Slate description..."
|
||||
onChange={this._handleChange}
|
||||
onSubmit={this._handleSubmit}
|
||||
/>
|
||||
|
@ -10,7 +10,6 @@ import { css } from "@emotion/react";
|
||||
|
||||
const STYLES_HEADER = css`
|
||||
font-family: ${Constants.font.semiBold};
|
||||
font-size: 18px;
|
||||
margin-top: 32px;
|
||||
`;
|
||||
|
||||
|
@ -6,8 +6,9 @@ import * as Strings from "~/common/strings";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { dispatchCustomEvent } from "~/common/custom-events";
|
||||
import { ButtonWarning } from "~/components/system/components/Buttons";
|
||||
import { SidebarWarningMessage } from "~/components/core/WarningMessage";
|
||||
|
||||
const SIZE_LIMIT = 1000000;
|
||||
const DEFAULT_IMAGE = "";
|
||||
|
||||
const STYLES_GROUP = css`
|
||||
display: flex;
|
||||
@ -21,8 +22,27 @@ const STYLES_GROUP = css`
|
||||
|
||||
const STYLES_HEADER = css`
|
||||
font-family: ${Constants.font.semiBold};
|
||||
font-size: 18px;
|
||||
margin-top: 32px;
|
||||
${"" /* margin-top: 32px; */}
|
||||
`;
|
||||
|
||||
const STYLES_IMAGE_BOX = css`
|
||||
max-width: 368px;
|
||||
max-height: 368px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: ${Constants.system.white};
|
||||
overflow: hidden;
|
||||
${"" /* box-shadow: 0 0 0 1px ${Constants.system.border} inset; */}
|
||||
border-radius: 4px;
|
||||
`;
|
||||
|
||||
const STYLES_GROUPING = css`
|
||||
width: 100%;
|
||||
border: 1px solid rgba(196, 196, 196, 0.5);
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
`;
|
||||
|
||||
export default class SidebarSingleSlateSettings extends React.Component {
|
||||
@ -45,6 +65,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
|
||||
body: this.state.body,
|
||||
},
|
||||
});
|
||||
console.log(response);
|
||||
|
||||
if (!response) {
|
||||
dispatchCustomEvent({
|
||||
@ -125,6 +146,22 @@ export default class SidebarSingleSlateSettings extends React.Component {
|
||||
render() {
|
||||
const slug = Strings.createSlug(this.state.name);
|
||||
const url = `/${this.props.viewer.username}/${slug}`;
|
||||
let preview = this.props.current.data.preview;
|
||||
if (!preview) {
|
||||
for (let object of this.props.current.data.objects) {
|
||||
if (
|
||||
object.type &&
|
||||
object.type.startsWith("image/") &&
|
||||
(!object.size || object.size < SIZE_LIMIT)
|
||||
) {
|
||||
preview = object.url.replace("https://undefined", "https://");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!preview) {
|
||||
preview = DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
@ -138,56 +175,91 @@ export default class SidebarSingleSlateSettings extends React.Component {
|
||||
Slate settings
|
||||
</System.P>
|
||||
|
||||
<System.P css={STYLES_HEADER}>Name</System.P>
|
||||
<System.P
|
||||
style={{
|
||||
marginTop: 12,
|
||||
}}
|
||||
>
|
||||
Changing the slatename will change your public slate URL. Your slate
|
||||
URL is:{" "}
|
||||
<a href={url} target="_blank">
|
||||
https://slate.host{url}
|
||||
</a>
|
||||
</System.P>
|
||||
<System.Input
|
||||
placeholder="Slate name..."
|
||||
style={{ marginTop: 24 }}
|
||||
name="name"
|
||||
value={this.state.name}
|
||||
placeholder="Name"
|
||||
onChange={this._handleChange}
|
||||
onSubmit={this._handleSubmit}
|
||||
descriptionStyle={{ fontSize: "20px !important" }}
|
||||
labelStyle={{ fontSize: "20px" }}
|
||||
/>
|
||||
|
||||
<System.P css={STYLES_HEADER}>Description</System.P>
|
||||
|
||||
<System.Textarea
|
||||
style={{ marginTop: 12 }}
|
||||
name="body"
|
||||
value={this.state.body}
|
||||
onChange={this._handleChange}
|
||||
onSubmit={this._handleSubmit}
|
||||
/>
|
||||
|
||||
<System.P css={STYLES_HEADER} style={{ marginTop: 48 }}>
|
||||
Privacy
|
||||
</System.P>
|
||||
<div css={STYLES_GROUP}>
|
||||
<System.P style={{ marginRight: 16 }}>
|
||||
{this.state.public
|
||||
? "Public. Anyone can search for and view this slate."
|
||||
: "Private. Only you can view this slate."}
|
||||
<div css={STYLES_GROUPING}>
|
||||
<System.P css={STYLES_HEADER}>Name</System.P>
|
||||
<System.P
|
||||
style={{
|
||||
marginTop: 12,
|
||||
}}
|
||||
>
|
||||
Changing the slatename will change your public slate URL. Your slate
|
||||
URL is:{" "}
|
||||
</System.P>
|
||||
<System.Toggle
|
||||
name="public"
|
||||
<System.P
|
||||
style={{
|
||||
marginTop: 12,
|
||||
}}
|
||||
>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
style={{ color: Constants.system.brand }}
|
||||
>
|
||||
https://slate.host{url}
|
||||
</a>
|
||||
</System.P>
|
||||
|
||||
<System.Input
|
||||
placeholder="Slate name..."
|
||||
style={{ marginTop: 8, boxShadow: "none" }}
|
||||
name="name"
|
||||
value={this.state.name}
|
||||
placeholder="Name"
|
||||
onChange={this._handleChange}
|
||||
active={this.state.public}
|
||||
onSubmit={this._handleSubmit}
|
||||
descriptionStyle={{ fontSize: "20px !important" }}
|
||||
labelStyle={{ fontSize: "20px" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div css={STYLES_GROUPING}>
|
||||
<System.P css={STYLES_HEADER}>Description</System.P>
|
||||
|
||||
<System.Textarea
|
||||
style={{ marginTop: 12, boxShadow: "none" }}
|
||||
name="body"
|
||||
value={this.state.body}
|
||||
onChange={this._handleChange}
|
||||
onSubmit={this._handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div css={STYLES_GROUPING}>
|
||||
<System.P css={STYLES_HEADER}>Preview image</System.P>
|
||||
|
||||
<System.P
|
||||
style={{
|
||||
marginTop: 12,
|
||||
}}
|
||||
>
|
||||
This is the image that shows when you share a link to your slate.
|
||||
</System.P>
|
||||
|
||||
<div css={STYLES_IMAGE_BOX} style={{ marginTop: 24 }}>
|
||||
<img
|
||||
src={preview}
|
||||
alt=""
|
||||
style={{ maxWidth: "368px", maxHeight: "368px" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div css={STYLES_GROUPING}>
|
||||
<System.P css={STYLES_HEADER}>Privacy</System.P>
|
||||
<div css={STYLES_GROUP}>
|
||||
<System.P style={{ marginRight: 16 }}>
|
||||
{this.state.public
|
||||
? "Public. Anyone can search for and view this slate."
|
||||
: "Private. Only you can view this slate."}
|
||||
</System.P>
|
||||
<System.Toggle
|
||||
name="public"
|
||||
onChange={this._handleChange}
|
||||
active={this.state.public}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 40 }}>
|
||||
<System.ButtonPrimary
|
||||
full
|
||||
|
@ -11,13 +11,13 @@ const STYLES_BUTTON = `
|
||||
outline: 0;
|
||||
border: 0;
|
||||
min-height: 40px;
|
||||
padding: 4px 16px;
|
||||
padding: 4px 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.2px;
|
||||
font-family: ${Constants.font.medium};
|
||||
font-family: ${Constants.font.semiBold};
|
||||
transition: 200ms ease all;
|
||||
overflow-wrap: break-word;
|
||||
user-select: none;
|
||||
@ -104,7 +104,7 @@ const STYLES_BUTTON_SECONDARY = css`
|
||||
cursor: pointer;
|
||||
background-color: ${Constants.system.white};
|
||||
box-shadow: 0 0 0 1px ${Constants.system.border} inset;
|
||||
color: ${Constants.system.brand};
|
||||
color: ${Constants.system.black};
|
||||
|
||||
:focus {
|
||||
outline: 0;
|
||||
@ -175,6 +175,7 @@ const STYLES_BUTTON_DISABLED = css`
|
||||
cursor: not-allowed;
|
||||
background-color: ${Constants.system.gray};
|
||||
color: ${Constants.system.darkGray};
|
||||
box-shadow: 0 0 0 1px ${Constants.system.gray} inset;
|
||||
|
||||
:focus {
|
||||
outline: 0;
|
||||
|
@ -19,7 +19,7 @@ const STYLES_CHECKBOX_FIGURE = css`
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 0 1px ${Constants.system.darkGray};
|
||||
background-color: ${Constants.system.white};
|
||||
border-radius: 3px;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -91,7 +91,8 @@ export class CheckBox extends React.Component {
|
||||
>
|
||||
{this.props.value ? (
|
||||
<SVG.CheckBox
|
||||
height="14px"
|
||||
height="12px"
|
||||
strokeWidth="4"
|
||||
style={{ color: Constants.system.white }}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -41,9 +41,9 @@ export class PopoverNavigation extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div css={STYLES_POPOVER} style={this.props.style}>
|
||||
{this.props.navigation.map((each) => (
|
||||
{this.props.navigation.map((each, i) => (
|
||||
<div
|
||||
key={each.text}
|
||||
key={i}
|
||||
css={STYLES_POPOVER_ITEM}
|
||||
style={this.props.itemStyle}
|
||||
onClick={each.onClick}
|
||||
|
@ -26,7 +26,7 @@ const STYLES_DIAL = css`
|
||||
border-radius: 24px;
|
||||
margin-top: 4px;
|
||||
margin-left: 4px;
|
||||
background: ${Constants.system.white};
|
||||
background: ${Constants.system.foreground};
|
||||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
|
||||
transition: transform 200ms ease;
|
||||
`;
|
||||
|
@ -116,15 +116,16 @@ export class GlobalTooltip extends React.Component {
|
||||
};
|
||||
|
||||
_handleAdd = (e) => {
|
||||
if (
|
||||
this.props.allowedTypes &&
|
||||
!this.props.allowedTypes.includes(e.detail.type)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if (
|
||||
// this.props.allowedTypes &&
|
||||
// !this.props.allowedTypes.includes(e.detail.type)
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
// console.log("got here");
|
||||
// if (!e.detail.bubbleRect.width && !e.detail.bubbleRect.height) return;
|
||||
// console.log("had width and height");
|
||||
let tooltips = this.state.tooltips;
|
||||
|
||||
tooltips[e.detail.id] = {
|
||||
id: e.detail.id,
|
||||
show: false,
|
||||
@ -153,10 +154,14 @@ export class GlobalTooltip extends React.Component {
|
||||
};
|
||||
|
||||
_handleShow = async (e) => {
|
||||
console.log(this.state.tooltips);
|
||||
console.log(this.state.tooltips[e.detail.id]);
|
||||
if (this.state.tooltips[e.detail.id]) {
|
||||
let tooltips = this.state.tooltips;
|
||||
if (!tooltips[e.detail.id].style) {
|
||||
let rect = tooltips[e.detail.id].root.getBoundingClientRect();
|
||||
let anchor = tooltips[e.detail.id].root;
|
||||
let rect = anchor.getBoundingClientRect();
|
||||
console.log(rect);
|
||||
let style = this.getOrientation(
|
||||
rect,
|
||||
tooltips[e.detail.id].bubbleRect,
|
||||
@ -209,8 +214,9 @@ export class TooltipWrapper extends React.Component {
|
||||
sample: true,
|
||||
};
|
||||
|
||||
componentDidMount = async () => {
|
||||
componentDidMount = () => {
|
||||
let bubbleRect = this._bubble.getBoundingClientRect();
|
||||
console.log(this._bubble);
|
||||
|
||||
dispatchCustomEvent({
|
||||
name: "add-tooltip",
|
||||
@ -236,7 +242,7 @@ export class TooltipWrapper extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{ display: "inline-flex" }}>
|
||||
<React.Fragment>
|
||||
{this.state.sample ? (
|
||||
<div
|
||||
ref={(c) => {
|
||||
@ -256,7 +262,7 @@ export class TooltipWrapper extends React.Component {
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import getUserById from "~/node_common/data/methods/get-user-by-id";
|
||||
//NOTE(martina):
|
||||
// Pending user upload queries
|
||||
import getPendingDataForUserId from "~/node_common/data/methods/get-pending-data-for-user-id";
|
||||
import removePendingDataForUserId from "~/node_common/data/methods/remove-pending-data-for-user-id";
|
||||
import deletePendingDataByUserId from "~/node_common/data/methods/delete-pending-data-by-user-id";
|
||||
import createPendingData from "~/node_common/data/methods/create-pending-data";
|
||||
|
||||
// NOTE(jim):
|
||||
@ -21,6 +21,7 @@ import getSlatesByUserId from "~/node_common/data/methods/get-slates-by-user-id"
|
||||
import updateSlateById from "~/node_common/data/methods/update-slate-by-id";
|
||||
import deleteSlatesForUserId from "~/node_common/data/methods/delete-slates-for-user-id";
|
||||
import deleteSlateById from "~/node_common/data/methods/delete-slate-by-id";
|
||||
import deleteRepostsByCid from "~/node_common/data/methods/delete-reposts-by-cid";
|
||||
|
||||
// NOTE(jim):
|
||||
// API postgres queries
|
||||
@ -80,7 +81,7 @@ export {
|
||||
getUserById,
|
||||
// NOTE(martina): Pending user upload operations
|
||||
getPendingDataForUserId,
|
||||
removePendingDataForUserId,
|
||||
deletePendingDataByUserId,
|
||||
createPendingData,
|
||||
// NOTE(jim): Slate operations.
|
||||
createSlate,
|
||||
@ -90,6 +91,7 @@ export {
|
||||
updateSlateById,
|
||||
deleteSlatesForUserId,
|
||||
deleteSlateById,
|
||||
deleteRepostsByCid,
|
||||
// NOTE(jim): API key operations,
|
||||
createAPIKeyForUserId,
|
||||
deleteAPIKeyById,
|
||||
|
36
node_common/data/methods/delete-reposts-by-cid.js
Normal file
36
node_common/data/methods/delete-reposts-by-cid.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { runQuery } from "~/node_common/data/utilities";
|
||||
|
||||
export default async ({ cid, ownerId }) => {
|
||||
return await runQuery({
|
||||
label: "DELETE_SLATES_FOR_USER_ID", //change this
|
||||
queryFn: async (DB) => {
|
||||
const hasCid = (cidValue) =>
|
||||
DB.raw(`(?? -> ??) @> ?::jsonb`, ["data", "objects", JSON.stringify({ cid: cidValue })]);
|
||||
//NOTE(martina): this is WIP. Do not use yet
|
||||
const slates = await DB.select("*").from("slates").where(hasCid(cid));
|
||||
|
||||
console.log(slates);
|
||||
return true;
|
||||
|
||||
// if (!slates || slates.error) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// let slateIds = slates.map((slate) => slate.id);
|
||||
|
||||
// const subscriptions = await DB.from("subscriptions")
|
||||
// .whereIn("target_slate_id", slateIds)
|
||||
// .del();
|
||||
|
||||
// const data = await DB.from("slates").where(hasUser(userId)).del();
|
||||
|
||||
// return 1 === data;
|
||||
},
|
||||
errorFn: async (e) => {
|
||||
return {
|
||||
error: true,
|
||||
decorator: "DELETE_SLATES_FOR_USER_ID", //chngae this
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
@ -90,13 +90,11 @@ export const addData = ({ user, files }) => {
|
||||
// TODO(jim): Since we don't support bucket organization... yet.
|
||||
// Add just pushes to the first set. But we can change this easily later.
|
||||
let noRepeats = [...files];
|
||||
|
||||
for (let i = 0; i < library.length; i++) {
|
||||
let cids = library[i].children.map((file) => file.ipfs);
|
||||
for (let j = 0; j < files.length; j++) {
|
||||
if (
|
||||
cids.includes(`/ipfs/${files[j].ipfs}`) ||
|
||||
cids.includes(files[j].ipfs)
|
||||
) {
|
||||
if (cids.includes(files[j].ipfs)) {
|
||||
noRepeats[j] = null;
|
||||
}
|
||||
}
|
||||
|
@ -80,8 +80,24 @@ export const getById = async ({ id }) => {
|
||||
});
|
||||
|
||||
let bytes = 0;
|
||||
let imageBytes = 0;
|
||||
let videoBytes = 0;
|
||||
let audioBytes = 0;
|
||||
let epubBytes = 0;
|
||||
let pdfBytes = 0;
|
||||
user.data.library[0].children.forEach((each) => {
|
||||
bytes = each.size + bytes;
|
||||
if (each.type && each.type.startsWith("image/")) {
|
||||
imageBytes += each.size;
|
||||
} else if (each.type && each.type.startsWith("video/")) {
|
||||
videoBytes += each.size;
|
||||
} else if (each.type && each.type.startsWith("audio/")) {
|
||||
audioBytes += each.size;
|
||||
} else if (each.type && each.type.startsWith("application/epub")) {
|
||||
epubBytes += each.size;
|
||||
} else if (each.type && each.type.startsWith("application/pdf")) {
|
||||
pdfBytes += each.size;
|
||||
}
|
||||
bytes += each.size;
|
||||
});
|
||||
|
||||
return {
|
||||
@ -104,6 +120,11 @@ export const getById = async ({ id }) => {
|
||||
stats: {
|
||||
bytes,
|
||||
maximumBytes: Constants.TEXTILE_ACCOUNT_BYTE_LIMIT,
|
||||
imageBytes,
|
||||
videoBytes,
|
||||
audioBytes,
|
||||
epubBytes,
|
||||
pdfBytes,
|
||||
},
|
||||
keys,
|
||||
activity,
|
||||
|
@ -24,6 +24,7 @@ export const slate = (entity) => {
|
||||
name: entity.data.name ? entity.data.name : "",
|
||||
body: entity.data.body ? entity.data.body : "",
|
||||
objects: entity.data.objects,
|
||||
layouts: entity.data.layouts,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ import JWT from "jsonwebtoken";
|
||||
import BCrypt from "bcrypt";
|
||||
|
||||
import { Buckets, PrivateKey, Pow, Client, ThreadID } from "@textile/hub";
|
||||
import { CompressedPixelFormat } from "three";
|
||||
|
||||
const BUCKET_NAME = "data";
|
||||
|
||||
@ -159,6 +160,15 @@ export const setupWithThread = async ({ buckets }) => {
|
||||
return buckets;
|
||||
};
|
||||
|
||||
export const addExistingCIDToData = async ({ buckets, key, path, cid }) => {
|
||||
try {
|
||||
await buckets.setPath(key, path || "/", cid);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// NOTE(jim): Requires @textile/hub
|
||||
export const getBucketAPIFromUserToken = async ({ user, bucketName, encrypted = false }) => {
|
||||
const token = user.data.tokens.api;
|
||||
|
@ -51,6 +51,7 @@
|
||||
"abort-controller": "^3.0.0",
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"bcrypt": "^5.0.0",
|
||||
"blurhash": "^1.1.3",
|
||||
"body-parser": "^1.19.0",
|
||||
"busboy": "^0.3.1",
|
||||
"compression": "^1.7.4",
|
||||
@ -72,6 +73,7 @@
|
||||
"pg": "^8.3.3",
|
||||
"prismjs": "^1.20.0",
|
||||
"react": "^16.13.1",
|
||||
"react-blurhash": "github:zeroxme/react-blurhash#master",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-draggable": "^4.4.3",
|
||||
"react-grid-layout": "^1.0.0",
|
||||
|
@ -4,7 +4,11 @@ import Application from "~/components/core/Application";
|
||||
|
||||
export const getServerSideProps = async ({ query }) => {
|
||||
return {
|
||||
props: { viewer: query.viewer, analytics: query.analytics },
|
||||
props: {
|
||||
viewer: query.viewer,
|
||||
analytics: query.analytics,
|
||||
mobile: query.mobile,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -14,6 +18,7 @@ export default class ApplicationPage extends React.Component {
|
||||
<Application
|
||||
viewer={this.props.viewer}
|
||||
analytics={this.props.analytics}
|
||||
mobile={this.props.mobile}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as System from "~/components/system";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Window from "~/common/window";
|
||||
import * as Actions from "~/common/actions";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { ProcessedText } from "~/components/system/components/Typography";
|
||||
@ -11,9 +11,12 @@ import { Alert } from "~/components/core/Alert";
|
||||
import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
|
||||
import WebsitePrototypeHeaderGeneric from "~/components/core/WebsitePrototypeHeaderGeneric";
|
||||
import WebsitePrototypeFooter from "~/components/core/WebsitePrototypeFooter";
|
||||
import Slate, { generateLayout } from "~/components/core/Slate";
|
||||
import { SlateLayout } from "~/components/core/SlateLayout";
|
||||
import SlateMediaObject from "~/components/core/SlateMediaObject";
|
||||
|
||||
const SIZE_LIMIT = 1000000; //NOTE(martina): 1mb limit for twitter preview images
|
||||
const DEFAULT_IMAGE = "";
|
||||
|
||||
const STYLES_ROOT = css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -26,10 +29,10 @@ const STYLES_ROOT = css`
|
||||
|
||||
const STYLES_SLATE = css`
|
||||
padding: 0 88px 0 88px;
|
||||
max-width: 1660px;
|
||||
${"" /* max-width: 1660px; */}
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0 auto 0 auto;
|
||||
margin: 48px auto 0 auto;
|
||||
min-height: 10%;
|
||||
height: 100%;
|
||||
|
||||
@ -45,12 +48,6 @@ export const getServerSideProps = async (context) => {
|
||||
};
|
||||
|
||||
export default class SlatePage extends React.Component {
|
||||
state = {
|
||||
layouts: this.props.slate.data.layouts
|
||||
? this.props.slate.data.layouts
|
||||
: { lg: generateLayout(this.props.slate.data.objects) },
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.slate) {
|
||||
return null;
|
||||
@ -71,10 +68,8 @@ export default class SlatePage extends React.Component {
|
||||
cid,
|
||||
id: each.id,
|
||||
data: each,
|
||||
editing: false,
|
||||
component: (
|
||||
<SlateMediaObject key={each.id} useImageFallback data={each} />
|
||||
),
|
||||
isOwner: false,
|
||||
component: <SlateMediaObject key={each.id} useImageFallback data={each} />,
|
||||
};
|
||||
}),
|
||||
},
|
||||
@ -88,9 +83,7 @@ export default class SlatePage extends React.Component {
|
||||
name: "slate-global-open-carousel",
|
||||
detail: {
|
||||
index,
|
||||
baseURL: `${this.props.creator.username}/${
|
||||
this.props.slate.slatename
|
||||
}`,
|
||||
baseURL: `${this.props.creator.username}/${this.props.slate.slatename}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -106,25 +99,37 @@ export default class SlatePage extends React.Component {
|
||||
},
|
||||
});
|
||||
|
||||
_handleSave = async (layouts) => {
|
||||
await Actions.updateSlate({
|
||||
id: this.props.slate.id,
|
||||
layoutOnly: true,
|
||||
data: { layouts },
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let title = `${this.props.creator.username}/${this.props.slate.slatename}`;
|
||||
let url = `https://slate.host/${this.props.creator.username}/${
|
||||
this.props.slate.slatename
|
||||
}`;
|
||||
let url = `https://slate.host/${this.props.creator.username}/${this.props.slate.slatename}`;
|
||||
let headerURL = `https://slate.host/${this.props.creator.username}`;
|
||||
let description = this.props.slate.data.body;
|
||||
|
||||
const { objects } = this.props.slate.data;
|
||||
let { objects, layouts, body, preview } = this.props.slate.data;
|
||||
|
||||
// TODO(jim): Takes the first image found
|
||||
// but we want this to be a user choice.
|
||||
let image;
|
||||
for (let i = 0; i < objects.length; i++) {
|
||||
if (objects[i].type && objects[i].type.startsWith("image/")) {
|
||||
image = objects[i].url;
|
||||
break;
|
||||
let image = preview;
|
||||
if (!image) {
|
||||
for (let i = 0; i < objects.length; i++) {
|
||||
if (
|
||||
objects[i].type &&
|
||||
objects[i].type.startsWith("image/") &&
|
||||
(!objects[i].size || objects[i].size < SIZE_LIMIT)
|
||||
) {
|
||||
image = objects[i].url.replace("https://undefined", "https://");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!image) {
|
||||
image = DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
if (!Strings.isEmpty(this.props.cid)) {
|
||||
let object = objects.find((each) => {
|
||||
@ -135,40 +140,37 @@ export default class SlatePage extends React.Component {
|
||||
|
||||
if (object) {
|
||||
title = !Strings.isEmpty(object.title) ? object.title : this.props.cid;
|
||||
description = !Strings.isEmpty(object.body)
|
||||
? Strings.elide(object.body)
|
||||
: `An object on ${url}`;
|
||||
body = !Strings.isEmpty(object.body) ? Strings.elide(object.body) : `An object on ${url}`;
|
||||
image = object.type.includes("image/") ? object.url : image;
|
||||
url = `${url}/cid:${this.props.cid}`;
|
||||
}
|
||||
}
|
||||
|
||||
const headerTitle = `${this.props.creator.username} / ${
|
||||
this.props.slate.slatename
|
||||
}`;
|
||||
const headerTitle = `${this.props.creator.username} / ${this.props.slate.slatename}`;
|
||||
|
||||
return (
|
||||
<WebsitePrototypeWrapper
|
||||
title={title}
|
||||
description={description}
|
||||
url={url}
|
||||
image={image}
|
||||
>
|
||||
<WebsitePrototypeWrapper title={title} description={body} url={url} image={image}>
|
||||
<div css={STYLES_ROOT}>
|
||||
<WebsitePrototypeHeaderGeneric href={headerURL} title={headerTitle}>
|
||||
<ProcessedText text={this.props.slate.data.body} />
|
||||
</WebsitePrototypeHeaderGeneric>
|
||||
<div css={STYLES_SLATE}>
|
||||
<Slate
|
||||
editing={false}
|
||||
layouts={this.state.layouts}
|
||||
<SlateLayout
|
||||
external
|
||||
slateId={this.props.slate.id}
|
||||
layout={layouts && layouts.ver === "2.0" ? layouts.layout : null}
|
||||
onSaveLayout={this._handleSave}
|
||||
isOwner={false}
|
||||
fileNames={layouts && layouts.ver === "2.0" ? layouts.fileNames : false}
|
||||
items={objects}
|
||||
onSelect={this._handleSelect}
|
||||
defaultLayout={layouts && layouts.ver === "2.0" ? layouts.defaultLayout : true}
|
||||
/>
|
||||
</div>
|
||||
<WebsitePrototypeFooter style={{ marginTop: 88 }} />
|
||||
</div>
|
||||
<System.GlobalCarousel />
|
||||
<System.GlobalModal />
|
||||
</WebsitePrototypeWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -54,14 +54,12 @@ export default async (req, res) => {
|
||||
|
||||
const slateId = req.params ? req.params.b : null;
|
||||
|
||||
await Data.createPendingData({
|
||||
data: finalData,
|
||||
owner_user_id: user.id,
|
||||
slate_id: slateId,
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
decorator: "SERVER_UPLOAD",
|
||||
data: finalData,
|
||||
data: {
|
||||
data: finalData,
|
||||
owner_user_id: user.id,
|
||||
slate_id: slateId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
87
pages/api/data/add.js
Normal file
87
pages/api/data/add.js
Normal file
@ -0,0 +1,87 @@
|
||||
import * as Upload from "~/node_common/upload";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
import * as Data from "~/node_common/data";
|
||||
import * as LibraryManager from "~/node_common/managers/library";
|
||||
|
||||
import { Buckets } from "@textile/hub";
|
||||
|
||||
export default async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
|
||||
const user = await Data.getUserById({
|
||||
id,
|
||||
});
|
||||
|
||||
if (!user || user.error) {
|
||||
return res
|
||||
.status(403)
|
||||
.send({ decorator: "SERVER_ADD_TO_SLATE_USER_NOT_FOUND", error: true });
|
||||
}
|
||||
|
||||
let {
|
||||
buckets,
|
||||
bucketKey,
|
||||
bucketRoot,
|
||||
bucketName,
|
||||
} = await Utilities.getBucketAPIFromUserToken({
|
||||
user,
|
||||
});
|
||||
|
||||
if (!buckets) {
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_GET_BUCKET_DATA",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (!req.body.data.items) {
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_NO_CID",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
|
||||
let userCIDs = user.data.library[0].children.map((file) =>
|
||||
file.ipfs.replace("/ipfs/", "")
|
||||
);
|
||||
let newFiles = [];
|
||||
for (let item of req.body.data.items) {
|
||||
let cid = item.cid;
|
||||
if (userCIDs.includes(cid)) {
|
||||
continue;
|
||||
}
|
||||
let response = await Utilities.addExistingCIDToData({
|
||||
buckets,
|
||||
key: bucketKey,
|
||||
path: bucketRoot.path,
|
||||
cid,
|
||||
});
|
||||
|
||||
if (response && !response.error) {
|
||||
let owner = await Data.getUserById({ id: item.ownerId });
|
||||
if (owner && !owner.error) {
|
||||
for (let file of owner.data.library[0].children) {
|
||||
if (file.cid === cid || file.ipfs === `/ipfs/${cid}`) {
|
||||
file.date = new Date();
|
||||
newFiles.push(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { updatedUserDataFields } = LibraryManager.addData({
|
||||
user,
|
||||
files: newFiles,
|
||||
});
|
||||
await Data.updateUserById({
|
||||
id: user.id,
|
||||
data: updatedUserDataFields,
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
decorator: "SERVER_ADD_EXISTING_CID_TO_DATA",
|
||||
data: true,
|
||||
});
|
||||
};
|
32
pages/api/data/create-pending.js
Normal file
32
pages/api/data/create-pending.js
Normal file
@ -0,0 +1,32 @@
|
||||
import * as Upload from "~/node_common/upload";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
import * as Data from "~/node_common/data";
|
||||
|
||||
export default async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
|
||||
if (!id) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "CREATE_PENDING_ERROR", error: true });
|
||||
}
|
||||
|
||||
const response = await Data.createPendingData(req.body.data);
|
||||
|
||||
if (!response) {
|
||||
return res
|
||||
.status(404)
|
||||
.send({ decorator: "CREATE_PENDING_ERROR", error: true });
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: response.decorator, error: response.error });
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
decorator: "CREATE_PENDING_DATA",
|
||||
data: response,
|
||||
});
|
||||
};
|
@ -11,7 +11,7 @@ export default async (req, res) => {
|
||||
.send({ decorator: "PROCESS_PENDING_ERROR", error: true });
|
||||
}
|
||||
|
||||
const response = await Data.removePendingDataForUserId({ owner_user_id: id });
|
||||
const response = await Data.deletePendingDataByUserId({ owner_user_id: id });
|
||||
|
||||
if (!response) {
|
||||
return res
|
||||
|
@ -1,194 +0,0 @@
|
||||
import * as Data from "~/node_common/data";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Social from "~/node_common/social";
|
||||
|
||||
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 } = await Utilities.getBucketAPIFromUserToken({
|
||||
user,
|
||||
});
|
||||
|
||||
if (!buckets) {
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_BUCKET_INIT_FAILURE",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(jim): Put this call into a file for all Textile related calls.
|
||||
let r = null;
|
||||
try {
|
||||
r = await buckets.list();
|
||||
} catch (e) {
|
||||
Social.sendTextileSlackMessage({
|
||||
file: "/pages/api/data/remove-multiple.js",
|
||||
user,
|
||||
message: e.message,
|
||||
code: e.code,
|
||||
functionName: `buckets.list`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!r) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_REMOVE_MULTIPLE_NO_TEXTILE", error: true });
|
||||
}
|
||||
|
||||
// TODO(jim): Put this call into a file for all Textile related calls.
|
||||
let items = null;
|
||||
try {
|
||||
items = await buckets.listIpfsPath(r[0].path);
|
||||
} catch (e) {
|
||||
Social.sendTextileSlackMessage({
|
||||
file: "/pages/api/data/remove-multiple.js",
|
||||
user,
|
||||
message: e.message,
|
||||
code: e.code,
|
||||
functionName: `buckets.listIpfsPath`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!items) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_REMOVE_MULTIPLE_NO_TEXTILE", error: true });
|
||||
}
|
||||
|
||||
let entities = [];
|
||||
for (let i = 0; i < items.items.length; i++) {
|
||||
if (req.body.data.cids.includes(items.items[i].cid)) {
|
||||
entities.push(items.items[i]);
|
||||
if (entities.length === items.items.length) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entities.length) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_REMOVE_DATA_NO_CID", error: true });
|
||||
}
|
||||
|
||||
let bucketRemoval;
|
||||
// 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) {
|
||||
Social.sendTextileSlackMessage({
|
||||
file: "/pages/api/data/remove.js",
|
||||
user: user,
|
||||
message: e.message,
|
||||
code: e.code,
|
||||
functionName: `buckets.removePath`,
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
};
|
@ -3,33 +3,10 @@ import * as Utilities from "~/node_common/utilities";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Social from "~/node_common/social";
|
||||
|
||||
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(),
|
||||
};
|
||||
});
|
||||
};
|
||||
import { read } from "fs";
|
||||
|
||||
export default async (req, res) => {
|
||||
if (Strings.isEmpty(req.body.data.cid)) {
|
||||
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 });
|
||||
@ -63,7 +40,7 @@ export default async (req, res) => {
|
||||
r = await buckets.list();
|
||||
} catch (e) {
|
||||
Social.sendTextileSlackMessage({
|
||||
file: "/pages/api/data/remove.js",
|
||||
file: "/pages/api/data/remove-multiple.js",
|
||||
user,
|
||||
message: e.message,
|
||||
code: e.code,
|
||||
@ -74,7 +51,7 @@ export default async (req, res) => {
|
||||
if (!r) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_REMOVE_DATA_NO_TEXTILE", error: true });
|
||||
.send({ decorator: "SERVER_REMOVE_MULTIPLE_NO_TEXTILE", error: true });
|
||||
}
|
||||
|
||||
// TODO(jim): Put this call into a file for all Textile related calls.
|
||||
@ -83,7 +60,7 @@ export default async (req, res) => {
|
||||
items = await buckets.listIpfsPath(r[0].path);
|
||||
} catch (e) {
|
||||
Social.sendTextileSlackMessage({
|
||||
file: "/pages/api/data/remove.js",
|
||||
file: "/pages/api/data/remove-multiple.js",
|
||||
user,
|
||||
message: e.message,
|
||||
code: e.code,
|
||||
@ -94,69 +71,79 @@ export default async (req, res) => {
|
||||
if (!items) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_REMOVE_DATA_NO_TEXTILE", error: true });
|
||||
.send({ decorator: "SERVER_REMOVE_MULTIPLE_NO_TEXTILE", error: true });
|
||||
}
|
||||
|
||||
let entity;
|
||||
let entities = [];
|
||||
for (let i = 0; i < items.items.length; i++) {
|
||||
if (items.items[i].cid === req.body.data.cid) {
|
||||
entity = items.items[i];
|
||||
break;
|
||||
if (req.body.data.cids.includes(items.items[i].cid)) {
|
||||
entities.push(items.items[i]);
|
||||
if (entities.length === items.items.length) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
if (!entities.length) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_REMOVE_DATA_NO_CID", error: true });
|
||||
}
|
||||
|
||||
let bucketRemoval;
|
||||
try {
|
||||
bucketRemoval = await buckets.removePath(bucketKey, entity.name);
|
||||
} catch (e) {
|
||||
Social.sendTextileSlackMessage({
|
||||
file: "/pages/api/data/remove.js",
|
||||
user,
|
||||
message: e.message,
|
||||
code: e.code,
|
||||
functionName: `buckets.removePath`,
|
||||
});
|
||||
// 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) {
|
||||
Social.sendTextileSlackMessage({
|
||||
file: "/pages/api/data/remove.js",
|
||||
user: user,
|
||||
message: e.message,
|
||||
code: e.code,
|
||||
functionName: `buckets.removePath`,
|
||||
});
|
||||
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_REMOVE_DATA_NO_LINK", error: true });
|
||||
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++) {
|
||||
const slate = slates[i];
|
||||
let slate = slates[i];
|
||||
|
||||
let removal = false;
|
||||
const objects = slate.data.objects.filter((o) => {
|
||||
if (o.url.includes(req.body.data.cid)) {
|
||||
removal = true;
|
||||
return 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) {
|
||||
const layouts = await Data.updateSlateById({
|
||||
let layouts = await Data.updateSlateById({
|
||||
id: slate.id,
|
||||
updated_at: new Date(),
|
||||
data: {
|
||||
...slate.data,
|
||||
objects,
|
||||
layouts: { lg: generateLayout(objects) },
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(martina):
|
||||
// Removes the reposted file from other people's slates
|
||||
for (let cid of req.body.data.cids) {
|
||||
Data.deleteRepostsByCid({ cid, ownerId: id });
|
||||
}
|
||||
|
||||
// NOTE(jim):
|
||||
// Removes the file reference from your library
|
||||
const response = await Data.updateUserById({
|
||||
@ -166,9 +153,14 @@ export default async (req, res) => {
|
||||
library: [
|
||||
{
|
||||
...user.data.library[0],
|
||||
children: user.data.library[0].children.filter(
|
||||
(o) => !o.ipfs.includes(req.body.data.cid)
|
||||
),
|
||||
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;
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -177,6 +169,6 @@ export default async (req, res) => {
|
||||
return res.status(200).send({
|
||||
decorator: "SERVER_REMOVE_DATA",
|
||||
success: true,
|
||||
bucketItems: items.items,
|
||||
bucketItems: items.itemsList,
|
||||
});
|
||||
};
|
||||
|
@ -1,31 +1,7 @@
|
||||
import * as Constants from "~/node_common/constants";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
import * as Data from "~/node_common/data";
|
||||
|
||||
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(),
|
||||
};
|
||||
});
|
||||
};
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
export default async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
@ -77,9 +53,20 @@ export default async (req, res) => {
|
||||
}
|
||||
|
||||
let slateURLs = slate.data.objects.map((file) => file.url);
|
||||
let newIPFSs = [];
|
||||
let addlObjects = newObjects
|
||||
.filter((each) => {
|
||||
|
||||
let addlObjects;
|
||||
if (req.body.fromSlate) {
|
||||
let newURLs = [];
|
||||
addlObjects = newObjects.filter((each) => {
|
||||
if (slateURLs.includes(each.url) || newURLs.includes(each.url)) {
|
||||
return false;
|
||||
}
|
||||
newURLs.push(each.url);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
let newIPFSs = [];
|
||||
addlObjects = newObjects.filter((each) => {
|
||||
if (
|
||||
slateURLs.includes(
|
||||
`${Constants.IPFS_GATEWAY_URL}/${each.ipfs.replace("/ipfs/", "")}`
|
||||
@ -90,30 +77,37 @@ export default async (req, res) => {
|
||||
}
|
||||
newIPFSs.push(each.ipfs);
|
||||
return true;
|
||||
})
|
||||
.map((each) => {
|
||||
let cid = each.ipfs.replace("/ipfs/", "");
|
||||
return {
|
||||
id: each.id,
|
||||
ownerId: user.id,
|
||||
name: each.name,
|
||||
title: each.title,
|
||||
type: each.type,
|
||||
url: `${Constants.IPFS_GATEWAY_URL}/${cid}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
addlObjects = addlObjects.map((each) => {
|
||||
let url = each.url
|
||||
? each.url
|
||||
: `${Constants.IPFS_GATEWAY_URL}/${each.ipfs.replace("/ipfs/", "")}`;
|
||||
let cid = each.cid
|
||||
? each.cid
|
||||
: each.ipfs
|
||||
? each.ipfs.replace("/ipfs/", "")
|
||||
: Strings.urlToCid(each.url);
|
||||
return {
|
||||
blurhash: each.blurhash,
|
||||
cid: cid,
|
||||
size: each.size,
|
||||
id: each.id,
|
||||
ownerId: req.body.fromSlate ? each.ownerId : user.id,
|
||||
name: each.name,
|
||||
title: each.title,
|
||||
type: each.type,
|
||||
url,
|
||||
};
|
||||
});
|
||||
|
||||
const objects = [...slate.data.objects, ...addlObjects];
|
||||
|
||||
// TODO(jim): Preserve layouts when adding.
|
||||
let layouts = { lg: generateLayout(objects) };
|
||||
|
||||
const update = await Data.updateSlateById({
|
||||
id: slate.id,
|
||||
updated_at: new Date(),
|
||||
data: {
|
||||
...slate.data,
|
||||
layouts,
|
||||
objects,
|
||||
},
|
||||
});
|
||||
|
@ -4,28 +4,34 @@ import * as Strings from "~/common/strings";
|
||||
|
||||
export default async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
if (!id) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_FIND_USER_UPDATE_SLATE", error: true });
|
||||
}
|
||||
let layoutOnly = req.body.data.layoutOnly && req.body.data.data.layouts;
|
||||
|
||||
const user = await Data.getUserById({
|
||||
id,
|
||||
});
|
||||
let user;
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).send({
|
||||
decorator: "SERVER_FIND_USER_UPDATE_SLATE_USER_NOT_FOUND",
|
||||
error: true,
|
||||
if (!layoutOnly) {
|
||||
if (!id) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_FIND_USER_UPDATE_SLATE", error: true });
|
||||
}
|
||||
|
||||
user = await Data.getUserById({
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
if (user.error) {
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_FIND_USER_UPDATE_SLATE_USER_NOT_FOUND",
|
||||
error: true,
|
||||
});
|
||||
if (!user) {
|
||||
return res.status(404).send({
|
||||
decorator: "SERVER_FIND_USER_UPDATE_SLATE_USER_NOT_FOUND",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (user.error) {
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_FIND_USER_UPDATE_SLATE_USER_NOT_FOUND",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const response = await Data.getSlateById({ id: req.body.data.id });
|
||||
@ -49,34 +55,47 @@ export default async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (!req.body.data.data.name) {
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_UPDATE_SLATE_MUST_PROVIDE_NAME",
|
||||
error: true,
|
||||
if (!layoutOnly && req.body.data.data.name) {
|
||||
const existingSlate = await Data.getSlateByName({
|
||||
slatename: req.body.data.data.name,
|
||||
ownerId: user.id,
|
||||
});
|
||||
if (existingSlate && existingSlate.id !== req.body.data.id) {
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_UPDATE_SLATE_NAME_TAKEN",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const existingSlate = await Data.getSlateByName({
|
||||
slatename: req.body.data.data.name,
|
||||
ownerId: user.id,
|
||||
});
|
||||
if (existingSlate && existingSlate.id !== req.body.data.id) {
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_UPDATE_SLATE_NAME_TAKEN",
|
||||
error: true,
|
||||
let isOwner = id && response.data.ownerId === id;
|
||||
|
||||
let slate;
|
||||
if (!isOwner && req.body.data.data.layouts) {
|
||||
slate = await Data.updateSlateById({
|
||||
id: response.id,
|
||||
slatename: response.slatename,
|
||||
updated_at: response.updated_at,
|
||||
data: {
|
||||
...response.data,
|
||||
layouts: req.body.data.data.layouts,
|
||||
},
|
||||
});
|
||||
} else if (isOwner) {
|
||||
slate = await Data.updateSlateById({
|
||||
id: response.id,
|
||||
slatename: req.body.data.data.name
|
||||
? Strings.createSlug(req.body.data.data.name)
|
||||
: response.slatename,
|
||||
updated_at:
|
||||
layoutOnly || req.body.data.autoSave ? response.updated_at : new Date(),
|
||||
data: {
|
||||
...response.data,
|
||||
...req.body.data.data,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const slate = await Data.updateSlateById({
|
||||
id: response.id,
|
||||
slatename: Strings.createSlug(req.body.data.data.name),
|
||||
updated_at: new Date(),
|
||||
data: {
|
||||
...response.data,
|
||||
...req.body.data.data,
|
||||
},
|
||||
});
|
||||
|
||||
if (!slate) {
|
||||
return res
|
||||
.status(404)
|
||||
|
@ -16,21 +16,15 @@ export default async (req, res) => {
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
return res
|
||||
.status(403)
|
||||
.send({ decorator: "SERVER_EXISTING_USER_ALREADY", error: true });
|
||||
return res.status(403).send({ decorator: "SERVER_EXISTING_USER_ALREADY", error: true });
|
||||
}
|
||||
|
||||
if (!Validations.username(req.body.data.username)) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_INVALID_USERNAME", error: true });
|
||||
return res.status(500).send({ decorator: "SERVER_INVALID_USERNAME", error: true });
|
||||
}
|
||||
|
||||
if (!Validations.password(req.body.data.password)) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_INVALID_PASSWORD", error: true });
|
||||
return res.status(500).send({ decorator: "SERVER_INVALID_PASSWORD", error: true });
|
||||
}
|
||||
|
||||
const rounds = Number(Environment.LOCAL_PASSWORD_ROUNDS);
|
||||
@ -47,11 +41,7 @@ export default async (req, res) => {
|
||||
// Don't do this once you refactor.
|
||||
const newUsername = req.body.data.username.toLowerCase();
|
||||
|
||||
const {
|
||||
buckets,
|
||||
bucketKey,
|
||||
bucketName,
|
||||
} = await Utilities.getBucketAPIFromUserToken({
|
||||
const { buckets, bucketKey, bucketName } = await Utilities.getBucketAPIFromUserToken({
|
||||
user: {
|
||||
username: newUsername,
|
||||
data: { tokens: { api } },
|
||||
@ -59,9 +49,7 @@ export default async (req, res) => {
|
||||
});
|
||||
|
||||
if (!buckets) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_BUCKET_INIT_FAILURE", error: true });
|
||||
return res.status(500).send({ decorator: "SERVER_BUCKET_INIT_FAILURE", error: true });
|
||||
}
|
||||
|
||||
const photo = await SlateManager.getRandomSlateElementURL({
|
||||
@ -76,7 +64,7 @@ export default async (req, res) => {
|
||||
username: newUsername,
|
||||
data: {
|
||||
photo,
|
||||
body: "A user of Slate.",
|
||||
body: "",
|
||||
settings_deals_auto_approve: false,
|
||||
allow_filecoin_directory_listing: false,
|
||||
allow_automatic_data_storage: true,
|
||||
@ -87,15 +75,11 @@ export default async (req, res) => {
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res
|
||||
.status(404)
|
||||
.send({ decorator: "SERVER_USER_CREATE_USER_NOT_FOUND", error: true });
|
||||
return res.status(404).send({ decorator: "SERVER_USER_CREATE_USER_NOT_FOUND", error: true });
|
||||
}
|
||||
|
||||
if (user.error) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_USER_CREATE_USER_NOT_FOUND", error: true });
|
||||
return res.status(500).send({ decorator: "SERVER_USER_CREATE_USER_NOT_FOUND", error: true });
|
||||
}
|
||||
|
||||
const userProfileURL = `https://slate.host/${user.username}`;
|
||||
|
@ -4,31 +4,7 @@ import * as LibraryManager from "~/node_common/managers/library";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Upload from "~/node_common/upload";
|
||||
|
||||
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(),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// NOTE(jim): To support multipart request.
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
@ -94,9 +70,7 @@ export default async (req, res) => {
|
||||
}
|
||||
|
||||
if (!uploadResponse) {
|
||||
return res
|
||||
.status(413)
|
||||
.send({ decorator: "V1_SERVER_API_UPLOAD_ERROR", error: true });
|
||||
return res.status(413).send({ decorator: "V1_SERVER_API_UPLOAD_ERROR", error: true });
|
||||
}
|
||||
|
||||
if (uploadResponse.error) {
|
||||
@ -152,16 +126,12 @@ export default async (req, res) => {
|
||||
};
|
||||
const objects = [...slate.data.objects, newSlateObjectEntity];
|
||||
|
||||
// TODO(jim): Preserve layouts when adding.
|
||||
let layouts = { lg: generateLayout(objects) };
|
||||
|
||||
const updatedSlate = await Data.updateSlateById({
|
||||
id: slate.id,
|
||||
updated_at: new Date(),
|
||||
data: {
|
||||
...slate.data,
|
||||
objects,
|
||||
layouts,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -203,7 +203,7 @@ export default class SceneProfile extends React.Component {
|
||||
? null
|
||||
: buttons
|
||||
}
|
||||
editing={this.props.viewer.username === this.props.data.username}
|
||||
isOwner={this.props.viewer.username === this.props.data.username}
|
||||
/>
|
||||
</ScenePage>
|
||||
);
|
||||
|
@ -14,10 +14,11 @@ import {
|
||||
ButtonSecondary,
|
||||
} from "~/components/system/components/Buttons";
|
||||
import { dispatchCustomEvent } from "~/common/custom-events";
|
||||
import { SlateLayout } from "~/components/core/SlateLayout";
|
||||
import { SlateLayoutMobile } from "~/components/core/SlateLayoutMobile";
|
||||
|
||||
import ScenePage from "~/components/core/ScenePage";
|
||||
import ScenePageHeader from "~/components/core/ScenePageHeader";
|
||||
import Slate, { generateLayout } from "~/components/core/Slate";
|
||||
import SlateMediaObject from "~/components/core/SlateMediaObject";
|
||||
import CircleButtonGray from "~/components/core/CircleButtonGray";
|
||||
import EmptyState from "~/components/core/EmptyState";
|
||||
@ -28,12 +29,6 @@ const STYLES_ICONS = css`
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const STYLES_ACTIONS = css`
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
padding-left: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_USERNAME = css`
|
||||
cursor: pointer;
|
||||
|
||||
@ -54,23 +49,6 @@ const STYLES_MOBILE_ONLY = css`
|
||||
}
|
||||
`;
|
||||
|
||||
const moveIndex = (set, fromIndex, toIndex) => {
|
||||
const element = set[fromIndex];
|
||||
set.splice(fromIndex, 1);
|
||||
set.splice(toIndex, 0, element);
|
||||
|
||||
return set;
|
||||
};
|
||||
|
||||
const setStateData = (source) => {
|
||||
return {
|
||||
objects: source.data.objects,
|
||||
layouts: source.data.layouts
|
||||
? source.data.layouts
|
||||
: { lg: generateLayout(source.data.objects) },
|
||||
};
|
||||
};
|
||||
|
||||
let isMounted = false;
|
||||
|
||||
export default class SceneSlate extends React.Component {
|
||||
@ -78,29 +56,29 @@ export default class SceneSlate extends React.Component {
|
||||
_remoteLock = false;
|
||||
|
||||
state = {
|
||||
...setStateData(this.props.current, this.props.viewer),
|
||||
...(this.props.current, this.props.viewer),
|
||||
loading: false,
|
||||
saving: "IDLE",
|
||||
editing: this.props.current.data.ownerId === this.props.viewer.id,
|
||||
isOwner: this.props.current.data.ownerId === this.props.viewer.id,
|
||||
editing: false,
|
||||
followLoading: false,
|
||||
};
|
||||
|
||||
// NOTE(jim):
|
||||
// The purpose of this is to update the Scene appropriately when
|
||||
// it changes but isn't mounted.
|
||||
componentDidUpdate(prevProps) {
|
||||
async componentDidUpdate(prevProps) {
|
||||
if (prevProps.current.id !== this.props.current.id) {
|
||||
this.setState({
|
||||
...setStateData(this.props.current, this.props.viewer),
|
||||
await this.setState({
|
||||
loading: false,
|
||||
saving: "IDLE",
|
||||
editing: this.props.current.data.ownerId === this.props.viewer.id,
|
||||
isOwner: this.props.current.data.ownerId === this.props.viewer.id,
|
||||
});
|
||||
|
||||
this._handleUpdateCarousel({
|
||||
objects: this.props.current.data.objects,
|
||||
editing: this.state.editing,
|
||||
});
|
||||
this._handleUpdateCarousel(
|
||||
this.props.current.data.objects,
|
||||
this.state.isOwner
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,13 +86,13 @@ export default class SceneSlate extends React.Component {
|
||||
if (isMounted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
isMounted = true;
|
||||
|
||||
this._handleUpdateCarousel(this.state);
|
||||
this._handleUpdateCarousel();
|
||||
|
||||
window.addEventListener(
|
||||
"remote-update-slate-screen",
|
||||
this._handleRemoteUpdate
|
||||
this._handleRemoteAddObject
|
||||
);
|
||||
window.addEventListener(
|
||||
"remote-delete-object",
|
||||
@ -122,8 +100,27 @@ export default class SceneSlate extends React.Component {
|
||||
);
|
||||
window.addEventListener(
|
||||
"remote-object-update",
|
||||
this._handleRemoteSaveObject
|
||||
this._handleRemoteEditObject
|
||||
);
|
||||
|
||||
if (this.state.isOwner) {
|
||||
let changed = false;
|
||||
let objects = [...this.props.current.data.objects];
|
||||
for (let obj of objects) {
|
||||
if (!obj.size) {
|
||||
let matches = this.props.viewer.library[0].children.filter((file) => {
|
||||
return file.id === obj.id;
|
||||
});
|
||||
if (matches.length) {
|
||||
obj.size = matches[0].size;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
this._handleSave(null, objects, null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -131,7 +128,7 @@ export default class SceneSlate extends React.Component {
|
||||
|
||||
window.removeEventListener(
|
||||
"remote-update-slate-screen",
|
||||
this._handleRemoteUpdate
|
||||
this._handleRemoteAddObject
|
||||
);
|
||||
window.removeEventListener(
|
||||
"remote-delete-object",
|
||||
@ -139,51 +136,12 @@ export default class SceneSlate extends React.Component {
|
||||
);
|
||||
window.removeEventListener(
|
||||
"remote-object-update",
|
||||
this._handleRemoteSaveObject
|
||||
this._handleRemoteEditObject
|
||||
);
|
||||
}
|
||||
|
||||
_handleRemoteUpdate = async ({ detail }) => {
|
||||
if (!this._remoteLock) {
|
||||
this._remoteLock = true;
|
||||
const response = await Actions.getSlateById({
|
||||
id: this.props.current.id,
|
||||
});
|
||||
|
||||
if (!response) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: {
|
||||
message:
|
||||
"We're having trouble refreshing right now. Please try again later",
|
||||
},
|
||||
},
|
||||
});
|
||||
this._remoteLock = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: {
|
||||
decorator: response.error,
|
||||
},
|
||||
},
|
||||
});
|
||||
this._remoteLock = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ layouts: null, objects: null });
|
||||
|
||||
const { slate } = response;
|
||||
|
||||
await this._handleSave(null, slate.data.objects, slate.data.layouts);
|
||||
this._remoteLock = false;
|
||||
}
|
||||
_handleRemoteAddObject = () => {
|
||||
this._handleUpdateCarousel();
|
||||
};
|
||||
|
||||
_handleFollow = () => {
|
||||
@ -196,76 +154,81 @@ export default class SceneSlate extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
_handleChangeLayout = async (layout, layouts) => {
|
||||
this.setState({ layouts, saving: "IDLE" });
|
||||
_handleSaveLayout = async (layouts, autoSave) => {
|
||||
await this._handleSave(null, null, layouts, autoSave);
|
||||
};
|
||||
|
||||
_handleSaveLayout = async () => {
|
||||
await this._handleSave(null, null, this.state.layouts);
|
||||
};
|
||||
|
||||
_handleMoveIndex = async (from, to) => {
|
||||
const objects = moveIndex(this.state.objects, from.index, to.index);
|
||||
this.setState({ objects });
|
||||
await this._handleSave(null, objects);
|
||||
};
|
||||
|
||||
_handleSave = async (e, objects, layouts) => {
|
||||
_handleSave = async (e, objects, layouts, autoSave = false, preview) => {
|
||||
this.setState({ loading: true, saving: "SAVING" });
|
||||
|
||||
let layoutOnly = layouts && !objects;
|
||||
|
||||
let data = {};
|
||||
if (objects) {
|
||||
data.objects = objects;
|
||||
}
|
||||
if (layouts) {
|
||||
data.layouts = layouts;
|
||||
}
|
||||
if (preview) {
|
||||
data.preview = preview;
|
||||
}
|
||||
const response = await Actions.updateSlate({
|
||||
id: this.props.current.id,
|
||||
data: {
|
||||
name: this.props.current.data.name,
|
||||
objects: objects ? objects : this.state.objects,
|
||||
layouts: layouts ? layouts : this.state.layouts,
|
||||
},
|
||||
layoutOnly,
|
||||
autoSave,
|
||||
data,
|
||||
});
|
||||
|
||||
if (!response) {
|
||||
this.setState({ loading: false, saving: "ERROR" });
|
||||
System.dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: {
|
||||
message:
|
||||
"We're having trouble connecting right now. Please try again later",
|
||||
if (!autoSave) {
|
||||
if (!response) {
|
||||
this.setState({ loading: false, saving: "ERROR" });
|
||||
System.dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: {
|
||||
message:
|
||||
"We're having trouble connecting right now. Please try again later",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
this.setState({ loading: false, saving: "ERROR" });
|
||||
System.dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: { alert: { decorator: response.decorator } },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
this.setState({ loading: false, saving: "ERROR" });
|
||||
System.dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: { alert: { decorator: response.decorator } },
|
||||
});
|
||||
if (this.state.isOwner) {
|
||||
await this.props.onRehydrate();
|
||||
}
|
||||
|
||||
await this.props.onRehydrate();
|
||||
|
||||
this.setState({
|
||||
saving: "SAVED",
|
||||
layouts: layouts ? layouts : this.state.layouts,
|
||||
objects: objects ? objects : this.state.objects,
|
||||
});
|
||||
|
||||
this._handleUpdateCarousel({
|
||||
objects: objects ? objects : this.state.objects,
|
||||
editing: this.state.editing,
|
||||
});
|
||||
if (!layoutOnly) {
|
||||
this._handleUpdateCarousel(
|
||||
objects ? objects : this.props.current.data.objects,
|
||||
this.state.isOwner
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
_handleRemoteSaveObject = async ({ detail }) => {
|
||||
_handleRemoteEditObject = async ({ detail }) => {
|
||||
const { object } = detail;
|
||||
console.log(object);
|
||||
|
||||
System.dispatchCustomEvent({
|
||||
name: "state-global-carousel-loading",
|
||||
detail: { saving: true },
|
||||
});
|
||||
|
||||
const objects = [...this.state.objects];
|
||||
const objects = [...this.props.current.data.objects];
|
||||
for (let i = 0; i < objects.length; i++) {
|
||||
if (objects[i].id === object.id) {
|
||||
objects[i] = object;
|
||||
@ -281,9 +244,10 @@ export default class SceneSlate extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
_handleUpdateCarousel = (state) => {
|
||||
const objects = [...state.objects];
|
||||
|
||||
_handleUpdateCarousel = (newObjects, isOwner) => {
|
||||
let objects = newObjects
|
||||
? newObjects
|
||||
: [...this.props.current.data.objects];
|
||||
System.dispatchCustomEvent({
|
||||
name: "slate-global-create-carousel",
|
||||
detail: {
|
||||
@ -298,7 +262,7 @@ export default class SceneSlate extends React.Component {
|
||||
onDelete: () =>
|
||||
System.dispatchCustomEvent({
|
||||
name: "remote-delete-object",
|
||||
detail: { id: data.id },
|
||||
detail: { ids: [data.id] },
|
||||
}),
|
||||
onObjectSave: (object) =>
|
||||
System.dispatchCustomEvent({
|
||||
@ -310,7 +274,7 @@ export default class SceneSlate extends React.Component {
|
||||
data,
|
||||
username: this.props.viewer.username,
|
||||
slatename: this.props.current.slatename,
|
||||
editing: this.state.editing,
|
||||
isOwner: isOwner ? isOwner : this.state.isOwner,
|
||||
component: <SlateMediaObject key={each.id} data={data} />,
|
||||
};
|
||||
}),
|
||||
@ -318,27 +282,56 @@ export default class SceneSlate extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
_handleRemoteDeleteObject = async ({ detail }) => {
|
||||
const { id } = detail;
|
||||
_handleDeleteFiles = async (cids) => {
|
||||
const message = `Are you sure you want to delete these files? They will be deleted from your slates as well`;
|
||||
if (!window.confirm(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (response.error) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: { alert: { decorator: response.decorator } },
|
||||
});
|
||||
return;
|
||||
}
|
||||
await this.props.onRehydrate();
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: { message: "Files successfully deleted!", status: "INFO" },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
_handleRemoteDeleteObject = async ({ detail }) => {
|
||||
console.log(detail.ids);
|
||||
System.dispatchCustomEvent({
|
||||
name: "state-global-carousel-loading",
|
||||
detail: { loading: true },
|
||||
});
|
||||
|
||||
const objects = this.state.objects.filter((o, i) => {
|
||||
return o.id !== id;
|
||||
let objects = this.props.current.data.objects.filter((obj) => {
|
||||
return !detail.ids.includes(obj.id);
|
||||
});
|
||||
|
||||
// TODO(jim): This is a brute force way to handle this.
|
||||
const layouts = { lg: generateLayout(objects) };
|
||||
|
||||
const response = await Actions.updateSlate({
|
||||
id: this.props.current.id,
|
||||
data: {
|
||||
name: this.props.current.data.name,
|
||||
objects,
|
||||
layouts,
|
||||
},
|
||||
});
|
||||
|
||||
@ -356,8 +349,8 @@ export default class SceneSlate extends React.Component {
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
System.dispatchCustomEvent({
|
||||
name: "state-global-carousel-loading",
|
||||
@ -367,17 +360,10 @@ export default class SceneSlate extends React.Component {
|
||||
name: "create-alert",
|
||||
detail: { alert: { decorator: response.decorator } },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._handleUpdateCarousel({
|
||||
objects: response.slate.data.objects,
|
||||
editing: this.state.editing,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
objects: response.slate.data.objects,
|
||||
layouts: response.slate.data.layouts,
|
||||
});
|
||||
this._handleUpdateCarousel(response.slate.data.objects);
|
||||
|
||||
await this.props.onRehydrate();
|
||||
|
||||
@ -399,10 +385,35 @@ export default class SceneSlate extends React.Component {
|
||||
value: "SIDEBAR_ADD_FILE_TO_BUCKET",
|
||||
data: this.props.current,
|
||||
});
|
||||
this._handleUpdateCarousel({
|
||||
objects: this.props.current.data.objects,
|
||||
editing: this.state.editing,
|
||||
});
|
||||
};
|
||||
|
||||
_handleSaveCopy = async (items) => {
|
||||
this.setState({ loading: true });
|
||||
let response = await Actions.addCIDToData({ items });
|
||||
|
||||
if (!response) {
|
||||
this.setState({ loading: false, saving: "ERROR" });
|
||||
System.dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: {
|
||||
message:
|
||||
"We're having trouble connecting right now. Please try again later",
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (response.error) {
|
||||
this.setState({ loading: false, saving: "ERROR" });
|
||||
System.dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: { alert: { decorator: response.decorator } },
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({ loading: false, saving: "SAVED" });
|
||||
this.props.onRehydrate();
|
||||
};
|
||||
|
||||
_handleShowSettings = () => {
|
||||
@ -413,41 +424,37 @@ export default class SceneSlate extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
_handleSlateLink = async () => {
|
||||
//NOTE(martina): not needed if links only happen on your own slate (know your own username already)
|
||||
// const response = await Actions.getUsername({
|
||||
// id: this.props.current.data.ownerId,
|
||||
// });
|
||||
// const username = response.data;
|
||||
|
||||
return window.open(
|
||||
`/${this.props.viewer.username}/${this.props.current.slatename}`
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { id, user, data, slatename } = this.props.current;
|
||||
const { body = "" } = data;
|
||||
const { objects, layouts } = this.state;
|
||||
const { user, data } = this.props.current;
|
||||
const { body = "", preview } = data;
|
||||
let objects = this.props.current.data.objects;
|
||||
let layouts = this.props.current.data.layouts;
|
||||
const isPublic = data.public;
|
||||
|
||||
let following = !!this.props.viewer.subscriptions.filter((subscription) => {
|
||||
return subscription.target_slate_id === this.props.current.id;
|
||||
}).length;
|
||||
|
||||
let onMobile = Validations.onMobile();
|
||||
let actions = this.state.editing ? (
|
||||
<span css={STYLES_ACTIONS}>
|
||||
let actions = this.state.isOwner ? (
|
||||
<span>
|
||||
<CircleButtonGray onClick={this._handleAdd} style={{ marginRight: 16 }}>
|
||||
<SVG.Plus height="16px" />
|
||||
</CircleButtonGray>
|
||||
{isPublic ? (
|
||||
<CircleButtonGray
|
||||
onClick={() => this._handleSlateLink()}
|
||||
style={{ marginRight: 16 }}
|
||||
<a
|
||||
href={
|
||||
user
|
||||
? `/${user.username}/${this.props.current.slatename}`
|
||||
: this.state.isOwner
|
||||
? `/${this.props.viewer.username}/${this.props.current.slatename}`
|
||||
: ""
|
||||
}
|
||||
target="_blank"
|
||||
>
|
||||
<SVG.DeepLink height="16px" />
|
||||
</CircleButtonGray>
|
||||
<CircleButtonGray style={{ marginRight: 16 }}>
|
||||
<SVG.Upload height="16px" />
|
||||
</CircleButtonGray>
|
||||
</a>
|
||||
) : null}
|
||||
<CircleButtonGray onClick={this._handleShowSettings}>
|
||||
<SVG.Settings height="16px" />
|
||||
@ -475,12 +482,9 @@ export default class SceneSlate extends React.Component {
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<ScenePage
|
||||
style={{ paddingLeft: "24px", paddingRight: "24px" }}
|
||||
contentstyle={{ maxWidth: "1660px" }}
|
||||
>
|
||||
<ScenePage contentstyle={{ maxWidth: "1660px" }}>
|
||||
<ScenePageHeader
|
||||
style={{ padding: `0 24px 0 24px` }}
|
||||
wide
|
||||
title={
|
||||
user ? (
|
||||
<span>
|
||||
@ -505,23 +509,71 @@ export default class SceneSlate extends React.Component {
|
||||
}
|
||||
actions={<span css={STYLES_MOBILE_HIDDEN}>{actions}</span>}
|
||||
>
|
||||
<ProcessedText text={body} />
|
||||
<span
|
||||
style={{
|
||||
color: Constants.system.darkGray,
|
||||
fontFamily: Constants.font.medium,
|
||||
}}
|
||||
>
|
||||
<ProcessedText text={body} />
|
||||
</span>
|
||||
</ScenePageHeader>
|
||||
<span css={STYLES_MOBILE_ONLY}>{actions}</span>
|
||||
{objects && objects.length ? (
|
||||
layouts ? (
|
||||
<Slate
|
||||
editing={onMobile ? false : this.state.editing}
|
||||
saving={this.state.saving}
|
||||
this.props.mobile ? (
|
||||
<SlateLayoutMobile
|
||||
isOwner={this.state.isOwner}
|
||||
items={objects}
|
||||
layouts={layouts}
|
||||
onLayoutChange={this._handleChangeLayout}
|
||||
onLayoutSave={this._handleSaveLayout}
|
||||
onMoveIndex={this._handleMoveIndex}
|
||||
fileNames={
|
||||
layouts && layouts.ver === "2.0" ? layouts.fileNames : false
|
||||
}
|
||||
onSelect={this._handleSelect}
|
||||
/>
|
||||
) : null
|
||||
) : this.state.editing ? (
|
||||
) : (
|
||||
<div style={{ marginTop: this.state.isOwner ? 24 : 48 }}>
|
||||
<SlateLayout
|
||||
link={
|
||||
user
|
||||
? `${window.location.hostname}${
|
||||
window.location.port ? ":" + window.location.port : ""
|
||||
}/${user.username}/${this.props.current.slatename}`
|
||||
: this.state.isOwner
|
||||
? `${window.location.hostname}${
|
||||
window.location.port ? ":" + window.location.port : ""
|
||||
}/${this.props.viewer.username}/${
|
||||
this.props.current.slatename
|
||||
}`
|
||||
: ""
|
||||
}
|
||||
viewer={this.props.viewer}
|
||||
slateId={this.props.current.id}
|
||||
layout={
|
||||
layouts && layouts.ver === "2.0" ? layouts.layout || [] : null
|
||||
}
|
||||
onSaveLayout={this._handleSaveLayout}
|
||||
isOwner={this.state.isOwner}
|
||||
fileNames={
|
||||
layouts && layouts.ver === "2.0" ? layouts.fileNames : false
|
||||
}
|
||||
preview={preview}
|
||||
onSavePreview={(preview) =>
|
||||
this._handleSave(null, null, null, false, preview)
|
||||
}
|
||||
items={objects}
|
||||
onSelect={this._handleSelect}
|
||||
defaultLayout={
|
||||
layouts && layouts.ver === "2.0"
|
||||
? layouts.defaultLayout
|
||||
: true
|
||||
}
|
||||
onAction={this.props.onAction}
|
||||
onRemoveFromSlate={this._handleRemoteDeleteObject}
|
||||
onDeleteFiles={this._handleDeleteFiles}
|
||||
onSaveCopy={this._handleSaveCopy}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
) : this.state.isOwner ? (
|
||||
<div style={{ padding: "24px" }}>
|
||||
<EmptyState>
|
||||
<div css={STYLES_ICONS}>
|
||||
@ -536,7 +588,11 @@ export default class SceneSlate extends React.Component {
|
||||
</div>
|
||||
</EmptyState>
|
||||
</div>
|
||||
) : null}
|
||||
) : (
|
||||
<div style={{ padding: "24px" }}>
|
||||
<EmptyState>There's nothing here :)</EmptyState>
|
||||
</div>
|
||||
)}
|
||||
</ScenePage>
|
||||
);
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ export default class SceneSlates extends React.Component {
|
||||
{this.state.tab === 0 ? (
|
||||
this.props.viewer.slates && this.props.viewer.slates.length ? (
|
||||
<SlatePreviewBlocks
|
||||
editing
|
||||
isOwner
|
||||
slates={this.props.viewer.slates}
|
||||
username={this.props.viewer.username}
|
||||
onAction={this.props.onAction}
|
||||
@ -132,7 +132,7 @@ export default class SceneSlates extends React.Component {
|
||||
<SlatePreviewBlock
|
||||
slate={slate}
|
||||
username={this.props.viewer.username}
|
||||
editing
|
||||
isOwner
|
||||
/>
|
||||
</div>
|
||||
)) */}
|
||||
|
12
server.js
12
server.js
@ -76,6 +76,8 @@ app.prepare().then(async () => {
|
||||
});
|
||||
|
||||
server.get("/_", async (req, res) => {
|
||||
let mobile = Validations.onMobile(req.headers["user-agent"]);
|
||||
|
||||
const isBucketsAvailable = await Utilities.checkTextile();
|
||||
|
||||
if (!isBucketsAvailable) {
|
||||
@ -95,6 +97,7 @@ app.prepare().then(async () => {
|
||||
return app.render(req, res, "/_", {
|
||||
viewer,
|
||||
analytics,
|
||||
mobile,
|
||||
});
|
||||
});
|
||||
|
||||
@ -117,9 +120,11 @@ app.prepare().then(async () => {
|
||||
server.all("/_/:a/:b", async (r, s) => handler(r, s, r.url));
|
||||
|
||||
server.get("/:username", async (req, res) => {
|
||||
let mobile = Validations.onMobile(req.headers["user-agent"]);
|
||||
|
||||
// TODO(jim): Temporary workaround
|
||||
if (!Validations.userRoute(req.params.username)) {
|
||||
return handler(req, res, req.url);
|
||||
return handler(req, res, req.url, { mobile });
|
||||
}
|
||||
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
@ -151,13 +156,15 @@ app.prepare().then(async () => {
|
||||
return app.render(req, res, "/_/profile", {
|
||||
viewer,
|
||||
creator: Serializers.user({ ...creator, slates }),
|
||||
mobile,
|
||||
});
|
||||
});
|
||||
|
||||
server.get("/:username/:slatename", async (req, res) => {
|
||||
let mobile = Validations.onMobile(req.headers["user-agent"]);
|
||||
// TODO(jim): Temporary workaround
|
||||
if (!Validations.userRoute(req.params.username)) {
|
||||
return handler(req, res, req.url);
|
||||
return handler(req, res, req.url, { mobile });
|
||||
}
|
||||
|
||||
const slate = await Data.getSlateByName({
|
||||
@ -204,6 +211,7 @@ app.prepare().then(async () => {
|
||||
viewer,
|
||||
creator: Serializers.user(creator),
|
||||
slate,
|
||||
mobile,
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user