From 543c6244053a844da2e7a090cfcadcc7863afead Mon Sep 17 00:00:00 2001 From: Arun Date: Fri, 15 Jan 2021 20:32:46 +0530 Subject: [PATCH] feat: Add toast (#2772) Co-authored-by: Lipis Co-authored-by: dwelle --- src/actions/actionStyles.ts | 5 ++ src/appState.ts | 2 + src/components/App.tsx | 12 ++++ src/components/Toast.scss | 32 +++++++++ src/components/Toast.tsx | 34 +++++++++ src/constants.ts | 1 + src/data/index.ts | 2 +- src/locales/en.json | 4 ++ .../regressionTests.test.tsx.snap | 71 ++++++++++++++++++- src/types.ts | 1 + 10 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 src/components/Toast.scss create mode 100644 src/components/Toast.tsx diff --git a/src/actions/actionStyles.ts b/src/actions/actionStyles.ts index 991f62b9..dab50b63 100644 --- a/src/actions/actionStyles.ts +++ b/src/actions/actionStyles.ts @@ -4,6 +4,7 @@ import { redrawTextBoundingBox, } from "../element"; import { CODES, KEYS } from "../keys"; +import { t } from "../i18n"; import { register } from "./register"; import { mutateElement, newElementWith } from "../element/mutateElement"; import { @@ -23,6 +24,10 @@ export const actionCopyStyles = register({ copiedStyles = JSON.stringify(element); } return { + appState: { + ...appState, + toastMessage: t("toast.copyStyles"), + }, commitToHistory: false, }; }, diff --git a/src/appState.ts b/src/appState.ts index 02036a43..dd2aeb8a 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -67,6 +67,7 @@ export const getDefaultAppState = (): Omit< showStats: false, startBoundElement: null, suggestedBindings: [], + toastMessage: null, viewBackgroundColor: oc.white, width: window.innerWidth, zenModeEnabled: false, @@ -145,6 +146,7 @@ const APP_STATE_STORAGE_CONF = (< showStats: { browser: true, export: false }, startBoundElement: { browser: false, export: false }, suggestedBindings: { browser: false, export: false }, + toastMessage: { browser: false, export: false }, viewBackgroundColor: { browser: true, export: true }, width: { browser: false, export: false }, zenModeEnabled: { browser: true, export: false }, diff --git a/src/components/App.tsx b/src/components/App.tsx index 3126d0f1..faca963c 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -158,6 +158,7 @@ import { import ContextMenu from "./ContextMenu"; import LayerUI from "./LayerUI"; import { Stats } from "./Stats"; +import { Toast } from "./Toast"; const { history } = createHistory(); @@ -376,6 +377,12 @@ class App extends React.Component { onClose={this.toggleStats} /> )} + {this.state.toastMessage !== null && ( + + )}
{ this.canvas!, this.state, ); + this.setState({ toastMessage: t("toast.copyToClipboardAsPng") }); } catch (error) { console.error(error); this.setState({ errorMessage: error.message }); @@ -1168,6 +1176,10 @@ class App extends React.Component { }); }; + clearToast = () => { + this.setState({ toastMessage: null }); + }; + public updateScene = withBatchedUpdates((sceneData: SceneData) => { if (sceneData.commitToHistory) { history.resumeRecording(); diff --git a/src/components/Toast.scss b/src/components/Toast.scss new file mode 100644 index 00000000..493e2fbd --- /dev/null +++ b/src/components/Toast.scss @@ -0,0 +1,32 @@ +@import "../css/_variables"; + +.excalidraw { + .Toast { + animation: fade-in 0.5s; + background-color: var(--button-gray-1); + border-radius: 4px; + bottom: 10px; + box-sizing: border-box; + cursor: default; + left: 50%; + margin-left: -150px; + padding: 4px 0; + position: fixed; + text-align: center; + width: 300px; + z-index: 999999; + } + + .Toast__message { + color: var(--popup-text-color); + } + + @keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } + } +} diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx new file mode 100644 index 00000000..d7ae9140 --- /dev/null +++ b/src/components/Toast.tsx @@ -0,0 +1,34 @@ +import React, { useCallback, useEffect, useRef } from "react"; +import { TOAST_TIMEOUT } from "../constants"; +import "./Toast.scss"; + +export const Toast = ({ + message, + clearToast, +}: { + message: string; + clearToast: () => void; +}) => { + const timerRef = useRef(0); + + const scheduleTimeout = useCallback( + () => + (timerRef.current = window.setTimeout(() => clearToast(), TOAST_TIMEOUT)), + [clearToast], + ); + + useEffect(() => { + scheduleTimeout(); + return () => clearTimeout(timerRef.current); + }, [scheduleTimeout, message]); + + return ( +
clearTimeout(timerRef?.current)} + onMouseLeave={scheduleTimeout} + > +

{message}

+
+ ); +}; diff --git a/src/constants.ts b/src/constants.ts index 4f22b35e..b577da57 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -89,3 +89,4 @@ export const STORAGE_KEYS = { export const TAP_TWICE_TIMEOUT = 300; export const TOUCH_CTX_MENU_TIMEOUT = 500; export const TITLE_TIMEOUT = 10000; +export const TOAST_TIMEOUT = 5000; diff --git a/src/data/index.ts b/src/data/index.ts index 41de22a4..a91411c9 100644 --- a/src/data/index.ts +++ b/src/data/index.ts @@ -36,7 +36,7 @@ export const exportCanvas = async ( }, ) => { if (elements.length === 0) { - return window.alert(t("alerts.cannotExportEmptyCanvas")); + throw new Error(t("alerts.cannotExportEmptyCanvas")); } if (type === "svg" || type === "clipboard-svg") { const tempSvg = exportToSvg(elements, { diff --git a/src/locales/en.json b/src/locales/en.json index b48b1056..23a64285 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -232,5 +232,9 @@ "title": "Stats for nerds", "total": "Total", "width": "Width" + }, + "toast": { + "copyStyles": "Copied styles.", + "copyToClipboardAsPng": "Copied to clipboard as PNG." } } diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index fe8a549d..12270bcb 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -74,6 +74,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -539,6 +540,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -986,6 +988,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -1761,6 +1764,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -1967,6 +1971,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -2417,6 +2422,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -2664,6 +2670,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -2828,6 +2835,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -3299,6 +3307,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -3607,6 +3616,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -3810,6 +3820,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -4049,6 +4060,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -4299,6 +4311,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -4699,6 +4712,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -4969,6 +4983,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -5293,6 +5308,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -5476,6 +5492,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -5637,6 +5654,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -6094,6 +6112,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -6402,6 +6421,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -8438,6 +8458,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -8798,6 +8819,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -9048,6 +9070,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -9299,6 +9322,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -9606,6 +9630,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -9767,6 +9792,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -9928,6 +9954,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -10089,6 +10116,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -10280,6 +10308,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -10471,6 +10500,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -10662,6 +10692,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -10853,6 +10884,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -11014,6 +11046,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -11175,6 +11208,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -11366,6 +11400,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -11527,6 +11562,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -11729,6 +11765,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -12435,6 +12472,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -12681,6 +12719,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -12778,6 +12817,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -12877,6 +12917,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -13038,6 +13079,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -13343,6 +13385,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -13648,6 +13691,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": "Copied styles.", "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -13744,7 +13788,7 @@ Object { exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of elements 1`] = `1`; -exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `6`; +exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `7`; exports[`regression tests selecting 'Delete' in context menu deletes element: [end of test] appState 1`] = ` Object { @@ -13807,6 +13851,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -14002,6 +14047,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -14254,6 +14300,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -14569,6 +14616,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": "Copied styles.", "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -15340,7 +15388,7 @@ Object { exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of elements 1`] = `2`; -exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `21`; +exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `22`; exports[`regression tests selecting 'Send backward' in context menu sends element backward: [end of test] appState 1`] = ` Object { @@ -15405,6 +15453,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -15710,6 +15759,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -16019,6 +16069,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -16394,6 +16445,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -16564,6 +16616,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -16876,6 +16929,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -17115,6 +17169,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -17369,6 +17424,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -17683,6 +17739,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -17782,6 +17839,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -17954,6 +18012,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -18759,6 +18818,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -18860,6 +18920,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -19635,6 +19696,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -20035,6 +20097,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -20281,6 +20344,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -20380,6 +20444,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -20873,6 +20938,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, @@ -20970,6 +21036,7 @@ Object { "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], + "toastMessage": null, "viewBackgroundColor": "#ffffff", "width": 1024, "zenModeEnabled": false, diff --git a/src/types.ts b/src/types.ts index 2cfa017d..24b2e815 100644 --- a/src/types.ts +++ b/src/types.ts @@ -82,6 +82,7 @@ export type AppState = { previousSelectedElementIds: { [id: string]: boolean }; shouldCacheIgnoreZoom: boolean; showShortcutsDialog: boolean; + toastMessage: string | null; zenModeEnabled: boolean; appearance: "light" | "dark"; gridSize: number | null;