Keep errors, elements and comments consistent (#2340)

Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
Lipis 2020-11-05 19:06:18 +02:00 committed by GitHub
parent 2a20c44338
commit 5d295415db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 96 additions and 98 deletions

View File

@ -1,7 +1,7 @@
const { CLIEngine } = require("eslint"); const { CLIEngine } = require("eslint");
// see https://github.com/okonet/lint-staged#how-can-i-ignore-files-from-eslintignore- // see https://github.com/okonet/lint-staged#how-can-i-ignore-files-from-eslintignore-
// for explanation // for explanation
const cli = new CLIEngine({}); const cli = new CLIEngine({});
module.exports = { module.exports = {

View File

@ -540,7 +540,7 @@ export const actionChangeSharpness = register({
appState, appState,
); );
const shouldUpdateForNonLinearElements = targetElements.length const shouldUpdateForNonLinearElements = targetElements.length
? targetElements.every((e) => !isLinearElement(e)) ? targetElements.every((el) => !isLinearElement(el))
: !isLinearElementType(appState.elementType); : !isLinearElementType(appState.elementType);
const shouldUpdateForLinearElements = targetElements.length const shouldUpdateForLinearElements = targetElements.length
? targetElements.every(isLinearElement) ? targetElements.every(isLinearElement)

View File

@ -57,9 +57,9 @@ export const copyToClipboard = async (
try { try {
PREFER_APP_CLIPBOARD = false; PREFER_APP_CLIPBOARD = false;
await copyTextToSystemClipboard(json); await copyTextToSystemClipboard(json);
} catch (err) { } catch (error) {
PREFER_APP_CLIPBOARD = true; PREFER_APP_CLIPBOARD = true;
console.error(err); console.error(error);
} }
}; };
@ -128,7 +128,7 @@ export const parseClipboard = async (
} }
// if system clipboard contains spreadsheet, use it even though it's // if system clipboard contains spreadsheet, use it even though it's
// technically possible it's staler than in-app clipboard // technically possible it's staler than in-app clipboard
const spreadsheetResult = parsePotentialSpreadsheet(systemClipboard); const spreadsheetResult = parsePotentialSpreadsheet(systemClipboard);
if (spreadsheetResult) { if (spreadsheetResult) {
return spreadsheetResult; return spreadsheetResult;
@ -150,8 +150,8 @@ export const parseClipboard = async (
return appClipboardData; return appClipboardData;
} catch { } catch {
// system clipboard doesn't contain excalidraw elements → return plaintext // system clipboard doesn't contain excalidraw elements → return plaintext
// unless we set a flag to prefer in-app clipboard because browser didn't // unless we set a flag to prefer in-app clipboard because browser didn't
// support storing to system clipboard on copy // support storing to system clipboard on copy
return PREFER_APP_CLIPBOARD && appClipboardData.elements return PREFER_APP_CLIPBOARD && appClipboardData.elements
? appClipboardData ? appClipboardData
: { text: systemClipboard }; : { text: systemClipboard };
@ -170,7 +170,7 @@ export const copyTextToSystemClipboard = async (text: string | null) => {
if (probablySupportsClipboardWriteText) { if (probablySupportsClipboardWriteText) {
try { try {
// NOTE: doesn't work on FF on non-HTTPS domains, or when document // NOTE: doesn't work on FF on non-HTTPS domains, or when document
// not focused // not focused
await navigator.clipboard.writeText(text || ""); await navigator.clipboard.writeText(text || "");
copied = true; copied = true;
} catch (error) { } catch (error) {
@ -179,7 +179,7 @@ export const copyTextToSystemClipboard = async (text: string | null) => {
} }
// Note that execCommand doesn't allow copying empty strings, so if we're // Note that execCommand doesn't allow copying empty strings, so if we're
// clearing clipboard using this API, we must copy at least an empty char // clearing clipboard using this API, we must copy at least an empty char
if (!copied && !copyTextViaExecCommand(text || " ")) { if (!copied && !copyTextViaExecCommand(text || " ")) {
throw new Error("couldn't copy"); throw new Error("couldn't copy");
} }

View File

@ -523,7 +523,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
collabForceLoadFlag, collabForceLoadFlag,
); );
// if loading same room as the one previously unloaded within 15sec // if loading same room as the one previously unloaded within 15sec
// force reload without prompting // force reload without prompting
if (previousRoom === roomID && Date.now() - timestamp < 15000) { if (previousRoom === roomID && Date.now() - timestamp < 15000) {
return true; return true;
} }
@ -561,8 +561,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
history.clear(); history.clear();
}; };
/** Completely resets scene & history. // Completely resets scene & history.
* Do not use for clear scene user action. */ // Do not use for clear scene user action.
private resetScene = withBatchedUpdates(() => { private resetScene = withBatchedUpdates(() => {
this.scene.replaceAllElements([]); this.scene.replaceAllElements([]);
this.setState({ this.setState({
@ -654,7 +654,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
if (isCollaborationScene) { if (isCollaborationScene) {
// when joining a room we don't want user's local scene data to be merged // when joining a room we don't want user's local scene data to be merged
// into the remote scene // into the remote scene
this.resetScene(); this.resetScene();
this.initializeSocketClient({ showLoadingState: true }); this.initializeSocketClient({ showLoadingState: true });
@ -847,7 +847,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
!isSavedToFirebase(this.portal, syncableElements) !isSavedToFirebase(this.portal, syncableElements)
) { ) {
// this won't run in time if user decides to leave the site, but // this won't run in time if user decides to leave the site, but
// the purpose is to run in immediately after user decides to stay // the purpose is to run in immediately after user decides to stay
this.saveCollabRoomToFirebase(syncableElements); this.saveCollabRoomToFirebase(syncableElements);
event.preventDefault(); event.preventDefault();
@ -943,7 +943,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const { atLeastOneVisibleElement, scrollBars } = renderScene( const { atLeastOneVisibleElement, scrollBars } = renderScene(
elements.filter((element) => { elements.filter((element) => {
// don't render text element that's being currently edited (it's // don't render text element that's being currently edited (it's
// rendered on remote only) // rendered on remote only)
return ( return (
!this.state.editingElement || !this.state.editingElement ||
this.state.editingElement.type !== "text" || this.state.editingElement.type !== "text" ||
@ -1108,7 +1108,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const elementUnderCursor = document.elementFromPoint(cursorX, cursorY); const elementUnderCursor = document.elementFromPoint(cursorX, cursorY);
if ( if (
// if no ClipboardEvent supplied, assume we're pasting via contextMenu // if no ClipboardEvent supplied, assume we're pasting via contextMenu
// thus these checks don't make sense // thus these checks don't make sense
event && event &&
(!(elementUnderCursor instanceof HTMLCanvasElement) || (!(elementUnderCursor instanceof HTMLCanvasElement) ||
isWritableElement(target)) isWritableElement(target))
@ -1380,7 +1380,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const roomKey = roomMatch[2]; const roomKey = roomMatch[2];
// fallback in case you're not alone in the room but still don't receive // fallback in case you're not alone in the room but still don't receive
// initial SCENE_UPDATE message // initial SCENE_UPDATE message
this.socketInitializationTimer = setTimeout( this.socketInitializationTimer = setTimeout(
this.initializeSocket, this.initializeSocket,
INITIAL_SCENE_UPDATE_TIMEOUT, INITIAL_SCENE_UPDATE_TIMEOUT,
@ -1432,7 +1432,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
decryptedData.payload.socketID; decryptedData.payload.socketID;
// NOTE purposefully mutating collaborators map in case of // NOTE purposefully mutating collaborators map in case of
// pointer updates so as not to trigger LayerUI rerender // pointer updates so as not to trigger LayerUI rerender
this.setState((state) => { this.setState((state) => {
if (!state.collaborators.has(socketId)) { if (!state.collaborators.has(socketId)) {
state.collaborators.set(socketId, {}); state.collaborators.set(socketId, {});
@ -1467,9 +1467,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
if (elements) { if (elements) {
this.handleRemoteSceneUpdate(elements, { initFromSnapshot: true }); this.handleRemoteSceneUpdate(elements, { initFromSnapshot: true });
} }
} catch (e) { } catch (error) {
// log the error and move on. other peers will sync us the scene. // log the error and move on. other peers will sync us the scene.
console.error(e); console.error(error);
} }
} }
}; };
@ -1814,7 +1814,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}); });
// do an initial update to re-initialize element position since we were // do an initial update to re-initialize element position since we were
// modifying element's x/y for sake of editor (case: syncing to remote) // modifying element's x/y for sake of editor (case: syncing to remote)
updateElement(element.text); updateElement(element.text);
} }
@ -1920,7 +1920,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
if (existingTextElement) { if (existingTextElement) {
// if text element is no longer centered to a container, reset // if text element is no longer centered to a container, reset
// verticalAlign to default because it's currently internal-only // verticalAlign to default because it's currently internal-only
if (!parentCenterPosition || element.textAlign !== "center") { if (!parentCenterPosition || element.textAlign !== "center") {
mutateElement(element, { verticalAlign: DEFAULT_VERTICAL_ALIGN }); mutateElement(element, { verticalAlign: DEFAULT_VERTICAL_ALIGN });
} }
@ -1931,7 +1931,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
]); ]);
// case: creating new text not centered to parent elemenent → offset Y // case: creating new text not centered to parent elemenent → offset Y
// so that the text is centered to cursor position // so that the text is centered to cursor position
if (!parentCenterPosition) { if (!parentCenterPosition) {
mutateElement(element, { mutateElement(element, {
y: element.y - element.baseline / 2, y: element.y - element.baseline / 2,
@ -1952,7 +1952,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
event: React.MouseEvent<HTMLCanvasElement>, event: React.MouseEvent<HTMLCanvasElement>,
) => { ) => {
// case: double-clicking with arrow/line tool selected would both create // case: double-clicking with arrow/line tool selected would both create
// text and enter multiElement mode // text and enter multiElement mode
if (this.state.multiElement) { if (this.state.multiElement) {
return; return;
} }
@ -2129,7 +2129,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
if (lastPoint === lastCommittedPoint) { if (lastPoint === lastCommittedPoint) {
// if we haven't yet created a temp point and we're beyond commit-zone // if we haven't yet created a temp point and we're beyond commit-zone
// threshold, add a point // threshold, add a point
if ( if (
distance2d( distance2d(
scenePointerX - rx, scenePointerX - rx,
@ -2144,11 +2144,11 @@ class App extends React.Component<ExcalidrawProps, AppState> {
} else { } else {
document.documentElement.style.cursor = CURSOR_TYPE.POINTER; document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
// in this branch, we're inside the commit zone, and no uncommitted // in this branch, we're inside the commit zone, and no uncommitted
// point exists. Thus do nothing (don't add/remove points). // point exists. Thus do nothing (don't add/remove points).
} }
} else { } else {
// cursor moved inside commit zone, and there's uncommitted point, // cursor moved inside commit zone, and there's uncommitted point,
// thus remove it // thus remove it
if ( if (
points.length > 2 && points.length > 2 &&
lastCommittedPoint && lastCommittedPoint &&
@ -2293,8 +2293,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
// fixes pointermove causing selection of UI texts #32 // fixes pointermove causing selection of UI texts #32
event.preventDefault(); event.preventDefault();
// Preventing the event above disables default behavior // Preventing the event above disables default behavior
// of defocusing potentially focused element, which is what we // of defocusing potentially focused element, which is what we
// want when clicking inside the canvas. // want when clicking inside the canvas.
if (document.activeElement instanceof HTMLElement) { if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur(); document.activeElement.blur();
} }
@ -2739,8 +2739,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
} }
// Add hit element to selection. At this point if we're not holding // Add hit element to selection. At this point if we're not holding
// SHIFT the previously selected element(s) were deselected above // SHIFT the previously selected element(s) were deselected above
// (make sure you use setState updater to use latest state) // (make sure you use setState updater to use latest state)
if ( if (
!someHitElementIsSelected && !someHitElementIsSelected &&
!pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements !pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements
@ -2798,8 +2798,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
pointerDownState: PointerDownState, pointerDownState: PointerDownState,
): void => { ): void => {
// if we're currently still editing text, clicking outside // if we're currently still editing text, clicking outside
// should only finalize it, not create another (irrespective // should only finalize it, not create another (irrespective
// of state.elementLocked) // of state.elementLocked)
if (this.state.editingElement?.type === "text") { if (this.state.editingElement?.type === "text") {
return; return;
} }
@ -2860,7 +2860,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}, },
})); }));
// clicking outside commit zone → update reference for last committed // clicking outside commit zone → update reference for last committed
// point // point
mutateElement(multiElement, { mutateElement(multiElement, {
lastCommittedPoint: multiElement.points[multiElement.points.length - 1], lastCommittedPoint: multiElement.points[multiElement.points.length - 1],
}); });
@ -2986,9 +2986,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
); );
// for arrows/lines, don't start dragging until a given threshold // for arrows/lines, don't start dragging until a given threshold
// to ensure we don't create a 2-point arrow by mistake when // to ensure we don't create a 2-point arrow by mistake when
// user clicks mouse in a way that it moves a tiny bit (thus // user clicks mouse in a way that it moves a tiny bit (thus
// triggering pointermove) // triggering pointermove)
if ( if (
!pointerDownState.drag.hasOccurred && !pointerDownState.drag.hasOccurred &&
(this.state.elementType === "arrow" || (this.state.elementType === "arrow" ||
@ -3127,7 +3127,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
if ( if (
this.state.selectedElementIds[element.id] || this.state.selectedElementIds[element.id] ||
// case: the state.selectedElementIds might not have been // case: the state.selectedElementIds might not have been
// updated yet by the time this mousemove event is fired // updated yet by the time this mousemove event is fired
(element.id === hitElement?.id && (element.id === hitElement?.id &&
pointerDownState.hit.wasAddedToSelection) pointerDownState.hit.wasAddedToSelection)
) { ) {
@ -3331,7 +3331,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
selectionElement: null, selectionElement: null,
cursorButton: "up", cursorButton: "up",
// text elements are reset on finalize, and resetting on pointerup // text elements are reset on finalize, and resetting on pointerup
// may cause issues with double taps // may cause issues with double taps
editingElement: editingElement:
multiElement || isTextElement(this.state.editingElement) multiElement || isTextElement(this.state.editingElement)
? this.state.editingElement ? this.state.editingElement
@ -3481,8 +3481,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
if (this.state.selectedElementIds[hitElement.id]) { if (this.state.selectedElementIds[hitElement.id]) {
if (isSelectedViaGroup(this.state, hitElement)) { if (isSelectedViaGroup(this.state, hitElement)) {
// We want to unselect all groups hitElement is part of // We want to unselect all groups hitElement is part of
// as well as all elements that are part of the groups // as well as all elements that are part of the groups
// hitElement is part of // hitElement is part of
const idsOfSelectedElementsThatAreInGroups = hitElement.groupIds const idsOfSelectedElementsThatAreInGroups = hitElement.groupIds
.flatMap((groupId) => .flatMap((groupId) =>
getElementsInGroup(this.scene.getElements(), groupId), getElementsInGroup(this.scene.getElements(), groupId),

View File

@ -167,8 +167,7 @@ class Portal {
const newElements = sceneElements const newElements = sceneElements
.reduce((elements, element) => { .reduce((elements, element) => {
// if the remote element references one that's currently // if the remote element references one that's currently
// edited on local, skip it (it'll be added in the next // edited on local, skip it (it'll be added in the next step)
// step)
if ( if (
element.id === this.app.state.editingElement?.id || element.id === this.app.state.editingElement?.id ||
element.id === this.app.state.resizingElement?.id || element.id === this.app.state.resizingElement?.id ||

View File

@ -108,7 +108,8 @@ export const loadFromBlob = async (
}, },
localAppState, localAppState,
); );
} catch { } catch (error) {
console.error(error.message);
throw new Error(t("alerts.couldNotLoadInvalidFile")); throw new Error(t("alerts.couldNotLoadInvalidFile"));
} }
}; };

View File

@ -86,7 +86,7 @@ export const isSavedToFirebase = (
return firebaseSceneVersionCache.get(portal.socket) === sceneVersion; return firebaseSceneVersionCache.get(portal.socket) === sceneVersion;
} }
// if no room exists, consider the room saved so that we don't unnecessarily // if no room exists, consider the room saved so that we don't unnecessarily
// prevent unload (there's nothing we could do at that point anyway) // prevent unload (there's nothing we could do at that point anyway)
return true; return true;
}; };
@ -97,7 +97,7 @@ export async function saveToFirebase(
const { roomID, roomKey, socket } = portal; const { roomID, roomKey, socket } = portal;
if ( if (
// if no room exists, consider the room saved because there's nothing we can // if no room exists, consider the room saved because there's nothing we can
// do at this point // do at this point
!roomID || !roomID ||
!roomKey || !roomKey ||
!socket || !socket ||

View File

@ -31,7 +31,7 @@ export class Library {
} }
// detect z-index difference by checking the excalidraw elements // detect z-index difference by checking the excalidraw elements
// are in order // are in order
return libraryItem.every((libItemExcalidrawItem, idx) => { return libraryItem.every((libItemExcalidrawItem, idx) => {
return ( return (
libItemExcalidrawItem.id === targetLibraryItem[idx].id && libItemExcalidrawItem.id === targetLibraryItem[idx].id &&
@ -69,8 +69,8 @@ export class Library {
Library.libraryCache = JSON.parse(JSON.stringify(items)); Library.libraryCache = JSON.parse(JSON.stringify(items));
resolve(items); resolve(items);
} catch (e) { } catch (error) {
console.error(e); console.error(error);
resolve([]); resolve([]);
} }
}); });
@ -81,12 +81,12 @@ export class Library {
try { try {
const serializedItems = JSON.stringify(items); const serializedItems = JSON.stringify(items);
// cache optimistically so that consumers have access to the latest // cache optimistically so that consumers have access to the latest
// immediately // immediately
Library.libraryCache = JSON.parse(serializedItems); Library.libraryCache = JSON.parse(serializedItems);
localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY, serializedItems); localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY, serializedItems);
} catch (e) { } catch (error) {
Library.libraryCache = prevLibraryItems; Library.libraryCache = prevLibraryItems;
console.error(e); console.error(error);
} }
}; };
} }

View File

@ -32,7 +32,7 @@ const restoreElementWithProperties = <T extends ExcalidrawElement>(
const base: Pick<T, keyof ExcalidrawElement> = { const base: Pick<T, keyof ExcalidrawElement> = {
type: element.type, type: element.type,
// all elements must have version > 0 so getSceneVersion() will pick up // all elements must have version > 0 so getSceneVersion() will pick up
// newly added elements // newly added elements
version: element.version || 1, version: element.version || 1,
versionNonce: element.versionNonce ?? 0, versionNonce: element.versionNonce ?? 0,
isDeleted: element.isDeleted ?? false, isDeleted: element.isDeleted ?? false,
@ -112,9 +112,9 @@ const restoreElement = (
case "diamond": case "diamond":
return restoreElementWithProperties(element, {}); return restoreElementWithProperties(element, {});
// don't use default case so as to catch a missing an element type case // Don't use default case so as to catch a missing an element type case.
// (we also don't want to throw, but instead return void so we // We also don't want to throw, but instead return void so we filter
// filter out these unsupported elements from the restored array) // out these unsupported elements from the restored array.
} }
}; };
@ -123,7 +123,7 @@ export const restoreElements = (
): ExcalidrawElement[] => { ): ExcalidrawElement[] => {
return (elements || []).reduce((elements, element) => { return (elements || []).reduce((elements, element) => {
// filtering out selection, which is legacy, no longer kept in elements, // filtering out selection, which is legacy, no longer kept in elements,
// and causing issues if retained // and causing issues if retained
if (element.type !== "selection" && !isInvisiblySmallElement(element)) { if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
const migratedElement = restoreElement(element); const migratedElement = restoreElement(element);
if (migratedElement) { if (migratedElement) {
@ -161,7 +161,7 @@ const restoreAppState = (
...nextAppState, ...nextAppState,
offsetLeft: appState.offsetLeft || 0, offsetLeft: appState.offsetLeft || 0,
offsetTop: appState.offsetTop || 0, offsetTop: appState.offsetTop || 0,
/* Migrates from previous version where appState.zoom was a number */ // Migrates from previous version where appState.zoom was a number
zoom: zoom:
typeof appState.zoom === "number" typeof appState.zoom === "number"
? { ? {

View File

@ -227,7 +227,7 @@ export class LinearElementEditor {
); );
// if we clicked on a point, set the element as hitElement otherwise // if we clicked on a point, set the element as hitElement otherwise
// it would get deselected if the point is outside the hitbox area // it would get deselected if the point is outside the hitbox area
if (clickedPointIndex > -1) { if (clickedPointIndex > -1) {
ret.hitElement = element; ret.hitElement = element;
} else { } else {
@ -379,8 +379,8 @@ export class LinearElementEditor {
const pointHandles = this.getPointsGlobalCoordinates(element); const pointHandles = this.getPointsGlobalCoordinates(element);
let idx = pointHandles.length; let idx = pointHandles.length;
// loop from right to left because points on the right are rendered over // loop from right to left because points on the right are rendered over
// points on the left, thus should take precedence when clicking, if they // points on the left, thus should take precedence when clicking, if they
// overlap // overlap
while (--idx > -1) { while (--idx > -1) {
const point = pointHandles[idx]; const point = pointHandles[idx];
if ( if (
@ -458,10 +458,10 @@ export class LinearElementEditor {
const { points } = element; const { points } = element;
// in case we're moving start point, instead of modifying its position // in case we're moving start point, instead of modifying its position
// which would break the invariant of it being at [0,0], we move // which would break the invariant of it being at [0,0], we move
// all the other points in the opposite direction by delta to // all the other points in the opposite direction by delta to
// offset it. We do the same with actual element.x/y position, so // offset it. We do the same with actual element.x/y position, so
// this hacks are completely transparent to the user. // this hacks are completely transparent to the user.
let offsetX = 0; let offsetX = 0;
let offsetY = 0; let offsetY = 0;
@ -475,7 +475,7 @@ export class LinearElementEditor {
nextPoints.splice(pointIndex, 1); nextPoints.splice(pointIndex, 1);
if (pointIndex === 0) { if (pointIndex === 0) {
// if deleting first point, make the next to be [0,0] and recalculate // if deleting first point, make the next to be [0,0] and recalculate
// positions of the rest with respect to it // positions of the rest with respect to it
offsetX = nextPoints[0][0]; offsetX = nextPoints[0][0];
offsetY = nextPoints[0][1]; offsetY = nextPoints[0][1];
nextPoints = nextPoints.map((point, idx) => { nextPoints = nextPoints.map((point, idx) => {

View File

@ -225,7 +225,7 @@ export const newLinearElement = (
}; };
// Simplified deep clone for the purpose of cloning ExcalidrawElement only // Simplified deep clone for the purpose of cloning ExcalidrawElement only
// (doesn't clone Date, RegExp, Map, Set, Typed arrays etc.) // (doesn't clone Date, RegExp, Map, Set, Typed arrays etc.)
// //
// Adapted from https://github.com/lukeed/klona // Adapted from https://github.com/lukeed/klona
export const deepCopyElement = (val: any, depth: number = 0) => { export const deepCopyElement = (val: any, depth: number = 0) => {

View File

@ -25,7 +25,7 @@ const getTransform = (
const { zoom, offsetTop, offsetLeft } = appState; const { zoom, offsetTop, offsetLeft } = appState;
const degree = (180 * angle) / Math.PI; const degree = (180 * angle) / Math.PI;
// offsets must be multiplied by 2 to account for the division by 2 of // offsets must be multiplied by 2 to account for the division by 2 of
// the whole expression afterwards // the whole expression afterwards
return `translate(${((width - offsetLeft * 2) * (zoom.value - 1)) / 2}px, ${ return `translate(${((width - offsetLeft * 2) * (zoom.value - 1)) / 2}px, ${
((height - offsetTop * 2) * (zoom.value - 1)) / 2 ((height - offsetTop * 2) * (zoom.value - 1)) / 2
}px) scale(${zoom.value}) rotate(${degree}deg)`; }px) scale(${zoom.value}) rotate(${degree}deg)`;
@ -166,7 +166,7 @@ export const textWysiwyg = ({
const rebindBlur = () => { const rebindBlur = () => {
window.removeEventListener("pointerup", rebindBlur); window.removeEventListener("pointerup", rebindBlur);
// deferred to guard against focus traps on various UIs that steal focus // deferred to guard against focus traps on various UIs that steal focus
// upon pointerUp // upon pointerUp
setTimeout(() => { setTimeout(() => {
editable.onblur = handleSubmit; editable.onblur = handleSubmit;
// case: clicking on the same property → no change → no update → no focus // case: clicking on the same property → no change → no update → no focus
@ -184,7 +184,7 @@ export const textWysiwyg = ({
editable.onblur = null; editable.onblur = null;
window.addEventListener("pointerup", rebindBlur); window.addEventListener("pointerup", rebindBlur);
// handle edge-case where pointerup doesn't fire e.g. due to user // handle edge-case where pointerup doesn't fire e.g. due to user
// alt-tabbing away // alt-tabbing away
window.addEventListener("blur", handleSubmit); window.addEventListener("blur", handleSubmit);
} }
}; };
@ -199,7 +199,7 @@ export const textWysiwyg = ({
editable.onblur = handleSubmit; editable.onblur = handleSubmit;
// reposition wysiwyg in case of window resize. Happens on mobile when // reposition wysiwyg in case of window resize. Happens on mobile when
// device keyboard is opened. // device keyboard is opened.
window.addEventListener("resize", updateWysiwygStyle); window.addEventListener("resize", updateWysiwygStyle);
window.addEventListener("pointerdown", onPointerDown); window.addEventListener("pointerdown", onPointerDown);
window.addEventListener("wheel", stopEvent, { window.addEventListener("wheel", stopEvent, {

View File

@ -126,7 +126,7 @@ const drawElementOnCanvas = (
const shouldTemporarilyAttach = rtl && !context.canvas.isConnected; const shouldTemporarilyAttach = rtl && !context.canvas.isConnected;
if (shouldTemporarilyAttach) { if (shouldTemporarilyAttach) {
// to correctly render RTL text mixed with LTR, we have to append it // to correctly render RTL text mixed with LTR, we have to append it
// to the DOM // to the DOM
document.body.appendChild(context.canvas); document.body.appendChild(context.canvas);
} }
context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr"); context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr");
@ -194,17 +194,17 @@ export const generateRoughOptions = (element: ExcalidrawElement): Options => {
? DASHARRAY_DOTTED ? DASHARRAY_DOTTED
: undefined, : undefined,
// for non-solid strokes, disable multiStroke because it tends to make // for non-solid strokes, disable multiStroke because it tends to make
// dashes/dots overlay each other // dashes/dots overlay each other
disableMultiStroke: element.strokeStyle !== "solid", disableMultiStroke: element.strokeStyle !== "solid",
// for non-solid strokes, increase the width a bit to make it visually // for non-solid strokes, increase the width a bit to make it visually
// similar to solid strokes, because we're also disabling multiStroke // similar to solid strokes, because we're also disabling multiStroke
strokeWidth: strokeWidth:
element.strokeStyle !== "solid" element.strokeStyle !== "solid"
? element.strokeWidth + 0.5 ? element.strokeWidth + 0.5
: element.strokeWidth, : element.strokeWidth,
// when increasing strokeWidth, we must explicitly set fillWeight and // when increasing strokeWidth, we must explicitly set fillWeight and
// hachureGap because if not specified, roughjs uses strokeWidth to // hachureGap because if not specified, roughjs uses strokeWidth to
// calculate them (and we don't want the fills to be modified) // calculate them (and we don't want the fills to be modified)
fillWeight: element.strokeWidth / 2, fillWeight: element.strokeWidth / 2,
hachureGap: element.strokeWidth * 4, hachureGap: element.strokeWidth * 4,
roughness: element.roughness, roughness: element.roughness,

View File

@ -187,7 +187,7 @@ export const renderScene = (
renderSelection = true, renderSelection = true,
// Whether to employ render optimizations to improve performance. // Whether to employ render optimizations to improve performance.
// Should not be turned on for export operations and similar, because it // Should not be turned on for export operations and similar, because it
// doesn't guarantee pixel-perfect output. // doesn't guarantee pixel-perfect output.
renderOptimizations = false, renderOptimizations = false,
renderGrid = true, renderGrid = true,
}: { }: {

View File

@ -129,7 +129,7 @@ class Scene {
} }
}); });
// done not for memory leaks, but to guard against possible late fires // done not for memory leaks, but to guard against possible late fires
// (I guess?) // (I guess?)
this.callbacks.clear(); this.callbacks.clear();
} }
} }

View File

@ -75,10 +75,9 @@ const registerValidSW = (swUrl: string, config?: Config) => {
// but the previous service worker will still serve the older // but the previous service worker will still serve the older
// content until all client tabs are closed. // content until all client tabs are closed.
// console.log( console.info(
// "New content is available and will be used when all " + "New content is available and will be used when all tabs for this page are closed.",
// "tabs for this page are closed.", );
// );
// Execute callback // Execute callback
if (config && config.onUpdate) { if (config && config.onUpdate) {
@ -89,7 +88,7 @@ const registerValidSW = (swUrl: string, config?: Config) => {
// It's the perfect time to display a // It's the perfect time to display a
// "Content is cached for offline use." message. // "Content is cached for offline use." message.
// console.log("Content is cached for offline use."); console.info("Content is cached for offline use.");
// Execute callback // Execute callback
if (config && config.onSuccess) { if (config && config.onSuccess) {
@ -128,10 +127,11 @@ const checkValidServiceWorker = (swUrl: string, config?: Config) => {
registerValidSW(swUrl, config); registerValidSW(swUrl, config);
} }
}) })
.catch(() => { .catch((error) => {
// console.log( console.info(
// "No internet connection found. App is running in offline mode.", "No internet connection found. App is running in offline mode.",
// ); error.message,
);
}); });
}; };

View File

@ -68,7 +68,7 @@ describe("element binding", () => {
expect(API.getSelectedElement().type).toBe("arrow"); expect(API.getSelectedElement().type).toBe("arrow");
// NOTE this mouse down/up + await needs to be done in order to repro // NOTE this mouse down/up + await needs to be done in order to repro
// the issue, due to https://github.com/excalidraw/excalidraw/blob/46bff3daceb602accf60c40a84610797260fca94/src/components/App.tsx#L740 // the issue, due to https://github.com/excalidraw/excalidraw/blob/46bff3daceb602accf60c40a84610797260fca94/src/components/App.tsx#L740
mouse.reset(); mouse.reset();
expect(h.state.editingLinearElement).not.toBe(null); expect(h.state.editingLinearElement).not.toBe(null);
mouse.down(0, 0); mouse.down(0, 0);

View File

@ -19,7 +19,7 @@ const testElements = [
text: "😀", text: "😀",
}), }),
// can't get jsdom text measurement to work so this is a temp hack // can't get jsdom text measurement to work so this is a temp hack
// to ensure the element isn't stripped as invisible // to ensure the element isn't stripped as invisible
width: 16, width: 16,
height: 16, height: 16,
}, },

View File

@ -1569,7 +1569,7 @@ it(
expect(API.getSelectedElements().length).toBe(3); expect(API.getSelectedElements().length).toBe(3);
// clicking on first rectangle that is part of the group should select // clicking on first rectangle that is part of the group should select
// that group (exclusively) // that group (exclusively)
mouse.clickOn(rect1); mouse.clickOn(rect1);
expect(API.getSelectedElements().length).toBe(2); expect(API.getSelectedElements().length).toBe(2);
expect(Object.keys(h.state.selectedGroupIds).length).toBe(1); expect(Object.keys(h.state.selectedGroupIds).length).toBe(1);
@ -1594,8 +1594,7 @@ it(
mouse.up(100, 100); mouse.up(100, 100);
// Select first rectangle while keeping third one selected. // Select first rectangle while keeping third one selected.
// Third rectangle is selected because it was the last element // Third rectangle is selected because it was the last element to be created.
// to be created.
mouse.reset(); mouse.reset();
Keyboard.withModifierKeys({ shift: true }, () => { Keyboard.withModifierKeys({ shift: true }, () => {
mouse.click(); mouse.click();
@ -1616,8 +1615,7 @@ it(
}); });
expect(API.getSelectedElements().length).toBe(3); expect(API.getSelectedElements().length).toBe(3);
// pointer down o first rectangle that is // Pointer down o first rectangle that is part of the group
// part of the group
mouse.reset(); mouse.reset();
Keyboard.withModifierKeys({ shift: true }, () => { Keyboard.withModifierKeys({ shift: true }, () => {
mouse.down(); mouse.down();

View File

@ -41,7 +41,7 @@ export type AppState = {
startBoundElement: NonDeleted<ExcalidrawBindableElement> | null; startBoundElement: NonDeleted<ExcalidrawBindableElement> | null;
suggestedBindings: SuggestedBinding[]; suggestedBindings: SuggestedBinding[];
// element being edited, but not necessarily added to elements array yet // element being edited, but not necessarily added to elements array yet
// (e.g. text element when typing into the input) // (e.g. text element when typing into the input)
editingElement: NonDeletedExcalidrawElement | null; editingElement: NonDeletedExcalidrawElement | null;
editingLinearElement: LinearElementEditor | null; editingLinearElement: LinearElementEditor | null;
elementType: typeof SHAPES[number]["value"]; elementType: typeof SHAPES[number]["value"];

View File

@ -94,7 +94,7 @@ export const measureText = (text: string, font: FontString) => {
line.innerText = text line.innerText = text
.split("\n") .split("\n")
// replace empty lines with single space because leading/trailing empty // replace empty lines with single space because leading/trailing empty
// lines would be stripped from computation // lines would be stripped from computation
.map((x) => x || " ") .map((x) => x || " ")
.join("\n"); .join("\n");
const width = line.offsetWidth; const width = line.offsetWidth;

View File

@ -62,7 +62,7 @@ const getTargetIndex = (
return false; return false;
} }
// if we're editing group, find closest sibling irrespective of whether // if we're editing group, find closest sibling irrespective of whether
// there's a different-group element between them (for legacy reasons) // there's a different-group element between them (for legacy reasons)
if (appState.editingGroupId) { if (appState.editingGroupId) {
return element.groupIds.includes(appState.editingGroupId); return element.groupIds.includes(appState.editingGroupId);
} }
@ -106,7 +106,7 @@ const getTargetIndex = (
if (elementsInSiblingGroup.length) { if (elementsInSiblingGroup.length) {
// assumes getElementsInGroup() returned elements are sorted // assumes getElementsInGroup() returned elements are sorted
// by zIndex (ascending) // by zIndex (ascending)
return direction === "left" return direction === "left"
? elements.indexOf(elementsInSiblingGroup[0]) ? elements.indexOf(elementsInSiblingGroup[0])
: elements.indexOf( : elements.indexOf(