mobile refactor

This commit is contained in:
Martina 2020-09-30 17:41:53 -07:00
parent 9e12cb402a
commit bf079ffc8a
23 changed files with 580 additions and 283 deletions

View File

@ -51,6 +51,12 @@ const REJECT_LIST = [
"data", "data",
]; ];
export const onMobile = () => {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
};
export const userRoute = (text) => { export const userRoute = (text) => {
if (!USERNAME_REGEX.test(text)) { if (!USERNAME_REGEX.test(text)) {
return false; return false;

View File

@ -19,13 +19,14 @@ const STYLES_ALERT = `
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
transition: top 0.25s;
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
top: 56px;
left: 0px; left: 0px;
right: 0px; right: 0px;
width: 100%;
} }
`; `;
@ -139,7 +140,7 @@ export class Alert extends React.Component {
// NOTE(jim): Replaces the filecoin banner on some navigation pages. // NOTE(jim): Replaces the filecoin banner on some navigation pages.
if (this.props.filecoin) { if (this.props.filecoin) {
return ( return (
<div css={STYLES_MESSAGE}> <div css={STYLES_MESSAGE} style={this.props.style}>
<div css={STYLES_MESSAGE_BOX} style={{ fontSize: 14 }}> <div css={STYLES_MESSAGE_BOX} style={{ fontSize: 14 }}>
You are on the Filecoin Testnet. Test FIL may take a moment to You are on the Filecoin Testnet. Test FIL may take a moment to
reach your wallet. reach your wallet.
@ -149,7 +150,7 @@ export class Alert extends React.Component {
} }
return ( return (
<div css={STYLES_MESSAGE}> <div css={STYLES_MESSAGE} style={this.props.style}>
<div css={STYLES_MESSAGE_BOX} style={{ fontSize: 14 }}> <div css={STYLES_MESSAGE_BOX} style={{ fontSize: 14 }}>
Please don't upload sensitive information to Slate yet. Private Please don't upload sensitive information to Slate yet. Private
storage is coming soon. storage is coming soon.
@ -165,7 +166,7 @@ export class Alert extends React.Component {
return ( return (
<div <div
css={STYLES_INFO} css={STYLES_INFO}
style={{ cursor: "pointer" }} style={{ cursor: "pointer", ...this.props.style }}
onClick={() => onClick={() =>
this.props.onAction({ this.props.onAction({
type: "SIDEBAR", type: "SIDEBAR",

View File

@ -700,6 +700,7 @@ export default class ApplicationPage extends React.Component {
<SceneSignIn <SceneSignIn
onCreateUser={this._handleCreateUser} onCreateUser={this._handleCreateUser}
onAuthenticate={this._handleAuthenticate} onAuthenticate={this._handleAuthenticate}
onAction={this._handleAction}
/> />
</WebsitePrototypeWrapper> </WebsitePrototypeWrapper>
); );

View File

@ -160,6 +160,8 @@ export default class ApplicationHeader extends React.Component {
style={{ transform: `rotate(180deg)` }} style={{ transform: `rotate(180deg)` }}
/> />
</span> </span>
</span>
<span css={STYLES_MOBILE_HIDDEN}>
<span <span
css={STYLES_ICON_ELEMENT} css={STYLES_ICON_ELEMENT}
style={ style={
@ -171,7 +173,8 @@ export default class ApplicationHeader extends React.Component {
> >
<SVG.NavigationArrow height="24px" /> <SVG.NavigationArrow height="24px" />
</span> </span>
</span>
<span css={STYLES_MOBILE_HIDDEN}>
<span <span
css={this.state.isRefreshing ? STYLES_ROTATION : STYLES_STATIC} css={this.state.isRefreshing ? STYLES_ROTATION : STYLES_STATIC}
style={{ marginLeft: 24 }} style={{ marginLeft: 24 }}
@ -180,7 +183,8 @@ export default class ApplicationHeader extends React.Component {
<SVG.Refresh height="20px" /> <SVG.Refresh height="20px" />
</span> </span>
</span> </span>
</span>
<span css={STYLES_MOBILE_HIDDEN}>
<span <span
css={STYLES_ICON_ELEMENT} css={STYLES_ICON_ELEMENT}
style={{ marginLeft: 24 }} style={{ marginLeft: 24 }}

View File

@ -1,6 +1,7 @@
import * as React from "react"; import * as React from "react";
import * as Constants from "~/common/constants"; import * as Constants from "~/common/constants";
import * as SVG from "~/common/svg"; import * as SVG from "~/common/svg";
import * as Validations from "~/common/validations";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { GlobalTooltip } from "~/components/system/components/fragments/GlobalTooltip"; import { GlobalTooltip } from "~/components/system/components/fragments/GlobalTooltip";
@ -51,6 +52,7 @@ const STYLES_HEADER = css`
left: ${Constants.sizes.navigation}px; left: ${Constants.sizes.navigation}px;
right: 0; right: 0;
top: 0; top: 0;
transition: top 0.25s;
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
left: 0; left: 0;
} }
@ -163,11 +165,59 @@ const STYLES_BLOCK = css`
color: rgba(0, 0, 0, 0.25); color: rgba(0, 0, 0, 0.25);
`; `;
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 ApplicationLayout extends React.Component { export default class ApplicationLayout extends React.Component {
_sidebar; _sidebar;
_navigation; _navigation;
_body; _body;
state = {
alertTop: 56,
};
componentDidMount = () => {
this.prevScrollPos = window.pageYOffset;
if (Validations.onMobile()) {
window.addEventListener("scroll", this._handleScroll);
}
};
componentWillUnmount = () => {
if (Validations.onMobile()) {
window.removeEventListener("scroll", this._handleScroll);
}
};
_handleScroll = () => {
console.log(document.getElementById("slate-mobile-alert"));
let currentScrollPos = window.pageYOffset;
if (this.prevScrollPos > currentScrollPos) {
if (document.getElementById("slate-mobile-header")) {
document.getElementById("slate-mobile-header").style.top = "0px";
}
this.setState({ alertTop: 56 });
} else {
if (currentScrollPos > 56) {
if (document.getElementById("slate-mobile-header")) {
document.getElementById("slate-mobile-header").style.top = "-56px";
}
this.setState({ alertTop: 0 });
}
}
this.prevScrollPos = currentScrollPos;
};
_handleDismiss = (e) => { _handleDismiss = (e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -184,11 +234,7 @@ export default class ApplicationLayout extends React.Component {
allowedTypes={["sidebar"]} allowedTypes={["sidebar"]}
/> />
<div css={STYLES_SIDEBAR_HEADER}> <div css={STYLES_SIDEBAR_HEADER}>
<div <div css={STYLES_BLOCK} onClick={this._handleDismiss}>
css={STYLES_BLOCK}
onMouseUp={this._handleDismiss}
onTouchEnd={this._handleDismiss}
>
<SVG.Dismiss height="24px" /> <SVG.Dismiss height="24px" />
</div> </div>
</div> </div>
@ -212,8 +258,16 @@ export default class ApplicationLayout extends React.Component {
</div> </div>
<div css={STYLES_CONTENT}> <div css={STYLES_CONTENT}>
<GlobalTooltip elementRef={this._body} allowedTypes={["body"]} /> {/* <GlobalTooltip elementRef={this._body} allowedTypes={["body"]} /> */}
<div css={STYLES_HEADER}>{this.props.header}</div> <GlobalTooltip allowedTypes={["body"]} />
<span css={STYLES_MOBILE_HIDDEN}>
<div css={STYLES_HEADER}>{this.props.header}</div>
</span>
<span css={STYLES_MOBILE_ONLY}>
<div css={STYLES_HEADER} id="slate-mobile-header">
{this.props.header}
</div>
</span>
<div <div
css={STYLES_BODY_WEB} css={STYLES_BODY_WEB}
ref={(c) => { ref={(c) => {
@ -235,14 +289,10 @@ export default class ApplicationLayout extends React.Component {
</div> </div>
<div css={STYLES_BODY_MOBILE}> <div css={STYLES_BODY_MOBILE}>
<Alert <Alert
id="slate-mobile-alert"
fileLoading={this.props.fileLoading} fileLoading={this.props.fileLoading}
onAction={this.props.onAction} onAction={this.props.onAction}
style={{ style={{ top: this.state.alertTop }}
top: 0,
left: 0,
width: "100%",
zIndex: Constants.zindex.modal,
}}
/> />
{this.props.children} {this.props.children}
</div> </div>

View File

@ -124,8 +124,7 @@ const Item = (props) => {
> >
<span <span
css={STYLES_EXPANDER} css={STYLES_EXPANDER}
onMouseUp={props.onToggleShow ? props.onToggleShow : null} onClick={props.onToggleShow ? props.onToggleShow : null}
onTouchEnd={props.onToggleShow ? props.onToggleShow : null}
> >
<span <span
css={STYLES_ICON_ELEMENT} css={STYLES_ICON_ELEMENT}
@ -143,14 +142,7 @@ const Item = (props) => {
</span> </span>
<span <span
css={STYLES_ICON} css={STYLES_ICON}
onMouseUp={() => onClick={() =>
props.onAction({
type: "NAVIGATE",
value: props.id,
data: props.data,
})
}
onTouchEnd={() =>
props.onAction({ props.onAction({
type: "NAVIGATE", type: "NAVIGATE",
value: props.id, value: props.id,
@ -169,14 +161,7 @@ const Item = (props) => {
<span <span
css={STYLES_CHILDREN} css={STYLES_CHILDREN}
children={props.children} children={props.children}
onMouseUp={() => onClick={() =>
props.onAction({
type: "NAVIGATE",
value: props.id,
data: props.data,
})
}
onTouchEnd={() =>
props.onAction({ props.onAction({
type: "NAVIGATE", type: "NAVIGATE",
value: props.id, value: props.id,
@ -267,6 +252,7 @@ export default class ApplicationNavigation extends React.Component {
return ( return (
<div <div
key={each.id}
css={ css={
each.id === "V1_NAVIGATION_ARCHIVE" || each.id === "V1_NAVIGATION_ARCHIVE" ||
each.id === "V1_NAVIGATION_API" each.id === "V1_NAVIGATION_API"

View File

@ -15,6 +15,10 @@ const STYLES_CONTAINER = css`
padding: 32px; padding: 32px;
max-width: 100%; max-width: 100%;
width: 100%; width: 100%;
@media (max-width: ${Constants.sizes.mobile}px) {
padding: 24px;
}
`; `;
const STYLES_DATA = css` const STYLES_DATA = css`

View File

@ -184,6 +184,12 @@ const STYLES_IMAGE_GRID = css`
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(214px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(214px, 1fr));
margin: 0 -27px; margin: 0 -27px;
@media (max-width: ${Constants.sizes.mobile}px) {
display: grid;
grid-template-columns: 1fr 1fr;
margin: 0px -12px;
}
`; `;
const STYLES_IMAGE_BOX = css` const STYLES_IMAGE_BOX = css`
@ -197,6 +203,18 @@ const STYLES_IMAGE_BOX = css`
0 0 40px 0 ${Constants.system.shadow}; 0 0 40px 0 ${Constants.system.shadow};
cursor: pointer; cursor: pointer;
position: relative; position: relative;
@media (max-width: ${Constants.sizes.mobile}px) {
width: 144px;
height: 144px;
margin: 12px auto;
}
`;
const STYLES_MOBILE_HIDDEN = css`
@media (max-width: ${Constants.sizes.mobile}px) {
display: none;
}
`; `;
const delay = (ms) => new Promise((resolve) => window.setTimeout(resolve, ms)); const delay = (ms) => new Promise((resolve) => window.setTimeout(resolve, ms));
@ -601,7 +619,7 @@ export default class DataView extends React.Component {
const header = ( const header = (
<div css={STYLES_HEADER_LINE}> <div css={STYLES_HEADER_LINE}>
<TabGroup disabled tabs={["Uploads"]} style={{ margin: 0 }} /> <TabGroup disabled tabs={["Uploads"]} style={{ margin: 0 }} />
<React.Fragment> <span css={STYLES_MOBILE_HIDDEN}>
<div <div
css={STYLES_ICON_BOX} css={STYLES_ICON_BOX}
onClick={() => { onClick={() => {
@ -634,7 +652,7 @@ export default class DataView extends React.Component {
height="24px" height="24px"
/> />
</div> </div>
</React.Fragment> </span>
</div> </div>
); );
const footer = ( const footer = (
@ -738,104 +756,112 @@ export default class DataView extends React.Component {
title={each.file || each.name} title={each.file || each.name}
type={each.type || each.icon} type={each.type || each.icon}
/> />
{numChecked || <span css={STYLES_MOBILE_HIDDEN}>
this.state.hover === i || {numChecked ||
this.state.menu === each.id ? ( this.state.hover === i ||
<React.Fragment> this.state.menu === each.id ? (
<div <React.Fragment>
css={STYLES_ICON_BOX_BACKGROUND} <div
onClick={ css={STYLES_ICON_BOX_BACKGROUND}
this.state.loading[cid] onClick={
? () => {} this.state.loading[cid]
: (e) => { ? () => {}
e.stopPropagation(); : (e) => {
this.setState({ e.stopPropagation();
menu: this.setState({
this.state.menu === each.id menu:
? null this.state.menu === each.id
: each.id, ? null
}); : each.id,
} });
} }
> }
{this.state.loading[cid] ? ( >
<LoaderSpinner style={{ height: 24, width: 24 }} /> {this.state.loading[cid] ? (
) : ( <LoaderSpinner
<SVG.MoreHorizontal height="24px" /> style={{ height: 24, width: 24 }}
)}
{this.state.menu === each.id ? (
<Boundary
captureResize={true}
captureScroll={false}
enabled
onOutsideRectEvent={this._handleHide}
>
<PopoverNavigation
style={{
top: "32px",
right: "0px",
}}
navigation={[
{
text: "Copy CID",
onClick: (e) => this._handleCopy(e, cid),
},
{
text: "Copy link",
onClick: (e) =>
this._handleCopy(
e,
`${Constants.gateways.ipfs}/${cid}`
),
},
{
text: "Delete",
onClick: (e) => {
e.stopPropagation();
this.setState({ menu: null }, () =>
this._handleDelete(cid)
);
},
},
]}
/> />
</Boundary> ) : (
) : null} <SVG.MoreHorizontal height="24px" />
</div> )}
<div
onClick={(e) => { {this.state.menu === each.id ? (
e.stopPropagation(); <Boundary
e.preventDefault(); captureResize={true}
let checked = this.state.checked; captureScroll={false}
if (checked[this.state.startIndex + i]) { enabled
delete checked[this.state.startIndex + i]; onOutsideRectEvent={this._handleHide}
} else { >
checked[this.state.startIndex + i] = true; <PopoverNavigation
} style={{
this.setState({ checked }); top: "32px",
}} right: "0px",
> }}
<CheckBox navigation={[
name={this.state.startIndex + i} {
value={ text: "Copy CID",
!!this.state.checked[this.state.startIndex + i] onClick: (e) => this._handleCopy(e, cid),
} },
onChange={this._handleCheckBox} {
boxStyle={{ text: "Copy link",
height: 24, onClick: (e) =>
width: 24, this._handleCopy(
backgroundColor: this.state.checked[ e,
this.state.startIndex + i `${Constants.gateways.ipfs}/${cid}`
] ),
? Constants.system.brand },
: "rgba(255, 255, 255, 0.75)", {
text: "Delete",
onClick: (e) => {
e.stopPropagation();
this.setState({ menu: null }, () =>
this._handleDelete(cid)
);
},
},
]}
/>
</Boundary>
) : null}
</div>
<div
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
let checked = this.state.checked;
if (checked[this.state.startIndex + i]) {
delete checked[this.state.startIndex + i];
} else {
checked[this.state.startIndex + i] = true;
}
this.setState({ checked });
}} }}
style={{ position: "absolute", bottom: 8, left: 8 }} >
/> <CheckBox
</div> name={this.state.startIndex + i}
</React.Fragment> value={
) : null} !!this.state.checked[this.state.startIndex + i]
}
onChange={this._handleCheckBox}
boxStyle={{
height: 24,
width: 24,
backgroundColor: this.state.checked[
this.state.startIndex + i
]
? Constants.system.brand
: "rgba(255, 255, 255, 0.75)",
}}
style={{
position: "absolute",
bottom: 8,
left: 8,
}}
/>
</div>
</React.Fragment>
) : null}
</span>
</div> </div>
); );
})} })}

View File

@ -36,6 +36,10 @@ const STYLES_HEADER = css`
max-width: 688px; max-width: 688px;
white-space: pre-wrap; white-space: pre-wrap;
overflow-wrap: break-word; overflow-wrap: break-word;
@media (max-width: ${Constants.sizes.mobile}px) {
font-size: ${Constants.typescale.lvl3};
}
`; `;
const STYLES_DESCRIPTION = css` const STYLES_DESCRIPTION = css`

View File

@ -8,7 +8,6 @@ import { LoaderSpinner } from "~/components/system/components/Loaders";
const STYLES_DROPDOWN_CONTAINER = css` const STYLES_DROPDOWN_CONTAINER = css`
box-sizing: border-box; box-sizing: border-box;
z-index: ${Constants.zindex.modal}; z-index: ${Constants.zindex.modal};
height: 100%;
`; `;
const STYLES_DROPDOWN = css` const STYLES_DROPDOWN = css`
@ -21,10 +20,16 @@ const STYLES_DROPDOWN = css`
width: 100%; width: 100%;
scrollbar-width: none; scrollbar-width: none;
padding-bottom: 24px; padding-bottom: 24px;
height: calc(100% - 16px);
overflow-y: scroll;
::-webkit-scrollbar { ::-webkit-scrollbar {
display: none; display: none;
} }
@media (max-width: ${Constants.sizes.mobile}px) {
height: calc(100% - 36px);
}
`; `;
const STYLES_DROPDOWN_ITEM = css` const STYLES_DROPDOWN_ITEM = css`

View File

@ -30,6 +30,11 @@ const STYLES_CONTAINER = css`
border-radius: 4px; border-radius: 4px;
background-color: ${Constants.system.white}; background-color: ${Constants.system.white};
box-shadow: 0 0 60px 8px rgba(0, 0, 0, 0.03); box-shadow: 0 0 60px 8px rgba(0, 0, 0, 0.03);
@media (max-width: ${Constants.sizes.mobile}px) {
max-width: 95vw;
padding: 20px;
}
`; `;
const STYLES_MODAL = css` const STYLES_MODAL = css`
@ -42,8 +47,8 @@ const STYLES_MODAL = css`
`; `;
const STYLES_SEARCH_DROPDOWN = { const STYLES_SEARCH_DROPDOWN = {
height: "calc(100% - 16px)", // height: "calc(100% - 16px)",
overflowY: "scroll", // overflowY: "scroll",
}; };
const STYLES_USER_ENTRY_CONTAINER = css` const STYLES_USER_ENTRY_CONTAINER = css`

View File

@ -2,6 +2,7 @@ import * as React from "react";
import * as Constants from "~/common/constants"; import * as Constants from "~/common/constants";
import * as SVG from "~/common/svg"; import * as SVG from "~/common/svg";
import * as Strings from "~/common/strings"; import * as Strings from "~/common/strings";
import * as Validations from "~/common/validations";
import * as Actions from "~/common/actions"; import * as Actions from "~/common/actions";
import { Responsive, WidthProvider } from "react-grid-layout"; import { Responsive, WidthProvider } from "react-grid-layout";
@ -88,6 +89,12 @@ const STYLES_ACTION_BUTTON = css`
} }
`; `;
const STYLES_MOBILE_HIDDEN = css`
@media (max-width: ${Constants.sizes.mobile}px) {
display: none;
}
`;
const ResponsiveReactGridLayout = WidthProvider(Responsive); const ResponsiveReactGridLayout = WidthProvider(Responsive);
const COLUMN_MAP = { lg: 12, md: 8, sm: 6, xs: 4, xxs: 2 }; const COLUMN_MAP = { lg: 12, md: 8, sm: 6, xs: 4, xxs: 2 };
@ -178,32 +185,42 @@ export default class Slate extends React.Component {
} }
return ( return (
<div key={index} css={STYLES_ITEM}> <div
key={index}
css={STYLES_ITEM}
onClick={
Validations.onMobile()
? (e) => this._handleSelect(e, index)
: () => {}
}
>
<SlateMediaObjectPreview <SlateMediaObjectPreview
charCap={70} charCap={70}
type={data.type} type={data.type}
url={data.url} url={data.url}
title={data.title || data.name} title={data.title || data.name}
/> />
<figure css={STYLES_BUTTON}> <span css={STYLES_MOBILE_HIDDEN}>
<CircleButtonGray <figure css={STYLES_BUTTON}>
style={{ margin: 8 }}
onMouseUp={(e) => this._handleSelect(e, index)}
onTouchEnd={(e) => this._handleSelect(e, index)}
>
<SVG.Eye height="16px" />
</CircleButtonGray>
{data.deeplink ? (
<CircleButtonGray <CircleButtonGray
style={{ margin: 8 }} style={{ margin: 8, cursor: "pointer" }}
onMouseUp={(e) => this._handleDeepLink(e, data)} onMouseUp={(e) => this._handleSelect(e, index)}
onTouchEnd={(e) => this._handleDeepLink(e, data)} onTouchEnd={(e) => this._handleSelect(e, index)}
> >
<SVG.DeepLink height="16px" /> <SVG.Eye height="16px" />
</CircleButtonGray> </CircleButtonGray>
) : null}
</figure> {data.deeplink ? (
<CircleButtonGray
style={{ margin: 8 }}
onMouseUp={(e) => this._handleDeepLink(e, data)}
onTouchEnd={(e) => this._handleDeepLink(e, data)}
>
<SVG.DeepLink height="16px" />
</CircleButtonGray>
) : null}
</figure>
</span>
</div> </div>
); );
}); });
@ -245,8 +262,7 @@ export default class Slate extends React.Component {
</span> </span>
<span <span
css={STYLES_ACTION_BUTTON} css={STYLES_ACTION_BUTTON}
onMouseUp={this._handleSaveLayout} onClick={this._handleSaveLayout}
onTouchEnd={this._handleSaveLayout}
style={{ style={{
backgroundColor: backgroundColor:
this.props.saving === "IDLE" ? Constants.system.brand : null, this.props.saving === "IDLE" ? Constants.system.brand : null,

View File

@ -5,6 +5,14 @@ import * as SVG from "~/common/svg";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { FileTypeIcon } from "~/components/core/FileTypeIcon"; import { FileTypeIcon } from "~/components/core/FileTypeIcon";
const STYLES_IMAGE_CONTAINER = css`
background-color: ${Constants.system.foreground};
width: 100%;
height: 100%;
background-size: cover;
background-position: 50% 50%;
`;
const STYLES_IMAGE = css` const STYLES_IMAGE = css`
background-color: ${Constants.system.foreground}; background-color: ${Constants.system.foreground};
display: block; display: block;
@ -53,6 +61,14 @@ export default class SlateMediaObjectPreview extends React.Component {
: this.props.title; : this.props.title;
if (this.props.type && this.props.type.startsWith("image/")) { if (this.props.type && this.props.type.startsWith("image/")) {
if (this.props.centeredImage) {
return (
<div
css={STYLES_IMAGE_CONTAINER}
style={{ backgroundImage: `url(${url})`, ...this.props.imageStyle }}
/>
);
}
return <img css={STYLES_IMAGE} style={this.props.imageStyle} src={url} />; return <img css={STYLES_IMAGE} style={this.props.imageStyle} src={url} />;
} }

View File

@ -323,8 +323,7 @@ export default class SlateMediaObjectSidebar extends React.Component {
<span <span
key="sidebar-media-object-delete" key="sidebar-media-object-delete"
css={STYLES_BUTTON} css={STYLES_BUTTON}
onMouseUp={() => this.props.onDelete(this.props.id)} onClick={() => this.props.onDelete(this.props.id)}
onTouchEnd={() => this.props.onDelete(this.props.id)}
> >
{this.props.loading ? ( {this.props.loading ? (
<LoaderSpinner style={{ height: 16, width: 16 }} /> <LoaderSpinner style={{ height: 16, width: 16 }} />

View File

@ -11,6 +11,18 @@ import { dispatchCustomEvent } from "~/common/custom-events";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
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;
}
`;
const STYLES_CREATE_NEW = css` const STYLES_CREATE_NEW = css`
color: ${Constants.system.darkGray}; color: ${Constants.system.darkGray};
box-shadow: 0px 0px 0px 1px rgba(229, 229, 229, 0.5) inset; box-shadow: 0px 0px 0px 1px rgba(229, 229, 229, 0.5) inset;
@ -23,7 +35,10 @@ const STYLES_CREATE_NEW = css`
margin: 0px 12px; margin: 0px 12px;
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
margin: 0 8px; margin: 0;
border-radius: 8;
width: 100%;
height: 100%;
} }
`; `;
@ -161,6 +176,11 @@ const STYLES_BLOCK = css`
margin: 24px auto 48px auto; margin: 24px auto 48px auto;
max-width: ${Constants.sizes.desktop}px; max-width: ${Constants.sizes.desktop}px;
cursor: pointer; cursor: pointer;
@media (max-width: ${Constants.sizes.mobile}px) {
padding: 16px;
margin: 24px auto;
}
`; `;
const STYLES_TITLE_LINE = css` const STYLES_TITLE_LINE = css`
@ -170,6 +190,10 @@ const STYLES_TITLE_LINE = css`
font-size: ${Constants.typescale.lvl1}; font-size: ${Constants.typescale.lvl1};
margin-bottom: 16px; margin-bottom: 16px;
overflow-wrap: break-word; overflow-wrap: break-word;
@media (max-width: ${Constants.sizes.mobile}px) {
display: none;
}
`; `;
const STYLES_COPY_INPUT = css` const STYLES_COPY_INPUT = css`
@ -195,6 +219,10 @@ const STYLES_BODY = css`
line-height: 20px; line-height: 20px;
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
@media (max-width: ${Constants.sizes.mobile}px) {
display: none;
}
`; `;
const STYLES_ICON_BOX = css` const STYLES_ICON_BOX = css`
@ -227,6 +255,20 @@ export default class SlatePreviewBlock extends React.Component {
state = { state = {
showMenu: false, showMenu: false,
copyValue: "", copyValue: "",
windowWidth: 360,
};
componentDidMount = () => {
this.calculateWidth();
window.addEventListener("resize", this.calculateWidth);
};
componentWillUnmount = () => {
window.removeEventListener("resize", this.calculateWidth);
};
calculateWidth = () => {
this.setState({ windowWidth: window.innerWidth });
}; };
_handleCopy = (e, value) => { _handleCopy = (e, value) => {
@ -267,6 +309,9 @@ export default class SlatePreviewBlock extends React.Component {
if (!this.props.editing && !this.props.slate.data.objects.length) { if (!this.props.editing && !this.props.slate.data.objects.length) {
return null; return null;
} }
let first = this.props.slate.data.objects
? this.props.slate.data.objects[0]
: null;
let contextMenu = ( let contextMenu = (
<React.Fragment> <React.Fragment>
<Boundary <Boundary
@ -387,10 +432,47 @@ export default class SlatePreviewBlock extends React.Component {
) : ( ) : (
<div style={{ height: "8px" }} /> <div style={{ height: "8px" }} />
)} )}
<SlatePreviewRow <span css={STYLES_MOBILE_ONLY}>
{...this.props} <div
previewStyle={this.props.previewStyle} css={STYLES_TITLE}
/> style={{ marginBottom: 8, fontSize: Constants.typescale.lvl1 }}
>
{this.props.slate.data.name}
</div>
<div style={{ marginBottom: 16, fontSize: 12 }}>
{this.props.slate.data.objects.length} Object
{this.props.slate.data.objects.length === 1 ? "" : "s"}
</div>
<div
style={{
width: "100%",
height: `${this.state.windowWidth - 80}px`,
}}
>
{first ? (
<SlateMediaObjectPreview
centeredImage
charCap={30}
type={first.type}
url={first.url}
style={{ borderRadius: 8 }}
imageStyle={{ borderRadius: 8 }}
title={first.title || first.name}
/>
) : (
<div css={STYLES_CREATE_NEW} key="add-files">
<SVG.Plus height="24px" />
<div>Add Files</div>
</div>
)}
</div>
</span>
<span css={STYLES_MOBILE_HIDDEN}>
<SlatePreviewRow
{...this.props}
previewStyle={this.props.previewStyle}
/>
</span>
</div> </div>
); );
} }

