mirror of
https://github.com/filecoin-project/slate.git
synced 2024-09-11 22:07:08 +03:00
added standard typography styles
This commit is contained in:
parent
a01c262f6b
commit
17f1ca18cf
@ -19,7 +19,7 @@ export const init = ({ resource = "", viewer, onUpdate, onNewActiveUser = () =>
|
||||
console.log(`${resource}: init`);
|
||||
|
||||
if (client) {
|
||||
console.log("ERROR: Already have client !?");
|
||||
console.log("ERROR: Already has websocket client");
|
||||
return client;
|
||||
}
|
||||
|
||||
@ -31,7 +31,6 @@ export const init = ({ resource = "", viewer, onUpdate, onNewActiveUser = () =>
|
||||
}
|
||||
|
||||
const payload = { type: "SUBSCRIBE_VIEWER", data: { id: viewer.id } };
|
||||
console.log(payload);
|
||||
client.send(JSON.stringify(payload));
|
||||
});
|
||||
|
||||
@ -76,7 +75,7 @@ export const init = ({ resource = "", viewer, onUpdate, onNewActiveUser = () =>
|
||||
}
|
||||
|
||||
if (type === "UPDATE") {
|
||||
onUpdate(data);
|
||||
onUpdate({ viewer: data });
|
||||
}
|
||||
|
||||
if (type === "UPDATE_USERS_ONLINE" && typeof onNewActiveUser === "function") {
|
||||
@ -86,7 +85,7 @@ export const init = ({ resource = "", viewer, onUpdate, onNewActiveUser = () =>
|
||||
|
||||
client.addEventListener("close", (e) => {
|
||||
if (e.reason === "SIGN_OUT") {
|
||||
window.location.replace("/_");
|
||||
window.location.replace("/_/auth");
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
client = null;
|
||||
|
@ -61,6 +61,7 @@ export const system = {
|
||||
active: "#00BB00",
|
||||
blurBlack: "#262626",
|
||||
bgBlurGray: "#403F42",
|
||||
grayLight2: "#AEAEB2",
|
||||
};
|
||||
|
||||
export const shadow = {
|
||||
@ -78,6 +79,7 @@ export const zindex = {
|
||||
header: 4,
|
||||
modal: 6,
|
||||
tooltip: 7,
|
||||
cta: 8,
|
||||
};
|
||||
|
||||
export const font = {
|
||||
@ -131,3 +133,5 @@ export const filetypes = {
|
||||
videos: ["video/mpeg", "video/webm", "video/quicktime"],
|
||||
books: ["application/pdf", "application/epub+zip", "application/vnd.amazon.ebook"],
|
||||
};
|
||||
|
||||
export const linkPreviewSizeLimit = 5000000; //NOTE(martina): 5mb limit for twitter preview images
|
||||
|
@ -1,146 +1,205 @@
|
||||
import * as Actions from "~/common/actions";
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
// NOTE(jim):
|
||||
// Recursion for nested entities (any number).
|
||||
export const getCurrent = ({ id, data }) => {
|
||||
let target = null;
|
||||
let activeIds = {};
|
||||
export const getById = (id, viewer) => {
|
||||
let target;
|
||||
|
||||
const findById = (state, id) => {
|
||||
for (let i = 0; i < state.length; i++) {
|
||||
if (state[i].id === id) {
|
||||
target = state[i];
|
||||
activeIds[state[i].id] = true;
|
||||
if (id) {
|
||||
target = navigation.find((each) => each.id === id);
|
||||
}
|
||||
if (!target) {
|
||||
return { ...errorPage };
|
||||
}
|
||||
|
||||
if (target.id === "NAV_SLATE") {
|
||||
target.slateId = data && data.id;
|
||||
}
|
||||
}
|
||||
if (viewer && target.id === authPage.id) {
|
||||
return { ...activityPage }; //NOTE(martina): authenticated users should be redirected to the home page rather than the
|
||||
}
|
||||
|
||||
// if (!target && state[i].children) {
|
||||
// activeIds[state[i].id] = true;
|
||||
// findById(state[i].children, id);
|
||||
if (!viewer && !target.externalAllowed) {
|
||||
return { ...authPage }; //NOTE(martina): redirect to sign in page if try to access internal page while logged out
|
||||
}
|
||||
|
||||
// if (!target) {
|
||||
// activeIds[state[i].id] = false;
|
||||
// }
|
||||
// }
|
||||
return { ...target };
|
||||
};
|
||||
|
||||
export const getByHref = (href, viewer) => {
|
||||
let pathname;
|
||||
if (href) {
|
||||
pathname = href.split("?")[0];
|
||||
}
|
||||
if (!pathname) {
|
||||
return { page: { ...errorPage } };
|
||||
}
|
||||
if (pathname === "/_") {
|
||||
return { page: { ...activityPage } };
|
||||
}
|
||||
|
||||
let page = navigation.find((each) => pathname.startsWith(each.pathname));
|
||||
|
||||
let details;
|
||||
if (page) {
|
||||
page = { ...page };
|
||||
if (page.id === "NAV_SLATE" || page.id === "NAV_PROFILE") {
|
||||
details = {
|
||||
id: pathname.replace(page.pathname, ""),
|
||||
};
|
||||
}
|
||||
};
|
||||
} else {
|
||||
let params = pathname.slice(1).split("/");
|
||||
if (params.length === 1) {
|
||||
page = { ...profilePage };
|
||||
details = {
|
||||
username: params[0],
|
||||
};
|
||||
} else if (params.length === 2) {
|
||||
page = { ...slatePage };
|
||||
details = {
|
||||
username: params[0],
|
||||
slatename: params[1],
|
||||
};
|
||||
}
|
||||
}
|
||||
page.pathname = href;
|
||||
|
||||
findById(navigation, id);
|
||||
let redirected = false;
|
||||
|
||||
return { target, activeIds };
|
||||
if (viewer && page === authPage) {
|
||||
redirected = true;
|
||||
page = { ...activityPage };
|
||||
}
|
||||
|
||||
if (!viewer && !page.externalAllowed) {
|
||||
redirected = true;
|
||||
page = { ...authPage }; //NOTE(martina): redirect to sign in page if try to access internal page while logged out
|
||||
}
|
||||
|
||||
if (!page) {
|
||||
window.location.replace("/_/404");
|
||||
}
|
||||
|
||||
//NOTE(martina): to transform query params into more easily usable key value pairs in page
|
||||
if (!redirected) {
|
||||
let params = Strings.getParamsFromUrl(href);
|
||||
if (page.id === "NAV_PROFILE" && page.cid) {
|
||||
params.tab = "FILES";
|
||||
}
|
||||
page.params = params;
|
||||
}
|
||||
return { page, details };
|
||||
};
|
||||
|
||||
const authPage = {
|
||||
id: "NAV_SIGN_IN",
|
||||
name: "Sign in",
|
||||
pageTitle: "Sign in & Sign up",
|
||||
ignore: true,
|
||||
pathname: "/_/auth",
|
||||
};
|
||||
|
||||
const dataPage = {
|
||||
id: "NAV_DATA",
|
||||
name: "Files",
|
||||
pageTitle: "Your Files",
|
||||
pathname: "/_/data",
|
||||
mainNav: true,
|
||||
};
|
||||
|
||||
const activityPage = {
|
||||
id: "NAV_ACTIVITY",
|
||||
name: "Activity",
|
||||
pageTitle: "Activity",
|
||||
ignore: true,
|
||||
externalAllowed: true,
|
||||
pathname: "/_/activity",
|
||||
mainNav: true,
|
||||
};
|
||||
|
||||
const slatePage = {
|
||||
id: "NAV_SLATE",
|
||||
name: "Collection",
|
||||
pageTitle: "A Collection",
|
||||
ignore: true,
|
||||
externalAllowed: true,
|
||||
pathname: "/$/slate/",
|
||||
};
|
||||
|
||||
const profilePage = {
|
||||
id: "NAV_PROFILE",
|
||||
name: "Profile",
|
||||
pageTitle: "A Profile",
|
||||
ignore: true,
|
||||
externalAllowed: true,
|
||||
pathname: "/$/user/",
|
||||
};
|
||||
|
||||
const errorPage = {
|
||||
id: "NAV_ERROR",
|
||||
name: "404",
|
||||
pageTitle: "404 Not found",
|
||||
ignore: true,
|
||||
externalAllowed: true,
|
||||
pathname: "/_/404",
|
||||
};
|
||||
|
||||
export const navigation = [
|
||||
{
|
||||
id: "NAV_DATA",
|
||||
decorator: "DATA",
|
||||
name: "Home",
|
||||
pageTitle: "Welcome back!",
|
||||
},
|
||||
{
|
||||
id: "NAV_ACTIVITY",
|
||||
decorator: "ACTIVITY",
|
||||
name: "Activity",
|
||||
pageTitle: "Activity",
|
||||
ignore: true,
|
||||
},
|
||||
{
|
||||
id: "NAV_EXPLORE",
|
||||
decorator: "EXPLORE",
|
||||
name: "Explore",
|
||||
pageTitle: "Welcome back!",
|
||||
ignore: true,
|
||||
},
|
||||
errorPage,
|
||||
authPage,
|
||||
activityPage,
|
||||
dataPage,
|
||||
{
|
||||
id: "NAV_SLATES",
|
||||
decorator: "SLATES",
|
||||
name: "Collections",
|
||||
pageTitle: "Collections",
|
||||
ignore: true,
|
||||
},
|
||||
{
|
||||
id: "NAV_SLATES_FOLLOWING",
|
||||
decorator: "SLATES_FOLLOWING",
|
||||
name: "Collections following",
|
||||
pageTitle: "Collections following",
|
||||
pageTitle: "Your Collections",
|
||||
ignore: true,
|
||||
pathname: "/_/collections",
|
||||
mainNav: true,
|
||||
},
|
||||
// {
|
||||
// id: "NAV_SEARCH",
|
||||
// name: "Search",
|
||||
// pageTitle: "Search Slate",
|
||||
// ignore: true,
|
||||
// pathname: "/_/search",
|
||||
// mainNav: true,
|
||||
// },
|
||||
{
|
||||
id: "NAV_DIRECTORY",
|
||||
decorator: "DIRECTORY",
|
||||
name: "Directory",
|
||||
pageTitle: "Your directory",
|
||||
},
|
||||
{
|
||||
id: "NAV_DIRECTORY_FOLLOWERS",
|
||||
decorator: "DIRECTORY_FOLLOWERS",
|
||||
name: "Directory",
|
||||
pageTitle: "Your directory",
|
||||
ignore: true,
|
||||
},
|
||||
{
|
||||
id: "NAV_SLATE",
|
||||
decorator: "SLATE",
|
||||
name: "Collection",
|
||||
pageTitle: "Collection",
|
||||
ignore: true,
|
||||
pageTitle: "Your Following",
|
||||
pathname: "/_/directory",
|
||||
},
|
||||
slatePage,
|
||||
{
|
||||
id: "NAV_FILECOIN",
|
||||
decorator: "FILECOIN",
|
||||
name: "Filecoin",
|
||||
pageTitle: "Archive on Filecoin",
|
||||
filecoin: true,
|
||||
pathname: "/_/filecoin",
|
||||
},
|
||||
{
|
||||
id: "NAV_STORAGE_DEAL",
|
||||
decorator: "STORAGE_DEAL",
|
||||
name: "Storage Deal",
|
||||
filecoin: true,
|
||||
pageTitle: "Make a one-off Filecoin storage deal",
|
||||
pageTitle: "Filecoin Storage Deal",
|
||||
pathname: "/_/storage-deal",
|
||||
},
|
||||
{
|
||||
id: "NAV_API",
|
||||
decorator: "API",
|
||||
name: "API",
|
||||
pageTitle: "Developer API",
|
||||
pathname: "/_/api",
|
||||
},
|
||||
{
|
||||
id: "NAV_SETTINGS",
|
||||
decorator: "SETTINGS",
|
||||
name: "Profile & Account Settings",
|
||||
pageTitle: "Your Profile & Account Settings",
|
||||
ignore: true,
|
||||
},
|
||||
{
|
||||
id: "NAV_PROFILE_FILES",
|
||||
decorator: "PROFILE_FILES",
|
||||
name: "Profile",
|
||||
pageTitle: "Profile",
|
||||
ignore: true,
|
||||
},
|
||||
{
|
||||
id: "NAV_PROFILE",
|
||||
decorator: "PROFILE",
|
||||
name: "Profile",
|
||||
pageTitle: "Profile",
|
||||
ignore: true,
|
||||
},
|
||||
{
|
||||
id: "NAV_PROFILE_PEERS",
|
||||
decorator: "PROFILE_PEERS",
|
||||
name: "Profile",
|
||||
pageTitle: "Profile",
|
||||
ignore: true,
|
||||
},
|
||||
{
|
||||
id: "NAV_FILE",
|
||||
decorator: "FILE",
|
||||
name: "File",
|
||||
pageTitle: "File",
|
||||
name: "Settings",
|
||||
pageTitle: "Profile & Account Settings",
|
||||
ignore: true,
|
||||
pathname: "/_/settings",
|
||||
},
|
||||
profilePage,
|
||||
// {
|
||||
// id: "NAV_FILE",
|
||||
// name: "File",
|
||||
// pageTitle: "A File",
|
||||
// ignore: true,
|
||||
// externalAllowed: true,
|
||||
// },
|
||||
];
|
||||
|
@ -217,6 +217,34 @@ export const urlToCid = (url) => {
|
||||
.replace("hub.textile.io/ipfs/", "");
|
||||
};
|
||||
|
||||
export const getQueryStringFromParams = (params) => {
|
||||
let pairs = Object.entries(params).map(([key, value]) => `${key}=${value}`);
|
||||
let query = "?".concat(pairs.join("&"));
|
||||
if (query.length === 1) {
|
||||
return "";
|
||||
}
|
||||
return query;
|
||||
};
|
||||
|
||||
//NOTE(martina): works with both url and search passed in
|
||||
export const getParamsFromUrl = (url) => {
|
||||
let startIndex = url.indexOf("?") + 1;
|
||||
let search = url.slice(startIndex);
|
||||
if (search.length < 3) {
|
||||
return {};
|
||||
}
|
||||
let params = {};
|
||||
let pairs = search.split("&");
|
||||
for (let pair of pairs) {
|
||||
let key = pair.split("=")[0];
|
||||
let value = pair.slice(key.length + 1);
|
||||
if (key && value) {
|
||||
params[key] = value;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
};
|
||||
|
||||
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);
|
||||
|
148
common/styles.js
Normal file
148
common/styles.js
Normal file
@ -0,0 +1,148 @@
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
/* TYPOGRAPHY */
|
||||
|
||||
export const HEADING_01 = css`
|
||||
font-family: ${Constants.font.text};
|
||||
font-size: 1.953rem;
|
||||
font-weight: medium;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.02px;
|
||||
`;
|
||||
|
||||
export const HEADING_02 = css`
|
||||
font-family: ${Constants.font.text};
|
||||
font-size: 1.563rem;
|
||||
font-weight: medium;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.02px;
|
||||
`;
|
||||
|
||||
export const HEADING_03 = css`
|
||||
font-family: ${Constants.font.text};
|
||||
font-size: 1.25rem;
|
||||
font-weight: medium;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.02px;
|
||||
`;
|
||||
|
||||
export const HEADING_04 = css`
|
||||
font-family: ${Constants.font.text};
|
||||
font-size: 1rem;
|
||||
font-weight: medium;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.01px;
|
||||
`;
|
||||
|
||||
export const HEADING_05 = css`
|
||||
font-family: ${Constants.font.text};
|
||||
font-size: 0.875rem;
|
||||
font-weight: medium;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.01px;
|
||||
`;
|
||||
|
||||
export const BODY_01 = css`
|
||||
font-family: ${Constants.font.text};
|
||||
font-size: 1rem;
|
||||
font-weight: regular;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.01px;
|
||||
`;
|
||||
|
||||
export const BODY_02 = css`
|
||||
font-family: ${Constants.font.text};
|
||||
font-size: 0.875rem;
|
||||
font-weight: regular;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.01px;
|
||||
`;
|
||||
|
||||
export const SMALL_TEXT = css`
|
||||
font-family: ${Constants.font.text};
|
||||
font-size: 0.75rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.3;
|
||||
`;
|
||||
|
||||
export const CODE_01 = css`
|
||||
font-family: ${Constants.font.code};
|
||||
font-size: 0.75rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.3;
|
||||
`;
|
||||
|
||||
export const CODE_02 = css`
|
||||
font-family: ${Constants.font.code};
|
||||
font-size: 0.875rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
`;
|
||||
|
||||
/* FREQUENTLY USED */
|
||||
|
||||
export const HORIZONTAL_CONTAINER = css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
export const VERTICAL_CONTAINER = css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const HORIZONTAL_CONTAINER_CENTERED = css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const VERTICAL_CONTAINER_CENTERED = css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const CONTAINER_CENTERED = css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const ICON_CONTAINER = css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
margin: -4px;
|
||||
cursor: pointer;
|
||||
color: ${Constants.system.black};
|
||||
|
||||
:hover {
|
||||
color: ${Constants.system.brand};
|
||||
}
|
||||
`;
|
||||
|
||||
export const HOVERABLE = css`
|
||||
cursor: pointer;
|
||||
|
||||
:hover {
|
||||
color: ${Constants.system.brand};
|
||||
}
|
||||
`;
|
||||
|
||||
export const MOBILE_HIDDEN = css`
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MOBILE_ONLY = css`
|
||||
@media (min-width: ${Constants.sizes.mobile}px) {
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
@ -1692,6 +1692,22 @@ export const Menu = (props) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const MenuMinimal = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
stroke="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path d="M3.33331 6H21.3333" />
|
||||
<path d="M3.33331 18H21.3333" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Globe = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -25,7 +25,11 @@ export const authenticate = async (state) => {
|
||||
const jwt = cookies.get(Credentials.session.key);
|
||||
|
||||
if (jwt) {
|
||||
cookies.remove(Credentials.session.key);
|
||||
cookies.remove(Credentials.session.key, {
|
||||
path: "/",
|
||||
maxAge: 3600 * 24 * 7,
|
||||
sameSite: "strict",
|
||||
});
|
||||
}
|
||||
|
||||
let response = await Actions.signIn(state);
|
||||
@ -38,7 +42,7 @@ export const authenticate = async (state) => {
|
||||
// + One week.
|
||||
// + Only requests to the same site.
|
||||
// + Not using sessionStorage so the cookie doesn't leave when the browser dies.
|
||||
cookies.set(Credentials.session.key, response.token, true, {
|
||||
cookies.set(Credentials.session.key, response.token, {
|
||||
path: "/",
|
||||
maxAge: 3600 * 24 * 7,
|
||||
sameSite: "strict",
|
||||
@ -48,7 +52,7 @@ export const authenticate = async (state) => {
|
||||
return response;
|
||||
};
|
||||
|
||||
// NOTE(jim): Signs a user out and redirects to the sign in screen.
|
||||
// NOTE(jim): Signs a user out
|
||||
export const signOut = async ({ viewer }) => {
|
||||
let wsclient = Websockets.getClient();
|
||||
if (wsclient) {
|
||||
@ -60,10 +64,14 @@ export const signOut = async ({ viewer }) => {
|
||||
|
||||
const jwt = cookies.get(Credentials.session.key);
|
||||
if (jwt) {
|
||||
cookies.remove(Credentials.session.key);
|
||||
cookies.remove(Credentials.session.key, {
|
||||
path: "/",
|
||||
maxAge: 3600 * 24 * 7,
|
||||
sameSite: "strict",
|
||||
});
|
||||
}
|
||||
|
||||
window.location.replace("/_");
|
||||
window.location.replace("/_/auth");
|
||||
return;
|
||||
};
|
||||
|
||||
@ -79,11 +87,11 @@ export const deleteMe = async ({ viewer }) => {
|
||||
|
||||
await signOut({ viewer });
|
||||
|
||||
let wsclient = Websockets.getClient();
|
||||
if (wsclient) {
|
||||
await Websockets.deleteClient();
|
||||
wsclient = null;
|
||||
}
|
||||
// let wsclient = Websockets.getClient();
|
||||
// if (wsclient) {
|
||||
// await Websockets.deleteClient();
|
||||
// wsclient = null;
|
||||
// }
|
||||
|
||||
return response;
|
||||
};
|
||||
|
@ -115,10 +115,13 @@ export class Alert extends React.Component {
|
||||
|
||||
_handleDismissPrivacyAlert = (e) => {
|
||||
Actions.updateStatus({ status: { hidePrivacyAlert: true } });
|
||||
this.props.onUpdateViewer({
|
||||
data: {
|
||||
...this.props.viewer.data,
|
||||
status: { ...this.props.viewer.data.status, hidePrivacyAlert: true },
|
||||
this.props.onAction({
|
||||
type: "UDPATE_VIEWER",
|
||||
viewer: {
|
||||
data: {
|
||||
...this.props.viewer.data,
|
||||
status: { ...this.props.viewer.data.status, hidePrivacyAlert: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -126,7 +129,7 @@ export class Alert extends React.Component {
|
||||
render() {
|
||||
if (!this.state.message) {
|
||||
if (!this.props.fileLoading || !Object.keys(this.props.fileLoading).length) {
|
||||
if (this.props.noWarning) {
|
||||
if (this.props.noWarning || !this.props.viewer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import * as NavigationData from "~/common/navigation-data";
|
||||
import * as Actions from "~/common/actions";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as State from "~/common/state";
|
||||
import * as Styles from "~/common/styles";
|
||||
import * as Credentials from "~/common/credentials";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as Validations from "~/common/validations";
|
||||
@ -15,6 +15,7 @@ import * as Events from "~/common/custom-events";
|
||||
|
||||
// NOTE(jim):
|
||||
// Scenes each have an ID and can be navigated to with _handleAction
|
||||
import SceneError from "~/scenes/SceneError";
|
||||
import SceneEditAccount from "~/scenes/SceneEditAccount";
|
||||
import SceneFile from "~/scenes/SceneFile";
|
||||
import SceneFilesFolder from "~/scenes/SceneFilesFolder";
|
||||
@ -49,6 +50,7 @@ import SidebarEditTags from "~/components/sidebars/SidebarEditTags";
|
||||
import ApplicationHeader from "~/components/core/ApplicationHeader";
|
||||
import ApplicationLayout from "~/components/core/ApplicationLayout";
|
||||
import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
|
||||
import CTATransition from "~/components/core/CTATransition";
|
||||
|
||||
import { GlobalModal } from "~/components/system/components/GlobalModal";
|
||||
import { OnboardingModal } from "~/components/core/OnboardingModal";
|
||||
@ -56,6 +58,7 @@ import { SearchModal } from "~/components/core/SearchModal";
|
||||
import { Alert } from "~/components/core/Alert";
|
||||
import { announcements } from "~/components/core/OnboardingModal";
|
||||
import { Logo } from "~/common/logo";
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
|
||||
const SIDEBARS = {
|
||||
SIDEBAR_FILECOIN_ARCHIVE: <SidebarFilecoinArchive />,
|
||||
@ -73,23 +76,20 @@ const SIDEBARS = {
|
||||
};
|
||||
|
||||
const SCENES = {
|
||||
ACTIVITY: <SceneActivity tab={0} />,
|
||||
EXPLORE: <SceneActivity tab={1} />,
|
||||
DIRECTORY: <SceneDirectory />,
|
||||
PROFILE_FILES: <SceneProfile tab={0} />,
|
||||
PROFILE: <SceneProfile tab={1} />,
|
||||
PROFILE_PEERS: <SceneProfile tab={2} />,
|
||||
DATA: <SceneFilesFolder />,
|
||||
FILE: <SceneFile />,
|
||||
SLATE: <SceneSlate />,
|
||||
API: <SceneSettingsDeveloper />,
|
||||
SETTINGS: <SceneEditAccount />,
|
||||
SLATES: <SceneSlates tab={0} />,
|
||||
SLATES_FOLLOWING: <SceneSlates tab={1} />,
|
||||
DIRECTORY: <SceneDirectory tab={0} />,
|
||||
DIRECTORY_FOLLOWERS: <SceneDirectory tab={1} />,
|
||||
FILECOIN: <SceneArchive />,
|
||||
STORAGE_DEAL: <SceneMakeFilecoinDeal />,
|
||||
NAV_ERROR: <SceneError />,
|
||||
NAV_SIGN_IN: <SceneSignIn />,
|
||||
NAV_ACTIVITY: <SceneActivity />,
|
||||
NAV_DIRECTORY: <SceneDirectory />,
|
||||
NAV_PROFILE: <SceneProfile />,
|
||||
NAV_DATA: <SceneFilesFolder />,
|
||||
// NAV_FILE: <SceneFile />,
|
||||
NAV_SLATE: <SceneSlate />,
|
||||
NAV_API: <SceneSettingsDeveloper />,
|
||||
NAV_SETTINGS: <SceneEditAccount />,
|
||||
NAV_SLATES: <SceneSlates />,
|
||||
NAV_DIRECTORY: <SceneDirectory />,
|
||||
NAV_FILECOIN: <SceneArchive />,
|
||||
NAV_STORAGE_DEAL: <SceneMakeFilecoinDeal />,
|
||||
};
|
||||
|
||||
let mounted;
|
||||
@ -100,15 +100,18 @@ export default class ApplicationPage extends React.Component {
|
||||
state = {
|
||||
selected: {},
|
||||
viewer: this.props.viewer,
|
||||
data: null,
|
||||
page: this.props.page || {},
|
||||
data: this.props.data,
|
||||
activePage: this.props.page?.id,
|
||||
sidebar: null,
|
||||
online: null,
|
||||
isMobile: this.props.isMobile,
|
||||
loaded: false,
|
||||
activeUsers: null,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
this._handleWindowResize();
|
||||
if (mounted) {
|
||||
return false;
|
||||
}
|
||||
@ -128,8 +131,9 @@ export default class ApplicationPage extends React.Component {
|
||||
if (this.state.viewer) {
|
||||
await this._handleSetupWebsocket();
|
||||
}
|
||||
|
||||
this._handleURLRedirect();
|
||||
console.log(this.props.page);
|
||||
console.log(this.props.data);
|
||||
// this._handleBackForward();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -160,17 +164,9 @@ export default class ApplicationPage extends React.Component {
|
||||
|
||||
this._handleRegisterFileLoading({ fileLoading });
|
||||
|
||||
let page;
|
||||
if (typeof window !== "undefined") {
|
||||
page = window?.history?.state;
|
||||
}
|
||||
if (!page || !page.id) {
|
||||
page = { id: "NAV_DATA", scrollTop: 0, data: null };
|
||||
}
|
||||
const current = NavigationData.getCurrent(page);
|
||||
|
||||
const page = this.state.page;
|
||||
let slate = null;
|
||||
if (current.target.id === "NAV_SLATE" && this.state.data?.ownerId === this.state.viewer?.id) {
|
||||
if (page?.id === "NAV_SLATE" && this.state.data?.ownerId === this.state.viewer?.id) {
|
||||
slate = this.state.data;
|
||||
}
|
||||
|
||||
@ -181,7 +177,8 @@ export default class ApplicationPage extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
_handleUpdateViewer = (newViewerState, callback) => {
|
||||
_handleUpdateViewer = ({ viewer, callback }) => {
|
||||
// _handleUpdateViewer = (newViewerState, callback) => {
|
||||
// let setAsyncState = (newState) =>
|
||||
// new Promise((resolve) =>
|
||||
// this.setState(
|
||||
@ -194,17 +191,11 @@ export default class ApplicationPage extends React.Component {
|
||||
// await setAsyncState(newViewerState);
|
||||
|
||||
//NOTE(martina): if updating viewer affects this.state.data (e.g. you're viewing your own slate), update data as well
|
||||
if (newViewerState.slates?.length) {
|
||||
let page;
|
||||
if (typeof window !== "undefined") {
|
||||
page = window?.history?.state;
|
||||
}
|
||||
if (!page || !page.id) {
|
||||
page = { id: "NAV_DATA", scrollTop: 0, data: null };
|
||||
}
|
||||
if (page.id === "NAV_SLATE" && this.state.data?.ownerId === this.state.viewer.id) {
|
||||
if (viewer?.slates?.length) {
|
||||
const page = this.state.page;
|
||||
if (page?.id === "NAV_SLATE" && this.state.data?.ownerId === this.state.viewer.id) {
|
||||
let data = this.state.data;
|
||||
for (let slate of newViewerState.slates) {
|
||||
for (let slate of viewer.slates) {
|
||||
if (slate.id === data.id) {
|
||||
data = slate;
|
||||
break;
|
||||
@ -212,13 +203,14 @@ export default class ApplicationPage extends React.Component {
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
viewer: { ...this.state.viewer, ...newViewerState },
|
||||
viewer: { ...this.state.viewer, ...viewer },
|
||||
data,
|
||||
},
|
||||
() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
console.log(this.state.viewer);
|
||||
}
|
||||
);
|
||||
return;
|
||||
@ -226,9 +218,10 @@ export default class ApplicationPage extends React.Component {
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
viewer: { ...this.state.viewer, ...newViewerState },
|
||||
viewer: { ...this.state.viewer, ...viewer },
|
||||
},
|
||||
() => {
|
||||
console.log(this.state.viewer);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
@ -236,7 +229,8 @@ export default class ApplicationPage extends React.Component {
|
||||
);
|
||||
};
|
||||
|
||||
_handleUpdateData = (data, callback) => {
|
||||
_handleUpdateData = ({ data, callback }) => {
|
||||
// _handleUpdateData = (data, callback) => {
|
||||
//TODO(martina): maybe add a default window.history.replacestate where it pushes the new data to browser?
|
||||
this.setState({ data }, () => {
|
||||
if (callback) {
|
||||
@ -256,7 +250,6 @@ export default class ApplicationPage extends React.Component {
|
||||
console.log("WEBSOCKET: NOT AUTHENTICATED");
|
||||
return;
|
||||
}
|
||||
console.log("inside handle setup websocket in application.js");
|
||||
wsclient = Websockets.init({
|
||||
resource: this.props.resources.pubsub,
|
||||
viewer: this.state.viewer,
|
||||
@ -311,17 +304,10 @@ export default class ApplicationPage extends React.Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
let page;
|
||||
if (typeof window !== "undefined") {
|
||||
page = window?.history?.state;
|
||||
}
|
||||
if (!page || !page.id) {
|
||||
page = { id: "NAV_DATA", scrollTop: 0, data: null };
|
||||
}
|
||||
const current = NavigationData.getCurrent(page);
|
||||
let page = this.state.page;
|
||||
|
||||
let slate = null;
|
||||
if (current.target.id === "NAV_SLATE" && this.state.data?.ownerId === this.state.viewer?.id) {
|
||||
if (page?.id === "NAV_SLATE" && this.state.data?.ownerId === this.state.viewer?.id) {
|
||||
slate = this.state.data;
|
||||
}
|
||||
|
||||
@ -485,17 +471,17 @@ export default class ApplicationPage extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
return this._handleAuthenticate(state, true);
|
||||
this._handleAuthenticate(state, true);
|
||||
};
|
||||
|
||||
_handleAuthenticate = async (state, newAccount) => {
|
||||
let response = await UserBehaviors.authenticate(state);
|
||||
if (!response) {
|
||||
if (Events.hasError(response)) {
|
||||
return;
|
||||
}
|
||||
let viewer = await UserBehaviors.hydrate();
|
||||
if (!viewer) {
|
||||
return viewer;
|
||||
if (Events.hasError(viewer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ viewer });
|
||||
@ -507,7 +493,6 @@ export default class ApplicationPage extends React.Component {
|
||||
unseenAnnouncements.push(feature);
|
||||
}
|
||||
}
|
||||
console.log(unseenAnnouncements);
|
||||
|
||||
if (newAccount || unseenAnnouncements.length) {
|
||||
Events.dispatchCustomEvent({
|
||||
@ -530,28 +515,28 @@ export default class ApplicationPage extends React.Component {
|
||||
Actions.updateSearch("create-user");
|
||||
}
|
||||
|
||||
let redirected = this._handleURLRedirect();
|
||||
if (!redirected) {
|
||||
this._handleAction({ type: "NAVIGATE", value: "NAV_DATA" });
|
||||
}
|
||||
// let redirected = this._handleURLRedirect();
|
||||
// if (!redirected) {
|
||||
// this._handleAction({ type: "NAVIGATE", value: "NAV_DATA" });
|
||||
// }
|
||||
return response;
|
||||
};
|
||||
|
||||
_handleURLRedirect = () => {
|
||||
const id = Window.getQueryParameterByName("scene");
|
||||
const user = Window.getQueryParameterByName("user");
|
||||
const slate = Window.getQueryParameterByName("slate");
|
||||
const cid = Window.getQueryParameterByName("cid");
|
||||
// _handleURLRedirect = () => {
|
||||
// const id = Window.getQueryParameterByName("scene");
|
||||
// const username = Window.getQueryParameterByName("username");
|
||||
// const slatename = Window.getQueryParameterByName("slatename");
|
||||
// const cid = Window.getQueryParameterByName("cid");
|
||||
|
||||
if (!Strings.isEmpty(id) && this.state.viewer) {
|
||||
this._handleNavigateTo({ id, user, slate, cid }, null, true);
|
||||
return true;
|
||||
}
|
||||
if (!this.state.loaded) {
|
||||
this.setState({ loaded: true });
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// if (!Strings.isEmpty(id)) {
|
||||
// this._handleNavigateTo({ id, username, slatename, cid }, null, true);
|
||||
// return true;
|
||||
// }
|
||||
// if (!this.state.loaded) {
|
||||
// this.setState({ loaded: true });
|
||||
// }
|
||||
// return false;
|
||||
// };
|
||||
|
||||
_handleSelectedChange = (e) => {
|
||||
this.setState({
|
||||
@ -565,30 +550,15 @@ export default class ApplicationPage extends React.Component {
|
||||
|
||||
_handleAction = (options) => {
|
||||
if (options.type === "NAVIGATE") {
|
||||
// NOTE(martina): The `scene` property is only necessary when you need to display a component different from the one corresponding to the tab it appears in
|
||||
// + e.g. to display <SceneProfile/> while on the Home tab
|
||||
// + `scene` should be the decorator of the component you want displayed
|
||||
return this._handleNavigateTo(
|
||||
{
|
||||
...options,
|
||||
id: options.value,
|
||||
redirect: null,
|
||||
},
|
||||
options.data,
|
||||
options.redirect
|
||||
);
|
||||
return this._handleNavigateTo(options);
|
||||
}
|
||||
|
||||
if (options.type === "NEW_WINDOW") {
|
||||
return window.open(options.value);
|
||||
if (options.type === "UPDATE_VIEWER") {
|
||||
return this._handleUpdateViewer(options);
|
||||
}
|
||||
|
||||
if (options.type === "ACTION") {
|
||||
Events.dispatchMessage({ message: JSON.stringify(options), status: "INFO" });
|
||||
}
|
||||
|
||||
if (options.type === "DOWNLOAD") {
|
||||
Events.dispatchMessage({ message: JSON.stringify(options), status: "INFO" });
|
||||
if (options.type === "UPDATE_PARAMS") {
|
||||
return this._handleUpdatePageParams(options);
|
||||
}
|
||||
|
||||
if (options.type === "SIDEBAR") {
|
||||
@ -602,125 +572,167 @@ export default class ApplicationPage extends React.Component {
|
||||
return this._handleRegisterFileCancelled({ key: options.value });
|
||||
}
|
||||
|
||||
return alert(JSON.stringify(options));
|
||||
};
|
||||
|
||||
_handleNavigateTo = (next, data = null, redirect = false) => {
|
||||
if (redirect) {
|
||||
window.history.replaceState(
|
||||
{ ...next, data },
|
||||
"Slate",
|
||||
`/_${next.id ? `?scene=${next.id}` : ""}`
|
||||
);
|
||||
} else {
|
||||
window.history.pushState(
|
||||
{ ...next, data },
|
||||
"Slate",
|
||||
`/_${next.id ? `?scene=${next.id}` : ""}`
|
||||
);
|
||||
if (options.type === "NEW_WINDOW") {
|
||||
return window.open(options.value);
|
||||
}
|
||||
|
||||
if (!this.state.loaded) {
|
||||
this.setState({ loaded: true });
|
||||
console.log("Error: Failed to _handleAction because TYPE did not match any known actions");
|
||||
};
|
||||
|
||||
_handleNavigateTo = async ({ href, redirect = false, popstate = false }) => {
|
||||
const { page, details } = NavigationData.getByHref(href, this.state.viewer);
|
||||
|
||||
Events.dispatchCustomEvent({ name: "slate-global-close-carousel", detail: {} });
|
||||
|
||||
if (redirect || popstate) {
|
||||
window.history.replaceState(null, "Slate", page.pathname);
|
||||
} else {
|
||||
window.history.pushState(null, "Slate", page.pathname);
|
||||
}
|
||||
|
||||
let state = { data: null, sidebar: null, page };
|
||||
if (!next.ignore) {
|
||||
state.activePage = page.id;
|
||||
}
|
||||
|
||||
let body = document.documentElement || document.body;
|
||||
if (data) {
|
||||
this.setState(
|
||||
{
|
||||
data,
|
||||
sidebar: null,
|
||||
},
|
||||
() => body.scrollTo(0, 0)
|
||||
);
|
||||
} else {
|
||||
this.setState(
|
||||
{
|
||||
sidebar: null,
|
||||
},
|
||||
() => body.scrollTo(0, 0)
|
||||
);
|
||||
if (page.id === "NAV_SLATE" || page.id === "NAV_PROFILE") {
|
||||
state.loading = true;
|
||||
}
|
||||
this.setState(state, () => {
|
||||
if (!popstate) {
|
||||
body.scrollTo(0, 0);
|
||||
}
|
||||
if (page.id === "NAV_SLATE" || page.id === "NAV_PROFILE") {
|
||||
this.updateDataAndPathname({ page, details });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
_handleBackForward = (e) => {
|
||||
let page = window.history.state;
|
||||
this.setState({
|
||||
sidebar: null,
|
||||
data: page.data,
|
||||
updateDataAndPathname = async ({ page, details }) => {
|
||||
let pathname = page.pathname.split("?")[0];
|
||||
let search = Strings.getQueryStringFromParams(page.params);
|
||||
let data;
|
||||
if (page?.id === "NAV_SLATE") {
|
||||
let response = await Actions.getSerializedSlate(details);
|
||||
if (!response || response.error) {
|
||||
this.setState({ loading: false });
|
||||
this._handleNavigateTo({ href: "/_/404", redirect: true });
|
||||
return;
|
||||
}
|
||||
data = response.data;
|
||||
pathname = `/${data.user.username}/${data.slatename}${search}`;
|
||||
} else if (page?.id === "NAV_PROFILE") {
|
||||
let response = await Actions.getSerializedProfile(details);
|
||||
if (!response || response.error) {
|
||||
this.setState({ loading: false });
|
||||
this._handleNavigateTo({ href: "/_/404", redirect: true });
|
||||
return;
|
||||
}
|
||||
data = response.data;
|
||||
pathname = `/${data.username}${search}`;
|
||||
}
|
||||
|
||||
this.setState({ data, loading: false });
|
||||
|
||||
window.history.replaceState(null, "Slate", pathname);
|
||||
};
|
||||
|
||||
_handleUpdatePageParams = ({ params, callback, redirect = false }) => {
|
||||
let query = Strings.getQueryStringFromParams(params);
|
||||
const href = window.location.pathname.concat(query);
|
||||
if (redirect) {
|
||||
window.history.replaceState(null, "Slate", href);
|
||||
} else {
|
||||
window.history.pushState(null, "Slate", href);
|
||||
}
|
||||
this.setState({ page: { ...this.state.page, params } }, () => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
Events.dispatchCustomEvent({ name: "slate-global-close-carousel", detail: {} });
|
||||
};
|
||||
|
||||
// _handleUpdatePageParams = ({ search, callback }) => {
|
||||
// if (!search.length) {
|
||||
// return;
|
||||
// }
|
||||
// let target = {};
|
||||
// let searchParams = search.replace("?", "");
|
||||
// let pairs = searchParams.split("&");
|
||||
// for (let pair of pairs) {
|
||||
// let key = pair.split("=")[0];
|
||||
// let value = pair.slice(key.length + 1);
|
||||
// if (key && value) {
|
||||
// target[key] = value;
|
||||
// }
|
||||
// }
|
||||
// const href = window.location.pathname + "?" + searchParams;
|
||||
// if (target) {
|
||||
// window.history.replaceState(null, "Slate", href);
|
||||
// this.setState({ page: target }, () => {
|
||||
// if (callback) {
|
||||
// callback();
|
||||
// }
|
||||
// });
|
||||
// } else {
|
||||
// window.history.replaceState(null, "Slate", href);
|
||||
// if (callback) {
|
||||
// callback();
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
_handleBackForward = () => {
|
||||
let href = window.location.pathname.concat(
|
||||
window.location.search ? `${window.location.search}` : ""
|
||||
);
|
||||
this._handleNavigateTo({ href, popstate: true });
|
||||
};
|
||||
|
||||
render() {
|
||||
// NOTE(jim): Not authenticated.
|
||||
if (!this.state.viewer) {
|
||||
return (
|
||||
<WebsitePrototypeWrapper
|
||||
title="Slate: sign in"
|
||||
description="Sign in to your Slate account to manage your assets."
|
||||
url="https://slate.host/_"
|
||||
>
|
||||
<Alert noWarning style={{ top: 0, zIndex: Constants.zindex.sidebar }} />
|
||||
<SceneSignIn
|
||||
onCreateUser={this._handleCreateUser}
|
||||
onAuthenticate={this._handleAuthenticate}
|
||||
onAction={this._handleAction}
|
||||
/>
|
||||
</WebsitePrototypeWrapper>
|
||||
let page = this.state.page;
|
||||
if (!page?.id) {
|
||||
page = NavigationData.getById(null, this.state.viewer);
|
||||
}
|
||||
let headerElement;
|
||||
if (page.id !== "NAV_SIGN_IN") {
|
||||
headerElement = (
|
||||
<ApplicationHeader
|
||||
viewer={this.state.viewer}
|
||||
navigation={NavigationData.navigation}
|
||||
page={page}
|
||||
onAction={this._handleAction}
|
||||
isMobile={this.state.isMobile}
|
||||
isMac={this.props.isMac}
|
||||
activePage={this.state.activePage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// NOTE(jim): Authenticated.
|
||||
let page;
|
||||
if (typeof window !== "undefined") {
|
||||
page = window?.history?.state;
|
||||
}
|
||||
if (!page || !page.id) {
|
||||
page = { id: "NAV_DATA", scrollTop: 0, data: null };
|
||||
}
|
||||
const current = NavigationData.getCurrent(page);
|
||||
|
||||
// NOTE(jim): Only happens during a bad query parameter.
|
||||
if (!current.target) {
|
||||
window.location.replace("/_");
|
||||
return null;
|
||||
}
|
||||
|
||||
let headerElement = (
|
||||
<ApplicationHeader
|
||||
viewer={this.state.viewer}
|
||||
navigation={NavigationData.navigation}
|
||||
activeIds={current.activeIds}
|
||||
onAction={this._handleAction}
|
||||
isMobile={this.state.isMobile}
|
||||
isMac={this.props.isMac}
|
||||
/>
|
||||
);
|
||||
|
||||
const scene = React.cloneElement(SCENES[page.scene || current.target.decorator], {
|
||||
const scene = React.cloneElement(SCENES[page.id], {
|
||||
key: this.state.data?.id,
|
||||
page: page,
|
||||
current: current.target,
|
||||
data: this.state.data,
|
||||
viewer: this.state.viewer,
|
||||
selected: this.state.selected,
|
||||
onSelectedChange: this._handleSelectedChange,
|
||||
onAuthenticate: this._handleAuthenticate,
|
||||
onCreateUser: this._handleCreateUser,
|
||||
onAction: this._handleAction,
|
||||
onUpload: this._handleUploadFiles,
|
||||
onUpdateData: this._handleUpdateData,
|
||||
onUpdateViewer: this._handleUpdateViewer,
|
||||
sceneId: current.target.id,
|
||||
isMobile: this.state.isMobile,
|
||||
isMac: this.props.isMac,
|
||||
resources: this.props.resources,
|
||||
activeUsers: this.state.activeUsers,
|
||||
userBucketCID: this.state.userBucketCID,
|
||||
external: !!!this.state.viewer,
|
||||
});
|
||||
|
||||
let sidebarElement;
|
||||
if (this.state.sidebar) {
|
||||
sidebarElement = React.cloneElement(this.state.sidebar, {
|
||||
current: current.target,
|
||||
page: page,
|
||||
selected: this.state.selected,
|
||||
viewer: this.state.viewer,
|
||||
data: this.state.data,
|
||||
@ -730,34 +742,30 @@ export default class ApplicationPage extends React.Component {
|
||||
onCancel: this._handleDismissSidebar,
|
||||
onUpload: this._handleUploadFiles,
|
||||
onAction: this._handleAction,
|
||||
onUpdateViewer: this._handleUpdateViewer,
|
||||
resources: this.props.resources,
|
||||
});
|
||||
}
|
||||
|
||||
const title = `Slate : ${current.target.pageTitle}`;
|
||||
const title = `Slate: ${page.pageTitle}`;
|
||||
const description = "";
|
||||
const url = "https://slate.host/_";
|
||||
|
||||
// console.log("application state:", { target: current.target });
|
||||
// console.log("application state:", { data: this.state.data });
|
||||
|
||||
if (!this.state.loaded) {
|
||||
return (
|
||||
<WebsitePrototypeWrapper description={description} title={title} url={url}>
|
||||
<div
|
||||
style={{
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Logo style={{ width: "20vw", maxWidth: "200px" }} />
|
||||
</div>
|
||||
</WebsitePrototypeWrapper>
|
||||
);
|
||||
}
|
||||
// if (!this.state.loaded) {
|
||||
// return (
|
||||
// <WebsitePrototypeWrapper description={description} title={title} url={url}>
|
||||
// <div
|
||||
// style={{
|
||||
// height: "100vh",
|
||||
// display: "flex",
|
||||
// alignItems: "center",
|
||||
// justifyContent: "center",
|
||||
// }}
|
||||
// >
|
||||
// <Logo style={{ width: "20vw", maxWidth: "200px" }} />
|
||||
// </div>
|
||||
// </WebsitePrototypeWrapper>
|
||||
// );
|
||||
// }
|
||||
return (
|
||||
<React.Fragment>
|
||||
<WebsitePrototypeWrapper description={description} title={title} url={url}>
|
||||
@ -767,13 +775,23 @@ export default class ApplicationPage extends React.Component {
|
||||
sidebar={sidebarElement}
|
||||
onDismissSidebar={this._handleDismissSidebar}
|
||||
fileLoading={this.state.fileLoading}
|
||||
filecoin={current.target.filecoin}
|
||||
isMobile={this.state.isMobile}
|
||||
isMac={this.props.isMac}
|
||||
viewer={this.state.viewer}
|
||||
onUpdateViewer={this._handleUpdateViewer}
|
||||
>
|
||||
{scene}
|
||||
{this.state.loading ? (
|
||||
<div
|
||||
css={Styles.CONTAINER_CENTERED}
|
||||
style={{
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
}}
|
||||
>
|
||||
<LoaderSpinner style={{ height: 32, width: 32 }} />
|
||||
</div>
|
||||
) : (
|
||||
scene
|
||||
)}
|
||||
</ApplicationLayout>
|
||||
<GlobalModal />
|
||||
<SearchModal
|
||||
@ -782,7 +800,8 @@ export default class ApplicationPage extends React.Component {
|
||||
isMobile={this.props.isMobile}
|
||||
resourceURI={this.props.resources.search}
|
||||
/>
|
||||
{!this.state.loaded ? (
|
||||
<CTATransition onAction={this._handleAction} />
|
||||
{/* {!this.state.loaded ? (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
@ -795,7 +814,7 @@ export default class ApplicationPage extends React.Component {
|
||||
>
|
||||
<Logo style={{ width: "20vw", maxWidth: "200px" }} />
|
||||
</div>
|
||||
) : null}
|
||||
) : null} */}
|
||||
</WebsitePrototypeWrapper>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -2,88 +2,80 @@ import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/common/svg";
|
||||
import * as Events from "~/common/custom-events";
|
||||
import * as Styles from "~/common/styles";
|
||||
|
||||
import ApplicationUserControls from "~/components/core/ApplicationUserControls";
|
||||
import {
|
||||
ApplicationUserControls,
|
||||
ApplicationUserControlsPopup,
|
||||
} from "~/components/core/ApplicationUserControls";
|
||||
|
||||
import { css, keyframes } from "@emotion/react";
|
||||
import { Boundary } from "~/components/system/components/fragments/Boundary";
|
||||
import { PopoverNavigation } from "~/components/system";
|
||||
import { Logo, Symbol } from "~/common/logo";
|
||||
import { Link } from "~/components/core/Link";
|
||||
import { ButtonPrimary, ButtonTertiary } from "~/components/system/components/Buttons";
|
||||
|
||||
const IconMap = {
|
||||
// ACTIVITY: <SVG.Home height="20px" />,
|
||||
ENCRYPTED: <SVG.SecurityLock height="20px" />,
|
||||
NETWORK: <SVG.Activity height="20px" />,
|
||||
DIRECTORY: <SVG.Directory height="20px" />,
|
||||
// DATA: <SVG.Folder height="20px" />,
|
||||
DATA: <SVG.Home height="20px" />,
|
||||
WALLET: <SVG.OldWallet height="20px" />,
|
||||
DEALS: <SVG.Deals height="20px" />,
|
||||
STORAGE_DEAL: <SVG.HardDrive height="20px" />,
|
||||
SLATES: <SVG.Layers height="20px" />,
|
||||
SLATE: <SVG.Slate height="20px" />,
|
||||
LOCAL_DATA: <SVG.HardDrive height="20px" />,
|
||||
PROFILE_PAGE: <SVG.ProfileUser height="20px" />,
|
||||
API: <SVG.Tool height="20px" />,
|
||||
SETTINGS: <SVG.Settings height="20px" />,
|
||||
DIRECTORY: <SVG.Directory height="20px" />,
|
||||
FILECOIN: <SVG.Wallet height="20px" />,
|
||||
MINERS: <SVG.Miners height="20px" />,
|
||||
};
|
||||
|
||||
const STYLES_SHORTCUTS = css`
|
||||
background-color: ${Constants.system.white};
|
||||
border-radius: 2px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-left: 4px;
|
||||
font-size: 15px;
|
||||
const STYLES_NAV_LINKS = css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${Constants.system.textGray};
|
||||
flex-direction: row;
|
||||
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_ICON_ELEMENT = css`
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
const STYLES_NAV_LINK = css`
|
||||
color: ${Constants.system.textGray};
|
||||
user-select: none;
|
||||
text-decoration: none;
|
||||
transition: 200ms ease color;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
padding: 4px 24px;
|
||||
font-size: ${Constants.typescale.lvl1};
|
||||
|
||||
:hover {
|
||||
color: ${Constants.system.brand};
|
||||
}
|
||||
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
border-bottom: 1px solid ${Constants.system.grayLight2};
|
||||
margin: 0px 24px;
|
||||
padding: 12px 0px;
|
||||
${Styles.BODY_02};
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_APPLICATION_HEADER = css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
const STYLES_APPLICATION_HEADER_CONTAINER = css`
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
padding: 0 24px 0 16px;
|
||||
pointer-events: none;
|
||||
background-color: ${Constants.system.foreground};
|
||||
background-color: ${Constants.system.white};
|
||||
|
||||
@supports ((-webkit-backdrop-filter: blur(25px)) or (backdrop-filter: blur(25px))) {
|
||||
-webkit-backdrop-filter: blur(25px);
|
||||
backdrop-filter: blur(25px);
|
||||
background-color: rgba(248, 248, 248, 0.7);
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_APPLICATION_HEADER = css`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
${"" /* justify-content: space-between; */}
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
${"" /* padding: 0 24px 0 16px; */}
|
||||
padding: 0px 32px;
|
||||
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
padding: 0px 12px;
|
||||
padding: 0px 24px;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_LEFT = css`
|
||||
flex-shrink: 0;
|
||||
${"" /* width: 352px; */}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@ -92,38 +84,34 @@ const STYLES_LEFT = css`
|
||||
const STYLES_MIDDLE = css`
|
||||
min-width: 10%;
|
||||
width: 100%;
|
||||
padding: 0 24px 0 48px;
|
||||
`;
|
||||
|
||||
const STYLES_MOBILE_HIDDEN = css`
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
display: none;
|
||||
}
|
||||
padding: 0 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const STYLES_RIGHT = css`
|
||||
min-width: 10%;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const rotate = keyframes`
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`;
|
||||
const STYLES_BACKGROUND = css`
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: ${Constants.system.bgBlurGrayBlack};
|
||||
pointer-events: auto;
|
||||
|
||||
const STYLES_ROTATION = css`
|
||||
animation: ${rotate} 1s linear infinite;
|
||||
`;
|
||||
|
||||
const STYLES_STATIC = css`
|
||||
transition: 200ms ease all;
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 50%;
|
||||
}
|
||||
to {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
animation: fade-in 200ms ease-out;
|
||||
`;
|
||||
|
||||
export default class ApplicationHeader extends React.Component {
|
||||
@ -135,6 +123,7 @@ export default class ApplicationHeader extends React.Component {
|
||||
);
|
||||
|
||||
state = {
|
||||
showDropdown: false,
|
||||
popup: null,
|
||||
isRefreshing: false,
|
||||
};
|
||||
@ -162,6 +151,7 @@ export default class ApplicationHeader extends React.Component {
|
||||
};
|
||||
|
||||
_handleCreateSearch = (e) => {
|
||||
this.setState({ showDropdown: false });
|
||||
Events.dispatchCustomEvent({
|
||||
name: "show-search",
|
||||
detail: {},
|
||||
@ -169,6 +159,7 @@ export default class ApplicationHeader extends React.Component {
|
||||
};
|
||||
|
||||
_handleTogglePopup = (value) => {
|
||||
console.log(value);
|
||||
if (!value || this.state.popup === value) {
|
||||
this.setState({ popup: null });
|
||||
} else {
|
||||
@ -177,135 +168,217 @@ export default class ApplicationHeader extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
// const isBackDisabled = this.props.currentIndex === 0 || this.props.history.length < 2;
|
||||
const navigation = this.props.navigation.filter((item) => item.mainNav);
|
||||
|
||||
// const isForwardDisabled =
|
||||
// this.props.currentIndex === this.props.history.length - 1 || this.props.history.length < 2;
|
||||
return (
|
||||
<header css={STYLES_APPLICATION_HEADER}>
|
||||
<div css={STYLES_LEFT}>
|
||||
<span
|
||||
css={STYLES_ICON_ELEMENT}
|
||||
style={{ position: "relative" }}
|
||||
onClick={() => this._handleTogglePopup("nav")}
|
||||
>
|
||||
<SVG.Menu height="24px" />
|
||||
{this.state.popup === "nav" ? (
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled
|
||||
onOutsideRectEvent={() => this._handleTogglePopup()}
|
||||
style={this.props.style}
|
||||
if (!this.props.viewer) {
|
||||
const searchComponent = (
|
||||
<div
|
||||
onClick={this._handleCreateSearch}
|
||||
css={Styles.HORIZONTAL_CONTAINER_CENTERED}
|
||||
style={{ border: "none", pointerEvents: "auto", cursor: "pointer" }}
|
||||
>
|
||||
<SVG.Search
|
||||
height="16px"
|
||||
style={{ color: Constants.system.textGrayDark, marginRight: 8 }}
|
||||
/>
|
||||
<span css={Styles.BODY_02} style={{ color: Constants.system.textGray }}>
|
||||
Search Slate...
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
//NOTE(martina): signed out view
|
||||
return (
|
||||
<header css={STYLES_APPLICATION_HEADER_CONTAINER}>
|
||||
<div css={STYLES_APPLICATION_HEADER}>
|
||||
<div css={STYLES_LEFT}>
|
||||
<Symbol style={{ height: 24, marginRight: 16 }} />
|
||||
<div css={Styles.MOBILE_ONLY}>{searchComponent}</div>
|
||||
</div>
|
||||
<div css={STYLES_MIDDLE}>
|
||||
<span css={Styles.MOBILE_HIDDEN}>{searchComponent}</span>
|
||||
</div>
|
||||
<div css={STYLES_RIGHT}>
|
||||
<Link
|
||||
href="/_/auth?tab=signin"
|
||||
onAction={this.props.onAction}
|
||||
style={{ pointerEvents: "auto" }}
|
||||
>
|
||||
<PopoverNavigation
|
||||
style={{ top: 44, left: 8, width: 220, padding: "12px 24px" }}
|
||||
itemStyle={{ padding: "12px 0" }}
|
||||
navigation={this.props.navigation
|
||||
.filter((item) => !item.ignore)
|
||||
.map((item) => {
|
||||
return {
|
||||
text: (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: this.props.activeIds[item.id] ? Constants.system.brand : null,
|
||||
}}
|
||||
>
|
||||
<span style={{ marginRight: 16 }}>{IconMap[item.decorator]}</span>
|
||||
<span style={{ fontSize: 18 }}>{item.name}</span>
|
||||
</div>
|
||||
),
|
||||
onClick: (e) => {
|
||||
this._handleTogglePopup();
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: item.id,
|
||||
});
|
||||
},
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</Boundary>
|
||||
) : null}
|
||||
</span>
|
||||
{/* <span css={STYLES_MOBILE_HIDDEN} style={{ marginLeft: 32 }}>
|
||||
<span
|
||||
css={STYLES_ICON_ELEMENT}
|
||||
style={
|
||||
isBackDisabled ? { cursor: "not-allowed", color: Constants.system.border } : null
|
||||
}
|
||||
onClick={isBackDisabled ? () => {} : this.props.onBack}
|
||||
>
|
||||
<SVG.ChevronLeft height="28px" />
|
||||
</span>
|
||||
<span
|
||||
css={STYLES_ICON_ELEMENT}
|
||||
style={
|
||||
isForwardDisabled
|
||||
? { cursor: "not-allowed", color: Constants.system.border, marginLeft: 8 }
|
||||
: { marginLeft: 8 }
|
||||
}
|
||||
onClick={isForwardDisabled ? () => {} : this.props.onForward}
|
||||
>
|
||||
<SVG.ChevronRight height="28px" />
|
||||
</span>
|
||||
</span> */}
|
||||
{/* <div
|
||||
style={{
|
||||
height: 28,
|
||||
margin: "0px 12px",
|
||||
borderRight: `1px solid ${Constants.system.border}`,
|
||||
}}
|
||||
/> */}
|
||||
<span
|
||||
css={STYLES_ICON_ELEMENT}
|
||||
style={{ marginLeft: 24 }}
|
||||
onClick={this._handleCreateSearch}
|
||||
>
|
||||
<SVG.Search height="24px" />
|
||||
</span>
|
||||
<span css={STYLES_MOBILE_HIDDEN}>
|
||||
<span css={Styles.MOBILE_HIDDEN}>
|
||||
<ButtonTertiary
|
||||
style={{
|
||||
padding: "0px 12px",
|
||||
minHeight: "30px",
|
||||
fontFamily: Constants.font.text,
|
||||
marginRight: 8,
|
||||
}}
|
||||
>
|
||||
Sign in
|
||||
</ButtonTertiary>
|
||||
</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/_/auth?tab=signup"
|
||||
onAction={this.props.onAction}
|
||||
style={{ pointerEvents: "auto" }}
|
||||
>
|
||||
<ButtonPrimary
|
||||
style={{
|
||||
padding: "0px 12px",
|
||||
minHeight: "30px",
|
||||
fontFamily: Constants.font.text,
|
||||
}}
|
||||
>
|
||||
Sign up
|
||||
</ButtonPrimary>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
const mobilePopup = (
|
||||
// <Boundary
|
||||
// captureResize={false}
|
||||
// captureScroll={false}
|
||||
// enabled={this.state.popup === "profile"}
|
||||
// onOutsideRectEvent={(e) => {
|
||||
// e.stopPropagation();
|
||||
// e.preventDefault();
|
||||
// this._handleTogglePopup(e);
|
||||
// }}
|
||||
// >
|
||||
<>
|
||||
<ApplicationUserControlsPopup
|
||||
popup={this.state.popup}
|
||||
onTogglePopup={this._handleTogglePopup}
|
||||
viewer={this.props.viewer}
|
||||
onAction={this.props.onAction}
|
||||
style={{ pointerEvents: "auto", paddingBottom: 16 }}
|
||||
/>
|
||||
<div css={STYLES_BACKGROUND} />
|
||||
</>
|
||||
// </Boundary>
|
||||
);
|
||||
|
||||
const mobileDropdown = (
|
||||
<>
|
||||
<Boundary
|
||||
captureResize={false}
|
||||
captureScroll={false}
|
||||
enabled={this.state.showDropdown}
|
||||
onOutsideRectEvent={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.setState({ showDropdown: false });
|
||||
}}
|
||||
>
|
||||
<div css={STYLES_NAV_LINKS} style={{ pointerEvents: "auto", paddingBottom: 16 }}>
|
||||
{this.props.navigation
|
||||
.filter((item) => item.mainNav)
|
||||
.map((item) => (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={item.pathname}
|
||||
onAction={this.props.onAction}
|
||||
onClick={() => this.setState({ showDropdown: false })}
|
||||
>
|
||||
<div
|
||||
css={STYLES_NAV_LINK}
|
||||
style={{
|
||||
color: this.props.activePage === item.id ? Constants.system.black : null,
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={this._handleCreateSearch}
|
||||
css={STYLES_NAV_LINK}
|
||||
style={{ border: "none" }}
|
||||
>
|
||||
<div css={STYLES_SHORTCUTS} style={{ width: "auto" }}>
|
||||
{this.searchModKey}
|
||||
Search
|
||||
</div>
|
||||
</div>
|
||||
</Boundary>
|
||||
<div css={STYLES_BACKGROUND} />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ width: "100vw", height: "100vh", position: "absolute" }} />
|
||||
<header css={STYLES_APPLICATION_HEADER_CONTAINER}>
|
||||
<span css={Styles.MOBILE_HIDDEN}>
|
||||
<div css={STYLES_APPLICATION_HEADER}>
|
||||
<div css={STYLES_LEFT}>
|
||||
<Symbol style={{ height: 24 }} />
|
||||
</div>
|
||||
<div css={STYLES_MIDDLE}>
|
||||
<div css={STYLES_NAV_LINKS} style={{ pointerEvents: "auto" }}>
|
||||
{navigation.map((item, i) => (
|
||||
<Link key={item.id} href={item.pathname} onAction={this.props.onAction}>
|
||||
<div
|
||||
css={STYLES_NAV_LINK}
|
||||
style={{
|
||||
color: this.props.activePage === item.id ? Constants.system.black : null,
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
<div onClick={this._handleCreateSearch} css={STYLES_NAV_LINK}>
|
||||
Search
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div css={STYLES_RIGHT}>
|
||||
<span style={{ pointerEvents: "auto", marginLeft: 24 }}>
|
||||
<ApplicationUserControls
|
||||
popup={this.state.popup}
|
||||
onTogglePopup={this._handleTogglePopup}
|
||||
viewer={this.props.viewer}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div css={STYLES_SHORTCUTS}>F</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
{/* <div css={STYLES_MIDDLE} /> */}
|
||||
<div css={STYLES_RIGHT}>
|
||||
{/* <span css={STYLES_MOBILE_HIDDEN}>
|
||||
<span
|
||||
css={STYLES_ICON_ELEMENT}
|
||||
onClick={() =>
|
||||
this.props.onAction({
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_HELP",
|
||||
})
|
||||
}
|
||||
>
|
||||
<SVG.Help height="24px" />
|
||||
</span>
|
||||
</span> */}
|
||||
<span style={{ pointerEvents: "auto", marginLeft: 24 }}>
|
||||
<ApplicationUserControls
|
||||
popup={this.state.popup}
|
||||
onTogglePopup={this._handleTogglePopup}
|
||||
viewer={this.props.viewer}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
<span css={Styles.MOBILE_ONLY}>
|
||||
<div css={STYLES_APPLICATION_HEADER}>
|
||||
<div css={STYLES_LEFT}>
|
||||
<div
|
||||
css={Styles.ICON_CONTAINER}
|
||||
style={{ pointerEvents: "auto" }}
|
||||
onClick={() => this.setState({ showDropdown: !this.state.showDropdown })}
|
||||
>
|
||||
<SVG.MenuMinimal height="16px" />
|
||||
</div>
|
||||
</div>
|
||||
<div css={STYLES_MIDDLE}>
|
||||
<Symbol style={{ height: 24 }} />
|
||||
</div>
|
||||
<div css={STYLES_RIGHT}>
|
||||
<span style={{ pointerEvents: "auto", marginLeft: 24 }}>
|
||||
<ApplicationUserControls
|
||||
popup={false}
|
||||
onTogglePopup={this._handleTogglePopup}
|
||||
viewer={this.props.viewer}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.popup === "profile"
|
||||
? mobilePopup
|
||||
: this.state.showDropdown
|
||||
? mobileDropdown
|
||||
: null}
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -169,23 +169,20 @@ export default class ApplicationLayout extends React.Component {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div css={STYLES_CONTENT}>
|
||||
{/* <GlobalTooltip elementRef={this._body} allowedTypes={["body"]} /> */}
|
||||
<GlobalTooltip />
|
||||
|
||||
<div
|
||||
css={STYLES_HEADER}
|
||||
style={{ top: this.props.isMobile ? this.state.headerTop : null }}
|
||||
>
|
||||
{this.props.header}
|
||||
</div>
|
||||
|
||||
{this.props.header && (
|
||||
<div
|
||||
css={STYLES_HEADER}
|
||||
style={{ top: this.props.isMobile ? this.state.headerTop : null }}
|
||||
>
|
||||
{this.props.header}
|
||||
</div>
|
||||
)}
|
||||
<Alert
|
||||
noWarning={this.props.viewer.data.status?.hidePrivacyAlert}
|
||||
noWarning={this.props.viewer ? this.props.viewer.data.status?.hidePrivacyAlert : false}
|
||||
fileLoading={this.props.fileLoading}
|
||||
onAction={this.props.onAction}
|
||||
filecoin={this.props.filecoin}
|
||||
id={this.props.isMobile ? "slate-mobile-alert" : null}
|
||||
onUpdateViewer={this.props.onUpdateViewer}
|
||||
viewer={this.props.viewer}
|
||||
style={
|
||||
this.props.isMobile
|
||||
|
@ -1,10 +1,11 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/common/svg";
|
||||
import * as Styles from "~/common/styles";
|
||||
import * as UserBehaviors from "~/common/user-behaviors";
|
||||
|
||||
import { PopoverNavigation } from "~/components/system";
|
||||
import { css } from "@emotion/react";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
import { Boundary } from "~/components/system/components/fragments/Boundary";
|
||||
|
||||
@ -47,19 +48,19 @@ const STYLES_PROFILE_MOBILE = css`
|
||||
`;
|
||||
|
||||
const STYLES_PROFILE_IMAGE = css`
|
||||
background-color: ${Constants.system.white};
|
||||
background-color: ${Constants.system.foreground};
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
flex-shrink: 0;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
${"" /* @media (max-width: ${Constants.sizes.mobile}px) {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
} */}
|
||||
`;
|
||||
|
||||
const STYLES_PROFILE_USERNAME = css`
|
||||
@ -100,35 +101,10 @@ const STYLES_ITEM_BOX = css`
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_MENU = css`
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
right: 0px;
|
||||
|
||||
${"" /* @media (max-width: ${Constants.sizes.mobile}px) {
|
||||
top: 48px;
|
||||
left: 0px;
|
||||
} */}
|
||||
`;
|
||||
|
||||
const STYLES_MOBILE_HIDDEN = css`
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_MOBILE_ONLY = css`
|
||||
@media (min-width: ${Constants.sizes.mobile}px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export default class ApplicationUserControls extends React.Component {
|
||||
_handleAction = (e, data) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
export class ApplicationUserControlsPopup extends React.Component {
|
||||
_handleAction = (props) => {
|
||||
this.props.onTogglePopup();
|
||||
return this.props.onAction(data);
|
||||
this.props.onAction(props);
|
||||
};
|
||||
|
||||
_handleSignOut = (e) => {
|
||||
@ -139,55 +115,166 @@ export default class ApplicationUserControls extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
let tooltip = (
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled
|
||||
onOutsideRectEvent={() => this.props.onTogglePopup()}
|
||||
style={this.props.style}
|
||||
>
|
||||
<div>
|
||||
<PopoverNavigation
|
||||
style={{ top: 36, right: 0, padding: "12px 24px", width: 220 }}
|
||||
itemStyle={{ padding: "12px 0", fontSize: 18, justifyContent: "flex-end" }}
|
||||
navigation={[
|
||||
{
|
||||
text: "View profile",
|
||||
onClick: (e) =>
|
||||
this._handleAction(e, {
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_PROFILE",
|
||||
data: this.props.viewer,
|
||||
}),
|
||||
},
|
||||
{
|
||||
text: "Account settings",
|
||||
onClick: (e) =>
|
||||
this._handleAction(e, {
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_SETTINGS",
|
||||
}),
|
||||
},
|
||||
{
|
||||
text: "Contact us",
|
||||
onClick: (e) =>
|
||||
this._handleAction(e, {
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_HELP",
|
||||
}),
|
||||
},
|
||||
{
|
||||
text: "Sign out",
|
||||
onClick: (e) => {
|
||||
this._handleSignOut(e);
|
||||
},
|
||||
},
|
||||
]}
|
||||
if (this.props.popup === "profile") {
|
||||
const topSection = (
|
||||
<div css={Styles.HORIZONTAL_CONTAINER} style={{ marginBottom: 14 }}>
|
||||
<span
|
||||
css={STYLES_PROFILE_IMAGE}
|
||||
style={{
|
||||
cursor: "default",
|
||||
width: 46,
|
||||
height: 46,
|
||||
marginRight: 16,
|
||||
backgroundImage: `url('${this.props.viewer.data.photo}')`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
css={Styles.VERTICAL_CONTAINER}
|
||||
style={{
|
||||
height: 46,
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div css={Styles.HEADING_04}>
|
||||
{this.props.viewer.data.name || `@${this.props.viewer.username}`}
|
||||
</div>
|
||||
<div css={Styles.HORIZONTAL_CONTAINER}>
|
||||
<span css={Styles.SMALL_TEXT} style={{ marginRight: 8 }}>{`${
|
||||
this.props.viewer.library.length
|
||||
} File${this.props.viewer.library.length === 1 ? "" : "s"}`}</span>
|
||||
<span css={Styles.SMALL_TEXT}>{`${this.props.viewer.slates.length} Collection${
|
||||
this.props.viewer.slates.length === 1 ? "" : "s"
|
||||
}`}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Boundary>
|
||||
);
|
||||
);
|
||||
|
||||
const navigation = [
|
||||
[
|
||||
{
|
||||
text: (
|
||||
<Link href={`/$/user/${this.props.viewer.id}`} onAction={this._handleAction}>
|
||||
Profile
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<Link href={"/_/directory"} onAction={this._handleAction}>
|
||||
Directory
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
text: (
|
||||
<Link href={"/_/filecoin"} onAction={this._handleAction}>
|
||||
Filecoin
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<Link href={"/_/storage-deal"} onAction={this._handleAction}>
|
||||
Storage deal
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<Link href={"/_/api"} onAction={this._handleAction}>
|
||||
API
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
text: (
|
||||
<Link href={"/_/settings"} onAction={this._handleAction}>
|
||||
Settings
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
text: "Help",
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
this._handleAction({
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_HELP",
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Sign out",
|
||||
onClick: (e) => {
|
||||
this._handleSignOut(e);
|
||||
},
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div css={Styles.MOBILE_ONLY}>
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled={this.props.popup === "profile"}
|
||||
onOutsideRectEvent={() => this.props.onTogglePopup()}
|
||||
>
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
width: "max-content",
|
||||
position: "relative",
|
||||
border: "none",
|
||||
boxShadow: "none",
|
||||
width: "100vw",
|
||||
background: "none",
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
css={Styles.HEADING_04}
|
||||
itemStyle={{ fontSize: Constants.typescale.lvl0 }}
|
||||
topSection={topSection}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</Boundary>
|
||||
</div>
|
||||
<div css={Styles.MOBILE_HIDDEN}>
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled={this.props.popup === "profile"}
|
||||
onOutsideRectEvent={() => this.props.onTogglePopup()}
|
||||
>
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
top: 36,
|
||||
right: 0,
|
||||
width: "max-content",
|
||||
}}
|
||||
css={Styles.HEADING_04}
|
||||
itemStyle={{ fontSize: Constants.typescale.lvl0 }}
|
||||
topSection={topSection}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</Boundary>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class ApplicationUserControls extends React.Component {
|
||||
render() {
|
||||
let tooltip = <ApplicationUserControlsPopup {...this.props} />;
|
||||
return (
|
||||
<div css={STYLES_HEADER}>
|
||||
<div css={STYLES_PROFILE_MOBILE} style={{ position: "relative" }}>
|
||||
|
@ -7,9 +7,10 @@ import * as Credentials from "~/common/credentials";
|
||||
import { Boundary } from "~/components/system/components/fragments/Boundary";
|
||||
import { css } from "@emotion/react";
|
||||
import { Logo } from "~/common/logo";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
const STYLES_BACKGROUND = css`
|
||||
z-index: ${Constants.zindex.tooltip};
|
||||
z-index: ${Constants.zindex.cta};
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
@ -17,7 +18,6 @@ const STYLES_BACKGROUND = css`
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: ${Constants.zindex.modal};
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
@ -88,10 +88,38 @@ const STYLES_LINK_ITEM = css`
|
||||
`;
|
||||
|
||||
export default class CTATransition extends React.Component {
|
||||
state = {
|
||||
visible: false,
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
window.addEventListener("slate-global-open-cta", this._handleOpen);
|
||||
window.addEventListener("slate-global-close-cta", this._handleClose);
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
window.removeEventListener("slate-global-open-cta", this._handleOpen);
|
||||
window.removeEventListener("slate-global-close-cta", this._handleClose);
|
||||
};
|
||||
|
||||
_handleOpen = (e) => {
|
||||
this.setState({ visible: true });
|
||||
};
|
||||
|
||||
_handleClose = (e) => {
|
||||
this.setState({ visible: false });
|
||||
};
|
||||
|
||||
_handleAction = (props) => {
|
||||
this.setState({ visible: false }, () => {
|
||||
this.props.onAction(props);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{open && (
|
||||
this.state.visible && (
|
||||
<div>
|
||||
<div css={STYLES_BACKGROUND}>
|
||||
<div css={STYLES_TRANSITION}>
|
||||
<div css={STYLES_EXPLAINER}>Sign up or sign in to continue</div>
|
||||
@ -100,7 +128,7 @@ export default class CTATransition extends React.Component {
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled
|
||||
onOutsideRectEvent={this.props.onClose}
|
||||
onOutsideRectEvent={this._handleClose}
|
||||
>
|
||||
<div css={STYLES_POPOVER}>
|
||||
<Logo height="36px" style={{ display: "block", margin: "56px auto 0px auto" }} />
|
||||
@ -108,20 +136,27 @@ export default class CTATransition extends React.Component {
|
||||
<System.P style={{ margin: "56px 0", textAlign: "center" }}>
|
||||
An open-source file sharing network for research and collaboration
|
||||
</System.P>
|
||||
<a href={this.props.redirectURL} style={{ textDecoration: `none` }}>
|
||||
<System.ButtonPrimary full style={{ marginBottom: 16 }}>
|
||||
Continue to sign up
|
||||
</System.ButtonPrimary>{" "}
|
||||
</a>
|
||||
<a css={STYLES_LINK_ITEM} href={this.props.redirectURL}>
|
||||
Already have an account?
|
||||
</a>
|
||||
<Link href={"/_/auth?tab=signup"} onAction={this._handleAction}>
|
||||
<div style={{ textDecoration: `none` }}>
|
||||
<System.ButtonPrimary full style={{ marginBottom: 16 }}>
|
||||
Continue to sign up
|
||||
</System.ButtonPrimary>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href={"/_/auth?tab=signin"} onAction={this._handleAction}>
|
||||
<div css={STYLES_LINK_ITEM}>Already have an account?</div>
|
||||
</Link>
|
||||
</div>
|
||||
</Boundary>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// `/_${Strings.createQueryParams({
|
||||
// scene: "NAV_PROFILE",
|
||||
// user: this.props.creator.username,
|
||||
// })}`
|
||||
|
@ -17,6 +17,7 @@ import { Input } from "~/components/system/components/Input";
|
||||
import { Toggle } from "~/components/system/components/Toggle";
|
||||
import { Textarea } from "~/components/system/components/Textarea";
|
||||
import { Tag } from "~/components/system/components/Tag";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
import isEqual from "lodash/isEqual";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
@ -279,11 +280,11 @@ export const FileTypeDefaultPreview = (props) => {
|
||||
|
||||
class CarouselSidebar extends React.Component {
|
||||
state = {
|
||||
name: this.props.data.data.name || this.props.data.filename,
|
||||
body: this.props.data.data.body,
|
||||
source: this.props.data.data.source,
|
||||
author: this.props.data.data.author,
|
||||
tags: this.props.data.data.tags || [],
|
||||
name: this.props.file.data.name || this.props.file.filename,
|
||||
body: this.props.file.data.body,
|
||||
source: this.props.file.data.source,
|
||||
author: this.props.file.data.author,
|
||||
tags: this.props.file.data.tags || [],
|
||||
suggestions: this.props.viewer?.tags || [],
|
||||
selected: {},
|
||||
isPublic: false,
|
||||
@ -316,9 +317,13 @@ class CarouselSidebar extends React.Component {
|
||||
};
|
||||
|
||||
calculateSelected = () => {
|
||||
if (!this.props.viewer) {
|
||||
this.setState({ selected: {}, inPublicSlates: 0, isPublic: this.props.file.isPublic });
|
||||
return;
|
||||
}
|
||||
let inPublicSlates = 0;
|
||||
let selected = {};
|
||||
const id = this.props.data.id;
|
||||
const id = this.props.file.id;
|
||||
for (let slate of this.props.viewer.slates) {
|
||||
if (slate.objects.some((obj) => obj.id === id)) {
|
||||
if (slate.isPublic) {
|
||||
@ -327,7 +332,7 @@ class CarouselSidebar extends React.Component {
|
||||
selected[slate.id] = true;
|
||||
}
|
||||
}
|
||||
this.setState({ selected, inPublicSlates, isPublic: this.props.data.isPublic });
|
||||
this.setState({ selected, inPublicSlates, isPublic: this.props.file.isPublic });
|
||||
};
|
||||
|
||||
_handleToggleAccordion = (tab) => {
|
||||
@ -363,9 +368,9 @@ class CarouselSidebar extends React.Component {
|
||||
|
||||
_handleSave = async () => {
|
||||
if (this.props.external || !this.props.isOwner) return;
|
||||
this.props.onUpdateViewer({ tags: this.state.suggestions });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { tags: this.state.suggestions } });
|
||||
const response = await Actions.updateFile({
|
||||
id: this.props.data.id,
|
||||
id: this.props.file.id,
|
||||
data: {
|
||||
name: this.state.name,
|
||||
body: this.state.body,
|
||||
@ -379,6 +384,10 @@ class CarouselSidebar extends React.Component {
|
||||
};
|
||||
|
||||
_handleSaveCopy = async (data) => {
|
||||
if (!this.props.viewer) {
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
return;
|
||||
}
|
||||
this.setState({ loading: "savingCopy" });
|
||||
|
||||
await UserBehaviors.saveCopy({ files: [data] });
|
||||
@ -386,10 +395,10 @@ class CarouselSidebar extends React.Component {
|
||||
};
|
||||
|
||||
_handleUpload = async (e) => {
|
||||
if (this.props.external || !this.props.isOwner) return;
|
||||
if (this.props.external || !this.props.isOwner || !this.props.viewer) return;
|
||||
e.persist();
|
||||
this.setState({ isUploading: true });
|
||||
let previousCoverId = this.props.data.data.coverImage?.id;
|
||||
let previousCoverId = this.props.file.data.coverImage?.id;
|
||||
if (!e || !e.target) {
|
||||
this.setState({ isUploading: false });
|
||||
return;
|
||||
@ -405,7 +414,7 @@ class CarouselSidebar extends React.Component {
|
||||
//TODO(martina): create an endpoint specifically for cover images instead of this, which will delete original cover image etc
|
||||
|
||||
let updateReponse = await Actions.updateFile({
|
||||
id: this.props.data.id,
|
||||
id: this.props.file.id,
|
||||
data: {
|
||||
coverImage,
|
||||
},
|
||||
@ -422,14 +431,18 @@ class CarouselSidebar extends React.Component {
|
||||
};
|
||||
|
||||
_handleDownload = () => {
|
||||
if (this.props.data.data.type === "application/unity") {
|
||||
if (!this.props.viewer) {
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
return;
|
||||
}
|
||||
if (this.props.file.data.type === "application/unity") {
|
||||
this.setState({ isDownloading: true }, async () => {
|
||||
const response = await UserBehaviors.downloadZip(this.props.data);
|
||||
const response = await UserBehaviors.downloadZip(this.props.file);
|
||||
this.setState({ isDownloading: false });
|
||||
Events.hasError(response);
|
||||
});
|
||||
} else {
|
||||
UserBehaviors.download(this.props.data);
|
||||
UserBehaviors.download(this.props.file);
|
||||
}
|
||||
};
|
||||
|
||||
@ -439,22 +452,22 @@ class CarouselSidebar extends React.Component {
|
||||
this.props.onAction({
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_CREATE_SLATE",
|
||||
data: { files: [this.props.data] },
|
||||
data: { files: [this.props.file] },
|
||||
});
|
||||
};
|
||||
|
||||
_handleDelete = (res) => {
|
||||
if (this.props.external || !this.props.isOwner) return;
|
||||
if (this.props.external || !this.props.isOwner || !this.props.viewer) return;
|
||||
|
||||
if (!res) {
|
||||
this.setState({ modalShow: false });
|
||||
return;
|
||||
}
|
||||
const id = this.props.data.id;
|
||||
const id = this.props.file.id;
|
||||
|
||||
let updatedLibrary = this.props.viewer.library.filter((obj) => obj.id !== id);
|
||||
if (this.props.carouselType === "SLATE") {
|
||||
const slateId = this.props.current.id;
|
||||
const slateId = this.props.data.id;
|
||||
let slates = this.props.viewer.slates;
|
||||
for (let slate of slates) {
|
||||
if (slate.id === slateId) {
|
||||
@ -462,9 +475,9 @@ class CarouselSidebar extends React.Component {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.props.onUpdateViewer({ library: updatedLibrary, slates });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { library: updatedLibrary, slates } });
|
||||
} else {
|
||||
this.props.onUpdateViewer({ library: updatedLibrary });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { library: updatedLibrary } });
|
||||
}
|
||||
|
||||
UserBehaviors.deleteFiles(id);
|
||||
@ -476,14 +489,14 @@ class CarouselSidebar extends React.Component {
|
||||
if (slate.isPublic) {
|
||||
inPublicSlates -= 1;
|
||||
}
|
||||
UserBehaviors.removeFromSlate({ slate, ids: [this.props.data.id] });
|
||||
UserBehaviors.removeFromSlate({ slate, ids: [this.props.file.id] });
|
||||
} else {
|
||||
if (slate.isPublic) {
|
||||
inPublicSlates += 1;
|
||||
}
|
||||
UserBehaviors.addToSlate({
|
||||
slate,
|
||||
files: [this.props.data],
|
||||
files: [this.props.file],
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
@ -496,12 +509,17 @@ class CarouselSidebar extends React.Component {
|
||||
};
|
||||
|
||||
_handleRemove = async () => {
|
||||
if (!this.props.carouselType === "SLATE" || this.props.external || !this.props.isOwner) {
|
||||
if (
|
||||
!this.props.carouselType === "SLATE" ||
|
||||
this.props.external ||
|
||||
!this.props.isOwner ||
|
||||
!this.props.viewer
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = this.props.data.id;
|
||||
const slateId = this.props.current.id;
|
||||
const id = this.props.file.id;
|
||||
const slateId = this.props.data.id;
|
||||
let slates = this.props.viewer.slates;
|
||||
for (let slate of slates) {
|
||||
if (slate.id === slateId) {
|
||||
@ -509,13 +527,13 @@ class CarouselSidebar extends React.Component {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.props.onUpdateViewer({ slates });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { slates } });
|
||||
|
||||
UserBehaviors.removeFromSlate({ slate: this.props.current, ids: [this.props.data.id] });
|
||||
UserBehaviors.removeFromSlate({ slate: this.props.data, ids: [this.props.file.id] });
|
||||
};
|
||||
|
||||
_handleToggleVisibility = async (e) => {
|
||||
if (this.props.external || !this.props.isOwner) return;
|
||||
if (this.props.external || !this.props.isOwner || !this.props.viewer) return;
|
||||
const isVisible = this.state.isPublic || this.state.inPublicSlates > 0;
|
||||
let selected = cloneDeep(this.state.selected);
|
||||
if (this.state.inPublicSlates) {
|
||||
@ -538,19 +556,19 @@ class CarouselSidebar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.carouselType === "SLATE" && this.props.current.isPublic) {
|
||||
const slateId = this.props.current.id;
|
||||
if (this.props.carouselType === "SLATE" && this.props.data.isPublic) {
|
||||
const slateId = this.props.data.id;
|
||||
let slates = cloneDeep(this.props.viewer.slates);
|
||||
for (let slate of slates) {
|
||||
if (slate.id === slateId) {
|
||||
slate.objects = slate.objects.filter((obj) => obj.id !== this.props.data.id);
|
||||
slate.objects = slate.objects.filter((obj) => obj.id !== this.props.file.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.props.onUpdateViewer({ slates });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { slates } });
|
||||
}
|
||||
|
||||
let response = await Actions.toggleFilePrivacy({ ...this.props.data, isPublic: !isVisible });
|
||||
let response = await Actions.toggleFilePrivacy({ ...this.props.file, isPublic: !isVisible });
|
||||
Events.hasError(response);
|
||||
if (isVisible) {
|
||||
this.setState({ inPublicSlates: 0, isPublic: false, selected });
|
||||
@ -561,7 +579,7 @@ class CarouselSidebar extends React.Component {
|
||||
|
||||
render() {
|
||||
const isVisible = this.state.isPublic || this.state.inPublicSlates > 0 ? true : false;
|
||||
const file = this.props.data;
|
||||
const file = this.props.file;
|
||||
const { coverImage, type, size } = file.data;
|
||||
const editingAllowed = this.props.isOwner && !this.props.isRepost && !this.props.external;
|
||||
|
||||
@ -679,19 +697,23 @@ class CarouselSidebar extends React.Component {
|
||||
{
|
||||
this.props.carouselType === "ACTIVITY"
|
||||
? actions.push(
|
||||
<div
|
||||
key="go-to-slate"
|
||||
css={STYLES_ACTION}
|
||||
onClick={() =>
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_SLATE",
|
||||
data: file.slate,
|
||||
})
|
||||
}
|
||||
>
|
||||
<SVG.Slate height="24px" />
|
||||
<span style={{ marginLeft: 16 }}>Go to collection</span>
|
||||
<div style={{ borderBottom: "1px solid #3c3c3c" }}>
|
||||
<Link href={`/$/slate/${file.slateId}`} onAction={this.props.onAction}>
|
||||
<div
|
||||
key="go-to-slate"
|
||||
css={STYLES_ACTION}
|
||||
// onClick={() =>
|
||||
// this.props.onAction({
|
||||
// type: "NAVIGATE",
|
||||
// value: "NAV_SLATE",
|
||||
// data: file.slate,
|
||||
// })
|
||||
// }
|
||||
>
|
||||
<SVG.Slate height="24px" />
|
||||
<span style={{ marginLeft: 16 }}>Go to collection</span>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
: null;
|
||||
@ -713,7 +735,7 @@ class CarouselSidebar extends React.Component {
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!this.props.external && (!this.props.isOwner || this.props.isRepost)) {
|
||||
if (!this.props.isOwner || this.props.isRepost) {
|
||||
actions.push(
|
||||
<div key="save-copy" css={STYLES_ACTION} onClick={() => this._handleSaveCopy(file)}>
|
||||
<SVG.Save height="24px" />
|
||||
@ -843,15 +865,15 @@ class CarouselSidebar extends React.Component {
|
||||
return (
|
||||
<>
|
||||
{this.state.modalShow && (
|
||||
<ConfirmationModal
|
||||
<ConfirmationModal
|
||||
type={"DELETE"}
|
||||
withValidation={false}
|
||||
callback={this._handleDelete}
|
||||
callback={this._handleDelete}
|
||||
header={`Are you sure you want to delete the file “${this.state.name}”?`}
|
||||
subHeader={`This file will be deleted from all connected collections and your file library. You can’t undo this action.`}
|
||||
/>
|
||||
)}
|
||||
<div css={STYLES_SIDEBAR} style={{ display: this.props.display }}>
|
||||
<div css={STYLES_SIDEBAR} style={{ display: this.props.display, paddingBottom: 96 }}>
|
||||
{this.state.showSavedMessage && (
|
||||
<div css={STYLES_AUTOSAVE}>
|
||||
<SVG.Check height="14px" style={{ marginRight: 4 }} />
|
||||
@ -861,59 +883,55 @@ class CarouselSidebar extends React.Component {
|
||||
<div key="s-1" css={STYLES_DISMISS_BOX} onClick={this.props.onClose}>
|
||||
<SVG.Dismiss height="24px" />
|
||||
</div>
|
||||
|
||||
<div key="s-2" style={{ marginBottom: 80 }}>
|
||||
{elements}
|
||||
|
||||
{!this.props.external && <div css={STYLES_ACTIONS}>{actions}</div>}
|
||||
{privacy}
|
||||
{uploadCoverImage}
|
||||
{!this.props.external && (
|
||||
<>
|
||||
<div
|
||||
css={STYLES_SECTION_HEADER}
|
||||
style={{ cursor: "pointer", marginTop: 48 }}
|
||||
onClick={() => this._handleToggleAccordion("showConnectedSection")}
|
||||
{elements}
|
||||
<div css={STYLES_ACTIONS}>{actions}</div>
|
||||
{privacy}
|
||||
{uploadCoverImage}
|
||||
{!this.props.external && this.props.viewer && (
|
||||
<>
|
||||
<div
|
||||
css={STYLES_SECTION_HEADER}
|
||||
style={{ cursor: "pointer", marginTop: 48 }}
|
||||
onClick={() => this._handleToggleAccordion("showConnectedSection")}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
marginRight: 8,
|
||||
transform: this.state.showConnectedSection ? "none" : "rotate(-90deg)",
|
||||
transition: "100ms ease transform",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
marginRight: 8,
|
||||
transform: this.state.showConnectedSection ? "none" : "rotate(-90deg)",
|
||||
transition: "100ms ease transform",
|
||||
}}
|
||||
>
|
||||
<SVG.ChevronDown height="24px" display="block" />
|
||||
</span>
|
||||
<span>Add to collection</span>
|
||||
<SVG.ChevronDown height="24px" display="block" />
|
||||
</span>
|
||||
<span>Add to collection</span>
|
||||
</div>
|
||||
{this.state.showConnectedSection && (
|
||||
<div style={{ width: "100%", margin: "24px 0 44px 0" }}>
|
||||
<SlatePicker
|
||||
dark
|
||||
slates={this.props.viewer.slates || []}
|
||||
onCreateSlate={this._handleCreateSlate}
|
||||
selectedColor={Constants.system.white}
|
||||
files={[this.props.file]}
|
||||
selected={this.state.selected}
|
||||
onAdd={this._handleAdd}
|
||||
/>
|
||||
</div>
|
||||
{this.state.showConnectedSection && (
|
||||
<div style={{ width: "100%", margin: "24px 0 44px 0" }}>
|
||||
<SlatePicker
|
||||
dark
|
||||
slates={this.props.viewer.slates}
|
||||
onCreateSlate={this._handleCreateSlate}
|
||||
selectedColor={Constants.system.white}
|
||||
files={[this.props.data]}
|
||||
selected={this.state.selected}
|
||||
onAdd={this._handleAdd}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{this.props.data.filename.endsWith(".md") ? (
|
||||
<>
|
||||
<div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}>
|
||||
Settings
|
||||
</div>
|
||||
<div css={STYLES_OPTIONS_SECTION}>
|
||||
<div css={STYLES_TEXT}>Dark mode</div>
|
||||
<Toggle dark active={this.props?.theme?.darkmode} onChange={this._handleDarkMode} />
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
{this.props.file.filename.endsWith(".md") ? (
|
||||
<>
|
||||
<div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}>
|
||||
Settings
|
||||
</div>
|
||||
<div css={STYLES_OPTIONS_SECTION}>
|
||||
<div css={STYLES_TEXT}>Dark mode</div>
|
||||
<Toggle dark active={this.props?.theme?.darkmode} onChange={this._handleDarkMode} />
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -937,4 +955,4 @@ export default withTheme(CarouselSidebar);
|
||||
: "This file is currently not visible to others unless they have the link."}
|
||||
</div>
|
||||
</> */
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import * as Window from "~/common/window";
|
||||
import * as UserBehaviors from "~/common/user-behaviors";
|
||||
import * as Events from "~/common/custom-events";
|
||||
|
||||
import { Link } from "~/components/core/Link";
|
||||
import { css } from "@emotion/react";
|
||||
import { Boundary } from "~/components/system/components/fragments/Boundary";
|
||||
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
|
||||
@ -457,6 +458,10 @@ export default class DataView extends React.Component {
|
||||
};
|
||||
|
||||
_handleDownloadFiles = async () => {
|
||||
if (!this.props.viewer) {
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
return;
|
||||
}
|
||||
const selectedFiles = this.props.items.filter((_, i) => this.state.checked[i]);
|
||||
UserBehaviors.compressAndDownloadFiles({
|
||||
files: selectedFiles,
|
||||
@ -483,19 +488,12 @@ export default class DataView extends React.Component {
|
||||
}
|
||||
|
||||
let library = this.props.viewer.library.filter((obj) => !ids.includes(obj.id));
|
||||
this.props.onUpdateViewer({ library });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { library } });
|
||||
|
||||
UserBehaviors.deleteFiles(ids);
|
||||
this.setState({ checked: {}, modalShow: false });
|
||||
};
|
||||
|
||||
_handleSelect = (index) => {
|
||||
Events.dispatchCustomEvent({
|
||||
name: "slate-global-open-carousel",
|
||||
detail: { index },
|
||||
});
|
||||
};
|
||||
|
||||
_handleCheckBoxMouseEnter = (i) => {
|
||||
if (this.props.isOwner) {
|
||||
this.setState({ hover: i });
|
||||
@ -526,6 +524,10 @@ export default class DataView extends React.Component {
|
||||
};
|
||||
|
||||
_handleAddToSlate = (e) => {
|
||||
if (!this.props.viewer) {
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
return;
|
||||
}
|
||||
let userFiles = this.props.viewer.library;
|
||||
let files = Object.keys(this.state.checked).map((index) => userFiles[index]);
|
||||
this.props.onAction({
|
||||
@ -646,30 +648,32 @@ export default class DataView extends React.Component {
|
||||
>
|
||||
Add to collection
|
||||
</ButtonPrimary>
|
||||
<ButtonPrimary
|
||||
transparent
|
||||
style={{ color: Constants.system.white }}
|
||||
onClick={() => {
|
||||
this.props.onAction({
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_EDIT_TAGS",
|
||||
data: {
|
||||
numChecked,
|
||||
commonTags: this.getCommonTagFromSelectedItems(),
|
||||
objects: this.props.items,
|
||||
checked: this.state.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Edit tag{numChecked > 1 ? "s" : ""}
|
||||
</ButtonPrimary>
|
||||
{this.props.isOwner && (
|
||||
<ButtonPrimary
|
||||
transparent
|
||||
style={{ color: Constants.system.white }}
|
||||
onClick={() => {
|
||||
this.props.onAction({
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_EDIT_TAGS",
|
||||
data: {
|
||||
numChecked,
|
||||
commonTags: this.getCommonTagFromSelectedItems(),
|
||||
objects: this.props.items,
|
||||
checked: this.state.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Edit tags
|
||||
</ButtonPrimary>
|
||||
)}
|
||||
<ButtonWarning
|
||||
transparent
|
||||
style={{ marginLeft: 8, color: Constants.system.white }}
|
||||
onClick={() => this._handleDownloadFiles()}
|
||||
>
|
||||
{Strings.pluralize("Download file", numChecked)}
|
||||
Download
|
||||
</ButtonWarning>
|
||||
{this.props.isOwner && (
|
||||
<ButtonWarning
|
||||
@ -677,14 +681,14 @@ export default class DataView extends React.Component {
|
||||
style={{ marginLeft: 8, color: Constants.system.white }}
|
||||
onClick={() => this.setState({ modalShow: true })}
|
||||
>
|
||||
{Strings.pluralize("Delete file", numChecked)}
|
||||
Delete
|
||||
</ButtonWarning>
|
||||
)}
|
||||
{this.state.modalShow && (
|
||||
<ConfirmationModal
|
||||
<ConfirmationModal
|
||||
type={"DELETE"}
|
||||
withValidation={false}
|
||||
callback={this._handleDelete}
|
||||
callback={this._handleDelete}
|
||||
header={`Are you sure you want to delete the selected files?`}
|
||||
subHeader={`These files will be deleted from all connected collections and your file library. You can’t undo this action.`}
|
||||
/>
|
||||
@ -704,129 +708,67 @@ export default class DataView extends React.Component {
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
if (this.props.view === 0) {
|
||||
if (this.props.view === "grid") {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<GroupSelectable onSelection={this._handleDragAndSelect}>
|
||||
<div css={STYLES_IMAGE_GRID} ref={this.gridWrapperEl}>
|
||||
{this.props.items.slice(0, this.state.viewLimit).map((each, i) => {
|
||||
const cid = each.cid;
|
||||
return (
|
||||
<Selectable
|
||||
<Link
|
||||
key={each.id}
|
||||
draggable={!numChecked}
|
||||
onDragStart={(e) => {
|
||||
this._disableDragAndDropUploadEvent();
|
||||
this._handleDragToDesktop(e, each);
|
||||
}}
|
||||
onDragEnd={this._enableDragAndDropUploadEvent}
|
||||
selectableKey={i}
|
||||
css={STYLES_IMAGE_BOX}
|
||||
style={{
|
||||
width: this.state.imageSize,
|
||||
height: this.state.imageSize,
|
||||
boxShadow: numChecked
|
||||
? `0px 0px 0px 1px ${Constants.system.lightBorder} inset,
|
||||
0 0 40px 0 ${Constants.system.shadow}`
|
||||
: "",
|
||||
}}
|
||||
onClick={() => this._handleSelect(i)}
|
||||
onMouseEnter={() => this._handleCheckBoxMouseEnter(i)}
|
||||
onMouseLeave={() => this._handleCheckBoxMouseLeave(i)}
|
||||
redirect
|
||||
params={{ ...this.props.page?.params, cid: each.cid }}
|
||||
onAction={this.props.onAction}
|
||||
>
|
||||
<SlateMediaObjectPreview file={each} />
|
||||
<span css={STYLES_MOBILE_HIDDEN} style={{ pointerEvents: "auto" }}>
|
||||
{numChecked || this.state.hover === i || this.state.menu === each.id ? (
|
||||
<React.Fragment>
|
||||
{/* <div
|
||||
css={STYLES_ICON_BOX_BACKGROUND}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({
|
||||
menu: this.state.menu === each.id ? null : each.id,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SVG.MoreHorizontal height="24px" />
|
||||
{this.state.menu === each.id ? (
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled
|
||||
onOutsideRectEvent={this._handleHide}
|
||||
>
|
||||
{this.props.isOwner ? (
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
top: "32px",
|
||||
right: "0px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: "Copy CID",
|
||||
onClick: (e) => this._handleCopy(e, cid),
|
||||
},
|
||||
{
|
||||
text: "Copy link",
|
||||
onClick: (e) =>
|
||||
this._handleCopy(e, Strings.getURLfromCID(cid)),
|
||||
},
|
||||
{
|
||||
text: "Delete",
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({ menu: null }, () =>
|
||||
this._handleDelete(cid, each.id)
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
top: "32px",
|
||||
right: "0px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: "Copy CID",
|
||||
onClick: (e) => this._handleCopy(e, cid),
|
||||
},
|
||||
{
|
||||
text: "Copy link",
|
||||
onClick: (e) =>
|
||||
this._handleCopy(e, Strings.getURLfromCID(cid)),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Boundary>
|
||||
) : null}
|
||||
</div> */}
|
||||
|
||||
<div onClick={(e) => this._handleCheckBox(e, i)}>
|
||||
<CheckBox
|
||||
name={i}
|
||||
value={!!this.state.checked[i]}
|
||||
boxStyle={{
|
||||
height: 24,
|
||||
width: 24,
|
||||
backgroundColor: this.state.checked[i]
|
||||
? Constants.system.brand
|
||||
: "rgba(255, 255, 255, 0.75)",
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
</span>
|
||||
</Selectable>
|
||||
<Selectable
|
||||
key={each.id}
|
||||
draggable={!numChecked}
|
||||
onDragStart={(e) => {
|
||||
this._disableDragAndDropUploadEvent();
|
||||
this._handleDragToDesktop(e, each);
|
||||
}}
|
||||
onDragEnd={this._enableDragAndDropUploadEvent}
|
||||
selectableKey={i}
|
||||
css={STYLES_IMAGE_BOX}
|
||||
style={{
|
||||
width: this.state.imageSize,
|
||||
height: this.state.imageSize,
|
||||
boxShadow: numChecked
|
||||
? `0px 0px 0px 1px ${Constants.system.lightBorder} inset,
|
||||
0 0 40px 0 ${Constants.system.shadow}`
|
||||
: "",
|
||||
}}
|
||||
onMouseEnter={() => this._handleCheckBoxMouseEnter(i)}
|
||||
onMouseLeave={() => this._handleCheckBoxMouseLeave(i)}
|
||||
>
|
||||
<SlateMediaObjectPreview file={each} />
|
||||
<span css={STYLES_MOBILE_HIDDEN} style={{ pointerEvents: "auto" }}>
|
||||
{numChecked || this.state.hover === i || this.state.menu === each.id ? (
|
||||
<React.Fragment>
|
||||
<div onClick={(e) => this._handleCheckBox(e, i)}>
|
||||
<CheckBox
|
||||
name={i}
|
||||
value={!!this.state.checked[i]}
|
||||
boxStyle={{
|
||||
height: 24,
|
||||
width: 24,
|
||||
backgroundColor: this.state.checked[i]
|
||||
? Constants.system.brand
|
||||
: "rgba(255, 255, 255, 0.75)",
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
</span>
|
||||
</Selectable>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
{[0, 1, 2, 3].map((i) => (
|
||||
@ -923,18 +865,24 @@ export default class DataView extends React.Component {
|
||||
onDragEnd={this._enableDragAndDropUploadEvent}
|
||||
>
|
||||
<FilePreviewBubble cid={cid} type={each.data.type}>
|
||||
<div css={STYLES_CONTAINER_HOVER} onClick={() => this._handleSelect(index)}>
|
||||
<div css={STYLES_ICON_BOX_HOVER} style={{ paddingLeft: 0, paddingRight: 18 }}>
|
||||
<FileTypeIcon type={each.data.type} height="24px" />
|
||||
<Link
|
||||
redirect
|
||||
params={{ ...this.props.page.params, cid: each.cid }}
|
||||
onAction={this.props.onAction}
|
||||
>
|
||||
<div css={STYLES_CONTAINER_HOVER}>
|
||||
<div css={STYLES_ICON_BOX_HOVER} style={{ paddingLeft: 0, paddingRight: 18 }}>
|
||||
<FileTypeIcon type={each.data.type} height="24px" />
|
||||
</div>
|
||||
<div css={STYLES_LINK}>{each.data.name || each.filename}</div>
|
||||
</div>
|
||||
<div css={STYLES_LINK}>{each.data.name || each.filename}</div>
|
||||
</div>
|
||||
</Link>
|
||||
</FilePreviewBubble>
|
||||
</Selectable>
|
||||
),
|
||||
tags: <>{each.data.tags?.length && <Tags tags={each.data.tags} />}</>,
|
||||
size: <div css={STYLES_VALUE}>{Strings.bytesToSize(each.data.size)}</div>,
|
||||
more: (
|
||||
more: this.props.isOwner ? (
|
||||
<div
|
||||
css={STYLES_ICON_BOX_HOVER}
|
||||
onClick={() =>
|
||||
@ -957,27 +905,29 @@ export default class DataView extends React.Component {
|
||||
right: "40px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: "Copy CID",
|
||||
onClick: (e) => this._handleCopy(e, cid),
|
||||
},
|
||||
// {
|
||||
// text: "Copy link",
|
||||
// onClick: (e) => this._handleCopy(e, Strings.getURLfromCID(cid)),
|
||||
// },
|
||||
{
|
||||
text: "Delete",
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({ menu: null, modalShow: true });
|
||||
[
|
||||
{
|
||||
text: "Copy CID",
|
||||
onClick: (e) => this._handleCopy(e, cid),
|
||||
},
|
||||
},
|
||||
// {
|
||||
// text: "Copy link",
|
||||
// onClick: (e) => this._handleCopy(e, Strings.getURLfromCID(cid)),
|
||||
// },
|
||||
{
|
||||
text: "Delete",
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({ menu: null, modalShow: true });
|
||||
},
|
||||
},
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</Boundary>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
) : null,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -8,11 +8,11 @@ const withView = (Component) => (props) => {
|
||||
|
||||
const [isIntersecting, setIntersecting] = React.useState(false);
|
||||
|
||||
const observer = new IntersectionObserver(([entry]) => {
|
||||
if (entry.isIntersecting) setIntersecting(true);
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const observer = new IntersectionObserver(([entry]) => {
|
||||
if (entry.isIntersecting) setIntersecting(true);
|
||||
});
|
||||
|
||||
observer.observe(ref.current);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
|
83
components/core/Link.js
Normal file
83
components/core/Link.js
Normal file
@ -0,0 +1,83 @@
|
||||
import * as React from "react";
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
export class Link extends React.Component {
|
||||
state = {
|
||||
href: this.props.href
|
||||
? this.props.href
|
||||
: this.props.params
|
||||
? Strings.getQueryStringFromParams(this.props.params)
|
||||
: null,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onUpdate: () => {
|
||||
console.log(
|
||||
`ERROR: onUpdate is missing from a Link object called with href ${this.props.href}`
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
_handleClick = (e) => {
|
||||
if (!this.state.href) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isLeftClick = event.button === 0; //NOTE(martina): should process right clicks normally
|
||||
|
||||
const isModified = event.metaKey || event.altKey || event.ctrlKey || event.shiftKey; //NOTE(martina): should process ctrl/shift/etc + click normally
|
||||
|
||||
const hasNoTarget = !this.props.target; //NOTE(martina): should process normally if specify opening in another tab etc.
|
||||
|
||||
if (!isLeftClick || isModified || !hasNoTarget) {
|
||||
return; //NOTE(martina): always allow ctrl + click or right click to continue, and don't trigger an onClick
|
||||
}
|
||||
|
||||
e.preventDefault(); //NOTE(martina): prevents the anchor component from getting clicked, so that we can handle the behavior manually
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(); //NOTE(martina): onClick is triggered whether or not it is disabled
|
||||
}
|
||||
|
||||
if (this.props.disabled) {
|
||||
return; //NOTE(martina): disabled = true disables the onAction from firing
|
||||
}
|
||||
|
||||
if (this.props.href) {
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
href: this.props.href,
|
||||
callback: this.props.callback,
|
||||
redirect: this.props.redirect,
|
||||
});
|
||||
} else {
|
||||
this.props.onAction({
|
||||
type: "UPDATE_PARAMS",
|
||||
params: this.props.params,
|
||||
callback: this.props.callback,
|
||||
redirect: this.props.redirect,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span onClick={this._handleClick}>
|
||||
<a
|
||||
style={{
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
cursor: "pointer",
|
||||
...this.props.style,
|
||||
}}
|
||||
css={this.props.css}
|
||||
target={this.props.target}
|
||||
href={this.state.href}
|
||||
>
|
||||
{this.props.children}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { Logo } from "~/common/logo.js";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
const STYLES_ROOT = css`
|
||||
position: -webkit-sticky;
|
||||
@ -195,7 +196,7 @@ const NewWebsitePrototypeHeader = (props) => {
|
||||
};
|
||||
|
||||
const communityURL = "https://github.com/filecoin-project/slate";
|
||||
const signInURL = "/_";
|
||||
const signInURL = "/_/auth";
|
||||
const styleMenu = open ? openMenu : null;
|
||||
const styleBurgerBun = open ? openBurgerBun : null;
|
||||
const styleBurgerBun2 = open ? openBurgerBun2 : null;
|
||||
|
@ -7,6 +7,8 @@ import * as Utilities from "~/common/utilities";
|
||||
import * as Events from "~/common/custom-events";
|
||||
import * as Window from "~/common/window";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Link } from "~/components/core/Link";
|
||||
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
|
||||
import { css } from "@emotion/react";
|
||||
import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons";
|
||||
@ -239,48 +241,236 @@ const STYLES_DIRECTORY_NAME = css`
|
||||
// opacity: 0;
|
||||
// `;
|
||||
|
||||
function UserEntry({
|
||||
user,
|
||||
button,
|
||||
onClick,
|
||||
message,
|
||||
external,
|
||||
url,
|
||||
checkStatus,
|
||||
showStatusIndicator,
|
||||
}) {
|
||||
function UserEntry({ user, button, onClick, message, checkStatus }) {
|
||||
const isOnline = checkStatus({ id: user.id });
|
||||
|
||||
return (
|
||||
<div key={user.username} css={STYLES_USER_ENTRY}>
|
||||
{external ? (
|
||||
<a css={STYLES_USER} style={{ textDecoration: "none" }} href={url}>
|
||||
<div
|
||||
css={STYLES_DIRECTORY_PROFILE_IMAGE}
|
||||
style={{ backgroundImage: `url(${user.data.photo})` }}
|
||||
>
|
||||
{showStatusIndicator && isOnline && <div css={STYLES_DIRECTORY_STATUS_INDICATOR} />}
|
||||
</div>
|
||||
<span css={STYLES_DIRECTORY_NAME}>
|
||||
{user.data.name || `@${user.username}`}
|
||||
{message ? <span css={STYLES_MESSAGE}>{message}</span> : null}
|
||||
</span>
|
||||
</a>
|
||||
) : (
|
||||
<div css={STYLES_USER} onClick={onClick}>
|
||||
<div
|
||||
css={STYLES_DIRECTORY_PROFILE_IMAGE}
|
||||
style={{ backgroundImage: `url(${user.data.photo})` }}
|
||||
>
|
||||
{isOnline && <div css={STYLES_DIRECTORY_STATUS_INDICATOR} />}
|
||||
</div>
|
||||
<span css={STYLES_DIRECTORY_NAME}>
|
||||
{user.data.name || `@${user.username}`}
|
||||
{message ? <span css={STYLES_MESSAGE}>{message}</span> : null}
|
||||
</span>
|
||||
<div css={STYLES_USER} onClick={onClick}>
|
||||
<div
|
||||
css={STYLES_DIRECTORY_PROFILE_IMAGE}
|
||||
style={{ backgroundImage: `url(${user.data.photo})` }}
|
||||
>
|
||||
{isOnline && <div css={STYLES_DIRECTORY_STATUS_INDICATOR} />}
|
||||
</div>
|
||||
<span css={STYLES_DIRECTORY_NAME}>
|
||||
{user.data.name || `@${user.username}`}
|
||||
{message ? <span css={STYLES_MESSAGE}>{message}</span> : null}
|
||||
</span>
|
||||
</div>
|
||||
{button}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FilesPage({
|
||||
library,
|
||||
isOwner,
|
||||
isMobile,
|
||||
viewer,
|
||||
onAction,
|
||||
resources,
|
||||
page,
|
||||
tab = "grid",
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
{isMobile ? null : (
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
{
|
||||
title: <SVG.GridView height="24px" style={{ display: "block" }} />,
|
||||
value: { tab: "grid", subtab: "files" },
|
||||
},
|
||||
{
|
||||
title: <SVG.TableView height="24px" style={{ display: "block" }} />,
|
||||
value: { tab: "table", subtab: "files" },
|
||||
},
|
||||
]}
|
||||
value={tab}
|
||||
onAction={onAction}
|
||||
style={{ margin: "0 0 24px 0" }}
|
||||
/>
|
||||
)}
|
||||
{external ? null : button}
|
||||
{library.length ? (
|
||||
<DataView
|
||||
key="scene-profile"
|
||||
onAction={onAction}
|
||||
viewer={viewer}
|
||||
isOwner={isOwner}
|
||||
items={library}
|
||||
view={tab}
|
||||
resources={resources}
|
||||
page={page}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState>
|
||||
<FileTypeGroup />
|
||||
<div style={{ marginTop: 24 }}>This user does not have any public files yet</div>
|
||||
</EmptyState>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CollectionsPage({
|
||||
user,
|
||||
viewer,
|
||||
fetched,
|
||||
subscriptions,
|
||||
tab = "collections",
|
||||
isOwner,
|
||||
onAction,
|
||||
}) {
|
||||
let slates = [];
|
||||
if (tab === "collections") {
|
||||
slates = user.slates
|
||||
? isOwner
|
||||
? user.slates.filter((slate) => slate.isPublic === true)
|
||||
: user.slates
|
||||
: slates;
|
||||
} else {
|
||||
slates = subscriptions;
|
||||
}
|
||||
slates = slates || [];
|
||||
return (
|
||||
<div>
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
{ title: "Collections", value: { tab: "collections", subtab: "collections" } },
|
||||
{ title: "Subscribed", value: { tab: "subscribed", subtab: "collections" } },
|
||||
]}
|
||||
value={tab}
|
||||
onAction={onAction}
|
||||
style={{ margin: "0 0 24px 0" }}
|
||||
/>
|
||||
{slates?.length ? (
|
||||
<SlatePreviewBlocks external={!viewer} slates={slates || []} onAction={onAction} />
|
||||
) : (
|
||||
<EmptyState>
|
||||
{tab === "collections" || fetched ? (
|
||||
<React.Fragment>
|
||||
<SVG.Slate height="24px" style={{ marginBottom: 24 }} />
|
||||
{tab === "collections"
|
||||
? `This user does not have any public collections yet`
|
||||
: `This user is not following any collections yet`}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<LoaderSpinner style={{ height: 24, width: 24 }} />
|
||||
)}
|
||||
</EmptyState>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PeersPage({
|
||||
checkStatus,
|
||||
viewer,
|
||||
following,
|
||||
followers,
|
||||
fetched,
|
||||
tab = "following",
|
||||
onAction,
|
||||
onLoginModal,
|
||||
}) {
|
||||
const [selectedUser, setSelectedUser] = useState(false);
|
||||
const selectUser = (e, id) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (!id || selectedUser === id) {
|
||||
setSelectedUser(null);
|
||||
} else {
|
||||
setSelectedUser(id);
|
||||
}
|
||||
};
|
||||
const followUser = async (e, id) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
selectUser(e, null);
|
||||
if (!viewer) {
|
||||
onLoginModal();
|
||||
return;
|
||||
}
|
||||
await Actions.createSubscription({
|
||||
userId: id,
|
||||
});
|
||||
};
|
||||
|
||||
let peers = tab === "following" ? following : followers;
|
||||
peers = peers.map((relation) => {
|
||||
const following = !!(
|
||||
viewer &&
|
||||
viewer.following.some((subscription) => {
|
||||
return subscription.id === relation.id;
|
||||
}).length
|
||||
);
|
||||
let button =
|
||||
!viewer || relation.id !== viewer?.id ? (
|
||||
<div css={STYLES_ITEM_BOX} onClick={(e) => selectUser(e, relation.id)}>
|
||||
<SVG.MoreHorizontal height="24px" />
|
||||
{selectedUser === relation.id ? (
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled
|
||||
onOutsideRectEvent={(e) => selectUser(e)}
|
||||
>
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
top: "40px",
|
||||
right: "0px",
|
||||
}}
|
||||
navigation={[
|
||||
[
|
||||
{
|
||||
text: following ? "Unfollow" : "Follow",
|
||||
onClick: (e) => followUser(e, relation.id),
|
||||
},
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</Boundary>
|
||||
) : null}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Link href={`/$/user/${relation.id}`} onAction={onAction}>
|
||||
<UserEntry key={relation.id} user={relation} button={button} checkStatus={checkStatus} />
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
{ title: "Following", value: { tab: "following", subtab: "peers" } },
|
||||
{ title: "Followers", value: { tab: "followers", subtab: "peers" } },
|
||||
]}
|
||||
value={tab}
|
||||
onAction={onAction}
|
||||
style={{ margin: "0 0 24px 0" }}
|
||||
/>
|
||||
<div>
|
||||
{peers?.length ? (
|
||||
peers
|
||||
) : (
|
||||
<EmptyState>
|
||||
{fetched ? (
|
||||
<React.Fragment>
|
||||
<SVG.Users height="24px" style={{ marginBottom: 24 }} />
|
||||
{tab === "following"
|
||||
? `This user is not following anyone yet`
|
||||
: `This user does not have any followers yet`}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<LoaderSpinner style={{ height: 24, width: 24 }} />
|
||||
)}
|
||||
</EmptyState>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -289,12 +479,7 @@ export default class Profile extends React.Component {
|
||||
_ref = null;
|
||||
|
||||
state = {
|
||||
view: 0,
|
||||
slateTab: 0,
|
||||
peerTab: 0,
|
||||
// copyValue: "",
|
||||
contextMenu: null,
|
||||
slates: this.props.user.slates,
|
||||
subscriptions: [],
|
||||
followers: [],
|
||||
following: [],
|
||||
@ -305,21 +490,22 @@ export default class Profile extends React.Component {
|
||||
return entry.id === this.props.user.id;
|
||||
}),
|
||||
fetched: false,
|
||||
tab: this.props.tab || 0,
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
this._handleUpdatePage();
|
||||
this.fetchSocial();
|
||||
};
|
||||
|
||||
componentDidUpdate = (prevProps) => {
|
||||
if (this.props.page?.tab !== prevProps.page?.tab) {
|
||||
this.setState({ tab: this.props.page.tab });
|
||||
if (!this.state.fetched && this.props.page.params !== prevProps.page.params) {
|
||||
this.fetchSocial();
|
||||
}
|
||||
};
|
||||
|
||||
fetchSocial = async () => {
|
||||
if (this.state.fetched) return;
|
||||
if (this.props.page.params?.subtab !== "peers" && this.props.page.params?.tab !== "subscribed")
|
||||
return;
|
||||
let following, followers, subscriptions;
|
||||
if (this.props.user.id === this.props.viewer?.id) {
|
||||
following = this.props.viewer?.following;
|
||||
@ -343,31 +529,23 @@ export default class Profile extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
// _handleCopy = (e, value) => {
|
||||
// e.stopPropagation();
|
||||
// this.setState({ copyValue: value }, () => {
|
||||
// this._ref.select();
|
||||
// document.execCommand("copy");
|
||||
// this._handleHide();
|
||||
// });
|
||||
// };
|
||||
|
||||
_handleHide = (e) => {
|
||||
this.setState({ contextMenu: null });
|
||||
};
|
||||
|
||||
_handleClick = (e, value) => {
|
||||
e.stopPropagation();
|
||||
if (this.state.contextMenu === value) {
|
||||
this._handleHide();
|
||||
} else {
|
||||
this.setState({ contextMenu: value });
|
||||
}
|
||||
};
|
||||
// _handleClick = (e, value) => {
|
||||
// e.stopPropagation();
|
||||
// if (this.state.contextMenu === value) {
|
||||
// this._handleHide();
|
||||
// } else {
|
||||
// this.setState({ contextMenu: value });
|
||||
// }
|
||||
// };
|
||||
|
||||
_handleFollow = async (e, id) => {
|
||||
if (this.props.external) {
|
||||
this._handleRedirectToInternal();
|
||||
this._handleLoginModal();
|
||||
return;
|
||||
}
|
||||
this._handleHide();
|
||||
e.stopPropagation();
|
||||
@ -376,33 +554,12 @@ export default class Profile extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
_handleRedirectToInternal = () => {
|
||||
this.setState({ visible: true });
|
||||
};
|
||||
|
||||
_handleSwitchTab = (tab) => {
|
||||
if (typeof window !== "undefined") {
|
||||
this.setState({ tab });
|
||||
window.history.pushState({ ...window.history.state, tab }, "", window.location.pathname);
|
||||
_handleLoginModal = (e) => {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
if (tab === 2 && !this.state.fetched) {
|
||||
this.fetchSocial();
|
||||
}
|
||||
};
|
||||
|
||||
_handleUpdatePage = () => {
|
||||
let tab;
|
||||
if (typeof window !== "undefined") {
|
||||
tab = window?.history?.state.tab;
|
||||
}
|
||||
if (typeof tab === "undefined") {
|
||||
tab = 0;
|
||||
}
|
||||
this.setState({ tab }, () => {
|
||||
if (this.state.tab === 2 || (this.state.tab === 1 && this.state.slateTab === 1)) {
|
||||
this.fetchSocial();
|
||||
}
|
||||
});
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
};
|
||||
|
||||
checkStatus = ({ id }) => {
|
||||
@ -411,81 +568,15 @@ export default class Profile extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
let tab = this.state.tab || 0;
|
||||
let publicFiles = this.props.user.library;
|
||||
let subtab = this.props.page.params?.subtab
|
||||
? this.props.page.params?.subtab
|
||||
: this.props.page.params?.cid
|
||||
? "files"
|
||||
: "collections";
|
||||
let tab = this.props.page.params?.tab;
|
||||
let library = this.props.user.library;
|
||||
let isOwner = this.props.isOwner;
|
||||
let user = this.props.user;
|
||||
let username = this.state.slateTab === 0 ? user.username : null;
|
||||
let slates = [];
|
||||
if (tab === 1) {
|
||||
if (this.state.slateTab === 0) {
|
||||
slates = user.slates
|
||||
? isOwner
|
||||
? user.slates.filter((slate) => slate.isPublic === true)
|
||||
: user.slates
|
||||
: null;
|
||||
} else {
|
||||
slates = this.state.subscriptions;
|
||||
}
|
||||
}
|
||||
let exploreSlates = this.props.exploreSlates;
|
||||
let peers = this.state.peerTab === 0 ? this.state.following : this.state.followers;
|
||||
if (tab === 2) {
|
||||
peers = peers.map((relation) => {
|
||||
let button = (
|
||||
<div css={STYLES_ITEM_BOX} onClick={(e) => this._handleClick(e, relation.id)}>
|
||||
<SVG.MoreHorizontal height="24px" />
|
||||
{this.state.contextMenu === relation.id ? (
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled
|
||||
onOutsideRectEvent={(e) => this._handleClick(e, relation.id)}
|
||||
>
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
top: "40px",
|
||||
right: "0px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: this.props.viewer?.following.some((subscription) => {
|
||||
return subscription.id === relation.id;
|
||||
}).length
|
||||
? "Unfollow"
|
||||
: "Follow",
|
||||
onClick: this.props.viewer
|
||||
? (e) => this._handleFollow(e, relation.id)
|
||||
: () => this.setState({ visible: true }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Boundary>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<UserEntry
|
||||
key={relation.id}
|
||||
user={relation}
|
||||
button={button}
|
||||
checkStatus={this.checkStatus}
|
||||
showStatusIndicator={this.props.isAuthenticated}
|
||||
onClick={() => {
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: this.props.sceneId,
|
||||
scene: "PROFILE",
|
||||
data: relation,
|
||||
});
|
||||
}}
|
||||
external={this.props.external}
|
||||
url={`/${relation.username}`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const showStatusIndicator = this.props.isAuthenticated;
|
||||
|
||||
@ -493,14 +584,14 @@ export default class Profile extends React.Component {
|
||||
<div>
|
||||
<GlobalCarousel
|
||||
carouselType="PROFILE"
|
||||
onUpdateViewer={this.props.onUpdateViewer}
|
||||
resources={this.props.resources}
|
||||
viewer={this.props.viewer}
|
||||
objects={publicFiles}
|
||||
objects={library}
|
||||
isOwner={this.props.isOwner}
|
||||
onAction={this.props.onAction}
|
||||
isMobile={this.props.isMobile}
|
||||
external={this.props.external}
|
||||
params={this.props.page.params}
|
||||
/>
|
||||
<div css={STYLES_PROFILE_BACKGROUND}>
|
||||
<div css={STYLES_PROFILE_INFO}>
|
||||
@ -547,7 +638,7 @@ export default class Profile extends React.Component {
|
||||
<div css={STYLES_STATS}>
|
||||
<div css={STYLES_STAT}>
|
||||
<div style={{ fontFamily: `${Constants.font.text}` }}>
|
||||
{publicFiles.length}{" "}
|
||||
{library.length}{" "}
|
||||
<span style={{ color: `${Constants.system.darkGray}` }}>Files</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -561,162 +652,37 @@ export default class Profile extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.visible && (
|
||||
<div>
|
||||
<CTATransition
|
||||
onClose={() => this.setState({ visible: false })}
|
||||
viewer={this.props.viewer}
|
||||
open={this.state.visible}
|
||||
redirectURL={`/_${Strings.createQueryParams({
|
||||
scene: "NAV_PROFILE",
|
||||
user: user.username,
|
||||
})}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div css={STYLES_PROFILE}>
|
||||
<TabGroup
|
||||
tabs={["Files", "Collections", "Peers"]}
|
||||
value={tab}
|
||||
onChange={this._handleSwitchTab}
|
||||
tabs={[
|
||||
{ title: "Files", value: { subtab: "files" } },
|
||||
{ title: "Collections", value: { subtab: "collections" } },
|
||||
{ title: "Peers", value: { subtab: "peers" } },
|
||||
]}
|
||||
value={subtab}
|
||||
onAction={this.props.onAction}
|
||||
style={{ marginTop: 0, marginBottom: 32 }}
|
||||
itemStyle={{ margin: "0px 16px" }}
|
||||
/>
|
||||
{tab === 0 ? (
|
||||
<div>
|
||||
{this.props.isMobile ? null : (
|
||||
<div style={{ display: `flex` }}>
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
<SVG.GridView height="24px" style={{ display: "block" }} />,
|
||||
<SVG.TableView height="24px" style={{ display: "block" }} />,
|
||||
]}
|
||||
value={this.state.view}
|
||||
onChange={(value) => this.setState({ view: value })}
|
||||
style={{ margin: "0 0 24px 0", justifyContent: "flex-end" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{publicFiles.length ? (
|
||||
<DataView
|
||||
key="scene-profile"
|
||||
onAction={this.props.onAction}
|
||||
viewer={this.props.viewer}
|
||||
isOwner={isOwner}
|
||||
items={publicFiles}
|
||||
onUpdateViewer={this.props.onUpdateViewer}
|
||||
view={this.state.view}
|
||||
resources={this.props.resources}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState>
|
||||
<FileTypeGroup />
|
||||
<div style={{ marginTop: 24 }}>This user does not have any public files yet</div>
|
||||
</EmptyState>
|
||||
)}
|
||||
</div>
|
||||
{subtab === "files" ? <FilesPage {...this.props} library={library} tab={tab} /> : null}
|
||||
{subtab === "collections" ? (
|
||||
<CollectionsPage
|
||||
{...this.props}
|
||||
tab={tab}
|
||||
fetched={this.state.fetched}
|
||||
subscriptions={this.state.subscriptions}
|
||||
/>
|
||||
) : null}
|
||||
{tab === 1 ? (
|
||||
<div>
|
||||
<SecondaryTabGroup
|
||||
tabs={["Collections", "Following"]}
|
||||
value={this.state.slateTab}
|
||||
onChange={(value) => {
|
||||
this.setState({ slateTab: value }, () => {
|
||||
if (!this.state.fetched) {
|
||||
this.fetchSocial();
|
||||
}
|
||||
});
|
||||
}}
|
||||
style={{ margin: "0 0 24px 0" }}
|
||||
/>
|
||||
{slates?.length ? (
|
||||
<SlatePreviewBlocks
|
||||
isOwner={this.state.slateTab === 0 ? isOwner : false}
|
||||
external={this.props.external}
|
||||
slates={slates || []}
|
||||
username={username}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{this.props.external && exploreSlates.length != 0 ? (
|
||||
<React.Fragment>
|
||||
<EmptyState style={{ border: `none`, height: `120px` }}>
|
||||
{this.state.fetched || this.state.slateTab == 0 ? (
|
||||
<React.Fragment>
|
||||
<SVG.Slate height="24px" style={{ marginBottom: 24 }} />
|
||||
{this.state.slateTab === 0
|
||||
? `This user does not have any public collections yet`
|
||||
: `This user is not following any collections yet`}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<LoaderSpinner style={{ height: 24, width: 24 }} />
|
||||
)}
|
||||
</EmptyState>
|
||||
<div css={STYLES_EXPLORE}>Explore Collections</div>
|
||||
<SlatePreviewBlocks
|
||||
isOwner={false}
|
||||
external={this.props.external}
|
||||
slates={exploreSlates}
|
||||
username={exploreSlates.username}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<EmptyState>
|
||||
{this.state.fetched || this.state.slateTab == 0 ? (
|
||||
<React.Fragment>
|
||||
<SVG.Slate height="24px" style={{ marginBottom: 24 }} />
|
||||
{this.state.slateTab === 0
|
||||
? `This user does not have any public collections yet`
|
||||
: `This user is not following any collections yet`}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<LoaderSpinner style={{ height: 24, width: 24 }} />
|
||||
)}
|
||||
</EmptyState>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
{tab === 2 ? (
|
||||
<div>
|
||||
<SecondaryTabGroup
|
||||
tabs={["Following", "Followers"]}
|
||||
value={this.state.peerTab}
|
||||
onChange={(value) => this.setState({ peerTab: value })}
|
||||
style={{ margin: "0 0 24px 0" }}
|
||||
/>
|
||||
<div>
|
||||
{peers?.length ? (
|
||||
peers
|
||||
) : (
|
||||
<EmptyState>
|
||||
{this.state.fetched || this.state.slateTab == 0 ? (
|
||||
<React.Fragment>
|
||||
<SVG.Users height="24px" style={{ marginBottom: 24 }} />
|
||||
{this.state.peerTab === 0
|
||||
? `This user is not following anyone yet`
|
||||
: `This user does not have any followers yet`}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<LoaderSpinner style={{ height: 24, width: 24 }} />
|
||||
)}
|
||||
</EmptyState>
|
||||
)}
|
||||
</div>
|
||||
{/* <input
|
||||
readOnly
|
||||
ref={(c) => {
|
||||
this._ref = c;
|
||||
}}
|
||||
value={this.state.copyValue}
|
||||
tabIndex="-1"
|
||||
css={STYLES_COPY_INPUT}
|
||||
/> */}
|
||||
</div>
|
||||
{subtab === "peers" ? (
|
||||
<PeersPage
|
||||
{...this.props}
|
||||
tab={tab}
|
||||
onLoginModal={this._handleLoginModal}
|
||||
checkStatus={this.checkStatus}
|
||||
following={this.state.following}
|
||||
followers={this.state.followers}
|
||||
fetched={this.state.fetched}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,6 +9,7 @@ import * as Validations from "~/common/validations";
|
||||
import MiniSearch from "minisearch";
|
||||
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
|
||||
|
||||
import { Link } from "~/components/core/Link";
|
||||
import { css } from "@emotion/react";
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
import { Boundary } from "~/components/system/components/fragments/Boundary";
|
||||
@ -437,6 +438,21 @@ const STYLES_DISMISS_BOX = css`
|
||||
outline: 0;
|
||||
`;
|
||||
|
||||
const getHref = (result) => {
|
||||
if (result.type === "SLATE") {
|
||||
return `/$/slate/${result.data.slate.id}`;
|
||||
} else if (result.type === "USER") {
|
||||
return `/$/user/${result.data.user.id}`;
|
||||
} else if (result.type === "FILE") {
|
||||
return `/$/user/${result.data.user.id}?cid=${result.data.file.cid}`;
|
||||
} else if (result.type === "DATA_FILE") {
|
||||
return `/_/data?cid=${result.data.file.cid}`;
|
||||
} else {
|
||||
console.log("GET HREF FAILED B/C RESULT WAS:");
|
||||
console.log(result);
|
||||
}
|
||||
};
|
||||
|
||||
export class SearchModal extends React.Component {
|
||||
_input;
|
||||
_optionRoot;
|
||||
@ -484,24 +500,27 @@ export class SearchModal extends React.Component {
|
||||
this.debounceInstance = Window.debounce(() => {
|
||||
this._handleSearch();
|
||||
}, 500);
|
||||
let defaultResults = this.props.viewer.slates;
|
||||
let defaultResults = this.props.viewer ? this.props.viewer.slates : [];
|
||||
defaultResults = defaultResults.map((slate) => {
|
||||
return {
|
||||
id: slate.id,
|
||||
type: "SLATE",
|
||||
data: { slate: slate },
|
||||
data: { slate },
|
||||
component: <SlateEntry slate={slate} user={this.props.viewer} />,
|
||||
preview: <SlatePreview slate={slate} user={this.props.viewer} />,
|
||||
href: `/$/slate/${slate.id}`,
|
||||
};
|
||||
});
|
||||
this.setState({ defaultResults });
|
||||
let networkIds = [];
|
||||
let slateIds = [];
|
||||
for (let sub of this.props.viewer.subscriptions) {
|
||||
if (sub.target_user_id) {
|
||||
networkIds.push(sub.target_user_id);
|
||||
} else if (sub.target_slate_id) {
|
||||
slateIds.push(sub.target_slate_id);
|
||||
if (this.props.viewer?.subscriptions) {
|
||||
for (let sub of this.props.viewer.subscriptions) {
|
||||
if (sub.target_user_id) {
|
||||
networkIds.push(sub.target_user_id);
|
||||
} else if (sub.target_slate_id) {
|
||||
slateIds.push(sub.target_slate_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.networkIds = networkIds;
|
||||
@ -509,6 +528,9 @@ export class SearchModal extends React.Component {
|
||||
};
|
||||
|
||||
fillLocalDirectory = () => {
|
||||
if (!this.props.viewer) {
|
||||
return;
|
||||
}
|
||||
this.localSearch = new MiniSearch({
|
||||
fields: ["name", "title"],
|
||||
storeFields: ["type", "data", "id"],
|
||||
@ -614,7 +636,9 @@ export class SearchModal extends React.Component {
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 13) {
|
||||
if (results.length > this.state.selectedIndex && this.state.selectedIndex >= 0) {
|
||||
this._handleSelect(results[this.state.selectedIndex]);
|
||||
let href = results[this.state.selectedIndex].href;
|
||||
console.log("key down navigate");
|
||||
this.props.onAction({ type: "NAVIGATE", href });
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
@ -629,7 +653,7 @@ export class SearchModal extends React.Component {
|
||||
let searchResults = [];
|
||||
let results = [];
|
||||
let ids = new Set();
|
||||
if (this.state.typeFilter !== "USER") {
|
||||
if (this.state.typeFilter !== "USER" && this.props.viewer) {
|
||||
let filter;
|
||||
if (this.state.typeFilter === "FILE") {
|
||||
filter = {
|
||||
@ -686,7 +710,7 @@ export class SearchModal extends React.Component {
|
||||
file={item.data.file}
|
||||
slate={item.data.slate}
|
||||
user={this.props.viewer}
|
||||
viewerId={this.props.viewer.id}
|
||||
viewerId={this.props.viewer?.id}
|
||||
/>
|
||||
),
|
||||
});
|
||||
@ -715,6 +739,7 @@ export class SearchModal extends React.Component {
|
||||
results.push({
|
||||
id,
|
||||
type: res.type,
|
||||
href: res.href,
|
||||
data: res,
|
||||
component: <UserEntry user={res.user} />,
|
||||
preview: <UserPreview user={res.user} />,
|
||||
@ -726,6 +751,7 @@ export class SearchModal extends React.Component {
|
||||
results.push({
|
||||
id,
|
||||
type: res.type,
|
||||
href: res.href,
|
||||
data: res,
|
||||
component: <SlateEntry slate={res.slate} user={res.user} />,
|
||||
preview: <SlatePreview slate={res.slate} user={res.user} />,
|
||||
@ -737,6 +763,7 @@ export class SearchModal extends React.Component {
|
||||
results.push({
|
||||
id,
|
||||
type: res.type,
|
||||
href: res.href,
|
||||
data: res,
|
||||
component: <FileEntry file={res.file} />,
|
||||
preview: (
|
||||
@ -744,12 +771,16 @@ export class SearchModal extends React.Component {
|
||||
file={res.file}
|
||||
slate={res.slate}
|
||||
user={res.user}
|
||||
viewerId={this.props.viewer.id}
|
||||
viewerId={this.props.viewer?.id}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
results = results.map((res) => {
|
||||
return { ...res, href: getHref(res) };
|
||||
});
|
||||
console.log(results);
|
||||
this.setState({ results, selectedIndex: 0 });
|
||||
if (this._optionRoot) {
|
||||
this._optionRoot.scrollTop = 0;
|
||||
@ -757,6 +788,9 @@ export class SearchModal extends React.Component {
|
||||
};
|
||||
|
||||
processResults = (searchResults) => {
|
||||
if (!this.state.viewer) {
|
||||
return searchResults;
|
||||
}
|
||||
let results = searchResults;
|
||||
if (this.state.scopeFilter === "MY") {
|
||||
results = results.filter((res) => {
|
||||
@ -809,65 +843,65 @@ export class SearchModal extends React.Component {
|
||||
return results;
|
||||
};
|
||||
|
||||
_handleSelect = async (res) => {
|
||||
if (res.type === "SLATE") {
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_SLATE",
|
||||
data: res.data.slate,
|
||||
});
|
||||
} else if (res.type === "USER") {
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_PROFILE",
|
||||
data: res.data.user,
|
||||
});
|
||||
} else if (res.type === "DATA_FILE" || res.data.file.ownerId === this.props.viewer.id) {
|
||||
await this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_DATA",
|
||||
fileId: res.data.file.id,
|
||||
});
|
||||
} else if (res.type === "FILE") {
|
||||
await this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_PROFILE",
|
||||
data: res.data.user,
|
||||
fileId: res.data.file.id,
|
||||
});
|
||||
}
|
||||
this._handleHide();
|
||||
};
|
||||
// _handleSelect = async (res) => {
|
||||
// if (res.type === "SLATE") {
|
||||
// this.props.onAction({
|
||||
// type: "NAVIGATE",
|
||||
// value: "NAV_SLATE",
|
||||
// data: res.data.slate,
|
||||
// });
|
||||
// } else if (res.type === "USER") {
|
||||
// this.props.onAction({
|
||||
// type: "NAVIGATE",
|
||||
// value: "NAV_PROFILE",
|
||||
// data: res.data.user,
|
||||
// });
|
||||
// } else if (res.type === "DATA_FILE" || res.data.file.ownerId === this.props.viewer?.id) {
|
||||
// await this.props.onAction({
|
||||
// type: "NAVIGATE",
|
||||
// value: "NAV_DATA",
|
||||
// fileId: res.data.file.id,
|
||||
// });
|
||||
// } else if (res.type === "FILE") {
|
||||
// await this.props.onAction({
|
||||
// type: "NAVIGATE",
|
||||
// value: "NAV_PROFILE",
|
||||
// data: res.data.user,
|
||||
// fileId: res.data.file.id,
|
||||
// });
|
||||
// }
|
||||
// this._handleHide();
|
||||
// };
|
||||
|
||||
_handleRedirect = async (destination) => {
|
||||
if (destination === "FMU") {
|
||||
let isProd = window.location.hostname.includes("slate.host");
|
||||
this._handleSelect({
|
||||
type: "FILE",
|
||||
data: {
|
||||
file: { id: "rick-roll" },
|
||||
slate: {
|
||||
id: isProd
|
||||
? "01edcede-53c9-46b3-ac63-8f8479e10bcf"
|
||||
: "60d199e7-6bf5-4994-94e8-b17547c64449",
|
||||
data: {
|
||||
objects: [
|
||||
{
|
||||
id: "rick-roll",
|
||||
url:
|
||||
"https://slate.textile.io/ipfs/bafybeifcxjvbad4lgpnbwff2dafufmnlylylmku4qoqtlkwgidupwi6f3a",
|
||||
ownerId: "owner",
|
||||
name: "Never gonna give you up",
|
||||
title: "never-gonna-give-you-up.mp4",
|
||||
type: "video/mp4",
|
||||
},
|
||||
],
|
||||
},
|
||||
ownerId: "owner",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
// if (destination === "FMU") {
|
||||
// let isProd = window.location.hostname.includes("slate.host");
|
||||
// this._handleSelect({
|
||||
// type: "FILE",
|
||||
// data: {
|
||||
// file: { id: "rick-roll" },
|
||||
// slate: {
|
||||
// id: isProd
|
||||
// ? "01edcede-53c9-46b3-ac63-8f8479e10bcf"
|
||||
// : "60d199e7-6bf5-4994-94e8-b17547c64449",
|
||||
// data: {
|
||||
// objects: [
|
||||
// {
|
||||
// id: "rick-roll",
|
||||
// url:
|
||||
// "https://slate.textile.io/ipfs/bafybeifcxjvbad4lgpnbwff2dafufmnlylylmku4qoqtlkwgidupwi6f3a",
|
||||
// ownerId: "owner",
|
||||
// name: "Never gonna give you up",
|
||||
// title: "never-gonna-give-you-up.mp4",
|
||||
// type: "video/mp4",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ownerId: "owner",
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
this.props.onAction({
|
||||
type: "SIDEBAR",
|
||||
value: destination,
|
||||
@ -913,12 +947,101 @@ export class SearchModal extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
_handleSelectIndex = (i) => {
|
||||
if (this.state.selectedIndex === i || this.props.isMobile) {
|
||||
console.log("handle hide");
|
||||
this._handleHide();
|
||||
} else {
|
||||
console.log("set state");
|
||||
// this.setState({ selectedIndex: i });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let selectedIndex = this.state.selectedIndex;
|
||||
let results =
|
||||
this.state.inputValue && this.state.inputValue.length
|
||||
? this.state.results
|
||||
: this.state.defaultResults;
|
||||
|
||||
const filterDropdown = this.props.viewer ? (
|
||||
<div style={{ flexShrink: 0, position: "relative" }}>
|
||||
<div
|
||||
css={STYLES_FILTER_BUTTON}
|
||||
style={{
|
||||
marginRight: 0,
|
||||
marginLeft: 16,
|
||||
color: this.state.scopeFilter ? Constants.system.brand : Constants.system.textGray,
|
||||
}}
|
||||
onClick={() => this.setState({ filterTooltip: !this.state.filterTooltip })}
|
||||
>
|
||||
<SVG.Filter height="16px" />
|
||||
</div>
|
||||
{this.state.filterTooltip ? (
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled
|
||||
onOutsideRectEvent={() => this.setState({ filterTooltip: false })}
|
||||
>
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
right: 0,
|
||||
top: 44,
|
||||
borderColor: Constants.system.bgGray,
|
||||
color: Constants.system.textGray,
|
||||
width: 124,
|
||||
}}
|
||||
navigation={[
|
||||
[
|
||||
{
|
||||
text: (
|
||||
<span
|
||||
style={{
|
||||
color: this.state.scopeFilter ? "inherit" : Constants.system.brand,
|
||||
}}
|
||||
>
|
||||
All
|
||||
</span>
|
||||
),
|
||||
onClick: () => this._handleFilterScope(null),
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<span
|
||||
style={{
|
||||
color:
|
||||
this.state.scopeFilter === "MY" ? Constants.system.brand : "inherit",
|
||||
}}
|
||||
>
|
||||
My stuff
|
||||
</span>
|
||||
),
|
||||
onClick: () => this._handleFilterScope("MY"),
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<span
|
||||
style={{
|
||||
color:
|
||||
this.state.scopeFilter === "NETWORK"
|
||||
? Constants.system.brand
|
||||
: "inherit",
|
||||
}}
|
||||
>
|
||||
My network
|
||||
</span>
|
||||
),
|
||||
onClick: () => this._handleFilterScope("NETWORK"),
|
||||
},
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</Boundary>
|
||||
) : null}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div
|
||||
css={STYLES_BACKGROUND}
|
||||
@ -1047,87 +1170,7 @@ export class SearchModal extends React.Component {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flexShrink: 0, position: "relative" }}>
|
||||
<div
|
||||
css={STYLES_FILTER_BUTTON}
|
||||
style={{
|
||||
marginRight: 0,
|
||||
marginLeft: 16,
|
||||
color: this.state.scopeFilter
|
||||
? Constants.system.brand
|
||||
: Constants.system.textGray,
|
||||
}}
|
||||
onClick={() =>
|
||||
this.setState({ filterTooltip: !this.state.filterTooltip })
|
||||
}
|
||||
>
|
||||
<SVG.Filter height="16px" />
|
||||
</div>
|
||||
{this.state.filterTooltip ? (
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled
|
||||
onOutsideRectEvent={() => this.setState({ filterTooltip: false })}
|
||||
>
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
right: 0,
|
||||
top: 44,
|
||||
borderColor: Constants.system.bgGray,
|
||||
color: Constants.system.textGray,
|
||||
width: 124,
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: (
|
||||
<span
|
||||
style={{
|
||||
color: this.state.scopeFilter
|
||||
? "inherit"
|
||||
: Constants.system.brand,
|
||||
}}
|
||||
>
|
||||
All
|
||||
</span>
|
||||
),
|
||||
onClick: () => this._handleFilterScope(null),
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<span
|
||||
style={{
|
||||
color:
|
||||
this.state.scopeFilter === "MY"
|
||||
? Constants.system.brand
|
||||
: "inherit",
|
||||
}}
|
||||
>
|
||||
My stuff
|
||||
</span>
|
||||
),
|
||||
onClick: () => this._handleFilterScope("MY"),
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<span
|
||||
style={{
|
||||
color:
|
||||
this.state.scopeFilter === "NETWORK"
|
||||
? Constants.system.brand
|
||||
: "inherit",
|
||||
}}
|
||||
>
|
||||
My network
|
||||
</span>
|
||||
),
|
||||
onClick: () => this._handleFilterScope("NETWORK"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Boundary>
|
||||
) : null}
|
||||
</div>
|
||||
{filterDropdown}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@ -1138,45 +1181,57 @@ export class SearchModal extends React.Component {
|
||||
css={STYLES_DROPDOWN}
|
||||
>
|
||||
{results.map((each, i) => (
|
||||
<div
|
||||
key={each.id}
|
||||
css={STYLES_DROPDOWN_ITEM}
|
||||
style={{
|
||||
background:
|
||||
selectedIndex === i
|
||||
? "rgba(196, 196, 196, 0.1)"
|
||||
: Constants.system.white,
|
||||
paddingRight: selectedIndex === i ? "88px" : "4px",
|
||||
}}
|
||||
onClick={() => {
|
||||
selectedIndex === i || this.props.isMobile
|
||||
? this._handleSelect(each)
|
||||
: this.setState({ selectedIndex: i });
|
||||
}}
|
||||
<Link
|
||||
disabled={this.props.isMobile ? false : selectedIndex !== i}
|
||||
href={each.href}
|
||||
onAction={this.props.onAction}
|
||||
onClick={() => this._handleSelectIndex(i)}
|
||||
>
|
||||
{each.component}
|
||||
{selectedIndex === i ? (
|
||||
<div css={STYLES_RETURN}>
|
||||
<SVG.ArrowDownLeft height="16px" style={{ marginRight: 8 }} /> Return
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
key={each.id}
|
||||
css={STYLES_DROPDOWN_ITEM}
|
||||
style={{
|
||||
background:
|
||||
selectedIndex === i
|
||||
? "rgba(196, 196, 196, 0.1)"
|
||||
: Constants.system.white,
|
||||
paddingRight: selectedIndex === i ? "88px" : "4px",
|
||||
}}
|
||||
// onClick={() => {
|
||||
// selectedIndex === i || this.props.isMobile
|
||||
// ? this._handleSelect(each)
|
||||
// : this.setState({ selectedIndex: i });
|
||||
// }}
|
||||
onClick={() => this.setState({ selectedIndex: i })}
|
||||
>
|
||||
{each.component}
|
||||
{selectedIndex === i ? (
|
||||
<div css={STYLES_RETURN}>
|
||||
<SVG.ArrowDownLeft height="16px" style={{ marginRight: 8 }} />{" "}
|
||||
Return
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
{results &&
|
||||
results.length &&
|
||||
selectedIndex < results.length &&
|
||||
selectedIndex >= 0 ? (
|
||||
<div
|
||||
css={STYLES_PREVIEW_PANEL}
|
||||
onClick={() => {
|
||||
if (selectedIndex >= 0 && selectedIndex < results.length) {
|
||||
this._handleSelect(results[selectedIndex]);
|
||||
}
|
||||
}}
|
||||
{results?.length && selectedIndex < results.length && selectedIndex >= 0 ? (
|
||||
<Link
|
||||
href={results[selectedIndex].href}
|
||||
onAction={this.props.onAction}
|
||||
onClick={this._handleHide}
|
||||
>
|
||||
{results[selectedIndex].preview}
|
||||
</div>
|
||||
<div
|
||||
css={STYLES_PREVIEW_PANEL}
|
||||
// onClick={() => {
|
||||
// if (selectedIndex >= 0 && selectedIndex < results.length) {
|
||||
// this._handleSelect(results[selectedIndex]);
|
||||
// }
|
||||
// }}
|
||||
>
|
||||
{results[selectedIndex].preview}
|
||||
</div>
|
||||
</Link>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
)}
|
||||
@ -1195,9 +1250,9 @@ export class SearchModal extends React.Component {
|
||||
>
|
||||
FAQ
|
||||
</span>
|
||||
<span style={{ cursor: "pointer" }} onClick={() => this._handleRedirect("FMU")}>
|
||||
{/* <span style={{ cursor: "pointer" }} onClick={() => this._handleRedirect("FMU")}>
|
||||
I'm Feeling Lucky
|
||||
</span>
|
||||
</span> */}
|
||||
</div>
|
||||
</div>
|
||||
</Boundary>
|
||||
|
@ -146,6 +146,9 @@ export class SignIn extends React.Component {
|
||||
password: this.state.password,
|
||||
});
|
||||
}
|
||||
if (!Events.hasError(response)) {
|
||||
window.location.replace("/_/activity");
|
||||
}
|
||||
this.setState({ loading: false });
|
||||
};
|
||||
|
||||
@ -190,9 +193,7 @@ export class SignIn extends React.Component {
|
||||
<Logo height="36px" style={{ display: "block", margin: "56px auto 0px auto" }} />
|
||||
|
||||
<System.P style={{ margin: "56px 0", textAlign: "center" }}>
|
||||
{this.props.external
|
||||
? "Sign up or sign in to continue"
|
||||
: "An open-source file sharing network for research and collaboration"}
|
||||
An open-source file sharing network for research and collaboration
|
||||
</System.P>
|
||||
|
||||
<System.ButtonPrimary
|
||||
|
@ -10,12 +10,12 @@ import * as Events from "~/common/custom-events";
|
||||
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
|
||||
import CTATransition from "~/components/core/CTATransition";
|
||||
|
||||
import { Link } from "~/components/core/Link";
|
||||
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
|
||||
import { CheckBox } from "~/components/system/components/CheckBox";
|
||||
import { css } from "@emotion/react";
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
import { Toggle } from "~/components/system/components/Toggle";
|
||||
import { DynamicIcon } from "~/components/core/DynamicIcon";
|
||||
import { Tooltip } from "~/components/core/Tooltip";
|
||||
import {
|
||||
ButtonPrimary,
|
||||
@ -34,8 +34,6 @@ const MARGIN = 20;
|
||||
const CONTAINER_SIZE = 5 * SIZE + 4 * MARGIN;
|
||||
const TAG_HEIGHT = 20;
|
||||
|
||||
const SIZE_LIMIT = 1000000; //NOTE(martina): 1mb limit for twitter preview images
|
||||
|
||||
const generateLayout = (items) => {
|
||||
if (!items) {
|
||||
return [];
|
||||
@ -328,7 +326,6 @@ export class SlateLayout extends React.Component {
|
||||
copyValue: "",
|
||||
tooltip: null,
|
||||
keyboardTooltip: false,
|
||||
signInModal: false,
|
||||
modalShowDeleteFiles: false,
|
||||
};
|
||||
|
||||
@ -964,6 +961,10 @@ export class SlateLayout extends React.Component {
|
||||
};
|
||||
|
||||
_handleDownload = (e, i) => {
|
||||
if (!this.props.viewer) {
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (i !== undefined) {
|
||||
@ -972,6 +973,10 @@ export class SlateLayout extends React.Component {
|
||||
};
|
||||
|
||||
_handleSaveCopy = async (e, i) => {
|
||||
if (!this.props.viewer) {
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
let items = [];
|
||||
@ -987,6 +992,10 @@ export class SlateLayout extends React.Component {
|
||||
};
|
||||
|
||||
_handleAddToSlate = (e, i) => {
|
||||
if (!this.props.viewer) {
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
let items = [];
|
||||
@ -1019,16 +1028,16 @@ export class SlateLayout extends React.Component {
|
||||
}
|
||||
|
||||
let slates = this.props.viewer.slates;
|
||||
let slateId = this.props.current.id;
|
||||
let slateId = this.props.data.id;
|
||||
for (let slate of slates) {
|
||||
if (slate.id === slateId) {
|
||||
slate.objects = slate.objects.filter((obj) => !ids.includes(obj.id.replace("data-", "")));
|
||||
this.props.onUpdateViewer({ slates });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { slates } });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UserBehaviors.removeFromSlate({ slate: this.props.current, ids });
|
||||
UserBehaviors.removeFromSlate({ slate: this.props.data, ids });
|
||||
};
|
||||
|
||||
_stopPropagation = (e) => e.stopPropagation();
|
||||
@ -1052,8 +1061,8 @@ export class SlateLayout extends React.Component {
|
||||
};
|
||||
|
||||
_handleDeleteModal = () => {
|
||||
this.setState({ modalShowDeleteFiles: true })
|
||||
}
|
||||
this.setState({ modalShowDeleteFiles: true });
|
||||
};
|
||||
|
||||
_handleDeleteFiles = async (res, i) => {
|
||||
if (!res) {
|
||||
@ -1069,13 +1078,13 @@ export class SlateLayout extends React.Component {
|
||||
}
|
||||
}
|
||||
let slates = this.props.viewer.slates;
|
||||
let slateId = this.props.current.id;
|
||||
let slateId = this.props.data.id;
|
||||
for (let slate of slates) {
|
||||
if (slate.id === slateId) {
|
||||
slate.objects = slate.objects.filter(
|
||||
(obj) => !ids.includes(obj.id.replace("data-", "")) && !cids.includes(obj.cid)
|
||||
);
|
||||
this.props.onUpdateViewer({ slates });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { slates } });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1092,7 +1101,7 @@ export class SlateLayout extends React.Component {
|
||||
_handleLoginModal = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({ signInModal: true });
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -1137,15 +1146,20 @@ export class SlateLayout extends React.Component {
|
||||
Reset layout
|
||||
</ButtonDisabled>
|
||||
) : (
|
||||
<ButtonSecondary onClick={() => { this.setState({ modalShowResetLayout: true }) }} style={{ marginRight: 16 }}>
|
||||
<ButtonSecondary
|
||||
onClick={() => {
|
||||
this.setState({ modalShowResetLayout: true });
|
||||
}}
|
||||
style={{ marginRight: 16 }}
|
||||
>
|
||||
Reset layout
|
||||
</ButtonSecondary>
|
||||
)}
|
||||
{this.state.modalShowResetLayout && (
|
||||
<ConfirmationModal
|
||||
<ConfirmationModal
|
||||
type={"CONFIRM"}
|
||||
withValidation={false}
|
||||
callback={this._handleResetLayout}
|
||||
callback={this._handleResetLayout}
|
||||
header={`Are you sure you want to reset your layout to the default column layout?`}
|
||||
subHeader={`You can’t undo this action.`}
|
||||
/>
|
||||
@ -1282,50 +1296,56 @@ export class SlateLayout extends React.Component {
|
||||
>
|
||||
{this.state.show ? (
|
||||
this.state.layout.map((pos, i) => (
|
||||
<Selectable
|
||||
css={this.state.editing ? STYLES_ITEM_EDITING : STYLES_ITEM}
|
||||
<Link
|
||||
key={i}
|
||||
name={i}
|
||||
draggable={!(numChecked || this.state.editing)}
|
||||
onDragStart={(e) => {
|
||||
this._disableDragAndDropUploadEvent();
|
||||
this._handleDragToDesktop(e, this.state.items[i]);
|
||||
}}
|
||||
onDragEnd={this._enableDragAndDropUploadEvent}
|
||||
selectableKey={i}
|
||||
onMouseEnter={() => this.setState({ hover: i })}
|
||||
onMouseLeave={() => this.setState({ hover: null })}
|
||||
onMouseDown={this.state.editing ? (e) => this._handleMouseDown(e, i) : () => {}}
|
||||
onClick={this.state.editing ? () => {} : () => this.props.onSelect(i)}
|
||||
style={{
|
||||
top: pos.y * unit,
|
||||
left: pos.x * unit,
|
||||
width: pos.w * unit,
|
||||
height: this.state.fileNames ? (pos.h + TAG_HEIGHT) * unit : pos.h * unit,
|
||||
zIndex: pos.z,
|
||||
boxShadow:
|
||||
this.state.dragIndex === i ? `0 0 44px 0 rgba(0, 0, 0, 0.25)` : null,
|
||||
backgroundColor: Constants.system.white,
|
||||
}}
|
||||
redirect
|
||||
params={{ ...this.props.page?.params, cid: this.state.items[i].cid }}
|
||||
onAction={this.props.onAction}
|
||||
>
|
||||
<SlateMediaObjectPreview
|
||||
file={this.state.items[i]}
|
||||
iconOnly={this.state.fileNames}
|
||||
charCap={70}
|
||||
<Selectable
|
||||
css={this.state.editing ? STYLES_ITEM_EDITING : STYLES_ITEM}
|
||||
name={i}
|
||||
draggable={!(numChecked || this.state.editing)}
|
||||
onDragStart={(e) => {
|
||||
this._disableDragAndDropUploadEvent();
|
||||
this._handleDragToDesktop(e, this.state.items[i]);
|
||||
}}
|
||||
onDragEnd={this._enableDragAndDropUploadEvent}
|
||||
selectableKey={i}
|
||||
onMouseEnter={() => this.setState({ hover: i })}
|
||||
onMouseLeave={() => this.setState({ hover: null })}
|
||||
onMouseDown={
|
||||
this.state.editing ? (e) => this._handleMouseDown(e, i) : () => {}
|
||||
}
|
||||
// onClick={this.state.editing ? () => {} : () => this.props.onSelect(i)}
|
||||
style={{
|
||||
height: pos.h * unit,
|
||||
top: pos.y * unit,
|
||||
left: pos.x * unit,
|
||||
width: pos.w * unit,
|
||||
background: Constants.system.white,
|
||||
height: this.state.fileNames ? (pos.h + TAG_HEIGHT) * unit : pos.h * unit,
|
||||
zIndex: pos.z,
|
||||
boxShadow:
|
||||
this.state.dragIndex === i ? `0 0 44px 0 rgba(0, 0, 0, 0.25)` : null,
|
||||
backgroundColor: Constants.system.white,
|
||||
}}
|
||||
imageStyle={{
|
||||
width: pos.w * unit,
|
||||
height: pos.h * unit,
|
||||
maxHeight: "none",
|
||||
}}
|
||||
/>
|
||||
{numChecked || this.state.hover === i ? (
|
||||
<div css={STYLES_MOBILE_HIDDEN}>
|
||||
{this.props.external ? null : (
|
||||
>
|
||||
<SlateMediaObjectPreview
|
||||
file={this.state.items[i]}
|
||||
iconOnly={this.state.fileNames}
|
||||
charCap={70}
|
||||
style={{
|
||||
height: pos.h * unit,
|
||||
width: pos.w * unit,
|
||||
background: Constants.system.white,
|
||||
}}
|
||||
imageStyle={{
|
||||
width: pos.w * unit,
|
||||
height: pos.h * unit,
|
||||
maxHeight: "none",
|
||||
}}
|
||||
/>
|
||||
{numChecked || this.state.hover === i ? (
|
||||
<div css={STYLES_MOBILE_HIDDEN}>
|
||||
<div
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
@ -1362,323 +1382,364 @@ export class SlateLayout extends React.Component {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{this.state.hover !== i ? null : this.state.editing ? (
|
||||
<React.Fragment>
|
||||
{this.state.tooltip && this.state.tooltip.startsWith(`${i}-`) ? (
|
||||
<Tooltip
|
||||
light
|
||||
style={
|
||||
this.state.tooltip === `${i}-remove`
|
||||
? {
|
||||
position: "absolute",
|
||||
top: 36,
|
||||
right: 8,
|
||||
}
|
||||
{this.state.hover !== i ? null : this.state.editing ? (
|
||||
<React.Fragment>
|
||||
{this.state.tooltip && this.state.tooltip.startsWith(`${i}-`) ? (
|
||||
<Tooltip
|
||||
light
|
||||
style={
|
||||
this.state.tooltip === `${i}-remove`
|
||||
? {
|
||||
position: "absolute",
|
||||
top: 36,
|
||||
right: 8,
|
||||
}
|
||||
: this.state.tooltip === `${i}-view`
|
||||
? {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% + 28px)",
|
||||
}
|
||||
: this.state.tooltip === `${i}-download`
|
||||
? {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% - 12px)",
|
||||
}
|
||||
: {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% - 52px)",
|
||||
color: Constants.system.red,
|
||||
}
|
||||
}
|
||||
>
|
||||
{this.state.tooltip === `${i}-remove`
|
||||
? "Remove from collection"
|
||||
: this.state.tooltip === `${i}-view`
|
||||
? {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% + 28px)",
|
||||
}
|
||||
? "View file"
|
||||
: this.state.tooltip === `${i}-download`
|
||||
? {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% - 12px)",
|
||||
}
|
||||
: {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% - 52px)",
|
||||
color: Constants.system.red,
|
||||
}
|
||||
}
|
||||
>
|
||||
{this.state.tooltip === `${i}-remove`
|
||||
? "Remove from collection"
|
||||
: this.state.tooltip === `${i}-view`
|
||||
? "View file"
|
||||
: this.state.tooltip === `${i}-download`
|
||||
? "Download"
|
||||
: "Delete file"}
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<div
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-remove` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={(e) => {
|
||||
this._handleRemoveFromSlate(e, i);
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
cursor: "pointer",
|
||||
margin: 0,
|
||||
}}
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
>
|
||||
<SVG.DismissCircle height="24px" />
|
||||
</div>
|
||||
<div
|
||||
css={STYLES_ICON_ROW}
|
||||
style={{
|
||||
bottom: this.state.fileNames
|
||||
? `calc(24px + ${TAG_HEIGHT}px)`
|
||||
: "24px",
|
||||
left: `calc(50% - 60px)`,
|
||||
}}
|
||||
>
|
||||
? "Download"
|
||||
: "Delete file"}
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-view` })}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-remove` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={(e) => {
|
||||
this._stopProp(e);
|
||||
this.props.onSelect(i);
|
||||
this._handleRemoveFromSlate(e, i);
|
||||
}}
|
||||
>
|
||||
<SVG.Eye height="16px" />
|
||||
</div>
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-download` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={(e) => {
|
||||
this._handleDownload(e, i);
|
||||
}}
|
||||
>
|
||||
<SVG.Download height="16px" />
|
||||
</div>
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-delete` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={
|
||||
this.state.items[i].ownerId === this.props.viewer.id
|
||||
? () => {
|
||||
this.setState({ modalShowDeleteFiles: true })
|
||||
}
|
||||
: () => {}
|
||||
}
|
||||
style={{
|
||||
cursor:
|
||||
this.state.items[i].ownerId === this.props.viewer.id
|
||||
? "pointer"
|
||||
: "not-allowed",
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
cursor: "pointer",
|
||||
margin: 0,
|
||||
}}
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
>
|
||||
<SVG.DismissCircle height="24px" />
|
||||
</div>
|
||||
<div
|
||||
css={STYLES_ICON_ROW}
|
||||
style={{
|
||||
bottom: this.state.fileNames
|
||||
? `calc(24px + ${TAG_HEIGHT}px)`
|
||||
: "24px",
|
||||
left: `calc(50% - 60px)`,
|
||||
}}
|
||||
>
|
||||
<SVG.Trash
|
||||
height="16px"
|
||||
style={{
|
||||
color:
|
||||
this.state.items[i].ownerId === this.props.viewer.id
|
||||
? Constants.system.red
|
||||
: "#999999",
|
||||
<Link
|
||||
redirect
|
||||
params={{
|
||||
...this.props.page?.params,
|
||||
cid: this.state.items[i].cid,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{this.state.tooltip && this.state.tooltip.startsWith(`${i}-`) ? (
|
||||
<Tooltip
|
||||
light
|
||||
style={
|
||||
this.state.tooltip === `${i}-add`
|
||||
? {
|
||||
position: "absolute",
|
||||
top: 36,
|
||||
right: 8,
|
||||
}
|
||||
: this.state.tooltip === `${i}-copy`
|
||||
? {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% + 28px)",
|
||||
}
|
||||
: this.state.tooltip === `${i}-download`
|
||||
? {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% - 12px)",
|
||||
}
|
||||
: {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% - 52px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
{this.state.tooltip === `${i}-add`
|
||||
? "Add to collection"
|
||||
: this.state.tooltip === `${i}-copy`
|
||||
? "Copy link"
|
||||
: this.state.tooltip === `${i}-download`
|
||||
? "Download"
|
||||
: this.state.tooltip === `${i}-preview`
|
||||
? "Make cover image"
|
||||
: "Save copy"}
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<div
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-add` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={
|
||||
this.props.external
|
||||
? this._handleLoginModal
|
||||
: (e) => {
|
||||
this._handleAddToSlate(e, i);
|
||||
}
|
||||
}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
cursor: "pointer",
|
||||
margin: 0,
|
||||
}}
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
>
|
||||
<SVG.PlusCircle height="24px" />
|
||||
</div>
|
||||
<div
|
||||
css={STYLES_ICON_ROW}
|
||||
style={{
|
||||
bottom: this.state.fileNames
|
||||
? `calc(24px + ${TAG_HEIGHT}px)`
|
||||
: "24px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-download` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={
|
||||
this.props.external
|
||||
? this._handleLoginModal
|
||||
: (e) => {
|
||||
this._handleDownload(e, i);
|
||||
}
|
||||
}
|
||||
>
|
||||
<SVG.Download height="16px" />
|
||||
</div>
|
||||
{this.props.isOwner ? (
|
||||
onAction={this.props.onAction}
|
||||
>
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-view` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
// onClick={(e) => {
|
||||
// this._stopProp(e);
|
||||
// this.props.onSelect(i);
|
||||
// }}
|
||||
>
|
||||
<SVG.Eye height="16px" />
|
||||
</div>
|
||||
</Link>
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-preview` })}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-download` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={(e) => {
|
||||
this._handleDownload(e, i);
|
||||
}}
|
||||
>
|
||||
<SVG.Download height="16px" />
|
||||
</div>
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-delete` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={
|
||||
this.state.items[i].ownerId === this.props.viewer.id
|
||||
? () => {
|
||||
this.setState({ modalShowDeleteFiles: true });
|
||||
}
|
||||
: () => {}
|
||||
}
|
||||
style={{
|
||||
cursor:
|
||||
this.state.items[i].ownerId === this.props.viewer.id
|
||||
? "pointer"
|
||||
: "not-allowed",
|
||||
}}
|
||||
>
|
||||
<SVG.Trash
|
||||
height="16px"
|
||||
style={{
|
||||
color:
|
||||
this.state.items[i].ownerId === this.props.viewer.id
|
||||
? Constants.system.red
|
||||
: "#999999",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{this.state.tooltip && this.state.tooltip.startsWith(`${i}-`) ? (
|
||||
<Tooltip
|
||||
light
|
||||
style={
|
||||
this.state.tooltip === `${i}-add` ||
|
||||
this.state.tooltip === `${i}-remove`
|
||||
? {
|
||||
position: "absolute",
|
||||
top: 36,
|
||||
right: 8,
|
||||
}
|
||||
: this.state.tooltip === `${i}-copy`
|
||||
? {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% + 28px)",
|
||||
}
|
||||
: this.state.tooltip === `${i}-download`
|
||||
? {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% - 12px)",
|
||||
}
|
||||
: {
|
||||
position: "absolute",
|
||||
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
|
||||
right: "calc(50% - 52px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
{this.state.tooltip === `${i}-add`
|
||||
? "Add to collection"
|
||||
: this.state.tooltip === `${i}-copy`
|
||||
? "Copy link"
|
||||
: this.state.tooltip === `${i}-download`
|
||||
? "Download"
|
||||
: this.state.tooltip === `${i}-preview`
|
||||
? "Make cover image"
|
||||
: this.state.tooltip === `${i}-remove`
|
||||
? "Remove from collection"
|
||||
: "Save copy"}
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{this.props.isOwner ? (
|
||||
<div
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-remove` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={
|
||||
this.props.external
|
||||
? this._handleLoginModal
|
||||
: this.state.items[i].data.type &&
|
||||
Validations.isPreviewableImage(
|
||||
this.state.items[i].data.type
|
||||
) &&
|
||||
this.state.items[i].data.size &&
|
||||
this.state.items[i].data.size < SIZE_LIMIT
|
||||
? (e) => this._handleSetPreview(e, i)
|
||||
: () => {}
|
||||
}
|
||||
style={
|
||||
this.props.preview ===
|
||||
Strings.getURLfromCID(this.state.items[i].cid)
|
||||
? {
|
||||
backgroundColor: "rgba(0, 97, 187, 0.75)",
|
||||
}
|
||||
: this.state.items[i].data.type &&
|
||||
Validations.isPreviewableImage(
|
||||
this.state.items[i].data.type
|
||||
) &&
|
||||
this.state.items[i].data.size &&
|
||||
this.state.items[i].data.size < SIZE_LIMIT
|
||||
? {}
|
||||
: {
|
||||
color: "#999999",
|
||||
cursor: "not-allowed",
|
||||
: (e) => {
|
||||
this._handleRemoveFromSlate(e, i);
|
||||
}
|
||||
}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
cursor: "pointer",
|
||||
margin: 0,
|
||||
}}
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
>
|
||||
{this.props.preview ===
|
||||
Strings.getURLfromCID(this.state.items[i].cid) ? (
|
||||
<SVG.DesktopEye
|
||||
height="16px"
|
||||
style={{
|
||||
color: Constants.system.white,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<SVG.Desktop height="16px" />
|
||||
)}
|
||||
<SVG.DismissCircle height="24px" />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-save` })}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-add` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={
|
||||
this.props.external
|
||||
? this._handleLoginModal
|
||||
: (e) => this._handleSaveCopy(e, i)
|
||||
: (e) => {
|
||||
this._handleAddToSlate(e, i);
|
||||
}
|
||||
}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
cursor: "pointer",
|
||||
margin: 0,
|
||||
}}
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
>
|
||||
<SVG.Save height="16px" />
|
||||
<SVG.PlusCircle height="24px" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
{this.state.fileNames ? (
|
||||
<div
|
||||
css={STYLES_FILE_TAG}
|
||||
style={{
|
||||
fontSize: `${Math.min(TAG_HEIGHT * unit * 0.7, 14)}px`,
|
||||
height: `${TAG_HEIGHT * unit}px`,
|
||||
}}
|
||||
>
|
||||
<span css={STYLES_FILE_NAME}>
|
||||
{this.state.items[i].data.name || this.state.items[i].filename}
|
||||
</span>
|
||||
<span css={STYLES_FILE_TYPE}>
|
||||
{Strings.getFileExtension(this.state.items[i].filename)}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{this.state.editing ? (
|
||||
<div
|
||||
css={STYLES_HANDLE_BOX}
|
||||
onMouseDown={(e) => this._handleMouseDownResize(e, i)}
|
||||
style={{
|
||||
display:
|
||||
this.state.hover === i || this.state.dragIndex === i ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<SVG.DragHandle height="24px" />
|
||||
</div>
|
||||
) : null}
|
||||
</Selectable>
|
||||
<div
|
||||
css={STYLES_ICON_ROW}
|
||||
style={{
|
||||
bottom: this.state.fileNames
|
||||
? `calc(24px + ${TAG_HEIGHT}px)`
|
||||
: "24px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-download` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={
|
||||
this.props.external
|
||||
? this._handleLoginModal
|
||||
: (e) => {
|
||||
this._handleDownload(e, i);
|
||||
}
|
||||
}
|
||||
>
|
||||
<SVG.Download height="16px" />
|
||||
</div>
|
||||
{this.props.isOwner ? (
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-preview` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={
|
||||
this.props.external
|
||||
? this._handleLoginModal
|
||||
: this.state.items[i].data.type &&
|
||||
Validations.isPreviewableImage(
|
||||
this.state.items[i].data.type
|
||||
) &&
|
||||
this.state.items[i].data.size &&
|
||||
this.state.items[i].data.size <
|
||||
Constants.linkPreviewSizeLimit
|
||||
? (e) => this._handleSetPreview(e, i)
|
||||
: () => {}
|
||||
}
|
||||
style={
|
||||
this.props.preview ===
|
||||
Strings.getURLfromCID(this.state.items[i].cid)
|
||||
? {
|
||||
backgroundColor: "rgba(0, 97, 187, 0.75)",
|
||||
}
|
||||
: this.state.items[i].data.type &&
|
||||
Validations.isPreviewableImage(
|
||||
this.state.items[i].data.type
|
||||
) &&
|
||||
this.state.items[i].data.size &&
|
||||
this.state.items[i].data.size <
|
||||
Constants.linkPreviewSizeLimit
|
||||
? {}
|
||||
: {
|
||||
color: "#999999",
|
||||
cursor: "not-allowed",
|
||||
}
|
||||
}
|
||||
>
|
||||
{this.props.preview ===
|
||||
Strings.getURLfromCID(this.state.items[i].cid) ? (
|
||||
<SVG.DesktopEye
|
||||
height="16px"
|
||||
style={{
|
||||
color: Constants.system.white,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<SVG.Desktop height="16px" />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
css={STYLES_ICON_CIRCLE}
|
||||
onMouseDown={this._stopProp}
|
||||
onMouseUp={this._stopProp}
|
||||
onMouseEnter={() => this.setState({ tooltip: `${i}-save` })}
|
||||
onMouseLeave={() => this.setState({ tooltip: null })}
|
||||
onClick={
|
||||
this.props.external
|
||||
? this._handleLoginModal
|
||||
: (e) => this._handleSaveCopy(e, i)
|
||||
}
|
||||
>
|
||||
<SVG.Save height="16px" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
{this.state.fileNames ? (
|
||||
<div
|
||||
css={STYLES_FILE_TAG}
|
||||
style={{
|
||||
fontSize: `${Math.min(TAG_HEIGHT * unit * 0.7, 14)}px`,
|
||||
height: `${TAG_HEIGHT * unit}px`,
|
||||
}}
|
||||
>
|
||||
<span css={STYLES_FILE_NAME}>
|
||||
{this.state.items[i].data.name || this.state.items[i].filename}
|
||||
</span>
|
||||
<span css={STYLES_FILE_TYPE}>
|
||||
{Strings.getFileExtension(this.state.items[i].filename)}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{this.state.editing ? (
|
||||
<div
|
||||
css={STYLES_HANDLE_BOX}
|
||||
onMouseDown={(e) => this._handleMouseDownResize(e, i)}
|
||||
style={{
|
||||
display:
|
||||
this.state.hover === i || this.state.dragIndex === i
|
||||
? "block"
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
<SVG.DragHandle height="24px" />
|
||||
</div>
|
||||
) : null}
|
||||
</Selectable>
|
||||
</Link>
|
||||
))
|
||||
) : (
|
||||
<div css={STYLES_LOADER}>
|
||||
@ -1688,7 +1749,7 @@ export class SlateLayout extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
{this.state.modalShowDeleteFiles && (
|
||||
<ConfirmationModal
|
||||
<ConfirmationModal
|
||||
type={"DELETE"}
|
||||
withValidation={false}
|
||||
callback={this._handleDeleteFiles}
|
||||
@ -1772,20 +1833,6 @@ export class SlateLayout extends React.Component {
|
||||
value={this.state.copyValue}
|
||||
css={STYLES_COPY_INPUT}
|
||||
/>
|
||||
{this.props.external && this.state.signInModal && (
|
||||
<div>
|
||||
<CTATransition
|
||||
onClose={() => this.setState({ signInModal: false })}
|
||||
viewer={this.props.viewer}
|
||||
open={this.state.signInModal}
|
||||
redirectURL={`/_${Strings.createQueryParams({
|
||||
scene: "NAV_SLATE",
|
||||
user: this.props.creator.username,
|
||||
slate: this.props.slate.slatename,
|
||||
})}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</GroupSelectable>
|
||||
</div>
|
||||
);
|
||||
|
@ -12,17 +12,20 @@ import { endsWithAny } from "~/common/utilities";
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
const STYLES_FAILURE = css`
|
||||
background-color: ${Constants.system.pitchBlack};
|
||||
color: ${Constants.system.white};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 88px;
|
||||
font-size: 24px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding: 24px 36px;
|
||||
height: 100px;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
min-height: 10%;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
background-color: rgba(20, 20, 20, 0.8);
|
||||
`;
|
||||
|
||||
const STYLES_OBJECT = css`
|
||||
@ -94,7 +97,7 @@ export default class SlateMediaObject extends React.Component {
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<a href={url} target="_blank">
|
||||
<a href={url} target="_blank" style={{ textDecoration: "none" }}>
|
||||
<div css={STYLES_FAILURE}>Tap to open PDF in new tab</div>
|
||||
</a>
|
||||
) : (
|
||||
|
@ -8,6 +8,7 @@ import { Logo } from "~/common/logo";
|
||||
import { css } from "@emotion/react";
|
||||
import { Boundary } from "~/components/system/components/fragments/Boundary";
|
||||
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
import ProcessedText from "~/components/core/ProcessedText";
|
||||
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
|
||||
@ -277,10 +278,12 @@ export class SlatePreviewBlock extends React.Component {
|
||||
right: "-12px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: "Copy collection ID",
|
||||
onClick: (e) => this._handleCopy(e, this.props.slate.id),
|
||||
},
|
||||
[
|
||||
{
|
||||
text: "Copy collection ID",
|
||||
onClick: (e) => this._handleCopy(e, this.props.slate.id),
|
||||
},
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</Boundary>
|
||||
@ -437,44 +440,15 @@ export default class SlatePreviewBlocks extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div css={STYLES_SLATES}>
|
||||
{this.props.external
|
||||
? this.props.slates?.map((slate) => (
|
||||
<a
|
||||
key={slate.id}
|
||||
style={{ textDecoration: "none", color: Constants.system.black }}
|
||||
href={
|
||||
!!this.props.username
|
||||
? `/${this.props.username}/${slate.slatename}`
|
||||
: `/$/slate/${slate.id}`
|
||||
}
|
||||
>
|
||||
<SlatePreviewBlock
|
||||
isOwner={this.props.isOwner}
|
||||
username={this.props.username}
|
||||
slate={slate}
|
||||
external={this.props.external}
|
||||
/>
|
||||
</a>
|
||||
))
|
||||
: this.props.slates?.map((slate) => (
|
||||
<div
|
||||
key={slate.id}
|
||||
onClick={() =>
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_SLATE",
|
||||
data: { decorator: "SLATE", ...slate },
|
||||
})
|
||||
}
|
||||
>
|
||||
<SlatePreviewBlock
|
||||
isOwner={this.props.isOwner}
|
||||
username={this.props.username}
|
||||
slate={slate}
|
||||
external={this.props.external}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{this.props.slates?.map((slate) => (
|
||||
<Link key={slate.id} href={`/$/slate/${slate.id}`} onAction={this.props.onAction}>
|
||||
<SlatePreviewBlock
|
||||
isOwner={this.props.isOwner}
|
||||
slate={slate}
|
||||
external={this.props.external}
|
||||
/>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
const STYLES_PRIMARY_TAB_GROUP = css`
|
||||
font-size: ${Constants.typescale.lvl2};
|
||||
@ -81,7 +82,8 @@ const STYLES_NAVTAB = css`
|
||||
|
||||
@media (max-width: ${Constants.sizes.mobile}px) {
|
||||
margin-right: 12px;
|
||||
font-size: ${Constants.typescale.lvl1};
|
||||
height: 40px;
|
||||
font-size: ${Constants.typescale.lvl0};
|
||||
}
|
||||
|
||||
:last-child {
|
||||
@ -91,99 +93,94 @@ const STYLES_NAVTAB = css`
|
||||
|
||||
export class SecondaryTabGroup extends React.Component {
|
||||
render() {
|
||||
const value = this.props.value || this.props.tabs[0].value;
|
||||
console.log(this.props.value);
|
||||
const disabled = this.props.disabled;
|
||||
return (
|
||||
<div css={STYLES_SECONDARY_TAB_GROUP} style={this.props.style}>
|
||||
{this.props.tabs.map((tab, i) => (
|
||||
<div
|
||||
css={STYLES_TAB}
|
||||
key={this.props.onAction ? tab.title : i}
|
||||
style={{
|
||||
color:
|
||||
this.props.disabled || this.props.value === i
|
||||
? Constants.system.black
|
||||
: "rgba(0,0,0,0.25)",
|
||||
cursor: this.props.disabled ? "auto" : "pointer",
|
||||
...this.props.itemStyle,
|
||||
backgroundColor: this.props.value === i ? Constants.system.white : "transparent",
|
||||
}}
|
||||
onClick={
|
||||
this.props.disabled || this.props.value === i
|
||||
? () => {}
|
||||
: this.props.onAction
|
||||
? () => this.props.onAction({ type: "NAVIGATE", value: tab.value })
|
||||
: () => this.props.onChange(i)
|
||||
}
|
||||
>
|
||||
{this.props.onAction ? tab.title : tab}
|
||||
</div>
|
||||
))}
|
||||
{this.props.tabs.map((tab, i) => {
|
||||
const selected = value === tab.value?.tab;
|
||||
return (
|
||||
<Link key={i} params={tab.value} onAction={this.props.onAction}>
|
||||
<div
|
||||
css={STYLES_TAB}
|
||||
style={{
|
||||
color: disabled || selected ? Constants.system.black : "rgba(0,0,0,0.25)",
|
||||
cursor: disabled ? "auto" : "pointer",
|
||||
...this.props.itemStyle,
|
||||
backgroundColor: selected ? Constants.system.white : "transparent",
|
||||
}}
|
||||
// onClick={disabled || selected ? () => {} : () => this.props.onChange(tab.value)}
|
||||
>
|
||||
{tab.title}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class PrimaryTabGroup extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div css={STYLES_PRIMARY_TAB_GROUP} style={this.props.style}>
|
||||
{this.props.tabs.map((tab, i) => (
|
||||
<div
|
||||
css={STYLES_TAB}
|
||||
key={this.props.onAction ? tab.title : tab}
|
||||
style={{
|
||||
padding: "8px 16px 8px 0",
|
||||
color:
|
||||
this.props.disabled || this.props.value === i
|
||||
? Constants.system.black
|
||||
: "rgba(0,0,0,0.25)",
|
||||
cursor: this.props.disabled ? "auto" : "pointer",
|
||||
fontFamily: Constants.font.medium,
|
||||
...this.props.itemStyle,
|
||||
}}
|
||||
onClick={
|
||||
this.props.disabled || this.props.value === i
|
||||
? () => {}
|
||||
: this.props.onAction
|
||||
? () => this.props.onAction({ type: "NAVIGATE", value: tab.value })
|
||||
: () => this.props.onChange(i)
|
||||
}
|
||||
>
|
||||
{this.props.onAction ? tab.title : tab}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
// export class PrimaryTabGroup extends React.Component {
|
||||
// render() {
|
||||
// return (
|
||||
// <div css={STYLES_PRIMARY_TAB_GROUP} style={this.props.style}>
|
||||
// {this.props.tabs.map((tab, i) => (
|
||||
// <div
|
||||
// css={STYLES_TAB}
|
||||
// key={this.props.onAction ? tab.title : tab}
|
||||
// style={{
|
||||
// padding: "8px 16px 8px 0",
|
||||
// color:
|
||||
// this.props.disabled || this.props.value === i
|
||||
// ? Constants.system.black
|
||||
// : "rgba(0,0,0,0.25)",
|
||||
// cursor: this.props.disabled ? "auto" : "pointer",
|
||||
// fontFamily: Constants.font.medium,
|
||||
// ...this.props.itemStyle,
|
||||
// }}
|
||||
// onClick={
|
||||
// this.props.disabled || this.props.value === i
|
||||
// ? () => {}
|
||||
// : this.props.onAction
|
||||
// ? () => this.props.onAction({ type: "NAVIGATE", value: tab.value })
|
||||
// : () => this.props.onChange(i)
|
||||
// }
|
||||
// >
|
||||
// {this.props.onAction ? tab.title : tab}
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
export class TabGroup extends React.Component {
|
||||
render() {
|
||||
const value = this.props.value || this.props.tabs[0].value;
|
||||
const disabled = this.props.disabled;
|
||||
return (
|
||||
<div css={STYLES_TAB_GROUP} style={this.props.style}>
|
||||
{this.props.tabs.map((tab, i) => (
|
||||
<div
|
||||
css={STYLES_NAVTAB}
|
||||
key={this.props.onAction ? tab.title : tab}
|
||||
style={{
|
||||
color:
|
||||
this.props.disabled || this.props.value === i
|
||||
? Constants.system.black
|
||||
: "rgba(0,0,0,0.25)",
|
||||
cursor: this.props.disabled ? "auto" : "pointer",
|
||||
borderBottom: this.props.value === i ? `1px solid ${Constants.system.black}` : "none",
|
||||
...this.props.itemStyle,
|
||||
}}
|
||||
onClick={
|
||||
this.props.disabled || this.props.value === i
|
||||
? () => {}
|
||||
: this.props.onAction
|
||||
? () => this.props.onAction({ type: "NAVIGATE", value: tab.value })
|
||||
: () => this.props.onChange(i)
|
||||
}
|
||||
>
|
||||
{this.props.onAction ? tab.title : tab}
|
||||
</div>
|
||||
))}
|
||||
{this.props.tabs.map((tab, i) => {
|
||||
const selected = value === tab.value?.subtab;
|
||||
return (
|
||||
<Link key={i} params={tab.value} onAction={this.props.onAction}>
|
||||
<div
|
||||
css={STYLES_NAVTAB}
|
||||
style={{
|
||||
color: disabled || selected ? Constants.system.black : "rgba(0,0,0,0.25)",
|
||||
cursor: disabled ? "auto" : "pointer",
|
||||
...this.props.itemStyle,
|
||||
borderBottom: selected ? `1px solid ${Constants.system.black}` : "none",
|
||||
}}
|
||||
// onClick={disabled || selected ? () => {} : () => this.props.onChange(tab.value)}
|
||||
>
|
||||
{tab.title}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -62,17 +62,17 @@ const WebsitePrototypeHeader = (props) => {
|
||||
<div css={STYLES_CONTAINER} style={props.style}>
|
||||
<div css={STYLES_LEFT}>
|
||||
<a css={STYLES_LINK} href="/" style={{ marginRight: 16, position: "relative", top: "1px" }}>
|
||||
<Logo style={{ height: 16 }} />
|
||||
<Logo style={{ height: 20 }} />
|
||||
</a>
|
||||
</div>
|
||||
<div css={STYLES_RIGHT}>
|
||||
{/* <div css={STYLES_RIGHT}>
|
||||
<a css={STYLES_LINK} href="/_" style={{ marginRight: 24 }}>
|
||||
Sign up
|
||||
</a>
|
||||
<a css={STYLES_LINK} href="/_">
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -87,10 +87,10 @@ const WebsitePrototypeHeaderGeneric = (props) => {
|
||||
</a>
|
||||
</div>
|
||||
<div css={STYLES_RIGHT}>
|
||||
<a css={STYLES_LINK} href="/_" style={{ marginRight: 24 }}>
|
||||
<a css={STYLES_LINK} href="/_/auth?tab=signup" style={{ marginRight: 24 }}>
|
||||
Sign up
|
||||
</a>
|
||||
<a css={STYLES_LINK} href="/_">
|
||||
<a css={STYLES_LINK} href="/_/auth?tab=signin">
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
|
@ -6,6 +6,9 @@ export default class WebsitePrototypeWrapper extends React.Component {
|
||||
static defaultProps = {
|
||||
image:
|
||||
"https://slate.textile.io/ipfs/bafybeihtmqpx2lnlvaerfhq5imi2y3jzuf4jqspmmqbth3ebim4ebc2lqy",
|
||||
title: "Slate",
|
||||
url: "https://slate.host/_",
|
||||
description: "An open-source file sharing network for research and collaboration",
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -80,8 +80,8 @@ export default class SidebarAddFileToBucket extends React.Component {
|
||||
this.props.onUpload({
|
||||
files: e.target.files,
|
||||
slate:
|
||||
this.props.current && this.props.current.slateId
|
||||
? { id: this.props.current.slateId }
|
||||
this.props.page.id === "NAV_SLATE" && this.props.data?.id
|
||||
? { id: this.props.data.id }
|
||||
: null,
|
||||
});
|
||||
this.props.onCancel();
|
||||
@ -134,12 +134,10 @@ export default class SidebarAddFileToBucket extends React.Component {
|
||||
|
||||
<System.P>
|
||||
Click below or drop a file anywhere on the page to upload a file
|
||||
{this.props.current &&
|
||||
(this.props.current.slatename ||
|
||||
(this.props.current.data && this.props.current.data.name)) ? (
|
||||
{this.props.data?.slatename || this.props.data?.data.name ? (
|
||||
<span>
|
||||
{" "}
|
||||
to <strong>{Strings.getPresentationSlateName(this.props.current)}</strong>
|
||||
to <strong>{Strings.getPresentationSlateName(this.props.data)}</strong>
|
||||
</span>
|
||||
) : (
|
||||
""
|
||||
|
@ -9,6 +9,7 @@ import * as SVG from "~/common/svg";
|
||||
|
||||
import { RadioGroup } from "~/components/system/components/RadioGroup";
|
||||
import { css } from "@emotion/react";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
const STYLES_TEXT = css`
|
||||
color: ${Constants.system.textGray};
|
||||
@ -91,10 +92,7 @@ export default class SidebarCreateSlate extends React.Component {
|
||||
() =>
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_SLATE",
|
||||
data: {
|
||||
id: response.slate.id,
|
||||
},
|
||||
href: `/$/slate/${response.slate.id}`,
|
||||
}),
|
||||
200
|
||||
);
|
||||
|
@ -86,7 +86,7 @@ export default class SidebarEditTags extends React.Component {
|
||||
this._handleSave();
|
||||
|
||||
let newSuggestions = new Set([...this.state.suggestions, ...this.state.tags]);
|
||||
this.props.onUpdateViewer({ tags: Array.from(newSuggestions) });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { tags: Array.from(newSuggestions) } });
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -27,7 +27,7 @@ const STYLES_TEXT = css`
|
||||
|
||||
export default class SidebarCreateSlate extends React.Component {
|
||||
state = {
|
||||
name: this.props.viewer.data && this.props.viewer.data.name ? this.props.viewer.data.name : "",
|
||||
name: this.props.viewer?.data?.name ? this.props.viewer.data.name : "",
|
||||
email: "",
|
||||
twitter: "",
|
||||
message: "",
|
||||
@ -51,12 +51,12 @@ export default class SidebarCreateSlate extends React.Component {
|
||||
});
|
||||
|
||||
const response = await Actions.createSupportMessage({
|
||||
username: this.props.viewer.username,
|
||||
username: this.props.viewer?.username || "",
|
||||
name: this.state.name,
|
||||
email: this.state.email,
|
||||
twitter: this.state.twitter,
|
||||
message: this.state.message,
|
||||
stored: Strings.bytesToSize(this.props.viewer.stats.bytes),
|
||||
stored: Strings.bytesToSize(this.props.viewer?.stats.bytes || 0),
|
||||
});
|
||||
|
||||
Events.hasError(response);
|
||||
|
@ -11,8 +11,8 @@ import * as UserBehaviors from "~/common/user-behaviors";
|
||||
import { RadioGroup } from "~/components/system/components/RadioGroup";
|
||||
import { css } from "@emotion/react";
|
||||
import { ConfirmationModal } from "~/components/core/ConfirmationModal";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
const SIZE_LIMIT = 1000000;
|
||||
const DEFAULT_IMAGE =
|
||||
"https://slate.textile.io/ipfs/bafkreiaow45dlq5xaydaeqocdxvffudibrzh2c6qandpqkb6t3ahbvh6re";
|
||||
|
||||
@ -75,7 +75,10 @@ export default class SidebarSingleSlateSettings extends React.Component {
|
||||
slate.data.tags = this.state.tags;
|
||||
|
||||
let newSuggestions = new Set([...this.state.suggestions, ...this.state.tags]);
|
||||
this.props.onUpdateViewer({ slates, tags: Array.from(newSuggestions) });
|
||||
this.props.onAction({
|
||||
type: "UPDATE_VIEWER",
|
||||
viewer: { slates, tags: Array.from(newSuggestions) },
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
@ -111,16 +114,16 @@ export default class SidebarSingleSlateSettings extends React.Component {
|
||||
|
||||
_handleDelete = async (res) => {
|
||||
if (!res) {
|
||||
this.setState({ modalShow: false })
|
||||
this.setState({ modalShow: false });
|
||||
return;
|
||||
}
|
||||
|
||||
let slates = this.props.viewer.slates.filter((slate) => slate.id !== this.props.data.id);
|
||||
this.props.onUpdateViewer({ slates });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { slates } });
|
||||
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_SLATES",
|
||||
value: "/_/collections",
|
||||
});
|
||||
const response = await Actions.deleteSlate({
|
||||
id: this.props.data.id,
|
||||
@ -130,7 +133,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ modalShow: false })
|
||||
this.setState({ modalShow: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -143,7 +146,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
|
||||
object.data.type &&
|
||||
Validations.isPreviewableImage(object.data.type) &&
|
||||
object.data.size &&
|
||||
object.data.size < SIZE_LIMIT
|
||||
object.data.size < Constants.linkPreviewSizeLimit
|
||||
) {
|
||||
preview = Strings.getURLfromCID(object.cid);
|
||||
break;
|
||||
@ -304,16 +307,20 @@ export default class SidebarSingleSlateSettings extends React.Component {
|
||||
</System.ButtonPrimary>
|
||||
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<System.ButtonWarning full onClick={() => this.setState({ modalShow: true })} style={{ overflow: "hidden" }}>
|
||||
<System.ButtonWarning
|
||||
full
|
||||
onClick={() => this.setState({ modalShow: true })}
|
||||
style={{ overflow: "hidden" }}
|
||||
>
|
||||
Delete collection
|
||||
</System.ButtonWarning>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.modalShow && (
|
||||
<ConfirmationModal
|
||||
<ConfirmationModal
|
||||
type={"DELETE"}
|
||||
withValidation={false}
|
||||
callback={this._handleDelete}
|
||||
callback={this._handleDelete}
|
||||
header={`Are you sure you want to delete the collection “${this.state.slatename}”?`}
|
||||
subHeader={`This collection will be deleted but all your files will remain in your file library. You can’t undo this action.`}
|
||||
/>
|
||||
|
@ -116,31 +116,28 @@ const STYLES_MOBILE_HIDDEN = css`
|
||||
|
||||
export class GlobalCarousel extends React.Component {
|
||||
state = {
|
||||
index: 0,
|
||||
visible: false,
|
||||
showSidebar: true,
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
window.addEventListener("keydown", this._handleKeyDown);
|
||||
window.addEventListener("slate-global-open-carousel", this._handleOpen);
|
||||
window.addEventListener("slate-global-close-carousel", this._handleClose);
|
||||
// window.addEventListener("slate-global-open-carousel", this._handleOpen);
|
||||
// window.addEventListener("slate-global-close-carousel", this._handleClose);
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
window.removeEventListener("keydown", this._handleKeyDown);
|
||||
window.removeEventListener("slate-global-open-carousel", this._handleOpen);
|
||||
window.removeEventListener("slate-global-close-carousel", this._handleClose);
|
||||
// window.removeEventListener("slate-global-open-carousel", this._handleOpen);
|
||||
// window.removeEventListener("slate-global-close-carousel", this._handleClose);
|
||||
};
|
||||
|
||||
componentDidUpdate = (prevProps) => {
|
||||
if (
|
||||
!this.props.objects ||
|
||||
this.props.objects.length == 0 ||
|
||||
this.state.index >= this.props.objects.length
|
||||
) {
|
||||
this._handleClose();
|
||||
findSelectedIndex = () => {
|
||||
const cid = this.props.params?.cid;
|
||||
if (!cid) {
|
||||
return -1;
|
||||
}
|
||||
let index = this.props.objects.findIndex((elem) => elem.cid === cid);
|
||||
return index;
|
||||
};
|
||||
|
||||
_handleKeyDown = (e) => {
|
||||
@ -173,95 +170,132 @@ export class GlobalCarousel extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
setWindowState = (data = {}) => {
|
||||
const cid = data?.cid;
|
||||
if (this.props.carouselType === "ACTIVITY") {
|
||||
window.history.replaceState(
|
||||
{ ...window.history.state, cid: cid },
|
||||
null,
|
||||
cid ? `/${data.owner}/${data.slate.slatename}/cid:${cid}` : `/_?scene=NAV_ACTIVITY`
|
||||
);
|
||||
return;
|
||||
}
|
||||
// setWindowState = (data = {}) => {
|
||||
// const cid = data?.cid;
|
||||
// if (this.props.carouselType === "ACTIVITY") {
|
||||
// window.history.replaceState(
|
||||
// { ...window.history.state, cid: cid },
|
||||
// null,
|
||||
// cid ? `/${data.owner}/${data.slate.slatename}/cid:${cid}` : `/_?scene=NAV_ACTIVITY`
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
|
||||
let baseURL = window.location.pathname.split("/");
|
||||
if (this.props.carouselType === "SLATE") {
|
||||
baseURL.length = 3;
|
||||
} else if (this.props.carouselType === "PROFILE") {
|
||||
baseURL.length = 2;
|
||||
} else if (this.props.carouselType === "DATA") {
|
||||
baseURL.length = 2;
|
||||
if (cid) {
|
||||
baseURL[1] = this.props.viewer.username;
|
||||
} else {
|
||||
baseURL[1] = "_?scene=NAV_DATA";
|
||||
}
|
||||
}
|
||||
baseURL = baseURL.join("/");
|
||||
// let baseURL = window.location.pathname.split("/");
|
||||
// if (this.props.carouselType === "SLATE") {
|
||||
// baseURL.length = 3;
|
||||
// } else if (this.props.carouselType === "PROFILE") {
|
||||
// baseURL.length = 2;
|
||||
// } else if (this.props.carouselType === "DATA") {
|
||||
// baseURL.length = 2;
|
||||
// if (cid) {
|
||||
// baseURL[1] = this.props.viewer.username;
|
||||
// } else {
|
||||
// baseURL[1] = "_?scene=NAV_DATA";
|
||||
// }
|
||||
// }
|
||||
// baseURL = baseURL.join("/");
|
||||
|
||||
window.history.replaceState(
|
||||
{ ...window.history.state, cid: cid },
|
||||
null,
|
||||
cid ? `${baseURL}/cid:${cid}` : baseURL
|
||||
);
|
||||
};
|
||||
// window.history.replaceState(
|
||||
// { ...window.history.state, cid: cid },
|
||||
// null,
|
||||
// cid ? `${baseURL}/cid:${cid}` : baseURL
|
||||
// );
|
||||
// };
|
||||
|
||||
_handleOpen = (e) => {
|
||||
let index = e.detail.index;
|
||||
const objects = this.props.objects;
|
||||
if (e.detail.index === null) {
|
||||
if (e.detail.id !== null) {
|
||||
index = objects.findIndex((obj) => obj.id === e.detail.id);
|
||||
}
|
||||
}
|
||||
if (index === null || index < 0 || index >= objects.length) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
visible: true,
|
||||
index: e.detail.index,
|
||||
});
|
||||
const data = objects[e.detail.index];
|
||||
this.setWindowState(data);
|
||||
};
|
||||
// _handleOpen = (e) => {
|
||||
// let index = e.detail.index;
|
||||
// const objects = this.props.objects;
|
||||
// if (e.detail.index === null) {
|
||||
// if (e.detail.id !== null) {
|
||||
// index = objects.findIndex((obj) => obj.id === e.detail.id);
|
||||
// }
|
||||
// }
|
||||
// if (index === null || index < 0 || index >= objects.length) {
|
||||
// return;
|
||||
// }
|
||||
// this.setState({
|
||||
// visible: true,
|
||||
// index: e.detail.index,
|
||||
// });
|
||||
// const data = objects[e.detail.index];
|
||||
// this.setWindowState(data);
|
||||
// };
|
||||
|
||||
_handleClose = (e) => {
|
||||
if (this.state.visible) {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
this.setState({ visible: false, index: 0 });
|
||||
this.setWindowState();
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(-1);
|
||||
} else {
|
||||
let params = { ...this.props.params };
|
||||
delete params.cid;
|
||||
this.props.onAction({
|
||||
type: "UPDATE_PARAMS",
|
||||
params,
|
||||
redirect: true,
|
||||
});
|
||||
}
|
||||
|
||||
// this.setState({ visible: false, index: 0 });
|
||||
// this.setWindowState();
|
||||
};
|
||||
|
||||
_handleNext = (e) => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
let index = this.state.index + 1;
|
||||
if (index >= this.props.objects.length) {
|
||||
return;
|
||||
if (this.props.onChange) {
|
||||
let index = this.props.index + 1;
|
||||
if (index >= this.props.objects.length) return;
|
||||
this.props.onChange(index);
|
||||
} else {
|
||||
let index = this.findSelectedIndex() + 1;
|
||||
if (index >= this.props.objects.length) {
|
||||
return;
|
||||
}
|
||||
let cid = this.props.objects[index].cid;
|
||||
// this.setState({ index });
|
||||
this.props.onAction({
|
||||
type: "UPDATE_PARAMS",
|
||||
params: { ...this.props.params, cid },
|
||||
redirect: true,
|
||||
});
|
||||
}
|
||||
this.setState({ index });
|
||||
|
||||
const data = this.props.objects[index];
|
||||
this.setWindowState(data);
|
||||
// const data = this.props.objects[index];
|
||||
// this.setWindowState(data);
|
||||
};
|
||||
|
||||
//it uses the initial cid to set which index it is, then it goes off its internal index from there and sets apge still but doesn't get from it?
|
||||
//though that
|
||||
//maybe the initial open is triggered by page, combined with index?
|
||||
//or mayube
|
||||
|
||||
_handlePrevious = (e) => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
let index = this.state.index - 1;
|
||||
if (index < 0) {
|
||||
return;
|
||||
if (this.props.onChange) {
|
||||
let index = this.props.index - 1;
|
||||
if (index < 0) return;
|
||||
this.props.onChange(index);
|
||||
} else {
|
||||
let index = this.findSelectedIndex() - 1;
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
let cid = this.props.objects[index].cid;
|
||||
// this.setState({ index });
|
||||
this.props.onAction({
|
||||
type: "UPDATE_PARAMS",
|
||||
params: { ...this.props.params, cid },
|
||||
redirect: true,
|
||||
});
|
||||
}
|
||||
this.setState({ index });
|
||||
|
||||
const data = this.props.objects[index];
|
||||
this.setWindowState(data);
|
||||
// const data = this.props.objects[index];
|
||||
// this.setWindowState(data);
|
||||
};
|
||||
|
||||
_handleToggleSidebar = (e) => {
|
||||
@ -272,27 +306,33 @@ export class GlobalCarousel extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
if (
|
||||
!this.state.visible ||
|
||||
!this.props.carouselType ||
|
||||
this.state.index < 0 ||
|
||||
this.state.index >= this.props.objects.length
|
||||
) {
|
||||
let index;
|
||||
if (this.props.onChange) {
|
||||
index = this.props.index;
|
||||
} else {
|
||||
index = this.findSelectedIndex();
|
||||
}
|
||||
if (!this.props.carouselType || index < 0 || index >= this.props.objects.length) {
|
||||
return null;
|
||||
}
|
||||
let data = this.props.objects[this.state.index];
|
||||
let { isMobile, isOwner } = this.props;
|
||||
let file = this.props.objects[index];
|
||||
if (!file) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let { isMobile } = this.props;
|
||||
|
||||
let isRepost = false;
|
||||
if (this.props.carouselType === "SLATE") {
|
||||
isRepost = this.props.current?.ownerId !== data.ownerId;
|
||||
isRepost = this.props.data?.ownerId !== file.ownerId;
|
||||
}
|
||||
|
||||
let slide = <SlateMediaObject file={data} isMobile={isMobile} />;
|
||||
let slide = <SlateMediaObject file={file} isMobile={isMobile} />;
|
||||
|
||||
return (
|
||||
<div css={STYLES_ROOT}>
|
||||
<Alert
|
||||
viewer={this.props.viewer}
|
||||
noWarning
|
||||
id={isMobile ? "slate-mobile-alert" : null}
|
||||
style={
|
||||
@ -308,7 +348,7 @@ export class GlobalCarousel extends React.Component {
|
||||
}
|
||||
/>
|
||||
<div css={STYLES_ROOT_CONTENT} style={this.props.style} onClick={this._handleClose}>
|
||||
{this.state.index > 0 && (
|
||||
{index > 0 && (
|
||||
<span
|
||||
css={STYLES_BOX}
|
||||
onClick={this._handlePrevious}
|
||||
@ -317,7 +357,7 @@ export class GlobalCarousel extends React.Component {
|
||||
<SVG.ChevronLeft height="20px" />
|
||||
</span>
|
||||
)}
|
||||
{this.state.index < this.props.objects.length - 1 && (
|
||||
{index < this.props.objects.length - 1 && (
|
||||
<span
|
||||
css={STYLES_BOX}
|
||||
onClick={this._handleNext}
|
||||
@ -351,12 +391,13 @@ export class GlobalCarousel extends React.Component {
|
||||
</div>
|
||||
<span css={STYLES_MOBILE_HIDDEN}>
|
||||
<CarouselSidebar
|
||||
key={data.id}
|
||||
key={file.id}
|
||||
{...this.props}
|
||||
data={data}
|
||||
file={file}
|
||||
display={this.state.showSidebar ? "block" : "none"}
|
||||
onClose={this._handleClose}
|
||||
isRepost={isRepost}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as Styles from "~/common/styles";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
@ -7,27 +8,37 @@ const STYLES_POPOVER = css`
|
||||
z-index: ${Constants.zindex.tooltip};
|
||||
box-sizing: border-box;
|
||||
font-family: ${Constants.font.text};
|
||||
width: 200px;
|
||||
min-width: 200px;
|
||||
border-radius: 4px;
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
background-color: ${Constants.system.white};
|
||||
color: ${Constants.system.pitchBlack};
|
||||
box-shadow: ${Constants.shadow.medium};
|
||||
padding: 16px 24px;
|
||||
padding: 16px;
|
||||
border: 1px solid ${Constants.system.gray40};
|
||||
`;
|
||||
|
||||
const STYLES_POPOVER_SECTION = css`
|
||||
border-bottom: 1px solid ${Constants.system.gray40};
|
||||
padding-bottom: 6px;
|
||||
margin-bottom: 6px;
|
||||
width: calc(100% - 32px);
|
||||
|
||||
:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: -6px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_POPOVER_ITEM = css`
|
||||
font-family: ${Constants.font.medium};
|
||||
box-sizing: border-box;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 8px 0px;
|
||||
padding: 6px 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: 200ms ease all;
|
||||
cursor: pointer;
|
||||
font-size: ${Constants.typescale.lvlN1};
|
||||
|
||||
:hover {
|
||||
color: ${Constants.system.brand};
|
||||
@ -37,15 +48,27 @@ const STYLES_POPOVER_ITEM = css`
|
||||
export class PopoverNavigation extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div css={STYLES_POPOVER} style={this.props.style}>
|
||||
{this.props.navigation.map((each, i) => (
|
||||
<div
|
||||
key={i}
|
||||
css={STYLES_POPOVER_ITEM}
|
||||
style={this.props.itemStyle}
|
||||
onClick={each.onClick}
|
||||
>
|
||||
{each.text}
|
||||
<div
|
||||
css={STYLES_POPOVER}
|
||||
style={this.props.style}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{this.props.topSection}
|
||||
{this.props.navigation.map((section, i) => (
|
||||
<div css={STYLES_POPOVER_SECTION}>
|
||||
{section.map((each, j) => (
|
||||
<div
|
||||
key={`${i}-${j}`}
|
||||
css={STYLES_POPOVER_ITEM}
|
||||
style={this.props.itemStyle}
|
||||
onClick={each.onClick}
|
||||
>
|
||||
<div css={Styles.HEADING_05 || this.props.css}>{each.text}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -98,6 +98,8 @@ export class Boundary extends React.PureComponent {
|
||||
};
|
||||
|
||||
_handleOutsideRectEvent = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.props.onOutsideRectEvent(e);
|
||||
};
|
||||
|
||||
|
@ -6,6 +6,7 @@ import * as Strings from "~/common/strings";
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
import { CodeText } from "~/components/system/components/fragments/CodeText";
|
||||
import { css } from "@emotion/react";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
import Avatar from "~/components/core/Avatar";
|
||||
|
||||
@ -146,7 +147,7 @@ const STYLES_TABLE_CONTENT_LINK = css`
|
||||
}
|
||||
`;
|
||||
|
||||
const Link = (props) => {
|
||||
const LinkItem = (props) => {
|
||||
return <span css={STYLES_TABLE_CONTENT_LINK} {...props} />;
|
||||
};
|
||||
|
||||
@ -188,7 +189,11 @@ export const TableContent = ({ type, text, action, data = {}, onAction }) => {
|
||||
case "DEAL_CATEGORY":
|
||||
return <React.Fragment>{text == 1 ? "Storage" : "Retrieval"}</React.Fragment>;
|
||||
case "BUTTON":
|
||||
return <Link onClick={() => onAction({ type: "SIDEBAR", value: action, data })}>{text}</Link>;
|
||||
return (
|
||||
<LinkItem onClick={() => onAction({ type: "SIDEBAR", value: action, data })}>
|
||||
{text}
|
||||
</LinkItem>
|
||||
);
|
||||
case "TRANSACTION_DIRECTION":
|
||||
return COMPONENTS_TRANSACTION_DIRECTION[text];
|
||||
case "TRANSACTION_STATUS":
|
||||
@ -274,14 +279,16 @@ export const TableContent = ({ type, text, action, data = {}, onAction }) => {
|
||||
return text;
|
||||
}
|
||||
|
||||
return <Link onClick={() => window.open(text)}>{text}</Link>;
|
||||
return <LinkItem onClick={() => window.open(text)}>{text}</LinkItem>;
|
||||
case "SLATE_LINK":
|
||||
if (!data) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link onClick={() => onAction({ type: "NAVIGATE", value: data.id, data })}>{text}</Link>
|
||||
<LinkItem onClick={() => onAction({ type: "NAVIGATE", value: data.id, data })}>
|
||||
{text}
|
||||
</LinkItem>
|
||||
);
|
||||
case "FILE_LINK":
|
||||
if (!data) {
|
||||
@ -289,7 +296,9 @@ export const TableContent = ({ type, text, action, data = {}, onAction }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Link onClick={() => onAction({ type: "NAVIGATE", value: "NAV_FILE", data })}>{text}</Link>
|
||||
<LinkItem onClick={() => onAction({ type: "NAVIGATE", value: "NAV_FILE", data })}>
|
||||
{text}
|
||||
</LinkItem>
|
||||
);
|
||||
default:
|
||||
return text;
|
||||
|
@ -28,20 +28,31 @@ export default async ({
|
||||
"activity.id",
|
||||
"activity.type",
|
||||
"activity.createdAt",
|
||||
users(),
|
||||
slates(),
|
||||
"activity.slateId",
|
||||
// users(),
|
||||
// slates(),
|
||||
files()
|
||||
)
|
||||
.from("activity")
|
||||
.join("users", "users.id", "=", "activity.ownerId")
|
||||
// .join("users", "users.id", "=", "activity.ownerId")
|
||||
.leftJoin("files", "files.id", "=", "activity.fileId")
|
||||
.leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
.where("activity.type", "CREATE_SLATE_OBJECT")
|
||||
.where("activity.createdAt", "<", date.toISOString())
|
||||
.whereIn("activity.ownerId", following)
|
||||
.orWhereIn("activity.slateId", subscriptions)
|
||||
// .leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
.whereRaw("?? < ? and ?? = ? and (?? = any(?) or ?? = any(?))", [
|
||||
"activity.createdAt",
|
||||
date.toISOString(),
|
||||
"activity.type",
|
||||
"CREATE_SLATE_OBJECT",
|
||||
"activity.ownerId",
|
||||
following,
|
||||
"activity.slateId",
|
||||
subscriptions,
|
||||
])
|
||||
// .where("activity.type", "CREATE_SLATE_OBJECT")
|
||||
// .where("activity.createdAt", "<", date.toISOString())
|
||||
// .whereIn("activity.ownerId", following)
|
||||
// .orWhereIn("activity.slateId", subscriptions)
|
||||
.orderBy("activity.createdAt", "desc")
|
||||
.limit(100);
|
||||
.limit(96);
|
||||
} else if (latestTimestamp) {
|
||||
//NOTE(martina): for fetching new updates since the last time they loaded
|
||||
let date = new Date(latestTimestamp);
|
||||
@ -50,39 +61,59 @@ export default async ({
|
||||
"activity.id",
|
||||
"activity.type",
|
||||
"activity.createdAt",
|
||||
users(),
|
||||
slates(),
|
||||
"activity.slateId",
|
||||
// users(),
|
||||
// slates(),
|
||||
files()
|
||||
)
|
||||
.from("activity")
|
||||
.join("users", "users.id", "=", "activity.ownerId")
|
||||
// .join("users", "users.id", "=", "activity.ownerId")
|
||||
.leftJoin("files", "files.id", "=", "activity.fileId")
|
||||
.leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
.where("activity.createdAt", ">", date.toISOString())
|
||||
.where("activity.type", "CREATE_SLATE_OBJECT")
|
||||
.whereIn("activity.ownerId", following)
|
||||
.orWhereIn("activity.slateId", subscriptions)
|
||||
// .leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
.whereRaw("?? > ? and ?? = ? and (?? = any(?) or ?? = any(?))", [
|
||||
"activity.createdAt",
|
||||
date.toISOString(),
|
||||
"activity.type",
|
||||
"CREATE_SLATE_OBJECT",
|
||||
"activity.ownerId",
|
||||
following,
|
||||
"activity.slateId",
|
||||
subscriptions,
|
||||
])
|
||||
// .where("activity.createdAt", ">", date.toISOString())
|
||||
// .where("activity.type", "CREATE_SLATE_OBJECT")
|
||||
// .whereIn("activity.ownerId", following)
|
||||
// .orWhereIn("activity.slateId", subscriptions)
|
||||
.orderBy("activity.createdAt", "desc")
|
||||
.limit(100);
|
||||
.limit(96);
|
||||
} else {
|
||||
//NOTE(martina): for the first fetch they make, when they have not loaded any explore events yet
|
||||
query = await DB.select(
|
||||
"activity.id",
|
||||
"activity.type",
|
||||
"activity.createdAt",
|
||||
users(),
|
||||
slates(),
|
||||
"activity.slateId",
|
||||
// users(),
|
||||
// slates(),
|
||||
files()
|
||||
)
|
||||
.from("activity")
|
||||
.join("users", "users.id", "=", "activity.ownerId")
|
||||
// .join("users", "users.id", "=", "activity.ownerId")
|
||||
.leftJoin("files", "files.id", "=", "activity.fileId")
|
||||
.leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
.where("activity.type", "CREATE_SLATE_OBJECT")
|
||||
.whereIn("activity.ownerId", following)
|
||||
.orWhereIn("activity.slateId", subscriptions)
|
||||
// .leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
.whereRaw("?? = ? and (?? = any(?) or ?? = any(?))", [
|
||||
"activity.type",
|
||||
"CREATE_SLATE_OBJECT",
|
||||
"activity.ownerId",
|
||||
following,
|
||||
"activity.slateId",
|
||||
subscriptions,
|
||||
])
|
||||
// .where("activity.type", "CREATE_SLATE_OBJECT")
|
||||
// .whereIn("activity.ownerId", following)
|
||||
// .orWhereIn("activity.slateId", subscriptions)
|
||||
.orderBy("activity.createdAt", "desc")
|
||||
.limit(100);
|
||||
.limit(96);
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
|
@ -23,18 +23,19 @@ export default async ({ earliestTimestamp, latestTimestamp }) => {
|
||||
"activity.id",
|
||||
"activity.type",
|
||||
"activity.createdAt",
|
||||
users(),
|
||||
slates(),
|
||||
"activity.slateId",
|
||||
// users(),
|
||||
// slates(),
|
||||
files()
|
||||
)
|
||||
.from("activity")
|
||||
.join("users", "users.id", "=", "activity.ownerId")
|
||||
// .join("users", "users.id", "=", "activity.ownerId")
|
||||
.leftJoin("files", "files.id", "=", "activity.fileId")
|
||||
.leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
// .leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
.where("activity.createdAt", "<", date.toISOString())
|
||||
.where("activity.type", "CREATE_SLATE_OBJECT")
|
||||
.orderBy("activity.createdAt", "desc")
|
||||
.limit(100);
|
||||
.limit(96);
|
||||
} else if (latestTimestamp) {
|
||||
//NOTE(martina): for fetching new updates since the last time they loaded
|
||||
let date = new Date(latestTimestamp);
|
||||
@ -43,35 +44,37 @@ export default async ({ earliestTimestamp, latestTimestamp }) => {
|
||||
"activity.id",
|
||||
"activity.type",
|
||||
"activity.createdAt",
|
||||
users(),
|
||||
slates(),
|
||||
"activity.slateId",
|
||||
// users(),
|
||||
// slates(),
|
||||
files()
|
||||
)
|
||||
.from("activity")
|
||||
.join("users", "users.id", "=", "activity.ownerId")
|
||||
// .join("users", "users.id", "=", "activity.ownerId")
|
||||
.leftJoin("files", "files.id", "=", "activity.fileId")
|
||||
.leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
// .leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
.where("activity.createdAt", ">", date.toISOString())
|
||||
.where("activity.type", "CREATE_SLATE_OBJECT")
|
||||
.orderBy("activity.createdAt", "desc")
|
||||
.limit(100);
|
||||
.limit(96);
|
||||
} else {
|
||||
//NOTE(martina): for the first fetch they make, when they have not loaded any explore events yet
|
||||
query = await DB.select(
|
||||
"activity.id",
|
||||
"activity.type",
|
||||
"activity.createdAt",
|
||||
users(),
|
||||
slates(),
|
||||
"activity.slateId",
|
||||
// users(),
|
||||
// slates(),
|
||||
files()
|
||||
)
|
||||
.from("activity")
|
||||
.join("users", "users.id", "=", "activity.ownerId")
|
||||
// .join("users", "users.id", "=", "activity.ownerId")
|
||||
.leftJoin("files", "files.id", "=", "activity.fileId")
|
||||
.leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
// .leftJoin("slates", "slates.id", "=", "activity.slateId")
|
||||
.where("activity.type", "CREATE_SLATE_OBJECT")
|
||||
.orderBy("activity.createdAt", "desc")
|
||||
.limit(100);
|
||||
.limit(96);
|
||||
}
|
||||
|
||||
if (!query || query.error) {
|
||||
|
@ -20,10 +20,18 @@ export default async ({ ids, sanitize = false, publicOnly = false }) => {
|
||||
.from("files")
|
||||
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
|
||||
.leftJoin("slates", "slates.id", "=", "slate_files.slateId")
|
||||
.whereIn("files.id", ids)
|
||||
.where("files.isPublic", true)
|
||||
.orWhereIn("files.id", ids)
|
||||
.andWhere("slates.isPublic", true)
|
||||
.whereRaw("?? = any(?) and (?? = ? or ?? = ?)", [
|
||||
"files.id",
|
||||
ids,
|
||||
"files.isPublic",
|
||||
true,
|
||||
"slates.isPublic",
|
||||
true,
|
||||
])
|
||||
// .whereIn("files.id", ids)
|
||||
// .where("files.isPublic", true)
|
||||
// .orWhereIn("files.id", ids)
|
||||
// .andWhere("slates.isPublic", true)
|
||||
.groupBy("files.id");
|
||||
} else {
|
||||
query = await DB.select("*").from("files").whereIn("id", ids);
|
||||
|
@ -20,8 +20,16 @@ export default async ({ id, sanitize = false, publicOnly = false }) => {
|
||||
.from("files")
|
||||
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
|
||||
.leftJoin("slates", "slate_files.slateId", "=", "slates.id")
|
||||
.where({ "files.ownerId": id, "slates.isPublic": true })
|
||||
.orWhere({ "files.ownerId": id, "files.isPublic": true })
|
||||
.whereRaw("?? = ? and (?? = ? or ?? = ?)", [
|
||||
"files.ownerId",
|
||||
id,
|
||||
"files.isPublic",
|
||||
true,
|
||||
"slates.isPublic",
|
||||
true,
|
||||
])
|
||||
// .where({ "files.ownerId": id, "slates.isPublic": true })
|
||||
// .orWhere({ "files.ownerId": id, "files.isPublic": true })
|
||||
.orderBy("files.createdAt", "desc")
|
||||
.groupBy("files.id");
|
||||
} else {
|
||||
|
@ -53,8 +53,16 @@ export default async ({ id, sanitize = false, includeFiles = false, publicOnly =
|
||||
.from("files")
|
||||
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
|
||||
.leftJoin("slates", "slate_files.slateId", "=", "slates.id")
|
||||
.where({ "files.ownerId": id, "slates.isPublic": true })
|
||||
.orWhere({ "files.ownerId": id, "files.isPublic": true })
|
||||
.whereRaw("?? = ? and (?? = ? or ?? = ?)", [
|
||||
"files.ownerId",
|
||||
id,
|
||||
"files.isPublic",
|
||||
true,
|
||||
"slates.isPublic",
|
||||
true,
|
||||
])
|
||||
// .where({ "files.ownerId": id, "slates.isPublic": true })
|
||||
// .orWhere({ "files.ownerId": id, "files.isPublic": true })
|
||||
.orderBy("files.createdAt", "desc")
|
||||
.groupBy("files.id");
|
||||
|
||||
|
@ -64,25 +64,35 @@ export default async ({ username, sanitize = false, includeFiles = false, public
|
||||
// .first();
|
||||
query = await DB.select("*").from("users").where({ username }).first();
|
||||
|
||||
const id = query.id;
|
||||
const id = query?.id;
|
||||
|
||||
let library = await DB.select(
|
||||
"files.id",
|
||||
"files.ownerId",
|
||||
"files.cid",
|
||||
"files.isPublic",
|
||||
"files.filename",
|
||||
"files.data"
|
||||
)
|
||||
.from("files")
|
||||
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
|
||||
.leftJoin("slates", "slate_files.slateId", "=", "slates.id")
|
||||
.where({ "files.ownerId": id, "slates.isPublic": true })
|
||||
.orWhere({ "files.ownerId": id, "files.isPublic": true })
|
||||
.orderBy("files.createdAt", "desc")
|
||||
.groupBy("files.id");
|
||||
if (id) {
|
||||
let library = await DB.select(
|
||||
"files.id",
|
||||
"files.ownerId",
|
||||
"files.cid",
|
||||
"files.isPublic",
|
||||
"files.filename",
|
||||
"files.data"
|
||||
)
|
||||
.from("files")
|
||||
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
|
||||
.leftJoin("slates", "slate_files.slateId", "=", "slates.id")
|
||||
// .where({ "files.ownerId": id, "slates.isPublic": true })
|
||||
// .orWhere({ "files.ownerId": id, "files.isPublic": true })
|
||||
.whereRaw("?? = ? and (?? = ? or ?? = ?)", [
|
||||
"files.ownerId",
|
||||
id,
|
||||
"files.isPublic",
|
||||
true,
|
||||
"slates.isPublic",
|
||||
true,
|
||||
])
|
||||
.orderBy("files.createdAt", "desc")
|
||||
.groupBy("files.id");
|
||||
|
||||
query.library = library;
|
||||
query.library = library;
|
||||
}
|
||||
} else {
|
||||
query = await DB.select(
|
||||
"users.id",
|
||||
|
@ -221,7 +221,7 @@ export const getById = async ({ id }) => {
|
||||
pdfBytes,
|
||||
},
|
||||
tags,
|
||||
userBucketCID: bucketRoot?.path,
|
||||
userBucketCID: bucketRoot?.path || null,
|
||||
keys,
|
||||
slates,
|
||||
subscriptions,
|
||||
|
@ -6,20 +6,19 @@ import { IncomingWebhook } from "@slack/webhook";
|
||||
const url = `https://hooks.slack.com/services/${Environment.SUPPORT_SLACK_WEBHOOK_KEY}`;
|
||||
const webhook = new IncomingWebhook(url);
|
||||
|
||||
export const sendSlackMessage = ({
|
||||
username,
|
||||
name,
|
||||
email,
|
||||
twitter,
|
||||
message,
|
||||
stored,
|
||||
}) => {
|
||||
export const sendSlackMessage = ({ username, name, email, twitter, message, stored }) => {
|
||||
if (Strings.isEmpty(Environment.SUPPORT_SLACK_WEBHOOK_KEY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userProfileURL = `https://slate.host/${username}`;
|
||||
const userURL = `<${userProfileURL}|${username}>`;
|
||||
let userURL;
|
||||
if (username) {
|
||||
const userProfileURL = `https://slate.host/${username}`;
|
||||
userURL = `<${userProfileURL}|${username}>`;
|
||||
} else {
|
||||
userURL = "[Not authenticated]";
|
||||
}
|
||||
|
||||
let twitterURL = "";
|
||||
if (twitter) {
|
||||
const twitterProfileURL = `https://twitter.com/${twitter.replace("@", "")}`;
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -4556,7 +4556,11 @@
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
|
||||
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
|
||||
<<<<<<< HEAD
|
||||
"deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.",
|
||||
=======
|
||||
"deprecated": "core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.",
|
||||
>>>>>>> added standard typography styles
|
||||
"hasInstallScript": true
|
||||
},
|
||||
"node_modules/core-js-compat": {
|
||||
|
@ -3,13 +3,18 @@ import * as React from "react";
|
||||
import Application from "~/components/core/Application";
|
||||
|
||||
export const getServerSideProps = async ({ query }) => {
|
||||
// return {
|
||||
// props: {
|
||||
// viewer: query.viewer,
|
||||
// isMobile: query.isMobile,
|
||||
// isMac: query.isMac,
|
||||
// resources: query.resources,
|
||||
// page: query.page,
|
||||
// data: query.data,
|
||||
// },
|
||||
// };
|
||||
return {
|
||||
props: {
|
||||
viewer: query.viewer,
|
||||
isMobile: query.isMobile,
|
||||
isMac: query.isMac,
|
||||
resources: query.resources,
|
||||
},
|
||||
props: { ...query },
|
||||
};
|
||||
};
|
||||
|
||||
@ -17,10 +22,13 @@ export default class ApplicationPage extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Application
|
||||
viewer={this.props.viewer}
|
||||
isMobile={this.props.isMobile}
|
||||
isMac={this.props.isMac}
|
||||
resources={this.props.resources}
|
||||
{...this.props}
|
||||
// viewer={this.props.viewer}
|
||||
// isMobile={this.props.isMobile}
|
||||
// isMac={this.props.isMac}
|
||||
// resources={this.props.resources}
|
||||
// page={this.props.page}
|
||||
// data={this.props.data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import WebsitePrototypeHeader from "~/components/core/WebsitePrototypeHeader";
|
||||
import WebsitePrototypeFooter from "~/components/core/WebsitePrototypeFooter";
|
||||
import CTATransition from "~/components/core/CTATransition";
|
||||
|
||||
const SIZE_LIMIT = 1000000; //NOTE(martina): 1mb limit for twitter preview images
|
||||
const DEFAULT_IMAGE =
|
||||
"https://slate.textile.io/ipfs/bafkreiaow45dlq5xaydaeqocdxvffudibrzh2c6qandpqkb6t3ahbvh6re";
|
||||
const DEFAULT_BOOK =
|
||||
@ -233,7 +232,7 @@ export default class SlatePage extends React.Component {
|
||||
objects[i].data.type &&
|
||||
Validations.isPreviewableImage(objects[i].data.type) &&
|
||||
objects[i].data.size &&
|
||||
objects[i].data.size < SIZE_LIMIT
|
||||
objects[i].data.size < Constants.linkPreviewSizeLimit
|
||||
) {
|
||||
image = Strings.getURLfromCID(objects[i].cid);
|
||||
break;
|
||||
@ -320,7 +319,7 @@ export default class SlatePage extends React.Component {
|
||||
</div>
|
||||
<div css={STYLES_SLATE}>
|
||||
<GlobalCarousel
|
||||
current={this.props.slate}
|
||||
data={this.props.slate}
|
||||
carouselType="SLATE"
|
||||
viewer={this.props.viewer}
|
||||
objects={objects}
|
@ -60,5 +60,5 @@ export default async (req, res) => {
|
||||
return res.status(400).send({ decorator: "SERVER_GET_ACTIVITY_NOT_FOUND", error: true });
|
||||
}
|
||||
|
||||
return res.status(200).send({ decorator: "SERVER_GET_ACTIVITY", activity: response });
|
||||
return res.status(200).send({ decorator: "SERVER_GET_ACTIVITY", data: response });
|
||||
};
|
||||
|
@ -29,5 +29,5 @@ export default async (req, res) => {
|
||||
return res.status(400).send({ decorator: "SERVER_GET_EXPLORE_NOT_FOUND", error: true });
|
||||
}
|
||||
|
||||
return res.status(200).send({ decorator: "SERVER_GET_EXPLORE", explore: response });
|
||||
return res.status(200).send({ decorator: "SERVER_GET_EXPLORE", data: response });
|
||||
};
|
||||
|
@ -3,6 +3,8 @@ import * as Utilities from "~/node_common/utilities";
|
||||
|
||||
export default async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
console.log(id);
|
||||
console.log(req);
|
||||
if (!id) {
|
||||
return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true });
|
||||
}
|
||||
|
@ -59,7 +59,4 @@ export default async (req, res) => {
|
||||
const token = JWT.sign({ id: user.id, username: user.username }, Environment.JWT_SECRET);
|
||||
|
||||
res.status(200).send({ decorator: "SERVER_SIGN_IN", success: true, token });
|
||||
if (req.body.data.redirectURL) {
|
||||
res.redirect(req.body.data.redirectURL);
|
||||
}
|
||||
};
|
||||
|
@ -4,29 +4,6 @@ import * as Utilities from "~/node_common/utilities";
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
export default async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
if (!id) {
|
||||
return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true });
|
||||
}
|
||||
|
||||
const user = await Data.getUserById({
|
||||
id,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).send({
|
||||
decorator: "SERVER_USER_NOT_FOUND",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (user.error) {
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_USER_NOT_FOUND",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
|
||||
let slate;
|
||||
if (req.body.data.id) {
|
||||
slate = await Data.getSlateById({ id: req.body.data.id, includeFiles: true, sanitize: true });
|
||||
@ -46,11 +23,15 @@ export default async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (!slate.isPublic && slate.ownerId !== id) {
|
||||
return res.status(403).send({
|
||||
decorator: "SERVER_GET_SERIALIZED_SLATE_PRIVATE_ACCESS_DENIED",
|
||||
error: true,
|
||||
});
|
||||
if (!slate.isPublic) {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
|
||||
if (slate.ownerId !== id) {
|
||||
return res.status(403).send({
|
||||
decorator: "SERVER_GET_SERIALIZED_SLATE_PRIVATE_ACCESS_DENIED",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let owner = await Data.getUserById({ id: slate.ownerId, sanitize: true });
|
||||
@ -66,6 +47,6 @@ export default async (req, res) => {
|
||||
|
||||
return res.status(200).send({
|
||||
decorator: "SERVER_GET_SERIALIZED_SLATE",
|
||||
slate,
|
||||
data: slate,
|
||||
});
|
||||
};
|
||||
|
@ -4,29 +4,6 @@ import * as Serializers from "~/node_common/serializers";
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
export default async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
if (!id) {
|
||||
return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true });
|
||||
}
|
||||
|
||||
const user = await Data.getUserById({
|
||||
id,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).send({
|
||||
decorator: "SERVER_USER_NOT_FOUND",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (user.error) {
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_USER_NOT_FOUND",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
|
||||
const response = await Data.getSlateById({
|
||||
id: req.body.data.id,
|
||||
includeFiles: true,
|
||||
@ -41,11 +18,15 @@ export default async (req, res) => {
|
||||
return res.status(500).send({ decorator: "SERVER_GET_SLATE_NOT_FOUND", error: true });
|
||||
}
|
||||
|
||||
if (!response.isPublic && response.ownerId !== id) {
|
||||
return res.status(403).send({
|
||||
decorator: "SERVER_GET_SLATE_PRIVATE_ACCESS_DENIED",
|
||||
error: true,
|
||||
});
|
||||
if (!response.isPublic) {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
|
||||
if (!ownerId || response.ownerId !== id) {
|
||||
return res.status(403).send({
|
||||
decorator: "SERVER_GET_SLATE_PRIVATE_ACCESS_DENIED",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).send({ decorator: "SERVER_GET_SLATE", slate: response });
|
||||
|
@ -4,22 +4,22 @@ import * as Serializers from "~/node_common/serializers";
|
||||
import * as Support from "~/node_common/support";
|
||||
|
||||
export default async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
if (!id) {
|
||||
return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true });
|
||||
}
|
||||
// const id = Utilities.getIdFromCookie(req);
|
||||
// if (!id) {
|
||||
// return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true });
|
||||
// }
|
||||
|
||||
const user = await Data.getUserById({
|
||||
id,
|
||||
});
|
||||
// const user = await Data.getUserById({
|
||||
// id,
|
||||
// });
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).send({ decorator: "SERVER_USER_NOT_FOUND", error: true });
|
||||
}
|
||||
// if (!user) {
|
||||
// return res.status(404).send({ decorator: "SERVER_USER_NOT_FOUND", error: true });
|
||||
// }
|
||||
|
||||
if (user.error) {
|
||||
return res.status(500).send({ decorator: "SERVER_USER_NOT_FOUND", error: true });
|
||||
}
|
||||
// if (user.error) {
|
||||
// return res.status(500).send({ decorator: "SERVER_USER_NOT_FOUND", error: true });
|
||||
// }
|
||||
|
||||
if (!req.body.data) {
|
||||
return res.status(500).send({
|
||||
@ -42,10 +42,6 @@ export default async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (!req.body.data.username) {
|
||||
req.body.data.username = user.username;
|
||||
}
|
||||
|
||||
let status = await Support.sendSlackMessage(req.body.data);
|
||||
if (status) {
|
||||
return res.status(200).send({
|
||||
|
@ -3,10 +3,6 @@ import * as Strings from "~/common/strings";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
|
||||
export default async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
if (!id) {
|
||||
return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true });
|
||||
}
|
||||
let user;
|
||||
|
||||
if (req.body.data.id) {
|
||||
|
@ -10,6 +10,7 @@ import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
|
||||
import { css } from "@emotion/react";
|
||||
import { TabGroup, PrimaryTabGroup, SecondaryTabGroup } from "~/components/core/TabGroup";
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
import EmptyState from "~/components/core/EmptyState";
|
||||
import ScenePage from "~/components/core/ScenePage";
|
||||
@ -64,15 +65,6 @@ const STYLES_SECONDARY = css`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const STYLES_SECONDARY_HOVERABLE = css`
|
||||
${STYLES_SECONDARY}
|
||||
padding: 8px 16px;
|
||||
|
||||
:hover {
|
||||
color: ${Constants.system.brand} !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_GRADIENT = css`
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
@ -112,8 +104,8 @@ class ActivitySquare extends React.Component {
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
const size = this.props.size;
|
||||
const isImage =
|
||||
Validations.isPreviewableImage(item.file.data.type) || !!item.file.data.coverImage;
|
||||
// const isImage =
|
||||
// Validations.isPreviewableImage(item.file.data.type) || !!item.file.data.coverImage;
|
||||
return (
|
||||
<div
|
||||
css={STYLES_IMAGE_BOX}
|
||||
@ -128,42 +120,30 @@ class ActivitySquare extends React.Component {
|
||||
style={{ border: "none" }}
|
||||
imageStyle={{ border: "none" }}
|
||||
/>
|
||||
{this.state.showText || this.props.isMobile ? <div css={STYLES_GRADIENT} /> : null}
|
||||
{this.state.showText || this.props.isMobile ? (
|
||||
<div css={STYLES_TEXT_AREA} style={{ width: this.props.size }}>
|
||||
{/* {isImage ? null : (
|
||||
<div
|
||||
css={STYLES_TITLE}
|
||||
style={{
|
||||
color: Constants.system.textGray,
|
||||
}}
|
||||
>
|
||||
{item.file.data.name || item.file.filename}
|
||||
</div>
|
||||
)} */}
|
||||
<span
|
||||
style={{
|
||||
color: Constants.system.white,
|
||||
}}
|
||||
css={this.props.onClick ? STYLES_SECONDARY_HOVERABLE : STYLES_SECONDARY}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.props.onClick();
|
||||
}}
|
||||
>
|
||||
<SVG.ArrowDownLeft
|
||||
height="10px"
|
||||
style={{ transform: "scaleX(-1)", marginRight: 4 }}
|
||||
/>
|
||||
{item.slate.data.name || item.slate.slatename}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// {this.state.showText || this.props.isMobile ? <div css={STYLES_GRADIENT} /> : null}
|
||||
// {this.state.showText || this.props.isMobile ? (
|
||||
// <div css={STYLES_TEXT_AREA} style={{ width: this.props.size }}>
|
||||
// <span
|
||||
// style={{
|
||||
// color: Constants.system.white,
|
||||
// padding: "8px 16px",
|
||||
// }}
|
||||
// css={STYLES_SECONDARY}
|
||||
// >
|
||||
// <SVG.ArrowDownLeft
|
||||
// height="10px"
|
||||
// style={{ transform: "scaleX(-1)", marginRight: 4 }}
|
||||
// />
|
||||
// {item.slate.data.name || item.slate.slatename}
|
||||
// </span>
|
||||
// </div>
|
||||
// ) : null}
|
||||
|
||||
const ActivityRectangle = ({ item, width, height }) => {
|
||||
let file;
|
||||
for (let obj of item.slate?.objects || []) {
|
||||
@ -212,21 +192,21 @@ export default class SceneActivity extends React.Component {
|
||||
counter = 0;
|
||||
state = {
|
||||
imageSize: 200,
|
||||
tab: 0,
|
||||
loading: "loading",
|
||||
loading: false,
|
||||
carouselIndex: -1,
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
this.fetchActivityItems(true);
|
||||
this.calculateWidth();
|
||||
this.debounceInstance = Window.debounce(this.calculateWidth, 200);
|
||||
this.scrollDebounceInstance = Window.debounce(this._handleScroll, 200);
|
||||
window.addEventListener("resize", this.debounceInstance);
|
||||
window.addEventListener("scroll", this.scrollDebounceInstance);
|
||||
this.fetchActivityItems(true);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.tab !== this.props.tab) {
|
||||
if (prevProps.page.params?.tab !== this.props.page.params?.tab) {
|
||||
this.fetchActivityItems(true);
|
||||
}
|
||||
}
|
||||
@ -258,10 +238,23 @@ export default class SceneActivity extends React.Component {
|
||||
};
|
||||
|
||||
fetchActivityItems = async (update = false) => {
|
||||
const isExplore = this.props.tab === 1;
|
||||
if (this.state.loading === "loading") return;
|
||||
let tab = this.props.page.params?.tab;
|
||||
if (!tab) {
|
||||
if (this.props.viewer) {
|
||||
tab = "activity";
|
||||
} else {
|
||||
tab = "explore";
|
||||
}
|
||||
}
|
||||
const isExplore = tab === "explore";
|
||||
this.setState({ loading: "loading" });
|
||||
let activity = isExplore ? this.props.viewer.explore || [] : this.props.viewer.activity || [];
|
||||
|
||||
let activity;
|
||||
if (this.props.viewer) {
|
||||
activity = isExplore ? this.props.viewer?.explore || [] : this.props.viewer?.activity || [];
|
||||
} else {
|
||||
activity = this.state.explore || [];
|
||||
}
|
||||
let requestObject = {};
|
||||
if (activity.length) {
|
||||
if (update) {
|
||||
@ -271,6 +264,7 @@ export default class SceneActivity extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
console.log("start fetching");
|
||||
let response;
|
||||
if (isExplore) {
|
||||
response = await Actions.getExplore(requestObject);
|
||||
@ -280,13 +274,13 @@ export default class SceneActivity extends React.Component {
|
||||
|
||||
response = await Actions.getActivity(requestObject);
|
||||
}
|
||||
|
||||
console.log("finished fetching");
|
||||
if (Events.hasError(response)) {
|
||||
this.setState({ loading: "failed" });
|
||||
this.setState({ loading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
let newItems = response.activity || response.explore;
|
||||
let newItems = response.data;
|
||||
|
||||
if (update) {
|
||||
activity.unshift(...newItems);
|
||||
@ -297,21 +291,26 @@ export default class SceneActivity extends React.Component {
|
||||
activity.push(...newItems);
|
||||
}
|
||||
|
||||
if (this.props.tab === 0) {
|
||||
this.props.onUpdateViewer({ activity: activity });
|
||||
if (this.props.viewer) {
|
||||
if (!isExplore) {
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { activity: activity } });
|
||||
} else {
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { explore: activity } });
|
||||
}
|
||||
this.setState({ loading: false });
|
||||
} else {
|
||||
this.props.onUpdateViewer({ explore: activity });
|
||||
this.setState({ explore: activity, loading: false });
|
||||
}
|
||||
this.setState({ loading: false });
|
||||
};
|
||||
|
||||
formatActivity = (userActivity) => {
|
||||
let activity = [];
|
||||
for (let item of userActivity) {
|
||||
if (item.slate && !item.slate.isPublic) {
|
||||
continue;
|
||||
}
|
||||
if (item.type === "CREATE_SLATE_OBJECT" && item.slate && item.file) {
|
||||
// if (item.slate && !item.slate.isPublic) {
|
||||
// continue;
|
||||
// }
|
||||
if (item.type === "CREATE_SLATE_OBJECT") {
|
||||
//&& item.slate && item.file
|
||||
activity.push(item);
|
||||
} else if (item.type === "CREATE_SLATE" && item.slate) {
|
||||
activity.push(item);
|
||||
@ -345,14 +344,6 @@ export default class SceneActivity extends React.Component {
|
||||
// return activity;
|
||||
};
|
||||
|
||||
_handleCreateSlate = () => {
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_SLATES",
|
||||
data: null,
|
||||
});
|
||||
};
|
||||
|
||||
calculateWidth = () => {
|
||||
let windowWidth = window.innerWidth;
|
||||
let imageSize;
|
||||
@ -370,83 +361,92 @@ export default class SceneActivity extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
let activity =
|
||||
this.props.tab === 0 ? this.props.viewer.activity || [] : this.props.viewer.explore || [];
|
||||
let tab = this.props.page.params?.tab;
|
||||
if (!tab) {
|
||||
if (this.props.viewer) {
|
||||
tab = "activity";
|
||||
} else {
|
||||
tab = "explore";
|
||||
}
|
||||
}
|
||||
let activity;
|
||||
console.log(this.props.viewer);
|
||||
if (this.props.viewer) {
|
||||
activity =
|
||||
tab === "activity" ? this.props.viewer?.activity || [] : this.props.viewer?.explore || [];
|
||||
} else {
|
||||
activity = this.state.explore || [];
|
||||
}
|
||||
console.log(activity);
|
||||
let items = activity
|
||||
.filter((item) => item.type === "CREATE_SLATE_OBJECT")
|
||||
.map((item) => {
|
||||
return {
|
||||
...item.file,
|
||||
slate: item.slate,
|
||||
owner: item.owner?.username,
|
||||
slateId: item.slateId,
|
||||
// slate: item.slate,
|
||||
// owner: item.owner?.username,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<ScenePage>
|
||||
<ScenePageHeader
|
||||
title={
|
||||
this.props.isMobile ? (
|
||||
<TabGroup
|
||||
tabs={[
|
||||
{ title: "Files", value: "NAV_DATA" },
|
||||
{ title: "Collections", value: "NAV_SLATES" },
|
||||
{ title: "Activity", value: "NAV_ACTIVITY" },
|
||||
]}
|
||||
value={2}
|
||||
onAction={this.props.onAction}
|
||||
onChange={(value) => this.setState({ tab: value })}
|
||||
style={{ marginTop: 0, marginBottom: 32 }}
|
||||
itemStyle={{ margin: "0px 12px" }}
|
||||
/>
|
||||
) : (
|
||||
<PrimaryTabGroup
|
||||
tabs={[
|
||||
{ title: "Files", value: "NAV_DATA" },
|
||||
{ title: "Collections", value: "NAV_SLATES" },
|
||||
{ title: "Activity", value: "NAV_ACTIVITY" },
|
||||
]}
|
||||
value={2}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
actions={
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
{ title: "My network", value: "NAV_ACTIVITY" },
|
||||
{ title: "Explore", value: "NAV_EXPLORE" },
|
||||
]}
|
||||
value={this.props.tab}
|
||||
onAction={this.props.onAction}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{this.props.viewer && (
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
{ title: "My network", value: { tab: "activity" } },
|
||||
{ title: "Explore", value: { tab: "explore" } },
|
||||
]}
|
||||
value={tab}
|
||||
onAction={this.props.onAction}
|
||||
style={{ marginTop: 0 }}
|
||||
/>
|
||||
)}
|
||||
<GlobalCarousel
|
||||
carouselType="ACTIVITY"
|
||||
viewer={this.props.viewer}
|
||||
objects={items}
|
||||
onAction={this.props.onAction}
|
||||
index={this.state.carouselIndex}
|
||||
onChange={(index) => {
|
||||
if (index >= items.length - 4) {
|
||||
this.fetchActivityItems();
|
||||
}
|
||||
this.setState({ carouselIndex: index });
|
||||
}}
|
||||
isMobile={this.props.isMobile}
|
||||
// params={this.props.page.params}
|
||||
isOwner={false}
|
||||
/>
|
||||
{activity.length ? (
|
||||
<div>
|
||||
<div css={STYLES_ACTIVITY_GRID}>
|
||||
{activity.map((item, index) => {
|
||||
{activity.map((item, i) => {
|
||||
if (item.type === "CREATE_SLATE") {
|
||||
return (
|
||||
<span
|
||||
<Link
|
||||
redirect
|
||||
key={i}
|
||||
disabled={this.props.isMobile ? false : true}
|
||||
// params={
|
||||
// this.props.isMobile
|
||||
// ? null
|
||||
// : { ...this.props.page.params, cid: item.file.cid }
|
||||
// }
|
||||
href={`/$/slate/${item.slateId}`}
|
||||
onAction={this.props.onAction}
|
||||
onClick={() => this.setState({ carouselIndex: i })}
|
||||
>
|
||||
{/* <span
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_SLATE",
|
||||
data: { decorator: "SLATE", ...item.slate },
|
||||
data: item.slate,
|
||||
})
|
||||
}
|
||||
>
|
||||
> */}
|
||||
<ActivityRectangle
|
||||
width={
|
||||
this.props.isMobile ? this.state.imageSize : this.state.imageSize * 2 + 20
|
||||
@ -454,51 +454,40 @@ export default class SceneActivity extends React.Component {
|
||||
height={this.state.imageSize}
|
||||
item={item}
|
||||
/>
|
||||
</span>
|
||||
{/* </span> */}
|
||||
</Link>
|
||||
);
|
||||
} else if (item.type === "CREATE_SLATE_OBJECT") {
|
||||
return (
|
||||
<span
|
||||
key={item.id}
|
||||
onClick={
|
||||
this.props.isMobile
|
||||
? () => {
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_SLATE",
|
||||
data: {
|
||||
decorator: "SLATE",
|
||||
...item.slate,
|
||||
},
|
||||
});
|
||||
}
|
||||
: () =>
|
||||
Events.dispatchCustomEvent({
|
||||
name: "slate-global-open-carousel",
|
||||
detail: { index: this.getItemIndexById(items, item) },
|
||||
})
|
||||
}
|
||||
<Link
|
||||
redirect
|
||||
key={i}
|
||||
disabled={this.props.isMobile ? false : true}
|
||||
// params={
|
||||
// this.props.isMobile
|
||||
// ? null
|
||||
// : { ...this.props.page.params, cid: item.file.cid }
|
||||
// }
|
||||
href={`/$/slate/${item.slateId}?cid=${item.file.cid}`}
|
||||
onAction={this.props.onAction}
|
||||
onClick={() => this.setState({ carouselIndex: i })}
|
||||
// onClick={
|
||||
// this.props.isMobile
|
||||
// ? () => {}
|
||||
// : () =>
|
||||
// Events.dispatchCustomEvent({
|
||||
// name: "slate-global-open-carousel",
|
||||
// detail: { index: this.getItemIndexById(items, item) },
|
||||
// })
|
||||
// }
|
||||
>
|
||||
<ActivitySquare
|
||||
size={this.state.imageSize}
|
||||
item={item}
|
||||
isMobile={this.props.isMobile}
|
||||
onClick={
|
||||
this.props.isMobile
|
||||
? () => {}
|
||||
: () => {
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: "NAV_SLATE",
|
||||
data: {
|
||||
decorator: "SLATE",
|
||||
...item.slate,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
|
@ -30,7 +30,6 @@ export default class SceneArchive extends React.Component {
|
||||
state = {
|
||||
deals: [],
|
||||
dealsLoaded: false,
|
||||
tab: 0,
|
||||
networkViewer: null,
|
||||
allow_filecoin_directory_listing: this.props.viewer.data.settings
|
||||
?.allow_filecoin_directory_listing,
|
||||
@ -114,6 +113,7 @@ export default class SceneArchive extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let tab = this.props.page.params?.tab || "archive";
|
||||
return (
|
||||
<ScenePage>
|
||||
<ScenePageHeader title="Filecoin">
|
||||
@ -122,14 +122,19 @@ export default class SceneArchive extends React.Component {
|
||||
</ScenePageHeader>
|
||||
|
||||
<SecondaryTabGroup
|
||||
tabs={["Archive Settings", "Wallet", "API", "Miners"]}
|
||||
value={this.state.tab}
|
||||
onChange={(value) => this.setState({ tab: value })}
|
||||
tabs={[
|
||||
{ title: "Archive Settings", value: { tab: "archive" } },
|
||||
{ title: "Wallet", value: { tab: "wallet" } },
|
||||
{ title: "API", value: { tab: "api" } },
|
||||
{ title: "Miners", value: { tab: "miners" } },
|
||||
]}
|
||||
value={tab}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
|
||||
{this.state.networkViewer ? (
|
||||
<React.Fragment>
|
||||
{this.state.tab === 0 ? (
|
||||
{tab === "archive" ? (
|
||||
<React.Fragment>
|
||||
<ScenePageHeader>
|
||||
Use this section to archive all of your data on to Filecoin through a storage
|
||||
@ -201,7 +206,7 @@ export default class SceneArchive extends React.Component {
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
|
||||
{this.state.tab === 1 ? (
|
||||
{tab === "wallet" ? (
|
||||
<React.Fragment>
|
||||
<SceneWallet {...this.props} networkViewer={this.state.networkViewer} />
|
||||
<br />
|
||||
@ -216,7 +221,7 @@ export default class SceneArchive extends React.Component {
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
|
||||
{this.state.tab === 2 ? (
|
||||
{tab === "api" ? (
|
||||
<React.Fragment>
|
||||
{this.state.routes ? (
|
||||
<SceneSentinel routes={this.state.routes} />
|
||||
@ -228,7 +233,7 @@ export default class SceneArchive extends React.Component {
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
|
||||
{this.state.tab === 3 ? (
|
||||
{tab === "miners" ? (
|
||||
<React.Fragment>
|
||||
{this.state.miners ? (
|
||||
<SceneMiners miners={this.state.miners} />
|
||||
|
@ -7,7 +7,7 @@ import { css } from "@emotion/react";
|
||||
import { SecondaryTabGroup } from "~/components/core/TabGroup";
|
||||
import { Boundary } from "~/components/system/components/fragments/Boundary";
|
||||
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
|
||||
import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
import ScenePage from "~/components/core/ScenePage";
|
||||
import ScenePageHeader from "~/components/core/ScenePageHeader";
|
||||
@ -211,10 +211,12 @@ export default class SceneDirectory extends React.Component {
|
||||
right: "0px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: "Unfollow",
|
||||
onClick: (e) => this._handleFollow(e, relation.id),
|
||||
},
|
||||
[
|
||||
{
|
||||
text: "Unfollow",
|
||||
onClick: (e) => this._handleFollow(e, relation.id),
|
||||
},
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</Boundary>
|
||||
@ -222,20 +224,22 @@ export default class SceneDirectory extends React.Component {
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<UserEntry
|
||||
key={relation.id}
|
||||
user={relation}
|
||||
button={button}
|
||||
checkStatus={this.checkStatus}
|
||||
onClick={() => {
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: this.props.sceneId,
|
||||
scene: "PROFILE",
|
||||
data: relation,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Link href={`/$/user/${relation.id}`} onAction={this.props.onAction}>
|
||||
<UserEntry
|
||||
key={relation.id}
|
||||
user={relation}
|
||||
button={button}
|
||||
checkStatus={this.checkStatus}
|
||||
// onClick={() => {
|
||||
// this.props.onAction({
|
||||
// type: "NAVIGATE",
|
||||
// value: "NAV_PROFILE",
|
||||
// shallow: true,
|
||||
// data: relation,
|
||||
// });
|
||||
// }}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
@ -256,14 +260,16 @@ export default class SceneDirectory extends React.Component {
|
||||
right: "0px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: this.props.viewer.following.some((user) => {
|
||||
return user.id === relation.id;
|
||||
})
|
||||
? "Unfollow"
|
||||
: "Follow",
|
||||
onClick: (e) => this._handleFollow(e, relation.id),
|
||||
},
|
||||
[
|
||||
{
|
||||
text: this.props.viewer.following.some((user) => {
|
||||
return user.id === relation.id;
|
||||
})
|
||||
? "Unfollow"
|
||||
: "Follow",
|
||||
onClick: (e) => this._handleFollow(e, relation.id),
|
||||
},
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</Boundary>
|
||||
@ -271,35 +277,38 @@ export default class SceneDirectory extends React.Component {
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<UserEntry
|
||||
key={relation.id}
|
||||
user={relation}
|
||||
button={button}
|
||||
checkStatus={this.checkStatus}
|
||||
onClick={() => {
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: this.props.sceneId,
|
||||
scene: "PROFILE",
|
||||
data: relation,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Link href={`/$/user/${relation.id}`} onAction={this.props.onAction}>
|
||||
<UserEntry
|
||||
key={relation.id}
|
||||
user={relation}
|
||||
button={button}
|
||||
checkStatus={this.checkStatus}
|
||||
// onClick={() => {
|
||||
// this.props.onAction({
|
||||
// type: "NAVIGATE",
|
||||
// value: "NAV_PROFILE",
|
||||
// shallow: true,
|
||||
// data: relation,
|
||||
// });
|
||||
// }}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
let tab = this.props.page.params?.tab || "following";
|
||||
return (
|
||||
<ScenePage>
|
||||
<ScenePageHeader title="Directory" />
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
{ title: "Following", value: "NAV_DIRECTORY" },
|
||||
{ title: "Followers", value: "NAV_DIRECTORY_FOLLOWERS" },
|
||||
{ title: "Following", value: { tab: "following" } },
|
||||
{ title: "Followers", value: { tab: "followers" } },
|
||||
]}
|
||||
value={this.props.tab}
|
||||
value={tab}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
{this.props.tab === 0 ? (
|
||||
{tab === "following" ? (
|
||||
following && following.length ? (
|
||||
following
|
||||
) : (
|
||||
@ -310,7 +319,7 @@ export default class SceneDirectory extends React.Component {
|
||||
</EmptyState>
|
||||
)
|
||||
) : null}
|
||||
{this.props.tab === 1 ? (
|
||||
{tab === "followers" ? (
|
||||
followers && followers.length ? (
|
||||
followers
|
||||
) : (
|
||||
|
@ -56,7 +56,6 @@ export default class SceneEditAccount extends React.Component {
|
||||
changingAvatar: false,
|
||||
savingNameBio: false,
|
||||
changingFilecoin: false,
|
||||
tab: 0,
|
||||
modalShow: false,
|
||||
};
|
||||
|
||||
@ -107,7 +106,7 @@ export default class SceneEditAccount extends React.Component {
|
||||
}
|
||||
|
||||
let data = { ...this.props.viewer.data, body: this.state.body, name: this.state.name };
|
||||
this.props.onUpdateViewer({ username: this.state.username, data });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { username: this.state.username, data } });
|
||||
this.setState({ savingNameBio: true });
|
||||
|
||||
let response = await Actions.updateViewer({
|
||||
@ -160,12 +159,12 @@ export default class SceneEditAccount extends React.Component {
|
||||
}
|
||||
this.setState({ deleting: true });
|
||||
this.setState({ modalShow: false });
|
||||
|
||||
|
||||
await Window.delay(100);
|
||||
|
||||
await UserBehaviors.deleteMe({ viewer: this.props.viewer });
|
||||
window.location.replace("/_/auth");
|
||||
this.setState({ deleting: false });
|
||||
|
||||
};
|
||||
|
||||
_handleChange = (e) => {
|
||||
@ -173,16 +172,22 @@ export default class SceneEditAccount extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
let tab = this.props.page.params?.tab || "profile";
|
||||
return (
|
||||
<ScenePage>
|
||||
<ScenePageHeader title="Settings" />
|
||||
<SecondaryTabGroup
|
||||
tabs={["Profile", "Data Storage", "Security", "Account"]}
|
||||
value={this.state.tab}
|
||||
onChange={(value) => this.setState({ tab: value })}
|
||||
tabs={[
|
||||
{ title: "Profile", value: { tab: "profile" } },
|
||||
{ title: "Data Storage", value: { tab: "storage" } },
|
||||
{ title: "Security", value: { tab: "security" } },
|
||||
{ title: "Account", value: { tab: "account" } },
|
||||
]}
|
||||
value={tab}
|
||||
onAction={this.props.onAction}
|
||||
style={{ marginBottom: 48 }}
|
||||
/>
|
||||
{this.state.tab === 0 ? (
|
||||
{tab === "profile" ? (
|
||||
<div>
|
||||
<div css={STYLES_HEADER}>Your Avatar</div>
|
||||
|
||||
@ -227,7 +232,7 @@ export default class SceneEditAccount extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{this.state.tab === 1 ? (
|
||||
{tab === "storage" ? (
|
||||
<div style={{ maxWidth: 800 }}>
|
||||
<div css={STYLES_HEADER}>
|
||||
Allow Slate to make Filecoin archive storage deals on your behalf
|
||||
@ -278,7 +283,7 @@ export default class SceneEditAccount extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{this.state.tab === 2 ? (
|
||||
{tab === "security" ? (
|
||||
<div>
|
||||
<div css={STYLES_HEADER}>Change password</div>
|
||||
<div>Passwords must be a minimum of eight characters.</div>
|
||||
@ -311,7 +316,7 @@ export default class SceneEditAccount extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{this.state.tab === 3 ? (
|
||||
{tab === "account" ? (
|
||||
<div>
|
||||
<div css={STYLES_HEADER}>Change username</div>
|
||||
<div style={{ maxWidth: 800 }}>
|
||||
@ -359,19 +364,18 @@ export default class SceneEditAccount extends React.Component {
|
||||
tabIndex="-1"
|
||||
css={STYLES_COPY_INPUT}
|
||||
/>{" "}
|
||||
|
||||
{this.state.modalShow && (
|
||||
<ConfirmationModal
|
||||
type={"DELETE"}
|
||||
withValidation={true}
|
||||
matchValue={this.state.username}
|
||||
callback={this._handleDelete}
|
||||
header={`Are you sure you want to delete your account @${this.state.username}?`}
|
||||
subHeader={`You will lose all your files and collections. You can’t undo this action.`}
|
||||
inputHeader={`Please type your username to confirm`}
|
||||
inputPlaceholder={`username`}
|
||||
/>
|
||||
)}
|
||||
<ConfirmationModal
|
||||
type={"DELETE"}
|
||||
withValidation={true}
|
||||
matchValue={this.state.username}
|
||||
callback={this._handleDelete}
|
||||
header={`Are you sure you want to delete your account @${this.state.username}?`}
|
||||
subHeader={`You will lose all your files and collections. You can’t undo this action.`}
|
||||
inputHeader={`Please type your username to confirm`}
|
||||
inputPlaceholder={`username`}
|
||||
/>
|
||||
)}
|
||||
</ScenePage>
|
||||
);
|
||||
}
|
||||
|
139
scenes/SceneError.js
Normal file
139
scenes/SceneError.js
Normal file
@ -0,0 +1,139 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
import ScenePage from "~/components/core/ScenePage";
|
||||
import ScenePageHeader from "~/components/core/ScenePageHeader";
|
||||
|
||||
const STYLES_ROOT = css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
`;
|
||||
|
||||
// TODO(jim): Brand system colors.
|
||||
const STYLES_GLITCH = css`
|
||||
font-size: 120px;
|
||||
position: relative;
|
||||
|
||||
::before,
|
||||
::after {
|
||||
content: "404";
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
background-color: #f7f7f7;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
::before {
|
||||
text-shadow: 2px 0 #00ffea;
|
||||
animation: slate-client-animation-glitch 3s infinite linear;
|
||||
}
|
||||
|
||||
::after {
|
||||
text-shadow: -2px 0 #fe3a7f;
|
||||
animation: slate-client-animation-glitch 2s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes slate-client-animation-glitch {
|
||||
0% {
|
||||
clip: rect(64px, 9999px, 66px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(30px, 9999px, 36px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(80px, 9999px, 71px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(65px, 9999px, 64px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(88px, 9999px, 40px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(17px, 9999px, 79px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(24px, 9999px, 26px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(88px, 9999px, 26px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(88px, 9999px, 80px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(28px, 9999px, 51px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(23px, 9999px, 40px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(16px, 9999px, 86px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(23px, 9999px, 94px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(82px, 9999px, 39px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(37px, 9999px, 92px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(71px, 9999px, 52px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(28px, 9999px, 74px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(67px, 9999px, 96px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(40px, 9999px, 88px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(99px, 9999px, 61px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(76px, 9999px, 77px, 0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_MIDDLE = css`
|
||||
min-height: 10%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 24px;
|
||||
padding-top: 20vh;
|
||||
`;
|
||||
|
||||
export default class SceneError extends React.Component {
|
||||
render() {
|
||||
const title = `404`;
|
||||
const description = "The page you are looking for does not exist";
|
||||
const url = "https://slate.host/404";
|
||||
|
||||
return (
|
||||
<ScenePage>
|
||||
<div css={STYLES_MIDDLE}>
|
||||
<h1 css={STYLES_GLITCH}>404</h1>
|
||||
<h2 style={{ textAlign: "center" }}>The page you are looking for does not exist</h2>
|
||||
</div>
|
||||
</ScenePage>
|
||||
);
|
||||
}
|
||||
}
|
@ -113,7 +113,6 @@ const STYLES_COMMAND_TOOLTIP_ANCHOR = css`
|
||||
|
||||
export default class SceneFilesFolder extends React.Component {
|
||||
state = {
|
||||
view: 0,
|
||||
filterTooltip: false,
|
||||
fileTypes: {
|
||||
image: false,
|
||||
@ -128,9 +127,9 @@ export default class SceneFilesFolder extends React.Component {
|
||||
keyboardTooltip: false,
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
this.openCarouselToItem();
|
||||
};
|
||||
// componentDidMount = () => {
|
||||
// this.openCarouselToItem();
|
||||
// };
|
||||
|
||||
componentDidUpdate = (prevProps, prevState) => {
|
||||
if (prevProps.viewer.library !== this.props.viewer.library) {
|
||||
@ -138,9 +137,9 @@ export default class SceneFilesFolder extends React.Component {
|
||||
this._filterFiles();
|
||||
}
|
||||
}
|
||||
if (prevProps.page !== this.props.page) {
|
||||
this.openCarouselToItem();
|
||||
}
|
||||
// if (prevProps.page.params !== this.props.page.params) {
|
||||
// this.openCarouselToItem();
|
||||
// }
|
||||
};
|
||||
|
||||
_handleFilterTooltip = () => {
|
||||
@ -206,87 +205,50 @@ export default class SceneFilesFolder extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
openCarouselToItem = () => {
|
||||
let index = -1;
|
||||
let page = this.props.page;
|
||||
if (page?.fileId || page?.cid || page?.index) {
|
||||
if (page?.index) {
|
||||
index = page.index;
|
||||
} else {
|
||||
let library = this.props.viewer.library || [];
|
||||
for (let i = 0; i < library.length; i++) {
|
||||
let obj = library[i];
|
||||
if ((obj.cid && obj.cid === page?.cid) || (obj.id && obj.id === page?.fileId)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// openCarouselToItem = () => {
|
||||
// if (!this.props.page?.params || !this.props.viewer.library?.length) {
|
||||
// return;
|
||||
// }
|
||||
// let index = -1;
|
||||
// let params = this.props.page.params;
|
||||
// if (params?.fileId || params?.cid || params?.index) {
|
||||
// if (params?.index) {
|
||||
// index = params.index;
|
||||
// } else {
|
||||
// let library = this.props.viewer.library || [];
|
||||
// for (let i = 0; i < library.length; i++) {
|
||||
// let obj = library[i];
|
||||
// if ((obj.cid && obj.cid === params?.cid) || (obj.id && obj.id === params?.fileId)) {
|
||||
// index = i;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (index !== -1) {
|
||||
Events.dispatchCustomEvent({
|
||||
name: "slate-global-open-carousel",
|
||||
detail: { index },
|
||||
});
|
||||
}
|
||||
};
|
||||
// if (index !== -1) {
|
||||
// Events.dispatchCustomEvent({
|
||||
// name: "slate-global-open-carousel",
|
||||
// detail: { index },
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
render() {
|
||||
let files = this.state.filtersActive ? this.state.filteredFiles : this.props.viewer?.library;
|
||||
files = files || [];
|
||||
const tab = this.props.page.params?.tab || "grid";
|
||||
|
||||
return (
|
||||
<ScenePage>
|
||||
<ScenePageHeader
|
||||
title={
|
||||
this.props.isMobile ? (
|
||||
<TabGroup
|
||||
tabs={[
|
||||
{ title: "Files", value: "NAV_DATA" },
|
||||
{ title: "Collections", value: "NAV_SLATES" },
|
||||
{ title: "Activity", value: "NAV_ACTIVITY" },
|
||||
]}
|
||||
value={0}
|
||||
onAction={this.props.onAction}
|
||||
onChange={(value) => this.setState({ tab: value })}
|
||||
style={{ marginTop: 0, marginBottom: 32 }}
|
||||
itemStyle={{ margin: "0px 12px" }}
|
||||
/>
|
||||
) : (
|
||||
<PrimaryTabGroup
|
||||
tabs={[
|
||||
{ title: "Files", value: "NAV_DATA" },
|
||||
{ title: "Collections", value: "NAV_SLATES" },
|
||||
{ title: "Activity", value: "NAV_ACTIVITY" },
|
||||
]}
|
||||
value={0}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
actions={
|
||||
this.props.isMobile ? null : (
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
<SVG.GridView height="24px" style={{ display: "block" }} />,
|
||||
<SVG.TableView height="24px" style={{ display: "block" }} />,
|
||||
]}
|
||||
value={this.state.view}
|
||||
onChange={(value) => this.setState({ view: value })}
|
||||
style={{ margin: "0 0 24px 0" }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<GlobalCarousel
|
||||
carouselType="DATA"
|
||||
onUpdateViewer={this.props.onUpdateViewer}
|
||||
resources={this.props.resources}
|
||||
viewer={this.props.viewer}
|
||||
objects={files}
|
||||
onAction={this.props.onAction}
|
||||
isMobile={this.props.isMobile}
|
||||
params={this.props.page.params}
|
||||
isOwner={true}
|
||||
/>
|
||||
<DataMeter
|
||||
@ -307,6 +269,21 @@ export default class SceneFilesFolder extends React.Component {
|
||||
}
|
||||
/>
|
||||
<div css={STYLES_CONTAINER_WRAPPER}>
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
{
|
||||
title: <SVG.GridView height="24px" style={{ display: "block" }} />,
|
||||
value: { tab: "grid" },
|
||||
},
|
||||
{
|
||||
title: <SVG.TableView height="24px" style={{ display: "block" }} />,
|
||||
value: { tab: "table" },
|
||||
},
|
||||
]}
|
||||
value={tab}
|
||||
onAction={this.props.onAction}
|
||||
style={{ margin: "0 0 24px 0" }}
|
||||
/>
|
||||
<div css={STYLES_CONTAINER}>
|
||||
<div
|
||||
css={STYLES_BUTTONS_ROW}
|
||||
@ -486,10 +463,10 @@ export default class SceneFilesFolder extends React.Component {
|
||||
onAction={this.props.onAction}
|
||||
viewer={this.props.viewer}
|
||||
items={files}
|
||||
onUpdateViewer={this.props.onUpdateViewer}
|
||||
view={this.state.view}
|
||||
view={tab}
|
||||
resources={this.props.resources}
|
||||
isOwner={true}
|
||||
page={this.props.page}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState>
|
||||
|
@ -13,6 +13,7 @@ import { css } from "@emotion/react";
|
||||
import { createState } from "~/scenes/SceneSettings";
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
import { FilecoinNumber } from "@glif/filecoin-number";
|
||||
import { Link } from "~/components/core/Link";
|
||||
|
||||
import Section from "~/components/core/Section";
|
||||
import ScenePage from "~/components/core/ScenePage";
|
||||
@ -206,7 +207,7 @@ export default class SceneMakeFilecoinDeal extends React.Component {
|
||||
"Your storage deal was put in the queue. This can take up to 36 hours, check back later."
|
||||
);
|
||||
|
||||
this.props.onAction({ type: "NAVIGATE", value: "NAV_FILECOIN" });
|
||||
this.props.onAction({ type: "NAVIGATE", href: "/_/filecoin" });
|
||||
};
|
||||
|
||||
_handleRemove = async (cid) => {
|
||||
|
@ -22,106 +22,115 @@ const STYLES_LOADER = css`
|
||||
|
||||
export default class SceneProfile extends React.Component {
|
||||
state = {
|
||||
profile: null,
|
||||
notFound: false,
|
||||
isOwner: false,
|
||||
loading: true,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
componentDidMount = async () => {
|
||||
this.fetchProfile();
|
||||
};
|
||||
// componentDidMount = async () => {
|
||||
// if (this.props.data) {
|
||||
// this.openCarouselToItem();
|
||||
// }
|
||||
// };
|
||||
|
||||
componentDidUpdate = (prevProps) => {
|
||||
if (this.state.isOwner && this.props.viewer.library !== prevProps.viewer.library) {
|
||||
let filteredViewer = this.getFilteredViewer();
|
||||
this.setState({ profile: filteredViewer });
|
||||
} else if (this.props.page !== prevProps.page) {
|
||||
this.openCarouselToItem();
|
||||
}
|
||||
};
|
||||
// componentDidUpdate = (prevProps) => {
|
||||
// // if (
|
||||
// // this.state.isOwner &&
|
||||
// // this.props.viewer &&
|
||||
// // this.props.viewer.library !== prevProps.viewer.library
|
||||
// // ) {
|
||||
// // let filteredViewer = this.getFilteredViewer();
|
||||
// // this.setState({ profile: filteredViewer });
|
||||
// // } else
|
||||
// if (this.props.data !== prevProps.data || this.props.page.params !== prevProps.page.params) {
|
||||
// this.openCarouselToItem();
|
||||
// }
|
||||
// };
|
||||
|
||||
fetchProfile = async () => {
|
||||
const username = this.props.page.user || this.props.page.data?.username;
|
||||
let isOwner = false;
|
||||
let query;
|
||||
let targetUser;
|
||||
if (username) {
|
||||
if (username === this.props.viewer.username) {
|
||||
isOwner = true;
|
||||
targetUser = this.getFilteredViewer();
|
||||
} else {
|
||||
query = { username: username };
|
||||
}
|
||||
} else if (this.props.data?.id) {
|
||||
if (this.props.data.id === this.props.viewer.id) {
|
||||
isOwner = true;
|
||||
targetUser = this.getFilteredViewer();
|
||||
} else {
|
||||
query = { id: this.props.data.id };
|
||||
}
|
||||
}
|
||||
// fetchProfile = async () => {
|
||||
// const username = this.props.page.username || this.props.page.data?.username;
|
||||
// const { userId } = this.props.page;
|
||||
|
||||
if (!targetUser) {
|
||||
let response;
|
||||
if (query) {
|
||||
response = await Actions.getSerializedProfile(query);
|
||||
}
|
||||
// const id = userId || this.props.data?.id;
|
||||
|
||||
if (!response || response.error) {
|
||||
this.setState({ notFound: true });
|
||||
return;
|
||||
}
|
||||
// let isOwner = false;
|
||||
// let query;
|
||||
// let targetUser;
|
||||
// if (username) {
|
||||
// if (this.props.viewer && username === this.props.viewer.username) {
|
||||
// isOwner = true;
|
||||
// targetUser = this.getFilteredViewer();
|
||||
// } else {
|
||||
// query = { username };
|
||||
// }
|
||||
// } else if (id) {
|
||||
// if (this.props.viewer && id === this.props.viewer.id) {
|
||||
// isOwner = true;
|
||||
// targetUser = this.getFilteredViewer();
|
||||
// } else {
|
||||
// query = { id };
|
||||
// }
|
||||
// }
|
||||
|
||||
targetUser = response.data;
|
||||
}
|
||||
window.history.replaceState(
|
||||
{ ...window.history.state, data: targetUser },
|
||||
"A slate user",
|
||||
`/${targetUser.username}`
|
||||
);
|
||||
this.props.onUpdateData(targetUser);
|
||||
this.setState({ isOwner, profile: targetUser, loading: false }, this.openCarouselToItem);
|
||||
};
|
||||
// if (!targetUser) {
|
||||
// let response;
|
||||
// if (query) {
|
||||
// response = await Actions.getSerializedProfile(query);
|
||||
// }
|
||||
|
||||
openCarouselToItem = () => {
|
||||
if (!this.state.profile?.library?.length) {
|
||||
return;
|
||||
}
|
||||
const { cid, fileId, index } = this.props.page;
|
||||
// if (!response || response.error) {
|
||||
// this.setState({ notFound: true });
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (Strings.isEmpty(cid) && Strings.isEmpty(fileId) && typeof index === "undefined") {
|
||||
return;
|
||||
}
|
||||
// targetUser = response.data;
|
||||
// }
|
||||
// window.history.replaceState(
|
||||
// { ...window.history.state, data: targetUser },
|
||||
// "A slate user",
|
||||
// `/${targetUser.username}`
|
||||
// );
|
||||
// this.props.onUpdateData(targetUser);
|
||||
// this.setState({ isOwner, profile: targetUser, loading: false }, this.openCarouselToItem);
|
||||
// };
|
||||
|
||||
const library = this.state.profile.library;
|
||||
// openCarouselToItem = () => {
|
||||
// if (!this.props.data?.library?.length || !this.props.page?.params) {
|
||||
// return;
|
||||
// }
|
||||
// const { cid, fileId, index } = this.props.page.params;
|
||||
|
||||
let foundIndex = -1;
|
||||
if (index) {
|
||||
foundIndex = index;
|
||||
} else if (cid) {
|
||||
foundIndex = library.findIndex((object) => object.cid === cid);
|
||||
} else if (fileId) {
|
||||
foundIndex = library.findIndex((object) => object.id === fileId);
|
||||
}
|
||||
if (typeof foundIndex !== "undefined" && foundIndex !== -1) {
|
||||
Events.dispatchCustomEvent({
|
||||
name: "slate-global-open-carousel",
|
||||
detail: { index: foundIndex },
|
||||
});
|
||||
}
|
||||
// else {
|
||||
// Events.dispatchCustomEvent({
|
||||
// name: "create-alert",
|
||||
// detail: {
|
||||
// alert: {
|
||||
// message:
|
||||
// "The requested file could not be found. It could have been deleted or may be private",
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
};
|
||||
// if (Strings.isEmpty(cid) && Strings.isEmpty(fileId) && typeof index === "undefined") {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const library = this.props.data.library;
|
||||
|
||||
// let foundIndex = -1;
|
||||
// if (index) {
|
||||
// foundIndex = index;
|
||||
// } else if (cid) {
|
||||
// foundIndex = library.findIndex((object) => object.cid === cid);
|
||||
// } else if (fileId) {
|
||||
// foundIndex = library.findIndex((object) => object.id === fileId);
|
||||
// }
|
||||
// if (typeof foundIndex !== "undefined" && foundIndex !== -1) {
|
||||
// Events.dispatchCustomEvent({
|
||||
// name: "slate-global-open-carousel",
|
||||
// detail: { index: foundIndex },
|
||||
// });
|
||||
// }
|
||||
// // else {
|
||||
// // Events.dispatchCustomEvent({
|
||||
// // name: "create-alert",
|
||||
// // detail: {
|
||||
// // alert: {
|
||||
// // message:
|
||||
// // "The requested file could not be found. It could have been deleted or may be private",
|
||||
// // },
|
||||
// // },
|
||||
// // });
|
||||
// // }
|
||||
// };
|
||||
|
||||
getFilteredViewer = () => {
|
||||
let viewer = this.props.viewer;
|
||||
@ -130,36 +139,51 @@ export default class SceneProfile extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.notFound) {
|
||||
return (
|
||||
<ScenePage>
|
||||
<EmptyState>
|
||||
<SVG.Users height="24px" style={{ marginBottom: 24 }} />
|
||||
<div>We were unable to locate that user profile</div>
|
||||
</EmptyState>
|
||||
</ScenePage>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.loading) {
|
||||
let user = this.props.data;
|
||||
if (!user) {
|
||||
return (
|
||||
<ScenePage>
|
||||
<div css={STYLES_LOADER}>
|
||||
<LoaderSpinner />
|
||||
</div>
|
||||
</ScenePage>
|
||||
// <ScenePage>
|
||||
// <EmptyState>
|
||||
// <SVG.Users height="24px" style={{ marginBottom: 24 }} />
|
||||
// <div>We were unable to locate that user profile</div>
|
||||
// </EmptyState>
|
||||
// </ScenePage>
|
||||
);
|
||||
} else if (this.state.profile?.id) {
|
||||
} else {
|
||||
return (
|
||||
<Profile
|
||||
{...this.props}
|
||||
user={this.state.profile}
|
||||
isOwner={this.state.isOwner}
|
||||
user={user}
|
||||
isOwner={this.props.viewer ? user.id === this.props.viewer.id : false}
|
||||
isAuthenticated={this.props.viewer !== null}
|
||||
key={this.state.profile.id}
|
||||
key={user.id}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
// if (this.state.loading) {
|
||||
// return (
|
||||
// <ScenePage>
|
||||
// <div css={STYLES_LOADER}>
|
||||
// <LoaderSpinner />
|
||||
// </div>
|
||||
// </ScenePage>
|
||||
// );
|
||||
// } else {
|
||||
// return (
|
||||
// <Profile
|
||||
// {...this.props}
|
||||
// user={user}
|
||||
// isOwner={this.state.isOwner}
|
||||
// isAuthenticated={this.props.viewer !== null}
|
||||
// key={user.id}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
@ -95,15 +95,14 @@ class Key extends React.Component {
|
||||
</SquareButtonGray>
|
||||
|
||||
{this.state.modalShow && (
|
||||
<ConfirmationModal
|
||||
<ConfirmationModal
|
||||
type={"DELETE"}
|
||||
withValidation={false}
|
||||
callback={(e) => this._handleDelete(e, this.props.data.id)}
|
||||
callback={(e) => this._handleDelete(e, this.props.data.id)}
|
||||
header={`Are you sure you want to revoke this API key?`}
|
||||
subHeader={`Any services using it will no longer be able to access your Slate account.`}
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -117,7 +116,6 @@ export default class SceneSettingsDeveloper extends React.Component {
|
||||
language: "javascript",
|
||||
docs: "GET",
|
||||
copying: false,
|
||||
tab: 0,
|
||||
modalShow: false,
|
||||
};
|
||||
|
||||
@ -154,6 +152,7 @@ export default class SceneSettingsDeveloper extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const tab = this.props.page.params?.tab || "v2";
|
||||
let APIKey = "YOUR-API-KEY-HERE";
|
||||
let lang = this.state.language;
|
||||
if (this.props.viewer.keys) {
|
||||
@ -298,12 +297,15 @@ export default class SceneSettingsDeveloper extends React.Component {
|
||||
</ScenePageHeader>
|
||||
|
||||
<SecondaryTabGroup
|
||||
tabs={["Version 2.0", "Version 1.0"]}
|
||||
value={this.state.tab}
|
||||
onChange={(tab) => this.setState({ tab })}
|
||||
tabs={[
|
||||
{ title: "Version 2.0", value: { tab: "v2" } },
|
||||
{ title: "Version 1.0", value: { tab: "v1" } },
|
||||
]}
|
||||
value={tab}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
|
||||
{this.state.tab === 0 ? (
|
||||
{tab === "v2" ? (
|
||||
<>
|
||||
<APIDocsGetV2
|
||||
language={lang}
|
||||
|
@ -104,24 +104,20 @@ export default class SceneSignIn extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
let response = null;
|
||||
|
||||
if (this.state.scene === "CREATE_ACCOUNT") {
|
||||
response = await this.props.onCreateUser({
|
||||
await this.props.onCreateUser({
|
||||
username: this.state.username.toLowerCase(),
|
||||
password: this.state.password,
|
||||
accepted: this.state.accepted,
|
||||
});
|
||||
} else {
|
||||
response = await this.props.onAuthenticate({
|
||||
await this.props.onAuthenticate({
|
||||
username: this.state.username.toLowerCase(),
|
||||
password: this.state.password,
|
||||
});
|
||||
}
|
||||
|
||||
if (Events.hasError(response)) {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
this.setState({ loading: false });
|
||||
};
|
||||
|
||||
_handleCheckUsername = async () => {
|
||||
@ -160,7 +156,8 @@ export default class SceneSignIn extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div css={STYLES_ROOT}>
|
||||
<WebsitePrototypeHeader style={{ background: `none` }} />
|
||||
<div />
|
||||
{/* <WebsitePrototypeHeader style={{ background: `none` }} /> */}
|
||||
<div css={STYLES_MIDDLE}>
|
||||
<SignIn {...this.props} />
|
||||
</div>
|
||||
|
@ -8,6 +8,7 @@ import * as Strings from "~/common/strings";
|
||||
import * as UserBehaviors from "~/common/user-behaviors";
|
||||
import * as Events from "~/common/custom-events";
|
||||
|
||||
import { Link } from "~/components/core/Link";
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
import { css } from "@emotion/react";
|
||||
import { SlateLayout } from "~/components/core/SlateLayout";
|
||||
@ -63,145 +64,154 @@ export default class SceneSlate extends React.Component {
|
||||
accessDenied: false,
|
||||
};
|
||||
|
||||
componentDidMount = async () => {
|
||||
await this.fetchSlate();
|
||||
};
|
||||
// componentDidMount = async () => {
|
||||
// if (this.props.data) {
|
||||
// this.openCarouselToItem();
|
||||
// }
|
||||
// };
|
||||
|
||||
componentDidUpdate = async (prevProps) => {
|
||||
if (!this.props.data?.objects && !this.state.notFound) {
|
||||
await this.fetchSlate();
|
||||
} else if (this.props.page !== prevProps.page) {
|
||||
this.openCarouselToItem();
|
||||
}
|
||||
};
|
||||
// componentDidUpdate = async (prevProps) => {
|
||||
// // if (!this.props.data?.objects && !this.state.notFound) {
|
||||
// // await this.fetchSlate();
|
||||
// // } else
|
||||
// if (this.props.data !== prevProps.data || this.props.page.params !== prevProps.page.params) {
|
||||
// this.openCarouselToItem();
|
||||
// }
|
||||
// };
|
||||
|
||||
fetchSlate = async () => {
|
||||
const { user: username, slate: slatename } = this.props.page;
|
||||
// fetchSlate = async () => {
|
||||
// const { username, slatename, slateId } = this.props.page;
|
||||
|
||||
if (!this.props.data && (!username || !slatename)) {
|
||||
this.setState({ notFound: true });
|
||||
return;
|
||||
}
|
||||
// if (!this.props.data && (!username || !slatename)) {
|
||||
// this.setState({ notFound: true });
|
||||
// return;
|
||||
// }
|
||||
|
||||
//NOTE(martina): look for the slate in the user's slates
|
||||
let slate;
|
||||
if (this.props.data?.id) {
|
||||
for (let s of this.props.viewer.slates) {
|
||||
if (this.props.data.id && this.props.data.id === s.id) {
|
||||
slate = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (slatename && username === this.props.viewer.username) {
|
||||
for (let s of this.props.viewer.slates) {
|
||||
if (username && slatename === s.slatename) {
|
||||
slate = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!slate) {
|
||||
Events.dispatchMessage({ message: "We're having trouble fetching that slate right now." });
|
||||
this.setState({ notFound: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
// let id = slateId || this.props.data?.id;
|
||||
|
||||
if (slate) {
|
||||
window.history.replaceState(
|
||||
{ ...window.history.state, data: slate },
|
||||
"Slate",
|
||||
`/${this.props.viewer.username}/${slate.slatename}`
|
||||
);
|
||||
}
|
||||
// //NOTE(martina): look for the slate in the user's slates
|
||||
// let slate;
|
||||
// if (this.props.viewer) {
|
||||
// if (id) {
|
||||
// for (let s of this.props.viewer.slates) {
|
||||
// if (id && id === s.id) {
|
||||
// slate = s;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// } else if (slatename && username === this.props.viewer.username) {
|
||||
// for (let s of this.props.viewer.slates) {
|
||||
// if (username && slatename === s.slatename) {
|
||||
// slate = s;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (!slate) {
|
||||
// Events.dispatchMessage({
|
||||
// message: "We're having trouble fetching that slate right now.",
|
||||
// });
|
||||
// this.setState({ notFound: true });
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
if (!slate) {
|
||||
let query;
|
||||
if (username && slatename) {
|
||||
query = { username, slatename };
|
||||
} else if (this.props.data && this.props.data.id) {
|
||||
query = { id: this.props.data.id };
|
||||
}
|
||||
let response;
|
||||
if (query) {
|
||||
response = await Actions.getSerializedSlate(query);
|
||||
}
|
||||
if (response?.decorator == "SLATE_PRIVATE_ACCESS_DENIED") {
|
||||
this.setState({ accessDenied: true, loading: false });
|
||||
return;
|
||||
}
|
||||
if (Events.hasError(response)) {
|
||||
this.setState({ notFound: true, loading: false });
|
||||
return;
|
||||
}
|
||||
slate = response.slate;
|
||||
window.history.replaceState(
|
||||
{ ...window.history.state, data: slate },
|
||||
"Slate",
|
||||
`/${slate.user.username}/${slate.slatename}`
|
||||
);
|
||||
}
|
||||
this.props.onUpdateData(slate, () => {
|
||||
this.setState({ loading: false });
|
||||
this.openCarouselToItem();
|
||||
});
|
||||
};
|
||||
// if (slate) {
|
||||
// window.history.replaceState(
|
||||
// { ...window.history.state, data: slate },
|
||||
// "Slate",
|
||||
// `/${this.props.viewer.username}/${slate.slatename}`
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
openCarouselToItem = () => {
|
||||
if (!this.props.data?.objects?.length) {
|
||||
return;
|
||||
}
|
||||
let objects = this.props.data.objects;
|
||||
// if (!slate) {
|
||||
// let query;
|
||||
// if (username && slatename) {
|
||||
// query = { username, slatename };
|
||||
// } else if (id) {
|
||||
// query = { id };
|
||||
// }
|
||||
// let response;
|
||||
// if (query) {
|
||||
// response = await Actions.getSerializedSlate(query);
|
||||
// }
|
||||
// if (response?.decorator == "SLATE_PRIVATE_ACCESS_DENIED") {
|
||||
// this.setState({ accessDenied: true, loading: false });
|
||||
// return;
|
||||
// }
|
||||
// if (Events.hasError(response)) {
|
||||
// this.setState({ notFound: true, loading: false });
|
||||
// return;
|
||||
// }
|
||||
// slate = response.slate;
|
||||
// window.history.replaceState(
|
||||
// { ...window.history.state, data: slate },
|
||||
// "Slate",
|
||||
// `/${slate.user.username}/${slate.slatename}`
|
||||
// );
|
||||
// }
|
||||
// this.props.onUpdateData(slate, () => {
|
||||
// this.setState({ loading: false });
|
||||
// this.openCarouselToItem();
|
||||
// });
|
||||
// };
|
||||
|
||||
const { cid, fileId, index } = this.props.page;
|
||||
// openCarouselToItem = () => {
|
||||
// if (!this.props.data?.objects?.length || !this.props.page?.params) {
|
||||
// return;
|
||||
// }
|
||||
// let objects = this.props.data.objects;
|
||||
|
||||
if (Strings.isEmpty(cid) && Strings.isEmpty(fileId) && typeof index === "undefined") {
|
||||
return;
|
||||
}
|
||||
// const { cid, fileId, index } = this.props.page.params;
|
||||
|
||||
let foundIndex = -1;
|
||||
if (index) {
|
||||
foundIndex = index;
|
||||
} else if (cid) {
|
||||
foundIndex = objects.findIndex((object) => object.cid === cid);
|
||||
} else if (fileId) {
|
||||
foundIndex = objects.findIndex((object) => object.id === fileId);
|
||||
}
|
||||
// if (Strings.isEmpty(cid) && Strings.isEmpty(fileId) && typeof index === "undefined") {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (typeof foundIndex !== "undefined" && foundIndex !== -1) {
|
||||
Events.dispatchCustomEvent({
|
||||
name: "slate-global-open-carousel",
|
||||
detail: { index: foundIndex },
|
||||
});
|
||||
}
|
||||
};
|
||||
// let foundIndex = -1;
|
||||
// if (index) {
|
||||
// foundIndex = index;
|
||||
// } else if (cid) {
|
||||
// foundIndex = objects.findIndex((object) => object.cid === cid);
|
||||
// } else if (fileId) {
|
||||
// foundIndex = objects.findIndex((object) => object.id === fileId);
|
||||
// }
|
||||
|
||||
// if (typeof foundIndex !== "undefined" && foundIndex !== -1) {
|
||||
// Events.dispatchCustomEvent({
|
||||
// name: "slate-global-open-carousel",
|
||||
// detail: { index: foundIndex },
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
render() {
|
||||
if (this.state.notFound || this.state.accessDenied) {
|
||||
return (
|
||||
<ScenePage>
|
||||
<EmptyState>
|
||||
<SVG.Layers height="24px" style={{ marginBottom: 24 }} />
|
||||
<div>
|
||||
{this.state.accessDenied
|
||||
? "You do not have access to that collection"
|
||||
: "We were unable to locate that collection"}
|
||||
</div>
|
||||
</EmptyState>
|
||||
</ScenePage>
|
||||
);
|
||||
}
|
||||
if (this.state.loading) {
|
||||
if (!this.props.data) {
|
||||
return (
|
||||
// <ScenePage>
|
||||
// <EmptyState>
|
||||
// <SVG.Layers height="24px" style={{ marginBottom: 24 }} />
|
||||
// <div>We were unable to locate that collection</div>
|
||||
// </EmptyState>
|
||||
// </ScenePage>
|
||||
<ScenePage>
|
||||
<div css={STYLES_LOADER}>
|
||||
<LoaderSpinner />
|
||||
</div>
|
||||
</ScenePage>
|
||||
);
|
||||
} else if (this.props.data?.id) {
|
||||
return <SlatePage {...this.props} key={this.props.data.id} current={this.props.data} />;
|
||||
} else {
|
||||
return <SlatePage {...this.props} key={this.props.data.id} data={this.props.data} />;
|
||||
}
|
||||
return null;
|
||||
// if (this.state.loading) {
|
||||
// return (
|
||||
// <ScenePage>
|
||||
// <div css={STYLES_LOADER}>
|
||||
// <LoaderSpinner />
|
||||
// </div>
|
||||
// </ScenePage>
|
||||
// );
|
||||
// } else
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,11 +221,13 @@ class SlatePage extends React.Component {
|
||||
_remoteLock = false;
|
||||
|
||||
state = {
|
||||
...(this.props.current, this.props.viewer),
|
||||
...(this.props.data, this.props.viewer),
|
||||
editing: false,
|
||||
isSubscribed: this.props.viewer.subscriptions.some((subscription) => {
|
||||
return subscription.id === this.props.current.id;
|
||||
}),
|
||||
isSubscribed: this.props.viewer
|
||||
? this.props.viewer.subscriptions.some((subscription) => {
|
||||
return subscription.id === this.props.data.id;
|
||||
})
|
||||
: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -229,13 +241,14 @@ class SlatePage extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = this.props.current.objects.findIndex((object) => object.cid === cid);
|
||||
if (index !== -1) {
|
||||
Events.dispatchCustomEvent({
|
||||
name: "slate-global-open-carousel",
|
||||
detail: { index },
|
||||
});
|
||||
} else {
|
||||
const index = this.props.data.objects.findIndex((object) => object.cid === cid);
|
||||
// if (index !== -1) {
|
||||
// Events.dispatchCustomEvent({
|
||||
// name: "slate-global-open-carousel",
|
||||
// detail: { index },
|
||||
// });
|
||||
// }
|
||||
if (index === -1) {
|
||||
Events.dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
@ -249,26 +262,30 @@ class SlatePage extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.viewer.subscriptions !== prevProps.viewer.subscriptions) {
|
||||
if (this.props.viewer && this.props.viewer.subscriptions !== prevProps.viewer.subscriptions) {
|
||||
this.setState({
|
||||
isSubscribed: this.props.viewer.subscriptions.some((subscription) => {
|
||||
return subscription.id === this.props.current.id;
|
||||
return subscription.id === this.props.data.id;
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_handleSubscribe = () => {
|
||||
if (!this.props.viewer) {
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
return;
|
||||
}
|
||||
this.setState({ isSubscribed: !this.state.isSubscribed }, () => {
|
||||
Actions.createSubscription({
|
||||
slateId: this.props.current.id,
|
||||
slateId: this.props.data.id,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
_handleSaveLayout = async (layouts, autoSave) => {
|
||||
const response = await Actions.updateSlateLayout({
|
||||
id: this.props.current.id,
|
||||
id: this.props.data.id,
|
||||
layouts,
|
||||
});
|
||||
|
||||
@ -278,34 +295,41 @@ class SlatePage extends React.Component {
|
||||
};
|
||||
|
||||
_handleSavePreview = async (preview) => {
|
||||
let updateObject = { id: this.props.current.id, data: { preview } };
|
||||
if (!this.props.viewer) {
|
||||
return;
|
||||
}
|
||||
let updateObject = { id: this.props.data.id, data: { preview } };
|
||||
|
||||
let slates = this.props.viewer.slates;
|
||||
let slateId = this.props.current.id;
|
||||
let slateId = this.props.data.id;
|
||||
for (let slate of slates) {
|
||||
if (slate.id === slateId) {
|
||||
slate.data.preview = preview;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.props.onUpdateViewer({ slates });
|
||||
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { slates } });
|
||||
|
||||
const response = await Actions.updateSlate(updateObject);
|
||||
|
||||
Events.hasError(response);
|
||||
};
|
||||
|
||||
_handleSelect = (index) =>
|
||||
Events.dispatchCustomEvent({
|
||||
name: "slate-global-open-carousel",
|
||||
detail: { index },
|
||||
});
|
||||
// _handleSelect = (index) =>
|
||||
// Events.dispatchCustomEvent({
|
||||
// name: "slate-global-open-carousel",
|
||||
// detail: { index },
|
||||
// });
|
||||
|
||||
_handleAdd = async () => {
|
||||
if (!this.props.viewer) {
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
return;
|
||||
}
|
||||
await this.props.onAction({
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_ADD_FILE_TO_BUCKET",
|
||||
data: this.props.current,
|
||||
data: this.props.data,
|
||||
});
|
||||
};
|
||||
|
||||
@ -313,7 +337,7 @@ class SlatePage extends React.Component {
|
||||
return this.props.onAction({
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_SINGLE_SLATE_SETTINGS",
|
||||
data: this.props.current,
|
||||
data: this.props.data,
|
||||
});
|
||||
};
|
||||
|
||||
@ -330,8 +354,12 @@ class SlatePage extends React.Component {
|
||||
};
|
||||
|
||||
_handleDownload = () => {
|
||||
const slateName = this.props.current.data.name;
|
||||
const slateFiles = this.props.current.objects;
|
||||
if (!this.props.viewer) {
|
||||
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
|
||||
return;
|
||||
}
|
||||
const slateName = this.props.data.data.name;
|
||||
const slateFiles = this.props.data.objects;
|
||||
UserBehaviors.compressAndDownloadFiles({
|
||||
files: slateFiles,
|
||||
name: `${slateName}.zip`,
|
||||
@ -340,12 +368,12 @@ class SlatePage extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { user, data } = this.props.current;
|
||||
const { user, data } = this.props.data;
|
||||
const { body = "", preview } = data;
|
||||
let objects = this.props.current.objects;
|
||||
let layouts = this.props.current.data.layouts;
|
||||
const isPublic = this.props.current.isPublic;
|
||||
const isOwner = this.props.current.ownerId === this.props.viewer.id;
|
||||
let objects = this.props.data.objects;
|
||||
let layouts = this.props.data.data.layouts;
|
||||
const isPublic = this.props.data.isPublic;
|
||||
const isOwner = this.props.viewer ? this.props.data.ownerId === this.props.viewer.id : false;
|
||||
const tags = data.tags;
|
||||
|
||||
let actions = isOwner ? (
|
||||
@ -381,19 +409,21 @@ class SlatePage extends React.Component {
|
||||
title={
|
||||
user && !isOwner ? (
|
||||
<span>
|
||||
<span
|
||||
onClick={() =>
|
||||
this.props.onAction({
|
||||
type: "NAVIGATE",
|
||||
value: this.props.sceneId,
|
||||
scene: "PROFILE",
|
||||
data: user,
|
||||
})
|
||||
}
|
||||
css={STYLES_USERNAME}
|
||||
>
|
||||
{user.username}
|
||||
</span>{" "}
|
||||
<Link href={`/$/user/${user.id}`} onAction={this.props.onAction}>
|
||||
<span
|
||||
// onClick={() =>
|
||||
// this.props.onAction({
|
||||
// type: "NAVIGATE",
|
||||
// value: "NAV_PROFILE",
|
||||
// shallow: true,
|
||||
// data: user,
|
||||
// })
|
||||
// }
|
||||
css={STYLES_USERNAME}
|
||||
>
|
||||
{user.username}
|
||||
</span>{" "}
|
||||
</Link>
|
||||
/ {data.name}
|
||||
{isOwner && !isPublic && (
|
||||
<SVG.SecurityLock
|
||||
@ -424,12 +454,12 @@ class SlatePage extends React.Component {
|
||||
<>
|
||||
<GlobalCarousel
|
||||
carouselType="SLATE"
|
||||
onUpdateViewer={this.props.onUpdateViewer}
|
||||
viewer={this.props.viewer}
|
||||
objects={objects}
|
||||
current={this.props.current}
|
||||
data={this.props.data}
|
||||
onAction={this.props.onAction}
|
||||
isMobile={this.props.isMobile}
|
||||
params={this.props.page.params}
|
||||
isOwner={isOwner}
|
||||
external={this.props.external}
|
||||
/>
|
||||
@ -443,11 +473,12 @@ class SlatePage extends React.Component {
|
||||
) : (
|
||||
<div style={{ marginTop: isOwner ? 24 : 48 }}>
|
||||
<SlateLayout
|
||||
key={this.props.current.id}
|
||||
current={this.props.current}
|
||||
onUpdateViewer={this.props.onUpdateViewer}
|
||||
page={this.props.page}
|
||||
external={this.props.external}
|
||||
key={this.props.data.id}
|
||||
data={this.props.data}
|
||||
viewer={this.props.viewer}
|
||||
slateId={this.props.current.id}
|
||||
slateId={this.props.data.id}
|
||||
layout={layouts && layouts.ver === "2.0" ? layouts.layout || [] : null}
|
||||
onSaveLayout={this._handleSaveLayout}
|
||||
isOwner={isOwner}
|
||||
|
@ -31,67 +31,27 @@ export default class SceneSlates extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const tab = this.props.page.params?.tab || "collections";
|
||||
let subscriptions = this.props.viewer.subscriptions;
|
||||
|
||||
return (
|
||||
<ScenePage>
|
||||
<ScenePageHeader
|
||||
title={
|
||||
this.props.isMobile ? (
|
||||
<TabGroup
|
||||
tabs={[
|
||||
{ title: "Files", value: "NAV_DATA" },
|
||||
{ title: "Collections", value: "NAV_SLATES" },
|
||||
{ title: "Activity", value: "NAV_ACTIVITY" },
|
||||
]}
|
||||
value={1}
|
||||
onAction={this.props.onAction}
|
||||
onChange={(value) => this.setState({ tab: value })}
|
||||
style={{ marginTop: 0, marginBottom: 32 }}
|
||||
itemStyle={{ margin: "0px 12px" }}
|
||||
/>
|
||||
) : (
|
||||
<PrimaryTabGroup
|
||||
tabs={[
|
||||
{ title: "Files", value: "NAV_DATA" },
|
||||
{ title: "Collections", value: "NAV_SLATES" },
|
||||
{ title: "Activity", value: "NAV_ACTIVITY" },
|
||||
]}
|
||||
value={1}
|
||||
onAction={this.props.onAction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
actions={
|
||||
<div style={{ display: "flex", alignItems: "center", marginBottom: 24 }}>
|
||||
<SquareButtonGray onClick={this._handleAdd} style={{ marginRight: 16 }}>
|
||||
<SVG.Plus height="16px" />
|
||||
</SquareButtonGray>
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
{ title: "My Collections", value: "NAV_SLATES" },
|
||||
{ title: "Subscribed", value: "NAV_SLATES_FOLLOWING" },
|
||||
]}
|
||||
value={this.props.tab}
|
||||
onAction={this.props.onAction}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{/* <ScenePageHeader
|
||||
title="Slates"
|
||||
actions={
|
||||
this.props.tab === 0 ? (
|
||||
<SquareButtonGray onClick={this._handleAdd} style={{ marginLeft: 12 }}>
|
||||
<SVG.Plus height="16px" />
|
||||
</SquareButtonGray>
|
||||
) : null
|
||||
}
|
||||
/> */}
|
||||
|
||||
{this.props.tab === 0 ? (
|
||||
this.props.viewer.slates && this.props.viewer.slates.length ? (
|
||||
<div style={{ display: "flex", alignItems: "center", marginBottom: 24 }}>
|
||||
<SecondaryTabGroup
|
||||
tabs={[
|
||||
{ title: "My Collections", value: { tab: "collections" } },
|
||||
{ title: "Subscribed", value: { tab: "subscribed" } },
|
||||
]}
|
||||
value={tab}
|
||||
onAction={this.props.onAction}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
<SquareButtonGray onClick={this._handleAdd} style={{ marginLeft: 16 }}>
|
||||
<SVG.Plus height="16px" />
|
||||
</SquareButtonGray>
|
||||
</div>
|
||||
{tab === "collections" ? (
|
||||
this.props.viewer.slates?.length ? (
|
||||
<SlatePreviewBlocks
|
||||
isOwner
|
||||
slates={this.props.viewer.slates}
|
||||
@ -111,7 +71,7 @@ export default class SceneSlates extends React.Component {
|
||||
)
|
||||
) : null}
|
||||
|
||||
{this.props.tab === 1 ? (
|
||||
{tab === "subscribed" ? (
|
||||
subscriptions && subscriptions.length ? (
|
||||
<SlatePreviewBlocks
|
||||
slates={subscriptions}
|
||||
|
@ -12,6 +12,8 @@ console.log(`RUNNING: drop-database.js`);
|
||||
Promise.all([
|
||||
db.schema.dropTable("users"),
|
||||
db.schema.dropTable("slates"),
|
||||
db.schema.dropTable("slate_files"),
|
||||
db.schema.dropTable("files"),
|
||||
db.schema.dropTable("activity"),
|
||||
db.schema.dropTable("subscriptions"),
|
||||
db.schema.dropTable("keys"),
|
||||
|
@ -566,7 +566,7 @@ const runScript = async () => {
|
||||
// await printSlatesTable();
|
||||
|
||||
//NOTE(martina): add tables
|
||||
// await addTables();
|
||||
await addTables();
|
||||
|
||||
//NOTE(martina): put data into new tables
|
||||
// await DB("slate_files").del();
|
||||
|
481
server.js
481
server.js
@ -8,6 +8,7 @@ import * as NodeLogging from "~/node_common/node-logging";
|
||||
import * as Validations from "~/common/validations";
|
||||
import * as Window from "~/common/window";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as NavigationData from "~/common/navigation-data";
|
||||
|
||||
import limit from "express-rate-limit";
|
||||
import express from "express";
|
||||
@ -153,8 +154,8 @@ app.prepare().then(async () => {
|
||||
});
|
||||
|
||||
server.get("/_", async (req, res) => {
|
||||
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
|
||||
let isMac = Window.isMac(req.headers["user-agent"]);
|
||||
// let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
|
||||
// let isMac = Window.isMac(req.headers["user-agent"]);
|
||||
|
||||
const isBucketsAvailable = await Utilities.checkTextile();
|
||||
|
||||
@ -164,6 +165,37 @@ app.prepare().then(async () => {
|
||||
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
|
||||
let viewer = null;
|
||||
if (id) {
|
||||
viewer = await Data.getUserById({
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
if (viewer) {
|
||||
return res.redirect("/_/data");
|
||||
} else {
|
||||
return res.redirect("/_/explore");
|
||||
}
|
||||
|
||||
// let page = NavigationData.getById(null, viewer);
|
||||
|
||||
// return app.render(req, res, "/_", {
|
||||
// viewer,
|
||||
// isMobile,
|
||||
// isMac,
|
||||
// page,
|
||||
// data: null,
|
||||
// resources: EXTERNAL_RESOURCES,
|
||||
// });
|
||||
});
|
||||
|
||||
server.get("/_/:scene", async (req, res) => {
|
||||
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
|
||||
let isMac = Window.isMac(req.headers["user-agent"]);
|
||||
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
|
||||
let viewer = null;
|
||||
if (id) {
|
||||
viewer = await ViewerManager.getById({
|
||||
@ -171,146 +203,93 @@ app.prepare().then(async () => {
|
||||
});
|
||||
}
|
||||
|
||||
let { page } = NavigationData.getByHref(req.path, viewer);
|
||||
page = { ...page, params: req.query };
|
||||
if (!page) {
|
||||
return handler(req, res, req.url, {
|
||||
isMobile,
|
||||
resources: EXTERNAL_RESOURCES,
|
||||
});
|
||||
}
|
||||
|
||||
return app.render(req, res, "/_", {
|
||||
viewer,
|
||||
isMobile,
|
||||
isMac,
|
||||
viewer,
|
||||
page,
|
||||
data: null,
|
||||
resources: EXTERNAL_RESOURCES,
|
||||
});
|
||||
});
|
||||
|
||||
server.get("/_/integration-page", async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
|
||||
// let viewer = null;
|
||||
// if (id) {
|
||||
// viewer = await ViewerManager.getById({
|
||||
// id,
|
||||
// });
|
||||
// }
|
||||
|
||||
return app.render(req, res, "/_/integration-page", {
|
||||
viewer: null,
|
||||
});
|
||||
});
|
||||
|
||||
server.all("/_/:a", async (r, s) => handler(r, s, r.url));
|
||||
server.all("/_/:a/:b", async (r, s) => handler(r, s, r.url));
|
||||
|
||||
server.get("/[$]/slate/:id", async (req, res) => {
|
||||
const slate = await Data.getSlateById({
|
||||
id: req.params.id,
|
||||
includeFiles: true,
|
||||
sanitize: true,
|
||||
});
|
||||
|
||||
if (!slate) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
|
||||
if (slate.error) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
|
||||
if (!slate.isPublic) {
|
||||
return res.redirect("/403");
|
||||
if (!slate || slate.error) {
|
||||
return res.redirect("/_/404");
|
||||
}
|
||||
|
||||
const creator = await Data.getUserById({
|
||||
id: slate.ownerId,
|
||||
sanitize: true,
|
||||
});
|
||||
|
||||
if (!creator) {
|
||||
return res.redirect("/404");
|
||||
if (!creator || creator.error) {
|
||||
return res.redirect("/_/404");
|
||||
}
|
||||
|
||||
if (creator.error) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
let search = Strings.getQueryStringFromParams(req.query);
|
||||
|
||||
return res.redirect(`/${creator.username}/${slate.slatename}`);
|
||||
return res.redirect(`/${creator.username}/${slate.slatename}${search}`);
|
||||
});
|
||||
|
||||
server.get("/[$]/user/:id", async (req, res) => {
|
||||
const creator = await Data.getUserById({
|
||||
id: req.params.id,
|
||||
includeFiles: true,
|
||||
publicOnly: true,
|
||||
sanitize: true,
|
||||
});
|
||||
|
||||
if (!creator) {
|
||||
return res.redirect("/404");
|
||||
if (!creator || creator.error) {
|
||||
return res.redirect("/_/404");
|
||||
}
|
||||
|
||||
if (creator.error) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
let search = Strings.getQueryStringFromParams(req.query);
|
||||
|
||||
return res.redirect(`/${creator.username}`);
|
||||
return res.redirect(`/${creator.username}${search}`);
|
||||
});
|
||||
|
||||
server.get("/[$]/:id", async (req, res) => {
|
||||
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
|
||||
let isMac = Window.isMac(req.headers["user-agent"]);
|
||||
|
||||
const slate = await Data.getSlateById({
|
||||
id: req.params.id,
|
||||
includeFiles: true,
|
||||
sanitize: true,
|
||||
});
|
||||
|
||||
if (!slate) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
|
||||
if (slate.error) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
|
||||
if (!slate.isPublic) {
|
||||
return res.redirect("/403");
|
||||
if (!slate || slate.error) {
|
||||
return res.redirect("/_/404");
|
||||
}
|
||||
|
||||
const creator = await Data.getUserById({
|
||||
id: slate.ownerId,
|
||||
sanitize: true,
|
||||
});
|
||||
|
||||
if (!creator) {
|
||||
return res.redirect("/404");
|
||||
if (!creator || creator.error) {
|
||||
return res.redirect("/_/404");
|
||||
}
|
||||
|
||||
if (creator.error) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
let search = Strings.getQueryStringFromParams(req.query);
|
||||
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
|
||||
// let viewer = null;
|
||||
// if (id) {
|
||||
// viewer = await ViewerManager.getById({
|
||||
// id,
|
||||
// });
|
||||
// }
|
||||
|
||||
return app.render(req, res, "/_/slate", {
|
||||
viewer: null,
|
||||
creator,
|
||||
slate,
|
||||
isMobile,
|
||||
isMac,
|
||||
resources: EXTERNAL_RESOURCES,
|
||||
});
|
||||
return res.redirect(`/${creator.username}/${slate.slatename}${search}`);
|
||||
});
|
||||
|
||||
server.get("/:username", async (req, res) => {
|
||||
const username = req.params.username.toLowerCase();
|
||||
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
|
||||
let isMac = Window.isMac(req.headers["user-agent"]);
|
||||
|
||||
// TODO(jim): Temporary workaround
|
||||
if (!Validations.userRoute(req.params.username)) {
|
||||
if (!Validations.userRoute(username)) {
|
||||
return handler(req, res, req.url, {
|
||||
isMobile,
|
||||
resources: EXTERNAL_RESOURCES,
|
||||
@ -318,125 +297,120 @@ app.prepare().then(async () => {
|
||||
}
|
||||
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
const shouldViewerRedirect = await ViewerManager.shouldRedirect({ id });
|
||||
if (shouldViewerRedirect) {
|
||||
return res.redirect(
|
||||
`/_${Strings.createQueryParams({
|
||||
scene: "NAV_PROFILE",
|
||||
user: req.params.username,
|
||||
})}`
|
||||
);
|
||||
|
||||
let viewer = null;
|
||||
if (id) {
|
||||
viewer = await ViewerManager.getById({
|
||||
id,
|
||||
});
|
||||
}
|
||||
console.log(req.query);
|
||||
let { page } = NavigationData.getByHref(req.path, viewer);
|
||||
console.log(page);
|
||||
page = { ...page, params: req.query };
|
||||
console.log(page);
|
||||
|
||||
// let viewer = null;
|
||||
// if (id) {
|
||||
// viewer = await ViewerManager.getById({
|
||||
// id,
|
||||
// });
|
||||
// }
|
||||
|
||||
let creator = await Data.getUserByUsername({
|
||||
username: req.params.username.toLowerCase(),
|
||||
let user = await Data.getUserByUsername({
|
||||
username,
|
||||
includeFiles: true,
|
||||
sanitize: true,
|
||||
publicOnly: true,
|
||||
});
|
||||
|
||||
if (!creator) {
|
||||
return res.redirect("/404");
|
||||
if (!user) {
|
||||
return res.redirect("/_/404");
|
||||
}
|
||||
|
||||
if (creator.error) {
|
||||
return res.redirect("/404");
|
||||
if (user.error) {
|
||||
return res.redirect("/_/404");
|
||||
}
|
||||
|
||||
const slates = await Data.getSlatesByUserId({
|
||||
ownerId: creator.id,
|
||||
ownerId: user.id,
|
||||
sanitize: true,
|
||||
includeFiles: true,
|
||||
publicOnly: true,
|
||||
});
|
||||
|
||||
creator.slates = slates;
|
||||
user.slates = slates;
|
||||
|
||||
return app.render(req, res, "/_/profile", {
|
||||
viewer: null,
|
||||
creator,
|
||||
return app.render(req, res, "/_", {
|
||||
viewer,
|
||||
isMobile,
|
||||
isMac,
|
||||
data: user,
|
||||
page,
|
||||
resources: EXTERNAL_RESOURCES,
|
||||
exploreSlates,
|
||||
});
|
||||
});
|
||||
|
||||
server.get("/:username/cid::cid", async (req, res) => {
|
||||
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
|
||||
let isMac = Window.isMac(req.headers["user-agent"]);
|
||||
// server.get("/:username/cid::cid", async (req, res) => {
|
||||
// const username = req.params.username.toLowerCase();
|
||||
// const cid = req.params.cid.toLowerCase();
|
||||
|
||||
// TODO(jim): Temporary workaround
|
||||
if (!Validations.userRoute(req.params.username)) {
|
||||
return handler(req, res, req.url);
|
||||
}
|
||||
// let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
|
||||
// let isMac = Window.isMac(req.headers["user-agent"]);
|
||||
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
const shouldViewerRedirect = await ViewerManager.shouldRedirect({ id });
|
||||
if (shouldViewerRedirect) {
|
||||
return res.redirect(
|
||||
`/_${Strings.createQueryParams({
|
||||
scene: "NAV_PROFILE",
|
||||
user: req.params.username,
|
||||
cid: req.params.cid,
|
||||
})}`
|
||||
);
|
||||
}
|
||||
// // TODO(jim): Temporary workaround
|
||||
// if (!Validations.userRoute(username)) {
|
||||
// return handler(req, res, req.url);
|
||||
// }
|
||||
|
||||
// let viewer = null;
|
||||
// if (id) {
|
||||
// viewer = await ViewerManager.getById({
|
||||
// id,
|
||||
// });
|
||||
// }
|
||||
// const id = Utilities.getIdFromCookie(req);
|
||||
|
||||
let creator = await Data.getUserByUsername({
|
||||
username: req.params.username.toLowerCase(),
|
||||
includeFiles: true,
|
||||
sanitize: true,
|
||||
publicOnly: true,
|
||||
});
|
||||
// let user = await Data.getUserByUsername({
|
||||
// username,
|
||||
// includeFiles: true,
|
||||
// sanitize: true,
|
||||
// publicOnly: true,
|
||||
// });
|
||||
|
||||
if (!creator) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
// if (!user) {
|
||||
// return res.redirect("/_/404");
|
||||
// }
|
||||
|
||||
if (creator.error) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
// if (user.error) {
|
||||
// return res.redirect("/_/404");
|
||||
// }
|
||||
|
||||
const slates = await Data.getSlatesByUserId({
|
||||
ownerId: creator.id,
|
||||
sanitize: true,
|
||||
includeFiles: true,
|
||||
publicOnly: true,
|
||||
});
|
||||
// const slates = await Data.getSlatesByUserId({
|
||||
// ownerId: user.id,
|
||||
// sanitize: true,
|
||||
// includeFiles: true,
|
||||
// publicOnly: true,
|
||||
// });
|
||||
|
||||
creator.slates = slates;
|
||||
// user.slates = slates;
|
||||
|
||||
return app.render(req, res, "/_/profile", {
|
||||
viewer: null,
|
||||
creator,
|
||||
isMobile,
|
||||
isMac,
|
||||
resources: EXTERNAL_RESOURCES,
|
||||
cid: req.params.cid,
|
||||
});
|
||||
});
|
||||
// let viewer = null;
|
||||
// if (id) {
|
||||
// viewer = await ViewerManager.getById({
|
||||
// id,
|
||||
// });
|
||||
// }
|
||||
|
||||
// let page = NavigationData.getById("NAV_PROFILE", viewer);
|
||||
// page = { ...page, cid };
|
||||
|
||||
// return app.render(req, res, "/_", {
|
||||
// viewer,
|
||||
// isMobile,
|
||||
// isMac,
|
||||
// page,
|
||||
// data: user,
|
||||
// resources: EXTERNAL_RESOURCES,
|
||||
// });
|
||||
// });
|
||||
|
||||
server.get("/:username/:slatename", async (req, res) => {
|
||||
const username = req.params.username.toLowerCase();
|
||||
const slatename = req.params.slatename.toLowerCase();
|
||||
|
||||
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
|
||||
let isMac = Window.isMac(req.headers["user-agent"]);
|
||||
|
||||
// TODO(jim): Temporary workaround
|
||||
if (!Validations.userRoute(req.params.username)) {
|
||||
if (!Validations.userRoute(username)) {
|
||||
return handler(req, res, req.url, {
|
||||
isMobile,
|
||||
resources: EXTERNAL_RESOURCES,
|
||||
@ -444,146 +418,117 @@ app.prepare().then(async () => {
|
||||
}
|
||||
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
const shouldViewerRedirect = await ViewerManager.shouldRedirect({ id });
|
||||
if (shouldViewerRedirect) {
|
||||
return res.redirect(
|
||||
`/_${Strings.createQueryParams({
|
||||
scene: "NAV_SLATE",
|
||||
user: req.params.username,
|
||||
slate: req.params.slatename,
|
||||
})}`
|
||||
);
|
||||
|
||||
let viewer = null;
|
||||
if (id) {
|
||||
viewer = await ViewerManager.getById({
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
let { page } = NavigationData.getByHref(req.path, viewer);
|
||||
page = { ...page, params: req.query };
|
||||
|
||||
const slate = await Data.getSlateByName({
|
||||
slatename: req.params.slatename,
|
||||
username: req.params.username,
|
||||
slatename,
|
||||
username,
|
||||
includeFiles: true,
|
||||
sanitize: true,
|
||||
});
|
||||
|
||||
if (!slate) {
|
||||
return res.redirect("/404");
|
||||
if (!slate || slate.error || (!slate.isPublic && slate.ownerId !== id)) {
|
||||
return res.redirect("/_/404");
|
||||
}
|
||||
|
||||
if (slate.error) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
|
||||
if (!slate.isPublic && slate.ownerId !== id) {
|
||||
return res.redirect("/403");
|
||||
}
|
||||
|
||||
const creator = await Data.getUserById({
|
||||
const user = await Data.getUserById({
|
||||
id: slate.ownerId,
|
||||
sanitize: true,
|
||||
});
|
||||
|
||||
if (!creator) {
|
||||
return res.redirect("/404");
|
||||
if (!user) {
|
||||
return res.redirect("/_/404");
|
||||
}
|
||||
|
||||
if (creator.error) {
|
||||
return res.redirect("/404");
|
||||
if (user.error) {
|
||||
return res.redirect("/_/404");
|
||||
}
|
||||
|
||||
if (req.params.username !== creator.username) {
|
||||
return res.redirect("/403");
|
||||
}
|
||||
slate.user = user;
|
||||
|
||||
// let viewer = null;
|
||||
// if (id) {
|
||||
// viewer = await ViewerManager.getById({
|
||||
// id,
|
||||
// });
|
||||
// }
|
||||
|
||||
return app.render(req, res, "/_/slate", {
|
||||
viewer: null,
|
||||
creator,
|
||||
slate,
|
||||
return app.render(req, res, "/_", {
|
||||
viewer,
|
||||
isMobile,
|
||||
isMac,
|
||||
data: slate,
|
||||
page,
|
||||
resources: EXTERNAL_RESOURCES,
|
||||
});
|
||||
});
|
||||
|
||||
server.get("/:username/:slatename/cid::cid", async (req, res) => {
|
||||
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
|
||||
let isMac = Window.isMac(req.headers["user-agent"]);
|
||||
// server.get("/:username/:slatename/cid::cid", async (req, res) => {
|
||||
// const username = req.params.username.toLowerCase();
|
||||
// const slatename = req.params.slatename.toLowerCase();
|
||||
// const cid = req.params.cid.toLowerCase();
|
||||
|
||||
// TODO(jim): Temporary workaround
|
||||
if (!Validations.userRoute(req.params.username)) {
|
||||
return handler(req, res, req.url);
|
||||
}
|
||||
// let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
|
||||
// let isMac = Window.isMac(req.headers["user-agent"]);
|
||||
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
// // TODO(jim): Temporary workaround
|
||||
// if (!Validations.userRoute(username)) {
|
||||
// return handler(req, res, req.url);
|
||||
// }
|
||||
|
||||
const shouldViewerRedirect = await ViewerManager.shouldRedirect({ id });
|
||||
if (shouldViewerRedirect) {
|
||||
return res.redirect(
|
||||
`/_${Strings.createQueryParams({
|
||||
scene: "NAV_SLATE",
|
||||
user: req.params.username,
|
||||
slate: req.params.slatename,
|
||||
cid: req.params.cid,
|
||||
})}`
|
||||
);
|
||||
}
|
||||
// const id = Utilities.getIdFromCookie(req);
|
||||
|
||||
const slate = await Data.getSlateByName({
|
||||
slatename: req.params.slatename,
|
||||
username: req.params.username,
|
||||
includeFiles: true,
|
||||
sanitize: true,
|
||||
});
|
||||
// const slate = await Data.getSlateByName({
|
||||
// slatename,
|
||||
// username,
|
||||
// includeFiles: true,
|
||||
// sanitize: true,
|
||||
// });
|
||||
|
||||
if (!slate) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
// if (!slate) {
|
||||
// return res.redirect("/_/404");
|
||||
// }
|
||||
|
||||
if (slate.error) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
// if (slate.error || !slate.isPublic && slate.ownerId !== id) {
|
||||
// return res.redirect("/_/404");
|
||||
// }
|
||||
|
||||
if (!slate.isPublic && slate.ownerId !== id) {
|
||||
return res.redirect("/403");
|
||||
}
|
||||
// const user = await Data.getUserById({
|
||||
// id: slate.ownerId,
|
||||
// sanitize: true,
|
||||
// });
|
||||
|
||||
const creator = await Data.getUserById({
|
||||
id: slate.ownerId,
|
||||
sanitize: true,
|
||||
});
|
||||
// if (!user) {
|
||||
// return res.redirect("/_/404");
|
||||
// }
|
||||
|
||||
if (!creator) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
// if (user.error) {
|
||||
// return res.redirect("/_/404");
|
||||
// }
|
||||
|
||||
if (creator.error) {
|
||||
return res.redirect("/404");
|
||||
}
|
||||
// let viewer = null;
|
||||
// if (id) {
|
||||
// viewer = await ViewerManager.getById({
|
||||
// id,
|
||||
// });
|
||||
// }
|
||||
|
||||
if (req.params.username !== creator.username) {
|
||||
return res.redirect("/403");
|
||||
}
|
||||
// slate.user = user;
|
||||
|
||||
// let viewer = null;
|
||||
// if (id) {
|
||||
// viewer = await ViewerManager.getById({
|
||||
// id,
|
||||
// });
|
||||
// }
|
||||
// let page = NavigationData.getById("NAV_SLATE", viewer);
|
||||
// page = { ...page, cid };
|
||||
|
||||
return app.render(req, res, "/_/slate", {
|
||||
viewer: null,
|
||||
creator,
|
||||
slate,
|
||||
isMobile,
|
||||
isMac,
|
||||
resources: EXTERNAL_RESOURCES,
|
||||
cid: req.params.cid,
|
||||
});
|
||||
});
|
||||
// return app.render(req, res, "/_", {
|
||||
// viewer,
|
||||
// isMobile,
|
||||
// isMac,
|
||||
// data: slate,
|
||||
// page,
|
||||
// resources: EXTERNAL_RESOURCES,
|
||||
// });
|
||||
// });
|
||||
|
||||
server.all("*", async (r, s) => handler(r, s, r.url));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user