From 39d17c4a3ca371fe8515a1d21cdc95de00d146cb Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 22 Jun 2022 22:06:29 +0530 Subject: [PATCH 01/11] fix: delay loading until language imported (#5344) --- src/components/InitializeApp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/InitializeApp.tsx b/src/components/InitializeApp.tsx index f6ff278d38..c1941512a0 100644 --- a/src/components/InitializeApp.tsx +++ b/src/components/InitializeApp.tsx @@ -14,11 +14,11 @@ export const InitializeApp = (props: Props) => { useEffect(() => { const updateLang = async () => { await setLanguage(currentLang); + setLoading(false); }; const currentLang = languages.find((lang) => lang.code === props.langCode) || defaultLang; updateLang(); - setLoading(false); }, [props.langCode]); return loading ? : props.children; From 50bc7e099ae5ecc75d449d2e16bf783f6b4acd17 Mon Sep 17 00:00:00 2001 From: David Luzar Date: Thu, 23 Jun 2022 17:27:15 +0200 Subject: [PATCH 02/11] fix: unable to use cmd/ctrl-delete/backspace in inputs (#5348) --- src/components/App.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 4a861f085e..a16fcdf5e6 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1731,11 +1731,13 @@ class App extends React.Component { }); } + // bail if if ( + // inside an input (isWritableElement(event.target) && - !event[KEYS.CTRL_OR_CMD] && + // unless pressing escape (finalize action) event.key !== KEYS.ESCAPE) || - // case: using arrows to move between buttons + // or unless using arrows (to move between buttons) (isArrowKey(event.key) && isInputLike(event.target)) ) { return; From af31e9dcc2a0851b5e988e69e5743183a4b6ba0f Mon Sep 17 00:00:00 2001 From: David Luzar Date: Thu, 23 Jun 2022 17:33:50 +0200 Subject: [PATCH 03/11] fix: focus traps inside popovers (#5317) --- src/components/ColorPicker.tsx | 17 +++------------- src/components/Dialog.tsx | 9 +-------- src/components/Popover.tsx | 37 ++++++++++++++++++++++++++++++++++ src/utils.ts | 13 ++++++++++++ 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/components/ColorPicker.tsx b/src/components/ColorPicker.tsx index fd2847523c..76a66b5bf4 100644 --- a/src/components/ColorPicker.tsx +++ b/src/components/ColorPicker.tsx @@ -129,19 +129,7 @@ const Picker = ({ const handleKeyDown = (event: React.KeyboardEvent) => { let handled = false; - if (event.key === KEYS.TAB) { - handled = true; - const { activeElement } = document; - if (event.shiftKey) { - if (activeElement === firstItem.current) { - colorInput.current?.focus(); - event.preventDefault(); - } - } else if (activeElement === colorInput.current) { - firstItem.current?.focus(); - event.preventDefault(); - } - } else if (isArrowKey(event.key)) { + if (isArrowKey(event.key)) { handled = true; const { activeElement } = document; const isRTL = getLanguage().rtl; @@ -272,7 +260,8 @@ const Picker = ({ gallery.current = el; } }} - tabIndex={0} + // to allow focusing by clicking but not by tabbing + tabIndex={-1} >
{renderColors(colors)} diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx index 06615101ee..f406a8bba8 100644 --- a/src/components/Dialog.tsx +++ b/src/components/Dialog.tsx @@ -9,6 +9,7 @@ import { back, close } from "./icons"; import { Island } from "./Island"; import { Modal } from "./Modal"; import { AppState } from "../types"; +import { queryFocusableElements } from "../utils"; export interface DialogProps { children: React.ReactNode; @@ -64,14 +65,6 @@ export const Dialog = (props: DialogProps) => { return () => islandNode.removeEventListener("keydown", handleKeyDown); }, [islandNode, props.autofocus]); - const queryFocusableElements = (node: HTMLElement) => { - const focusableElements = node.querySelectorAll( - "button, a, input, select, textarea, div[tabindex]", - ); - - return focusableElements ? Array.from(focusableElements) : []; - }; - const onClose = () => { (lastActiveElement as HTMLElement).focus(); props.onCloseRequest(); diff --git a/src/components/Popover.tsx b/src/components/Popover.tsx index ca47d72832..ba424f3aed 100644 --- a/src/components/Popover.tsx +++ b/src/components/Popover.tsx @@ -1,6 +1,8 @@ import React, { useLayoutEffect, useRef, useEffect } from "react"; import "./Popover.scss"; import { unstable_batchedUpdates } from "react-dom"; +import { queryFocusableElements } from "../utils"; +import { KEYS } from "../keys"; type Props = { top?: number; @@ -27,6 +29,41 @@ export const Popover = ({ }: Props) => { const popoverRef = useRef(null); + const container = popoverRef.current; + + useEffect(() => { + if (!container) { + return; + } + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === KEYS.TAB) { + const focusableElements = queryFocusableElements(container); + const { activeElement } = document; + const currentIndex = focusableElements.findIndex( + (element) => element === activeElement, + ); + + if (currentIndex === 0 && event.shiftKey) { + focusableElements[focusableElements.length - 1].focus(); + event.preventDefault(); + event.stopImmediatePropagation(); + } else if ( + currentIndex === focusableElements.length - 1 && + !event.shiftKey + ) { + focusableElements[0].focus(); + event.preventDefault(); + event.stopImmediatePropagation(); + } + } + }; + + container.addEventListener("keydown", handleKeyDown); + + return () => container.removeEventListener("keydown", handleKeyDown); + }, [container]); + // ensure the popover doesn't overflow the viewport useLayoutEffect(() => { if (fitInViewport && popoverRef.current) { diff --git a/src/utils.ts b/src/utils.ts index d81ddfecf7..2e651ef8f7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -668,3 +668,16 @@ export const isPromiseLike = ( "finally" in value ); }; + +export const queryFocusableElements = (container: HTMLElement | null) => { + const focusableElements = container?.querySelectorAll( + "button, a, input, select, textarea, div[tabindex], label[tabindex]", + ); + + return focusableElements + ? Array.from(focusableElements).filter( + (element) => + element.tabIndex > -1 && !(element as HTMLInputElement).disabled, + ) + : []; +}; From 9135ebf2e281b177dd1273f123ccb0784353cac6 Mon Sep 17 00:00:00 2001 From: David Luzar Date: Thu, 23 Jun 2022 17:42:50 +0200 Subject: [PATCH 04/11] feat: redirect vscode.excalidraw.com to vscode marketplace (#5285) --- vercel.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vercel.json b/vercel.json index 316d4ce667..a621be1db3 100644 --- a/vercel.json +++ b/vercel.json @@ -27,6 +27,16 @@ { "source": "/webex/:match*", "destination": "https://for-webex.excalidraw.com" + }, + { + "source": "/:path*", + "has": [ + { + "type": "host", + "value": "vscode.excalidraw.com" + } + ], + "destination": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor" } ] } From 120c8f373c91512378c3150f0419ff3b291d3613 Mon Sep 17 00:00:00 2001 From: David Luzar Date: Sat, 25 Jun 2022 20:10:53 +0200 Subject: [PATCH 05/11] fix: library not scrollable when no published items installed (#5352) * fix: library not scrollable when no published items installed * show empty lib message in one case & fix i18n --- src/components/LibraryMenuItems.tsx | 22 +++++++++++++++++++--- src/locales/en.json | 1 + 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/components/LibraryMenuItems.tsx b/src/components/LibraryMenuItems.tsx index 2225185933..01223baed8 100644 --- a/src/components/LibraryMenuItems.tsx +++ b/src/components/LibraryMenuItems.tsx @@ -428,7 +428,7 @@ const LibraryMenuItems = ({ align="start" gap={1} style={{ - flex: publishedItems.length > 0 ? 1 : "0 0 auto", + flex: publishedItems.length > 0 ? 1 : "0 1 auto", marginBottom: 0, }} > @@ -467,7 +467,7 @@ const LibraryMenuItems = ({ fontSize: ".9rem", }} > - No items yet! + {t("library.noItems")}
0 || unpublishedItems.length > 0))) && (
{t("labels.excalidrawLib")}
)} - {publishedItems.length > 0 && renderLibrarySection(publishedItems)} + {publishedItems.length > 0 ? ( + renderLibrarySection(publishedItems) + ) : unpublishedItems.length > 0 ? ( +
+ {t("library.noItems")} +
+ ) : null} ); diff --git a/src/locales/en.json b/src/locales/en.json index b20f6a4e16..36dd36fc95 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -124,6 +124,7 @@ "sidebarLock": "Keep sidebar open" }, "library": { + "noItems": "No items added yet...", "hint_emptyLibrary": "Select an item on canvas to add it here, or install a library from the public repository, below.", "hint_emptyPrivateLibrary": "Select an item on canvas to add it here." }, From bbfd2b3cd3b944d1c8191e54d848401ed0a00e90 Mon Sep 17 00:00:00 2001 From: David Luzar Date: Tue, 28 Jun 2022 14:44:59 +0200 Subject: [PATCH 06/11] fix: file handle not persisted when importing excalidraw files (#5372) --- src/data/json.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/data/json.ts b/src/data/json.ts index 958cbe239c..037c5ca180 100644 --- a/src/data/json.ts +++ b/src/data/json.ts @@ -98,7 +98,12 @@ export const loadFromJSON = async ( // gets resolved. Else, iOS users cannot open `.excalidraw` files. // extensions: ["json", "excalidraw", "png", "svg"], }); - return loadFromBlob(await normalizeFile(file), localAppState, localElements); + return loadFromBlob( + await normalizeFile(file), + localAppState, + localElements, + file.handle, + ); }; export const isValidExcalidrawData = (data?: { From 0ef202f2df2ad096dde8f901d390bef3e08874d4 Mon Sep 17 00:00:00 2001 From: David Luzar Date: Sat, 2 Jul 2022 17:59:03 +0200 Subject: [PATCH 07/11] feat: support debugging PWA in dev (#4853) * feat: support enabling pwa in dev * enable workbox debug * add prebuild script * fix lint --- .env.development | 9 ++++++++ package.json | 3 ++- public/index.html | 16 +++++++++++++ {src => public}/service-worker.js | 37 ++++++++++++++++++++++--------- scripts/prebuild.js | 20 +++++++++++++++++ src/serviceWorker.tsx | 6 ++++- 6 files changed, 78 insertions(+), 13 deletions(-) rename {src => public}/service-worker.js (68%) create mode 100644 scripts/prebuild.js diff --git a/.env.development b/.env.development index f04f0868c9..1a5fbda3f9 100644 --- a/.env.development +++ b/.env.development @@ -11,3 +11,12 @@ REACT_APP_WS_SERVER_URL=http://localhost:3002 REACT_APP_PORTAL_URL= REACT_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}' + +# put these in your .env.local, or make sure you don't commit! +# must be lowercase `true` when turned on +# +# whether to enable Service Workers in development +REACT_APP_DEV_ENABLE_SW= +# whether to disable live reload / HMR. Usuaully what you want to do when +# debugging Service Workers. +REACT_APP_DEV_DISABLE_LIVE_RELOAD= diff --git a/package.json b/package.json index 8f27edb95a..e9aed5025e 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,8 @@ "build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build", "build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build", "build:version": "node ./scripts/build-version.js", - "build": "yarn build:app && yarn build:version", + "build:prebuild": "node ./scripts/prebuild.js", + "build": "yarn build:prebuild && yarn build:app && yarn build:version", "eject": "react-scripts eject", "fix:code": "yarn test:code --fix", "fix:other": "yarn prettier --write", diff --git a/public/index.html b/public/index.html index 31e4ed5604..0c576a2921 100644 --- a/public/index.html +++ b/public/index.html @@ -98,6 +98,22 @@ /> + <% if (process.env.REACT_APP_DEV_DISABLE_LIVE_RELOAD === "true") { %> + + <% } %>