View File

@ -11,6 +11,11 @@ const STYLES_TAB_GROUP = css`
flex-direction: row; flex-direction: row;
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
flex-wrap: wrap;
@media (max-width: ${Constants.sizes.mobile}px) {
margin: 24px 0px 24px 0px;
}
`; `;
const STYLES_TAB = css` const STYLES_TAB = css`
@ -23,6 +28,7 @@ const STYLES_TAB = css`
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
margin-right: 12px; margin-right: 12px;
font-size: ${Constants.typescale.lvl1};
} }
`; `;

View File

@ -87,6 +87,34 @@ const STYLES_EXPANDER = css`
} }
`; `;
const STYLES_DISMISS_BOX = css`
position: absolute;
top: 16px;
right: 16px;
color: ${Constants.system.darkGray};
cursor: pointer;
:hover {
color: ${Constants.system.black};
}
@media (min-width: ${Constants.sizes.mobile}px) {
display: none;
}
`;
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 class GlobalViewerCID extends React.Component { export class GlobalViewerCID extends React.Component {
state = { state = {
index: 0, index: 0,
@ -156,8 +184,11 @@ export class GlobalViewerCID extends React.Component {
}); });
}; };
_handleClose = () => _handleClose = (e) => {
e.stopPropagation();
e.preventDefault();
this.setState({ visible: false, index: 0, loading: false, saving: false }); this.setState({ visible: false, index: 0, loading: false, saving: false });
};
_handleCreate = (e) => { _handleCreate = (e) => {
const shouldPersist = const shouldPersist =
@ -202,44 +233,49 @@ export class GlobalViewerCID extends React.Component {
<div css={STYLES_ROOT_CONTENT} style={this.props.style}> <div css={STYLES_ROOT_CONTENT} style={this.props.style}>
<span <span
css={STYLES_BOX} css={STYLES_BOX}
onMouseUp={this._handlePrevious} onClick={this._handlePrevious}
onTouchEnd={this._handlePrevious}
style={{ top: 0, left: 16, bottom: 0 }} style={{ top: 0, left: 16, bottom: 0 }}
> >
<SVG.ChevronLeft height="20px" /> <SVG.ChevronLeft height="20px" />
</span> </span>
<span <span
css={STYLES_BOX} css={STYLES_BOX}
onMouseUp={this._handleNext} onClick={this._handleNext}
onTouchEnd={this._handleNext}
style={{ top: 0, right: 16, bottom: 0 }} style={{ top: 0, right: 16, bottom: 0 }}
> >
<SVG.ChevronRight height="20px" /> <SVG.ChevronRight height="20px" />
</span> </span>
{current.component} {current.component}
<div <span css={STYLES_MOBILE_HIDDEN}>
css={STYLES_EXPANDER} <div
onClick={() => css={STYLES_EXPANDER}
this.setState({ showSidebar: !this.state.showSidebar }) onClick={() =>
} this.setState({ showSidebar: !this.state.showSidebar })
> }
{this.state.showSidebar ? ( >
<SVG.Maximize height="24px" /> {this.state.showSidebar ? (
) : ( <SVG.Maximize height="24px" />
<SVG.Minimize height="24px" /> ) : (
)} <SVG.Minimize height="24px" />
)}
</div>
</span>
<div css={STYLES_DISMISS_BOX} onClick={this._handleClose}>
<SVG.Dismiss height="24px" />
</div> </div>
</div> </div>
<GlobalViewerCIDSidebar <span css={STYLES_MOBILE_HIDDEN}>
display={this.state.showSidebar ? "block" : "none"} <GlobalViewerCIDSidebar
onClose={this._handleClose} display={this.state.showSidebar ? "block" : "none"}
key={current.id} onClose={this._handleClose}
saving={this.state.saving} key={current.id}
loading={this.state.loading} saving={this.state.saving}
onRehydrate={this.props.onRehydrate} loading={this.state.loading}
onAction={this.props.onAction} onRehydrate={this.props.onRehydrate}
{...current} onAction={this.props.onAction}
/> {...current}
/>
</span>
</div> </div>
); );
} }

View File

@ -116,12 +116,7 @@ export default class GlobalViewerCIDSidebar extends React.Component {
if (this.props.onClose) { if (this.props.onClose) {
elements.push( elements.push(
<div <div key="s-1" css={STYLES_DISMISS_BOX} onClick={this.props.onClose}>
key="s-1"
css={STYLES_DISMISS_BOX}
onMouseUp={this.props.onClose}
onTouchEnd={this.props.onClose}
>
<SVG.Dismiss height="24px" /> <SVG.Dismiss height="24px" />
</div> </div>
); );

View File

@ -78,8 +78,7 @@ export const ButtonPrimary = (props) => {
children={props.children} children={props.children}
type={props.label} type={props.label}
htmlFor={props.htmlFor} htmlFor={props.htmlFor}
onMouseUp={props.onClick} onClick={props.onClick}
onTouchEnd={props.onClick}
/> />
); );
} }
@ -92,8 +91,7 @@ export const ButtonPrimary = (props) => {
: STYLES_BUTTON_PRIMARY : STYLES_BUTTON_PRIMARY
} }
style={{ ...props.style, width: props.full ? "100%" : "auto" }} style={{ ...props.style, width: props.full ? "100%" : "auto" }}
onMouseUp={props.onClick} onClick={props.onClick}
onTouchEnd={props.onClick}
children={props.children} children={props.children}
/> />
); );
@ -153,8 +151,7 @@ export const ButtonSecondary = (props) => {
: STYLES_BUTTON_SECONDARY : STYLES_BUTTON_SECONDARY
} }
style={{ ...props.style, width: props.full ? "100%" : "auto" }} style={{ ...props.style, width: props.full ? "100%" : "auto" }}
onMouseUp={props.onClick} onClick={props.onClick}
onTouchEnd={props.onClick}
children={props.children} children={props.children}
type={props.label} type={props.label}
htmlFor={props.htmlFor} htmlFor={props.htmlFor}
@ -169,8 +166,7 @@ export const ButtonSecondary = (props) => {
? STYLES_BUTTON_SECONDARY_TRANSPARENT ? STYLES_BUTTON_SECONDARY_TRANSPARENT
: STYLES_BUTTON_SECONDARY : STYLES_BUTTON_SECONDARY
} }
onMouseUp={props.onClick} onClick={props.onClick}
onTouchEnd={props.onClick}
children={props.children} children={props.children}
style={{ ...props.style, width: props.full ? "100%" : "auto" }} style={{ ...props.style, width: props.full ? "100%" : "auto" }}
/> />
@ -208,8 +204,7 @@ export const ButtonDisabled = (props) => {
? STYLES_BUTTON_DISABLED_TRANSPARENT ? STYLES_BUTTON_DISABLED_TRANSPARENT
: STYLES_BUTTON_DISABLED : STYLES_BUTTON_DISABLED
} }
onMouseUp={props.onClick} onClick={props.onClick}
onTouchEnd={props.onClick}
children={props.children} children={props.children}
type={props.label} type={props.label}
htmlFor={props.htmlFor} htmlFor={props.htmlFor}
@ -275,8 +270,7 @@ export const ButtonWarning = (props) => {
children={props.children} children={props.children}
type={props.label} type={props.label}
htmlFor={props.htmlFor} htmlFor={props.htmlFor}
onMouseUp={props.onClick} onClick={props.onClick}
onTouchEnd={props.onClick}
/> />
); );
} }
@ -289,8 +283,7 @@ export const ButtonWarning = (props) => {
: STYLES_BUTTON_WARNING : STYLES_BUTTON_WARNING
} }
style={{ ...props.style, width: props.full ? "100%" : "auto" }} style={{ ...props.style, width: props.full ? "100%" : "auto" }}
onMouseUp={props.onClick} onClick={props.onClick}
onTouchEnd={props.onClick}
children={props.children} children={props.children}
/> />
); );

View File

@ -36,6 +36,10 @@ const STYLES_USER = css`
color: ${Constants.system.brand}; color: ${Constants.system.brand};
font-family: ${Constants.font.medium}; font-family: ${Constants.font.medium};
font-size: ${Constants.typescale.lvl1}; font-size: ${Constants.typescale.lvl1};
@media (max-width: ${Constants.sizes.mobile}px) {
margin: 12px 16px;
}
`; `;
const STYLES_BUTTONS = css` const STYLES_BUTTONS = css`
@ -43,6 +47,11 @@ const STYLES_BUTTONS = css`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-right: 48px; margin-right: 48px;
justify-content: flex-end;
@media (max-width: ${Constants.sizes.mobile}px) {
margin-right: 8px;
}
`; `;
const STYLES_ITEM_BOX = css` const STYLES_ITEM_BOX = css`
@ -54,6 +63,10 @@ const STYLES_ITEM_BOX = css`
padding: 8px; padding: 8px;
margin-right: 48px; margin-right: 48px;
color: ${Constants.system.darkGray}; color: ${Constants.system.darkGray};
@media (max-width: ${Constants.sizes.mobile}px) {
margin-right: 8px;
}
`; `;
const STYLES_ACTION_BUTTON = css` const STYLES_ACTION_BUTTON = css`
@ -114,6 +127,18 @@ const STYLES_COPY_INPUT = css`
opacity: 0; opacity: 0;
`; `;
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 SceneDirectory extends React.Component { export default class SceneDirectory extends React.Component {
_ref; _ref;
@ -180,24 +205,53 @@ export default class SceneDirectory extends React.Component {
}) })
.map((relation) => { .map((relation) => {
let button = ( let button = (
<div css={STYLES_BUTTONS}> <React.Fragment>
<ButtonPrimary <span css={STYLES_MOBILE_ONLY}>
transparent <div css={STYLES_BUTTONS}>
style={{ fontSize: 16 }} <div
onClick={(e) => this._handleAccept(e, relation.owner.id)} css={STYLES_ITEM_BOX}
> onClick={(e) => this._handleAccept(e, relation.owner.id)}
Accept >
</ButtonPrimary> <SVG.CheckBox
<ButtonSecondary height="24px"
transparent style={{ color: Constants.system.brand }}
style={{ fontSize: 16 }} />
onClick={(e) => { </div>
this._handleDelete(e, relation.id); <div
}} css={STYLES_ITEM_BOX}
> style={{ marginRight: 0 }}
Decline onClick={(e) => {
</ButtonSecondary> this._handleDelete(e, relation.id);
</div> }}
>
<SVG.Dismiss
height="24px"
style={{ color: Constants.system.gray }}
/>
</div>
</div>
</span>
<span css={STYLES_MOBILE_HIDDEN}>
<div css={STYLES_BUTTONS}>
<ButtonPrimary
transparent
style={{ fontSize: 16 }}
onClick={(e) => this._handleAccept(e, relation.owner.id)}
>
Accept
</ButtonPrimary>
<ButtonSecondary
transparent
style={{ fontSize: 16 }}
onClick={(e) => {
this._handleDelete(e, relation.id);
}}
>
Decline
</ButtonSecondary>
</div>
</span>
</React.Fragment>
); );
return ( return (
<UserEntry <UserEntry

View File

@ -45,10 +45,7 @@ export default class SceneFilesFolder extends React.Component {
/> />
<TabGroup disabled tabs={["Usage"]} /> <TabGroup disabled tabs={["Usage"]} />
<DataMeter <DataMeter stats={this.props.viewer.stats} />
stats={this.props.viewer.stats}
style={{ margin: "48px 0 48px 0" }}
/>
{this.props.viewer.library[0].children && {this.props.viewer.library[0].children &&
this.props.viewer.library[0].children.length ? ( this.props.viewer.library[0].children.length ? (
<DataView <DataView

View File

@ -2,6 +2,7 @@ import * as React from "react";
import * as System from "~/components/system"; import * as System from "~/components/system";
import * as Actions from "~/common/actions"; import * as Actions from "~/common/actions";
import * as Constants from "~/common/constants"; import * as Constants from "~/common/constants";
import * as Validations from "~/common/validations";
import * as SVG from "~/common/svg"; import * as SVG from "~/common/svg";
import * as Strings from "~/common/strings"; import * as Strings from "~/common/strings";
import * as Window from "~/common/window"; import * as Window from "~/common/window";
@ -27,6 +28,12 @@ const STYLES_ICONS = css`
justify-content: center; justify-content: center;
`; `;
const STYLES_ACTIONS = css`
@media (max-width: ${Constants.sizes.mobile}px) {
padding-left: 24px;
}
`;
const STYLES_USERNAME = css` const STYLES_USERNAME = css`
cursor: pointer; cursor: pointer;
@ -35,6 +42,18 @@ const STYLES_USERNAME = css`
} }
`; `;
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;
}
`;
const moveIndex = (set, fromIndex, toIndex) => { const moveIndex = (set, fromIndex, toIndex) => {
const element = set[fromIndex]; const element = set[fromIndex];
set.splice(fromIndex, 1); set.splice(fromIndex, 1);
@ -416,6 +435,45 @@ export default class SceneSlate extends React.Component {
return subscription.target_slate_id === this.props.current.id; return subscription.target_slate_id === this.props.current.id;
}).length; }).length;
let onMobile = Validations.onMobile();
let actions = this.state.editing ? (
<span css={STYLES_ACTIONS}>
<CircleButtonGray onClick={this._handleAdd} style={{ marginRight: 16 }}>
<SVG.Plus height="16px" />
</CircleButtonGray>
{isPublic ? (
<CircleButtonGray
onClick={() => this._handleSlateLink()}
style={{ marginRight: 16 }}
>
<SVG.DeepLink height="16px" />
</CircleButtonGray>
) : null}
<CircleButtonGray onClick={this._handleShowSettings}>
<SVG.Settings height="16px" />
</CircleButtonGray>
</span>
) : (
<div onClick={this._handleFollow}>
{following ? (
<ButtonSecondary
transparent
style={{ minWidth: 120, paddingLeft: 0 }}
loading={this.state.followLoading}
>
Unfollow
</ButtonSecondary>
) : (
<ButtonPrimary
transparent
style={{ minWidth: 120, paddingLeft: 0 }}
loading={this.state.followLoading}
>
Follow
</ButtonPrimary>
)}
</div>
);
return ( return (
<ScenePage <ScenePage
style={{ paddingLeft: "24px", paddingRight: "24px" }} style={{ paddingLeft: "24px", paddingRight: "24px" }}
@ -445,61 +503,15 @@ export default class SceneSlate extends React.Component {
data.name data.name
) )
} }
actions={ actions={<span css={STYLES_MOBILE_HIDDEN}>actions</span>}
this.state.editing ? (
<React.Fragment>
<CircleButtonGray
onMouseUp={this._handleAdd}
onTouchEnd={this._handleAdd}
style={{ marginRight: 16 }}
>
<SVG.Plus height="16px" />
</CircleButtonGray>
{isPublic ? (
<CircleButtonGray
onMouseUp={() => this._handleSlateLink()}
onTouchEnd={() => this._handleSlateLink()}
style={{ marginRight: 16 }}
>
<SVG.DeepLink height="16px" />
</CircleButtonGray>
) : null}
<CircleButtonGray
onMouseUp={this._handleShowSettings}
onTouchEnd={this._handleShowSettings}
>
<SVG.Settings height="16px" />
</CircleButtonGray>
</React.Fragment>
) : (
<div onClick={this._handleFollow}>
{following ? (
<ButtonSecondary
transparent
style={{ minWidth: 120 }}
loading={this.state.followLoading}
>
Unfollow
</ButtonSecondary>
) : (
<ButtonPrimary
transparent
style={{ minWidth: 120 }}
loading={this.state.followLoading}
>
Follow
</ButtonPrimary>
)}
</div>
)
}
> >
<ProcessedText text={body} /> <ProcessedText text={body} />
</ScenePageHeader> </ScenePageHeader>
<span css={STYLES_MOBILE_ONLY}>{actions}</span>
{objects && objects.length ? ( {objects && objects.length ? (
layouts ? ( layouts ? (
<Slate <Slate
editing={this.state.editing} editing={onMobile ? false : this.state.editing}
saving={this.state.saving} saving={this.state.saving}
items={objects} items={objects}
layouts={layouts} layouts={layouts}

View File

@ -72,8 +72,7 @@ export default class SceneSlates extends React.Component {
actions={ actions={
this.state.tab === 0 ? ( this.state.tab === 0 ? (
<CircleButtonGray <CircleButtonGray
onMouseUp={this._handleAdd} onClick={this._handleAdd}
onTouchEnd={this._handleAdd}
style={{ marginLeft: 12 }} style={{ marginLeft: 12 }}
> >
<SVG.Plus height="16px" /> <SVG.Plus height="16px" />