Merge pull request #378 from filecoin-project/@martinalong/new-grid-layout

@martinalong/new grid layout
This commit is contained in:
martinalong 2020-10-21 16:06:36 -07:00 committed by GitHub
commit 516035a4ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 3197 additions and 1216 deletions

View File

@ -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 }),
});

View File

@ -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,

View File

@ -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 }) => {

View File

@ -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",

View File

@ -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);

View File

@ -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>
);

View File

@ -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
);
};

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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}

View 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>
);
}
}

View File

@ -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>
);

View File

@ -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;
`;

View File

@ -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";

View File

@ -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>
);
}
}

File diff suppressed because it is too large Load Diff

View 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>
);
}
}

View File

@ -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>

View File

@ -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"

View File

@ -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}

View 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>
);
};

View File

@ -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)}>

View File

@ -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({

View File

@ -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}
/>

View File

@ -10,7 +10,6 @@ import { css } from "@emotion/react";
const STYLES_HEADER = css`
font-family: ${Constants.font.semiBold};
font-size: 18px;
margin-top: 32px;
`;

View File

@ -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

View File

@ -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;

View File

@ -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}

View File

@ -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}

View File

@ -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;
`;

View File

@ -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>
);
}
}

View File

@ -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,

View 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
};
},
});
};

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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,
},
};
};

View File

@ -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;

View File

@ -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",

View File

@ -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}
/>
);
}

View File

@ -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>
);
}

View File

@ -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
View 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,
});
};

View 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,
});
};

View File

@ -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

View File

@ -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,
});
};

View File

@ -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,
});
};

View File

@ -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,
},
});

View File

@ -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)

View File

@ -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}`;

View File

@ -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,
},
});

View File

@ -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>
);

View File

@ -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>
);
}

View File

@ -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>
)) */}

View File

@ -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,
});
});