diff --git a/locales/en-US/server.ftl b/locales/en-US/server.ftl index 1d850e7907..9259b9803a 100644 --- a/locales/en-US/server.ftl +++ b/locales/en-US/server.ftl @@ -261,6 +261,15 @@ shotIndexPageNextPage = shotIndexNoExpirationSymbol = ∞ .title = This shot does not expire + +## Delete Confirmation Dialog + +shotDeleteConfirmationMessage = Are you sure you want to delete this shot? +shotDeleteCancel = Cancel + .title = Cancel +shotDeleteConfirm = Delete + .title = Delete + ## Metrics page ## All metrics strings are optional for translation diff --git a/server/src/delete-shot-button.js b/server/src/delete-shot-button.js new file mode 100644 index 0000000000..a26f32ab77 --- /dev/null +++ b/server/src/delete-shot-button.js @@ -0,0 +1,96 @@ +const React = require("react"); +const PropTypes = require("prop-types"); +const { Localized } = require("fluent-react/compat"); + +exports.DeleteShotButton = class DeleteShotButton extends React.Component { + constructor(props) { + super(props); + this.elRef = React.createRef(); + this.trashButtonRef = React.createRef(); + this.state = {confirmationPanelOpen: false} + this.maybeCloseDeleteConfirmation = this.maybeCloseDeleteConfirmation.bind(this); + } + + componentDidUpdate() { + if (this.state.confirmationPanelOpen) { + document.addEventListener("mousedown", this.maybeCloseDeleteConfirmation); + } else { + document.removeEventListener("mousedown", this.maybeCloseDeleteConfirmation); + } + } + + componentWillUnmount() { + document.removeEventListener("mousedown", this.maybeCloseDeleteConfirmation); + } + + onClickDelete(e) { + e.stopPropagation(); + e.preventDefault(); + this.props.clickDeleteHandler && this.props.clickDeleteHandler(); + this.setState({confirmationPanelOpen: true}); + } + + onConfirmDelete(e) { + e.stopPropagation(); + this.props.confirmDeleteHandler && this.props.confirmDeleteHandler(); + this.setState({confirmationPanelOpen: false}); + } + + onCancelDelete(e) { + e.stopPropagation(); + this.props.cancelDeleteHandler && this.props.cancelDeleteHandler(); + this.setState({confirmationPanelOpen: false}); + } + + maybeCloseDeleteConfirmation(e) { + if (this.elRef.current === e.target || this.elRef.current.contains(e.target)) { + return; + } + + this.onCancelDelete(e); + } + + render() { + let rightAlign = ""; + if (this.trashButtonRef.current && (this.trashButtonRef.current.getBoundingClientRect().left + 320) > document.body.scrollWidth) { + rightAlign = "right-align"; + } + + let confirmationPanel = null, deletePanelOpenClass = null; + if (this.state.confirmationPanelOpen) { + confirmationPanel =
+
+ +
Are you sure you want to delete this shot?
+
+
+ + + + + + +
+
; + deletePanelOpenClass = "active"; + } + + return ( +
+ + + + { confirmationPanel } +
+ ); + } +} + +exports.DeleteShotButton.propTypes = { + clickDeleteHandler: PropTypes.func, + confirmDeleteHandler: PropTypes.func, + cancelDeleteHandler: PropTypes.func +} diff --git a/server/src/pages/shot/view.js b/server/src/pages/shot/view.js index 2da716be11..a81921d910 100644 --- a/server/src/pages/shot/view.js +++ b/server/src/pages/shot/view.js @@ -5,6 +5,7 @@ const { Localized } = require("fluent-react/compat"); const { Footer } = require("../../footer-view"); const sendEvent = require("../../browser-send-event.js"); const { ShareButton } = require("../../share-buttons"); +const { DeleteShotButton } = require("../../delete-shot-button"); const { TimeDiff } = require("./time-diff"); const reactruntime = require("../../reactruntime"); const { Editor } = require("./editor"); @@ -182,15 +183,17 @@ class Body extends React.Component { this.setState({closeBanner: true}); } - onClickDelete(e) { + clickDeleteHandler() { sendEvent("start-delete", "navbar", {useBeacon: true}); - const confirmMessage = document.getElementById("shotPageConfirmDelete").textContent; - if (window.confirm(confirmMessage)) { - sendEvent("delete", "popup-confirm", {useBeacon: true}); - this.props.controller.deleteShot(this.props.shot); - } else { - sendEvent("cancel-delete", "popup-confirm"); - } + } + + confirmDeleteHandler() { + sendEvent("delete", "popup-confirm", {useBeacon: true}); + this.props.controller.deleteShot(this.props.shot); + } + + cancelDeleteHandler() { + sendEvent("cancel-delete", "popup-confirm"); } onClickFlag(e) { @@ -346,9 +349,10 @@ class Body extends React.Component { let editButton; const highlight = this.props.highlightEditButton ?
: null; if (this.props.isOwner) { - trashOrFlagButton = - - ; + trashOrFlagButton = ; editButton = ; diff --git a/server/src/pages/shotindex/view.js b/server/src/pages/shotindex/view.js index eb4614dce3..7499ae59c1 100644 --- a/server/src/pages/shotindex/view.js +++ b/server/src/pages/shotindex/view.js @@ -9,6 +9,7 @@ const Masonry = require("react-masonry-component"); const { Localized } = require("fluent-react/compat"); const { isValidClipImageUrl } = require("../../../shared/shot"); const { getThumbnailDimensions } = require("../../../shared/thumbnailGenerator"); +const { DeleteShotButton } = require("../../delete-shot-button"); class Head extends React.Component { @@ -331,8 +332,17 @@ class Card extends React.Component { neverExpireIndicator =
} + const deleteConfirmationClass = this.state.deletePanelOpen ? "panel-open" : ""; + return ( -
+
@@ -355,9 +365,10 @@ class Card extends React.Component { title="Download the shot image" ref={downloadButton => this.downloadButton = downloadButton} /> - -
{neverExpireIndicator}
@@ -426,20 +437,20 @@ class Card extends React.Component { return title; } - onClickDelete(shot, event) { - event.stopPropagation(); - event.preventDefault(); + clickDeleteHandler(event) { sendEvent("start-delete", "my-shots", {useBeacon: true}); - const confirmMessage = document.getElementById("shotIndexPageConfirmShotDelete").textContent; - if (window.confirm(confirmMessage)) { - sendEvent("delete", "my-shots-popup-confirm", {useBeacon: true}); - this.setState({deleted: true}); - controller.deleteShot(shot); - } else { - sendEvent("cancel-delete", "my-shots-popup-confirm"); - } - this.trashButton.blur(); - return false; + this.setState({ deletePanelOpen: true }); + } + + confirmDeleteHandler(shot, event) { + sendEvent("delete", "my-shots-popup-confirm", { useBeacon: true }); + controller.deleteShot(shot); + this.setState({ deleted: true, deletePanelOpen: false }); + } + + cancelDeleteHandler(event) { + this.setState({ deletePanelOpen: false }); + sendEvent("cancel-delete", "my-shots-popup-confirm"); } onClickDownload() { diff --git a/static/css/frame.scss b/static/css/frame.scss index 508d4f83f9..d7cc5b447f 100644 --- a/static/css/frame.scss +++ b/static/css/frame.scss @@ -1,4 +1,5 @@ @import "partials/partials"; +@import "partials/delete-confirmation"; .frame-header { @include respond-to("medium") { @@ -77,6 +78,13 @@ } } +.delete-confirmation-dialog { + &.right-align { + top: 48px; + } +} + + .back-to-index { @include respond-to("small") { background-position: left -5px center; diff --git a/static/css/partials/_buttons.scss b/static/css/partials/_buttons.scss index bec99599f1..a3333bd8ef 100644 --- a/static/css/partials/_buttons.scss +++ b/static/css/partials/_buttons.scss @@ -22,6 +22,10 @@ padding: 0 8px; } + &.active { + background-color: $light-active; + } + &.tiny { font-size: 14px; height: 26px; diff --git a/static/css/partials/_delete-confirmation.scss b/static/css/partials/_delete-confirmation.scss new file mode 100644 index 0000000000..df12472d6d --- /dev/null +++ b/static/css/partials/_delete-confirmation.scss @@ -0,0 +1,66 @@ +.delete-shot-button { + position: relative; +} + +.delete-confirmation-dialog { + background-color: #fff; + border: 1px solid #c7c7c7 ; + border-radius: 3px; + box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.25), 0 0 0 0 rgba(0, 0, 0, 0.29), inset 0 0 0 0 #fff; + min-height: 114px; + min-width: 320px; + padding: 18px; + position: absolute; + left: 3px; + top: 47px; + z-index: 3; + + .triangle { + position: absolute; + width: 0; + height: 0; + border-left: 9px solid transparent; + border-right: 9px solid transparent; + border-bottom: 15px solid $light-border; + top: -15px; + left: 8px; + + .triangle-inner { + position: absolute; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 13px solid #fff; + left: -8px; + top: 2px; + } + } + .delete-confirmation-message { + font-size: 15px; + } + .delete-confirmation-buttons { + display: flex; + justify-content: space-between; + margin-top: 18px; + + button { + &.primary { + width: 135px; + height: 40px; + } + &.secondary { + border: 1px solid #c7c7c7; + width: 133px; + height: 38px; + } + } + } + + &.right-align { + left: -281px; + .triangle { + left: 292px; + } + } +} diff --git a/static/css/shot-index.scss b/static/css/shot-index.scss index 987a771998..0b6432e8d1 100644 --- a/static/css/shot-index.scss +++ b/static/css/shot-index.scss @@ -1,4 +1,5 @@ @import "partials/partials"; +@import "partials/delete-confirmation"; //Shot index page styles