diff --git a/common/actions.js b/common/actions.js index 02da51a1..5cfb35fb 100644 --- a/common/actions.js +++ b/common/actions.js @@ -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) => { return await returnJSON(`/api/users/update`, { ...DEFAULT_OPTIONS, diff --git a/common/constants.js b/common/constants.js index ddbdf307..0bcf40fd 100644 --- a/common/constants.js +++ b/common/constants.js @@ -29,6 +29,7 @@ export const system = { slate: "#27292e", moonstone: "#807d78", wall: "#cfced3", + shadow: "rgba(15, 14, 18, 0.03)", }; export const zindex = { diff --git a/common/svg.js b/common/svg.js index f3ef2bd3..324c7b54 100644 --- a/common/svg.js +++ b/common/svg.js @@ -37,6 +37,26 @@ export const Directory = (props) => { ); }; +export const PlusCircle = (props) => { + return ( + + + + + + ); +}; + export const Users = (props) => { return ( ( ); +export const Minus = (props) => ( + + + +); + export const FilecoinLogo = (props) => ( { 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) => { if (Strings.isEmpty(text)) { return false; diff --git a/components/core/Application.js b/components/core/Application.js index 645710bb..7591757c 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -39,6 +39,7 @@ import SidebarCreateWalletAddress from "~/components/sidebars/SidebarCreateWalle import SidebarWalletSendFunds from "~/components/sidebars/SidebarWalletSendFunds"; import SidebarFileStorageDeal from "~/components/sidebars/SidebarFileStorageDeal"; import SidebarAddFileToBucket from "~/components/sidebars/SidebarAddFileToBucket"; +import SidebarAddFileToSlate from "~/components/sidebars/SidebarAddFileToSlate"; import SidebarDragDropNotice from "~/components/sidebars/SidebarDragDropNotice"; import SidebarSingleSlateSettings from "~/components/sidebars/SidebarSingleSlateSettings"; import SidebarFilecoinArchive from "~/components/sidebars/SidebarFilecoinArchive"; @@ -64,6 +65,7 @@ const SIDEBARS = { SIDEBAR_WALLET_SEND_FUNDS: , SIDEBAR_CREATE_WALLET_ADDRESS: , SIDEBAR_ADD_FILE_TO_BUCKET: , + SIDEBAR_ADD_FILE_TO_SLATE: , SIDEBAR_CREATE_SLATE: , SIDEBAR_DRAG_DROP_NOTICE: , SIDEBAR_SINGLE_SLATE_SETTINGS: , @@ -104,6 +106,7 @@ export default class ApplicationPage extends React.Component { sidebar: null, sidebarLoading: false, online: null, + sidebar: , //remove this }; async componentDidMount() { @@ -743,6 +746,7 @@ export default class ApplicationPage extends React.Component { selected: this.state.selected, viewer: this.state.viewer, data: this.state.data, + sidebarData: this.state.sidebarData, fileLoading: this.state.fileLoading, sidebarLoading: this.state.sidebarLoading, onSelectedChange: this._handleSelectedChange, diff --git a/components/core/ApplicationUserControls.js b/components/core/ApplicationUserControls.js index 1beabc6c..16b445e4 100644 --- a/components/core/ApplicationUserControls.js +++ b/components/core/ApplicationUserControls.js @@ -164,7 +164,7 @@ export default class ApplicationUserControls extends React.Component { diff --git a/components/core/DataMeter.js b/components/core/DataMeter.js index b1c77f69..2dd4cbc2 100644 --- a/components/core/DataMeter.js +++ b/components/core/DataMeter.js @@ -10,7 +10,8 @@ const MAX_IN_BYTES = 10737418240 * 4; const STYLES_CONTAINER = css` 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; max-width: 100%; width: 100%; diff --git a/components/core/DataView.js b/components/core/DataView.js index fbea8260..97f39ad5 100644 --- a/components/core/DataView.js +++ b/components/core/DataView.js @@ -14,7 +14,10 @@ import { generateLayout } from "~/components/core/Slate"; import { CheckBox } from "~/components/system/components/CheckBox"; import { Table } from "~/components/core/Table"; 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 SlateMediaObject from "~/components/core/SlateMediaObject"; @@ -40,11 +43,22 @@ const STYLES_ICON_BOX = css` 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` display: flex; align-items: center; margin-top: 80px; - margin-bottom: 42px; + margin-bottom: 30px; `; 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; align-items: center; `; @@ -87,8 +119,8 @@ const STYLES_LEFT = css` min-width: 10%; `; -const STYLES_RIGHT = css` - flex-shrink: 0; +const STYLES_FILES_SELECTED = css` + font-family: ${Constants.font.semiBold}; `; const STYLES_ICON_ELEMENT = css` @@ -120,10 +152,8 @@ const STYLES_COPY_INPUT = css` `; const STYLES_IMAGE_GRID = css` - display: flex; - flex-direction: row; - justify-content: space-between; - flex-wrap: wrap; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(214px, 1fr)); margin: 0 -27px; `; @@ -134,7 +164,8 @@ const STYLES_IMAGE_BOX = css` display: flex; align-items: 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; `; @@ -165,6 +196,7 @@ export default class DataView extends React.Component { "remote-slate-object-add", this._handleRemoteSlateObjectAdd ); + window.addEventListener("remote-update-carousel", this._handleUpdate); } await this._handleUpdate(); @@ -185,6 +217,7 @@ export default class DataView extends React.Component { "remote-slate-object-add", this._handleRemoteSlateObjectAdd ); + window.removeEventListener("remote-update-carousel", this._handleUpdate); } _increment = (direction) => { @@ -218,9 +251,8 @@ export default class DataView extends React.Component { if (!window.confirm(message)) { return; } - console.log(this.state.checked); 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( "/ipfs/", "" @@ -372,13 +404,9 @@ export default class DataView extends React.Component { detail: { loading: { id: slate.id } }, }); - const addResponse = await fetch(`/api/slates/add-url`, { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify({ slate, data: [{ title: data.name, ...data }] }), + const addResponse = await Actions.addFileToSlate({ + slate, + data: [{ title: data.name, ...data }], }); if (!addResponse) { @@ -491,10 +519,6 @@ export default class DataView extends React.Component { this.setState({ menu: null }); }; - _handleRemoteDeletion = async (e) => { - await this._handleDelete(e.detail.cid); - }; - _handleLoading = ({ cids }) => { let loading = this.state.loading; for (let cid of cids) { @@ -511,6 +535,23 @@ export default class DataView extends React.Component { 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() { const header = (
@@ -552,25 +593,8 @@ export default class DataView extends React.Component {
); const footer = ( -
-
- {Object.keys(this.state.checked).length ? ( - { - return !!elem; - }) - } - > - Delete {Object.keys(this.state.checked).length} file - {Object.keys(this.state.checked).length > 1 ? "s" : ""} - - ) : null} -
-
+ +
-
+ {Object.keys(this.state.checked).length ? ( +
+
+ + {Object.keys(this.state.checked).length} files selected + +
+
+ + Add to slate + + { + return !!elem; + }) + } + > + Delete files + +
+
+ ) : null} + ); if (this.state.view === "grid") { return ( @@ -640,7 +690,19 @@ export default class DataView extends React.Component { const columns = [ { key: "checkbox", - name: , + name: Object.keys(this.state.checked).length ? ( +
this.setState({ checked: {} })} + > + +
+ ) : ( + + ), width: "24px", }, { @@ -667,7 +729,7 @@ export default class DataView extends React.Component { return { ...each, - checkbox: this._handleCheckBox ? ( + checkbox: (
- ) : ( -
), name: (
numItems - ? props.slate.data.objects.slice(0, numItems) - : props.slate.data.objects; + let objects; + if (props.slate.data.objects.length === 0) { + objects = [ +
+ +
Add Files
+
, + ]; + } else { + let trimmed = + props.slate.data.objects.length > numItems + ? props.slate.data.objects.slice(0, numItems) + : props.slate.data.objects; + objects = trimmed.map((each) => ( +
+ +
+ )); + } + let numExtra = props.numItems + ? props.numItems - objects.length + : 5 - objects.length; + let extra = []; + for (let i = 0; i < numExtra; i++) { + extra.push( +
+ ); + } return (
- {objects.map((each) => ( -
- -
- ))} + {objects} + {extra}
); } 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; padding: 32px 40px; font-size: 12px; @@ -153,17 +197,6 @@ const STYLES_BODY = css` 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` height: 32px; width: 32px; @@ -354,20 +387,10 @@ export default class SlatePreviewBlock extends React.Component { ) : (
)} - {this.props.slate.data.objects && - this.props.slate.data.objects.length ? ( - - ) : ( -
-
- -
Add Files
-
-
- )} +
); } diff --git a/components/core/Table.js b/components/core/Table.js index 50a77e1d..3865b97f 100644 --- a/components/core/Table.js +++ b/components/core/Table.js @@ -20,6 +20,7 @@ const TABLE_COLUMN_WIDTH_DEFAULTS = { const STYLES_CONTAINER = css` border: 1px solid rgba(229, 229, 229, 0.75); + box-shadow: 0 0 40px 0 ${Constants.system.shadow}; `; const STYLES_TABLE_ROW = css` diff --git a/components/sidebars/SidebarAddFileToSlate.js b/components/sidebars/SidebarAddFileToSlate.js new file mode 100644 index 00000000..5881a181 --- /dev/null +++ b/components/sidebars/SidebarAddFileToSlate.js @@ -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 ( +
+ + Add files to slate + + + Slates +
+ +
+ Create new slate +
+
+
+ {this.props.viewer.slates.map((slate) => ( +
this._handleAdd(slate)}> +
+ {this.state.selected[slate.id] ? ( + + ) : ( + + )} +
+
+ {slate.data.name || slate.slatename} +
+
+ ))} +
+ + Add to slates + +
+ ); + } +} diff --git a/components/sidebars/SidebarCreateSlate.js b/components/sidebars/SidebarCreateSlate.js index 675703d1..3225b49a 100644 --- a/components/sidebars/SidebarCreateSlate.js +++ b/components/sidebars/SidebarCreateSlate.js @@ -3,6 +3,7 @@ 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 Actions from "~/common/actions"; import { dispatchCustomEvent } from "~/common/custom-events"; import { css } from "@emotion/react"; @@ -87,6 +88,48 @@ export default class SidebarCreateSlate extends React.Component { 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.props.onAction({ type: "NAVIGATE", diff --git a/components/sidebars/SidebarHelp.js b/components/sidebars/SidebarHelp.js index b781abae..d5fb18e6 100644 --- a/components/sidebars/SidebarHelp.js +++ b/components/sidebars/SidebarHelp.js @@ -28,7 +28,7 @@ export default class SidebarCreateSlate extends React.Component { _handleSubmit = async () => { this.setState({ loading: true }); - if (!this.state.email || !this.state.email.length) { + if (Strings.isEmpty(this.state.email)) { dispatchCustomEvent({ name: "create-alert", detail: { @@ -54,17 +54,6 @@ export default class SidebarCreateSlate extends React.Component { 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({ username: this.props.viewer.username, name: this.state.name, diff --git a/components/system/components/Buttons.js b/components/system/components/Buttons.js index b39e60f6..a23ad5ef 100644 --- a/components/system/components/Buttons.js +++ b/components/system/components/Buttons.js @@ -11,32 +11,13 @@ const STYLES_BUTTON = ` outline: 0; border: 0; min-height: 40px; - padding: 6px 24px 6px 24px; + padding: 4px 16px; display: inline-flex; align-items: center; justify-content: center; - font-size: 12px; + font-size: 14px; letter-spacing: 0.2px; - font-family: ${Constants.font.semiBold}; - 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}; + font-family: ${Constants.font.medium}; transition: 200ms ease all; overflow-wrap: break-word; user-select: none; @@ -60,30 +41,25 @@ const STYLES_BUTTON_PRIMARY = css` } `; -const STYLES_BUTTON_PRIMARY_FULL = css` - ${STYLES_BUTTON_FULL} +const STYLES_BUTTON_PRIMARY_TRANSPARENT = css` + ${STYLES_BUTTON} + ${"" /* font-size: 16px; + font-family: ${Constants.font.medium}; */} cursor: pointer; - background-color: ${Constants.system.brand}; - color: ${Constants.system.white}; - - :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; - } + background-color: transparent; + color: ${Constants.system.brand}; `; export const ButtonPrimary = (props) => { if (props.loading) { return ( @@ -93,8 +69,12 @@ export const ButtonPrimary = (props) => { if (props.type === "label") { return (