added standard typography styles

This commit is contained in:
Martina 2021-05-05 17:08:14 -07:00
parent a01c262f6b
commit 17f1ca18cf
72 changed files with 3905 additions and 3137 deletions

View File

@ -19,7 +19,7 @@ export const init = ({ resource = "", viewer, onUpdate, onNewActiveUser = () =>
console.log(`${resource}: init`); console.log(`${resource}: init`);
if (client) { if (client) {
console.log("ERROR: Already have client !?"); console.log("ERROR: Already has websocket client");
return client; return client;
} }
@ -31,7 +31,6 @@ export const init = ({ resource = "", viewer, onUpdate, onNewActiveUser = () =>
} }
const payload = { type: "SUBSCRIBE_VIEWER", data: { id: viewer.id } }; const payload = { type: "SUBSCRIBE_VIEWER", data: { id: viewer.id } };
console.log(payload);
client.send(JSON.stringify(payload)); client.send(JSON.stringify(payload));
}); });
@ -76,7 +75,7 @@ export const init = ({ resource = "", viewer, onUpdate, onNewActiveUser = () =>
} }
if (type === "UPDATE") { if (type === "UPDATE") {
onUpdate(data); onUpdate({ viewer: data });
} }
if (type === "UPDATE_USERS_ONLINE" && typeof onNewActiveUser === "function") { if (type === "UPDATE_USERS_ONLINE" && typeof onNewActiveUser === "function") {
@ -86,7 +85,7 @@ export const init = ({ resource = "", viewer, onUpdate, onNewActiveUser = () =>
client.addEventListener("close", (e) => { client.addEventListener("close", (e) => {
if (e.reason === "SIGN_OUT") { if (e.reason === "SIGN_OUT") {
window.location.replace("/_"); window.location.replace("/_/auth");
} else { } else {
setTimeout(() => { setTimeout(() => {
client = null; client = null;

View File

@ -61,6 +61,7 @@ export const system = {
active: "#00BB00", active: "#00BB00",
blurBlack: "#262626", blurBlack: "#262626",
bgBlurGray: "#403F42", bgBlurGray: "#403F42",
grayLight2: "#AEAEB2",
}; };
export const shadow = { export const shadow = {
@ -78,6 +79,7 @@ export const zindex = {
header: 4, header: 4,
modal: 6, modal: 6,
tooltip: 7, tooltip: 7,
cta: 8,
}; };
export const font = { export const font = {
@ -131,3 +133,5 @@ export const filetypes = {
videos: ["video/mpeg", "video/webm", "video/quicktime"], videos: ["video/mpeg", "video/webm", "video/quicktime"],
books: ["application/pdf", "application/epub+zip", "application/vnd.amazon.ebook"], books: ["application/pdf", "application/epub+zip", "application/vnd.amazon.ebook"],
}; };
export const linkPreviewSizeLimit = 5000000; //NOTE(martina): 5mb limit for twitter preview images

View File

@ -1,146 +1,205 @@
import * as Actions from "~/common/actions";
import * as Strings from "~/common/strings"; import * as Strings from "~/common/strings";
// NOTE(jim): export const getById = (id, viewer) => {
// Recursion for nested entities (any number). let target;
export const getCurrent = ({ id, data }) => {
let target = null;
let activeIds = {};
const findById = (state, id) => { if (id) {
for (let i = 0; i < state.length; i++) { target = navigation.find((each) => each.id === id);
if (state[i].id === id) { }
target = state[i]; if (!target) {
activeIds[state[i].id] = true; return { ...errorPage };
}
if (target.id === "NAV_SLATE") { if (viewer && target.id === authPage.id) {
target.slateId = data && data.id; return { ...activityPage }; //NOTE(martina): authenticated users should be redirected to the home page rather than the
} }
}
// if (!target && state[i].children) { if (!viewer && !target.externalAllowed) {
// activeIds[state[i].id] = true; return { ...authPage }; //NOTE(martina): redirect to sign in page if try to access internal page while logged out
// findById(state[i].children, id); }
// if (!target) { return { ...target };
// activeIds[state[i].id] = false; };
// }
// } 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 = [ export const navigation = [
{ errorPage,
id: "NAV_DATA", authPage,
decorator: "DATA", activityPage,
name: "Home", dataPage,
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,
},
{ {
id: "NAV_SLATES", id: "NAV_SLATES",
decorator: "SLATES",
name: "Collections", name: "Collections",
pageTitle: "Collections", pageTitle: "Your Collections",
ignore: true,
},
{
id: "NAV_SLATES_FOLLOWING",
decorator: "SLATES_FOLLOWING",
name: "Collections following",
pageTitle: "Collections following",
ignore: true, ignore: true,
pathname: "/_/collections",
mainNav: true,
}, },
// {
// id: "NAV_SEARCH",
// name: "Search",
// pageTitle: "Search Slate",
// ignore: true,
// pathname: "/_/search",
// mainNav: true,
// },
{ {
id: "NAV_DIRECTORY", id: "NAV_DIRECTORY",
decorator: "DIRECTORY",
name: "Directory", name: "Directory",
pageTitle: "Your directory", pageTitle: "Your Following",
}, pathname: "/_/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,
}, },
slatePage,
{ {
id: "NAV_FILECOIN", id: "NAV_FILECOIN",
decorator: "FILECOIN",
name: "Filecoin", name: "Filecoin",
pageTitle: "Archive on Filecoin", pageTitle: "Archive on Filecoin",
filecoin: true, pathname: "/_/filecoin",
}, },
{ {
id: "NAV_STORAGE_DEAL", id: "NAV_STORAGE_DEAL",
decorator: "STORAGE_DEAL",
name: "Storage Deal", name: "Storage Deal",
filecoin: true, pageTitle: "Filecoin Storage Deal",
pageTitle: "Make a one-off Filecoin storage deal", pathname: "/_/storage-deal",
}, },
{ {
id: "NAV_API", id: "NAV_API",
decorator: "API",
name: "API", name: "API",
pageTitle: "Developer API", pageTitle: "Developer API",
pathname: "/_/api",
}, },
{ {
id: "NAV_SETTINGS", id: "NAV_SETTINGS",
decorator: "SETTINGS", name: "Settings",
name: "Profile & Account Settings", pageTitle: "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",
ignore: true, ignore: true,
pathname: "/_/settings",
}, },
profilePage,
// {
// id: "NAV_FILE",
// name: "File",
// pageTitle: "A File",
// ignore: true,
// externalAllowed: true,
// },
]; ];

View File

@ -217,6 +217,34 @@ export const urlToCid = (url) => {
.replace("hub.textile.io/ipfs/", ""); .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) => { export const hexToRGBA = (hex, alpha = 1) => {
hex = hex.replace("#", ""); hex = hex.replace("#", "");
var r = parseInt(hex.length == 3 ? hex.slice(0, 1).repeat(2) : hex.slice(0, 2), 16); var r = parseInt(hex.length == 3 ? hex.slice(0, 1).repeat(2) : hex.slice(0, 2), 16);

148
common/styles.js Normal file
View 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;
}
`;

View File

@ -1692,6 +1692,22 @@ export const Menu = (props) => (
</svg> </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) => ( export const Globe = (props) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -25,7 +25,11 @@ export const authenticate = async (state) => {
const jwt = cookies.get(Credentials.session.key); const jwt = cookies.get(Credentials.session.key);
if (jwt) { 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); let response = await Actions.signIn(state);
@ -38,7 +42,7 @@ export const authenticate = async (state) => {
// + One week. // + One week.
// + Only requests to the same site. // + Only requests to the same site.
// + Not using sessionStorage so the cookie doesn't leave when the browser dies. // + 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: "/", path: "/",
maxAge: 3600 * 24 * 7, maxAge: 3600 * 24 * 7,
sameSite: "strict", sameSite: "strict",
@ -48,7 +52,7 @@ export const authenticate = async (state) => {
return response; 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 }) => { export const signOut = async ({ viewer }) => {
let wsclient = Websockets.getClient(); let wsclient = Websockets.getClient();
if (wsclient) { if (wsclient) {
@ -60,10 +64,14 @@ export const signOut = async ({ viewer }) => {
const jwt = cookies.get(Credentials.session.key); const jwt = cookies.get(Credentials.session.key);
if (jwt) { 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; return;
}; };
@ -79,11 +87,11 @@ export const deleteMe = async ({ viewer }) => {
await signOut({ viewer }); await signOut({ viewer });
let wsclient = Websockets.getClient(); // let wsclient = Websockets.getClient();
if (wsclient) { // if (wsclient) {
await Websockets.deleteClient(); // await Websockets.deleteClient();
wsclient = null; // wsclient = null;
} // }
return response; return response;
}; };

View File

@ -115,10 +115,13 @@ export class Alert extends React.Component {
_handleDismissPrivacyAlert = (e) => { _handleDismissPrivacyAlert = (e) => {
Actions.updateStatus({ status: { hidePrivacyAlert: true } }); Actions.updateStatus({ status: { hidePrivacyAlert: true } });
this.props.onUpdateViewer({ this.props.onAction({
data: { type: "UDPATE_VIEWER",
...this.props.viewer.data, viewer: {
status: { ...this.props.viewer.data.status, hidePrivacyAlert: true }, data: {
...this.props.viewer.data,
status: { ...this.props.viewer.data.status, hidePrivacyAlert: true },
},
}, },
}); });
}; };
@ -126,7 +129,7 @@ export class Alert extends React.Component {
render() { render() {
if (!this.state.message) { if (!this.state.message) {
if (!this.props.fileLoading || !Object.keys(this.props.fileLoading).length) { if (!this.props.fileLoading || !Object.keys(this.props.fileLoading).length) {
if (this.props.noWarning) { if (this.props.noWarning || !this.props.viewer) {
return null; return null;
} }

View File

@ -2,7 +2,7 @@ import * as React from "react";
import * as NavigationData from "~/common/navigation-data"; import * as NavigationData from "~/common/navigation-data";
import * as Actions from "~/common/actions"; import * as Actions from "~/common/actions";
import * as Strings from "~/common/strings"; 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 Credentials from "~/common/credentials";
import * as Constants from "~/common/constants"; import * as Constants from "~/common/constants";
import * as Validations from "~/common/validations"; import * as Validations from "~/common/validations";
@ -15,6 +15,7 @@ import * as Events from "~/common/custom-events";
// NOTE(jim): // NOTE(jim):
// Scenes each have an ID and can be navigated to with _handleAction // Scenes each have an ID and can be navigated to with _handleAction
import SceneError from "~/scenes/SceneError";
import SceneEditAccount from "~/scenes/SceneEditAccount"; import SceneEditAccount from "~/scenes/SceneEditAccount";
import SceneFile from "~/scenes/SceneFile"; import SceneFile from "~/scenes/SceneFile";
import SceneFilesFolder from "~/scenes/SceneFilesFolder"; import SceneFilesFolder from "~/scenes/SceneFilesFolder";
@ -49,6 +50,7 @@ import SidebarEditTags from "~/components/sidebars/SidebarEditTags";
import ApplicationHeader from "~/components/core/ApplicationHeader"; import ApplicationHeader from "~/components/core/ApplicationHeader";
import ApplicationLayout from "~/components/core/ApplicationLayout"; import ApplicationLayout from "~/components/core/ApplicationLayout";
import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper"; import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
import CTATransition from "~/components/core/CTATransition";
import { GlobalModal } from "~/components/system/components/GlobalModal"; import { GlobalModal } from "~/components/system/components/GlobalModal";
import { OnboardingModal } from "~/components/core/OnboardingModal"; import { OnboardingModal } from "~/components/core/OnboardingModal";
@ -56,6 +58,7 @@ import { SearchModal } from "~/components/core/SearchModal";
import { Alert } from "~/components/core/Alert"; import { Alert } from "~/components/core/Alert";
import { announcements } from "~/components/core/OnboardingModal"; import { announcements } from "~/components/core/OnboardingModal";
import { Logo } from "~/common/logo"; import { Logo } from "~/common/logo";
import { LoaderSpinner } from "~/components/system/components/Loaders";
const SIDEBARS = { const SIDEBARS = {
SIDEBAR_FILECOIN_ARCHIVE: <SidebarFilecoinArchive />, SIDEBAR_FILECOIN_ARCHIVE: <SidebarFilecoinArchive />,
@ -73,23 +76,20 @@ const SIDEBARS = {
}; };
const SCENES = { const SCENES = {
ACTIVITY: <SceneActivity tab={0} />, NAV_ERROR: <SceneError />,
EXPLORE: <SceneActivity tab={1} />, NAV_SIGN_IN: <SceneSignIn />,
DIRECTORY: <SceneDirectory />, NAV_ACTIVITY: <SceneActivity />,
PROFILE_FILES: <SceneProfile tab={0} />, NAV_DIRECTORY: <SceneDirectory />,
PROFILE: <SceneProfile tab={1} />, NAV_PROFILE: <SceneProfile />,
PROFILE_PEERS: <SceneProfile tab={2} />, NAV_DATA: <SceneFilesFolder />,
DATA: <SceneFilesFolder />, // NAV_FILE: <SceneFile />,
FILE: <SceneFile />, NAV_SLATE: <SceneSlate />,
SLATE: <SceneSlate />, NAV_API: <SceneSettingsDeveloper />,
API: <SceneSettingsDeveloper />, NAV_SETTINGS: <SceneEditAccount />,
SETTINGS: <SceneEditAccount />, NAV_SLATES: <SceneSlates />,
SLATES: <SceneSlates tab={0} />, NAV_DIRECTORY: <SceneDirectory />,
SLATES_FOLLOWING: <SceneSlates tab={1} />, NAV_FILECOIN: <SceneArchive />,
DIRECTORY: <SceneDirectory tab={0} />, NAV_STORAGE_DEAL: <SceneMakeFilecoinDeal />,
DIRECTORY_FOLLOWERS: <SceneDirectory tab={1} />,
FILECOIN: <SceneArchive />,
STORAGE_DEAL: <SceneMakeFilecoinDeal />,
}; };
let mounted; let mounted;
@ -100,15 +100,18 @@ export default class ApplicationPage extends React.Component {
state = { state = {
selected: {}, selected: {},
viewer: this.props.viewer, viewer: this.props.viewer,
data: null, page: this.props.page || {},
data: this.props.data,
activePage: this.props.page?.id,
sidebar: null, sidebar: null,
online: null, online: null,
isMobile: this.props.isMobile, isMobile: this.props.isMobile,
loaded: false,
activeUsers: null, activeUsers: null,
loading: false,
}; };
async componentDidMount() { async componentDidMount() {
this._handleWindowResize();
if (mounted) { if (mounted) {
return false; return false;
} }
@ -128,8 +131,9 @@ export default class ApplicationPage extends React.Component {
if (this.state.viewer) { if (this.state.viewer) {
await this._handleSetupWebsocket(); await this._handleSetupWebsocket();
} }
console.log(this.props.page);
this._handleURLRedirect(); console.log(this.props.data);
// this._handleBackForward();
} }
componentWillUnmount() { componentWillUnmount() {
@ -160,17 +164,9 @@ export default class ApplicationPage extends React.Component {
this._handleRegisterFileLoading({ fileLoading }); this._handleRegisterFileLoading({ fileLoading });
let page; const page = this.state.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 slate = null; 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; 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) => // let setAsyncState = (newState) =>
// new Promise((resolve) => // new Promise((resolve) =>
// this.setState( // this.setState(
@ -194,17 +191,11 @@ export default class ApplicationPage extends React.Component {
// await setAsyncState(newViewerState); // await setAsyncState(newViewerState);
//NOTE(martina): if updating viewer affects this.state.data (e.g. you're viewing your own slate), update data as well //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) { if (viewer?.slates?.length) {
let page; const page = this.state.page;
if (typeof window !== "undefined") { if (page?.id === "NAV_SLATE" && this.state.data?.ownerId === this.state.viewer.id) {
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) {
let data = this.state.data; let data = this.state.data;
for (let slate of newViewerState.slates) { for (let slate of viewer.slates) {
if (slate.id === data.id) { if (slate.id === data.id) {
data = slate; data = slate;
break; break;
@ -212,13 +203,14 @@ export default class ApplicationPage extends React.Component {
} }
this.setState( this.setState(
{ {
viewer: { ...this.state.viewer, ...newViewerState }, viewer: { ...this.state.viewer, ...viewer },
data, data,
}, },
() => { () => {
if (callback) { if (callback) {
callback(); callback();
} }
console.log(this.state.viewer);
} }
); );
return; return;
@ -226,9 +218,10 @@ export default class ApplicationPage extends React.Component {
} }
this.setState( this.setState(
{ {
viewer: { ...this.state.viewer, ...newViewerState }, viewer: { ...this.state.viewer, ...viewer },
}, },
() => { () => {
console.log(this.state.viewer);
if (callback) { if (callback) {
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? //TODO(martina): maybe add a default window.history.replacestate where it pushes the new data to browser?
this.setState({ data }, () => { this.setState({ data }, () => {
if (callback) { if (callback) {
@ -256,7 +250,6 @@ export default class ApplicationPage extends React.Component {
console.log("WEBSOCKET: NOT AUTHENTICATED"); console.log("WEBSOCKET: NOT AUTHENTICATED");
return; return;
} }
console.log("inside handle setup websocket in application.js");
wsclient = Websockets.init({ wsclient = Websockets.init({
resource: this.props.resources.pubsub, resource: this.props.resources.pubsub,
viewer: this.state.viewer, viewer: this.state.viewer,
@ -311,17 +304,10 @@ export default class ApplicationPage extends React.Component {
return null; return null;
} }
let page; let page = this.state.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 slate = null; 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; slate = this.state.data;
} }
@ -485,17 +471,17 @@ export default class ApplicationPage extends React.Component {
return; return;
} }
return this._handleAuthenticate(state, true); this._handleAuthenticate(state, true);
}; };
_handleAuthenticate = async (state, newAccount) => { _handleAuthenticate = async (state, newAccount) => {
let response = await UserBehaviors.authenticate(state); let response = await UserBehaviors.authenticate(state);
if (!response) { if (Events.hasError(response)) {
return; return;
} }
let viewer = await UserBehaviors.hydrate(); let viewer = await UserBehaviors.hydrate();
if (!viewer) { if (Events.hasError(viewer)) {
return viewer; return;
} }
this.setState({ viewer }); this.setState({ viewer });
@ -507,7 +493,6 @@ export default class ApplicationPage extends React.Component {
unseenAnnouncements.push(feature); unseenAnnouncements.push(feature);
} }
} }
console.log(unseenAnnouncements);
if (newAccount || unseenAnnouncements.length) { if (newAccount || unseenAnnouncements.length) {
Events.dispatchCustomEvent({ Events.dispatchCustomEvent({
@ -530,28 +515,28 @@ export default class ApplicationPage extends React.Component {
Actions.updateSearch("create-user"); Actions.updateSearch("create-user");
} }
let redirected = this._handleURLRedirect(); // let redirected = this._handleURLRedirect();
if (!redirected) { // if (!redirected) {
this._handleAction({ type: "NAVIGATE", value: "NAV_DATA" }); // this._handleAction({ type: "NAVIGATE", value: "NAV_DATA" });
} // }
return response; return response;
}; };
_handleURLRedirect = () => { // _handleURLRedirect = () => {
const id = Window.getQueryParameterByName("scene"); // const id = Window.getQueryParameterByName("scene");
const user = Window.getQueryParameterByName("user"); // const username = Window.getQueryParameterByName("username");
const slate = Window.getQueryParameterByName("slate"); // const slatename = Window.getQueryParameterByName("slatename");
const cid = Window.getQueryParameterByName("cid"); // const cid = Window.getQueryParameterByName("cid");
if (!Strings.isEmpty(id) && this.state.viewer) { // if (!Strings.isEmpty(id)) {
this._handleNavigateTo({ id, user, slate, cid }, null, true); // this._handleNavigateTo({ id, username, slatename, cid }, null, true);
return true; // return true;
} // }
if (!this.state.loaded) { // if (!this.state.loaded) {
this.setState({ loaded: true }); // this.setState({ loaded: true });
} // }
return false; // return false;
}; // };
_handleSelectedChange = (e) => { _handleSelectedChange = (e) => {
this.setState({ this.setState({
@ -565,30 +550,15 @@ export default class ApplicationPage extends React.Component {
_handleAction = (options) => { _handleAction = (options) => {
if (options.type === "NAVIGATE") { 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 return this._handleNavigateTo(options);
// + 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
);
} }
if (options.type === "NEW_WINDOW") { if (options.type === "UPDATE_VIEWER") {
return window.open(options.value); return this._handleUpdateViewer(options);
} }
if (options.type === "ACTION") { if (options.type === "UPDATE_PARAMS") {
Events.dispatchMessage({ message: JSON.stringify(options), status: "INFO" }); return this._handleUpdatePageParams(options);
}
if (options.type === "DOWNLOAD") {
Events.dispatchMessage({ message: JSON.stringify(options), status: "INFO" });
} }
if (options.type === "SIDEBAR") { if (options.type === "SIDEBAR") {
@ -602,125 +572,167 @@ export default class ApplicationPage extends React.Component {
return this._handleRegisterFileCancelled({ key: options.value }); return this._handleRegisterFileCancelled({ key: options.value });
} }
return alert(JSON.stringify(options)); if (options.type === "NEW_WINDOW") {
}; return window.open(options.value);
_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 (!this.state.loaded) { console.log("Error: Failed to _handleAction because TYPE did not match any known actions");
this.setState({ loaded: true }); };
_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; let body = document.documentElement || document.body;
if (data) { if (page.id === "NAV_SLATE" || page.id === "NAV_PROFILE") {
this.setState( state.loading = true;
{
data,
sidebar: null,
},
() => body.scrollTo(0, 0)
);
} else {
this.setState(
{
sidebar: null,
},
() => body.scrollTo(0, 0)
);
} }
this.setState(state, () => {
if (!popstate) {
body.scrollTo(0, 0);
}
if (page.id === "NAV_SLATE" || page.id === "NAV_PROFILE") {
this.updateDataAndPathname({ page, details });
}
});
}; };
_handleBackForward = (e) => { updateDataAndPathname = async ({ page, details }) => {
let page = window.history.state; let pathname = page.pathname.split("?")[0];
this.setState({ let search = Strings.getQueryStringFromParams(page.params);
sidebar: null, let data;
data: page.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() { render() {
// NOTE(jim): Not authenticated. let page = this.state.page;
if (!this.state.viewer) { if (!page?.id) {
return ( page = NavigationData.getById(null, this.state.viewer);
<WebsitePrototypeWrapper }
title="Slate: sign in" let headerElement;
description="Sign in to your Slate account to manage your assets." if (page.id !== "NAV_SIGN_IN") {
url="https://slate.host/_" headerElement = (
> <ApplicationHeader
<Alert noWarning style={{ top: 0, zIndex: Constants.zindex.sidebar }} /> viewer={this.state.viewer}
<SceneSignIn navigation={NavigationData.navigation}
onCreateUser={this._handleCreateUser} page={page}
onAuthenticate={this._handleAuthenticate} onAction={this._handleAction}
onAction={this._handleAction} isMobile={this.state.isMobile}
/> isMac={this.props.isMac}
</WebsitePrototypeWrapper> 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. const scene = React.cloneElement(SCENES[page.id], {
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], {
key: this.state.data?.id, key: this.state.data?.id,
page: page, page: page,
current: current.target,
data: this.state.data, data: this.state.data,
viewer: this.state.viewer, viewer: this.state.viewer,
selected: this.state.selected, selected: this.state.selected,
onSelectedChange: this._handleSelectedChange, onSelectedChange: this._handleSelectedChange,
onAuthenticate: this._handleAuthenticate,
onCreateUser: this._handleCreateUser,
onAction: this._handleAction, onAction: this._handleAction,
onUpload: this._handleUploadFiles, onUpload: this._handleUploadFiles,
onUpdateData: this._handleUpdateData,
onUpdateViewer: this._handleUpdateViewer,
sceneId: current.target.id,
isMobile: this.state.isMobile, isMobile: this.state.isMobile,
isMac: this.props.isMac, isMac: this.props.isMac,
resources: this.props.resources, resources: this.props.resources,
activeUsers: this.state.activeUsers, activeUsers: this.state.activeUsers,
userBucketCID: this.state.userBucketCID, userBucketCID: this.state.userBucketCID,
external: !!!this.state.viewer,
}); });
let sidebarElement; let sidebarElement;
if (this.state.sidebar) { if (this.state.sidebar) {
sidebarElement = React.cloneElement(this.state.sidebar, { sidebarElement = React.cloneElement(this.state.sidebar, {
current: current.target, page: page,
selected: this.state.selected, selected: this.state.selected,
viewer: this.state.viewer, viewer: this.state.viewer,
data: this.state.data, data: this.state.data,
@ -730,34 +742,30 @@ export default class ApplicationPage extends React.Component {
onCancel: this._handleDismissSidebar, onCancel: this._handleDismissSidebar,
onUpload: this._handleUploadFiles, onUpload: this._handleUploadFiles,
onAction: this._handleAction, onAction: this._handleAction,
onUpdateViewer: this._handleUpdateViewer,
resources: this.props.resources, resources: this.props.resources,
}); });
} }
const title = `Slate : ${current.target.pageTitle}`; const title = `Slate: ${page.pageTitle}`;
const description = ""; const description = "";
const url = "https://slate.host/_"; const url = "https://slate.host/_";
// console.log("application state:", { target: current.target }); // if (!this.state.loaded) {
// console.log("application state:", { data: this.state.data }); // return (
// <WebsitePrototypeWrapper description={description} title={title} url={url}>
if (!this.state.loaded) { // <div
return ( // style={{
<WebsitePrototypeWrapper description={description} title={title} url={url}> // height: "100vh",
<div // display: "flex",
style={{ // alignItems: "center",
height: "100vh", // justifyContent: "center",
display: "flex", // }}
alignItems: "center", // >
justifyContent: "center", // <Logo style={{ width: "20vw", maxWidth: "200px" }} />
}} // </div>
> // </WebsitePrototypeWrapper>
<Logo style={{ width: "20vw", maxWidth: "200px" }} /> // );
</div> // }
</WebsitePrototypeWrapper>
);
}
return ( return (
<React.Fragment> <React.Fragment>
<WebsitePrototypeWrapper description={description} title={title} url={url}> <WebsitePrototypeWrapper description={description} title={title} url={url}>
@ -767,13 +775,23 @@ export default class ApplicationPage extends React.Component {
sidebar={sidebarElement} sidebar={sidebarElement}
onDismissSidebar={this._handleDismissSidebar} onDismissSidebar={this._handleDismissSidebar}
fileLoading={this.state.fileLoading} fileLoading={this.state.fileLoading}
filecoin={current.target.filecoin}
isMobile={this.state.isMobile} isMobile={this.state.isMobile}
isMac={this.props.isMac} isMac={this.props.isMac}
viewer={this.state.viewer} 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> </ApplicationLayout>
<GlobalModal /> <GlobalModal />
<SearchModal <SearchModal
@ -782,7 +800,8 @@ export default class ApplicationPage extends React.Component {
isMobile={this.props.isMobile} isMobile={this.props.isMobile}
resourceURI={this.props.resources.search} resourceURI={this.props.resources.search}
/> />
{!this.state.loaded ? ( <CTATransition onAction={this._handleAction} />
{/* {!this.state.loaded ? (
<div <div
style={{ style={{
position: "absolute", position: "absolute",
@ -795,7 +814,7 @@ export default class ApplicationPage extends React.Component {
> >
<Logo style={{ width: "20vw", maxWidth: "200px" }} /> <Logo style={{ width: "20vw", maxWidth: "200px" }} />
</div> </div>
) : null} ) : null} */}
</WebsitePrototypeWrapper> </WebsitePrototypeWrapper>
</React.Fragment> </React.Fragment>
); );

View File

@ -2,88 +2,80 @@ import * as React from "react";
import * as Constants from "~/common/constants"; import * as Constants from "~/common/constants";
import * as SVG from "~/common/svg"; import * as SVG from "~/common/svg";
import * as Events from "~/common/custom-events"; 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 { css, keyframes } from "@emotion/react";
import { Boundary } from "~/components/system/components/fragments/Boundary"; import { Boundary } from "~/components/system/components/fragments/Boundary";
import { PopoverNavigation } from "~/components/system"; 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 = { const STYLES_NAV_LINKS = css`
// 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;
display: flex; display: flex;
align-items: center; flex-direction: row;
justify-content: center;
color: ${Constants.system.textGray}; @media (max-width: ${Constants.sizes.mobile}px) {
flex-direction: column;
overflow: hidden;
}
`; `;
const STYLES_ICON_ELEMENT = css` const STYLES_NAV_LINK = css`
height: 40px;
width: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
color: ${Constants.system.textGray}; color: ${Constants.system.textGray};
user-select: none; text-decoration: none;
transition: 200ms ease color;
display: block;
cursor: pointer; cursor: pointer;
pointer-events: auto; padding: 4px 24px;
font-size: ${Constants.typescale.lvl1};
:hover { :hover {
color: ${Constants.system.brand}; 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` const STYLES_APPLICATION_HEADER_CONTAINER = css`
display: flex;
align-items: center;
justify-content: space-between;
width: 100%; width: 100%;
height: 56px; background-color: ${Constants.system.white};
padding: 0 24px 0 16px;
pointer-events: none;
background-color: ${Constants.system.foreground};
@supports ((-webkit-backdrop-filter: blur(25px)) or (backdrop-filter: blur(25px))) { @supports ((-webkit-backdrop-filter: blur(25px)) or (backdrop-filter: blur(25px))) {
-webkit-backdrop-filter: blur(25px); -webkit-backdrop-filter: blur(25px);
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) { @media (max-width: ${Constants.sizes.mobile}px) {
padding: 0px 12px; padding: 0px 24px;
width: 100%; width: 100%;
} }
`; `;
const STYLES_LEFT = css` const STYLES_LEFT = css`
flex-shrink: 0; flex-shrink: 0;
${"" /* width: 352px; */}
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
@ -92,38 +84,34 @@ const STYLES_LEFT = css`
const STYLES_MIDDLE = css` const STYLES_MIDDLE = css`
min-width: 10%; min-width: 10%;
width: 100%; width: 100%;
padding: 0 24px 0 48px; padding: 0 24px;
`; display: flex;
justify-content: center;
const STYLES_MOBILE_HIDDEN = css`
@media (max-width: ${Constants.sizes.mobile}px) {
display: none;
}
`; `;
const STYLES_RIGHT = css` const STYLES_RIGHT = css`
min-width: 10%; flex-shrink: 0;
width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
`; `;
const rotate = keyframes` const STYLES_BACKGROUND = css`
0% { position: absolute;
transform: rotate(0deg); width: 100vw;
} height: 100vh;
100% { background-color: ${Constants.system.bgBlurGrayBlack};
transform: rotate(360deg); pointer-events: auto;
}
`;
const STYLES_ROTATION = css` @keyframes fade-in {
animation: ${rotate} 1s linear infinite; from {
`; opacity: 50%;
}
const STYLES_STATIC = css` to {
transition: 200ms ease all; opacity: 100%;
}
}
animation: fade-in 200ms ease-out;
`; `;
export default class ApplicationHeader extends React.Component { export default class ApplicationHeader extends React.Component {
@ -135,6 +123,7 @@ export default class ApplicationHeader extends React.Component {
); );
state = { state = {
showDropdown: false,
popup: null, popup: null,
isRefreshing: false, isRefreshing: false,
}; };
@ -162,6 +151,7 @@ export default class ApplicationHeader extends React.Component {
}; };
_handleCreateSearch = (e) => { _handleCreateSearch = (e) => {
this.setState({ showDropdown: false });
Events.dispatchCustomEvent({ Events.dispatchCustomEvent({
name: "show-search", name: "show-search",
detail: {}, detail: {},
@ -169,6 +159,7 @@ export default class ApplicationHeader extends React.Component {
}; };
_handleTogglePopup = (value) => { _handleTogglePopup = (value) => {
console.log(value);
if (!value || this.state.popup === value) { if (!value || this.state.popup === value) {
this.setState({ popup: null }); this.setState({ popup: null });
} else { } else {
@ -177,135 +168,217 @@ export default class ApplicationHeader extends React.Component {
}; };
render() { render() {
// const isBackDisabled = this.props.currentIndex === 0 || this.props.history.length < 2; const navigation = this.props.navigation.filter((item) => item.mainNav);
// const isForwardDisabled = if (!this.props.viewer) {
// this.props.currentIndex === this.props.history.length - 1 || this.props.history.length < 2; const searchComponent = (
return ( <div
<header css={STYLES_APPLICATION_HEADER}> onClick={this._handleCreateSearch}
<div css={STYLES_LEFT}> css={Styles.HORIZONTAL_CONTAINER_CENTERED}
<span style={{ border: "none", pointerEvents: "auto", cursor: "pointer" }}
css={STYLES_ICON_ELEMENT} >
style={{ position: "relative" }} <SVG.Search
onClick={() => this._handleTogglePopup("nav")} height="16px"
> style={{ color: Constants.system.textGrayDark, marginRight: 8 }}
<SVG.Menu height="24px" /> />
{this.state.popup === "nav" ? ( <span css={Styles.BODY_02} style={{ color: Constants.system.textGray }}>
<Boundary Search Slate...
captureResize={true} </span>
captureScroll={false} </div>
enabled );
onOutsideRectEvent={() => this._handleTogglePopup()}
style={this.props.style} //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 <span css={Styles.MOBILE_HIDDEN}>
style={{ top: 44, left: 8, width: 220, padding: "12px 24px" }} <ButtonTertiary
itemStyle={{ padding: "12px 0" }} style={{
navigation={this.props.navigation padding: "0px 12px",
.filter((item) => !item.ignore) minHeight: "30px",
.map((item) => { fontFamily: Constants.font.text,
return { marginRight: 8,
text: ( }}
<div >
style={{ Sign in
display: "flex", </ButtonTertiary>
alignItems: "center", </span>
color: this.props.activeIds[item.id] ? Constants.system.brand : null, </Link>
}} <Link
> href="/_/auth?tab=signup"
<span style={{ marginRight: 16 }}>{IconMap[item.decorator]}</span> onAction={this.props.onAction}
<span style={{ fontSize: 18 }}>{item.name}</span> style={{ pointerEvents: "auto" }}
</div> >
), <ButtonPrimary
onClick: (e) => { style={{
this._handleTogglePopup(); padding: "0px 12px",
this.props.onAction({ minHeight: "30px",
type: "NAVIGATE", fontFamily: Constants.font.text,
value: item.id, }}
}); >
}, Sign up
}; </ButtonPrimary>
})} </Link>
/> </div>
</Boundary> </div>
) : null} </header>
</span> );
{/* <span css={STYLES_MOBILE_HIDDEN} style={{ marginLeft: 32 }}> }
<span const mobilePopup = (
css={STYLES_ICON_ELEMENT} // <Boundary
style={ // captureResize={false}
isBackDisabled ? { cursor: "not-allowed", color: Constants.system.border } : null // captureScroll={false}
} // enabled={this.state.popup === "profile"}
onClick={isBackDisabled ? () => {} : this.props.onBack} // onOutsideRectEvent={(e) => {
> // e.stopPropagation();
<SVG.ChevronLeft height="28px" /> // e.preventDefault();
</span> // this._handleTogglePopup(e);
<span // }}
css={STYLES_ICON_ELEMENT} // >
style={ <>
isForwardDisabled <ApplicationUserControlsPopup
? { cursor: "not-allowed", color: Constants.system.border, marginLeft: 8 } popup={this.state.popup}
: { marginLeft: 8 } onTogglePopup={this._handleTogglePopup}
} viewer={this.props.viewer}
onClick={isForwardDisabled ? () => {} : this.props.onForward} onAction={this.props.onAction}
> style={{ pointerEvents: "auto", paddingBottom: 16 }}
<SVG.ChevronRight height="28px" /> />
</span> <div css={STYLES_BACKGROUND} />
</span> */} </>
{/* <div // </Boundary>
style={{ );
height: 28,
margin: "0px 12px", const mobileDropdown = (
borderRight: `1px solid ${Constants.system.border}`, <>
}} <Boundary
/> */} captureResize={false}
<span captureScroll={false}
css={STYLES_ICON_ELEMENT} enabled={this.state.showDropdown}
style={{ marginLeft: 24 }} onOutsideRectEvent={(e) => {
onClick={this._handleCreateSearch} e.stopPropagation();
> e.preventDefault();
<SVG.Search height="24px" /> this.setState({ showDropdown: false });
</span> }}
<span css={STYLES_MOBILE_HIDDEN}> >
<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 <div
style={{ onClick={this._handleCreateSearch}
display: "flex", css={STYLES_NAV_LINK}
alignItems: "center", style={{ border: "none" }}
cursor: "pointer",
}}
> >
<div css={STYLES_SHORTCUTS} style={{ width: "auto" }}> Search
{this.searchModKey} </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>
<div css={STYLES_SHORTCUTS}>F</div>
</div> </div>
</span> </span>
</div> <span css={Styles.MOBILE_ONLY}>
{/* <div css={STYLES_MIDDLE} /> */} <div css={STYLES_APPLICATION_HEADER}>
<div css={STYLES_RIGHT}> <div css={STYLES_LEFT}>
{/* <span css={STYLES_MOBILE_HIDDEN}> <div
<span css={Styles.ICON_CONTAINER}
css={STYLES_ICON_ELEMENT} style={{ pointerEvents: "auto" }}
onClick={() => onClick={() => this.setState({ showDropdown: !this.state.showDropdown })}
this.props.onAction({ >
type: "SIDEBAR", <SVG.MenuMinimal height="16px" />
value: "SIDEBAR_HELP", </div>
}) </div>
} <div css={STYLES_MIDDLE}>
> <Symbol style={{ height: 24 }} />
<SVG.Help height="24px" /> </div>
</span> <div css={STYLES_RIGHT}>
</span> */} <span style={{ pointerEvents: "auto", marginLeft: 24 }}>
<span style={{ pointerEvents: "auto", marginLeft: 24 }}> <ApplicationUserControls
<ApplicationUserControls popup={false}
popup={this.state.popup} onTogglePopup={this._handleTogglePopup}
onTogglePopup={this._handleTogglePopup} viewer={this.props.viewer}
viewer={this.props.viewer} onAction={this.props.onAction}
onAction={this.props.onAction} />
/> </span>
</div>
</div>
{this.state.popup === "profile"
? mobilePopup
: this.state.showDropdown
? mobileDropdown
: null}
</span> </span>
</div> </header>
</header> </>
); );
} }
} }

View File

@ -169,23 +169,20 @@ export default class ApplicationLayout extends React.Component {
return ( return (
<React.Fragment> <React.Fragment>
<div css={STYLES_CONTENT}> <div css={STYLES_CONTENT}>
{/* <GlobalTooltip elementRef={this._body} allowedTypes={["body"]} /> */}
<GlobalTooltip /> <GlobalTooltip />
{this.props.header && (
<div <div
css={STYLES_HEADER} css={STYLES_HEADER}
style={{ top: this.props.isMobile ? this.state.headerTop : null }} style={{ top: this.props.isMobile ? this.state.headerTop : null }}
> >
{this.props.header} {this.props.header}
</div> </div>
)}
<Alert <Alert
noWarning={this.props.viewer.data.status?.hidePrivacyAlert} noWarning={this.props.viewer ? this.props.viewer.data.status?.hidePrivacyAlert : false}
fileLoading={this.props.fileLoading} fileLoading={this.props.fileLoading}
onAction={this.props.onAction} onAction={this.props.onAction}
filecoin={this.props.filecoin}
id={this.props.isMobile ? "slate-mobile-alert" : null} id={this.props.isMobile ? "slate-mobile-alert" : null}
onUpdateViewer={this.props.onUpdateViewer}
viewer={this.props.viewer} viewer={this.props.viewer}
style={ style={
this.props.isMobile this.props.isMobile

View File

@ -1,10 +1,11 @@
import * as React from "react"; import * as React from "react";
import * as Constants from "~/common/constants"; 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 * as UserBehaviors from "~/common/user-behaviors";
import { PopoverNavigation } from "~/components/system"; import { PopoverNavigation } from "~/components/system";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { Link } from "~/components/core/Link";
import { Boundary } from "~/components/system/components/fragments/Boundary"; import { Boundary } from "~/components/system/components/fragments/Boundary";
@ -47,19 +48,19 @@ const STYLES_PROFILE_MOBILE = css`
`; `;
const STYLES_PROFILE_IMAGE = css` const STYLES_PROFILE_IMAGE = css`
background-color: ${Constants.system.white}; background-color: ${Constants.system.foreground};
background-size: cover; background-size: cover;
background-position: 50% 50%; background-position: 50% 50%;
flex-shrink: 0; flex-shrink: 0;
height: 32px; height: 24px;
width: 32px; width: 24px;
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
@media (max-width: ${Constants.sizes.mobile}px) { ${"" /* @media (max-width: ${Constants.sizes.mobile}px) {
height: 24px; height: 24px;
width: 24px; width: 24px;
} } */}
`; `;
const STYLES_PROFILE_USERNAME = css` const STYLES_PROFILE_USERNAME = css`
@ -100,35 +101,10 @@ const STYLES_ITEM_BOX = css`
} }
`; `;
const STYLES_MENU = css` export class ApplicationUserControlsPopup extends React.Component {
position: absolute; _handleAction = (props) => {
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();
this.props.onTogglePopup(); this.props.onTogglePopup();
return this.props.onAction(data); this.props.onAction(props);
}; };
_handleSignOut = (e) => { _handleSignOut = (e) => {
@ -139,55 +115,166 @@ export default class ApplicationUserControls extends React.Component {
}; };
render() { render() {
let tooltip = ( if (this.props.popup === "profile") {
<Boundary const topSection = (
captureResize={true} <div css={Styles.HORIZONTAL_CONTAINER} style={{ marginBottom: 14 }}>
captureScroll={false} <span
enabled css={STYLES_PROFILE_IMAGE}
onOutsideRectEvent={() => this.props.onTogglePopup()} style={{
style={this.props.style} cursor: "default",
> width: 46,
<div> height: 46,
<PopoverNavigation marginRight: 16,
style={{ top: 36, right: 0, padding: "12px 24px", width: 220 }} backgroundImage: `url('${this.props.viewer.data.photo}')`,
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);
},
},
]}
/> />
<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> </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 ( return (
<div css={STYLES_HEADER}> <div css={STYLES_HEADER}>
<div css={STYLES_PROFILE_MOBILE} style={{ position: "relative" }}> <div css={STYLES_PROFILE_MOBILE} style={{ position: "relative" }}>

View File

@ -7,9 +7,10 @@ import * as Credentials from "~/common/credentials";
import { Boundary } from "~/components/system/components/fragments/Boundary"; import { Boundary } from "~/components/system/components/fragments/Boundary";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { Logo } from "~/common/logo"; import { Logo } from "~/common/logo";
import { Link } from "~/components/core/Link";
const STYLES_BACKGROUND = css` const STYLES_BACKGROUND = css`
z-index: ${Constants.zindex.tooltip}; z-index: ${Constants.zindex.cta};
position: fixed; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
@ -17,7 +18,6 @@ const STYLES_BACKGROUND = css`
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: ${Constants.zindex.modal};
background-color: rgba(0, 0, 0, 0.85); background-color: rgba(0, 0, 0, 0.85);
text-align: center; text-align: center;
font-size: 1rem; font-size: 1rem;
@ -88,10 +88,38 @@ const STYLES_LINK_ITEM = css`
`; `;
export default class CTATransition extends React.Component { 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() { render() {
return ( return (
<div> this.state.visible && (
{open && ( <div>
<div css={STYLES_BACKGROUND}> <div css={STYLES_BACKGROUND}>
<div css={STYLES_TRANSITION}> <div css={STYLES_TRANSITION}>
<div css={STYLES_EXPLAINER}>Sign up or sign in to continue</div> <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} captureResize={true}
captureScroll={false} captureScroll={false}
enabled enabled
onOutsideRectEvent={this.props.onClose} onOutsideRectEvent={this._handleClose}
> >
<div css={STYLES_POPOVER}> <div css={STYLES_POPOVER}>
<Logo height="36px" style={{ display: "block", margin: "56px auto 0px auto" }} /> <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" }}> <System.P style={{ margin: "56px 0", textAlign: "center" }}>
An open-source file sharing network for research and collaboration An open-source file sharing network for research and collaboration
</System.P> </System.P>
<a href={this.props.redirectURL} style={{ textDecoration: `none` }}> <Link href={"/_/auth?tab=signup"} onAction={this._handleAction}>
<System.ButtonPrimary full style={{ marginBottom: 16 }}> <div style={{ textDecoration: `none` }}>
Continue to sign up <System.ButtonPrimary full style={{ marginBottom: 16 }}>
</System.ButtonPrimary>{" "} Continue to sign up
</a> </System.ButtonPrimary>
<a css={STYLES_LINK_ITEM} href={this.props.redirectURL}> </div>
Already have an account? </Link>
</a> <Link href={"/_/auth?tab=signin"} onAction={this._handleAction}>
<div css={STYLES_LINK_ITEM}>Already have an account?</div>
</Link>
</div> </div>
</Boundary> </Boundary>
</div> </div>
</div> </div>
)} </div>
</div> )
); );
} }
} }
// `/_${Strings.createQueryParams({
// scene: "NAV_PROFILE",
// user: this.props.creator.username,
// })}`

View File

@ -17,6 +17,7 @@ import { Input } from "~/components/system/components/Input";
import { Toggle } from "~/components/system/components/Toggle"; import { Toggle } from "~/components/system/components/Toggle";
import { Textarea } from "~/components/system/components/Textarea"; import { Textarea } from "~/components/system/components/Textarea";
import { Tag } from "~/components/system/components/Tag"; import { Tag } from "~/components/system/components/Tag";
import { Link } from "~/components/core/Link";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";
import cloneDeep from "lodash/cloneDeep"; import cloneDeep from "lodash/cloneDeep";
@ -279,11 +280,11 @@ export const FileTypeDefaultPreview = (props) => {
class CarouselSidebar extends React.Component { class CarouselSidebar extends React.Component {
state = { state = {
name: this.props.data.data.name || this.props.data.filename, name: this.props.file.data.name || this.props.file.filename,
body: this.props.data.data.body, body: this.props.file.data.body,
source: this.props.data.data.source, source: this.props.file.data.source,
author: this.props.data.data.author, author: this.props.file.data.author,
tags: this.props.data.data.tags || [], tags: this.props.file.data.tags || [],
suggestions: this.props.viewer?.tags || [], suggestions: this.props.viewer?.tags || [],
selected: {}, selected: {},
isPublic: false, isPublic: false,
@ -316,9 +317,13 @@ class CarouselSidebar extends React.Component {
}; };
calculateSelected = () => { calculateSelected = () => {
if (!this.props.viewer) {
this.setState({ selected: {}, inPublicSlates: 0, isPublic: this.props.file.isPublic });
return;
}
let inPublicSlates = 0; let inPublicSlates = 0;
let selected = {}; let selected = {};
const id = this.props.data.id; const id = this.props.file.id;
for (let slate of this.props.viewer.slates) { for (let slate of this.props.viewer.slates) {
if (slate.objects.some((obj) => obj.id === id)) { if (slate.objects.some((obj) => obj.id === id)) {
if (slate.isPublic) { if (slate.isPublic) {
@ -327,7 +332,7 @@ class CarouselSidebar extends React.Component {
selected[slate.id] = true; selected[slate.id] = true;
} }
} }
this.setState({ selected, inPublicSlates, isPublic: this.props.data.isPublic }); this.setState({ selected, inPublicSlates, isPublic: this.props.file.isPublic });
}; };
_handleToggleAccordion = (tab) => { _handleToggleAccordion = (tab) => {
@ -363,9 +368,9 @@ class CarouselSidebar extends React.Component {
_handleSave = async () => { _handleSave = async () => {
if (this.props.external || !this.props.isOwner) return; 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({ const response = await Actions.updateFile({
id: this.props.data.id, id: this.props.file.id,
data: { data: {
name: this.state.name, name: this.state.name,
body: this.state.body, body: this.state.body,
@ -379,6 +384,10 @@ class CarouselSidebar extends React.Component {
}; };
_handleSaveCopy = async (data) => { _handleSaveCopy = async (data) => {
if (!this.props.viewer) {
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
return;
}
this.setState({ loading: "savingCopy" }); this.setState({ loading: "savingCopy" });
await UserBehaviors.saveCopy({ files: [data] }); await UserBehaviors.saveCopy({ files: [data] });
@ -386,10 +395,10 @@ class CarouselSidebar extends React.Component {
}; };
_handleUpload = async (e) => { _handleUpload = async (e) => {
if (this.props.external || !this.props.isOwner) return; if (this.props.external || !this.props.isOwner || !this.props.viewer) return;
e.persist(); e.persist();
this.setState({ isUploading: true }); this.setState({ isUploading: true });
let previousCoverId = this.props.data.data.coverImage?.id; let previousCoverId = this.props.file.data.coverImage?.id;
if (!e || !e.target) { if (!e || !e.target) {
this.setState({ isUploading: false }); this.setState({ isUploading: false });
return; 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 //TODO(martina): create an endpoint specifically for cover images instead of this, which will delete original cover image etc
let updateReponse = await Actions.updateFile({ let updateReponse = await Actions.updateFile({
id: this.props.data.id, id: this.props.file.id,
data: { data: {
coverImage, coverImage,
}, },
@ -422,14 +431,18 @@ class CarouselSidebar extends React.Component {
}; };
_handleDownload = () => { _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 () => { 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 }); this.setState({ isDownloading: false });
Events.hasError(response); Events.hasError(response);
}); });
} else { } else {
UserBehaviors.download(this.props.data); UserBehaviors.download(this.props.file);
} }
}; };
@ -439,22 +452,22 @@ class CarouselSidebar extends React.Component {
this.props.onAction({ this.props.onAction({
type: "SIDEBAR", type: "SIDEBAR",
value: "SIDEBAR_CREATE_SLATE", value: "SIDEBAR_CREATE_SLATE",
data: { files: [this.props.data] }, data: { files: [this.props.file] },
}); });
}; };
_handleDelete = (res) => { _handleDelete = (res) => {
if (this.props.external || !this.props.isOwner) return; if (this.props.external || !this.props.isOwner || !this.props.viewer) return;
if (!res) { if (!res) {
this.setState({ modalShow: false }); this.setState({ modalShow: false });
return; return;
} }
const id = this.props.data.id; const id = this.props.file.id;
let updatedLibrary = this.props.viewer.library.filter((obj) => obj.id !== id); let updatedLibrary = this.props.viewer.library.filter((obj) => obj.id !== id);
if (this.props.carouselType === "SLATE") { if (this.props.carouselType === "SLATE") {
const slateId = this.props.current.id; const slateId = this.props.data.id;
let slates = this.props.viewer.slates; let slates = this.props.viewer.slates;
for (let slate of slates) { for (let slate of slates) {
if (slate.id === slateId) { if (slate.id === slateId) {
@ -462,9 +475,9 @@ class CarouselSidebar extends React.Component {
break; break;
} }
} }
this.props.onUpdateViewer({ library: updatedLibrary, slates }); this.props.onAction({ type: "UPDATE_VIEWER", viewer: { library: updatedLibrary, slates } });
} else { } else {
this.props.onUpdateViewer({ library: updatedLibrary }); this.props.onAction({ type: "UPDATE_VIEWER", viewer: { library: updatedLibrary } });
} }
UserBehaviors.deleteFiles(id); UserBehaviors.deleteFiles(id);
@ -476,14 +489,14 @@ class CarouselSidebar extends React.Component {
if (slate.isPublic) { if (slate.isPublic) {
inPublicSlates -= 1; inPublicSlates -= 1;
} }
UserBehaviors.removeFromSlate({ slate, ids: [this.props.data.id] }); UserBehaviors.removeFromSlate({ slate, ids: [this.props.file.id] });
} else { } else {
if (slate.isPublic) { if (slate.isPublic) {
inPublicSlates += 1; inPublicSlates += 1;
} }
UserBehaviors.addToSlate({ UserBehaviors.addToSlate({
slate, slate,
files: [this.props.data], files: [this.props.file],
}); });
} }
this.setState({ this.setState({
@ -496,12 +509,17 @@ class CarouselSidebar extends React.Component {
}; };
_handleRemove = async () => { _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; return;
} }
const id = this.props.data.id; const id = this.props.file.id;
const slateId = this.props.current.id; const slateId = this.props.data.id;
let slates = this.props.viewer.slates; let slates = this.props.viewer.slates;
for (let slate of slates) { for (let slate of slates) {
if (slate.id === slateId) { if (slate.id === slateId) {
@ -509,13 +527,13 @@ class CarouselSidebar extends React.Component {
break; 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) => { _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; const isVisible = this.state.isPublic || this.state.inPublicSlates > 0;
let selected = cloneDeep(this.state.selected); let selected = cloneDeep(this.state.selected);
if (this.state.inPublicSlates) { if (this.state.inPublicSlates) {
@ -538,19 +556,19 @@ class CarouselSidebar extends React.Component {
} }
} }
if (this.props.carouselType === "SLATE" && this.props.current.isPublic) { if (this.props.carouselType === "SLATE" && this.props.data.isPublic) {
const slateId = this.props.current.id; const slateId = this.props.data.id;
let slates = cloneDeep(this.props.viewer.slates); let slates = cloneDeep(this.props.viewer.slates);
for (let slate of slates) { for (let slate of slates) {
if (slate.id === slateId) { 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; 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); Events.hasError(response);
if (isVisible) { if (isVisible) {
this.setState({ inPublicSlates: 0, isPublic: false, selected }); this.setState({ inPublicSlates: 0, isPublic: false, selected });
@ -561,7 +579,7 @@ class CarouselSidebar extends React.Component {
render() { render() {
const isVisible = this.state.isPublic || this.state.inPublicSlates > 0 ? true : false; 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 { coverImage, type, size } = file.data;
const editingAllowed = this.props.isOwner && !this.props.isRepost && !this.props.external; const editingAllowed = this.props.isOwner && !this.props.isRepost && !this.props.external;
@ -679,19 +697,23 @@ class CarouselSidebar extends React.Component {
{ {
this.props.carouselType === "ACTIVITY" this.props.carouselType === "ACTIVITY"
? actions.push( ? actions.push(
<div <div style={{ borderBottom: "1px solid #3c3c3c" }}>
key="go-to-slate" <Link href={`/$/slate/${file.slateId}`} onAction={this.props.onAction}>
css={STYLES_ACTION} <div
onClick={() => key="go-to-slate"
this.props.onAction({ css={STYLES_ACTION}
type: "NAVIGATE", // onClick={() =>
value: "NAV_SLATE", // this.props.onAction({
data: file.slate, // type: "NAVIGATE",
}) // value: "NAV_SLATE",
} // data: file.slate,
> // })
<SVG.Slate height="24px" /> // }
<span style={{ marginLeft: 16 }}>Go to collection</span> >
<SVG.Slate height="24px" />
<span style={{ marginLeft: 16 }}>Go to collection</span>
</div>
</Link>
</div> </div>
) )
: null; : null;
@ -713,7 +735,7 @@ class CarouselSidebar extends React.Component {
</div> </div>
); );
if (!this.props.external && (!this.props.isOwner || this.props.isRepost)) { if (!this.props.isOwner || this.props.isRepost) {
actions.push( actions.push(
<div key="save-copy" css={STYLES_ACTION} onClick={() => this._handleSaveCopy(file)}> <div key="save-copy" css={STYLES_ACTION} onClick={() => this._handleSaveCopy(file)}>
<SVG.Save height="24px" /> <SVG.Save height="24px" />
@ -843,15 +865,15 @@ class CarouselSidebar extends React.Component {
return ( return (
<> <>
{this.state.modalShow && ( {this.state.modalShow && (
<ConfirmationModal <ConfirmationModal
type={"DELETE"} type={"DELETE"}
withValidation={false} withValidation={false}
callback={this._handleDelete} callback={this._handleDelete}
header={`Are you sure you want to delete the file “${this.state.name}”?`} 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 cant undo this action.`} subHeader={`This file will be deleted from all connected collections and your file library. You cant 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 && ( {this.state.showSavedMessage && (
<div css={STYLES_AUTOSAVE}> <div css={STYLES_AUTOSAVE}>
<SVG.Check height="14px" style={{ marginRight: 4 }} /> <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}> <div key="s-1" css={STYLES_DISMISS_BOX} onClick={this.props.onClose}>
<SVG.Dismiss height="24px" /> <SVG.Dismiss height="24px" />
</div> </div>
{elements}
<div key="s-2" style={{ marginBottom: 80 }}> <div css={STYLES_ACTIONS}>{actions}</div>
{elements} {privacy}
{uploadCoverImage}
{!this.props.external && <div css={STYLES_ACTIONS}>{actions}</div>} {!this.props.external && this.props.viewer && (
{privacy} <>
{uploadCoverImage} <div
{!this.props.external && ( css={STYLES_SECTION_HEADER}
<> style={{ cursor: "pointer", marginTop: 48 }}
<div onClick={() => this._handleToggleAccordion("showConnectedSection")}
css={STYLES_SECTION_HEADER} >
style={{ cursor: "pointer", marginTop: 48 }} <span
onClick={() => this._handleToggleAccordion("showConnectedSection")} style={{
marginRight: 8,
transform: this.state.showConnectedSection ? "none" : "rotate(-90deg)",
transition: "100ms ease transform",
}}
> >
<span <SVG.ChevronDown height="24px" display="block" />
style={{ </span>
marginRight: 8, <span>Add to collection</span>
transform: this.state.showConnectedSection ? "none" : "rotate(-90deg)", </div>
transition: "100ms ease transform", {this.state.showConnectedSection && (
}} <div style={{ width: "100%", margin: "24px 0 44px 0" }}>
> <SlatePicker
<SVG.ChevronDown height="24px" display="block" /> dark
</span> slates={this.props.viewer.slates || []}
<span>Add to collection</span> onCreateSlate={this._handleCreateSlate}
selectedColor={Constants.system.white}
files={[this.props.file]}
selected={this.state.selected}
onAdd={this._handleAdd}
/>
</div> </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") ? ( {this.props.file.filename.endsWith(".md") ? (
<> <>
<div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}> <div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}>
Settings Settings
</div> </div>
<div css={STYLES_OPTIONS_SECTION}> <div css={STYLES_OPTIONS_SECTION}>
<div css={STYLES_TEXT}>Dark mode</div> <div css={STYLES_TEXT}>Dark mode</div>
<Toggle dark active={this.props?.theme?.darkmode} onChange={this._handleDarkMode} /> <Toggle dark active={this.props?.theme?.darkmode} onChange={this._handleDarkMode} />
</div> </div>
</> </>
) : null} ) : null}
</div>
</div> </div>
</> </>
); );
@ -937,4 +955,4 @@ export default withTheme(CarouselSidebar);
: "This file is currently not visible to others unless they have the link."} : "This file is currently not visible to others unless they have the link."}
</div> </div>
</> */ </> */
} }

View File

@ -7,6 +7,7 @@ import * as Window from "~/common/window";
import * as UserBehaviors from "~/common/user-behaviors"; import * as UserBehaviors from "~/common/user-behaviors";
import * as Events from "~/common/custom-events"; import * as Events from "~/common/custom-events";
import { Link } from "~/components/core/Link";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { Boundary } from "~/components/system/components/fragments/Boundary"; import { Boundary } from "~/components/system/components/fragments/Boundary";
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation"; import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
@ -457,6 +458,10 @@ export default class DataView extends React.Component {
}; };
_handleDownloadFiles = async () => { _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]); const selectedFiles = this.props.items.filter((_, i) => this.state.checked[i]);
UserBehaviors.compressAndDownloadFiles({ UserBehaviors.compressAndDownloadFiles({
files: selectedFiles, 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)); 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); UserBehaviors.deleteFiles(ids);
this.setState({ checked: {}, modalShow: false }); this.setState({ checked: {}, modalShow: false });
}; };
_handleSelect = (index) => {
Events.dispatchCustomEvent({
name: "slate-global-open-carousel",
detail: { index },
});
};
_handleCheckBoxMouseEnter = (i) => { _handleCheckBoxMouseEnter = (i) => {
if (this.props.isOwner) { if (this.props.isOwner) {
this.setState({ hover: i }); this.setState({ hover: i });
@ -526,6 +524,10 @@ export default class DataView extends React.Component {
}; };
_handleAddToSlate = (e) => { _handleAddToSlate = (e) => {
if (!this.props.viewer) {
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
return;
}
let userFiles = this.props.viewer.library; let userFiles = this.props.viewer.library;
let files = Object.keys(this.state.checked).map((index) => userFiles[index]); let files = Object.keys(this.state.checked).map((index) => userFiles[index]);
this.props.onAction({ this.props.onAction({
@ -646,30 +648,32 @@ export default class DataView extends React.Component {
> >
Add to collection Add to collection
</ButtonPrimary> </ButtonPrimary>
<ButtonPrimary {this.props.isOwner && (
transparent <ButtonPrimary
style={{ color: Constants.system.white }} transparent
onClick={() => { style={{ color: Constants.system.white }}
this.props.onAction({ onClick={() => {
type: "SIDEBAR", this.props.onAction({
value: "SIDEBAR_EDIT_TAGS", type: "SIDEBAR",
data: { value: "SIDEBAR_EDIT_TAGS",
numChecked, data: {
commonTags: this.getCommonTagFromSelectedItems(), numChecked,
objects: this.props.items, commonTags: this.getCommonTagFromSelectedItems(),
checked: this.state.checked, objects: this.props.items,
}, checked: this.state.checked,
}); },
}} });
> }}
Edit tag{numChecked > 1 ? "s" : ""} >
</ButtonPrimary> Edit tags
</ButtonPrimary>
)}
<ButtonWarning <ButtonWarning
transparent transparent
style={{ marginLeft: 8, color: Constants.system.white }} style={{ marginLeft: 8, color: Constants.system.white }}
onClick={() => this._handleDownloadFiles()} onClick={() => this._handleDownloadFiles()}
> >
{Strings.pluralize("Download file", numChecked)} Download
</ButtonWarning> </ButtonWarning>
{this.props.isOwner && ( {this.props.isOwner && (
<ButtonWarning <ButtonWarning
@ -677,14 +681,14 @@ export default class DataView extends React.Component {
style={{ marginLeft: 8, color: Constants.system.white }} style={{ marginLeft: 8, color: Constants.system.white }}
onClick={() => this.setState({ modalShow: true })} onClick={() => this.setState({ modalShow: true })}
> >
{Strings.pluralize("Delete file", numChecked)} Delete
</ButtonWarning> </ButtonWarning>
)} )}
{this.state.modalShow && ( {this.state.modalShow && (
<ConfirmationModal <ConfirmationModal
type={"DELETE"} type={"DELETE"}
withValidation={false} withValidation={false}
callback={this._handleDelete} callback={this._handleDelete}
header={`Are you sure you want to delete the selected files?`} 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 cant undo this action.`} subHeader={`These files will be deleted from all connected collections and your file library. You cant undo this action.`}
/> />
@ -704,129 +708,67 @@ export default class DataView extends React.Component {
) : null} ) : null}
</React.Fragment> </React.Fragment>
); );
if (this.props.view === 0) { if (this.props.view === "grid") {
return ( return (
<React.Fragment> <React.Fragment>
<GroupSelectable onSelection={this._handleDragAndSelect}> <GroupSelectable onSelection={this._handleDragAndSelect}>
<div css={STYLES_IMAGE_GRID} ref={this.gridWrapperEl}> <div css={STYLES_IMAGE_GRID} ref={this.gridWrapperEl}>
{this.props.items.slice(0, this.state.viewLimit).map((each, i) => { {this.props.items.slice(0, this.state.viewLimit).map((each, i) => {
const cid = each.cid;
return ( return (
<Selectable <Link
key={each.id} key={each.id}
draggable={!numChecked} redirect
onDragStart={(e) => { params={{ ...this.props.page?.params, cid: each.cid }}
this._disableDragAndDropUploadEvent(); onAction={this.props.onAction}
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)}
> >
<SlateMediaObjectPreview file={each} /> <Selectable
<span css={STYLES_MOBILE_HIDDEN} style={{ pointerEvents: "auto" }}> key={each.id}
{numChecked || this.state.hover === i || this.state.menu === each.id ? ( draggable={!numChecked}
<React.Fragment> onDragStart={(e) => {
{/* <div this._disableDragAndDropUploadEvent();
css={STYLES_ICON_BOX_BACKGROUND} this._handleDragToDesktop(e, each);
onClick={(e) => { }}
e.stopPropagation(); onDragEnd={this._enableDragAndDropUploadEvent}
this.setState({ selectableKey={i}
menu: this.state.menu === each.id ? null : each.id, css={STYLES_IMAGE_BOX}
}); style={{
}} width: this.state.imageSize,
> height: this.state.imageSize,
<SVG.MoreHorizontal height="24px" /> boxShadow: numChecked
{this.state.menu === each.id ? ( ? `0px 0px 0px 1px ${Constants.system.lightBorder} inset,
<Boundary 0 0 40px 0 ${Constants.system.shadow}`
captureResize={true} : "",
captureScroll={false} }}
enabled onMouseEnter={() => this._handleCheckBoxMouseEnter(i)}
onOutsideRectEvent={this._handleHide} onMouseLeave={() => this._handleCheckBoxMouseLeave(i)}
> >
{this.props.isOwner ? ( <SlateMediaObjectPreview file={each} />
<PopoverNavigation <span css={STYLES_MOBILE_HIDDEN} style={{ pointerEvents: "auto" }}>
style={{ {numChecked || this.state.hover === i || this.state.menu === each.id ? (
top: "32px", <React.Fragment>
right: "0px", <div onClick={(e) => this._handleCheckBox(e, i)}>
}} <CheckBox
navigation={[ name={i}
{ value={!!this.state.checked[i]}
text: "Copy CID", boxStyle={{
onClick: (e) => this._handleCopy(e, cid), height: 24,
}, width: 24,
{ backgroundColor: this.state.checked[i]
text: "Copy link", ? Constants.system.brand
onClick: (e) => : "rgba(255, 255, 255, 0.75)",
this._handleCopy(e, Strings.getURLfromCID(cid)), }}
}, style={{
{ position: "absolute",
text: "Delete", bottom: 8,
onClick: (e) => { left: 8,
e.stopPropagation(); }}
this.setState({ menu: null }, () => />
this._handleDelete(cid, each.id) </div>
); </React.Fragment>
}, ) : null}
}, </span>
]} </Selectable>
/> </Link>
) : (
<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>
); );
})} })}
{[0, 1, 2, 3].map((i) => ( {[0, 1, 2, 3].map((i) => (
@ -923,18 +865,24 @@ export default class DataView extends React.Component {
onDragEnd={this._enableDragAndDropUploadEvent} onDragEnd={this._enableDragAndDropUploadEvent}
> >
<FilePreviewBubble cid={cid} type={each.data.type}> <FilePreviewBubble cid={cid} type={each.data.type}>
<div css={STYLES_CONTAINER_HOVER} onClick={() => this._handleSelect(index)}> <Link
<div css={STYLES_ICON_BOX_HOVER} style={{ paddingLeft: 0, paddingRight: 18 }}> redirect
<FileTypeIcon type={each.data.type} height="24px" /> 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>
<div css={STYLES_LINK}>{each.data.name || each.filename}</div> </Link>
</div>
</FilePreviewBubble> </FilePreviewBubble>
</Selectable> </Selectable>
), ),
tags: <>{each.data.tags?.length && <Tags tags={each.data.tags} />}</>, tags: <>{each.data.tags?.length && <Tags tags={each.data.tags} />}</>,
size: <div css={STYLES_VALUE}>{Strings.bytesToSize(each.data.size)}</div>, size: <div css={STYLES_VALUE}>{Strings.bytesToSize(each.data.size)}</div>,
more: ( more: this.props.isOwner ? (
<div <div
css={STYLES_ICON_BOX_HOVER} css={STYLES_ICON_BOX_HOVER}
onClick={() => onClick={() =>
@ -957,27 +905,29 @@ export default class DataView extends React.Component {
right: "40px", right: "40px",
}} }}
navigation={[ navigation={[
{ [
text: "Copy CID", {
onClick: (e) => this._handleCopy(e, cid), 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 link",
// onClick: (e) => this._handleCopy(e, Strings.getURLfromCID(cid)),
// },
{
text: "Delete",
onClick: (e) => {
e.stopPropagation();
this.setState({ menu: null, modalShow: true });
},
},
],
]} ]}
/> />
</Boundary> </Boundary>
) : null} ) : null}
</div> </div>
), ) : null,
}; };
}); });

View File

@ -8,11 +8,11 @@ const withView = (Component) => (props) => {
const [isIntersecting, setIntersecting] = React.useState(false); const [isIntersecting, setIntersecting] = React.useState(false);
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) setIntersecting(true);
});
React.useEffect(() => { React.useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) setIntersecting(true);
});
observer.observe(ref.current); observer.observe(ref.current);
return () => { return () => {
observer.disconnect(); observer.disconnect();

83
components/core/Link.js Normal file
View 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>
);
}
}

View File

@ -1,8 +1,9 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import * as Constants from "~/common/constants";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { Logo } from "~/common/logo.js"; import { Logo } from "~/common/logo.js";
import { Link } from "~/components/core/Link";
import * as Constants from "~/common/constants";
const STYLES_ROOT = css` const STYLES_ROOT = css`
position: -webkit-sticky; position: -webkit-sticky;
@ -195,7 +196,7 @@ const NewWebsitePrototypeHeader = (props) => {
}; };
const communityURL = "https://github.com/filecoin-project/slate"; const communityURL = "https://github.com/filecoin-project/slate";
const signInURL = "/_"; const signInURL = "/_/auth";
const styleMenu = open ? openMenu : null; const styleMenu = open ? openMenu : null;
const styleBurgerBun = open ? openBurgerBun : null; const styleBurgerBun = open ? openBurgerBun : null;
const styleBurgerBun2 = open ? openBurgerBun2 : null; const styleBurgerBun2 = open ? openBurgerBun2 : null;

View File

@ -7,6 +7,8 @@ import * as Utilities from "~/common/utilities";
import * as Events from "~/common/custom-events"; import * as Events from "~/common/custom-events";
import * as Window from "~/common/window"; import * as Window from "~/common/window";
import { useState } from "react";
import { Link } from "~/components/core/Link";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel"; import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons"; import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons";
@ -239,48 +241,236 @@ const STYLES_DIRECTORY_NAME = css`
// opacity: 0; // opacity: 0;
// `; // `;
function UserEntry({ function UserEntry({ user, button, onClick, message, checkStatus }) {
user,
button,
onClick,
message,
external,
url,
checkStatus,
showStatusIndicator,
}) {
const isOnline = checkStatus({ id: user.id }); const isOnline = checkStatus({ id: user.id });
return ( return (
<div key={user.username} css={STYLES_USER_ENTRY}> <div key={user.username} css={STYLES_USER_ENTRY}>
{external ? ( <div css={STYLES_USER} onClick={onClick}>
<a css={STYLES_USER} style={{ textDecoration: "none" }} href={url}> <div
<div css={STYLES_DIRECTORY_PROFILE_IMAGE}
css={STYLES_DIRECTORY_PROFILE_IMAGE} style={{ backgroundImage: `url(${user.data.photo})` }}
style={{ backgroundImage: `url(${user.data.photo})` }} >
> {isOnline && <div css={STYLES_DIRECTORY_STATUS_INDICATOR} />}
{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> </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> </div>
); );
} }
@ -289,12 +479,7 @@ export default class Profile extends React.Component {
_ref = null; _ref = null;
state = { state = {
view: 0,
slateTab: 0,
peerTab: 0,
// copyValue: "",
contextMenu: null, contextMenu: null,
slates: this.props.user.slates,
subscriptions: [], subscriptions: [],
followers: [], followers: [],
following: [], following: [],
@ -305,21 +490,22 @@ export default class Profile extends React.Component {
return entry.id === this.props.user.id; return entry.id === this.props.user.id;
}), }),
fetched: false, fetched: false,
tab: this.props.tab || 0,
}; };
componentDidMount = () => { componentDidMount = () => {
this._handleUpdatePage(); this.fetchSocial();
}; };
componentDidUpdate = (prevProps) => { componentDidUpdate = (prevProps) => {
if (this.props.page?.tab !== prevProps.page?.tab) { if (!this.state.fetched && this.props.page.params !== prevProps.page.params) {
this.setState({ tab: this.props.page.tab }); this.fetchSocial();
} }
}; };
fetchSocial = async () => { fetchSocial = async () => {
if (this.state.fetched) return; if (this.state.fetched) return;
if (this.props.page.params?.subtab !== "peers" && this.props.page.params?.tab !== "subscribed")
return;
let following, followers, subscriptions; let following, followers, subscriptions;
if (this.props.user.id === this.props.viewer?.id) { if (this.props.user.id === this.props.viewer?.id) {
following = this.props.viewer?.following; 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) => { _handleHide = (e) => {
this.setState({ contextMenu: null }); this.setState({ contextMenu: null });
}; };
_handleClick = (e, value) => { // _handleClick = (e, value) => {
e.stopPropagation(); // e.stopPropagation();
if (this.state.contextMenu === value) { // if (this.state.contextMenu === value) {
this._handleHide(); // this._handleHide();
} else { // } else {
this.setState({ contextMenu: value }); // this.setState({ contextMenu: value });
} // }
}; // };
_handleFollow = async (e, id) => { _handleFollow = async (e, id) => {
if (this.props.external) { if (this.props.external) {
this._handleRedirectToInternal(); this._handleLoginModal();
return;
} }
this._handleHide(); this._handleHide();
e.stopPropagation(); e.stopPropagation();
@ -376,33 +554,12 @@ export default class Profile extends React.Component {
}); });
}; };
_handleRedirectToInternal = () => { _handleLoginModal = (e) => {
this.setState({ visible: true }); if (e) {
}; e.preventDefault();
e.stopPropagation();
_handleSwitchTab = (tab) => {
if (typeof window !== "undefined") {
this.setState({ tab });
window.history.pushState({ ...window.history.state, tab }, "", window.location.pathname);
} }
if (tab === 2 && !this.state.fetched) { Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
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();
}
});
}; };
checkStatus = ({ id }) => { checkStatus = ({ id }) => {
@ -411,81 +568,15 @@ export default class Profile extends React.Component {
}; };
render() { render() {
let tab = this.state.tab || 0; let subtab = this.props.page.params?.subtab
let publicFiles = this.props.user.library; ? 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 isOwner = this.props.isOwner;
let user = this.props.user; 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; const showStatusIndicator = this.props.isAuthenticated;
@ -493,14 +584,14 @@ export default class Profile extends React.Component {
<div> <div>
<GlobalCarousel <GlobalCarousel
carouselType="PROFILE" carouselType="PROFILE"
onUpdateViewer={this.props.onUpdateViewer}
resources={this.props.resources} resources={this.props.resources}
viewer={this.props.viewer} viewer={this.props.viewer}
objects={publicFiles} objects={library}
isOwner={this.props.isOwner} isOwner={this.props.isOwner}
onAction={this.props.onAction} onAction={this.props.onAction}
isMobile={this.props.isMobile} isMobile={this.props.isMobile}
external={this.props.external} external={this.props.external}
params={this.props.page.params}
/> />
<div css={STYLES_PROFILE_BACKGROUND}> <div css={STYLES_PROFILE_BACKGROUND}>
<div css={STYLES_PROFILE_INFO}> <div css={STYLES_PROFILE_INFO}>
@ -547,7 +638,7 @@ export default class Profile extends React.Component {
<div css={STYLES_STATS}> <div css={STYLES_STATS}>
<div css={STYLES_STAT}> <div css={STYLES_STAT}>
<div style={{ fontFamily: `${Constants.font.text}` }}> <div style={{ fontFamily: `${Constants.font.text}` }}>
{publicFiles.length}{" "} {library.length}{" "}
<span style={{ color: `${Constants.system.darkGray}` }}>Files</span> <span style={{ color: `${Constants.system.darkGray}` }}>Files</span>
</div> </div>
</div> </div>
@ -561,162 +652,37 @@ export default class Profile extends React.Component {
</div> </div>
</div> </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}> <div css={STYLES_PROFILE}>
<TabGroup <TabGroup
tabs={["Files", "Collections", "Peers"]} tabs={[
value={tab} { title: "Files", value: { subtab: "files" } },
onChange={this._handleSwitchTab} { title: "Collections", value: { subtab: "collections" } },
{ title: "Peers", value: { subtab: "peers" } },
]}
value={subtab}
onAction={this.props.onAction}
style={{ marginTop: 0, marginBottom: 32 }} style={{ marginTop: 0, marginBottom: 32 }}
itemStyle={{ margin: "0px 16px" }} itemStyle={{ margin: "0px 16px" }}
/> />
{tab === 0 ? ( {subtab === "files" ? <FilesPage {...this.props} library={library} tab={tab} /> : null}
<div> {subtab === "collections" ? (
{this.props.isMobile ? null : ( <CollectionsPage
<div style={{ display: `flex` }}> {...this.props}
<SecondaryTabGroup tab={tab}
tabs={[ fetched={this.state.fetched}
<SVG.GridView height="24px" style={{ display: "block" }} />, subscriptions={this.state.subscriptions}
<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>
) : null} ) : null}
{tab === 1 ? ( {subtab === "peers" ? (
<div> <PeersPage
<SecondaryTabGroup {...this.props}
tabs={["Collections", "Following"]} tab={tab}
value={this.state.slateTab} onLoginModal={this._handleLoginModal}
onChange={(value) => { checkStatus={this.checkStatus}
this.setState({ slateTab: value }, () => { following={this.state.following}
if (!this.state.fetched) { followers={this.state.followers}
this.fetchSocial(); fetched={this.state.fetched}
} />
});
}}
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>
) : null} ) : null}
</div> </div>
</div> </div>

View File

@ -9,6 +9,7 @@ import * as Validations from "~/common/validations";
import MiniSearch from "minisearch"; import MiniSearch from "minisearch";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
import { Link } from "~/components/core/Link";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { LoaderSpinner } from "~/components/system/components/Loaders"; import { LoaderSpinner } from "~/components/system/components/Loaders";
import { Boundary } from "~/components/system/components/fragments/Boundary"; import { Boundary } from "~/components/system/components/fragments/Boundary";
@ -437,6 +438,21 @@ const STYLES_DISMISS_BOX = css`
outline: 0; 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 { export class SearchModal extends React.Component {
_input; _input;
_optionRoot; _optionRoot;
@ -484,24 +500,27 @@ export class SearchModal extends React.Component {
this.debounceInstance = Window.debounce(() => { this.debounceInstance = Window.debounce(() => {
this._handleSearch(); this._handleSearch();
}, 500); }, 500);
let defaultResults = this.props.viewer.slates; let defaultResults = this.props.viewer ? this.props.viewer.slates : [];
defaultResults = defaultResults.map((slate) => { defaultResults = defaultResults.map((slate) => {
return { return {
id: slate.id, id: slate.id,
type: "SLATE", type: "SLATE",
data: { slate: slate }, data: { slate },
component: <SlateEntry slate={slate} user={this.props.viewer} />, component: <SlateEntry slate={slate} user={this.props.viewer} />,
preview: <SlatePreview slate={slate} user={this.props.viewer} />, preview: <SlatePreview slate={slate} user={this.props.viewer} />,
href: `/$/slate/${slate.id}`,
}; };
}); });
this.setState({ defaultResults }); this.setState({ defaultResults });
let networkIds = []; let networkIds = [];
let slateIds = []; let slateIds = [];
for (let sub of this.props.viewer.subscriptions) { if (this.props.viewer?.subscriptions) {
if (sub.target_user_id) { for (let sub of this.props.viewer.subscriptions) {
networkIds.push(sub.target_user_id); if (sub.target_user_id) {
} else if (sub.target_slate_id) { networkIds.push(sub.target_user_id);
slateIds.push(sub.target_slate_id); } else if (sub.target_slate_id) {
slateIds.push(sub.target_slate_id);
}
} }
} }
this.networkIds = networkIds; this.networkIds = networkIds;
@ -509,6 +528,9 @@ export class SearchModal extends React.Component {
}; };
fillLocalDirectory = () => { fillLocalDirectory = () => {
if (!this.props.viewer) {
return;
}
this.localSearch = new MiniSearch({ this.localSearch = new MiniSearch({
fields: ["name", "title"], fields: ["name", "title"],
storeFields: ["type", "data", "id"], storeFields: ["type", "data", "id"],
@ -614,7 +636,9 @@ export class SearchModal extends React.Component {
e.preventDefault(); e.preventDefault();
} else if (e.keyCode === 13) { } else if (e.keyCode === 13) {
if (results.length > this.state.selectedIndex && this.state.selectedIndex >= 0) { 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(); e.preventDefault();
} }
@ -629,7 +653,7 @@ export class SearchModal extends React.Component {
let searchResults = []; let searchResults = [];
let results = []; let results = [];
let ids = new Set(); let ids = new Set();
if (this.state.typeFilter !== "USER") { if (this.state.typeFilter !== "USER" && this.props.viewer) {
let filter; let filter;
if (this.state.typeFilter === "FILE") { if (this.state.typeFilter === "FILE") {
filter = { filter = {
@ -686,7 +710,7 @@ export class SearchModal extends React.Component {
file={item.data.file} file={item.data.file}
slate={item.data.slate} slate={item.data.slate}
user={this.props.viewer} 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({ results.push({
id, id,
type: res.type, type: res.type,
href: res.href,
data: res, data: res,
component: <UserEntry user={res.user} />, component: <UserEntry user={res.user} />,
preview: <UserPreview user={res.user} />, preview: <UserPreview user={res.user} />,
@ -726,6 +751,7 @@ export class SearchModal extends React.Component {
results.push({ results.push({
id, id,
type: res.type, type: res.type,
href: res.href,
data: res, data: res,
component: <SlateEntry slate={res.slate} user={res.user} />, component: <SlateEntry slate={res.slate} user={res.user} />,
preview: <SlatePreview 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({ results.push({
id, id,
type: res.type, type: res.type,
href: res.href,
data: res, data: res,
component: <FileEntry file={res.file} />, component: <FileEntry file={res.file} />,
preview: ( preview: (
@ -744,12 +771,16 @@ export class SearchModal extends React.Component {
file={res.file} file={res.file}
slate={res.slate} slate={res.slate}
user={res.user} 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 }); this.setState({ results, selectedIndex: 0 });
if (this._optionRoot) { if (this._optionRoot) {
this._optionRoot.scrollTop = 0; this._optionRoot.scrollTop = 0;
@ -757,6 +788,9 @@ export class SearchModal extends React.Component {
}; };
processResults = (searchResults) => { processResults = (searchResults) => {
if (!this.state.viewer) {
return searchResults;
}
let results = searchResults; let results = searchResults;
if (this.state.scopeFilter === "MY") { if (this.state.scopeFilter === "MY") {
results = results.filter((res) => { results = results.filter((res) => {
@ -809,65 +843,65 @@ export class SearchModal extends React.Component {
return results; return results;
}; };
_handleSelect = async (res) => { // _handleSelect = async (res) => {
if (res.type === "SLATE") { // if (res.type === "SLATE") {
this.props.onAction({ // this.props.onAction({
type: "NAVIGATE", // type: "NAVIGATE",
value: "NAV_SLATE", // value: "NAV_SLATE",
data: res.data.slate, // data: res.data.slate,
}); // });
} else if (res.type === "USER") { // } else if (res.type === "USER") {
this.props.onAction({ // this.props.onAction({
type: "NAVIGATE", // type: "NAVIGATE",
value: "NAV_PROFILE", // value: "NAV_PROFILE",
data: res.data.user, // data: res.data.user,
}); // });
} else if (res.type === "DATA_FILE" || res.data.file.ownerId === this.props.viewer.id) { // } else if (res.type === "DATA_FILE" || res.data.file.ownerId === this.props.viewer?.id) {
await this.props.onAction({ // await this.props.onAction({
type: "NAVIGATE", // type: "NAVIGATE",
value: "NAV_DATA", // value: "NAV_DATA",
fileId: res.data.file.id, // fileId: res.data.file.id,
}); // });
} else if (res.type === "FILE") { // } else if (res.type === "FILE") {
await this.props.onAction({ // await this.props.onAction({
type: "NAVIGATE", // type: "NAVIGATE",
value: "NAV_PROFILE", // value: "NAV_PROFILE",
data: res.data.user, // data: res.data.user,
fileId: res.data.file.id, // fileId: res.data.file.id,
}); // });
} // }
this._handleHide(); // this._handleHide();
}; // };
_handleRedirect = async (destination) => { _handleRedirect = async (destination) => {
if (destination === "FMU") { // if (destination === "FMU") {
let isProd = window.location.hostname.includes("slate.host"); // let isProd = window.location.hostname.includes("slate.host");
this._handleSelect({ // this._handleSelect({
type: "FILE", // type: "FILE",
data: { // data: {
file: { id: "rick-roll" }, // file: { id: "rick-roll" },
slate: { // slate: {
id: isProd // id: isProd
? "01edcede-53c9-46b3-ac63-8f8479e10bcf" // ? "01edcede-53c9-46b3-ac63-8f8479e10bcf"
: "60d199e7-6bf5-4994-94e8-b17547c64449", // : "60d199e7-6bf5-4994-94e8-b17547c64449",
data: { // data: {
objects: [ // objects: [
{ // {
id: "rick-roll", // id: "rick-roll",
url: // url:
"https://slate.textile.io/ipfs/bafybeifcxjvbad4lgpnbwff2dafufmnlylylmku4qoqtlkwgidupwi6f3a", // "https://slate.textile.io/ipfs/bafybeifcxjvbad4lgpnbwff2dafufmnlylylmku4qoqtlkwgidupwi6f3a",
ownerId: "owner", // ownerId: "owner",
name: "Never gonna give you up", // name: "Never gonna give you up",
title: "never-gonna-give-you-up.mp4", // title: "never-gonna-give-you-up.mp4",
type: "video/mp4", // type: "video/mp4",
}, // },
], // ],
}, // },
ownerId: "owner", // ownerId: "owner",
}, // },
}, // },
}); // });
} // }
this.props.onAction({ this.props.onAction({
type: "SIDEBAR", type: "SIDEBAR",
value: destination, 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() { render() {
let selectedIndex = this.state.selectedIndex; let selectedIndex = this.state.selectedIndex;
let results = let results =
this.state.inputValue && this.state.inputValue.length this.state.inputValue && this.state.inputValue.length
? this.state.results ? this.state.results
: this.state.defaultResults; : 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 ( return (
<div <div
css={STYLES_BACKGROUND} css={STYLES_BACKGROUND}
@ -1047,87 +1170,7 @@ export class SearchModal extends React.Component {
</span> </span>
</div> </div>
</div> </div>
<div style={{ flexShrink: 0, position: "relative" }}> {filterDropdown}
<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>
</div> </div>
<div <div
@ -1138,45 +1181,57 @@ export class SearchModal extends React.Component {
css={STYLES_DROPDOWN} css={STYLES_DROPDOWN}
> >
{results.map((each, i) => ( {results.map((each, i) => (
<div <Link
key={each.id} disabled={this.props.isMobile ? false : selectedIndex !== i}
css={STYLES_DROPDOWN_ITEM} href={each.href}
style={{ onAction={this.props.onAction}
background: onClick={() => this._handleSelectIndex(i)}
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 });
}}
> >
{each.component} <div
{selectedIndex === i ? ( key={each.id}
<div css={STYLES_RETURN}> css={STYLES_DROPDOWN_ITEM}
<SVG.ArrowDownLeft height="16px" style={{ marginRight: 8 }} /> Return style={{
</div> background:
) : null} selectedIndex === i
</div> ? "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> </div>
{results && {results?.length && selectedIndex < results.length && selectedIndex >= 0 ? (
results.length && <Link
selectedIndex < results.length && href={results[selectedIndex].href}
selectedIndex >= 0 ? ( onAction={this.props.onAction}
<div onClick={this._handleHide}
css={STYLES_PREVIEW_PANEL}
onClick={() => {
if (selectedIndex >= 0 && selectedIndex < results.length) {
this._handleSelect(results[selectedIndex]);
}
}}
> >
{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} ) : null}
</React.Fragment> </React.Fragment>
)} )}
@ -1195,9 +1250,9 @@ export class SearchModal extends React.Component {
> >
FAQ FAQ
</span> </span>
<span style={{ cursor: "pointer" }} onClick={() => this._handleRedirect("FMU")}> {/* <span style={{ cursor: "pointer" }} onClick={() => this._handleRedirect("FMU")}>
I'm Feeling Lucky I'm Feeling Lucky
</span> </span> */}
</div> </div>
</div> </div>
</Boundary> </Boundary>

View File

@ -146,6 +146,9 @@ export class SignIn extends React.Component {
password: this.state.password, password: this.state.password,
}); });
} }
if (!Events.hasError(response)) {
window.location.replace("/_/activity");
}
this.setState({ loading: false }); 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" }} /> <Logo height="36px" style={{ display: "block", margin: "56px auto 0px auto" }} />
<System.P style={{ margin: "56px 0", textAlign: "center" }}> <System.P style={{ margin: "56px 0", textAlign: "center" }}>
{this.props.external An open-source file sharing network for research and collaboration
? "Sign up or sign in to continue"
: "An open-source file sharing network for research and collaboration"}
</System.P> </System.P>
<System.ButtonPrimary <System.ButtonPrimary

View File

@ -10,12 +10,12 @@ import * as Events from "~/common/custom-events";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
import CTATransition from "~/components/core/CTATransition"; import CTATransition from "~/components/core/CTATransition";
import { Link } from "~/components/core/Link";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel"; import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import { CheckBox } from "~/components/system/components/CheckBox"; import { CheckBox } from "~/components/system/components/CheckBox";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { LoaderSpinner } from "~/components/system/components/Loaders"; import { LoaderSpinner } from "~/components/system/components/Loaders";
import { Toggle } from "~/components/system/components/Toggle"; import { Toggle } from "~/components/system/components/Toggle";
import { DynamicIcon } from "~/components/core/DynamicIcon";
import { Tooltip } from "~/components/core/Tooltip"; import { Tooltip } from "~/components/core/Tooltip";
import { import {
ButtonPrimary, ButtonPrimary,
@ -34,8 +34,6 @@ const MARGIN = 20;
const CONTAINER_SIZE = 5 * SIZE + 4 * MARGIN; const CONTAINER_SIZE = 5 * SIZE + 4 * MARGIN;
const TAG_HEIGHT = 20; const TAG_HEIGHT = 20;
const SIZE_LIMIT = 1000000; //NOTE(martina): 1mb limit for twitter preview images
const generateLayout = (items) => { const generateLayout = (items) => {
if (!items) { if (!items) {
return []; return [];
@ -328,7 +326,6 @@ export class SlateLayout extends React.Component {
copyValue: "", copyValue: "",
tooltip: null, tooltip: null,
keyboardTooltip: false, keyboardTooltip: false,
signInModal: false,
modalShowDeleteFiles: false, modalShowDeleteFiles: false,
}; };
@ -964,6 +961,10 @@ export class SlateLayout extends React.Component {
}; };
_handleDownload = (e, i) => { _handleDownload = (e, i) => {
if (!this.props.viewer) {
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
return;
}
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (i !== undefined) { if (i !== undefined) {
@ -972,6 +973,10 @@ export class SlateLayout extends React.Component {
}; };
_handleSaveCopy = async (e, i) => { _handleSaveCopy = async (e, i) => {
if (!this.props.viewer) {
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
return;
}
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
let items = []; let items = [];
@ -987,6 +992,10 @@ export class SlateLayout extends React.Component {
}; };
_handleAddToSlate = (e, i) => { _handleAddToSlate = (e, i) => {
if (!this.props.viewer) {
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
return;
}
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
let items = []; let items = [];
@ -1019,16 +1028,16 @@ export class SlateLayout extends React.Component {
} }
let slates = this.props.viewer.slates; let slates = this.props.viewer.slates;
let slateId = this.props.current.id; let slateId = this.props.data.id;
for (let slate of slates) { for (let slate of slates) {
if (slate.id === slateId) { if (slate.id === slateId) {
slate.objects = slate.objects.filter((obj) => !ids.includes(obj.id.replace("data-", ""))); 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; break;
} }
} }
UserBehaviors.removeFromSlate({ slate: this.props.current, ids }); UserBehaviors.removeFromSlate({ slate: this.props.data, ids });
}; };
_stopPropagation = (e) => e.stopPropagation(); _stopPropagation = (e) => e.stopPropagation();
@ -1052,8 +1061,8 @@ export class SlateLayout extends React.Component {
}; };
_handleDeleteModal = () => { _handleDeleteModal = () => {
this.setState({ modalShowDeleteFiles: true }) this.setState({ modalShowDeleteFiles: true });
} };
_handleDeleteFiles = async (res, i) => { _handleDeleteFiles = async (res, i) => {
if (!res) { if (!res) {
@ -1069,13 +1078,13 @@ export class SlateLayout extends React.Component {
} }
} }
let slates = this.props.viewer.slates; let slates = this.props.viewer.slates;
let slateId = this.props.current.id; let slateId = this.props.data.id;
for (let slate of slates) { for (let slate of slates) {
if (slate.id === slateId) { if (slate.id === slateId) {
slate.objects = slate.objects.filter( slate.objects = slate.objects.filter(
(obj) => !ids.includes(obj.id.replace("data-", "")) && !cids.includes(obj.cid) (obj) => !ids.includes(obj.id.replace("data-", "")) && !cids.includes(obj.cid)
); );
this.props.onUpdateViewer({ slates }); this.props.onAction({ type: "UPDATE_VIEWER", viewer: { slates } });
break; break;
} }
} }
@ -1092,7 +1101,7 @@ export class SlateLayout extends React.Component {
_handleLoginModal = (e) => { _handleLoginModal = (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.setState({ signInModal: true }); Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
}; };
render() { render() {
@ -1137,15 +1146,20 @@ export class SlateLayout extends React.Component {
Reset layout Reset layout
</ButtonDisabled> </ButtonDisabled>
) : ( ) : (
<ButtonSecondary onClick={() => { this.setState({ modalShowResetLayout: true }) }} style={{ marginRight: 16 }}> <ButtonSecondary
onClick={() => {
this.setState({ modalShowResetLayout: true });
}}
style={{ marginRight: 16 }}
>
Reset layout Reset layout
</ButtonSecondary> </ButtonSecondary>
)} )}
{this.state.modalShowResetLayout && ( {this.state.modalShowResetLayout && (
<ConfirmationModal <ConfirmationModal
type={"CONFIRM"} type={"CONFIRM"}
withValidation={false} withValidation={false}
callback={this._handleResetLayout} callback={this._handleResetLayout}
header={`Are you sure you want to reset your layout to the default column layout?`} header={`Are you sure you want to reset your layout to the default column layout?`}
subHeader={`You cant undo this action.`} subHeader={`You cant undo this action.`}
/> />
@ -1282,50 +1296,56 @@ export class SlateLayout extends React.Component {
> >
{this.state.show ? ( {this.state.show ? (
this.state.layout.map((pos, i) => ( this.state.layout.map((pos, i) => (
<Selectable <Link
css={this.state.editing ? STYLES_ITEM_EDITING : STYLES_ITEM}
key={i} key={i}
name={i} redirect
draggable={!(numChecked || this.state.editing)} params={{ ...this.props.page?.params, cid: this.state.items[i].cid }}
onDragStart={(e) => { onAction={this.props.onAction}
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,
}}
> >
<SlateMediaObjectPreview <Selectable
file={this.state.items[i]} css={this.state.editing ? STYLES_ITEM_EDITING : STYLES_ITEM}
iconOnly={this.state.fileNames} name={i}
charCap={70} 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={{ style={{
height: pos.h * unit, top: pos.y * unit,
left: pos.x * unit,
width: pos.w * 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, <SlateMediaObjectPreview
height: pos.h * unit, file={this.state.items[i]}
maxHeight: "none", iconOnly={this.state.fileNames}
}} charCap={70}
/> style={{
{numChecked || this.state.hover === i ? ( height: pos.h * unit,
<div css={STYLES_MOBILE_HIDDEN}> width: pos.w * unit,
{this.props.external ? null : ( 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 <div
onMouseDown={this._stopProp} onMouseDown={this._stopProp}
onMouseUp={this._stopProp} onMouseUp={this._stopProp}
@ -1362,323 +1382,364 @@ export class SlateLayout extends React.Component {
}} }}
/> />
</div> </div>
)} {this.state.hover !== i ? null : this.state.editing ? (
{this.state.hover !== i ? null : this.state.editing ? ( <React.Fragment>
<React.Fragment> {this.state.tooltip && this.state.tooltip.startsWith(`${i}-`) ? (
{this.state.tooltip && this.state.tooltip.startsWith(`${i}-`) ? ( <Tooltip
<Tooltip light
light style={
style={ this.state.tooltip === `${i}-remove`
this.state.tooltip === `${i}-remove` ? {
? { position: "absolute",
position: "absolute", top: 36,
top: 36, right: 8,
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` : this.state.tooltip === `${i}-view`
? { ? "View file"
position: "absolute",
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52,
right: "calc(50% + 28px)",
}
: this.state.tooltip === `${i}-download` : this.state.tooltip === `${i}-download`
? { ? "Download"
position: "absolute", : "Delete file"}
bottom: this.state.fileNames ? 52 + TAG_HEIGHT : 52, </Tooltip>
right: "calc(50% - 12px)", ) : null}
}
: {
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)`,
}}
>
<div <div
css={STYLES_ICON_CIRCLE}
onMouseDown={this._stopProp} onMouseDown={this._stopProp}
onMouseUp={this._stopProp} onMouseUp={this._stopProp}
onMouseEnter={() => this.setState({ tooltip: `${i}-view` })} onMouseEnter={() => this.setState({ tooltip: `${i}-remove` })}
onMouseLeave={() => this.setState({ tooltip: null })} onMouseLeave={() => this.setState({ tooltip: null })}
onClick={(e) => { onClick={(e) => {
this._stopProp(e); this._handleRemoveFromSlate(e, i);
this.props.onSelect(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={{ style={{
cursor: position: "absolute",
this.state.items[i].ownerId === this.props.viewer.id top: 8,
? "pointer" right: 8,
: "not-allowed", 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 <Link
height="16px" redirect
style={{ params={{
color: ...this.props.page?.params,
this.state.items[i].ownerId === this.props.viewer.id cid: this.state.items[i].cid,
? Constants.system.red
: "#999999",
}} }}
/> onAction={this.props.onAction}
</div> >
<div
</div> css={STYLES_ICON_CIRCLE}
</React.Fragment> onMouseDown={this._stopProp}
) : ( onMouseUp={this._stopProp}
<React.Fragment> onMouseEnter={() => this.setState({ tooltip: `${i}-view` })}
{this.state.tooltip && this.state.tooltip.startsWith(`${i}-`) ? ( onMouseLeave={() => this.setState({ tooltip: null })}
<Tooltip // onClick={(e) => {
light // this._stopProp(e);
style={ // this.props.onSelect(i);
this.state.tooltip === `${i}-add` // }}
? { >
position: "absolute", <SVG.Eye height="16px" />
top: 36, </div>
right: 8, </Link>
}
: 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 ? (
<div <div
css={STYLES_ICON_CIRCLE} css={STYLES_ICON_CIRCLE}
onMouseDown={this._stopProp} onMouseDown={this._stopProp}
onMouseUp={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 })} onMouseLeave={() => this.setState({ tooltip: null })}
onClick={ onClick={
this.props.external this.props.external
? this._handleLoginModal ? this._handleLoginModal
: this.state.items[i].data.type && : (e) => {
Validations.isPreviewableImage( this._handleRemoveFromSlate(e, i);
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",
} }
} }
style={{
position: "absolute",
top: 8,
right: 8,
cursor: "pointer",
margin: 0,
}}
css={STYLES_ICON_CIRCLE}
> >
{this.props.preview === <SVG.DismissCircle height="24px" />
Strings.getURLfromCID(this.state.items[i].cid) ? (
<SVG.DesktopEye
height="16px"
style={{
color: Constants.system.white,
}}
/>
) : (
<SVG.Desktop height="16px" />
)}
</div> </div>
) : ( ) : (
<div <div
css={STYLES_ICON_CIRCLE}
onMouseDown={this._stopProp} onMouseDown={this._stopProp}
onMouseUp={this._stopProp} onMouseUp={this._stopProp}
onMouseEnter={() => this.setState({ tooltip: `${i}-save` })} onMouseEnter={() => this.setState({ tooltip: `${i}-add` })}
onMouseLeave={() => this.setState({ tooltip: null })} onMouseLeave={() => this.setState({ tooltip: null })}
onClick={ onClick={
this.props.external this.props.external
? this._handleLoginModal ? 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>
)} )}
</div> <div
</React.Fragment> css={STYLES_ICON_ROW}
)} style={{
</div> bottom: this.state.fileNames
) : null} ? `calc(24px + ${TAG_HEIGHT}px)`
{this.state.fileNames ? ( : "24px",
<div }}
css={STYLES_FILE_TAG} >
style={{ <div
fontSize: `${Math.min(TAG_HEIGHT * unit * 0.7, 14)}px`, css={STYLES_ICON_CIRCLE}
height: `${TAG_HEIGHT * unit}px`, onMouseDown={this._stopProp}
}} onMouseUp={this._stopProp}
> onMouseEnter={() => this.setState({ tooltip: `${i}-download` })}
<span css={STYLES_FILE_NAME}> onMouseLeave={() => this.setState({ tooltip: null })}
{this.state.items[i].data.name || this.state.items[i].filename} onClick={
</span> this.props.external
<span css={STYLES_FILE_TYPE}> ? this._handleLoginModal
{Strings.getFileExtension(this.state.items[i].filename)} : (e) => {
</span> this._handleDownload(e, i);
</div> }
) : null} }
{this.state.editing ? ( >
<div <SVG.Download height="16px" />
css={STYLES_HANDLE_BOX} </div>
onMouseDown={(e) => this._handleMouseDownResize(e, i)} {this.props.isOwner ? (
style={{ <div
display: css={STYLES_ICON_CIRCLE}
this.state.hover === i || this.state.dragIndex === i ? "block" : "none", onMouseDown={this._stopProp}
}} onMouseUp={this._stopProp}
> onMouseEnter={() => this.setState({ tooltip: `${i}-preview` })}
<SVG.DragHandle height="24px" /> onMouseLeave={() => this.setState({ tooltip: null })}
</div> onClick={
) : null} this.props.external
</Selectable> ? 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}> <div css={STYLES_LOADER}>
@ -1688,7 +1749,7 @@ export class SlateLayout extends React.Component {
</div> </div>
</div> </div>
{this.state.modalShowDeleteFiles && ( {this.state.modalShowDeleteFiles && (
<ConfirmationModal <ConfirmationModal
type={"DELETE"} type={"DELETE"}
withValidation={false} withValidation={false}
callback={this._handleDeleteFiles} callback={this._handleDeleteFiles}
@ -1772,20 +1833,6 @@ export class SlateLayout extends React.Component {
value={this.state.copyValue} value={this.state.copyValue}
css={STYLES_COPY_INPUT} 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> </GroupSelectable>
</div> </div>
); );

View File

@ -12,17 +12,20 @@ import { endsWithAny } from "~/common/utilities";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
const STYLES_FAILURE = css` const STYLES_FAILURE = css`
background-color: ${Constants.system.pitchBlack};
color: ${Constants.system.white}; color: ${Constants.system.white};
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 88px; font-size: 24px;
margin: 0; margin: 0;
padding: 0; padding: 24px 36px;
height: 100px;
border-radius: 4px;
width: 100%; width: 100%;
min-height: 10%; min-height: 10%;
height: 100%; height: 100%;
text-decoration: none;
background-color: rgba(20, 20, 20, 0.8);
`; `;
const STYLES_OBJECT = css` const STYLES_OBJECT = css`
@ -94,7 +97,7 @@ export default class SlateMediaObject extends React.Component {
return ( return (
<> <>
{isMobile ? ( {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> <div css={STYLES_FAILURE}>Tap to open PDF in new tab</div>
</a> </a>
) : ( ) : (

View File

@ -8,6 +8,7 @@ import { Logo } from "~/common/logo";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { Boundary } from "~/components/system/components/fragments/Boundary"; import { Boundary } from "~/components/system/components/fragments/Boundary";
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation"; import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
import { Link } from "~/components/core/Link";
import ProcessedText from "~/components/core/ProcessedText"; import ProcessedText from "~/components/core/ProcessedText";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
@ -277,10 +278,12 @@ export class SlatePreviewBlock extends React.Component {
right: "-12px", right: "-12px",
}} }}
navigation={[ 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> </Boundary>
@ -437,44 +440,15 @@ export default class SlatePreviewBlocks extends React.Component {
render() { render() {
return ( return (
<div css={STYLES_SLATES}> <div css={STYLES_SLATES}>
{this.props.external {this.props.slates?.map((slate) => (
? this.props.slates?.map((slate) => ( <Link key={slate.id} href={`/$/slate/${slate.id}`} onAction={this.props.onAction}>
<a <SlatePreviewBlock
key={slate.id} isOwner={this.props.isOwner}
style={{ textDecoration: "none", color: Constants.system.black }} slate={slate}
href={ external={this.props.external}
!!this.props.username />
? `/${this.props.username}/${slate.slatename}` </Link>
: `/$/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>
))}
</div> </div>
); );
} }

View File

@ -2,6 +2,7 @@ import * as React from "react";
import * as Constants from "~/common/constants"; import * as Constants from "~/common/constants";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { Link } from "~/components/core/Link";
const STYLES_PRIMARY_TAB_GROUP = css` const STYLES_PRIMARY_TAB_GROUP = css`
font-size: ${Constants.typescale.lvl2}; font-size: ${Constants.typescale.lvl2};
@ -81,7 +82,8 @@ const STYLES_NAVTAB = css`
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
margin-right: 12px; margin-right: 12px;
font-size: ${Constants.typescale.lvl1}; height: 40px;
font-size: ${Constants.typescale.lvl0};
} }
:last-child { :last-child {
@ -91,99 +93,94 @@ const STYLES_NAVTAB = css`
export class SecondaryTabGroup extends React.Component { export class SecondaryTabGroup extends React.Component {
render() { render() {
const value = this.props.value || this.props.tabs[0].value;
console.log(this.props.value);
const disabled = this.props.disabled;
return ( return (
<div css={STYLES_SECONDARY_TAB_GROUP} style={this.props.style}> <div css={STYLES_SECONDARY_TAB_GROUP} style={this.props.style}>
{this.props.tabs.map((tab, i) => ( {this.props.tabs.map((tab, i) => {
<div const selected = value === tab.value?.tab;
css={STYLES_TAB} return (
key={this.props.onAction ? tab.title : i} <Link key={i} params={tab.value} onAction={this.props.onAction}>
style={{ <div
color: css={STYLES_TAB}
this.props.disabled || this.props.value === i style={{
? Constants.system.black color: disabled || selected ? Constants.system.black : "rgba(0,0,0,0.25)",
: "rgba(0,0,0,0.25)", cursor: disabled ? "auto" : "pointer",
cursor: this.props.disabled ? "auto" : "pointer", ...this.props.itemStyle,
...this.props.itemStyle, backgroundColor: selected ? Constants.system.white : "transparent",
backgroundColor: this.props.value === i ? Constants.system.white : "transparent", }}
}} // onClick={disabled || selected ? () => {} : () => this.props.onChange(tab.value)}
onClick={ >
this.props.disabled || this.props.value === i {tab.title}
? () => {} </div>
: this.props.onAction </Link>
? () => this.props.onAction({ type: "NAVIGATE", value: tab.value }) );
: () => this.props.onChange(i) })}
}
>
{this.props.onAction ? tab.title : tab}
</div>
))}
</div> </div>
); );
} }
} }
export class PrimaryTabGroup extends React.Component { // export class PrimaryTabGroup extends React.Component {
render() { // render() {
return ( // return (
<div css={STYLES_PRIMARY_TAB_GROUP} style={this.props.style}> // <div css={STYLES_PRIMARY_TAB_GROUP} style={this.props.style}>
{this.props.tabs.map((tab, i) => ( // {this.props.tabs.map((tab, i) => (
<div // <div
css={STYLES_TAB} // css={STYLES_TAB}
key={this.props.onAction ? tab.title : tab} // key={this.props.onAction ? tab.title : tab}
style={{ // style={{
padding: "8px 16px 8px 0", // padding: "8px 16px 8px 0",
color: // color:
this.props.disabled || this.props.value === i // this.props.disabled || this.props.value === i
? Constants.system.black // ? Constants.system.black
: "rgba(0,0,0,0.25)", // : "rgba(0,0,0,0.25)",
cursor: this.props.disabled ? "auto" : "pointer", // cursor: this.props.disabled ? "auto" : "pointer",
fontFamily: Constants.font.medium, // fontFamily: Constants.font.medium,
...this.props.itemStyle, // ...this.props.itemStyle,
}} // }}
onClick={ // onClick={
this.props.disabled || this.props.value === i // this.props.disabled || this.props.value === i
? () => {} // ? () => {}
: this.props.onAction // : this.props.onAction
? () => this.props.onAction({ type: "NAVIGATE", value: tab.value }) // ? () => this.props.onAction({ type: "NAVIGATE", value: tab.value })
: () => this.props.onChange(i) // : () => this.props.onChange(i)
} // }
> // >
{this.props.onAction ? tab.title : tab} // {this.props.onAction ? tab.title : tab}
</div> // </div>
))} // ))}
</div> // </div>
); // );
} // }
} // }
export class TabGroup extends React.Component { export class TabGroup extends React.Component {
render() { render() {
const value = this.props.value || this.props.tabs[0].value;
const disabled = this.props.disabled;
return ( return (
<div css={STYLES_TAB_GROUP} style={this.props.style}> <div css={STYLES_TAB_GROUP} style={this.props.style}>
{this.props.tabs.map((tab, i) => ( {this.props.tabs.map((tab, i) => {
<div const selected = value === tab.value?.subtab;
css={STYLES_NAVTAB} return (
key={this.props.onAction ? tab.title : tab} <Link key={i} params={tab.value} onAction={this.props.onAction}>
style={{ <div
color: css={STYLES_NAVTAB}
this.props.disabled || this.props.value === i style={{
? Constants.system.black color: disabled || selected ? Constants.system.black : "rgba(0,0,0,0.25)",
: "rgba(0,0,0,0.25)", cursor: disabled ? "auto" : "pointer",
cursor: this.props.disabled ? "auto" : "pointer", ...this.props.itemStyle,
borderBottom: this.props.value === i ? `1px solid ${Constants.system.black}` : "none", borderBottom: selected ? `1px solid ${Constants.system.black}` : "none",
...this.props.itemStyle, }}
}} // onClick={disabled || selected ? () => {} : () => this.props.onChange(tab.value)}
onClick={ >
this.props.disabled || this.props.value === i {tab.title}
? () => {} </div>
: this.props.onAction </Link>
? () => this.props.onAction({ type: "NAVIGATE", value: tab.value }) );
: () => this.props.onChange(i) })}
}
>
{this.props.onAction ? tab.title : tab}
</div>
))}
</div> </div>
); );
} }

View File

@ -62,17 +62,17 @@ const WebsitePrototypeHeader = (props) => {
<div css={STYLES_CONTAINER} style={props.style}> <div css={STYLES_CONTAINER} style={props.style}>
<div css={STYLES_LEFT}> <div css={STYLES_LEFT}>
<a css={STYLES_LINK} href="/" style={{ marginRight: 16, position: "relative", top: "1px" }}> <a css={STYLES_LINK} href="/" style={{ marginRight: 16, position: "relative", top: "1px" }}>
<Logo style={{ height: 16 }} /> <Logo style={{ height: 20 }} />
</a> </a>
</div> </div>
<div css={STYLES_RIGHT}> {/* <div css={STYLES_RIGHT}>
<a css={STYLES_LINK} href="/_" style={{ marginRight: 24 }}> <a css={STYLES_LINK} href="/_" style={{ marginRight: 24 }}>
Sign up Sign up
</a> </a>
<a css={STYLES_LINK} href="/_"> <a css={STYLES_LINK} href="/_">
Sign in Sign in
</a> </a>
</div> </div> */}
</div> </div>
); );
}; };

View File

@ -87,10 +87,10 @@ const WebsitePrototypeHeaderGeneric = (props) => {
</a> </a>
</div> </div>
<div css={STYLES_RIGHT}> <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 Sign up
</a> </a>
<a css={STYLES_LINK} href="/_"> <a css={STYLES_LINK} href="/_/auth?tab=signin">
Sign in Sign in
</a> </a>
</div> </div>

View File

@ -6,6 +6,9 @@ export default class WebsitePrototypeWrapper extends React.Component {
static defaultProps = { static defaultProps = {
image: image:
"https://slate.textile.io/ipfs/bafybeihtmqpx2lnlvaerfhq5imi2y3jzuf4jqspmmqbth3ebim4ebc2lqy", "https://slate.textile.io/ipfs/bafybeihtmqpx2lnlvaerfhq5imi2y3jzuf4jqspmmqbth3ebim4ebc2lqy",
title: "Slate",
url: "https://slate.host/_",
description: "An open-source file sharing network for research and collaboration",
}; };
render() { render() {

View File

@ -80,8 +80,8 @@ export default class SidebarAddFileToBucket extends React.Component {
this.props.onUpload({ this.props.onUpload({
files: e.target.files, files: e.target.files,
slate: slate:
this.props.current && this.props.current.slateId this.props.page.id === "NAV_SLATE" && this.props.data?.id
? { id: this.props.current.slateId } ? { id: this.props.data.id }
: null, : null,
}); });
this.props.onCancel(); this.props.onCancel();
@ -134,12 +134,10 @@ export default class SidebarAddFileToBucket extends React.Component {
<System.P> <System.P>
Click below or drop a file anywhere on the page to upload a file Click below or drop a file anywhere on the page to upload a file
{this.props.current && {this.props.data?.slatename || this.props.data?.data.name ? (
(this.props.current.slatename ||
(this.props.current.data && this.props.current.data.name)) ? (
<span> <span>
{" "} {" "}
to <strong>{Strings.getPresentationSlateName(this.props.current)}</strong> to <strong>{Strings.getPresentationSlateName(this.props.data)}</strong>
</span> </span>
) : ( ) : (
"" ""

View File

@ -9,6 +9,7 @@ import * as SVG from "~/common/svg";
import { RadioGroup } from "~/components/system/components/RadioGroup"; import { RadioGroup } from "~/components/system/components/RadioGroup";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { Link } from "~/components/core/Link";
const STYLES_TEXT = css` const STYLES_TEXT = css`
color: ${Constants.system.textGray}; color: ${Constants.system.textGray};
@ -91,10 +92,7 @@ export default class SidebarCreateSlate extends React.Component {
() => () =>
this.props.onAction({ this.props.onAction({
type: "NAVIGATE", type: "NAVIGATE",
value: "NAV_SLATE", href: `/$/slate/${response.slate.id}`,
data: {
id: response.slate.id,
},
}), }),
200 200
); );

View File

@ -86,7 +86,7 @@ export default class SidebarEditTags extends React.Component {
this._handleSave(); this._handleSave();
let newSuggestions = new Set([...this.state.suggestions, ...this.state.tags]); 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() { render() {

View File

@ -27,7 +27,7 @@ const STYLES_TEXT = css`
export default class SidebarCreateSlate extends React.Component { export default class SidebarCreateSlate extends React.Component {
state = { 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: "", email: "",
twitter: "", twitter: "",
message: "", message: "",
@ -51,12 +51,12 @@ export default class SidebarCreateSlate extends React.Component {
}); });
const response = await Actions.createSupportMessage({ const response = await Actions.createSupportMessage({
username: this.props.viewer.username, username: this.props.viewer?.username || "",
name: this.state.name, name: this.state.name,
email: this.state.email, email: this.state.email,
twitter: this.state.twitter, twitter: this.state.twitter,
message: this.state.message, message: this.state.message,
stored: Strings.bytesToSize(this.props.viewer.stats.bytes), stored: Strings.bytesToSize(this.props.viewer?.stats.bytes || 0),
}); });
Events.hasError(response); Events.hasError(response);

View File

@ -11,8 +11,8 @@ import * as UserBehaviors from "~/common/user-behaviors";
import { RadioGroup } from "~/components/system/components/RadioGroup"; import { RadioGroup } from "~/components/system/components/RadioGroup";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { ConfirmationModal } from "~/components/core/ConfirmationModal"; import { ConfirmationModal } from "~/components/core/ConfirmationModal";
import { Link } from "~/components/core/Link";
const SIZE_LIMIT = 1000000;
const DEFAULT_IMAGE = const DEFAULT_IMAGE =
"https://slate.textile.io/ipfs/bafkreiaow45dlq5xaydaeqocdxvffudibrzh2c6qandpqkb6t3ahbvh6re"; "https://slate.textile.io/ipfs/bafkreiaow45dlq5xaydaeqocdxvffudibrzh2c6qandpqkb6t3ahbvh6re";
@ -75,7 +75,10 @@ export default class SidebarSingleSlateSettings extends React.Component {
slate.data.tags = this.state.tags; slate.data.tags = this.state.tags;
let newSuggestions = new Set([...this.state.suggestions, ...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; break;
} }
@ -111,16 +114,16 @@ export default class SidebarSingleSlateSettings extends React.Component {
_handleDelete = async (res) => { _handleDelete = async (res) => {
if (!res) { if (!res) {
this.setState({ modalShow: false }) this.setState({ modalShow: false });
return; return;
} }
let slates = this.props.viewer.slates.filter((slate) => slate.id !== this.props.data.id); 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({ this.props.onAction({
type: "NAVIGATE", type: "NAVIGATE",
value: "NAV_SLATES", value: "/_/collections",
}); });
const response = await Actions.deleteSlate({ const response = await Actions.deleteSlate({
id: this.props.data.id, id: this.props.data.id,
@ -130,7 +133,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
return; return;
} }
this.setState({ modalShow: false }) this.setState({ modalShow: false });
}; };
render() { render() {
@ -143,7 +146,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
object.data.type && object.data.type &&
Validations.isPreviewableImage(object.data.type) && Validations.isPreviewableImage(object.data.type) &&
object.data.size && object.data.size &&
object.data.size < SIZE_LIMIT object.data.size < Constants.linkPreviewSizeLimit
) { ) {
preview = Strings.getURLfromCID(object.cid); preview = Strings.getURLfromCID(object.cid);
break; break;
@ -304,16 +307,20 @@ export default class SidebarSingleSlateSettings extends React.Component {
</System.ButtonPrimary> </System.ButtonPrimary>
<div style={{ marginTop: 16 }}> <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 Delete collection
</System.ButtonWarning> </System.ButtonWarning>
</div> </div>
</div> </div>
{this.state.modalShow && ( {this.state.modalShow && (
<ConfirmationModal <ConfirmationModal
type={"DELETE"} type={"DELETE"}
withValidation={false} withValidation={false}
callback={this._handleDelete} callback={this._handleDelete}
header={`Are you sure you want to delete the collection “${this.state.slatename}”?`} 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 cant undo this action.`} subHeader={`This collection will be deleted but all your files will remain in your file library. You cant undo this action.`}
/> />

View File

@ -116,31 +116,28 @@ const STYLES_MOBILE_HIDDEN = css`
export class GlobalCarousel extends React.Component { export class GlobalCarousel extends React.Component {
state = { state = {
index: 0,
visible: false,
showSidebar: true, showSidebar: true,
}; };
componentDidMount = () => { componentDidMount = () => {
window.addEventListener("keydown", this._handleKeyDown); window.addEventListener("keydown", this._handleKeyDown);
window.addEventListener("slate-global-open-carousel", this._handleOpen); // window.addEventListener("slate-global-open-carousel", this._handleOpen);
window.addEventListener("slate-global-close-carousel", this._handleClose); // window.addEventListener("slate-global-close-carousel", this._handleClose);
}; };
componentWillUnmount = () => { componentWillUnmount = () => {
window.removeEventListener("keydown", this._handleKeyDown); window.removeEventListener("keydown", this._handleKeyDown);
window.removeEventListener("slate-global-open-carousel", this._handleOpen); // window.removeEventListener("slate-global-open-carousel", this._handleOpen);
window.removeEventListener("slate-global-close-carousel", this._handleClose); // window.removeEventListener("slate-global-close-carousel", this._handleClose);
}; };
componentDidUpdate = (prevProps) => { findSelectedIndex = () => {
if ( const cid = this.props.params?.cid;
!this.props.objects || if (!cid) {
this.props.objects.length == 0 || return -1;
this.state.index >= this.props.objects.length
) {
this._handleClose();
} }
let index = this.props.objects.findIndex((elem) => elem.cid === cid);
return index;
}; };
_handleKeyDown = (e) => { _handleKeyDown = (e) => {
@ -173,95 +170,132 @@ export class GlobalCarousel extends React.Component {
} }
}; };
setWindowState = (data = {}) => { // setWindowState = (data = {}) => {
const cid = data?.cid; // const cid = data?.cid;
if (this.props.carouselType === "ACTIVITY") { // if (this.props.carouselType === "ACTIVITY") {
window.history.replaceState( // window.history.replaceState(
{ ...window.history.state, cid: cid }, // { ...window.history.state, cid: cid },
null, // null,
cid ? `/${data.owner}/${data.slate.slatename}/cid:${cid}` : `/_?scene=NAV_ACTIVITY` // cid ? `/${data.owner}/${data.slate.slatename}/cid:${cid}` : `/_?scene=NAV_ACTIVITY`
); // );
return; // return;
} // }
let baseURL = window.location.pathname.split("/"); // let baseURL = window.location.pathname.split("/");
if (this.props.carouselType === "SLATE") { // if (this.props.carouselType === "SLATE") {
baseURL.length = 3; // baseURL.length = 3;
} else if (this.props.carouselType === "PROFILE") { // } else if (this.props.carouselType === "PROFILE") {
baseURL.length = 2; // baseURL.length = 2;
} else if (this.props.carouselType === "DATA") { // } else if (this.props.carouselType === "DATA") {
baseURL.length = 2; // baseURL.length = 2;
if (cid) { // if (cid) {
baseURL[1] = this.props.viewer.username; // baseURL[1] = this.props.viewer.username;
} else { // } else {
baseURL[1] = "_?scene=NAV_DATA"; // baseURL[1] = "_?scene=NAV_DATA";
} // }
} // }
baseURL = baseURL.join("/"); // baseURL = baseURL.join("/");
window.history.replaceState( // window.history.replaceState(
{ ...window.history.state, cid: cid }, // { ...window.history.state, cid: cid },
null, // null,
cid ? `${baseURL}/cid:${cid}` : baseURL // cid ? `${baseURL}/cid:${cid}` : baseURL
); // );
}; // };
_handleOpen = (e) => { // _handleOpen = (e) => {
let index = e.detail.index; // let index = e.detail.index;
const objects = this.props.objects; // const objects = this.props.objects;
if (e.detail.index === null) { // if (e.detail.index === null) {
if (e.detail.id !== null) { // if (e.detail.id !== null) {
index = objects.findIndex((obj) => obj.id === e.detail.id); // index = objects.findIndex((obj) => obj.id === e.detail.id);
} // }
} // }
if (index === null || index < 0 || index >= objects.length) { // if (index === null || index < 0 || index >= objects.length) {
return; // return;
} // }
this.setState({ // this.setState({
visible: true, // visible: true,
index: e.detail.index, // index: e.detail.index,
}); // });
const data = objects[e.detail.index]; // const data = objects[e.detail.index];
this.setWindowState(data); // this.setWindowState(data);
}; // };
_handleClose = (e) => { _handleClose = (e) => {
if (this.state.visible) { if (e) {
if (e) { e.stopPropagation();
e.stopPropagation(); e.preventDefault();
e.preventDefault();
}
this.setState({ visible: false, index: 0 });
this.setWindowState();
} }
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) => { _handleNext = (e) => {
if (e) { if (e) {
e.stopPropagation(); e.stopPropagation();
} }
let index = this.state.index + 1; if (this.props.onChange) {
if (index >= this.props.objects.length) { let index = this.props.index + 1;
return; 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) => { _handlePrevious = (e) => {
if (e) { if (e) {
e.stopPropagation(); e.stopPropagation();
} }
let index = this.state.index - 1; if (this.props.onChange) {
if (index < 0) { let index = this.props.index - 1;
return; 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) => { _handleToggleSidebar = (e) => {
@ -272,27 +306,33 @@ export class GlobalCarousel extends React.Component {
}; };
render() { render() {
if ( let index;
!this.state.visible || if (this.props.onChange) {
!this.props.carouselType || index = this.props.index;
this.state.index < 0 || } else {
this.state.index >= this.props.objects.length index = this.findSelectedIndex();
) { }
if (!this.props.carouselType || index < 0 || index >= this.props.objects.length) {
return null; return null;
} }
let data = this.props.objects[this.state.index]; let file = this.props.objects[index];
let { isMobile, isOwner } = this.props; if (!file) {
return null;
}
let { isMobile } = this.props;
let isRepost = false; let isRepost = false;
if (this.props.carouselType === "SLATE") { 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 ( return (
<div css={STYLES_ROOT}> <div css={STYLES_ROOT}>
<Alert <Alert
viewer={this.props.viewer}
noWarning noWarning
id={isMobile ? "slate-mobile-alert" : null} id={isMobile ? "slate-mobile-alert" : null}
style={ style={
@ -308,7 +348,7 @@ export class GlobalCarousel extends React.Component {
} }
/> />
<div css={STYLES_ROOT_CONTENT} style={this.props.style} onClick={this._handleClose}> <div css={STYLES_ROOT_CONTENT} style={this.props.style} onClick={this._handleClose}>
{this.state.index > 0 && ( {index > 0 && (
<span <span
css={STYLES_BOX} css={STYLES_BOX}
onClick={this._handlePrevious} onClick={this._handlePrevious}
@ -317,7 +357,7 @@ export class GlobalCarousel extends React.Component {
<SVG.ChevronLeft height="20px" /> <SVG.ChevronLeft height="20px" />
</span> </span>
)} )}
{this.state.index < this.props.objects.length - 1 && ( {index < this.props.objects.length - 1 && (
<span <span
css={STYLES_BOX} css={STYLES_BOX}
onClick={this._handleNext} onClick={this._handleNext}
@ -351,12 +391,13 @@ export class GlobalCarousel extends React.Component {
</div> </div>
<span css={STYLES_MOBILE_HIDDEN}> <span css={STYLES_MOBILE_HIDDEN}>
<CarouselSidebar <CarouselSidebar
key={data.id} key={file.id}
{...this.props} {...this.props}
data={data} file={file}
display={this.state.showSidebar ? "block" : "none"} display={this.state.showSidebar ? "block" : "none"}
onClose={this._handleClose} onClose={this._handleClose}
isRepost={isRepost} isRepost={isRepost}
onAction={this.props.onAction}
/> />
</span> </span>
</div> </div>

View File

@ -1,5 +1,6 @@
import * as React from "react"; import * as React from "react";
import * as Constants from "~/common/constants"; import * as Constants from "~/common/constants";
import * as Styles from "~/common/styles";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
@ -7,27 +8,37 @@ const STYLES_POPOVER = css`
z-index: ${Constants.zindex.tooltip}; z-index: ${Constants.zindex.tooltip};
box-sizing: border-box; box-sizing: border-box;
font-family: ${Constants.font.text}; font-family: ${Constants.font.text};
width: 200px; min-width: 200px;
border-radius: 4px; border-radius: 4px;
user-select: none; user-select: none;
position: absolute; position: absolute;
background-color: ${Constants.system.white}; background-color: ${Constants.system.white};
color: ${Constants.system.pitchBlack}; color: ${Constants.system.pitchBlack};
box-shadow: ${Constants.shadow.medium}; 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` const STYLES_POPOVER_ITEM = css`
font-family: ${Constants.font.medium};
box-sizing: border-box; box-sizing: border-box;
top: 0; padding: 6px 0px;
left: 0;
padding: 8px 0px;
display: flex; display: flex;
align-items: center; align-items: center;
transition: 200ms ease all; transition: 200ms ease all;
cursor: pointer; cursor: pointer;
font-size: ${Constants.typescale.lvlN1};
:hover { :hover {
color: ${Constants.system.brand}; color: ${Constants.system.brand};
@ -37,15 +48,27 @@ const STYLES_POPOVER_ITEM = css`
export class PopoverNavigation extends React.Component { export class PopoverNavigation extends React.Component {
render() { render() {
return ( return (
<div css={STYLES_POPOVER} style={this.props.style}> <div
{this.props.navigation.map((each, i) => ( css={STYLES_POPOVER}
<div style={this.props.style}
key={i} onClick={(e) => {
css={STYLES_POPOVER_ITEM} e.stopPropagation();
style={this.props.itemStyle} e.preventDefault();
onClick={each.onClick} }}
> >
{each.text} {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>
))} ))}
</div> </div>

View File

@ -98,6 +98,8 @@ export class Boundary extends React.PureComponent {
}; };
_handleOutsideRectEvent = (e) => { _handleOutsideRectEvent = (e) => {
e.stopPropagation();
e.preventDefault();
this.props.onOutsideRectEvent(e); this.props.onOutsideRectEvent(e);
}; };

View File

@ -6,6 +6,7 @@ import * as Strings from "~/common/strings";
import { LoaderSpinner } from "~/components/system/components/Loaders"; import { LoaderSpinner } from "~/components/system/components/Loaders";
import { CodeText } from "~/components/system/components/fragments/CodeText"; import { CodeText } from "~/components/system/components/fragments/CodeText";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { Link } from "~/components/core/Link";
import Avatar from "~/components/core/Avatar"; 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} />; return <span css={STYLES_TABLE_CONTENT_LINK} {...props} />;
}; };
@ -188,7 +189,11 @@ export const TableContent = ({ type, text, action, data = {}, onAction }) => {
case "DEAL_CATEGORY": case "DEAL_CATEGORY":
return <React.Fragment>{text == 1 ? "Storage" : "Retrieval"}</React.Fragment>; return <React.Fragment>{text == 1 ? "Storage" : "Retrieval"}</React.Fragment>;
case "BUTTON": 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": case "TRANSACTION_DIRECTION":
return COMPONENTS_TRANSACTION_DIRECTION[text]; return COMPONENTS_TRANSACTION_DIRECTION[text];
case "TRANSACTION_STATUS": case "TRANSACTION_STATUS":
@ -274,14 +279,16 @@ export const TableContent = ({ type, text, action, data = {}, onAction }) => {
return text; return text;
} }
return <Link onClick={() => window.open(text)}>{text}</Link>; return <LinkItem onClick={() => window.open(text)}>{text}</LinkItem>;
case "SLATE_LINK": case "SLATE_LINK":
if (!data) { if (!data) {
return text; return text;
} }
return ( 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": case "FILE_LINK":
if (!data) { if (!data) {
@ -289,7 +296,9 @@ export const TableContent = ({ type, text, action, data = {}, onAction }) => {
} }
return ( return (
<Link onClick={() => onAction({ type: "NAVIGATE", value: "NAV_FILE", data })}>{text}</Link> <LinkItem onClick={() => onAction({ type: "NAVIGATE", value: "NAV_FILE", data })}>
{text}
</LinkItem>
); );
default: default:
return text; return text;

View File

@ -28,20 +28,31 @@ export default async ({
"activity.id", "activity.id",
"activity.type", "activity.type",
"activity.createdAt", "activity.createdAt",
users(), "activity.slateId",
slates(), // users(),
// slates(),
files() files()
) )
.from("activity") .from("activity")
.join("users", "users.id", "=", "activity.ownerId") // .join("users", "users.id", "=", "activity.ownerId")
.leftJoin("files", "files.id", "=", "activity.fileId") .leftJoin("files", "files.id", "=", "activity.fileId")
.leftJoin("slates", "slates.id", "=", "activity.slateId") // .leftJoin("slates", "slates.id", "=", "activity.slateId")
.where("activity.type", "CREATE_SLATE_OBJECT") .whereRaw("?? < ? and ?? = ? and (?? = any(?) or ?? = any(?))", [
.where("activity.createdAt", "<", date.toISOString()) "activity.createdAt",
.whereIn("activity.ownerId", following) date.toISOString(),
.orWhereIn("activity.slateId", subscriptions) "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") .orderBy("activity.createdAt", "desc")
.limit(100); .limit(96);
} else if (latestTimestamp) { } else if (latestTimestamp) {
//NOTE(martina): for fetching new updates since the last time they loaded //NOTE(martina): for fetching new updates since the last time they loaded
let date = new Date(latestTimestamp); let date = new Date(latestTimestamp);
@ -50,39 +61,59 @@ export default async ({
"activity.id", "activity.id",
"activity.type", "activity.type",
"activity.createdAt", "activity.createdAt",
users(), "activity.slateId",
slates(), // users(),
// slates(),
files() files()
) )
.from("activity") .from("activity")
.join("users", "users.id", "=", "activity.ownerId") // .join("users", "users.id", "=", "activity.ownerId")
.leftJoin("files", "files.id", "=", "activity.fileId") .leftJoin("files", "files.id", "=", "activity.fileId")
.leftJoin("slates", "slates.id", "=", "activity.slateId") // .leftJoin("slates", "slates.id", "=", "activity.slateId")
.where("activity.createdAt", ">", date.toISOString()) .whereRaw("?? > ? and ?? = ? and (?? = any(?) or ?? = any(?))", [
.where("activity.type", "CREATE_SLATE_OBJECT") "activity.createdAt",
.whereIn("activity.ownerId", following) date.toISOString(),
.orWhereIn("activity.slateId", subscriptions) "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") .orderBy("activity.createdAt", "desc")
.limit(100); .limit(96);
} else { } else {
//NOTE(martina): for the first fetch they make, when they have not loaded any explore events yet //NOTE(martina): for the first fetch they make, when they have not loaded any explore events yet
query = await DB.select( query = await DB.select(
"activity.id", "activity.id",
"activity.type", "activity.type",
"activity.createdAt", "activity.createdAt",
users(), "activity.slateId",
slates(), // users(),
// slates(),
files() files()
) )
.from("activity") .from("activity")
.join("users", "users.id", "=", "activity.ownerId") // .join("users", "users.id", "=", "activity.ownerId")
.leftJoin("files", "files.id", "=", "activity.fileId") .leftJoin("files", "files.id", "=", "activity.fileId")
.leftJoin("slates", "slates.id", "=", "activity.slateId") // .leftJoin("slates", "slates.id", "=", "activity.slateId")
.where("activity.type", "CREATE_SLATE_OBJECT") .whereRaw("?? = ? and (?? = any(?) or ?? = any(?))", [
.whereIn("activity.ownerId", following) "activity.type",
.orWhereIn("activity.slateId", subscriptions) "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") .orderBy("activity.createdAt", "desc")
.limit(100); .limit(96);
} }
if (!query) { if (!query) {

View File

@ -23,18 +23,19 @@ export default async ({ earliestTimestamp, latestTimestamp }) => {
"activity.id", "activity.id",
"activity.type", "activity.type",
"activity.createdAt", "activity.createdAt",
users(), "activity.slateId",
slates(), // users(),
// slates(),
files() files()
) )
.from("activity") .from("activity")
.join("users", "users.id", "=", "activity.ownerId") // .join("users", "users.id", "=", "activity.ownerId")
.leftJoin("files", "files.id", "=", "activity.fileId") .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.createdAt", "<", date.toISOString())
.where("activity.type", "CREATE_SLATE_OBJECT") .where("activity.type", "CREATE_SLATE_OBJECT")
.orderBy("activity.createdAt", "desc") .orderBy("activity.createdAt", "desc")
.limit(100); .limit(96);
} else if (latestTimestamp) { } else if (latestTimestamp) {
//NOTE(martina): for fetching new updates since the last time they loaded //NOTE(martina): for fetching new updates since the last time they loaded
let date = new Date(latestTimestamp); let date = new Date(latestTimestamp);
@ -43,35 +44,37 @@ export default async ({ earliestTimestamp, latestTimestamp }) => {
"activity.id", "activity.id",
"activity.type", "activity.type",
"activity.createdAt", "activity.createdAt",
users(), "activity.slateId",
slates(), // users(),
// slates(),
files() files()
) )
.from("activity") .from("activity")
.join("users", "users.id", "=", "activity.ownerId") // .join("users", "users.id", "=", "activity.ownerId")
.leftJoin("files", "files.id", "=", "activity.fileId") .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.createdAt", ">", date.toISOString())
.where("activity.type", "CREATE_SLATE_OBJECT") .where("activity.type", "CREATE_SLATE_OBJECT")
.orderBy("activity.createdAt", "desc") .orderBy("activity.createdAt", "desc")
.limit(100); .limit(96);
} else { } else {
//NOTE(martina): for the first fetch they make, when they have not loaded any explore events yet //NOTE(martina): for the first fetch they make, when they have not loaded any explore events yet
query = await DB.select( query = await DB.select(
"activity.id", "activity.id",
"activity.type", "activity.type",
"activity.createdAt", "activity.createdAt",
users(), "activity.slateId",
slates(), // users(),
// slates(),
files() files()
) )
.from("activity") .from("activity")
.join("users", "users.id", "=", "activity.ownerId") // .join("users", "users.id", "=", "activity.ownerId")
.leftJoin("files", "files.id", "=", "activity.fileId") .leftJoin("files", "files.id", "=", "activity.fileId")
.leftJoin("slates", "slates.id", "=", "activity.slateId") // .leftJoin("slates", "slates.id", "=", "activity.slateId")
.where("activity.type", "CREATE_SLATE_OBJECT") .where("activity.type", "CREATE_SLATE_OBJECT")
.orderBy("activity.createdAt", "desc") .orderBy("activity.createdAt", "desc")
.limit(100); .limit(96);
} }
if (!query || query.error) { if (!query || query.error) {

View File

@ -20,10 +20,18 @@ export default async ({ ids, sanitize = false, publicOnly = false }) => {
.from("files") .from("files")
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id") .leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
.leftJoin("slates", "slates.id", "=", "slate_files.slateId") .leftJoin("slates", "slates.id", "=", "slate_files.slateId")
.whereIn("files.id", ids) .whereRaw("?? = any(?) and (?? = ? or ?? = ?)", [
.where("files.isPublic", true) "files.id",
.orWhereIn("files.id", ids) ids,
.andWhere("slates.isPublic", true) "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"); .groupBy("files.id");
} else { } else {
query = await DB.select("*").from("files").whereIn("id", ids); query = await DB.select("*").from("files").whereIn("id", ids);

View File

@ -20,8 +20,16 @@ export default async ({ id, sanitize = false, publicOnly = false }) => {
.from("files") .from("files")
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id") .leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
.leftJoin("slates", "slate_files.slateId", "=", "slates.id") .leftJoin("slates", "slate_files.slateId", "=", "slates.id")
.where({ "files.ownerId": id, "slates.isPublic": true }) .whereRaw("?? = ? and (?? = ? or ?? = ?)", [
.orWhere({ "files.ownerId": id, "files.isPublic": true }) "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") .orderBy("files.createdAt", "desc")
.groupBy("files.id"); .groupBy("files.id");
} else { } else {

View File

@ -53,8 +53,16 @@ export default async ({ id, sanitize = false, includeFiles = false, publicOnly =
.from("files") .from("files")
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id") .leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
.leftJoin("slates", "slate_files.slateId", "=", "slates.id") .leftJoin("slates", "slate_files.slateId", "=", "slates.id")
.where({ "files.ownerId": id, "slates.isPublic": true }) .whereRaw("?? = ? and (?? = ? or ?? = ?)", [
.orWhere({ "files.ownerId": id, "files.isPublic": true }) "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") .orderBy("files.createdAt", "desc")
.groupBy("files.id"); .groupBy("files.id");

View File

@ -64,25 +64,35 @@ export default async ({ username, sanitize = false, includeFiles = false, public
// .first(); // .first();
query = await DB.select("*").from("users").where({ username }).first(); query = await DB.select("*").from("users").where({ username }).first();
const id = query.id; const id = query?.id;
let library = await DB.select( if (id) {
"files.id", let library = await DB.select(
"files.ownerId", "files.id",
"files.cid", "files.ownerId",
"files.isPublic", "files.cid",
"files.filename", "files.isPublic",
"files.data" "files.filename",
) "files.data"
.from("files") )
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id") .from("files")
.leftJoin("slates", "slate_files.slateId", "=", "slates.id") .leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
.where({ "files.ownerId": id, "slates.isPublic": true }) .leftJoin("slates", "slate_files.slateId", "=", "slates.id")
.orWhere({ "files.ownerId": id, "files.isPublic": true }) // .where({ "files.ownerId": id, "slates.isPublic": true })
.orderBy("files.createdAt", "desc") // .orWhere({ "files.ownerId": id, "files.isPublic": true })
.groupBy("files.id"); .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 { } else {
query = await DB.select( query = await DB.select(
"users.id", "users.id",

View File

@ -221,7 +221,7 @@ export const getById = async ({ id }) => {
pdfBytes, pdfBytes,
}, },
tags, tags,
userBucketCID: bucketRoot?.path, userBucketCID: bucketRoot?.path || null,
keys, keys,
slates, slates,
subscriptions, subscriptions,

View File

@ -6,20 +6,19 @@ import { IncomingWebhook } from "@slack/webhook";
const url = `https://hooks.slack.com/services/${Environment.SUPPORT_SLACK_WEBHOOK_KEY}`; const url = `https://hooks.slack.com/services/${Environment.SUPPORT_SLACK_WEBHOOK_KEY}`;
const webhook = new IncomingWebhook(url); const webhook = new IncomingWebhook(url);
export const sendSlackMessage = ({ export const sendSlackMessage = ({ username, name, email, twitter, message, stored }) => {
username,
name,
email,
twitter,
message,
stored,
}) => {
if (Strings.isEmpty(Environment.SUPPORT_SLACK_WEBHOOK_KEY)) { if (Strings.isEmpty(Environment.SUPPORT_SLACK_WEBHOOK_KEY)) {
return false; return false;
} }
const userProfileURL = `https://slate.host/${username}`; let userURL;
const userURL = `<${userProfileURL}|${username}>`; if (username) {
const userProfileURL = `https://slate.host/${username}`;
userURL = `<${userProfileURL}|${username}>`;
} else {
userURL = "[Not authenticated]";
}
let twitterURL = ""; let twitterURL = "";
if (twitter) { if (twitter) {
const twitterProfileURL = `https://twitter.com/${twitter.replace("@", "")}`; const twitterProfileURL = `https://twitter.com/${twitter.replace("@", "")}`;

4
package-lock.json generated
View File

@ -4556,7 +4556,11 @@
"version": "2.6.12", "version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", "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.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 "hasInstallScript": true
}, },
"node_modules/core-js-compat": { "node_modules/core-js-compat": {

View File

@ -3,13 +3,18 @@ import * as React from "react";
import Application from "~/components/core/Application"; import Application from "~/components/core/Application";
export const getServerSideProps = async ({ query }) => { 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 { return {
props: { props: { ...query },
viewer: query.viewer,
isMobile: query.isMobile,
isMac: query.isMac,
resources: query.resources,
},
}; };
}; };
@ -17,10 +22,13 @@ export default class ApplicationPage extends React.Component {
render() { render() {
return ( return (
<Application <Application
viewer={this.props.viewer} {...this.props}
isMobile={this.props.isMobile} // viewer={this.props.viewer}
isMac={this.props.isMac} // isMobile={this.props.isMobile}
resources={this.props.resources} // isMac={this.props.isMac}
// resources={this.props.resources}
// page={this.props.page}
// data={this.props.data}
/> />
); );
} }

View File

@ -22,7 +22,6 @@ import WebsitePrototypeHeader from "~/components/core/WebsitePrototypeHeader";
import WebsitePrototypeFooter from "~/components/core/WebsitePrototypeFooter"; import WebsitePrototypeFooter from "~/components/core/WebsitePrototypeFooter";
import CTATransition from "~/components/core/CTATransition"; import CTATransition from "~/components/core/CTATransition";
const SIZE_LIMIT = 1000000; //NOTE(martina): 1mb limit for twitter preview images
const DEFAULT_IMAGE = const DEFAULT_IMAGE =
"https://slate.textile.io/ipfs/bafkreiaow45dlq5xaydaeqocdxvffudibrzh2c6qandpqkb6t3ahbvh6re"; "https://slate.textile.io/ipfs/bafkreiaow45dlq5xaydaeqocdxvffudibrzh2c6qandpqkb6t3ahbvh6re";
const DEFAULT_BOOK = const DEFAULT_BOOK =
@ -233,7 +232,7 @@ export default class SlatePage extends React.Component {
objects[i].data.type && objects[i].data.type &&
Validations.isPreviewableImage(objects[i].data.type) && Validations.isPreviewableImage(objects[i].data.type) &&
objects[i].data.size && objects[i].data.size &&
objects[i].data.size < SIZE_LIMIT objects[i].data.size < Constants.linkPreviewSizeLimit
) { ) {
image = Strings.getURLfromCID(objects[i].cid); image = Strings.getURLfromCID(objects[i].cid);
break; break;
@ -320,7 +319,7 @@ export default class SlatePage extends React.Component {
</div> </div>
<div css={STYLES_SLATE}> <div css={STYLES_SLATE}>
<GlobalCarousel <GlobalCarousel
current={this.props.slate} data={this.props.slate}
carouselType="SLATE" carouselType="SLATE"
viewer={this.props.viewer} viewer={this.props.viewer}
objects={objects} objects={objects}

View File

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

View File

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

View File

@ -3,6 +3,8 @@ import * as Utilities from "~/node_common/utilities";
export default async (req, res) => { export default async (req, res) => {
const id = Utilities.getIdFromCookie(req); const id = Utilities.getIdFromCookie(req);
console.log(id);
console.log(req);
if (!id) { if (!id) {
return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true }); return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true });
} }

View File

@ -59,7 +59,4 @@ export default async (req, res) => {
const token = JWT.sign({ id: user.id, username: user.username }, Environment.JWT_SECRET); const token = JWT.sign({ id: user.id, username: user.username }, Environment.JWT_SECRET);
res.status(200).send({ decorator: "SERVER_SIGN_IN", success: true, token }); res.status(200).send({ decorator: "SERVER_SIGN_IN", success: true, token });
if (req.body.data.redirectURL) {
res.redirect(req.body.data.redirectURL);
}
}; };

View File

@ -4,29 +4,6 @@ import * as Utilities from "~/node_common/utilities";
import * as Strings from "~/common/strings"; import * as Strings from "~/common/strings";
export default async (req, res) => { 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; let slate;
if (req.body.data.id) { if (req.body.data.id) {
slate = await Data.getSlateById({ id: req.body.data.id, includeFiles: true, sanitize: true }); 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) { if (!slate.isPublic) {
return res.status(403).send({ const id = Utilities.getIdFromCookie(req);
decorator: "SERVER_GET_SERIALIZED_SLATE_PRIVATE_ACCESS_DENIED",
error: true, 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 }); let owner = await Data.getUserById({ id: slate.ownerId, sanitize: true });
@ -66,6 +47,6 @@ export default async (req, res) => {
return res.status(200).send({ return res.status(200).send({
decorator: "SERVER_GET_SERIALIZED_SLATE", decorator: "SERVER_GET_SERIALIZED_SLATE",
slate, data: slate,
}); });
}; };

View File

@ -4,29 +4,6 @@ import * as Serializers from "~/node_common/serializers";
import * as Strings from "~/common/strings"; import * as Strings from "~/common/strings";
export default async (req, res) => { 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({ const response = await Data.getSlateById({
id: req.body.data.id, id: req.body.data.id,
includeFiles: true, includeFiles: true,
@ -41,11 +18,15 @@ export default async (req, res) => {
return res.status(500).send({ decorator: "SERVER_GET_SLATE_NOT_FOUND", error: true }); return res.status(500).send({ decorator: "SERVER_GET_SLATE_NOT_FOUND", error: true });
} }
if (!response.isPublic && response.ownerId !== id) { if (!response.isPublic) {
return res.status(403).send({ const id = Utilities.getIdFromCookie(req);
decorator: "SERVER_GET_SLATE_PRIVATE_ACCESS_DENIED",
error: true, 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 }); return res.status(200).send({ decorator: "SERVER_GET_SLATE", slate: response });

View File

@ -4,22 +4,22 @@ import * as Serializers from "~/node_common/serializers";
import * as Support from "~/node_common/support"; import * as Support from "~/node_common/support";
export default async (req, res) => { export default async (req, res) => {
const id = Utilities.getIdFromCookie(req); // const id = Utilities.getIdFromCookie(req);
if (!id) { // if (!id) {
return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true }); // return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true });
} // }
const user = await Data.getUserById({ // const user = await Data.getUserById({
id, // id,
}); // });
if (!user) { // if (!user) {
return res.status(404).send({ decorator: "SERVER_USER_NOT_FOUND", error: true }); // return res.status(404).send({ decorator: "SERVER_USER_NOT_FOUND", error: true });
} // }
if (user.error) { // if (user.error) {
return res.status(500).send({ decorator: "SERVER_USER_NOT_FOUND", error: true }); // return res.status(500).send({ decorator: "SERVER_USER_NOT_FOUND", error: true });
} // }
if (!req.body.data) { if (!req.body.data) {
return res.status(500).send({ 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); let status = await Support.sendSlackMessage(req.body.data);
if (status) { if (status) {
return res.status(200).send({ return res.status(200).send({

View File

@ -3,10 +3,6 @@ import * as Strings from "~/common/strings";
import * as Utilities from "~/node_common/utilities"; import * as Utilities from "~/node_common/utilities";
export default async (req, res) => { 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; let user;
if (req.body.data.id) { if (req.body.data.id) {

View File

@ -10,6 +10,7 @@ import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { TabGroup, PrimaryTabGroup, SecondaryTabGroup } from "~/components/core/TabGroup"; import { TabGroup, PrimaryTabGroup, SecondaryTabGroup } from "~/components/core/TabGroup";
import { LoaderSpinner } from "~/components/system/components/Loaders"; import { LoaderSpinner } from "~/components/system/components/Loaders";
import { Link } from "~/components/core/Link";
import EmptyState from "~/components/core/EmptyState"; import EmptyState from "~/components/core/EmptyState";
import ScenePage from "~/components/core/ScenePage"; import ScenePage from "~/components/core/ScenePage";
@ -64,15 +65,6 @@ const STYLES_SECONDARY = css`
width: 100%; width: 100%;
`; `;
const STYLES_SECONDARY_HOVERABLE = css`
${STYLES_SECONDARY}
padding: 8px 16px;
:hover {
color: ${Constants.system.brand} !important;
}
`;
const STYLES_GRADIENT = css` const STYLES_GRADIENT = css`
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
@ -112,8 +104,8 @@ class ActivitySquare extends React.Component {
render() { render() {
const item = this.props.item; const item = this.props.item;
const size = this.props.size; const size = this.props.size;
const isImage = // const isImage =
Validations.isPreviewableImage(item.file.data.type) || !!item.file.data.coverImage; // Validations.isPreviewableImage(item.file.data.type) || !!item.file.data.coverImage;
return ( return (
<div <div
css={STYLES_IMAGE_BOX} css={STYLES_IMAGE_BOX}
@ -128,42 +120,30 @@ class ActivitySquare extends React.Component {
style={{ border: "none" }} style={{ border: "none" }}
imageStyle={{ 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> </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 }) => { const ActivityRectangle = ({ item, width, height }) => {
let file; let file;
for (let obj of item.slate?.objects || []) { for (let obj of item.slate?.objects || []) {
@ -212,21 +192,21 @@ export default class SceneActivity extends React.Component {
counter = 0; counter = 0;
state = { state = {
imageSize: 200, imageSize: 200,
tab: 0, loading: false,
loading: "loading", carouselIndex: -1,
}; };
async componentDidMount() { async componentDidMount() {
this.fetchActivityItems(true);
this.calculateWidth(); this.calculateWidth();
this.debounceInstance = Window.debounce(this.calculateWidth, 200); this.debounceInstance = Window.debounce(this.calculateWidth, 200);
this.scrollDebounceInstance = Window.debounce(this._handleScroll, 200); this.scrollDebounceInstance = Window.debounce(this._handleScroll, 200);
window.addEventListener("resize", this.debounceInstance); window.addEventListener("resize", this.debounceInstance);
window.addEventListener("scroll", this.scrollDebounceInstance); window.addEventListener("scroll", this.scrollDebounceInstance);
this.fetchActivityItems(true);
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (prevProps.tab !== this.props.tab) { if (prevProps.page.params?.tab !== this.props.page.params?.tab) {
this.fetchActivityItems(true); this.fetchActivityItems(true);
} }
} }
@ -258,10 +238,23 @@ export default class SceneActivity extends React.Component {
}; };
fetchActivityItems = async (update = false) => { 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" }); 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 = {}; let requestObject = {};
if (activity.length) { if (activity.length) {
if (update) { if (update) {
@ -271,6 +264,7 @@ export default class SceneActivity extends React.Component {
} }
} }
console.log("start fetching");
let response; let response;
if (isExplore) { if (isExplore) {
response = await Actions.getExplore(requestObject); response = await Actions.getExplore(requestObject);
@ -280,13 +274,13 @@ export default class SceneActivity extends React.Component {
response = await Actions.getActivity(requestObject); response = await Actions.getActivity(requestObject);
} }
console.log("finished fetching");
if (Events.hasError(response)) { if (Events.hasError(response)) {
this.setState({ loading: "failed" }); this.setState({ loading: false });
return; return;
} }
let newItems = response.activity || response.explore; let newItems = response.data;
if (update) { if (update) {
activity.unshift(...newItems); activity.unshift(...newItems);
@ -297,21 +291,26 @@ export default class SceneActivity extends React.Component {
activity.push(...newItems); activity.push(...newItems);
} }
if (this.props.tab === 0) { if (this.props.viewer) {
this.props.onUpdateViewer({ activity: activity }); 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 { } else {
this.props.onUpdateViewer({ explore: activity }); this.setState({ explore: activity, loading: false });
} }
this.setState({ loading: false });
}; };
formatActivity = (userActivity) => { formatActivity = (userActivity) => {
let activity = []; let activity = [];
for (let item of userActivity) { for (let item of userActivity) {
if (item.slate && !item.slate.isPublic) { // if (item.slate && !item.slate.isPublic) {
continue; // continue;
} // }
if (item.type === "CREATE_SLATE_OBJECT" && item.slate && item.file) { if (item.type === "CREATE_SLATE_OBJECT") {
//&& item.slate && item.file
activity.push(item); activity.push(item);
} else if (item.type === "CREATE_SLATE" && item.slate) { } else if (item.type === "CREATE_SLATE" && item.slate) {
activity.push(item); activity.push(item);
@ -345,14 +344,6 @@ export default class SceneActivity extends React.Component {
// return activity; // return activity;
}; };
_handleCreateSlate = () => {
this.props.onAction({
type: "NAVIGATE",
value: "NAV_SLATES",
data: null,
});
};
calculateWidth = () => { calculateWidth = () => {
let windowWidth = window.innerWidth; let windowWidth = window.innerWidth;
let imageSize; let imageSize;
@ -370,83 +361,92 @@ export default class SceneActivity extends React.Component {
}; };
render() { render() {
let activity = let tab = this.props.page.params?.tab;
this.props.tab === 0 ? this.props.viewer.activity || [] : this.props.viewer.explore || []; 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 let items = activity
.filter((item) => item.type === "CREATE_SLATE_OBJECT") .filter((item) => item.type === "CREATE_SLATE_OBJECT")
.map((item) => { .map((item) => {
return { return {
...item.file, ...item.file,
slate: item.slate, slateId: item.slateId,
owner: item.owner?.username, // slate: item.slate,
// owner: item.owner?.username,
}; };
}); });
return ( return (
<ScenePage> <ScenePage>
<ScenePageHeader {this.props.viewer && (
title={ <SecondaryTabGroup
this.props.isMobile ? ( tabs={[
<TabGroup { title: "My network", value: { tab: "activity" } },
tabs={[ { title: "Explore", value: { tab: "explore" } },
{ title: "Files", value: "NAV_DATA" }, ]}
{ title: "Collections", value: "NAV_SLATES" }, value={tab}
{ title: "Activity", value: "NAV_ACTIVITY" }, onAction={this.props.onAction}
]} style={{ marginTop: 0 }}
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 }}
/>
}
/>
<GlobalCarousel <GlobalCarousel
carouselType="ACTIVITY" carouselType="ACTIVITY"
viewer={this.props.viewer} viewer={this.props.viewer}
objects={items} objects={items}
onAction={this.props.onAction} 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} isMobile={this.props.isMobile}
// params={this.props.page.params}
isOwner={false} isOwner={false}
/> />
{activity.length ? ( {activity.length ? (
<div> <div>
<div css={STYLES_ACTIVITY_GRID}> <div css={STYLES_ACTIVITY_GRID}>
{activity.map((item, index) => { {activity.map((item, i) => {
if (item.type === "CREATE_SLATE") { if (item.type === "CREATE_SLATE") {
return ( 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} key={item.id}
onClick={() => onClick={() =>
this.props.onAction({ this.props.onAction({
type: "NAVIGATE", type: "NAVIGATE",
value: "NAV_SLATE", value: "NAV_SLATE",
data: { decorator: "SLATE", ...item.slate }, data: item.slate,
}) })
} }
> > */}
<ActivityRectangle <ActivityRectangle
width={ width={
this.props.isMobile ? this.state.imageSize : this.state.imageSize * 2 + 20 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} height={this.state.imageSize}
item={item} item={item}
/> />
</span> {/* </span> */}
</Link>
); );
} else if (item.type === "CREATE_SLATE_OBJECT") { } else if (item.type === "CREATE_SLATE_OBJECT") {
return ( return (
<span <Link
key={item.id} redirect
onClick={ key={i}
this.props.isMobile disabled={this.props.isMobile ? false : true}
? () => { // params={
this.props.onAction({ // this.props.isMobile
type: "NAVIGATE", // ? null
value: "NAV_SLATE", // : { ...this.props.page.params, cid: item.file.cid }
data: { // }
decorator: "SLATE", href={`/$/slate/${item.slateId}?cid=${item.file.cid}`}
...item.slate, onAction={this.props.onAction}
}, onClick={() => this.setState({ carouselIndex: i })}
}); // onClick={
} // this.props.isMobile
: () => // ? () => {}
Events.dispatchCustomEvent({ // : () =>
name: "slate-global-open-carousel", // Events.dispatchCustomEvent({
detail: { index: this.getItemIndexById(items, item) }, // name: "slate-global-open-carousel",
}) // detail: { index: this.getItemIndexById(items, item) },
} // })
// }
> >
<ActivitySquare <ActivitySquare
size={this.state.imageSize} size={this.state.imageSize}
item={item} item={item}
isMobile={this.props.isMobile} isMobile={this.props.isMobile}
onClick={ onAction={this.props.onAction}
this.props.isMobile
? () => {}
: () => {
this.props.onAction({
type: "NAVIGATE",
value: "NAV_SLATE",
data: {
decorator: "SLATE",
...item.slate,
},
});
}
}
/> />
</span> </Link>
); );
} else { } else {
return null; return null;

View File

@ -30,7 +30,6 @@ export default class SceneArchive extends React.Component {
state = { state = {
deals: [], deals: [],
dealsLoaded: false, dealsLoaded: false,
tab: 0,
networkViewer: null, networkViewer: null,
allow_filecoin_directory_listing: this.props.viewer.data.settings allow_filecoin_directory_listing: this.props.viewer.data.settings
?.allow_filecoin_directory_listing, ?.allow_filecoin_directory_listing,
@ -114,6 +113,7 @@ export default class SceneArchive extends React.Component {
} }
render() { render() {
let tab = this.props.page.params?.tab || "archive";
return ( return (
<ScenePage> <ScenePage>
<ScenePageHeader title="Filecoin"> <ScenePageHeader title="Filecoin">
@ -122,14 +122,19 @@ export default class SceneArchive extends React.Component {
</ScenePageHeader> </ScenePageHeader>
<SecondaryTabGroup <SecondaryTabGroup
tabs={["Archive Settings", "Wallet", "API", "Miners"]} tabs={[
value={this.state.tab} { title: "Archive Settings", value: { tab: "archive" } },
onChange={(value) => this.setState({ tab: value })} { title: "Wallet", value: { tab: "wallet" } },
{ title: "API", value: { tab: "api" } },
{ title: "Miners", value: { tab: "miners" } },
]}
value={tab}
onAction={this.props.onAction}
/> />
{this.state.networkViewer ? ( {this.state.networkViewer ? (
<React.Fragment> <React.Fragment>
{this.state.tab === 0 ? ( {tab === "archive" ? (
<React.Fragment> <React.Fragment>
<ScenePageHeader> <ScenePageHeader>
Use this section to archive all of your data on to Filecoin through a storage 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> </React.Fragment>
) : null} ) : null}
{this.state.tab === 1 ? ( {tab === "wallet" ? (
<React.Fragment> <React.Fragment>
<SceneWallet {...this.props} networkViewer={this.state.networkViewer} /> <SceneWallet {...this.props} networkViewer={this.state.networkViewer} />
<br /> <br />
@ -216,7 +221,7 @@ export default class SceneArchive extends React.Component {
</React.Fragment> </React.Fragment>
) : null} ) : null}
{this.state.tab === 2 ? ( {tab === "api" ? (
<React.Fragment> <React.Fragment>
{this.state.routes ? ( {this.state.routes ? (
<SceneSentinel routes={this.state.routes} /> <SceneSentinel routes={this.state.routes} />
@ -228,7 +233,7 @@ export default class SceneArchive extends React.Component {
</React.Fragment> </React.Fragment>
) : null} ) : null}
{this.state.tab === 3 ? ( {tab === "miners" ? (
<React.Fragment> <React.Fragment>
{this.state.miners ? ( {this.state.miners ? (
<SceneMiners miners={this.state.miners} /> <SceneMiners miners={this.state.miners} />

View File

@ -7,7 +7,7 @@ import { css } from "@emotion/react";
import { SecondaryTabGroup } from "~/components/core/TabGroup"; import { SecondaryTabGroup } from "~/components/core/TabGroup";
import { Boundary } from "~/components/system/components/fragments/Boundary"; import { Boundary } from "~/components/system/components/fragments/Boundary";
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation"; 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 ScenePage from "~/components/core/ScenePage";
import ScenePageHeader from "~/components/core/ScenePageHeader"; import ScenePageHeader from "~/components/core/ScenePageHeader";
@ -211,10 +211,12 @@ export default class SceneDirectory extends React.Component {
right: "0px", right: "0px",
}} }}
navigation={[ navigation={[
{ [
text: "Unfollow", {
onClick: (e) => this._handleFollow(e, relation.id), text: "Unfollow",
}, onClick: (e) => this._handleFollow(e, relation.id),
},
],
]} ]}
/> />
</Boundary> </Boundary>
@ -222,20 +224,22 @@ export default class SceneDirectory extends React.Component {
</div> </div>
); );
return ( return (
<UserEntry <Link href={`/$/user/${relation.id}`} onAction={this.props.onAction}>
key={relation.id} <UserEntry
user={relation} key={relation.id}
button={button} user={relation}
checkStatus={this.checkStatus} button={button}
onClick={() => { checkStatus={this.checkStatus}
this.props.onAction({ // onClick={() => {
type: "NAVIGATE", // this.props.onAction({
value: this.props.sceneId, // type: "NAVIGATE",
scene: "PROFILE", // value: "NAV_PROFILE",
data: relation, // shallow: true,
}); // data: relation,
}} // });
/> // }}
/>
</Link>
); );
}); });
@ -256,14 +260,16 @@ export default class SceneDirectory extends React.Component {
right: "0px", right: "0px",
}} }}
navigation={[ navigation={[
{ [
text: this.props.viewer.following.some((user) => { {
return user.id === relation.id; text: this.props.viewer.following.some((user) => {
}) return user.id === relation.id;
? "Unfollow" })
: "Follow", ? "Unfollow"
onClick: (e) => this._handleFollow(e, relation.id), : "Follow",
}, onClick: (e) => this._handleFollow(e, relation.id),
},
],
]} ]}
/> />
</Boundary> </Boundary>
@ -271,35 +277,38 @@ export default class SceneDirectory extends React.Component {
</div> </div>
); );
return ( return (
<UserEntry <Link href={`/$/user/${relation.id}`} onAction={this.props.onAction}>
key={relation.id} <UserEntry
user={relation} key={relation.id}
button={button} user={relation}
checkStatus={this.checkStatus} button={button}
onClick={() => { checkStatus={this.checkStatus}
this.props.onAction({ // onClick={() => {
type: "NAVIGATE", // this.props.onAction({
value: this.props.sceneId, // type: "NAVIGATE",
scene: "PROFILE", // value: "NAV_PROFILE",
data: relation, // shallow: true,
}); // data: relation,
}} // });
/> // }}
/>
</Link>
); );
}); });
let tab = this.props.page.params?.tab || "following";
return ( return (
<ScenePage> <ScenePage>
<ScenePageHeader title="Directory" /> <ScenePageHeader title="Directory" />
<SecondaryTabGroup <SecondaryTabGroup
tabs={[ tabs={[
{ title: "Following", value: "NAV_DIRECTORY" }, { title: "Following", value: { tab: "following" } },
{ title: "Followers", value: "NAV_DIRECTORY_FOLLOWERS" }, { title: "Followers", value: { tab: "followers" } },
]} ]}
value={this.props.tab} value={tab}
onAction={this.props.onAction} onAction={this.props.onAction}
/> />
{this.props.tab === 0 ? ( {tab === "following" ? (
following && following.length ? ( following && following.length ? (
following following
) : ( ) : (
@ -310,7 +319,7 @@ export default class SceneDirectory extends React.Component {
</EmptyState> </EmptyState>
) )
) : null} ) : null}
{this.props.tab === 1 ? ( {tab === "followers" ? (
followers && followers.length ? ( followers && followers.length ? (
followers followers
) : ( ) : (

View File

@ -56,7 +56,6 @@ export default class SceneEditAccount extends React.Component {
changingAvatar: false, changingAvatar: false,
savingNameBio: false, savingNameBio: false,
changingFilecoin: false, changingFilecoin: false,
tab: 0,
modalShow: false, 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 }; 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 }); this.setState({ savingNameBio: true });
let response = await Actions.updateViewer({ let response = await Actions.updateViewer({
@ -160,12 +159,12 @@ export default class SceneEditAccount extends React.Component {
} }
this.setState({ deleting: true }); this.setState({ deleting: true });
this.setState({ modalShow: false }); this.setState({ modalShow: false });
await Window.delay(100); await Window.delay(100);
await UserBehaviors.deleteMe({ viewer: this.props.viewer }); await UserBehaviors.deleteMe({ viewer: this.props.viewer });
window.location.replace("/_/auth");
this.setState({ deleting: false }); this.setState({ deleting: false });
}; };
_handleChange = (e) => { _handleChange = (e) => {
@ -173,16 +172,22 @@ export default class SceneEditAccount extends React.Component {
}; };
render() { render() {
let tab = this.props.page.params?.tab || "profile";
return ( return (
<ScenePage> <ScenePage>
<ScenePageHeader title="Settings" /> <ScenePageHeader title="Settings" />
<SecondaryTabGroup <SecondaryTabGroup
tabs={["Profile", "Data Storage", "Security", "Account"]} tabs={[
value={this.state.tab} { title: "Profile", value: { tab: "profile" } },
onChange={(value) => this.setState({ tab: value })} { 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 }} style={{ marginBottom: 48 }}
/> />
{this.state.tab === 0 ? ( {tab === "profile" ? (
<div> <div>
<div css={STYLES_HEADER}>Your Avatar</div> <div css={STYLES_HEADER}>Your Avatar</div>
@ -227,7 +232,7 @@ export default class SceneEditAccount extends React.Component {
</div> </div>
</div> </div>
) : null} ) : null}
{this.state.tab === 1 ? ( {tab === "storage" ? (
<div style={{ maxWidth: 800 }}> <div style={{ maxWidth: 800 }}>
<div css={STYLES_HEADER}> <div css={STYLES_HEADER}>
Allow Slate to make Filecoin archive storage deals on your behalf Allow Slate to make Filecoin archive storage deals on your behalf
@ -278,7 +283,7 @@ export default class SceneEditAccount extends React.Component {
</div> </div>
</div> </div>
) : null} ) : null}
{this.state.tab === 2 ? ( {tab === "security" ? (
<div> <div>
<div css={STYLES_HEADER}>Change password</div> <div css={STYLES_HEADER}>Change password</div>
<div>Passwords must be a minimum of eight characters.</div> <div>Passwords must be a minimum of eight characters.</div>
@ -311,7 +316,7 @@ export default class SceneEditAccount extends React.Component {
</div> </div>
</div> </div>
) : null} ) : null}
{this.state.tab === 3 ? ( {tab === "account" ? (
<div> <div>
<div css={STYLES_HEADER}>Change username</div> <div css={STYLES_HEADER}>Change username</div>
<div style={{ maxWidth: 800 }}> <div style={{ maxWidth: 800 }}>
@ -359,19 +364,18 @@ export default class SceneEditAccount extends React.Component {
tabIndex="-1" tabIndex="-1"
css={STYLES_COPY_INPUT} css={STYLES_COPY_INPUT}
/>{" "} />{" "}
{this.state.modalShow && ( {this.state.modalShow && (
<ConfirmationModal <ConfirmationModal
type={"DELETE"} type={"DELETE"}
withValidation={true} withValidation={true}
matchValue={this.state.username} matchValue={this.state.username}
callback={this._handleDelete} callback={this._handleDelete}
header={`Are you sure you want to delete your account @${this.state.username}?`} header={`Are you sure you want to delete your account @${this.state.username}?`}
subHeader={`You will lose all your files and collections. You cant undo this action.`} subHeader={`You will lose all your files and collections. You cant undo this action.`}
inputHeader={`Please type your username to confirm`} inputHeader={`Please type your username to confirm`}
inputPlaceholder={`username`} inputPlaceholder={`username`}
/> />
)} )}
</ScenePage> </ScenePage>
); );
} }

139
scenes/SceneError.js Normal file
View 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>
);
}
}

View File

@ -113,7 +113,6 @@ const STYLES_COMMAND_TOOLTIP_ANCHOR = css`
export default class SceneFilesFolder extends React.Component { export default class SceneFilesFolder extends React.Component {
state = { state = {
view: 0,
filterTooltip: false, filterTooltip: false,
fileTypes: { fileTypes: {
image: false, image: false,
@ -128,9 +127,9 @@ export default class SceneFilesFolder extends React.Component {
keyboardTooltip: false, keyboardTooltip: false,
}; };
componentDidMount = () => { // componentDidMount = () => {
this.openCarouselToItem(); // this.openCarouselToItem();
}; // };
componentDidUpdate = (prevProps, prevState) => { componentDidUpdate = (prevProps, prevState) => {
if (prevProps.viewer.library !== this.props.viewer.library) { if (prevProps.viewer.library !== this.props.viewer.library) {
@ -138,9 +137,9 @@ export default class SceneFilesFolder extends React.Component {
this._filterFiles(); this._filterFiles();
} }
} }
if (prevProps.page !== this.props.page) { // if (prevProps.page.params !== this.props.page.params) {
this.openCarouselToItem(); // this.openCarouselToItem();
} // }
}; };
_handleFilterTooltip = () => { _handleFilterTooltip = () => {
@ -206,87 +205,50 @@ export default class SceneFilesFolder extends React.Component {
}); });
}; };
openCarouselToItem = () => { // openCarouselToItem = () => {
let index = -1; // if (!this.props.page?.params || !this.props.viewer.library?.length) {
let page = this.props.page; // return;
if (page?.fileId || page?.cid || page?.index) { // }
if (page?.index) { // let index = -1;
index = page.index; // let params = this.props.page.params;
} else { // if (params?.fileId || params?.cid || params?.index) {
let library = this.props.viewer.library || []; // if (params?.index) {
for (let i = 0; i < library.length; i++) { // index = params.index;
let obj = library[i]; // } else {
if ((obj.cid && obj.cid === page?.cid) || (obj.id && obj.id === page?.fileId)) { // let library = this.props.viewer.library || [];
index = i; // for (let i = 0; i < library.length; i++) {
break; // let obj = library[i];
} // if ((obj.cid && obj.cid === params?.cid) || (obj.id && obj.id === params?.fileId)) {
} // index = i;
} // break;
} // }
// }
// }
// }
if (index !== -1) { // if (index !== -1) {
Events.dispatchCustomEvent({ // Events.dispatchCustomEvent({
name: "slate-global-open-carousel", // name: "slate-global-open-carousel",
detail: { index }, // detail: { index },
}); // });
} // }
}; // };
render() { render() {
let files = this.state.filtersActive ? this.state.filteredFiles : this.props.viewer?.library; let files = this.state.filtersActive ? this.state.filteredFiles : this.props.viewer?.library;
files = files || []; files = files || [];
const tab = this.props.page.params?.tab || "grid";
return ( return (
<ScenePage> <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 <GlobalCarousel
carouselType="DATA" carouselType="DATA"
onUpdateViewer={this.props.onUpdateViewer}
resources={this.props.resources} resources={this.props.resources}
viewer={this.props.viewer} viewer={this.props.viewer}
objects={files} objects={files}
onAction={this.props.onAction} onAction={this.props.onAction}
isMobile={this.props.isMobile} isMobile={this.props.isMobile}
params={this.props.page.params}
isOwner={true} isOwner={true}
/> />
<DataMeter <DataMeter
@ -307,6 +269,21 @@ export default class SceneFilesFolder extends React.Component {
} }
/> />
<div css={STYLES_CONTAINER_WRAPPER}> <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_CONTAINER}>
<div <div
css={STYLES_BUTTONS_ROW} css={STYLES_BUTTONS_ROW}
@ -486,10 +463,10 @@ export default class SceneFilesFolder extends React.Component {
onAction={this.props.onAction} onAction={this.props.onAction}
viewer={this.props.viewer} viewer={this.props.viewer}
items={files} items={files}
onUpdateViewer={this.props.onUpdateViewer} view={tab}
view={this.state.view}
resources={this.props.resources} resources={this.props.resources}
isOwner={true} isOwner={true}
page={this.props.page}
/> />
) : ( ) : (
<EmptyState> <EmptyState>

View File

@ -13,6 +13,7 @@ import { css } from "@emotion/react";
import { createState } from "~/scenes/SceneSettings"; import { createState } from "~/scenes/SceneSettings";
import { LoaderSpinner } from "~/components/system/components/Loaders"; import { LoaderSpinner } from "~/components/system/components/Loaders";
import { FilecoinNumber } from "@glif/filecoin-number"; import { FilecoinNumber } from "@glif/filecoin-number";
import { Link } from "~/components/core/Link";
import Section from "~/components/core/Section"; import Section from "~/components/core/Section";
import ScenePage from "~/components/core/ScenePage"; 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." "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) => { _handleRemove = async (cid) => {

View File

@ -22,106 +22,115 @@ const STYLES_LOADER = css`
export default class SceneProfile extends React.Component { export default class SceneProfile extends React.Component {
state = { state = {
profile: null,
notFound: false, notFound: false,
isOwner: false, loading: false,
loading: true,
}; };
componentDidMount = async () => { // componentDidMount = async () => {
this.fetchProfile(); // if (this.props.data) {
}; // this.openCarouselToItem();
// }
// };
componentDidUpdate = (prevProps) => { // componentDidUpdate = (prevProps) => {
if (this.state.isOwner && this.props.viewer.library !== prevProps.viewer.library) { // // if (
let filteredViewer = this.getFilteredViewer(); // // this.state.isOwner &&
this.setState({ profile: filteredViewer }); // // this.props.viewer &&
} else if (this.props.page !== prevProps.page) { // // this.props.viewer.library !== prevProps.viewer.library
this.openCarouselToItem(); // // ) {
} // // 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 () => { // fetchProfile = async () => {
const username = this.props.page.user || this.props.page.data?.username; // const username = this.props.page.username || this.props.page.data?.username;
let isOwner = false; // const { userId } = this.props.page;
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 };
}
}
if (!targetUser) { // const id = userId || this.props.data?.id;
let response;
if (query) {
response = await Actions.getSerializedProfile(query);
}
if (!response || response.error) { // let isOwner = false;
this.setState({ notFound: true }); // let query;
return; // 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; // if (!targetUser) {
} // let response;
window.history.replaceState( // if (query) {
{ ...window.history.state, data: targetUser }, // response = await Actions.getSerializedProfile(query);
"A slate user", // }
`/${targetUser.username}`
);
this.props.onUpdateData(targetUser);
this.setState({ isOwner, profile: targetUser, loading: false }, this.openCarouselToItem);
};
openCarouselToItem = () => { // if (!response || response.error) {
if (!this.state.profile?.library?.length) { // this.setState({ notFound: true });
return; // return;
} // }
const { cid, fileId, index } = this.props.page;
if (Strings.isEmpty(cid) && Strings.isEmpty(fileId) && typeof index === "undefined") { // targetUser = response.data;
return; // }
} // 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 (Strings.isEmpty(cid) && Strings.isEmpty(fileId) && typeof index === "undefined") {
if (index) { // return;
foundIndex = index; // }
} else if (cid) {
foundIndex = library.findIndex((object) => object.cid === cid); // const library = this.props.data.library;
} else if (fileId) {
foundIndex = library.findIndex((object) => object.id === fileId); // let foundIndex = -1;
} // if (index) {
if (typeof foundIndex !== "undefined" && foundIndex !== -1) { // foundIndex = index;
Events.dispatchCustomEvent({ // } else if (cid) {
name: "slate-global-open-carousel", // foundIndex = library.findIndex((object) => object.cid === cid);
detail: { index: foundIndex }, // } else if (fileId) {
}); // foundIndex = library.findIndex((object) => object.id === fileId);
} // }
// else { // if (typeof foundIndex !== "undefined" && foundIndex !== -1) {
// Events.dispatchCustomEvent({ // Events.dispatchCustomEvent({
// name: "create-alert", // name: "slate-global-open-carousel",
// detail: { // detail: { index: foundIndex },
// alert: { // });
// message: // }
// "The requested file could not be found. It could have been deleted or may be private", // // 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 = () => { getFilteredViewer = () => {
let viewer = this.props.viewer; let viewer = this.props.viewer;
@ -130,36 +139,51 @@ export default class SceneProfile extends React.Component {
}; };
render() { render() {
if (this.state.notFound) { let user = this.props.data;
return ( if (!user) {
<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) {
return ( return (
<ScenePage> <ScenePage>
<div css={STYLES_LOADER}> <div css={STYLES_LOADER}>
<LoaderSpinner /> <LoaderSpinner />
</div> </div>
</ScenePage> </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 ( return (
<Profile <Profile
{...this.props} {...this.props}
user={this.state.profile} user={user}
isOwner={this.state.isOwner} isOwner={this.props.viewer ? user.id === this.props.viewer.id : false}
isAuthenticated={this.props.viewer !== null} 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}
// />
// );
// }
} }
} }

View File

@ -95,15 +95,14 @@ class Key extends React.Component {
</SquareButtonGray> </SquareButtonGray>
{this.state.modalShow && ( {this.state.modalShow && (
<ConfirmationModal <ConfirmationModal
type={"DELETE"} type={"DELETE"}
withValidation={false} 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?`} 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.`} subHeader={`Any services using it will no longer be able to access your Slate account.`}
/> />
)} )}
</div> </div>
); );
} }
@ -117,7 +116,6 @@ export default class SceneSettingsDeveloper extends React.Component {
language: "javascript", language: "javascript",
docs: "GET", docs: "GET",
copying: false, copying: false,
tab: 0,
modalShow: false, modalShow: false,
}; };
@ -154,6 +152,7 @@ export default class SceneSettingsDeveloper extends React.Component {
}; };
render() { render() {
const tab = this.props.page.params?.tab || "v2";
let APIKey = "YOUR-API-KEY-HERE"; let APIKey = "YOUR-API-KEY-HERE";
let lang = this.state.language; let lang = this.state.language;
if (this.props.viewer.keys) { if (this.props.viewer.keys) {
@ -298,12 +297,15 @@ export default class SceneSettingsDeveloper extends React.Component {
</ScenePageHeader> </ScenePageHeader>
<SecondaryTabGroup <SecondaryTabGroup
tabs={["Version 2.0", "Version 1.0"]} tabs={[
value={this.state.tab} { title: "Version 2.0", value: { tab: "v2" } },
onChange={(tab) => this.setState({ tab })} { title: "Version 1.0", value: { tab: "v1" } },
]}
value={tab}
onAction={this.props.onAction}
/> />
{this.state.tab === 0 ? ( {tab === "v2" ? (
<> <>
<APIDocsGetV2 <APIDocsGetV2
language={lang} language={lang}

View File

@ -104,24 +104,20 @@ export default class SceneSignIn extends React.Component {
return; return;
} }
let response = null;
if (this.state.scene === "CREATE_ACCOUNT") { if (this.state.scene === "CREATE_ACCOUNT") {
response = await this.props.onCreateUser({ await this.props.onCreateUser({
username: this.state.username.toLowerCase(), username: this.state.username.toLowerCase(),
password: this.state.password, password: this.state.password,
accepted: this.state.accepted, accepted: this.state.accepted,
}); });
} else { } else {
response = await this.props.onAuthenticate({ await this.props.onAuthenticate({
username: this.state.username.toLowerCase(), username: this.state.username.toLowerCase(),
password: this.state.password, password: this.state.password,
}); });
} }
if (Events.hasError(response)) { this.setState({ loading: false });
this.setState({ loading: false });
}
}; };
_handleCheckUsername = async () => { _handleCheckUsername = async () => {
@ -160,7 +156,8 @@ export default class SceneSignIn extends React.Component {
render() { render() {
return ( return (
<div css={STYLES_ROOT}> <div css={STYLES_ROOT}>
<WebsitePrototypeHeader style={{ background: `none` }} /> <div />
{/* <WebsitePrototypeHeader style={{ background: `none` }} /> */}
<div css={STYLES_MIDDLE}> <div css={STYLES_MIDDLE}>
<SignIn {...this.props} /> <SignIn {...this.props} />
</div> </div>

View File

@ -8,6 +8,7 @@ import * as Strings from "~/common/strings";
import * as UserBehaviors from "~/common/user-behaviors"; import * as UserBehaviors from "~/common/user-behaviors";
import * as Events from "~/common/custom-events"; import * as Events from "~/common/custom-events";
import { Link } from "~/components/core/Link";
import { LoaderSpinner } from "~/components/system/components/Loaders"; import { LoaderSpinner } from "~/components/system/components/Loaders";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { SlateLayout } from "~/components/core/SlateLayout"; import { SlateLayout } from "~/components/core/SlateLayout";
@ -63,145 +64,154 @@ export default class SceneSlate extends React.Component {
accessDenied: false, accessDenied: false,
}; };
componentDidMount = async () => { // componentDidMount = async () => {
await this.fetchSlate(); // if (this.props.data) {
}; // this.openCarouselToItem();
// }
// };
componentDidUpdate = async (prevProps) => { // componentDidUpdate = async (prevProps) => {
if (!this.props.data?.objects && !this.state.notFound) { // // if (!this.props.data?.objects && !this.state.notFound) {
await this.fetchSlate(); // // await this.fetchSlate();
} else if (this.props.page !== prevProps.page) { // // } else
this.openCarouselToItem(); // if (this.props.data !== prevProps.data || this.props.page.params !== prevProps.page.params) {
} // this.openCarouselToItem();
}; // }
// };
fetchSlate = async () => { // fetchSlate = async () => {
const { user: username, slate: slatename } = this.props.page; // const { username, slatename, slateId } = this.props.page;
if (!this.props.data && (!username || !slatename)) { // if (!this.props.data && (!username || !slatename)) {
this.setState({ notFound: true }); // this.setState({ notFound: true });
return; // return;
} // }
//NOTE(martina): look for the slate in the user's slates // let id = slateId || this.props.data?.id;
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;
}
}
if (slate) { // //NOTE(martina): look for the slate in the user's slates
window.history.replaceState( // let slate;
{ ...window.history.state, data: slate }, // if (this.props.viewer) {
"Slate", // if (id) {
`/${this.props.viewer.username}/${slate.slatename}` // 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) { // if (slate) {
let query; // window.history.replaceState(
if (username && slatename) { // { ...window.history.state, data: slate },
query = { username, slatename }; // "Slate",
} else if (this.props.data && this.props.data.id) { // `/${this.props.viewer.username}/${slate.slatename}`
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();
});
};
openCarouselToItem = () => { // if (!slate) {
if (!this.props.data?.objects?.length) { // let query;
return; // if (username && slatename) {
} // query = { username, slatename };
let objects = this.props.data.objects; // } 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") { // const { cid, fileId, index } = this.props.page.params;
return;
}
let foundIndex = -1; // if (Strings.isEmpty(cid) && Strings.isEmpty(fileId) && typeof index === "undefined") {
if (index) { // return;
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) { // let foundIndex = -1;
Events.dispatchCustomEvent({ // if (index) {
name: "slate-global-open-carousel", // foundIndex = index;
detail: { index: foundIndex }, // } 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() { render() {
if (this.state.notFound || this.state.accessDenied) { if (!this.props.data) {
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) {
return ( return (
// <ScenePage>
// <EmptyState>
// <SVG.Layers height="24px" style={{ marginBottom: 24 }} />
// <div>We were unable to locate that collection</div>
// </EmptyState>
// </ScenePage>
<ScenePage> <ScenePage>
<div css={STYLES_LOADER}> <div css={STYLES_LOADER}>
<LoaderSpinner /> <LoaderSpinner />
</div> </div>
</ScenePage> </ScenePage>
); );
} else if (this.props.data?.id) { } else {
return <SlatePage {...this.props} key={this.props.data.id} current={this.props.data} />; 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; _remoteLock = false;
state = { state = {
...(this.props.current, this.props.viewer), ...(this.props.data, this.props.viewer),
editing: false, editing: false,
isSubscribed: this.props.viewer.subscriptions.some((subscription) => { isSubscribed: this.props.viewer
return subscription.id === this.props.current.id; ? this.props.viewer.subscriptions.some((subscription) => {
}), return subscription.id === this.props.data.id;
})
: false,
}; };
componentDidMount() { componentDidMount() {
@ -229,13 +241,14 @@ class SlatePage extends React.Component {
return; return;
} }
const index = this.props.current.objects.findIndex((object) => object.cid === cid); const index = this.props.data.objects.findIndex((object) => object.cid === cid);
if (index !== -1) { // if (index !== -1) {
Events.dispatchCustomEvent({ // Events.dispatchCustomEvent({
name: "slate-global-open-carousel", // name: "slate-global-open-carousel",
detail: { index }, // detail: { index },
}); // });
} else { // }
if (index === -1) {
Events.dispatchCustomEvent({ Events.dispatchCustomEvent({
name: "create-alert", name: "create-alert",
detail: { detail: {
@ -249,26 +262,30 @@ class SlatePage extends React.Component {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.props.viewer.subscriptions !== prevProps.viewer.subscriptions) { if (this.props.viewer && this.props.viewer.subscriptions !== prevProps.viewer.subscriptions) {
this.setState({ this.setState({
isSubscribed: this.props.viewer.subscriptions.some((subscription) => { isSubscribed: this.props.viewer.subscriptions.some((subscription) => {
return subscription.id === this.props.current.id; return subscription.id === this.props.data.id;
}), }),
}); });
} }
} }
_handleSubscribe = () => { _handleSubscribe = () => {
if (!this.props.viewer) {
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
return;
}
this.setState({ isSubscribed: !this.state.isSubscribed }, () => { this.setState({ isSubscribed: !this.state.isSubscribed }, () => {
Actions.createSubscription({ Actions.createSubscription({
slateId: this.props.current.id, slateId: this.props.data.id,
}); });
}); });
}; };
_handleSaveLayout = async (layouts, autoSave) => { _handleSaveLayout = async (layouts, autoSave) => {
const response = await Actions.updateSlateLayout({ const response = await Actions.updateSlateLayout({
id: this.props.current.id, id: this.props.data.id,
layouts, layouts,
}); });
@ -278,34 +295,41 @@ class SlatePage extends React.Component {
}; };
_handleSavePreview = async (preview) => { _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 slates = this.props.viewer.slates;
let slateId = this.props.current.id; let slateId = this.props.data.id;
for (let slate of slates) { for (let slate of slates) {
if (slate.id === slateId) { if (slate.id === slateId) {
slate.data.preview = preview; slate.data.preview = preview;
break; break;
} }
} }
this.props.onUpdateViewer({ slates }); this.props.onAction({ type: "UPDATE_VIEWER", viewer: { slates } });
const response = await Actions.updateSlate(updateObject); const response = await Actions.updateSlate(updateObject);
Events.hasError(response); Events.hasError(response);
}; };
_handleSelect = (index) => // _handleSelect = (index) =>
Events.dispatchCustomEvent({ // Events.dispatchCustomEvent({
name: "slate-global-open-carousel", // name: "slate-global-open-carousel",
detail: { index }, // detail: { index },
}); // });
_handleAdd = async () => { _handleAdd = async () => {
if (!this.props.viewer) {
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
return;
}
await this.props.onAction({ await this.props.onAction({
type: "SIDEBAR", type: "SIDEBAR",
value: "SIDEBAR_ADD_FILE_TO_BUCKET", 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({ return this.props.onAction({
type: "SIDEBAR", type: "SIDEBAR",
value: "SIDEBAR_SINGLE_SLATE_SETTINGS", value: "SIDEBAR_SINGLE_SLATE_SETTINGS",
data: this.props.current, data: this.props.data,
}); });
}; };
@ -330,8 +354,12 @@ class SlatePage extends React.Component {
}; };
_handleDownload = () => { _handleDownload = () => {
const slateName = this.props.current.data.name; if (!this.props.viewer) {
const slateFiles = this.props.current.objects; Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
return;
}
const slateName = this.props.data.data.name;
const slateFiles = this.props.data.objects;
UserBehaviors.compressAndDownloadFiles({ UserBehaviors.compressAndDownloadFiles({
files: slateFiles, files: slateFiles,
name: `${slateName}.zip`, name: `${slateName}.zip`,
@ -340,12 +368,12 @@ class SlatePage extends React.Component {
}; };
render() { render() {
const { user, data } = this.props.current; const { user, data } = this.props.data;
const { body = "", preview } = data; const { body = "", preview } = data;
let objects = this.props.current.objects; let objects = this.props.data.objects;
let layouts = this.props.current.data.layouts; let layouts = this.props.data.data.layouts;
const isPublic = this.props.current.isPublic; const isPublic = this.props.data.isPublic;
const isOwner = this.props.current.ownerId === this.props.viewer.id; const isOwner = this.props.viewer ? this.props.data.ownerId === this.props.viewer.id : false;
const tags = data.tags; const tags = data.tags;
let actions = isOwner ? ( let actions = isOwner ? (
@ -381,19 +409,21 @@ class SlatePage extends React.Component {
title={ title={
user && !isOwner ? ( user && !isOwner ? (
<span> <span>
<span <Link href={`/$/user/${user.id}`} onAction={this.props.onAction}>
onClick={() => <span
this.props.onAction({ // onClick={() =>
type: "NAVIGATE", // this.props.onAction({
value: this.props.sceneId, // type: "NAVIGATE",
scene: "PROFILE", // value: "NAV_PROFILE",
data: user, // shallow: true,
}) // data: user,
} // })
css={STYLES_USERNAME} // }
> css={STYLES_USERNAME}
{user.username} >
</span>{" "} {user.username}
</span>{" "}
</Link>
/ {data.name} / {data.name}
{isOwner && !isPublic && ( {isOwner && !isPublic && (
<SVG.SecurityLock <SVG.SecurityLock
@ -424,12 +454,12 @@ class SlatePage extends React.Component {
<> <>
<GlobalCarousel <GlobalCarousel
carouselType="SLATE" carouselType="SLATE"
onUpdateViewer={this.props.onUpdateViewer}
viewer={this.props.viewer} viewer={this.props.viewer}
objects={objects} objects={objects}
current={this.props.current} data={this.props.data}
onAction={this.props.onAction} onAction={this.props.onAction}
isMobile={this.props.isMobile} isMobile={this.props.isMobile}
params={this.props.page.params}
isOwner={isOwner} isOwner={isOwner}
external={this.props.external} external={this.props.external}
/> />
@ -443,11 +473,12 @@ class SlatePage extends React.Component {
) : ( ) : (
<div style={{ marginTop: isOwner ? 24 : 48 }}> <div style={{ marginTop: isOwner ? 24 : 48 }}>
<SlateLayout <SlateLayout
key={this.props.current.id} page={this.props.page}
current={this.props.current} external={this.props.external}
onUpdateViewer={this.props.onUpdateViewer} key={this.props.data.id}
data={this.props.data}
viewer={this.props.viewer} viewer={this.props.viewer}
slateId={this.props.current.id} slateId={this.props.data.id}
layout={layouts && layouts.ver === "2.0" ? layouts.layout || [] : null} layout={layouts && layouts.ver === "2.0" ? layouts.layout || [] : null}
onSaveLayout={this._handleSaveLayout} onSaveLayout={this._handleSaveLayout}
isOwner={isOwner} isOwner={isOwner}

View File

@ -31,67 +31,27 @@ export default class SceneSlates extends React.Component {
}; };
render() { render() {
const tab = this.props.page.params?.tab || "collections";
let subscriptions = this.props.viewer.subscriptions; let subscriptions = this.props.viewer.subscriptions;
return ( return (
<ScenePage> <ScenePage>
<ScenePageHeader <div style={{ display: "flex", alignItems: "center", marginBottom: 24 }}>
title={ <SecondaryTabGroup
this.props.isMobile ? ( tabs={[
<TabGroup { title: "My Collections", value: { tab: "collections" } },
tabs={[ { title: "Subscribed", value: { tab: "subscribed" } },
{ title: "Files", value: "NAV_DATA" }, ]}
{ title: "Collections", value: "NAV_SLATES" }, value={tab}
{ title: "Activity", value: "NAV_ACTIVITY" }, onAction={this.props.onAction}
]} style={{ margin: 0 }}
value={1} />
onAction={this.props.onAction} <SquareButtonGray onClick={this._handleAdd} style={{ marginLeft: 16 }}>
onChange={(value) => this.setState({ tab: value })} <SVG.Plus height="16px" />
style={{ marginTop: 0, marginBottom: 32 }} </SquareButtonGray>
itemStyle={{ margin: "0px 12px" }} </div>
/> {tab === "collections" ? (
) : ( this.props.viewer.slates?.length ? (
<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 ? (
<SlatePreviewBlocks <SlatePreviewBlocks
isOwner isOwner
slates={this.props.viewer.slates} slates={this.props.viewer.slates}
@ -111,7 +71,7 @@ export default class SceneSlates extends React.Component {
) )
) : null} ) : null}
{this.props.tab === 1 ? ( {tab === "subscribed" ? (
subscriptions && subscriptions.length ? ( subscriptions && subscriptions.length ? (
<SlatePreviewBlocks <SlatePreviewBlocks
slates={subscriptions} slates={subscriptions}

View File

@ -12,6 +12,8 @@ console.log(`RUNNING: drop-database.js`);
Promise.all([ Promise.all([
db.schema.dropTable("users"), db.schema.dropTable("users"),
db.schema.dropTable("slates"), db.schema.dropTable("slates"),
db.schema.dropTable("slate_files"),
db.schema.dropTable("files"),
db.schema.dropTable("activity"), db.schema.dropTable("activity"),
db.schema.dropTable("subscriptions"), db.schema.dropTable("subscriptions"),
db.schema.dropTable("keys"), db.schema.dropTable("keys"),

View File

@ -566,7 +566,7 @@ const runScript = async () => {
// await printSlatesTable(); // await printSlatesTable();
//NOTE(martina): add tables //NOTE(martina): add tables
// await addTables(); await addTables();
//NOTE(martina): put data into new tables //NOTE(martina): put data into new tables
// await DB("slate_files").del(); // await DB("slate_files").del();

481
server.js
View File

@ -8,6 +8,7 @@ import * as NodeLogging from "~/node_common/node-logging";
import * as Validations from "~/common/validations"; import * as Validations from "~/common/validations";
import * as Window from "~/common/window"; import * as Window from "~/common/window";
import * as Strings from "~/common/strings"; import * as Strings from "~/common/strings";
import * as NavigationData from "~/common/navigation-data";
import limit from "express-rate-limit"; import limit from "express-rate-limit";
import express from "express"; import express from "express";
@ -153,8 +154,8 @@ app.prepare().then(async () => {
}); });
server.get("/_", async (req, res) => { server.get("/_", async (req, res) => {
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]); // let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
let isMac = Window.isMac(req.headers["user-agent"]); // let isMac = Window.isMac(req.headers["user-agent"]);
const isBucketsAvailable = await Utilities.checkTextile(); const isBucketsAvailable = await Utilities.checkTextile();
@ -164,6 +165,37 @@ app.prepare().then(async () => {
const id = Utilities.getIdFromCookie(req); 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; let viewer = null;
if (id) { if (id) {
viewer = await ViewerManager.getById({ 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, "/_", { return app.render(req, res, "/_", {
viewer,
isMobile, isMobile,
isMac, isMac,
viewer,
page,
data: null,
resources: EXTERNAL_RESOURCES, 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", async (r, s) => handler(r, s, r.url));
server.all("/_/:a/:b", 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) => { server.get("/[$]/slate/:id", async (req, res) => {
const slate = await Data.getSlateById({ const slate = await Data.getSlateById({
id: req.params.id, id: req.params.id,
includeFiles: true,
sanitize: true,
}); });
if (!slate) { if (!slate || slate.error) {
return res.redirect("/404"); return res.redirect("/_/404");
}
if (slate.error) {
return res.redirect("/404");
}
if (!slate.isPublic) {
return res.redirect("/403");
} }
const creator = await Data.getUserById({ const creator = await Data.getUserById({
id: slate.ownerId, id: slate.ownerId,
sanitize: true,
}); });
if (!creator) { if (!creator || creator.error) {
return res.redirect("/404"); return res.redirect("/_/404");
} }
if (creator.error) { let search = Strings.getQueryStringFromParams(req.query);
return res.redirect("/404");
}
return res.redirect(`/${creator.username}/${slate.slatename}`); return res.redirect(`/${creator.username}/${slate.slatename}${search}`);
}); });
server.get("/[$]/user/:id", async (req, res) => { server.get("/[$]/user/:id", async (req, res) => {
const creator = await Data.getUserById({ const creator = await Data.getUserById({
id: req.params.id, id: req.params.id,
includeFiles: true,
publicOnly: true,
sanitize: true,
}); });
if (!creator) { if (!creator || creator.error) {
return res.redirect("/404"); return res.redirect("/_/404");
} }
if (creator.error) { let search = Strings.getQueryStringFromParams(req.query);
return res.redirect("/404");
}
return res.redirect(`/${creator.username}`); return res.redirect(`/${creator.username}${search}`);
}); });
server.get("/[$]/:id", async (req, res) => { 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({ const slate = await Data.getSlateById({
id: req.params.id, id: req.params.id,
includeFiles: true,
sanitize: true,
}); });
if (!slate) { if (!slate || slate.error) {
return res.redirect("/404"); return res.redirect("/_/404");
}
if (slate.error) {
return res.redirect("/404");
}
if (!slate.isPublic) {
return res.redirect("/403");
} }
const creator = await Data.getUserById({ const creator = await Data.getUserById({
id: slate.ownerId, id: slate.ownerId,
sanitize: true,
}); });
if (!creator) { if (!creator || creator.error) {
return res.redirect("/404"); return res.redirect("/_/404");
} }
if (creator.error) { let search = Strings.getQueryStringFromParams(req.query);
return res.redirect("/404");
}
const id = Utilities.getIdFromCookie(req); return res.redirect(`/${creator.username}/${slate.slatename}${search}`);
// 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,
});
}); });
server.get("/:username", async (req, res) => { server.get("/:username", async (req, res) => {
const username = req.params.username.toLowerCase();
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]); let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
let isMac = Window.isMac(req.headers["user-agent"]); let isMac = Window.isMac(req.headers["user-agent"]);
// TODO(jim): Temporary workaround // TODO(jim): Temporary workaround
if (!Validations.userRoute(req.params.username)) { if (!Validations.userRoute(username)) {
return handler(req, res, req.url, { return handler(req, res, req.url, {
isMobile, isMobile,
resources: EXTERNAL_RESOURCES, resources: EXTERNAL_RESOURCES,
@ -318,125 +297,120 @@ app.prepare().then(async () => {
} }
const id = Utilities.getIdFromCookie(req); const id = Utilities.getIdFromCookie(req);
const shouldViewerRedirect = await ViewerManager.shouldRedirect({ id });
if (shouldViewerRedirect) { let viewer = null;
return res.redirect( if (id) {
`/_${Strings.createQueryParams({ viewer = await ViewerManager.getById({
scene: "NAV_PROFILE", id,
user: req.params.username, });
})}`
);
} }
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; let user = await Data.getUserByUsername({
// if (id) { username,
// viewer = await ViewerManager.getById({
// id,
// });
// }
let creator = await Data.getUserByUsername({
username: req.params.username.toLowerCase(),
includeFiles: true, includeFiles: true,
sanitize: true, sanitize: true,
publicOnly: true, publicOnly: true,
}); });
if (!creator) { if (!user) {
return res.redirect("/404"); return res.redirect("/_/404");
} }
if (creator.error) { if (user.error) {
return res.redirect("/404"); return res.redirect("/_/404");
} }
const slates = await Data.getSlatesByUserId({ const slates = await Data.getSlatesByUserId({
ownerId: creator.id, ownerId: user.id,
sanitize: true, sanitize: true,
includeFiles: true, includeFiles: true,
publicOnly: true, publicOnly: true,
}); });
creator.slates = slates; user.slates = slates;
return app.render(req, res, "/_/profile", { return app.render(req, res, "/_", {
viewer: null, viewer,
creator,
isMobile, isMobile,
isMac, isMac,
data: user,
page,
resources: EXTERNAL_RESOURCES, resources: EXTERNAL_RESOURCES,
exploreSlates,
}); });
}); });
server.get("/:username/cid::cid", async (req, res) => { // server.get("/:username/cid::cid", async (req, res) => {
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]); // const username = req.params.username.toLowerCase();
let isMac = Window.isMac(req.headers["user-agent"]); // const cid = req.params.cid.toLowerCase();
// TODO(jim): Temporary workaround // let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
if (!Validations.userRoute(req.params.username)) { // let isMac = Window.isMac(req.headers["user-agent"]);
return handler(req, res, req.url);
}
const id = Utilities.getIdFromCookie(req); // // TODO(jim): Temporary workaround
const shouldViewerRedirect = await ViewerManager.shouldRedirect({ id }); // if (!Validations.userRoute(username)) {
if (shouldViewerRedirect) { // return handler(req, res, req.url);
return res.redirect( // }
`/_${Strings.createQueryParams({
scene: "NAV_PROFILE",
user: req.params.username,
cid: req.params.cid,
})}`
);
}
// let viewer = null; // const id = Utilities.getIdFromCookie(req);
// if (id) {
// viewer = await ViewerManager.getById({
// id,
// });
// }
let creator = await Data.getUserByUsername({ // let user = await Data.getUserByUsername({
username: req.params.username.toLowerCase(), // username,
includeFiles: true, // includeFiles: true,
sanitize: true, // sanitize: true,
publicOnly: true, // publicOnly: true,
}); // });
if (!creator) { // if (!user) {
return res.redirect("/404"); // return res.redirect("/_/404");
} // }
if (creator.error) { // if (user.error) {
return res.redirect("/404"); // return res.redirect("/_/404");
} // }
const slates = await Data.getSlatesByUserId({ // const slates = await Data.getSlatesByUserId({
ownerId: creator.id, // ownerId: user.id,
sanitize: true, // sanitize: true,
includeFiles: true, // includeFiles: true,
publicOnly: true, // publicOnly: true,
}); // });
creator.slates = slates; // user.slates = slates;
return app.render(req, res, "/_/profile", { // let viewer = null;
viewer: null, // if (id) {
creator, // viewer = await ViewerManager.getById({
isMobile, // id,
isMac, // });
resources: EXTERNAL_RESOURCES, // }
cid: req.params.cid,
}); // 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) => { 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 isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
let isMac = Window.isMac(req.headers["user-agent"]); let isMac = Window.isMac(req.headers["user-agent"]);
// TODO(jim): Temporary workaround // TODO(jim): Temporary workaround
if (!Validations.userRoute(req.params.username)) { if (!Validations.userRoute(username)) {
return handler(req, res, req.url, { return handler(req, res, req.url, {
isMobile, isMobile,
resources: EXTERNAL_RESOURCES, resources: EXTERNAL_RESOURCES,
@ -444,146 +418,117 @@ app.prepare().then(async () => {
} }
const id = Utilities.getIdFromCookie(req); const id = Utilities.getIdFromCookie(req);
const shouldViewerRedirect = await ViewerManager.shouldRedirect({ id });
if (shouldViewerRedirect) { let viewer = null;
return res.redirect( if (id) {
`/_${Strings.createQueryParams({ viewer = await ViewerManager.getById({
scene: "NAV_SLATE", id,
user: req.params.username, });
slate: req.params.slatename,
})}`
);
} }
let { page } = NavigationData.getByHref(req.path, viewer);
page = { ...page, params: req.query };
const slate = await Data.getSlateByName({ const slate = await Data.getSlateByName({
slatename: req.params.slatename, slatename,
username: req.params.username, username,
includeFiles: true, includeFiles: true,
sanitize: true, sanitize: true,
}); });
if (!slate) { if (!slate || slate.error || (!slate.isPublic && slate.ownerId !== id)) {
return res.redirect("/404"); return res.redirect("/_/404");
} }
if (slate.error) { const user = await Data.getUserById({
return res.redirect("/404");
}
if (!slate.isPublic && slate.ownerId !== id) {
return res.redirect("/403");
}
const creator = await Data.getUserById({
id: slate.ownerId, id: slate.ownerId,
sanitize: true, sanitize: true,
}); });
if (!creator) { if (!user) {
return res.redirect("/404"); return res.redirect("/_/404");
} }
if (creator.error) { if (user.error) {
return res.redirect("/404"); return res.redirect("/_/404");
} }
if (req.params.username !== creator.username) { slate.user = user;
return res.redirect("/403");
}
// let viewer = null; return app.render(req, res, "/_", {
// if (id) { viewer,
// viewer = await ViewerManager.getById({
// id,
// });
// }
return app.render(req, res, "/_/slate", {
viewer: null,
creator,
slate,
isMobile, isMobile,
isMac, isMac,
data: slate,
page,
resources: EXTERNAL_RESOURCES, resources: EXTERNAL_RESOURCES,
}); });
}); });
server.get("/:username/:slatename/cid::cid", async (req, res) => { // server.get("/:username/:slatename/cid::cid", async (req, res) => {
let isMobile = Window.isMobileBrowser(req.headers["user-agent"]); // const username = req.params.username.toLowerCase();
let isMac = Window.isMac(req.headers["user-agent"]); // const slatename = req.params.slatename.toLowerCase();
// const cid = req.params.cid.toLowerCase();
// TODO(jim): Temporary workaround // let isMobile = Window.isMobileBrowser(req.headers["user-agent"]);
if (!Validations.userRoute(req.params.username)) { // let isMac = Window.isMac(req.headers["user-agent"]);
return handler(req, res, req.url);
}
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 }); // const id = Utilities.getIdFromCookie(req);
if (shouldViewerRedirect) {
return res.redirect(
`/_${Strings.createQueryParams({
scene: "NAV_SLATE",
user: req.params.username,
slate: req.params.slatename,
cid: req.params.cid,
})}`
);
}
const slate = await Data.getSlateByName({ // const slate = await Data.getSlateByName({
slatename: req.params.slatename, // slatename,
username: req.params.username, // username,
includeFiles: true, // includeFiles: true,
sanitize: true, // sanitize: true,
}); // });
if (!slate) { // if (!slate) {
return res.redirect("/404"); // return res.redirect("/_/404");
} // }
if (slate.error) { // if (slate.error || !slate.isPublic && slate.ownerId !== id) {
return res.redirect("/404"); // return res.redirect("/_/404");
} // }
if (!slate.isPublic && slate.ownerId !== id) { // const user = await Data.getUserById({
return res.redirect("/403"); // id: slate.ownerId,
} // sanitize: true,
// });
const creator = await Data.getUserById({ // if (!user) {
id: slate.ownerId, // return res.redirect("/_/404");
sanitize: true, // }
});
if (!creator) { // if (user.error) {
return res.redirect("/404"); // return res.redirect("/_/404");
} // }
if (creator.error) { // let viewer = null;
return res.redirect("/404"); // if (id) {
} // viewer = await ViewerManager.getById({
// id,
// });
// }
if (req.params.username !== creator.username) { // slate.user = user;
return res.redirect("/403");
}
// let viewer = null; // let page = NavigationData.getById("NAV_SLATE", viewer);
// if (id) { // page = { ...page, cid };
// viewer = await ViewerManager.getById({
// id,
// });
// }
return app.render(req, res, "/_/slate", { // return app.render(req, res, "/_", {
viewer: null, // viewer,
creator, // isMobile,
slate, // isMac,
isMobile, // data: slate,
isMac, // page,
resources: EXTERNAL_RESOURCES, // resources: EXTERNAL_RESOURCES,
cid: req.params.cid, // });
}); // });
});
server.all("*", async (r, s) => handler(r, s, r.url)); server.all("*", async (r, s) => handler(r, s, r.url));