diff --git a/common/user-behaviors.js b/common/user-behaviors.js index 99aa6d6c..84871cf4 100644 --- a/common/user-behaviors.js +++ b/common/user-behaviors.js @@ -69,11 +69,6 @@ export const signOut = async ({ viewer }) => { // NOTE(jim): Permanently deletes you, forever. export const deleteMe = async ({ viewer }) => { - const message = "Do you really want to delete your account? It will be permanently removed"; - if (!window.confirm(message)) { - return false; - } - await Actions.updateSearch("delete-user"); let response = await Actions.deleteViewer(); diff --git a/components/core/ApplicationLayout.js b/components/core/ApplicationLayout.js index bc6b69fc..ca5746f5 100644 --- a/components/core/ApplicationLayout.js +++ b/components/core/ApplicationLayout.js @@ -51,13 +51,11 @@ const STYLES_CONTENT = css` } `; -const STYLES_SIDEBAR = css` - z-index: ${Constants.zindex.sidebar}; +const STYLES_SIDEBAR_ELEMENTS = css` height: 100vh; width: ${Constants.sizes.sidebar}px; padding: 0; flex-shrink: 0; - position: fixed; background-color: rgba(195, 195, 196, 1); top: 0; right: 0; @@ -66,12 +64,20 @@ const STYLES_SIDEBAR = css` @media (max-width: ${Constants.sizes.mobile}px) { width: 100%; } - + /* @supports ((-webkit-backdrop-filter: blur(25px)) or (backdrop-filter: blur(25px))) { -webkit-backdrop-filter: blur(25px); backdrop-filter: blur(25px); background-color: rgba(195, 195, 196, 0.6); } + */ +`; + +const STYLES_SIDEBAR = css` + position: fixed; + top: 0; right: 0; + margin: auto; + z-index: ${Constants.zindex.sidebar}; `; const STYLES_SIDEBAR_HEADER = css` @@ -202,13 +208,15 @@ export default class ApplicationLayout extends React.Component { enabled onOutsideRectEvent={this._handleDismiss} > -
{ - this._sidebar = c; - }} - > - {sidebarElements} +
+
{ + this._sidebar = c; + }} + > + {sidebarElements} +
) : null} diff --git a/components/core/CarouselSidebar.js b/components/core/CarouselSidebar.js index 3d4f936a..384dd9ad 100644 --- a/components/core/CarouselSidebar.js +++ b/components/core/CarouselSidebar.js @@ -21,6 +21,7 @@ import { Tag } from "~/components/system/components/Tag"; import isEqual from "lodash/isEqual"; import cloneDeep from "lodash/cloneDeep"; import ProcessedText from "~/components/core/ProcessedText"; +import { ConfirmationModal } from "~/components/core/ConfirmationModal"; const DEFAULT_BOOK = "https://slate.textile.io/ipfs/bafkreibk32sw7arspy5kw3p5gkuidfcwjbwqyjdktd5wkqqxahvkm2qlyi"; @@ -38,7 +39,6 @@ const STYLES_NO_VISIBLE_SCROLL = css` scrollbar-width: none; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; - ::-webkit-scrollbar { width: 0px; display: none; @@ -77,13 +77,11 @@ const STYLES_SIDEBAR = css` justify-content: space-between; background-color: rgba(20, 20, 20, 0.8); ${STYLES_NO_VISIBLE_SCROLL} - @supports ((-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))) { -webkit-backdrop-filter: blur(75px); backdrop-filter: blur(75px); background-color: rgba(150, 150, 150, 0.2); } - @media (max-width: ${Constants.sizes.mobile}px) { display: none; } @@ -95,7 +93,6 @@ const STYLES_DISMISS_BOX = css` right: 16px; color: ${Constants.system.darkGray}; cursor: pointer; - :hover { color: ${Constants.system.white}; } @@ -123,7 +120,6 @@ const STYLES_META_TITLE = css` text-decoration: none; word-break: break-all; overflow-wrap: anywhere; - :hover { color: ${Constants.system.blue}; } @@ -173,11 +169,9 @@ const STYLES_ACTION = css` border-bottom: 1px solid #3c3c3c; display: flex; align-items: center; - :hover { color: ${Constants.system.brand}; } - :last-child { border: none; } @@ -243,7 +237,6 @@ const STYLES_AUTOSAVE = css` position: absolute; top: 24px; left: 16px; - @keyframes slate-animations-autosave { 0% { opacity: 0; @@ -300,6 +293,7 @@ class CarouselSidebar extends React.Component { showSavedMessage: false, showConnectedSection: false, showFileSection: true, + modalShow: false, }; componentDidMount = () => { @@ -449,11 +443,11 @@ class CarouselSidebar extends React.Component { }); }; - _handleDelete = () => { + _handleDelete = (res) => { if (this.props.external || !this.props.isOwner) return; - const message = - "Are you sure you want to delete this? It will be removed from your collections as well"; - if (!window.confirm(message)) { + + if (!res) { + this.setState({ modalShow: false }); return; } const id = this.props.data.id; @@ -745,7 +739,7 @@ class CarouselSidebar extends React.Component { if (editingAllowed) { actions.push( -
+
this.setState({ modalShow: true })}> Delete
@@ -847,70 +841,81 @@ class CarouselSidebar extends React.Component { } return ( -
- {this.state.showSavedMessage && ( -
- - Changes saved -
+ <> + {this.state.modalShow && ( + )} -
- -
- -
- {elements} - - {!this.props.external &&
{actions}
} - {privacy} - {uploadCoverImage} - {!this.props.external && ( - <> -
this._handleToggleAccordion("showConnectedSection")} - > - - - - Add to collection -
- {this.state.showConnectedSection && ( -
- -
- )} - +
+ {this.state.showSavedMessage && ( +
+ + Changes saved +
)} +
+ +
- {this.props.data.filename.endsWith(".md") ? ( - <> -
- Settings -
-
-
Dark mode
- -
- - ) : null} +
+ {elements} + + {!this.props.external &&
{actions}
} + {privacy} + {uploadCoverImage} + {!this.props.external && ( + <> +
this._handleToggleAccordion("showConnectedSection")} + > + + + + Add to collection +
+ {this.state.showConnectedSection && ( +
+ +
+ )} + + )} + + {this.props.data.filename.endsWith(".md") ? ( + <> +
+ Settings +
+
+
Dark mode
+ +
+ + ) : null} +
-
+ ); } } @@ -932,4 +937,4 @@ export default withTheme(CarouselSidebar); : "This file is currently not visible to others unless they have the link."}
*/ -} +} \ No newline at end of file diff --git a/components/core/ConfirmationModal.js b/components/core/ConfirmationModal.js new file mode 100644 index 00000000..b0ffc41e --- /dev/null +++ b/components/core/ConfirmationModal.js @@ -0,0 +1,128 @@ +import * as React from "react"; +import * as Constants from "~/common/constants"; + +import { css } from "@emotion/react"; +import { useState } from "react"; + +import { ButtonPrimaryFull, ButtonSecondaryFull, ButtonWarningFull } from "~/components/system/components/Buttons.js"; +import { Input } from "~/components/system/components/Input.js"; +import { Boundary } from "~/components/system/components/fragments/Boundary.js"; + +const STYLES_TRANSPARENT_BG = css ` + background-color: ${Constants.system.bgBlurGrayBlack}; + z-index: ${Constants.zindex.modal}; + width: 100vw; + height: 100vh; + position: fixed; + left: 0; + top: 0; +`; + +const STYLES_MAIN_MODAL = css ` + background-color: ${Constants.system.white}; + width: 380px; + height: auto; + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + border-radius: 8px; + padding: 24px; + text-align: left; +`; + +const STYLES_HEADER = css ` + color: ${Constants.system.black}; + font-size: ${Constants.typescale.lvl1}; + font-family: ${Constants.font.semiBold}; +`; + +const STYLES_SUB_HEADER = css ` + color: ${Constants.system.textGray}; + font-size: ${Constants.typescale.lvl0}; + font-family: ${Constants.font.text}; + margin-top: 16px; +`; + +const STYLES_INPUT_HEADER = css ` + color: ${Constants.system.black}; + font-size: ${Constants.typescale.lvlN1}; + font-family: ${Constants.font.semiBold}; + font-weight: bold; + margin-top: 24px; + margin-bottom: 8px; +`; + +export const ConfirmationModal = (props) => { + const [isEnabled, setIsEnabled] = useState(false); + + const lang = { + deleteText: 'Delete', + confirmText: 'Confirm', + cancelText: 'Cancel' + } + + const _handleChange = (e) => { + if (e.target.value === props.matchValue) { + setIsEnabled(true); + return; + } + setIsEnabled(false); + } + + let deleteButton = {props.buttonText || lang.deleteText}; + if (isEnabled) { + deleteButton = props.callback(true)}>{props.buttonText || lang.deleteText}; + } + + let confirmButton = {props.buttonText || lang.confirmText}; + if (isEnabled) { + confirmButton = props.callback(true)}>{props.buttonText || lang.confirmText}; + } + + return ( +
+ props.callback(false)}> +
+
{props.header}
+
{props.subHeader}
+ {props.type === "DELETE" && + <> + {props.withValidation ? ( + <> +
{props.inputHeader}
+ + props.callback(false)} style={{margin: '24px 0px 8px'}}>{lang.cancelText} + {deleteButton} + + ) : ( + <> + props.callback(false)} style={{ margin: '24px 0px 8px' }}>{lang.cancelText} + props.callback(true)}>{props.buttonText || lang.deleteText} + + )} + + } + + {props.type === "CONFIRM" && + <> + {props.withValidation ? ( + <> +
{props.inputHeader}
+ + props.callback(false)} style={{ margin: '24px 0px 8px' }}>{lang.cancelText} + {confirmButton} + + ) : ( + <> + props.callback(false)} style={{ margin: '24px 0px 8px' }}>{lang.cancelText} + props.callback(true)}>{props.buttonText || lang.confirmText} + + )} + + } +
+
+
+ ); +}; diff --git a/components/core/DataView.js b/components/core/DataView.js index 117ba1e3..4adbae2f 100644 --- a/components/core/DataView.js +++ b/components/core/DataView.js @@ -20,10 +20,10 @@ import { GroupSelectable, Selectable } from "~/components/core/Selectable/"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; import FilePreviewBubble from "~/components/core/FilePreviewBubble"; import isEqual from "lodash/isEqual"; +import { ConfirmationModal } from "~/components/core/ConfirmationModal"; const STYLES_CONTAINER_HOVER = css` display: flex; - :hover { color: ${Constants.system.brand}; } @@ -67,7 +67,6 @@ const STYLES_LINK = css` text-overflow: ellipsis; white-space: nowrap; max-width: 320px; - @media (max-width: ${Constants.sizes.tablet}px) { max-width: 120px; } @@ -86,7 +85,6 @@ const STYLES_ICON_BOX_HOVER = css` align-items: center; padding: 8px; cursor: pointer; - :hover { color: ${Constants.system.brand}; } @@ -117,7 +115,6 @@ const STYLES_ACTION_BAR = css` width: 90vw; max-width: 878px; height: 48px; - @media (max-width: ${Constants.sizes.mobile}px) { display: none; } @@ -131,7 +128,6 @@ const STYLES_ACTION_BAR_CONTAINER = css` display: flex; justify-content: center; z-index: ${Constants.zindex.header}; - @media (max-width: ${Constants.sizes.mobile}px) { display: none; } @@ -153,7 +149,6 @@ const STYLES_LEFT = css` const STYLES_FILES_SELECTED = css` font-family: ${Constants.font.semiBold}; color: ${Constants.system.white}; - @media (max-width: ${Constants.sizes.mobile}px) { display: none; } @@ -171,7 +166,6 @@ const STYLES_IMAGE_GRID = css` grid-column-gap: 20px; grid-row-gap: 20px; width: 100%; - @media (max-width: ${Constants.sizes.mobile}px) { grid-template-columns: repeat(2, 1fr); } @@ -185,11 +179,9 @@ const STYLES_IMAGE_BOX = css` justify-content: center; cursor: pointer; position: relative; - @media (max-width: ${Constants.sizes.mobile}px) { margin: 12px auto; } - :hover { box-shadow: 0px 0px 0px 1px ${Constants.system.lightBorder} inset, 0 0 40px 0 ${Constants.system.shadow}; @@ -223,12 +215,10 @@ const STYLES_TAG = css` font-family: ${Constants.font.text}; padding: 2px 8px; margin: 8px 8px 0 0; - span { line-height: 1.5; font-size: 14px; } - &:hover { background: ${Constants.system.gray30}; } @@ -307,6 +297,7 @@ export default class DataView extends React.Component { viewLimit: 40, scrollDebounce: false, imageSize: 100, + modalShow: false, }; isShiftDown = false; @@ -474,9 +465,9 @@ export default class DataView extends React.Component { this.setState({ checked: {} }); }; - _handleDelete = (id) => { - const message = `Are you sure you want to delete these files? They will be deleted from your collections as well`; - if (!window.confirm(message)) { + _handleDelete = (res, id) => { + if (!res) { + this.setState({ modalShow: false }); return; } @@ -495,7 +486,7 @@ export default class DataView extends React.Component { this.props.onUpdateViewer({ library }); UserBehaviors.deleteFiles(ids); - this.setState({ checked: {} }); + this.setState({ checked: {}, modalShow: false }); }; _handleSelect = (index) => { @@ -600,7 +591,6 @@ export default class DataView extends React.Component { return commonTags; }; - render() { let numChecked = Object.keys(this.state.checked).length || 0; // const header = ( @@ -637,6 +627,7 @@ export default class DataView extends React.Component { // //
// ); + const footer = ( {numChecked ? ( @@ -684,11 +675,20 @@ export default class DataView extends React.Component { this._handleDelete()} + onClick={() => this.setState({ modalShow: true })} > {Strings.pluralize("Delete file", numChecked)} )} + {this.state.modalShow && ( + + )}
{ @@ -969,7 +969,7 @@ export default class DataView extends React.Component { text: "Delete", onClick: (e) => { e.stopPropagation(); - this.setState({ menu: null }, () => this._handleDelete(each.id)); + this.setState({ menu: null, modalShow: true }); }, }, ]} diff --git a/components/core/SlateLayout.js b/components/core/SlateLayout.js index bb8a1b3d..c4637a42 100644 --- a/components/core/SlateLayout.js +++ b/components/core/SlateLayout.js @@ -24,6 +24,7 @@ import { ButtonWarning, } from "~/components/system/components/Buttons"; import { GroupSelectable, Selectable } from "~/components/core/Selectable/"; +import { ConfirmationModal } from "~/components/core/ConfirmationModal"; //NOTE(martina): sets 200px as the standard width for a 1080px wide layout with 20px margin btwn images. //If the container is larger or smaller, it scales accordingly by that factor @@ -328,6 +329,7 @@ export class SlateLayout extends React.Component { tooltip: null, keyboardTooltip: false, signInModal: false, + modalShowDeleteFiles: false, }; componentDidMount = async () => { @@ -887,10 +889,9 @@ export class SlateLayout extends React.Component { this.setState(state); }; - _handleResetLayout = async () => { - if ( - !window.confirm("Are you sure you want to reset your layout to the default column layout?") - ) { + _handleResetLayout = async (res) => { + if (!res) { + this.setState({ modalShowResetLayout: false }); return; } let prevLayout = this.cloneLayout(this.state.layout); @@ -907,6 +908,7 @@ export class SlateLayout extends React.Component { ], layout, zIndexMax: 1, + modalShowResetLayout: false, }); }; @@ -1049,14 +1051,15 @@ export class SlateLayout extends React.Component { e.dataTransfer.setData("DownloadURL", `${type}:${title}:${url}`); }; - _handleDeleteFiles = async (e, i) => { - const message = `Are you sure you want to delete these files? They will be deleted from your data and collections.`; - if (!window.confirm(message)) { + _handleDeleteModal = () => { + this.setState({ modalShowDeleteFiles: true }) + } + + _handleDeleteFiles = async (res, i) => { + if (!res) { + this.setState({ modalShowDeleteFiles: false }); return; } - - e.stopPropagation(); - e.preventDefault(); let ids = []; if (i !== undefined) { ids = [this.state.items[i].id.replace("data-", "")]; @@ -1078,7 +1081,7 @@ export class SlateLayout extends React.Component { } await UserBehaviors.deleteFiles(ids); - this.setState({ checked: {} }); + this.setState({ checked: {}, modalShowDeleteFiles: false }); }; _stopProp = (e) => { @@ -1134,10 +1137,19 @@ export class SlateLayout extends React.Component { Reset layout ) : ( - + { this.setState({ modalShowResetLayout: true }) }} style={{ marginRight: 16 }}> Reset layout )} + {this.state.modalShowResetLayout && ( + + )} {this.state.prevLayouts.length ? ( Undo @@ -1453,8 +1465,8 @@ export class SlateLayout extends React.Component { onMouseLeave={() => this.setState({ tooltip: null })} onClick={ this.state.items[i].ownerId === this.props.viewer.id - ? (e) => { - this._handleDeleteFiles(e, i); + ? () => { + this.setState({ modalShowDeleteFiles: true }) } : () => {} } @@ -1475,6 +1487,7 @@ export class SlateLayout extends React.Component { }} />
+
) : ( @@ -1674,6 +1687,15 @@ export class SlateLayout extends React.Component { )} + {this.state.modalShowDeleteFiles && ( + + )} {numChecked ? (
@@ -1710,7 +1732,7 @@ export class SlateLayout extends React.Component { {Strings.pluralize("Delete file", numChecked)} diff --git a/components/sidebars/SidebarSingleSlateSettings.js b/components/sidebars/SidebarSingleSlateSettings.js index 99275e80..91dc98e9 100644 --- a/components/sidebars/SidebarSingleSlateSettings.js +++ b/components/sidebars/SidebarSingleSlateSettings.js @@ -10,6 +10,7 @@ import * as UserBehaviors from "~/common/user-behaviors"; import { RadioGroup } from "~/components/system/components/RadioGroup"; import { css } from "@emotion/react"; +import { ConfirmationModal } from "~/components/core/ConfirmationModal"; const SIZE_LIMIT = 1000000; const DEFAULT_IMAGE = @@ -52,6 +53,7 @@ export default class SidebarSingleSlateSettings extends React.Component { name: this.props.data.data.name, tags: this.props.data.data?.tags || [], suggestions: this.props.viewer?.tags || [], + modalShow: false, }; componentDidMount = () => { @@ -107,12 +109,9 @@ export default class SidebarSingleSlateSettings extends React.Component { }); }; - _handleDelete = async (e) => { - if ( - !window.confirm( - "Are you sure you want to delete this Collection? This action is irreversible." - ) - ) { + _handleDelete = async (res) => { + if (!res) { + this.setState({ modalShow: false }) return; } @@ -130,6 +129,8 @@ export default class SidebarSingleSlateSettings extends React.Component { if (Events.hasError(response)) { return; } + + this.setState({ modalShow: false }) }; render() { @@ -303,11 +304,20 @@ export default class SidebarSingleSlateSettings extends React.Component {
- + this.setState({ modalShow: true })} style={{ overflow: "hidden" }}> Delete collection
+ {this.state.modalShow && ( + + )} ); } diff --git a/components/system/components/Buttons.js b/components/system/components/Buttons.js index f5d97347..ff92f992 100644 --- a/components/system/components/Buttons.js +++ b/components/system/components/Buttons.js @@ -39,6 +39,13 @@ const STYLES_BUTTON_PRIMARY = css` } `; +const STYLES_BUTTON_PRIMARY_DISABLED = css` + ${STYLES_BUTTON} + cursor: not-allowed; + background-color: ${Constants.system.bgBlue}; + color: ${Constants.system.white}; +`; + const STYLES_BUTTON_PRIMARY_TRANSPARENT = css` ${STYLES_BUTTON} cursor: pointer; @@ -71,6 +78,17 @@ export const ButtonPrimary = (props) => { ); } + if (props.disabled) { + return ( +