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

View File

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

View File

@ -1,146 +1,205 @@
import * as Actions from "~/common/actions";
import * as Strings from "~/common/strings";
// NOTE(jim):
// Recursion for nested entities (any number).
export const getCurrent = ({ id, data }) => {
let target = null;
let activeIds = {};
export const getById = (id, viewer) => {
let target;
const findById = (state, id) => {
for (let i = 0; i < state.length; i++) {
if (state[i].id === id) {
target = state[i];
activeIds[state[i].id] = true;
if (id) {
target = navigation.find((each) => each.id === id);
}
if (!target) {
return { ...errorPage };
}
if (target.id === "NAV_SLATE") {
target.slateId = data && data.id;
}
}
if (viewer && target.id === authPage.id) {
return { ...activityPage }; //NOTE(martina): authenticated users should be redirected to the home page rather than the
}
// if (!target && state[i].children) {
// activeIds[state[i].id] = true;
// findById(state[i].children, id);
if (!viewer && !target.externalAllowed) {
return { ...authPage }; //NOTE(martina): redirect to sign in page if try to access internal page while logged out
}
// if (!target) {
// activeIds[state[i].id] = false;
// }
// }
return { ...target };
};
export const getByHref = (href, viewer) => {
let pathname;
if (href) {
pathname = href.split("?")[0];
}
if (!pathname) {
return { page: { ...errorPage } };
}
if (pathname === "/_") {
return { page: { ...activityPage } };
}
let page = navigation.find((each) => pathname.startsWith(each.pathname));
let details;
if (page) {
page = { ...page };
if (page.id === "NAV_SLATE" || page.id === "NAV_PROFILE") {
details = {
id: pathname.replace(page.pathname, ""),
};
}
};
} else {
let params = pathname.slice(1).split("/");
if (params.length === 1) {
page = { ...profilePage };
details = {
username: params[0],
};
} else if (params.length === 2) {
page = { ...slatePage };
details = {
username: params[0],
slatename: params[1],
};
}
}
page.pathname = href;
findById(navigation, id);
let redirected = false;
return { target, activeIds };
if (viewer && page === authPage) {
redirected = true;
page = { ...activityPage };
}
if (!viewer && !page.externalAllowed) {
redirected = true;
page = { ...authPage }; //NOTE(martina): redirect to sign in page if try to access internal page while logged out
}
if (!page) {
window.location.replace("/_/404");
}
//NOTE(martina): to transform query params into more easily usable key value pairs in page
if (!redirected) {
let params = Strings.getParamsFromUrl(href);
if (page.id === "NAV_PROFILE" && page.cid) {
params.tab = "FILES";
}
page.params = params;
}
return { page, details };
};
const authPage = {
id: "NAV_SIGN_IN",
name: "Sign in",
pageTitle: "Sign in & Sign up",
ignore: true,
pathname: "/_/auth",
};
const dataPage = {
id: "NAV_DATA",
name: "Files",
pageTitle: "Your Files",
pathname: "/_/data",
mainNav: true,
};
const activityPage = {
id: "NAV_ACTIVITY",
name: "Activity",
pageTitle: "Activity",
ignore: true,
externalAllowed: true,
pathname: "/_/activity",
mainNav: true,
};
const slatePage = {
id: "NAV_SLATE",
name: "Collection",
pageTitle: "A Collection",
ignore: true,
externalAllowed: true,
pathname: "/$/slate/",
};
const profilePage = {
id: "NAV_PROFILE",
name: "Profile",
pageTitle: "A Profile",
ignore: true,
externalAllowed: true,
pathname: "/$/user/",
};
const errorPage = {
id: "NAV_ERROR",
name: "404",
pageTitle: "404 Not found",
ignore: true,
externalAllowed: true,
pathname: "/_/404",
};
export const navigation = [
{
id: "NAV_DATA",
decorator: "DATA",
name: "Home",
pageTitle: "Welcome back!",
},
{
id: "NAV_ACTIVITY",
decorator: "ACTIVITY",
name: "Activity",
pageTitle: "Activity",
ignore: true,
},
{
id: "NAV_EXPLORE",
decorator: "EXPLORE",
name: "Explore",
pageTitle: "Welcome back!",
ignore: true,
},
errorPage,
authPage,
activityPage,
dataPage,
{
id: "NAV_SLATES",
decorator: "SLATES",
name: "Collections",
pageTitle: "Collections",
ignore: true,
},
{
id: "NAV_SLATES_FOLLOWING",
decorator: "SLATES_FOLLOWING",
name: "Collections following",
pageTitle: "Collections following",
pageTitle: "Your Collections",
ignore: true,
pathname: "/_/collections",
mainNav: true,
},
// {
// id: "NAV_SEARCH",
// name: "Search",
// pageTitle: "Search Slate",
// ignore: true,
// pathname: "/_/search",
// mainNav: true,
// },
{
id: "NAV_DIRECTORY",
decorator: "DIRECTORY",
name: "Directory",
pageTitle: "Your directory",
},
{
id: "NAV_DIRECTORY_FOLLOWERS",
decorator: "DIRECTORY_FOLLOWERS",
name: "Directory",
pageTitle: "Your directory",
ignore: true,
},
{
id: "NAV_SLATE",
decorator: "SLATE",
name: "Collection",
pageTitle: "Collection",
ignore: true,
pageTitle: "Your Following",
pathname: "/_/directory",
},
slatePage,
{
id: "NAV_FILECOIN",
decorator: "FILECOIN",
name: "Filecoin",
pageTitle: "Archive on Filecoin",
filecoin: true,
pathname: "/_/filecoin",
},
{
id: "NAV_STORAGE_DEAL",
decorator: "STORAGE_DEAL",
name: "Storage Deal",
filecoin: true,
pageTitle: "Make a one-off Filecoin storage deal",
pageTitle: "Filecoin Storage Deal",
pathname: "/_/storage-deal",
},
{
id: "NAV_API",
decorator: "API",
name: "API",
pageTitle: "Developer API",
pathname: "/_/api",
},
{
id: "NAV_SETTINGS",
decorator: "SETTINGS",
name: "Profile & Account Settings",
pageTitle: "Your Profile & Account Settings",
ignore: true,
},
{
id: "NAV_PROFILE_FILES",
decorator: "PROFILE_FILES",
name: "Profile",
pageTitle: "Profile",
ignore: true,
},
{
id: "NAV_PROFILE",
decorator: "PROFILE",
name: "Profile",
pageTitle: "Profile",
ignore: true,
},
{
id: "NAV_PROFILE_PEERS",
decorator: "PROFILE_PEERS",
name: "Profile",
pageTitle: "Profile",
ignore: true,
},
{
id: "NAV_FILE",
decorator: "FILE",
name: "File",
pageTitle: "File",
name: "Settings",
pageTitle: "Profile & Account Settings",
ignore: true,
pathname: "/_/settings",
},
profilePage,
// {
// id: "NAV_FILE",
// name: "File",
// pageTitle: "A File",
// ignore: true,
// externalAllowed: true,
// },
];

View File

@ -217,6 +217,34 @@ export const urlToCid = (url) => {
.replace("hub.textile.io/ipfs/", "");
};
export const getQueryStringFromParams = (params) => {
let pairs = Object.entries(params).map(([key, value]) => `${key}=${value}`);
let query = "?".concat(pairs.join("&"));
if (query.length === 1) {
return "";
}
return query;
};
//NOTE(martina): works with both url and search passed in
export const getParamsFromUrl = (url) => {
let startIndex = url.indexOf("?") + 1;
let search = url.slice(startIndex);
if (search.length < 3) {
return {};
}
let params = {};
let pairs = search.split("&");
for (let pair of pairs) {
let key = pair.split("=")[0];
let value = pair.slice(key.length + 1);
if (key && value) {
params[key] = value;
}
}
return params;
};
export const hexToRGBA = (hex, alpha = 1) => {
hex = hex.replace("#", "");
var r = parseInt(hex.length == 3 ? hex.slice(0, 1).repeat(2) : hex.slice(0, 2), 16);

148
common/styles.js Normal file
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>
);
export const MenuMinimal = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
{...props}
>
<path d="M3.33331 6H21.3333" />
<path d="M3.33331 18H21.3333" />
</svg>
);
export const Globe = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -25,7 +25,11 @@ export const authenticate = async (state) => {
const jwt = cookies.get(Credentials.session.key);
if (jwt) {
cookies.remove(Credentials.session.key);
cookies.remove(Credentials.session.key, {
path: "/",
maxAge: 3600 * 24 * 7,
sameSite: "strict",
});
}
let response = await Actions.signIn(state);
@ -38,7 +42,7 @@ export const authenticate = async (state) => {
// + One week.
// + Only requests to the same site.
// + Not using sessionStorage so the cookie doesn't leave when the browser dies.
cookies.set(Credentials.session.key, response.token, true, {
cookies.set(Credentials.session.key, response.token, {
path: "/",
maxAge: 3600 * 24 * 7,
sameSite: "strict",
@ -48,7 +52,7 @@ export const authenticate = async (state) => {
return response;
};
// NOTE(jim): Signs a user out and redirects to the sign in screen.
// NOTE(jim): Signs a user out
export const signOut = async ({ viewer }) => {
let wsclient = Websockets.getClient();
if (wsclient) {
@ -60,10 +64,14 @@ export const signOut = async ({ viewer }) => {
const jwt = cookies.get(Credentials.session.key);
if (jwt) {
cookies.remove(Credentials.session.key);
cookies.remove(Credentials.session.key, {
path: "/",
maxAge: 3600 * 24 * 7,
sameSite: "strict",
});
}
window.location.replace("/_");
window.location.replace("/_/auth");
return;
};
@ -79,11 +87,11 @@ export const deleteMe = async ({ viewer }) => {
await signOut({ viewer });
let wsclient = Websockets.getClient();
if (wsclient) {
await Websockets.deleteClient();
wsclient = null;
}
// let wsclient = Websockets.getClient();
// if (wsclient) {
// await Websockets.deleteClient();
// wsclient = null;
// }
return response;
};

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,11 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import * as SVG from "~/common/svg";
import * as Styles from "~/common/styles";
import * as UserBehaviors from "~/common/user-behaviors";
import { PopoverNavigation } from "~/components/system";
import { css } from "@emotion/react";
import { Link } from "~/components/core/Link";
import { Boundary } from "~/components/system/components/fragments/Boundary";
@ -47,19 +48,19 @@ const STYLES_PROFILE_MOBILE = css`
`;
const STYLES_PROFILE_IMAGE = css`
background-color: ${Constants.system.white};
background-color: ${Constants.system.foreground};
background-size: cover;
background-position: 50% 50%;
flex-shrink: 0;
height: 32px;
width: 32px;
height: 24px;
width: 24px;
border-radius: 2px;
cursor: pointer;
@media (max-width: ${Constants.sizes.mobile}px) {
${"" /* @media (max-width: ${Constants.sizes.mobile}px) {
height: 24px;
width: 24px;
}
} */}
`;
const STYLES_PROFILE_USERNAME = css`
@ -100,35 +101,10 @@ const STYLES_ITEM_BOX = css`
}
`;
const STYLES_MENU = css`
position: absolute;
top: 48px;
right: 0px;
${"" /* @media (max-width: ${Constants.sizes.mobile}px) {
top: 48px;
left: 0px;
} */}
`;
const STYLES_MOBILE_HIDDEN = css`
@media (max-width: ${Constants.sizes.mobile}px) {
display: none;
}
`;
const STYLES_MOBILE_ONLY = css`
@media (min-width: ${Constants.sizes.mobile}px) {
display: none;
}
`;
export default class ApplicationUserControls extends React.Component {
_handleAction = (e, data) => {
e.preventDefault();
e.stopPropagation();
export class ApplicationUserControlsPopup extends React.Component {
_handleAction = (props) => {
this.props.onTogglePopup();
return this.props.onAction(data);
this.props.onAction(props);
};
_handleSignOut = (e) => {
@ -139,55 +115,166 @@ export default class ApplicationUserControls extends React.Component {
};
render() {
let tooltip = (
<Boundary
captureResize={true}
captureScroll={false}
enabled
onOutsideRectEvent={() => this.props.onTogglePopup()}
style={this.props.style}
>
<div>
<PopoverNavigation
style={{ top: 36, right: 0, padding: "12px 24px", width: 220 }}
itemStyle={{ padding: "12px 0", fontSize: 18, justifyContent: "flex-end" }}
navigation={[
{
text: "View profile",
onClick: (e) =>
this._handleAction(e, {
type: "NAVIGATE",
value: "NAV_PROFILE",
data: this.props.viewer,
}),
},
{
text: "Account settings",
onClick: (e) =>
this._handleAction(e, {
type: "NAVIGATE",
value: "NAV_SETTINGS",
}),
},
{
text: "Contact us",
onClick: (e) =>
this._handleAction(e, {
type: "SIDEBAR",
value: "SIDEBAR_HELP",
}),
},
{
text: "Sign out",
onClick: (e) => {
this._handleSignOut(e);
},
},
]}
if (this.props.popup === "profile") {
const topSection = (
<div css={Styles.HORIZONTAL_CONTAINER} style={{ marginBottom: 14 }}>
<span
css={STYLES_PROFILE_IMAGE}
style={{
cursor: "default",
width: 46,
height: 46,
marginRight: 16,
backgroundImage: `url('${this.props.viewer.data.photo}')`,
}}
/>
<div
css={Styles.VERTICAL_CONTAINER}
style={{
height: 46,
justifyContent: "space-between",
}}
>
<div css={Styles.HEADING_04}>
{this.props.viewer.data.name || `@${this.props.viewer.username}`}
</div>
<div css={Styles.HORIZONTAL_CONTAINER}>
<span css={Styles.SMALL_TEXT} style={{ marginRight: 8 }}>{`${
this.props.viewer.library.length
} File${this.props.viewer.library.length === 1 ? "" : "s"}`}</span>
<span css={Styles.SMALL_TEXT}>{`${this.props.viewer.slates.length} Collection${
this.props.viewer.slates.length === 1 ? "" : "s"
}`}</span>
</div>
</div>
</div>
</Boundary>
);
);
const navigation = [
[
{
text: (
<Link href={`/$/user/${this.props.viewer.id}`} onAction={this._handleAction}>
Profile
</Link>
),
},
{
text: (
<Link href={"/_/directory"} onAction={this._handleAction}>
Directory
</Link>
),
},
],
[
{
text: (
<Link href={"/_/filecoin"} onAction={this._handleAction}>
Filecoin
</Link>
),
},
{
text: (
<Link href={"/_/storage-deal"} onAction={this._handleAction}>
Storage deal
</Link>
),
},
{
text: (
<Link href={"/_/api"} onAction={this._handleAction}>
API
</Link>
),
},
],
[
{
text: (
<Link href={"/_/settings"} onAction={this._handleAction}>
Settings
</Link>
),
},
],
[
{
text: "Help",
onClick: (e) => {
e.stopPropagation();
this._handleAction({
type: "SIDEBAR",
value: "SIDEBAR_HELP",
});
},
},
{
text: "Sign out",
onClick: (e) => {
this._handleSignOut(e);
},
},
],
];
return (
<>
<div css={Styles.MOBILE_ONLY}>
<Boundary
captureResize={true}
captureScroll={false}
enabled={this.props.popup === "profile"}
onOutsideRectEvent={() => this.props.onTogglePopup()}
>
<PopoverNavigation
style={{
width: "max-content",
position: "relative",
border: "none",
boxShadow: "none",
width: "100vw",
background: "none",
pointerEvents: "auto",
}}
css={Styles.HEADING_04}
itemStyle={{ fontSize: Constants.typescale.lvl0 }}
topSection={topSection}
navigation={navigation}
/>
</Boundary>
</div>
<div css={Styles.MOBILE_HIDDEN}>
<Boundary
captureResize={true}
captureScroll={false}
enabled={this.props.popup === "profile"}
onOutsideRectEvent={() => this.props.onTogglePopup()}
>
<PopoverNavigation
style={{
top: 36,
right: 0,
width: "max-content",
}}
css={Styles.HEADING_04}
itemStyle={{ fontSize: Constants.typescale.lvl0 }}
topSection={topSection}
navigation={navigation}
/>
</Boundary>
</div>
</>
);
}
return null;
}
}
export class ApplicationUserControls extends React.Component {
render() {
let tooltip = <ApplicationUserControlsPopup {...this.props} />;
return (
<div css={STYLES_HEADER}>
<div css={STYLES_PROFILE_MOBILE} style={{ position: "relative" }}>

View File

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

View File

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

View File

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

View File

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

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

View File

@ -7,6 +7,8 @@ import * as Utilities from "~/common/utilities";
import * as Events from "~/common/custom-events";
import * as Window from "~/common/window";
import { useState } from "react";
import { Link } from "~/components/core/Link";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import { css } from "@emotion/react";
import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons";
@ -239,48 +241,236 @@ const STYLES_DIRECTORY_NAME = css`
// opacity: 0;
// `;
function UserEntry({
user,
button,
onClick,
message,
external,
url,
checkStatus,
showStatusIndicator,
}) {
function UserEntry({ user, button, onClick, message, checkStatus }) {
const isOnline = checkStatus({ id: user.id });
return (
<div key={user.username} css={STYLES_USER_ENTRY}>
{external ? (
<a css={STYLES_USER} style={{ textDecoration: "none" }} href={url}>
<div
css={STYLES_DIRECTORY_PROFILE_IMAGE}
style={{ backgroundImage: `url(${user.data.photo})` }}
>
{showStatusIndicator && isOnline && <div css={STYLES_DIRECTORY_STATUS_INDICATOR} />}
</div>
<span css={STYLES_DIRECTORY_NAME}>
{user.data.name || `@${user.username}`}
{message ? <span css={STYLES_MESSAGE}>{message}</span> : null}
</span>
</a>
) : (
<div css={STYLES_USER} onClick={onClick}>
<div
css={STYLES_DIRECTORY_PROFILE_IMAGE}
style={{ backgroundImage: `url(${user.data.photo})` }}
>
{isOnline && <div css={STYLES_DIRECTORY_STATUS_INDICATOR} />}
</div>
<span css={STYLES_DIRECTORY_NAME}>
{user.data.name || `@${user.username}`}
{message ? <span css={STYLES_MESSAGE}>{message}</span> : null}
</span>
<div css={STYLES_USER} onClick={onClick}>
<div
css={STYLES_DIRECTORY_PROFILE_IMAGE}
style={{ backgroundImage: `url(${user.data.photo})` }}
>
{isOnline && <div css={STYLES_DIRECTORY_STATUS_INDICATOR} />}
</div>
<span css={STYLES_DIRECTORY_NAME}>
{user.data.name || `@${user.username}`}
{message ? <span css={STYLES_MESSAGE}>{message}</span> : null}
</span>
</div>
{button}
</div>
);
}
function FilesPage({
library,
isOwner,
isMobile,
viewer,
onAction,
resources,
page,
tab = "grid",
}) {
return (
<div>
{isMobile ? null : (
<SecondaryTabGroup
tabs={[
{
title: <SVG.GridView height="24px" style={{ display: "block" }} />,
value: { tab: "grid", subtab: "files" },
},
{
title: <SVG.TableView height="24px" style={{ display: "block" }} />,
value: { tab: "table", subtab: "files" },
},
]}
value={tab}
onAction={onAction}
style={{ margin: "0 0 24px 0" }}
/>
)}
{external ? null : button}
{library.length ? (
<DataView
key="scene-profile"
onAction={onAction}
viewer={viewer}
isOwner={isOwner}
items={library}
view={tab}
resources={resources}
page={page}
/>
) : (
<EmptyState>
<FileTypeGroup />
<div style={{ marginTop: 24 }}>This user does not have any public files yet</div>
</EmptyState>
)}
</div>
);
}
function CollectionsPage({
user,
viewer,
fetched,
subscriptions,
tab = "collections",
isOwner,
onAction,
}) {
let slates = [];
if (tab === "collections") {
slates = user.slates
? isOwner
? user.slates.filter((slate) => slate.isPublic === true)
: user.slates
: slates;
} else {
slates = subscriptions;
}
slates = slates || [];
return (
<div>
<SecondaryTabGroup
tabs={[
{ title: "Collections", value: { tab: "collections", subtab: "collections" } },
{ title: "Subscribed", value: { tab: "subscribed", subtab: "collections" } },
]}
value={tab}
onAction={onAction}
style={{ margin: "0 0 24px 0" }}
/>
{slates?.length ? (
<SlatePreviewBlocks external={!viewer} slates={slates || []} onAction={onAction} />
) : (
<EmptyState>
{tab === "collections" || fetched ? (
<React.Fragment>
<SVG.Slate height="24px" style={{ marginBottom: 24 }} />
{tab === "collections"
? `This user does not have any public collections yet`
: `This user is not following any collections yet`}
</React.Fragment>
) : (
<LoaderSpinner style={{ height: 24, width: 24 }} />
)}
</EmptyState>
)}
</div>
);
}
function PeersPage({
checkStatus,
viewer,
following,
followers,
fetched,
tab = "following",
onAction,
onLoginModal,
}) {
const [selectedUser, setSelectedUser] = useState(false);
const selectUser = (e, id) => {
e.stopPropagation();
e.preventDefault();
if (!id || selectedUser === id) {
setSelectedUser(null);
} else {
setSelectedUser(id);
}
};
const followUser = async (e, id) => {
e.stopPropagation();
e.preventDefault();
selectUser(e, null);
if (!viewer) {
onLoginModal();
return;
}
await Actions.createSubscription({
userId: id,
});
};
let peers = tab === "following" ? following : followers;
peers = peers.map((relation) => {
const following = !!(
viewer &&
viewer.following.some((subscription) => {
return subscription.id === relation.id;
}).length
);
let button =
!viewer || relation.id !== viewer?.id ? (
<div css={STYLES_ITEM_BOX} onClick={(e) => selectUser(e, relation.id)}>
<SVG.MoreHorizontal height="24px" />
{selectedUser === relation.id ? (
<Boundary
captureResize={true}
captureScroll={false}
enabled
onOutsideRectEvent={(e) => selectUser(e)}
>
<PopoverNavigation
style={{
top: "40px",
right: "0px",
}}
navigation={[
[
{
text: following ? "Unfollow" : "Follow",
onClick: (e) => followUser(e, relation.id),
},
],
]}
/>
</Boundary>
) : null}
</div>
) : null;
return (
<Link href={`/$/user/${relation.id}`} onAction={onAction}>
<UserEntry key={relation.id} user={relation} button={button} checkStatus={checkStatus} />
</Link>
);
});
return (
<div>
<SecondaryTabGroup
tabs={[
{ title: "Following", value: { tab: "following", subtab: "peers" } },
{ title: "Followers", value: { tab: "followers", subtab: "peers" } },
]}
value={tab}
onAction={onAction}
style={{ margin: "0 0 24px 0" }}
/>
<div>
{peers?.length ? (
peers
) : (
<EmptyState>
{fetched ? (
<React.Fragment>
<SVG.Users height="24px" style={{ marginBottom: 24 }} />
{tab === "following"
? `This user is not following anyone yet`
: `This user does not have any followers yet`}
</React.Fragment>
) : (
<LoaderSpinner style={{ height: 24, width: 24 }} />
)}
</EmptyState>
)}
</div>
</div>
);
}
@ -289,12 +479,7 @@ export default class Profile extends React.Component {
_ref = null;
state = {
view: 0,
slateTab: 0,
peerTab: 0,
// copyValue: "",
contextMenu: null,
slates: this.props.user.slates,
subscriptions: [],
followers: [],
following: [],
@ -305,21 +490,22 @@ export default class Profile extends React.Component {
return entry.id === this.props.user.id;
}),
fetched: false,
tab: this.props.tab || 0,
};
componentDidMount = () => {
this._handleUpdatePage();
this.fetchSocial();
};
componentDidUpdate = (prevProps) => {
if (this.props.page?.tab !== prevProps.page?.tab) {
this.setState({ tab: this.props.page.tab });
if (!this.state.fetched && this.props.page.params !== prevProps.page.params) {
this.fetchSocial();
}
};
fetchSocial = async () => {
if (this.state.fetched) return;
if (this.props.page.params?.subtab !== "peers" && this.props.page.params?.tab !== "subscribed")
return;
let following, followers, subscriptions;
if (this.props.user.id === this.props.viewer?.id) {
following = this.props.viewer?.following;
@ -343,31 +529,23 @@ export default class Profile extends React.Component {
});
};
// _handleCopy = (e, value) => {
// e.stopPropagation();
// this.setState({ copyValue: value }, () => {
// this._ref.select();
// document.execCommand("copy");
// this._handleHide();
// });
// };
_handleHide = (e) => {
this.setState({ contextMenu: null });
};
_handleClick = (e, value) => {
e.stopPropagation();
if (this.state.contextMenu === value) {
this._handleHide();
} else {
this.setState({ contextMenu: value });
}
};
// _handleClick = (e, value) => {
// e.stopPropagation();
// if (this.state.contextMenu === value) {
// this._handleHide();
// } else {
// this.setState({ contextMenu: value });
// }
// };
_handleFollow = async (e, id) => {
if (this.props.external) {
this._handleRedirectToInternal();
this._handleLoginModal();
return;
}
this._handleHide();
e.stopPropagation();
@ -376,33 +554,12 @@ export default class Profile extends React.Component {
});
};
_handleRedirectToInternal = () => {
this.setState({ visible: true });
};
_handleSwitchTab = (tab) => {
if (typeof window !== "undefined") {
this.setState({ tab });
window.history.pushState({ ...window.history.state, tab }, "", window.location.pathname);
_handleLoginModal = (e) => {
if (e) {
e.preventDefault();
e.stopPropagation();
}
if (tab === 2 && !this.state.fetched) {
this.fetchSocial();
}
};
_handleUpdatePage = () => {
let tab;
if (typeof window !== "undefined") {
tab = window?.history?.state.tab;
}
if (typeof tab === "undefined") {
tab = 0;
}
this.setState({ tab }, () => {
if (this.state.tab === 2 || (this.state.tab === 1 && this.state.slateTab === 1)) {
this.fetchSocial();
}
});
Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} });
};
checkStatus = ({ id }) => {
@ -411,81 +568,15 @@ export default class Profile extends React.Component {
};
render() {
let tab = this.state.tab || 0;
let publicFiles = this.props.user.library;
let subtab = this.props.page.params?.subtab
? this.props.page.params?.subtab
: this.props.page.params?.cid
? "files"
: "collections";
let tab = this.props.page.params?.tab;
let library = this.props.user.library;
let isOwner = this.props.isOwner;
let user = this.props.user;
let username = this.state.slateTab === 0 ? user.username : null;
let slates = [];
if (tab === 1) {
if (this.state.slateTab === 0) {
slates = user.slates
? isOwner
? user.slates.filter((slate) => slate.isPublic === true)
: user.slates
: null;
} else {
slates = this.state.subscriptions;
}
}
let exploreSlates = this.props.exploreSlates;
let peers = this.state.peerTab === 0 ? this.state.following : this.state.followers;
if (tab === 2) {
peers = peers.map((relation) => {
let button = (
<div css={STYLES_ITEM_BOX} onClick={(e) => this._handleClick(e, relation.id)}>
<SVG.MoreHorizontal height="24px" />
{this.state.contextMenu === relation.id ? (
<Boundary
captureResize={true}
captureScroll={false}
enabled
onOutsideRectEvent={(e) => this._handleClick(e, relation.id)}
>
<PopoverNavigation
style={{
top: "40px",
right: "0px",
}}
navigation={[
{
text: this.props.viewer?.following.some((subscription) => {
return subscription.id === relation.id;
}).length
? "Unfollow"
: "Follow",
onClick: this.props.viewer
? (e) => this._handleFollow(e, relation.id)
: () => this.setState({ visible: true }),
},
]}
/>
</Boundary>
) : null}
</div>
);
return (
<UserEntry
key={relation.id}
user={relation}
button={button}
checkStatus={this.checkStatus}
showStatusIndicator={this.props.isAuthenticated}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation,
});
}}
external={this.props.external}
url={`/${relation.username}`}
/>
);
});
}
const showStatusIndicator = this.props.isAuthenticated;
@ -493,14 +584,14 @@ export default class Profile extends React.Component {
<div>
<GlobalCarousel
carouselType="PROFILE"
onUpdateViewer={this.props.onUpdateViewer}
resources={this.props.resources}
viewer={this.props.viewer}
objects={publicFiles}
objects={library}
isOwner={this.props.isOwner}
onAction={this.props.onAction}
isMobile={this.props.isMobile}
external={this.props.external}
params={this.props.page.params}
/>
<div css={STYLES_PROFILE_BACKGROUND}>
<div css={STYLES_PROFILE_INFO}>
@ -547,7 +638,7 @@ export default class Profile extends React.Component {
<div css={STYLES_STATS}>
<div css={STYLES_STAT}>
<div style={{ fontFamily: `${Constants.font.text}` }}>
{publicFiles.length}{" "}
{library.length}{" "}
<span style={{ color: `${Constants.system.darkGray}` }}>Files</span>
</div>
</div>
@ -561,162 +652,37 @@ export default class Profile extends React.Component {
</div>
</div>
</div>
{this.state.visible && (
<div>
<CTATransition
onClose={() => this.setState({ visible: false })}
viewer={this.props.viewer}
open={this.state.visible}
redirectURL={`/_${Strings.createQueryParams({
scene: "NAV_PROFILE",
user: user.username,
})}`}
/>
</div>
)}
<div css={STYLES_PROFILE}>
<TabGroup
tabs={["Files", "Collections", "Peers"]}
value={tab}
onChange={this._handleSwitchTab}
tabs={[
{ title: "Files", value: { subtab: "files" } },
{ title: "Collections", value: { subtab: "collections" } },
{ title: "Peers", value: { subtab: "peers" } },
]}
value={subtab}
onAction={this.props.onAction}
style={{ marginTop: 0, marginBottom: 32 }}
itemStyle={{ margin: "0px 16px" }}
/>
{tab === 0 ? (
<div>
{this.props.isMobile ? null : (
<div style={{ display: `flex` }}>
<SecondaryTabGroup
tabs={[
<SVG.GridView height="24px" style={{ display: "block" }} />,
<SVG.TableView height="24px" style={{ display: "block" }} />,
]}
value={this.state.view}
onChange={(value) => this.setState({ view: value })}
style={{ margin: "0 0 24px 0", justifyContent: "flex-end" }}
/>
</div>
)}
{publicFiles.length ? (
<DataView
key="scene-profile"
onAction={this.props.onAction}
viewer={this.props.viewer}
isOwner={isOwner}
items={publicFiles}
onUpdateViewer={this.props.onUpdateViewer}
view={this.state.view}
resources={this.props.resources}
/>
) : (
<EmptyState>
<FileTypeGroup />
<div style={{ marginTop: 24 }}>This user does not have any public files yet</div>
</EmptyState>
)}
</div>
{subtab === "files" ? <FilesPage {...this.props} library={library} tab={tab} /> : null}
{subtab === "collections" ? (
<CollectionsPage
{...this.props}
tab={tab}
fetched={this.state.fetched}
subscriptions={this.state.subscriptions}
/>
) : null}
{tab === 1 ? (
<div>
<SecondaryTabGroup
tabs={["Collections", "Following"]}
value={this.state.slateTab}
onChange={(value) => {
this.setState({ slateTab: value }, () => {
if (!this.state.fetched) {
this.fetchSocial();
}
});
}}
style={{ margin: "0 0 24px 0" }}
/>
{slates?.length ? (
<SlatePreviewBlocks
isOwner={this.state.slateTab === 0 ? isOwner : false}
external={this.props.external}
slates={slates || []}
username={username}
onAction={this.props.onAction}
/>
) : (
<React.Fragment>
{this.props.external && exploreSlates.length != 0 ? (
<React.Fragment>
<EmptyState style={{ border: `none`, height: `120px` }}>
{this.state.fetched || this.state.slateTab == 0 ? (
<React.Fragment>
<SVG.Slate height="24px" style={{ marginBottom: 24 }} />
{this.state.slateTab === 0
? `This user does not have any public collections yet`
: `This user is not following any collections yet`}
</React.Fragment>
) : (
<LoaderSpinner style={{ height: 24, width: 24 }} />
)}
</EmptyState>
<div css={STYLES_EXPLORE}>Explore Collections</div>
<SlatePreviewBlocks
isOwner={false}
external={this.props.external}
slates={exploreSlates}
username={exploreSlates.username}
onAction={this.props.onAction}
/>
</React.Fragment>
) : (
<EmptyState>
{this.state.fetched || this.state.slateTab == 0 ? (
<React.Fragment>
<SVG.Slate height="24px" style={{ marginBottom: 24 }} />
{this.state.slateTab === 0
? `This user does not have any public collections yet`
: `This user is not following any collections yet`}
</React.Fragment>
) : (
<LoaderSpinner style={{ height: 24, width: 24 }} />
)}
</EmptyState>
)}
</React.Fragment>
)}
</div>
) : null}
{tab === 2 ? (
<div>
<SecondaryTabGroup
tabs={["Following", "Followers"]}
value={this.state.peerTab}
onChange={(value) => this.setState({ peerTab: value })}
style={{ margin: "0 0 24px 0" }}
/>
<div>
{peers?.length ? (
peers
) : (
<EmptyState>
{this.state.fetched || this.state.slateTab == 0 ? (
<React.Fragment>
<SVG.Users height="24px" style={{ marginBottom: 24 }} />
{this.state.peerTab === 0
? `This user is not following anyone yet`
: `This user does not have any followers yet`}
</React.Fragment>
) : (
<LoaderSpinner style={{ height: 24, width: 24 }} />
)}
</EmptyState>
)}
</div>
{/* <input
readOnly
ref={(c) => {
this._ref = c;
}}
value={this.state.copyValue}
tabIndex="-1"
css={STYLES_COPY_INPUT}
/> */}
</div>
{subtab === "peers" ? (
<PeersPage
{...this.props}
tab={tab}
onLoginModal={this._handleLoginModal}
checkStatus={this.checkStatus}
following={this.state.following}
followers={this.state.followers}
fetched={this.state.fetched}
/>
) : null}
</div>
</div>

View File

@ -9,6 +9,7 @@ import * as Validations from "~/common/validations";
import MiniSearch from "minisearch";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
import { Link } from "~/components/core/Link";
import { css } from "@emotion/react";
import { LoaderSpinner } from "~/components/system/components/Loaders";
import { Boundary } from "~/components/system/components/fragments/Boundary";
@ -437,6 +438,21 @@ const STYLES_DISMISS_BOX = css`
outline: 0;
`;
const getHref = (result) => {
if (result.type === "SLATE") {
return `/$/slate/${result.data.slate.id}`;
} else if (result.type === "USER") {
return `/$/user/${result.data.user.id}`;
} else if (result.type === "FILE") {
return `/$/user/${result.data.user.id}?cid=${result.data.file.cid}`;
} else if (result.type === "DATA_FILE") {
return `/_/data?cid=${result.data.file.cid}`;
} else {
console.log("GET HREF FAILED B/C RESULT WAS:");
console.log(result);
}
};
export class SearchModal extends React.Component {
_input;
_optionRoot;
@ -484,24 +500,27 @@ export class SearchModal extends React.Component {
this.debounceInstance = Window.debounce(() => {
this._handleSearch();
}, 500);
let defaultResults = this.props.viewer.slates;
let defaultResults = this.props.viewer ? this.props.viewer.slates : [];
defaultResults = defaultResults.map((slate) => {
return {
id: slate.id,
type: "SLATE",
data: { slate: slate },
data: { slate },
component: <SlateEntry slate={slate} user={this.props.viewer} />,
preview: <SlatePreview slate={slate} user={this.props.viewer} />,
href: `/$/slate/${slate.id}`,
};
});
this.setState({ defaultResults });
let networkIds = [];
let slateIds = [];
for (let sub of this.props.viewer.subscriptions) {
if (sub.target_user_id) {
networkIds.push(sub.target_user_id);
} else if (sub.target_slate_id) {
slateIds.push(sub.target_slate_id);
if (this.props.viewer?.subscriptions) {
for (let sub of this.props.viewer.subscriptions) {
if (sub.target_user_id) {
networkIds.push(sub.target_user_id);
} else if (sub.target_slate_id) {
slateIds.push(sub.target_slate_id);
}
}
}
this.networkIds = networkIds;
@ -509,6 +528,9 @@ export class SearchModal extends React.Component {
};
fillLocalDirectory = () => {
if (!this.props.viewer) {
return;
}
this.localSearch = new MiniSearch({
fields: ["name", "title"],
storeFields: ["type", "data", "id"],
@ -614,7 +636,9 @@ export class SearchModal extends React.Component {
e.preventDefault();
} else if (e.keyCode === 13) {
if (results.length > this.state.selectedIndex && this.state.selectedIndex >= 0) {
this._handleSelect(results[this.state.selectedIndex]);
let href = results[this.state.selectedIndex].href;
console.log("key down navigate");
this.props.onAction({ type: "NAVIGATE", href });
}
e.preventDefault();
}
@ -629,7 +653,7 @@ export class SearchModal extends React.Component {
let searchResults = [];
let results = [];
let ids = new Set();
if (this.state.typeFilter !== "USER") {
if (this.state.typeFilter !== "USER" && this.props.viewer) {
let filter;
if (this.state.typeFilter === "FILE") {
filter = {
@ -686,7 +710,7 @@ export class SearchModal extends React.Component {
file={item.data.file}
slate={item.data.slate}
user={this.props.viewer}
viewerId={this.props.viewer.id}
viewerId={this.props.viewer?.id}
/>
),
});
@ -715,6 +739,7 @@ export class SearchModal extends React.Component {
results.push({
id,
type: res.type,
href: res.href,
data: res,
component: <UserEntry user={res.user} />,
preview: <UserPreview user={res.user} />,
@ -726,6 +751,7 @@ export class SearchModal extends React.Component {
results.push({
id,
type: res.type,
href: res.href,
data: res,
component: <SlateEntry slate={res.slate} user={res.user} />,
preview: <SlatePreview slate={res.slate} user={res.user} />,
@ -737,6 +763,7 @@ export class SearchModal extends React.Component {
results.push({
id,
type: res.type,
href: res.href,
data: res,
component: <FileEntry file={res.file} />,
preview: (
@ -744,12 +771,16 @@ export class SearchModal extends React.Component {
file={res.file}
slate={res.slate}
user={res.user}
viewerId={this.props.viewer.id}
viewerId={this.props.viewer?.id}
/>
),
});
}
}
results = results.map((res) => {
return { ...res, href: getHref(res) };
});
console.log(results);
this.setState({ results, selectedIndex: 0 });
if (this._optionRoot) {
this._optionRoot.scrollTop = 0;
@ -757,6 +788,9 @@ export class SearchModal extends React.Component {
};
processResults = (searchResults) => {
if (!this.state.viewer) {
return searchResults;
}
let results = searchResults;
if (this.state.scopeFilter === "MY") {
results = results.filter((res) => {
@ -809,65 +843,65 @@ export class SearchModal extends React.Component {
return results;
};
_handleSelect = async (res) => {
if (res.type === "SLATE") {
this.props.onAction({
type: "NAVIGATE",
value: "NAV_SLATE",
data: res.data.slate,
});
} else if (res.type === "USER") {
this.props.onAction({
type: "NAVIGATE",
value: "NAV_PROFILE",
data: res.data.user,
});
} else if (res.type === "DATA_FILE" || res.data.file.ownerId === this.props.viewer.id) {
await this.props.onAction({
type: "NAVIGATE",
value: "NAV_DATA",
fileId: res.data.file.id,
});
} else if (res.type === "FILE") {
await this.props.onAction({
type: "NAVIGATE",
value: "NAV_PROFILE",
data: res.data.user,
fileId: res.data.file.id,
});
}
this._handleHide();
};
// _handleSelect = async (res) => {
// if (res.type === "SLATE") {
// this.props.onAction({
// type: "NAVIGATE",
// value: "NAV_SLATE",
// data: res.data.slate,
// });
// } else if (res.type === "USER") {
// this.props.onAction({
// type: "NAVIGATE",
// value: "NAV_PROFILE",
// data: res.data.user,
// });
// } else if (res.type === "DATA_FILE" || res.data.file.ownerId === this.props.viewer?.id) {
// await this.props.onAction({
// type: "NAVIGATE",
// value: "NAV_DATA",
// fileId: res.data.file.id,
// });
// } else if (res.type === "FILE") {
// await this.props.onAction({
// type: "NAVIGATE",
// value: "NAV_PROFILE",
// data: res.data.user,
// fileId: res.data.file.id,
// });
// }
// this._handleHide();
// };
_handleRedirect = async (destination) => {
if (destination === "FMU") {
let isProd = window.location.hostname.includes("slate.host");
this._handleSelect({
type: "FILE",
data: {
file: { id: "rick-roll" },
slate: {
id: isProd
? "01edcede-53c9-46b3-ac63-8f8479e10bcf"
: "60d199e7-6bf5-4994-94e8-b17547c64449",
data: {
objects: [
{
id: "rick-roll",
url:
"https://slate.textile.io/ipfs/bafybeifcxjvbad4lgpnbwff2dafufmnlylylmku4qoqtlkwgidupwi6f3a",
ownerId: "owner",
name: "Never gonna give you up",
title: "never-gonna-give-you-up.mp4",
type: "video/mp4",
},
],
},
ownerId: "owner",
},
},
});
}
// if (destination === "FMU") {
// let isProd = window.location.hostname.includes("slate.host");
// this._handleSelect({
// type: "FILE",
// data: {
// file: { id: "rick-roll" },
// slate: {
// id: isProd
// ? "01edcede-53c9-46b3-ac63-8f8479e10bcf"
// : "60d199e7-6bf5-4994-94e8-b17547c64449",
// data: {
// objects: [
// {
// id: "rick-roll",
// url:
// "https://slate.textile.io/ipfs/bafybeifcxjvbad4lgpnbwff2dafufmnlylylmku4qoqtlkwgidupwi6f3a",
// ownerId: "owner",
// name: "Never gonna give you up",
// title: "never-gonna-give-you-up.mp4",
// type: "video/mp4",
// },
// ],
// },
// ownerId: "owner",
// },
// },
// });
// }
this.props.onAction({
type: "SIDEBAR",
value: destination,
@ -913,12 +947,101 @@ export class SearchModal extends React.Component {
});
};
_handleSelectIndex = (i) => {
if (this.state.selectedIndex === i || this.props.isMobile) {
console.log("handle hide");
this._handleHide();
} else {
console.log("set state");
// this.setState({ selectedIndex: i });
}
};
render() {
let selectedIndex = this.state.selectedIndex;
let results =
this.state.inputValue && this.state.inputValue.length
? this.state.results
: this.state.defaultResults;
const filterDropdown = this.props.viewer ? (
<div style={{ flexShrink: 0, position: "relative" }}>
<div
css={STYLES_FILTER_BUTTON}
style={{
marginRight: 0,
marginLeft: 16,
color: this.state.scopeFilter ? Constants.system.brand : Constants.system.textGray,
}}
onClick={() => this.setState({ filterTooltip: !this.state.filterTooltip })}
>
<SVG.Filter height="16px" />
</div>
{this.state.filterTooltip ? (
<Boundary
captureResize={true}
captureScroll={false}
enabled
onOutsideRectEvent={() => this.setState({ filterTooltip: false })}
>
<PopoverNavigation
style={{
right: 0,
top: 44,
borderColor: Constants.system.bgGray,
color: Constants.system.textGray,
width: 124,
}}
navigation={[
[
{
text: (
<span
style={{
color: this.state.scopeFilter ? "inherit" : Constants.system.brand,
}}
>
All
</span>
),
onClick: () => this._handleFilterScope(null),
},
{
text: (
<span
style={{
color:
this.state.scopeFilter === "MY" ? Constants.system.brand : "inherit",
}}
>
My stuff
</span>
),
onClick: () => this._handleFilterScope("MY"),
},
{
text: (
<span
style={{
color:
this.state.scopeFilter === "NETWORK"
? Constants.system.brand
: "inherit",
}}
>
My network
</span>
),
onClick: () => this._handleFilterScope("NETWORK"),
},
],
]}
/>
</Boundary>
) : null}
</div>
) : null;
return (
<div
css={STYLES_BACKGROUND}
@ -1047,87 +1170,7 @@ export class SearchModal extends React.Component {
</span>
</div>
</div>
<div style={{ flexShrink: 0, position: "relative" }}>
<div
css={STYLES_FILTER_BUTTON}
style={{
marginRight: 0,
marginLeft: 16,
color: this.state.scopeFilter
? Constants.system.brand
: Constants.system.textGray,
}}
onClick={() =>
this.setState({ filterTooltip: !this.state.filterTooltip })
}
>
<SVG.Filter height="16px" />
</div>
{this.state.filterTooltip ? (
<Boundary
captureResize={true}
captureScroll={false}
enabled
onOutsideRectEvent={() => this.setState({ filterTooltip: false })}
>
<PopoverNavigation
style={{
right: 0,
top: 44,
borderColor: Constants.system.bgGray,
color: Constants.system.textGray,
width: 124,
}}
navigation={[
{
text: (
<span
style={{
color: this.state.scopeFilter
? "inherit"
: Constants.system.brand,
}}
>
All
</span>
),
onClick: () => this._handleFilterScope(null),
},
{
text: (
<span
style={{
color:
this.state.scopeFilter === "MY"
? Constants.system.brand
: "inherit",
}}
>
My stuff
</span>
),
onClick: () => this._handleFilterScope("MY"),
},
{
text: (
<span
style={{
color:
this.state.scopeFilter === "NETWORK"
? Constants.system.brand
: "inherit",
}}
>
My network
</span>
),
onClick: () => this._handleFilterScope("NETWORK"),
},
]}
/>
</Boundary>
) : null}
</div>
{filterDropdown}
</div>
<div
@ -1138,45 +1181,57 @@ export class SearchModal extends React.Component {
css={STYLES_DROPDOWN}
>
{results.map((each, i) => (
<div
key={each.id}
css={STYLES_DROPDOWN_ITEM}
style={{
background:
selectedIndex === i
? "rgba(196, 196, 196, 0.1)"
: Constants.system.white,
paddingRight: selectedIndex === i ? "88px" : "4px",
}}
onClick={() => {
selectedIndex === i || this.props.isMobile
? this._handleSelect(each)
: this.setState({ selectedIndex: i });
}}
<Link
disabled={this.props.isMobile ? false : selectedIndex !== i}
href={each.href}
onAction={this.props.onAction}
onClick={() => this._handleSelectIndex(i)}
>
{each.component}
{selectedIndex === i ? (
<div css={STYLES_RETURN}>
<SVG.ArrowDownLeft height="16px" style={{ marginRight: 8 }} /> Return
</div>
) : null}
</div>
<div
key={each.id}
css={STYLES_DROPDOWN_ITEM}
style={{
background:
selectedIndex === i
? "rgba(196, 196, 196, 0.1)"
: Constants.system.white,
paddingRight: selectedIndex === i ? "88px" : "4px",
}}
// onClick={() => {
// selectedIndex === i || this.props.isMobile
// ? this._handleSelect(each)
// : this.setState({ selectedIndex: i });
// }}
onClick={() => this.setState({ selectedIndex: i })}
>
{each.component}
{selectedIndex === i ? (
<div css={STYLES_RETURN}>
<SVG.ArrowDownLeft height="16px" style={{ marginRight: 8 }} />{" "}
Return
</div>
) : null}
</div>
</Link>
))}
</div>
{results &&
results.length &&
selectedIndex < results.length &&
selectedIndex >= 0 ? (
<div
css={STYLES_PREVIEW_PANEL}
onClick={() => {
if (selectedIndex >= 0 && selectedIndex < results.length) {
this._handleSelect(results[selectedIndex]);
}
}}
{results?.length && selectedIndex < results.length && selectedIndex >= 0 ? (
<Link
href={results[selectedIndex].href}
onAction={this.props.onAction}
onClick={this._handleHide}
>
{results[selectedIndex].preview}
</div>
<div
css={STYLES_PREVIEW_PANEL}
// onClick={() => {
// if (selectedIndex >= 0 && selectedIndex < results.length) {
// this._handleSelect(results[selectedIndex]);
// }
// }}
>
{results[selectedIndex].preview}
</div>
</Link>
) : null}
</React.Fragment>
)}
@ -1195,9 +1250,9 @@ export class SearchModal extends React.Component {
>
FAQ
</span>
<span style={{ cursor: "pointer" }} onClick={() => this._handleRedirect("FMU")}>
{/* <span style={{ cursor: "pointer" }} onClick={() => this._handleRedirect("FMU")}>
I'm Feeling Lucky
</span>
</span> */}
</div>
</div>
</Boundary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -87,10 +87,10 @@ const WebsitePrototypeHeaderGeneric = (props) => {
</a>
</div>
<div css={STYLES_RIGHT}>
<a css={STYLES_LINK} href="/_" style={{ marginRight: 24 }}>
<a css={STYLES_LINK} href="/_/auth?tab=signup" style={{ marginRight: 24 }}>
Sign up
</a>
<a css={STYLES_LINK} href="/_">
<a css={STYLES_LINK} href="/_/auth?tab=signin">
Sign in
</a>
</div>

View File

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

View File

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

View File

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

View File

@ -86,7 +86,7 @@ export default class SidebarEditTags extends React.Component {
this._handleSave();
let newSuggestions = new Set([...this.state.suggestions, ...this.state.tags]);
this.props.onUpdateViewer({ tags: Array.from(newSuggestions) });
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { tags: Array.from(newSuggestions) } });
};
render() {

View File

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

View File

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

View File

@ -116,31 +116,28 @@ const STYLES_MOBILE_HIDDEN = css`
export class GlobalCarousel extends React.Component {
state = {
index: 0,
visible: false,
showSidebar: true,
};
componentDidMount = () => {
window.addEventListener("keydown", this._handleKeyDown);
window.addEventListener("slate-global-open-carousel", this._handleOpen);
window.addEventListener("slate-global-close-carousel", this._handleClose);
// window.addEventListener("slate-global-open-carousel", this._handleOpen);
// window.addEventListener("slate-global-close-carousel", this._handleClose);
};
componentWillUnmount = () => {
window.removeEventListener("keydown", this._handleKeyDown);
window.removeEventListener("slate-global-open-carousel", this._handleOpen);
window.removeEventListener("slate-global-close-carousel", this._handleClose);
// window.removeEventListener("slate-global-open-carousel", this._handleOpen);
// window.removeEventListener("slate-global-close-carousel", this._handleClose);
};
componentDidUpdate = (prevProps) => {
if (
!this.props.objects ||
this.props.objects.length == 0 ||
this.state.index >= this.props.objects.length
) {
this._handleClose();
findSelectedIndex = () => {
const cid = this.props.params?.cid;
if (!cid) {
return -1;
}
let index = this.props.objects.findIndex((elem) => elem.cid === cid);
return index;
};
_handleKeyDown = (e) => {
@ -173,95 +170,132 @@ export class GlobalCarousel extends React.Component {
}
};
setWindowState = (data = {}) => {
const cid = data?.cid;
if (this.props.carouselType === "ACTIVITY") {
window.history.replaceState(
{ ...window.history.state, cid: cid },
null,
cid ? `/${data.owner}/${data.slate.slatename}/cid:${cid}` : `/_?scene=NAV_ACTIVITY`
);
return;
}
// setWindowState = (data = {}) => {
// const cid = data?.cid;
// if (this.props.carouselType === "ACTIVITY") {
// window.history.replaceState(
// { ...window.history.state, cid: cid },
// null,
// cid ? `/${data.owner}/${data.slate.slatename}/cid:${cid}` : `/_?scene=NAV_ACTIVITY`
// );
// return;
// }
let baseURL = window.location.pathname.split("/");
if (this.props.carouselType === "SLATE") {
baseURL.length = 3;
} else if (this.props.carouselType === "PROFILE") {
baseURL.length = 2;
} else if (this.props.carouselType === "DATA") {
baseURL.length = 2;
if (cid) {
baseURL[1] = this.props.viewer.username;
} else {
baseURL[1] = "_?scene=NAV_DATA";
}
}
baseURL = baseURL.join("/");
// let baseURL = window.location.pathname.split("/");
// if (this.props.carouselType === "SLATE") {
// baseURL.length = 3;
// } else if (this.props.carouselType === "PROFILE") {
// baseURL.length = 2;
// } else if (this.props.carouselType === "DATA") {
// baseURL.length = 2;
// if (cid) {
// baseURL[1] = this.props.viewer.username;
// } else {
// baseURL[1] = "_?scene=NAV_DATA";
// }
// }
// baseURL = baseURL.join("/");
window.history.replaceState(
{ ...window.history.state, cid: cid },
null,
cid ? `${baseURL}/cid:${cid}` : baseURL
);
};
// window.history.replaceState(
// { ...window.history.state, cid: cid },
// null,
// cid ? `${baseURL}/cid:${cid}` : baseURL
// );
// };
_handleOpen = (e) => {
let index = e.detail.index;
const objects = this.props.objects;
if (e.detail.index === null) {
if (e.detail.id !== null) {
index = objects.findIndex((obj) => obj.id === e.detail.id);
}
}
if (index === null || index < 0 || index >= objects.length) {
return;
}
this.setState({
visible: true,
index: e.detail.index,
});
const data = objects[e.detail.index];
this.setWindowState(data);
};
// _handleOpen = (e) => {
// let index = e.detail.index;
// const objects = this.props.objects;
// if (e.detail.index === null) {
// if (e.detail.id !== null) {
// index = objects.findIndex((obj) => obj.id === e.detail.id);
// }
// }
// if (index === null || index < 0 || index >= objects.length) {
// return;
// }
// this.setState({
// visible: true,
// index: e.detail.index,
// });
// const data = objects[e.detail.index];
// this.setWindowState(data);
// };
_handleClose = (e) => {
if (this.state.visible) {
if (e) {
e.stopPropagation();
e.preventDefault();
}
this.setState({ visible: false, index: 0 });
this.setWindowState();
if (e) {
e.stopPropagation();
e.preventDefault();
}
if (this.props.onChange) {
this.props.onChange(-1);
} else {
let params = { ...this.props.params };
delete params.cid;
this.props.onAction({
type: "UPDATE_PARAMS",
params,
redirect: true,
});
}
// this.setState({ visible: false, index: 0 });
// this.setWindowState();
};
_handleNext = (e) => {
if (e) {
e.stopPropagation();
}
let index = this.state.index + 1;
if (index >= this.props.objects.length) {
return;
if (this.props.onChange) {
let index = this.props.index + 1;
if (index >= this.props.objects.length) return;
this.props.onChange(index);
} else {
let index = this.findSelectedIndex() + 1;
if (index >= this.props.objects.length) {
return;
}
let cid = this.props.objects[index].cid;
// this.setState({ index });
this.props.onAction({
type: "UPDATE_PARAMS",
params: { ...this.props.params, cid },
redirect: true,
});
}
this.setState({ index });
const data = this.props.objects[index];
this.setWindowState(data);
// const data = this.props.objects[index];
// this.setWindowState(data);
};
//it uses the initial cid to set which index it is, then it goes off its internal index from there and sets apge still but doesn't get from it?
//though that
//maybe the initial open is triggered by page, combined with index?
//or mayube
_handlePrevious = (e) => {
if (e) {
e.stopPropagation();
}
let index = this.state.index - 1;
if (index < 0) {
return;
if (this.props.onChange) {
let index = this.props.index - 1;
if (index < 0) return;
this.props.onChange(index);
} else {
let index = this.findSelectedIndex() - 1;
if (index < 0) {
return;
}
let cid = this.props.objects[index].cid;
// this.setState({ index });
this.props.onAction({
type: "UPDATE_PARAMS",
params: { ...this.props.params, cid },
redirect: true,
});
}
this.setState({ index });
const data = this.props.objects[index];
this.setWindowState(data);
// const data = this.props.objects[index];
// this.setWindowState(data);
};
_handleToggleSidebar = (e) => {
@ -272,27 +306,33 @@ export class GlobalCarousel extends React.Component {
};
render() {
if (
!this.state.visible ||
!this.props.carouselType ||
this.state.index < 0 ||
this.state.index >= this.props.objects.length
) {
let index;
if (this.props.onChange) {
index = this.props.index;
} else {
index = this.findSelectedIndex();
}
if (!this.props.carouselType || index < 0 || index >= this.props.objects.length) {
return null;
}
let data = this.props.objects[this.state.index];
let { isMobile, isOwner } = this.props;
let file = this.props.objects[index];
if (!file) {
return null;
}
let { isMobile } = this.props;
let isRepost = false;
if (this.props.carouselType === "SLATE") {
isRepost = this.props.current?.ownerId !== data.ownerId;
isRepost = this.props.data?.ownerId !== file.ownerId;
}
let slide = <SlateMediaObject file={data} isMobile={isMobile} />;
let slide = <SlateMediaObject file={file} isMobile={isMobile} />;
return (
<div css={STYLES_ROOT}>
<Alert
viewer={this.props.viewer}
noWarning
id={isMobile ? "slate-mobile-alert" : null}
style={
@ -308,7 +348,7 @@ export class GlobalCarousel extends React.Component {
}
/>
<div css={STYLES_ROOT_CONTENT} style={this.props.style} onClick={this._handleClose}>
{this.state.index > 0 && (
{index > 0 && (
<span
css={STYLES_BOX}
onClick={this._handlePrevious}
@ -317,7 +357,7 @@ export class GlobalCarousel extends React.Component {
<SVG.ChevronLeft height="20px" />
</span>
)}
{this.state.index < this.props.objects.length - 1 && (
{index < this.props.objects.length - 1 && (
<span
css={STYLES_BOX}
onClick={this._handleNext}
@ -351,12 +391,13 @@ export class GlobalCarousel extends React.Component {
</div>
<span css={STYLES_MOBILE_HIDDEN}>
<CarouselSidebar
key={data.id}
key={file.id}
{...this.props}
data={data}
file={file}
display={this.state.showSidebar ? "block" : "none"}
onClose={this._handleClose}
isRepost={isRepost}
onAction={this.props.onAction}
/>
</span>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -221,7 +221,7 @@ export const getById = async ({ id }) => {
pdfBytes,
},
tags,
userBucketCID: bucketRoot?.path,
userBucketCID: bucketRoot?.path || null,
keys,
slates,
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 webhook = new IncomingWebhook(url);
export const sendSlackMessage = ({
username,
name,
email,
twitter,
message,
stored,
}) => {
export const sendSlackMessage = ({ username, name, email, twitter, message, stored }) => {
if (Strings.isEmpty(Environment.SUPPORT_SLACK_WEBHOOK_KEY)) {
return false;
}
const userProfileURL = `https://slate.host/${username}`;
const userURL = `<${userProfileURL}|${username}>`;
let userURL;
if (username) {
const userProfileURL = `https://slate.host/${username}`;
userURL = `<${userProfileURL}|${username}>`;
} else {
userURL = "[Not authenticated]";
}
let twitterURL = "";
if (twitter) {
const twitterProfileURL = `https://twitter.com/${twitter.replace("@", "")}`;

4
package-lock.json generated
View File

@ -4556,7 +4556,11 @@
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
<<<<<<< HEAD
"deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.",
=======
"deprecated": "core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.",
>>>>>>> added standard typography styles
"hasInstallScript": true
},
"node_modules/core-js-compat": {

View File

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

View File

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

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(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(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) => {
const id = Utilities.getIdFromCookie(req);
console.log(id);
console.log(req);
if (!id) {
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);
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";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true });
}
const user = await Data.getUserById({
id,
});
if (!user) {
return res.status(404).send({
decorator: "SERVER_USER_NOT_FOUND",
error: true,
});
}
if (user.error) {
return res.status(500).send({
decorator: "SERVER_USER_NOT_FOUND",
error: true,
});
}
let slate;
if (req.body.data.id) {
slate = await Data.getSlateById({ id: req.body.data.id, includeFiles: true, sanitize: true });
@ -46,11 +23,15 @@ export default async (req, res) => {
});
}
if (!slate.isPublic && slate.ownerId !== id) {
return res.status(403).send({
decorator: "SERVER_GET_SERIALIZED_SLATE_PRIVATE_ACCESS_DENIED",
error: true,
});
if (!slate.isPublic) {
const id = Utilities.getIdFromCookie(req);
if (slate.ownerId !== id) {
return res.status(403).send({
decorator: "SERVER_GET_SERIALIZED_SLATE_PRIVATE_ACCESS_DENIED",
error: true,
});
}
}
let owner = await Data.getUserById({ id: slate.ownerId, sanitize: true });
@ -66,6 +47,6 @@ export default async (req, res) => {
return res.status(200).send({
decorator: "SERVER_GET_SERIALIZED_SLATE",
slate,
data: slate,
});
};

View File

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

View File

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

View File

@ -3,10 +3,6 @@ import * as Strings from "~/common/strings";
import * as Utilities from "~/node_common/utilities";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
return res.status(401).send({ decorator: "SERVER_NOT_AUTHENTICATED", error: true });
}
let user;
if (req.body.data.id) {

View File

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

View File

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

View File

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

View File

@ -56,7 +56,6 @@ export default class SceneEditAccount extends React.Component {
changingAvatar: false,
savingNameBio: false,
changingFilecoin: false,
tab: 0,
modalShow: false,
};
@ -107,7 +106,7 @@ export default class SceneEditAccount extends React.Component {
}
let data = { ...this.props.viewer.data, body: this.state.body, name: this.state.name };
this.props.onUpdateViewer({ username: this.state.username, data });
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { username: this.state.username, data } });
this.setState({ savingNameBio: true });
let response = await Actions.updateViewer({
@ -160,12 +159,12 @@ export default class SceneEditAccount extends React.Component {
}
this.setState({ deleting: true });
this.setState({ modalShow: false });
await Window.delay(100);
await UserBehaviors.deleteMe({ viewer: this.props.viewer });
window.location.replace("/_/auth");
this.setState({ deleting: false });
};
_handleChange = (e) => {
@ -173,16 +172,22 @@ export default class SceneEditAccount extends React.Component {
};
render() {
let tab = this.props.page.params?.tab || "profile";
return (
<ScenePage>
<ScenePageHeader title="Settings" />
<SecondaryTabGroup
tabs={["Profile", "Data Storage", "Security", "Account"]}
value={this.state.tab}
onChange={(value) => this.setState({ tab: value })}
tabs={[
{ title: "Profile", value: { tab: "profile" } },
{ title: "Data Storage", value: { tab: "storage" } },
{ title: "Security", value: { tab: "security" } },
{ title: "Account", value: { tab: "account" } },
]}
value={tab}
onAction={this.props.onAction}
style={{ marginBottom: 48 }}
/>
{this.state.tab === 0 ? (
{tab === "profile" ? (
<div>
<div css={STYLES_HEADER}>Your Avatar</div>
@ -227,7 +232,7 @@ export default class SceneEditAccount extends React.Component {
</div>
</div>
) : null}
{this.state.tab === 1 ? (
{tab === "storage" ? (
<div style={{ maxWidth: 800 }}>
<div css={STYLES_HEADER}>
Allow Slate to make Filecoin archive storage deals on your behalf
@ -278,7 +283,7 @@ export default class SceneEditAccount extends React.Component {
</div>
</div>
) : null}
{this.state.tab === 2 ? (
{tab === "security" ? (
<div>
<div css={STYLES_HEADER}>Change password</div>
<div>Passwords must be a minimum of eight characters.</div>
@ -311,7 +316,7 @@ export default class SceneEditAccount extends React.Component {
</div>
</div>
) : null}
{this.state.tab === 3 ? (
{tab === "account" ? (
<div>
<div css={STYLES_HEADER}>Change username</div>
<div style={{ maxWidth: 800 }}>
@ -359,19 +364,18 @@ export default class SceneEditAccount extends React.Component {
tabIndex="-1"
css={STYLES_COPY_INPUT}
/>{" "}
{this.state.modalShow && (
<ConfirmationModal
type={"DELETE"}
withValidation={true}
matchValue={this.state.username}
callback={this._handleDelete}
header={`Are you sure you want to delete your account @${this.state.username}?`}
subHeader={`You will lose all your files and collections. You cant undo this action.`}
inputHeader={`Please type your username to confirm`}
inputPlaceholder={`username`}
/>
)}
<ConfirmationModal
type={"DELETE"}
withValidation={true}
matchValue={this.state.username}
callback={this._handleDelete}
header={`Are you sure you want to delete your account @${this.state.username}?`}
subHeader={`You will lose all your files and collections. You cant undo this action.`}
inputHeader={`Please type your username to confirm`}
inputPlaceholder={`username`}
/>
)}
</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 {
state = {
view: 0,
filterTooltip: false,
fileTypes: {
image: false,
@ -128,9 +127,9 @@ export default class SceneFilesFolder extends React.Component {
keyboardTooltip: false,
};
componentDidMount = () => {
this.openCarouselToItem();
};
// componentDidMount = () => {
// this.openCarouselToItem();
// };
componentDidUpdate = (prevProps, prevState) => {
if (prevProps.viewer.library !== this.props.viewer.library) {
@ -138,9 +137,9 @@ export default class SceneFilesFolder extends React.Component {
this._filterFiles();
}
}
if (prevProps.page !== this.props.page) {
this.openCarouselToItem();
}
// if (prevProps.page.params !== this.props.page.params) {
// this.openCarouselToItem();
// }
};
_handleFilterTooltip = () => {
@ -206,87 +205,50 @@ export default class SceneFilesFolder extends React.Component {
});
};
openCarouselToItem = () => {
let index = -1;
let page = this.props.page;
if (page?.fileId || page?.cid || page?.index) {
if (page?.index) {
index = page.index;
} else {
let library = this.props.viewer.library || [];
for (let i = 0; i < library.length; i++) {
let obj = library[i];
if ((obj.cid && obj.cid === page?.cid) || (obj.id && obj.id === page?.fileId)) {
index = i;
break;
}
}
}
}
// openCarouselToItem = () => {
// if (!this.props.page?.params || !this.props.viewer.library?.length) {
// return;
// }
// let index = -1;
// let params = this.props.page.params;
// if (params?.fileId || params?.cid || params?.index) {
// if (params?.index) {
// index = params.index;
// } else {
// let library = this.props.viewer.library || [];
// for (let i = 0; i < library.length; i++) {
// let obj = library[i];
// if ((obj.cid && obj.cid === params?.cid) || (obj.id && obj.id === params?.fileId)) {
// index = i;
// break;
// }
// }
// }
// }
if (index !== -1) {
Events.dispatchCustomEvent({
name: "slate-global-open-carousel",
detail: { index },
});
}
};
// if (index !== -1) {
// Events.dispatchCustomEvent({
// name: "slate-global-open-carousel",
// detail: { index },
// });
// }
// };
render() {
let files = this.state.filtersActive ? this.state.filteredFiles : this.props.viewer?.library;
files = files || [];
const tab = this.props.page.params?.tab || "grid";
return (
<ScenePage>
<ScenePageHeader
title={
this.props.isMobile ? (
<TabGroup
tabs={[
{ title: "Files", value: "NAV_DATA" },
{ title: "Collections", value: "NAV_SLATES" },
{ title: "Activity", value: "NAV_ACTIVITY" },
]}
value={0}
onAction={this.props.onAction}
onChange={(value) => this.setState({ tab: value })}
style={{ marginTop: 0, marginBottom: 32 }}
itemStyle={{ margin: "0px 12px" }}
/>
) : (
<PrimaryTabGroup
tabs={[
{ title: "Files", value: "NAV_DATA" },
{ title: "Collections", value: "NAV_SLATES" },
{ title: "Activity", value: "NAV_ACTIVITY" },
]}
value={0}
onAction={this.props.onAction}
/>
)
}
actions={
this.props.isMobile ? null : (
<SecondaryTabGroup
tabs={[
<SVG.GridView height="24px" style={{ display: "block" }} />,
<SVG.TableView height="24px" style={{ display: "block" }} />,
]}
value={this.state.view}
onChange={(value) => this.setState({ view: value })}
style={{ margin: "0 0 24px 0" }}
/>
)
}
/>
<GlobalCarousel
carouselType="DATA"
onUpdateViewer={this.props.onUpdateViewer}
resources={this.props.resources}
viewer={this.props.viewer}
objects={files}
onAction={this.props.onAction}
isMobile={this.props.isMobile}
params={this.props.page.params}
isOwner={true}
/>
<DataMeter
@ -307,6 +269,21 @@ export default class SceneFilesFolder extends React.Component {
}
/>
<div css={STYLES_CONTAINER_WRAPPER}>
<SecondaryTabGroup
tabs={[
{
title: <SVG.GridView height="24px" style={{ display: "block" }} />,
value: { tab: "grid" },
},
{
title: <SVG.TableView height="24px" style={{ display: "block" }} />,
value: { tab: "table" },
},
]}
value={tab}
onAction={this.props.onAction}
style={{ margin: "0 0 24px 0" }}
/>
<div css={STYLES_CONTAINER}>
<div
css={STYLES_BUTTONS_ROW}
@ -486,10 +463,10 @@ export default class SceneFilesFolder extends React.Component {
onAction={this.props.onAction}
viewer={this.props.viewer}
items={files}
onUpdateViewer={this.props.onUpdateViewer}
view={this.state.view}
view={tab}
resources={this.props.resources}
isOwner={true}
page={this.props.page}
/>
) : (
<EmptyState>

View File

@ -13,6 +13,7 @@ import { css } from "@emotion/react";
import { createState } from "~/scenes/SceneSettings";
import { LoaderSpinner } from "~/components/system/components/Loaders";
import { FilecoinNumber } from "@glif/filecoin-number";
import { Link } from "~/components/core/Link";
import Section from "~/components/core/Section";
import ScenePage from "~/components/core/ScenePage";
@ -206,7 +207,7 @@ export default class SceneMakeFilecoinDeal extends React.Component {
"Your storage deal was put in the queue. This can take up to 36 hours, check back later."
);
this.props.onAction({ type: "NAVIGATE", value: "NAV_FILECOIN" });
this.props.onAction({ type: "NAVIGATE", href: "/_/filecoin" });
};
_handleRemove = async (cid) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

481
server.js
View File

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