finished data view edit for list view

This commit is contained in:
Martina 2020-09-23 13:52:00 -07:00
parent b140a10013
commit 2d513572f2
22 changed files with 586 additions and 253 deletions

View File

@ -133,6 +133,13 @@ export const processPendingFiles = async (data) => {
}); });
}; };
export const addFileToSlate = async (data) => {
return await returnJSON(`/api/slates/add-url`, {
...DEFAULT_OPTIONS,
body: JSON.stringify(data),
});
};
export const updateViewer = async (data) => { export const updateViewer = async (data) => {
return await returnJSON(`/api/users/update`, { return await returnJSON(`/api/users/update`, {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,

View File

@ -29,6 +29,7 @@ export const system = {
slate: "#27292e", slate: "#27292e",
moonstone: "#807d78", moonstone: "#807d78",
wall: "#cfced3", wall: "#cfced3",
shadow: "rgba(15, 14, 18, 0.03)",
}; };
export const zindex = { export const zindex = {

View File

@ -37,6 +37,26 @@ export const Directory = (props) => {
); );
}; };
export const PlusCircle = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
height={props.height}
style={props.style}
>
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" />
<path d="M12 8V16" />
<path d="M8 12H16" />
</svg>
);
};
export const Users = (props) => { export const Users = (props) => {
return ( return (
<svg <svg
@ -915,6 +935,21 @@ export const Plus = (props) => (
</svg> </svg>
); );
export const Minus = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M5 12H19" />
</svg>
);
export const FilecoinLogo = (props) => ( export const FilecoinLogo = (props) => (
<svg <svg
viewBox="0 0 127 127" viewBox="0 0 127 127"

View File

@ -75,16 +75,6 @@ export const slatename = (text) => {
return true; return true;
}; };
export const email = (text) => {
if (!text || !text.length) {
return false;
}
if (!EMAIL_REGEX.test(text)) {
return false;
}
return true;
};
export const username = (text) => { export const username = (text) => {
if (Strings.isEmpty(text)) { if (Strings.isEmpty(text)) {
return false; return false;

View File

@ -39,6 +39,7 @@ import SidebarCreateWalletAddress from "~/components/sidebars/SidebarCreateWalle
import SidebarWalletSendFunds from "~/components/sidebars/SidebarWalletSendFunds"; import SidebarWalletSendFunds from "~/components/sidebars/SidebarWalletSendFunds";
import SidebarFileStorageDeal from "~/components/sidebars/SidebarFileStorageDeal"; import SidebarFileStorageDeal from "~/components/sidebars/SidebarFileStorageDeal";
import SidebarAddFileToBucket from "~/components/sidebars/SidebarAddFileToBucket"; import SidebarAddFileToBucket from "~/components/sidebars/SidebarAddFileToBucket";
import SidebarAddFileToSlate from "~/components/sidebars/SidebarAddFileToSlate";
import SidebarDragDropNotice from "~/components/sidebars/SidebarDragDropNotice"; import SidebarDragDropNotice from "~/components/sidebars/SidebarDragDropNotice";
import SidebarSingleSlateSettings from "~/components/sidebars/SidebarSingleSlateSettings"; import SidebarSingleSlateSettings from "~/components/sidebars/SidebarSingleSlateSettings";
import SidebarFilecoinArchive from "~/components/sidebars/SidebarFilecoinArchive"; import SidebarFilecoinArchive from "~/components/sidebars/SidebarFilecoinArchive";
@ -64,6 +65,7 @@ const SIDEBARS = {
SIDEBAR_WALLET_SEND_FUNDS: <SidebarWalletSendFunds />, SIDEBAR_WALLET_SEND_FUNDS: <SidebarWalletSendFunds />,
SIDEBAR_CREATE_WALLET_ADDRESS: <SidebarCreateWalletAddress />, SIDEBAR_CREATE_WALLET_ADDRESS: <SidebarCreateWalletAddress />,
SIDEBAR_ADD_FILE_TO_BUCKET: <SidebarAddFileToBucket />, SIDEBAR_ADD_FILE_TO_BUCKET: <SidebarAddFileToBucket />,
SIDEBAR_ADD_FILE_TO_SLATE: <SidebarAddFileToSlate />,
SIDEBAR_CREATE_SLATE: <SidebarCreateSlate />, SIDEBAR_CREATE_SLATE: <SidebarCreateSlate />,
SIDEBAR_DRAG_DROP_NOTICE: <SidebarDragDropNotice />, SIDEBAR_DRAG_DROP_NOTICE: <SidebarDragDropNotice />,
SIDEBAR_SINGLE_SLATE_SETTINGS: <SidebarSingleSlateSettings />, SIDEBAR_SINGLE_SLATE_SETTINGS: <SidebarSingleSlateSettings />,
@ -104,6 +106,7 @@ export default class ApplicationPage extends React.Component {
sidebar: null, sidebar: null,
sidebarLoading: false, sidebarLoading: false,
online: null, online: null,
sidebar: <SidebarAddFileToSlate />, //remove this
}; };
async componentDidMount() { async componentDidMount() {
@ -743,6 +746,7 @@ export default class ApplicationPage extends React.Component {
selected: this.state.selected, selected: this.state.selected,
viewer: this.state.viewer, viewer: this.state.viewer,
data: this.state.data, data: this.state.data,
sidebarData: this.state.sidebarData,
fileLoading: this.state.fileLoading, fileLoading: this.state.fileLoading,
sidebarLoading: this.state.sidebarLoading, sidebarLoading: this.state.sidebarLoading,
onSelectedChange: this._handleSelectedChange, onSelectedChange: this._handleSelectedChange,

View File

@ -164,7 +164,7 @@ export default class ApplicationUserControls extends React.Component {
<Boundary <Boundary
captureResize={true} captureResize={true}
captureScroll={false} captureScroll={false}
enabled={this.state.visible} enabled
onOutsideRectEvent={this._handleHide} onOutsideRectEvent={this._handleHide}
style={this.props.style} style={this.props.style}
> >

View File

@ -10,7 +10,8 @@ const MAX_IN_BYTES = 10737418240 * 4;
const STYLES_CONTAINER = css` const STYLES_CONTAINER = css`
border-radius: 4px; border-radius: 4px;
border: 1px solid ${Constants.system.border}; box-shadow: 0 0 0 1px rgba(229, 229, 229, 0.75) inset,
0 0 40px 0 ${Constants.system.shadow};
padding: 32px; padding: 32px;
max-width: 100%; max-width: 100%;
width: 100%; width: 100%;

View File

@ -14,7 +14,10 @@ import { generateLayout } from "~/components/core/Slate";
import { CheckBox } from "~/components/system/components/CheckBox"; import { CheckBox } from "~/components/system/components/CheckBox";
import { Table } from "~/components/core/Table"; import { Table } from "~/components/core/Table";
import { FileTypeIcon } from "~/components/core/FileTypeIcon"; import { FileTypeIcon } from "~/components/core/FileTypeIcon";
import { ButtonWarning } from "~/components/system/components/Buttons"; import {
ButtonPrimary,
ButtonWarning,
} from "~/components/system/components/Buttons";
import { TabGroup } from "~/components/core/TabGroup"; import { TabGroup } from "~/components/core/TabGroup";
import SlateMediaObject from "~/components/core/SlateMediaObject"; import SlateMediaObject from "~/components/core/SlateMediaObject";
@ -40,11 +43,22 @@ const STYLES_ICON_BOX = css`
margin-left: 16px; margin-left: 16px;
`; `;
const STYLES_CANCEL_BOX = css`
height: 16px;
width: 16px;
background-color: ${Constants.system.brand};
border-radius: 3px;
position: relative;
right: 3px;
cursor: pointer;
box-shadow: 0 0 0 1px ${Constants.system.brand};
`;
const STYLES_HEADER_LINE = css` const STYLES_HEADER_LINE = css`
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 80px; margin-top: 80px;
margin-bottom: 42px; margin-bottom: 30px;
`; `;
const STYLES_LINK = css` const STYLES_LINK = css`
@ -77,7 +91,25 @@ const STYLES_ICON_BOX_HOVER = css`
} }
`; `;
const STYLES_ACTION_ROW = css` const STYLES_ARROWS = css`
display: flex;
align-items: center;
justify-content: flex-end;
`;
const STYLES_ACTION_BAR = css`
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 0 0 1px rgba(229, 229, 229, 0.75) inset,
0 0 40px 0 ${Constants.system.shadow};
border-radius: 4px;
padding: 12px 32px;
background-color: rgba(248, 248, 248, 0.75);
`;
const STYLES_RIGHT = css`
flex-shrink: 0;
display: flex; display: flex;
align-items: center; align-items: center;
`; `;
@ -87,8 +119,8 @@ const STYLES_LEFT = css`
min-width: 10%; min-width: 10%;
`; `;
const STYLES_RIGHT = css` const STYLES_FILES_SELECTED = css`
flex-shrink: 0; font-family: ${Constants.font.semiBold};
`; `;
const STYLES_ICON_ELEMENT = css` const STYLES_ICON_ELEMENT = css`
@ -120,10 +152,8 @@ const STYLES_COPY_INPUT = css`
`; `;
const STYLES_IMAGE_GRID = css` const STYLES_IMAGE_GRID = css`
display: flex; display: grid;
flex-direction: row; grid-template-columns: repeat(auto-fit, minmax(214px, 1fr));
justify-content: space-between;
flex-wrap: wrap;
margin: 0 -27px; margin: 0 -27px;
`; `;
@ -134,7 +164,8 @@ const STYLES_IMAGE_BOX = css`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-shadow: 0px 0px 0px 1px rgba(229, 229, 229, 0.5) inset; box-shadow: 0px 0px 0px 1px rgba(229, 229, 229, 0.75) inset,
0 0 40px 0 ${Constants.system.shadow};
cursor: pointer; cursor: pointer;
`; `;
@ -165,6 +196,7 @@ export default class DataView extends React.Component {
"remote-slate-object-add", "remote-slate-object-add",
this._handleRemoteSlateObjectAdd this._handleRemoteSlateObjectAdd
); );
window.addEventListener("remote-update-carousel", this._handleUpdate);
} }
await this._handleUpdate(); await this._handleUpdate();
@ -185,6 +217,7 @@ export default class DataView extends React.Component {
"remote-slate-object-add", "remote-slate-object-add",
this._handleRemoteSlateObjectAdd this._handleRemoteSlateObjectAdd
); );
window.removeEventListener("remote-update-carousel", this._handleUpdate);
} }
_increment = (direction) => { _increment = (direction) => {
@ -218,9 +251,8 @@ export default class DataView extends React.Component {
if (!window.confirm(message)) { if (!window.confirm(message)) {
return; return;
} }
console.log(this.state.checked);
let cids = Object.keys(this.state.checked).map((id) => { let cids = Object.keys(this.state.checked).map((id) => {
let index = parseInt(id.replace("checkbox-", "")); let index = parseInt(id);
return this.props.viewer.library[0].children[index].ipfs.replace( return this.props.viewer.library[0].children[index].ipfs.replace(
"/ipfs/", "/ipfs/",
"" ""
@ -372,13 +404,9 @@ export default class DataView extends React.Component {
detail: { loading: { id: slate.id } }, detail: { loading: { id: slate.id } },
}); });
const addResponse = await fetch(`/api/slates/add-url`, { const addResponse = await Actions.addFileToSlate({
method: "POST", slate,
headers: { data: [{ title: data.name, ...data }],
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ slate, data: [{ title: data.name, ...data }] }),
}); });
if (!addResponse) { if (!addResponse) {
@ -491,10 +519,6 @@ export default class DataView extends React.Component {
this.setState({ menu: null }); this.setState({ menu: null });
}; };
_handleRemoteDeletion = async (e) => {
await this._handleDelete(e.detail.cid);
};
_handleLoading = ({ cids }) => { _handleLoading = ({ cids }) => {
let loading = this.state.loading; let loading = this.state.loading;
for (let cid of cids) { for (let cid of cids) {
@ -511,6 +535,23 @@ export default class DataView extends React.Component {
this.setState({ [e.target.name]: e.target.value }); this.setState({ [e.target.name]: e.target.value });
}; };
_handleAddToSlate = (e) => {
let userFiles = this.props.viewer.library[0].children;
let files = Object.keys(this.state.checked).map(
(index) => userFiles[index]
);
this.props.onAction({
type: "SIDEBAR",
value: "SIDEBAR_ADD_FILE_TO_SLATE",
data: { files },
});
this._handleUncheckAll();
};
_handleUncheckAll = () => {
this.setState({ checked: {} });
};
render() { render() {
const header = ( const header = (
<div css={STYLES_HEADER_LINE}> <div css={STYLES_HEADER_LINE}>
@ -552,25 +593,8 @@ export default class DataView extends React.Component {
</div> </div>
); );
const footer = ( const footer = (
<div css={STYLES_ACTION_ROW}> <React.Fragment>
<div css={STYLES_LEFT}> <div css={STYLES_ARROWS}>
{Object.keys(this.state.checked).length ? (
<ButtonWarning
style={{ width: 160 }}
onClick={this._handleDeleteFiles}
loading={
this.state.loading &&
Object.values(this.state.loading).some((elem) => {
return !!elem;
})
}
>
Delete {Object.keys(this.state.checked).length} file
{Object.keys(this.state.checked).length > 1 ? "s" : ""}
</ButtonWarning>
) : null}
</div>
<div css={STYLES_RIGHT}>
<span <span
css={STYLES_ICON_ELEMENT} css={STYLES_ICON_ELEMENT}
style={ style={
@ -604,7 +628,33 @@ export default class DataView extends React.Component {
<SVG.NavigationArrow height="24px" /> <SVG.NavigationArrow height="24px" />
</span> </span>
</div> </div>
</div> {Object.keys(this.state.checked).length ? (
<div css={STYLES_ACTION_BAR}>
<div css={STYLES_LEFT}>
<span css={STYLES_FILES_SELECTED}>
{Object.keys(this.state.checked).length} files selected
</span>
</div>
<div css={STYLES_RIGHT}>
<ButtonPrimary transparent onClick={this._handleAddToSlate}>
Add to slate
</ButtonPrimary>
<ButtonWarning
transparent
onClick={this._handleDeleteFiles}
loading={
this.state.loading &&
Object.values(this.state.loading).some((elem) => {
return !!elem;
})
}
>
Delete files
</ButtonWarning>
</div>
</div>
) : null}
</React.Fragment>
); );
if (this.state.view === "grid") { if (this.state.view === "grid") {
return ( return (
@ -640,7 +690,19 @@ export default class DataView extends React.Component {
const columns = [ const columns = [
{ {
key: "checkbox", key: "checkbox",
name: <span />, name: Object.keys(this.state.checked).length ? (
<div
css={STYLES_CANCEL_BOX}
onClick={() => this.setState({ checked: {} })}
>
<SVG.Minus
height="16px"
style={{ color: Constants.system.white }}
/>
</div>
) : (
<span />
),
width: "24px", width: "24px",
}, },
{ {
@ -667,7 +729,7 @@ export default class DataView extends React.Component {
return { return {
...each, ...each,
checkbox: this._handleCheckBox ? ( checkbox: (
<div <div
style={{ style={{
margin: "12px 0", margin: "12px 0",
@ -680,19 +742,13 @@ export default class DataView extends React.Component {
}} }}
> >
<CheckBox <CheckBox
name={`checkbox-${this.state.startIndex + index}`} name={this.state.startIndex + index}
value={ value={!!this.state.checked[this.state.startIndex + index]}
!!this.state.checked[
`checkbox-${this.state.startIndex + index}`
]
}
onChange={this._handleCheckBox} onChange={this._handleCheckBox}
boxStyle={{ height: 16, width: 16 }} boxStyle={{ height: 16, width: 16 }}
style={{ position: "relative", right: 3 }} style={{ position: "relative", right: 3 }}
/> />
</div> </div>
) : (
<div />
), ),
name: ( name: (
<div <div

View File

@ -8,7 +8,7 @@ import { css } from "@emotion/react";
const STYLES_EMPTY_STATE = css` const STYLES_EMPTY_STATE = css`
width: 100%; width: 100%;
height: 328px; height: 328px;
border: 1px solid ${Constants.system.border}; border: 1px solid rgba(229, 229, 229, 0.75);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@ -37,7 +37,7 @@ const STYLES_DROPDOWN_ITEM = css`
margin-bottom: -1px; margin-bottom: -1px;
:hover { :hover {
border-color: ${Constants.system.border} !important; border-color: rgba(229, 229, 229, 0.75) !important;
} }
`; `;
@ -205,7 +205,7 @@ export class SearchDropdown extends React.Component {
style={{ style={{
borderColor: borderColor:
this.state.selectedIndex === i this.state.selectedIndex === i
? Constants.system.border ? "rgba(229, 229, 229, 0.75)"
: "transparent", : "transparent",
...this.props.itemStyle, ...this.props.itemStyle,
}} }}

View File

@ -11,6 +11,22 @@ import { dispatchCustomEvent } from "~/common/custom-events";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
const STYLES_CREATE_NEW = css`
color: ${Constants.system.darkGray};
box-shadow: 0px 0px 0px 1px rgba(229, 229, 229, 0.5) inset;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 160px;
height: 160px;
margin: 0px 12px;
@media (max-width: ${Constants.sizes.mobile}px) {
margin: 0 8px;
}
`;
const STYLES_IMAGE_ROW = css` const STYLES_IMAGE_ROW = css`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -18,9 +34,11 @@ const STYLES_IMAGE_ROW = css`
height: 160px; height: 160px;
justify-content: space-between; justify-content: space-between;
overflow: hidden; overflow: hidden;
margin: 0 -12px;
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
justify-content: center; justify-content: center;
margin: 0 -8px;
} }
`; `;
@ -35,19 +53,21 @@ const STYLES_ITEM_BOX = css`
cursor: pointer; cursor: pointer;
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
margin: 0 auto; margin: 0 8px;
} }
:hover { :hover {
color: ${Constants.system.brand}; color: ${Constants.system.brand};
} }
`;
:first-of-type { const STYLES_EMPTY_BOX = css`
margin-left: 0px; width: 160px;
} height: 160px;
margin: 0px 12px;
:last-of-type { @media (max-width: ${Constants.sizes.mobile}px) {
margin-right: 0px; margin: 0 8px;
} }
`; `;
@ -57,6 +77,7 @@ const STYLES_IMAGE_ROW_SMALL = css`
flex-wrap: wrap; flex-wrap: wrap;
height: 56px; height: 56px;
overflow: hidden; overflow: hidden;
margin: 0 -8px;
`; `;
const STYLES_ITEM_BOX_SMALL = css` const STYLES_ITEM_BOX_SMALL = css`
@ -67,49 +88,72 @@ const STYLES_ITEM_BOX_SMALL = css`
align-items: center; align-items: center;
justify-content: center; justify-content: center;
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;
`;
:first-of-type { const STYLES_EMPTY_BOX_SMALL = css`
margin-left: 0px; width: 56px;
} height: 56px;
margin: 0px 8px;
:last-of-type {
margin-right: 0px;
}
`; `;
export function SlatePreviewRow(props) { export function SlatePreviewRow(props) {
let numItems = props.numItems || 5; let numItems = props.numItems || 5;
let objects = let objects;
props.slate.data.objects.length > numItems if (props.slate.data.objects.length === 0) {
? props.slate.data.objects.slice(0, numItems) objects = [
: props.slate.data.objects; <div css={STYLES_CREATE_NEW} key="add-files">
<SVG.Plus height="24px" />
<div>Add Files</div>
</div>,
];
} else {
let trimmed =
props.slate.data.objects.length > numItems
? props.slate.data.objects.slice(0, numItems)
: props.slate.data.objects;
objects = trimmed.map((each) => (
<div
key={each.id}
css={props.small ? STYLES_ITEM_BOX_SMALL : STYLES_ITEM_BOX}
style={props.style}
>
<SlateMediaObjectPreview
charCap={30}
type={each.type}
url={each.url}
style={{ border: "none", ...props.previewStyle }}
title={each.title || each.name}
small={props.small}
/>
</div>
));
}
let numExtra = props.numItems
? props.numItems - objects.length
: 5 - objects.length;
let extra = [];
for (let i = 0; i < numExtra; i++) {
extra.push(
<div
key={`extra-${i}`}
css={props.small ? STYLES_EMPTY_BOX_SMALL : STYLES_EMPTY_BOX}
/>
);
}
return ( return (
<div <div
css={props.small ? STYLES_IMAGE_ROW_SMALL : STYLES_IMAGE_ROW} css={props.small ? STYLES_IMAGE_ROW_SMALL : STYLES_IMAGE_ROW}
style={props.containerStyle} style={props.containerStyle}
> >
{objects.map((each) => ( {objects}
<div {extra}
key={each.id}
css={props.small ? STYLES_ITEM_BOX_SMALL : STYLES_ITEM_BOX}
style={props.style}
>
<SlateMediaObjectPreview
charCap={30}
type={each.type}
url={each.url}
style={{ border: "none", ...props.previewStyle }}
title={each.title || each.name}
small={props.small}
/>
</div>
))}
</div> </div>
); );
} }
const STYLES_BLOCK = css` const STYLES_BLOCK = css`
box-shadow: 0 0 0 1px ${Constants.system.border} inset; box-shadow: 0 0 0 1px rgba(229, 229, 229, 0.75) inset,
0 0 40px 0 ${Constants.system.shadow};
border-radius: 8px; border-radius: 8px;
padding: 32px 40px; padding: 32px 40px;
font-size: 12px; font-size: 12px;
@ -153,17 +197,6 @@ const STYLES_BODY = css`
word-wrap: break-word; word-wrap: break-word;
`; `;
const STYLES_CREATE_NEW = css`
color: ${Constants.system.darkGray};
box-shadow: 0px 0px 0px 1px rgba(229, 229, 229, 0.5) inset;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 160px;
height: 160px;
`;
const STYLES_ICON_BOX = css` const STYLES_ICON_BOX = css`
height: 32px; height: 32px;
width: 32px; width: 32px;
@ -354,20 +387,10 @@ export default class SlatePreviewBlock extends React.Component {
) : ( ) : (
<div style={{ height: "8px" }} /> <div style={{ height: "8px" }} />
)} )}
{this.props.slate.data.objects && <SlatePreviewRow
this.props.slate.data.objects.length ? ( {...this.props}
<SlatePreviewRow previewStyle={this.props.previewStyle}
{...this.props} />
previewStyle={this.props.previewStyle}
/>
) : (
<div css={STYLES_IMAGE_ROW}>
<div css={STYLES_CREATE_NEW}>
<SVG.Plus height="24px" />
<div>Add Files</div>
</div>
</div>
)}
</div> </div>
); );
} }

View File

@ -20,6 +20,7 @@ const TABLE_COLUMN_WIDTH_DEFAULTS = {
const STYLES_CONTAINER = css` const STYLES_CONTAINER = css`
border: 1px solid rgba(229, 229, 229, 0.75); border: 1px solid rgba(229, 229, 229, 0.75);
box-shadow: 0 0 40px 0 ${Constants.system.shadow};
`; `;
const STYLES_TABLE_ROW = css` const STYLES_TABLE_ROW = css`

View File

@ -0,0 +1,180 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as System from "~/components/system";
import * as Validations from "~/common/validations";
import * as SVG from "~/common/svg";
import * as Actions from "~/common/actions";
import { dispatchCustomEvent } from "~/common/custom-events";
import { css } from "@emotion/react";
import { ButtonPrimary } from "~/components/system/components/Buttons";
const STYLES_SLATE_NAME = css`
overflow: hidden;
text-overflow: ellipsis;
font-family: ${Constants.font.medium};
`;
const STYLES_HEADER = css`
font-family: ${Constants.font.semiBold};
font-size: 18px;
margin-top: 32px;
margin-bottom: 16px;
`;
const STYLES_SLATE_LIST = css`
max-height: 400px;
overflow-y: scroll;
`;
const STYLES_SLATE_LINE = css`
display: flex;
align-items: center;
width: 100%;
padding: 12px 16px;
background-color: ${Constants.system.white};
margin-bottom: 1px;
cursor: pointer;
`;
const STYLES_ICON_BOX = css`
display: flex;
align-items: center;
`;
export default class SidebarAddFileToSlate extends React.Component {
state = {
selected: {},
};
_handleCreateSlate = async () => {
if (
Object.values(this.state.selected).some((value) => {
return !!value;
})
) {
await this._handleSubmit();
}
await this.props.onCancel();
this.props.onAction({
type: "SIDEBAR",
value: "SIDEBAR_CREATE_SLATE",
data: this.props.sidebarData,
});
};
_handleAdd = (slate) => {
if (this.state.selected[slate.id]) {
this.setState({
selected: { ...this.state.selected, [slate.id]: false },
});
} else {
this.setState({
selected: { ...this.state.selected, [slate.id]: slate },
});
}
};
_handleSubmit = async () => {
let data = this.props.sidebarData.files.map((file) => {
return { title: file.name, ...file };
});
for (let slate of Object.values(this.state.selected)) {
if (!slate) continue;
const addResponse = await Actions.addFileToSlate({ slate, data });
if (!addResponse) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message:
"We're having trouble connecting right now. Please try again later",
},
},
});
return;
} else if (addResponse.error) {
dispatchCustomEvent({
name: "create-alert",
detail: { alert: { decorator: addResponse.decorator } },
});
return;
}
}
await this.props.onRehydrate();
dispatchCustomEvent({
name: "remote-update-carousel",
detail: null,
});
this.props.onCancel();
};
render() {
return (
<div>
<System.P
style={{
fontFamily: Constants.font.semiBold,
fontSize: Constants.typescale.lvl3,
marginBottom: "64px",
}}
>
Add files to slate
</System.P>
<System.P css={STYLES_HEADER}>Slates</System.P>
<div
css={STYLES_SLATE_LINE}
style={{ marginBottom: 32 }}
onClick={this._handleCreateSlate}
>
<SVG.Plus
height="24px"
style={{ color: Constants.system.brand, marginRight: 8 }}
/>
<div
css={STYLES_SLATE_NAME}
style={{ color: Constants.system.brand }}
>
Create new slate
</div>
</div>
<div css={STYLES_SLATE_LIST}>
{this.props.viewer.slates.map((slate) => (
<div css={STYLES_SLATE_LINE} onClick={() => this._handleAdd(slate)}>
<div css={STYLES_ICON_BOX}>
{this.state.selected[slate.id] ? (
<SVG.Slate height="24px" style={{ marginRight: 8 }} />
) : (
<SVG.PlusCircle
height="24px"
style={{ color: Constants.system.darkGray, marginRight: 8 }}
/>
)}
</div>
<div
css={STYLES_SLATE_NAME}
style={
this.state.selected[slate.id]
? null
: { color: Constants.system.darkGray }
}
>
{slate.data.name || slate.slatename}
</div>
</div>
))}
</div>
<ButtonPrimary
full
onClick={this._handleSubmit}
style={{ marginTop: 32 }}
>
Add to slates
</ButtonPrimary>
</div>
);
}
}

View File

@ -3,6 +3,7 @@ import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants"; import * as Constants from "~/common/constants";
import * as System from "~/components/system"; import * as System from "~/components/system";
import * as Validations from "~/common/validations"; import * as Validations from "~/common/validations";
import * as Actions from "~/common/actions";
import { dispatchCustomEvent } from "~/common/custom-events"; import { dispatchCustomEvent } from "~/common/custom-events";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
@ -87,6 +88,48 @@ export default class SidebarCreateSlate extends React.Component {
return; return;
} }
if (
this.props.sidebarData &&
this.props.sidebarData.files &&
this.props.sidebarData.files[0].decorator === "FILE"
) {
let data = this.props.sidebarData.files.map((file) => {
return { title: file.name, ...file };
});
const addResponse = await Actions.addFileToSlate({
slate: response.slate,
data,
});
if (!addResponse) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message:
"We're having trouble connecting right now. Please try again later",
},
},
});
return;
}
if (addResponse.error) {
dispatchCustomEvent({
name: "create-alert",
detail: { alert: { decorator: response.decorator } },
});
return;
}
await this.props.onRehydrate();
dispatchCustomEvent({
name: "remote-update-carousel",
detail: null,
});
}
this.setState({ loading: false }); this.setState({ loading: false });
this.props.onAction({ this.props.onAction({
type: "NAVIGATE", type: "NAVIGATE",

View File

@ -28,7 +28,7 @@ export default class SidebarCreateSlate extends React.Component {
_handleSubmit = async () => { _handleSubmit = async () => {
this.setState({ loading: true }); this.setState({ loading: true });
if (!this.state.email || !this.state.email.length) { if (Strings.isEmpty(this.state.email)) {
dispatchCustomEvent({ dispatchCustomEvent({
name: "create-alert", name: "create-alert",
detail: { detail: {
@ -54,17 +54,6 @@ export default class SidebarCreateSlate extends React.Component {
return; return;
} }
if (!Validations.email(this.state.email)) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: { message: "Please check that your email address is valid" },
},
});
this.setState({ loading: false });
return;
}
const response = await Actions.createSupportMessage({ const response = await Actions.createSupportMessage({
username: this.props.viewer.username, username: this.props.viewer.username,
name: this.state.name, name: this.state.name,

View File

@ -11,32 +11,13 @@ const STYLES_BUTTON = `
outline: 0; outline: 0;
border: 0; border: 0;
min-height: 40px; min-height: 40px;
padding: 6px 24px 6px 24px; padding: 4px 16px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 12px; font-size: 14px;
letter-spacing: 0.2px; letter-spacing: 0.2px;
font-family: ${Constants.font.semiBold}; font-family: ${Constants.font.medium};
transition: 200ms ease all;
overflow-wrap: break-word;
user-select: none;
`;
const STYLES_BUTTON_FULL = `
box-sizing: border-box;
border-radius: 4px;
outline: 0;
border: 0;
min-height: 40px;
padding: 6px 24px 6px 24px;
display: flex;
width: 100%;
align-items: center;
justify-content: center;
font-size: 12px;
letter-spacing: 0.2px;
font-family: ${Constants.font.semiBold};
transition: 200ms ease all; transition: 200ms ease all;
overflow-wrap: break-word; overflow-wrap: break-word;
user-select: none; user-select: none;
@ -60,30 +41,25 @@ const STYLES_BUTTON_PRIMARY = css`
} }
`; `;
const STYLES_BUTTON_PRIMARY_FULL = css` const STYLES_BUTTON_PRIMARY_TRANSPARENT = css`
${STYLES_BUTTON_FULL} ${STYLES_BUTTON}
${"" /* font-size: 16px;
font-family: ${Constants.font.medium}; */}
cursor: pointer; cursor: pointer;
background-color: ${Constants.system.brand}; background-color: transparent;
color: ${Constants.system.white}; color: ${Constants.system.brand};
:hover {
background-color: #065ca8;
}
:focus {
box-shadow: inset 0 0 5px 2px rgba(0, 0, 0, 0.3);
background-color: #065ca8;
outline: 0;
border: 0;
}
`; `;
export const ButtonPrimary = (props) => { export const ButtonPrimary = (props) => {
if (props.loading) { if (props.loading) {
return ( return (
<button <button
css={props.full ? STYLES_BUTTON_PRIMARY_FULL : STYLES_BUTTON_PRIMARY} css={
style={props.style} props.transparent
? STYLES_BUTTON_PRIMARY_TRANSPARENT
: STYLES_BUTTON_PRIMARY
}
style={{ ...props.style, width: props.full ? "100%" : "auto" }}
> >
<LoaderSpinner style={{ height: 16, width: 16 }} /> <LoaderSpinner style={{ height: 16, width: 16 }} />
</button> </button>
@ -93,8 +69,12 @@ export const ButtonPrimary = (props) => {
if (props.type === "label") { if (props.type === "label") {
return ( return (
<label <label
css={props.full ? STYLES_BUTTON_PRIMARY_FULL : STYLES_BUTTON_PRIMARY} css={
style={props.style} props.transparent
? STYLES_BUTTON_PRIMARY_TRANSPARENT
: STYLES_BUTTON_PRIMARY
}
style={{ ...props.style, width: props.full ? "100%" : "auto" }}
children={props.children} children={props.children}
type={props.label} type={props.label}
htmlFor={props.htmlFor} htmlFor={props.htmlFor}
@ -106,8 +86,12 @@ export const ButtonPrimary = (props) => {
return ( return (
<button <button
css={props.full ? STYLES_BUTTON_PRIMARY_FULL : STYLES_BUTTON_PRIMARY} css={
style={props.style} props.transparent
? STYLES_BUTTON_PRIMARY_TRANSPARENT
: STYLES_BUTTON_PRIMARY
}
style={{ ...props.style, width: props.full ? "100%" : "auto" }}
onMouseUp={props.onClick} onMouseUp={props.onClick}
onTouchEnd={props.onClick} onTouchEnd={props.onClick}
children={props.children} children={props.children}
@ -137,22 +121,11 @@ const STYLES_BUTTON_SECONDARY = css`
} }
`; `;
const STYLES_BUTTON_SECONDARY_FULL = css` const STYLES_BUTTON_SECONDARY_TRANSPARENT = css`
${STYLES_BUTTON_FULL} ${STYLES_BUTTON}
cursor: pointer; cursor: pointer;
background-color: ${Constants.system.white}; background-color: transparent;
box-shadow: 0 0 0 1px ${Constants.system.border} inset; color: ${Constants.system.darkGray};
color: ${Constants.system.brand};
:hover {
${"" /* box-shadow: 0 0 0 1px #065ca8 inset;
color: #065ca8; */}
}
:focus {
outline: 0;
border: 0;
}
`; `;
export const ButtonSecondary = (props) => { export const ButtonSecondary = (props) => {
@ -160,9 +133,11 @@ export const ButtonSecondary = (props) => {
return ( return (
<button <button
css={ css={
props.full ? STYLES_BUTTON_SECONDARY_FULL : STYLES_BUTTON_SECONDARY props.transparent
? STYLES_BUTTON_SECONDARY_TRANSPARENT
: STYLES_BUTTON_SECONDARY
} }
style={props.style} style={{ ...props.style, width: props.full ? "100%" : "auto" }}
> >
<LoaderSpinner style={{ height: 16, width: 16 }} /> <LoaderSpinner style={{ height: 16, width: 16 }} />
</button> </button>
@ -173,9 +148,11 @@ export const ButtonSecondary = (props) => {
return ( return (
<label <label
css={ css={
props.full ? STYLES_BUTTON_SECONDARY_FULL : STYLES_BUTTON_SECONDARY props.transparent
? STYLES_BUTTON_SECONDARY_TRANSPARENT
: STYLES_BUTTON_SECONDARY
} }
style={props.style} style={{ ...props.style, width: props.full ? "100%" : "auto" }}
onMouseUp={props.onClick} onMouseUp={props.onClick}
onTouchEnd={props.onClick} onTouchEnd={props.onClick}
children={props.children} children={props.children}
@ -187,8 +164,13 @@ export const ButtonSecondary = (props) => {
return ( return (
<button <button
css={props.full ? STYLES_BUTTON_SECONDARY_FULL : STYLES_BUTTON_SECONDARY} css={
props.transparent
? STYLES_BUTTON_SECONDARY_TRANSPARENT
: STYLES_BUTTON_SECONDARY
}
{...props} {...props}
style={{ ...props.style, width: props.full ? "100%" : "auto" }}
/> />
); );
}; };
@ -209,23 +191,23 @@ const STYLES_BUTTON_DISABLED = css`
} }
`; `;
const STYLES_BUTTON_DISABLED_FULL = css` const STYLES_BUTTON_DISABLED_TRANSPARENT = css`
${STYLES_BUTTON_FULL} ${STYLES_BUTTON}
cursor: not-allowed; cursor: not-allowed;
background-color: ${Constants.system.gray}; background-color: transparent;
color: ${Constants.system.darkGray}; color: ${Constants.system.gray};
:focus {
outline: 0;
border: 0;
}
`; `;
export const ButtonDisabled = (props) => { export const ButtonDisabled = (props) => {
return ( return (
<button <button
css={props.full ? STYLES_BUTTON_DISABLED_FULL : STYLES_BUTTON_DISABLED} css={
props.transparent
? STYLES_BUTTON_DISABLED_TRANSPARENT
: STYLES_BUTTON_DISABLED
}
{...props} {...props}
style={{ ...props.style, width: props.full ? "100%" : "auto" }}
/> />
); );
}; };
@ -252,30 +234,23 @@ const STYLES_BUTTON_WARNING = css`
} }
`; `;
const STYLES_BUTTON_WARNING_FULL = css` const STYLES_BUTTON_WARNING_TRANSPARENT = css`
${STYLES_BUTTON_FULL} ${STYLES_BUTTON}
cursor: pointer; cursor: pointer;
background-color: #e0e0e0; background-color: transparent;
color: ${Constants.system.red}; color: ${Constants.system.red};
:hover {
background-color: #d4d4d4;
}
:focus {
box-shadow: inset 0 0 5px 2px rgba(0, 0, 0, 0.3);
background-color: #d4d4d4;
outline: 0;
border: 0;
}
`; `;
export const ButtonWarning = (props) => { export const ButtonWarning = (props) => {
if (props.loading) { if (props.loading) {
return ( return (
<button <button
css={props.full ? STYLES_BUTTON_WARNING_FULL : STYLES_BUTTON_WARNING} css={
style={props.style} props.transparent
? STYLES_BUTTON_WARNING_TRANSPARENT
: STYLES_BUTTON_WARNING
}
style={{ ...props.style, width: props.full ? "100%" : "auto" }}
> >
<LoaderSpinner style={{ height: 16, width: 16 }} /> <LoaderSpinner style={{ height: 16, width: 16 }} />
</button> </button>
@ -285,8 +260,12 @@ export const ButtonWarning = (props) => {
if (props.type === "label") { if (props.type === "label") {
return ( return (
<label <label
css={props.full ? STYLES_BUTTON_WARNING_FULL : STYLES_BUTTON_WARNING} css={
style={props.style} props.transparent
? STYLES_BUTTON_WARNING_TRANSPARENT
: STYLES_BUTTON_WARNING
}
style={{ ...props.style, width: props.full ? "100%" : "auto" }}
children={props.children} children={props.children}
type={props.label} type={props.label}
htmlFor={props.htmlFor} htmlFor={props.htmlFor}
@ -298,8 +277,12 @@ export const ButtonWarning = (props) => {
return ( return (
<button <button
css={props.full ? STYLES_BUTTON_WARNING_FULL : STYLES_BUTTON_WARNING} css={
style={props.style} props.transparent
? STYLES_BUTTON_WARNING_TRANSPARENT
: STYLES_BUTTON_WARNING
}
style={{ ...props.style, width: props.full ? "100%" : "auto" }}
onMouseUp={props.onClick} onMouseUp={props.onClick}
onTouchEnd={props.onClick} onTouchEnd={props.onClick}
children={props.children} children={props.children}

View File

@ -74,8 +74,27 @@ export class CheckBox extends React.Component {
render() { render() {
return ( return (
<label css={STYLES_CHECKBOX} style={this.props.style}> <label css={STYLES_CHECKBOX} style={this.props.style}>
<figure css={STYLES_CHECKBOX_FIGURE} style={this.props.boxStyle}> <figure
{this.props.value ? <SVG.CheckBox height="20px" /> : null} css={STYLES_CHECKBOX_FIGURE}
style={
this.props.value
? {
backgroundColor: Constants.system.brand,
boxShadow: `0 0 0 1px ${Constants.system.brand}`,
...this.props.boxStyle,
}
: {
backgroundColor: Constants.system.white,
...this.props.boxStyle,
}
}
>
{this.props.value ? (
<SVG.CheckBox
height="14px"
style={{ color: Constants.system.white }}
/>
) : null}
</figure> </figure>
<input <input
css={STYLES_CHECKBOX_INPUT} css={STYLES_CHECKBOX_INPUT}

View File

@ -70,6 +70,11 @@ export default async (req, res) => {
} }
let newObjects = []; let newObjects = [];
if (Array.isArray(req.body.data)) {
newObjects = [...req.body.data];
} else {
newObjects = [req.body.data];
}
const isArray = req.body.data && req.body.data.length; const isArray = req.body.data && req.body.data.length;
if (isArray) { if (isArray) {
newObjects = [...req.body.data]; newObjects = [...req.body.data];

View File

@ -8,6 +8,10 @@ import { css } from "@emotion/react";
import { TabGroup } from "~/components/core/TabGroup"; import { TabGroup } from "~/components/core/TabGroup";
import { Boundary } from "~/components/system/components/fragments/Boundary"; import { Boundary } from "~/components/system/components/fragments/Boundary";
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation"; import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
import {
ButtonPrimary,
ButtonSecondary,
} from "~/components/system/components/Buttons";
import ScenePage from "~/components/core/ScenePage"; import ScenePage from "~/components/core/ScenePage";
import ScenePageHeader from "~/components/core/ScenePageHeader"; import ScenePageHeader from "~/components/core/ScenePageHeader";
@ -178,21 +182,22 @@ export default class SceneDirectory extends React.Component {
.map((relation) => { .map((relation) => {
let button = ( let button = (
<div css={STYLES_BUTTONS}> <div css={STYLES_BUTTONS}>
<div <ButtonPrimary
css={STYLES_ACTION_BUTTON} transparent
style={{ fontSize: 16 }}
onClick={(e) => this._handleAccept(e, relation.owner.id)} onClick={(e) => this._handleAccept(e, relation.owner.id)}
> >
Accept Accept
</div> </ButtonPrimary>
<div <ButtonSecondary
css={STYLES_ACTION_BUTTON} transparent
style={{ color: Constants.system.darkGray, marginLeft: "16px" }} style={{ fontSize: 16 }}
onClick={(e) => { onClick={(e) => {
this._handleDelete(e, relation.id); this._handleDelete(e, relation.id);
}} }}
> >
Decline Decline
</div> </ButtonSecondary>
</div> </div>
); );
return ( return (

View File

@ -50,6 +50,7 @@ export default class SceneFilesFolder extends React.Component {
{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
onAction={this.props.onAction}
viewer={this.props.viewer} viewer={this.props.viewer}
items={this.props.viewer.library[0].children} items={this.props.viewer.library[0].children}
onRehydrate={this.props.onRehydrate} onRehydrate={this.props.onRehydrate}

View File

@ -54,18 +54,6 @@ export default class SceneHome extends React.Component {
{hasChildren ? ( {hasChildren ? (
<div style={{ marginTop: "48px" }}> <div style={{ marginTop: "48px" }}>
<DataView <DataView
buttons={[
{
name: "View files",
type: "NAVIGATE",
value: this.props.viewer.library[0].id,
},
{
name: "Upload data",
type: "SIDEBAR",
value: "SIDEBAR_ADD_FILE_TO_BUCKET",
},
]}
viewer={this.props.viewer} viewer={this.props.viewer}
items={this.props.viewer.library[0].children} items={this.props.viewer.library[0].children}
onAction={this.props.onAction} onAction={this.props.onAction}

View File

@ -516,6 +516,7 @@ export default class SceneSlate extends React.Component {
<div onClick={this._handleFollow}> <div onClick={this._handleFollow}>
{following ? ( {following ? (
<ButtonSecondary <ButtonSecondary
transparent
style={{ minWidth: 120 }} style={{ minWidth: 120 }}
loading={this.state.followLoading} loading={this.state.followLoading}
> >
@ -523,6 +524,7 @@ export default class SceneSlate extends React.Component {
</ButtonSecondary> </ButtonSecondary>
) : ( ) : (
<ButtonPrimary <ButtonPrimary
transparent
style={{ minWidth: 120 }} style={{ minWidth: 120 }}
loading={this.state.followLoading} loading={this.state.followLoading}
> >