Compare commits

..
Author SHA1 Message Date
zsviczian d64ac92cc2 lint 2023-11-12 12:04:47 +00:00
zsviczian 1d27ce16d8 fix pointer move cancelling locked element selection 2023-11-12 12:01:03 +00:00
9 changed files with 54 additions and 139 deletions
+10 -14
View File
@@ -2716,7 +2716,7 @@ class App extends React.Component<AppProps, AppState> {
}); });
}; };
togglePenMode = (force: boolean | null) => { togglePenMode = (force?: boolean) => {
this.setState((prevState) => { this.setState((prevState) => {
return { return {
penMode: force ?? !prevState.penMode, penMode: force ?? !prevState.penMode,
@@ -4740,13 +4740,9 @@ class App extends React.Component<AppProps, AppState> {
}); });
const { x, y } = viewportCoordsToSceneCoords(event, this.state); const { x, y } = viewportCoordsToSceneCoords(event, this.state);
const frame = this.getTopLayerFrameAtSceneCoords({ x, y });
mutateElement(pendingImageElement, { mutateElement(pendingImageElement, {
x, x,
y, y,
frameId: frame ? frame.id : null,
}); });
} else if (this.state.activeTool.type === "freedraw") { } else if (this.state.activeTool.type === "freedraw") {
this.handleFreeDrawElementOnPointerDown( this.handleFreeDrawElementOnPointerDown(
@@ -5613,11 +5609,9 @@ class App extends React.Component<AppProps, AppState> {
private createImageElement = ({ private createImageElement = ({
sceneX, sceneX,
sceneY, sceneY,
addToFrameUnderCursor = true,
}: { }: {
sceneX: number; sceneX: number;
sceneY: number; sceneY: number;
addToFrameUnderCursor?: boolean;
}) => { }) => {
const [gridX, gridY] = getGridPoint( const [gridX, gridY] = getGridPoint(
sceneX, sceneX,
@@ -5627,12 +5621,10 @@ class App extends React.Component<AppProps, AppState> {
: this.state.gridSize, : this.state.gridSize,
); );
const topLayerFrame = addToFrameUnderCursor const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
? this.getTopLayerFrameAtSceneCoords({ x: gridX,
x: gridX, y: gridY,
y: gridY, });
})
: null;
const element = newImageElement({ const element = newImageElement({
type: "image", type: "image",
@@ -5962,6 +5954,11 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState: PointerDownState, pointerDownState: PointerDownState,
) { ) {
return withBatchedUpdatesThrottled((event: PointerEvent) => { return withBatchedUpdatesThrottled((event: PointerEvent) => {
//To avoid pointerMove canceling the selection of locked elements on mobile
if (Boolean(this.state.contextMenu)) {
return;
}
// We need to initialize dragOffsetXY only after we've updated // We need to initialize dragOffsetXY only after we've updated
// `state.selectedElementIds` on pointerDown. Doing it here in pointerMove // `state.selectedElementIds` on pointerDown. Doing it here in pointerMove
// event handler should hopefully ensure we're already working with // event handler should hopefully ensure we're already working with
@@ -7562,7 +7559,6 @@ class App extends React.Component<AppProps, AppState> {
const imageElement = this.createImageElement({ const imageElement = this.createImageElement({
sceneX: x, sceneX: x,
sceneY: y, sceneY: y,
addToFrameUnderCursor: false,
}); });
if (insertOnCanvasDirectly) { if (insertOnCanvasDirectly) {
+2 -2
View File
@@ -66,7 +66,7 @@ interface LayerUIProps {
elements: readonly NonDeletedExcalidrawElement[]; elements: readonly NonDeletedExcalidrawElement[];
onLockToggle: () => void; onLockToggle: () => void;
onHandToolToggle: () => void; onHandToolToggle: () => void;
onPenModeToggle: AppClassProperties["togglePenMode"]; onPenModeToggle: () => void;
showExitZenModeBtn: boolean; showExitZenModeBtn: boolean;
langCode: Language["code"]; langCode: Language["code"];
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"]; renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
@@ -258,7 +258,7 @@ const LayerUI = ({
<PenModeButton <PenModeButton
zenModeEnabled={appState.zenModeEnabled} zenModeEnabled={appState.zenModeEnabled}
checked={appState.penMode} checked={appState.penMode}
onChange={() => onPenModeToggle(null)} onChange={onPenModeToggle}
title={t("toolBar.penMode")} title={t("toolBar.penMode")}
penDetected={appState.penDetected} penDetected={appState.penDetected}
/> />
+2 -2
View File
@@ -35,7 +35,7 @@ type MobileMenuProps = {
elements: readonly NonDeletedExcalidrawElement[]; elements: readonly NonDeletedExcalidrawElement[];
onLockToggle: () => void; onLockToggle: () => void;
onHandToolToggle: () => void; onHandToolToggle: () => void;
onPenModeToggle: AppClassProperties["togglePenMode"]; onPenModeToggle: () => void;
renderTopRightUI?: ( renderTopRightUI?: (
isMobile: boolean, isMobile: boolean,
@@ -94,7 +94,7 @@ export const MobileMenu = ({
)} )}
<PenModeButton <PenModeButton
checked={appState.penMode} checked={appState.penMode}
onChange={() => onPenModeToggle(null)} onChange={onPenModeToggle}
title={t("toolBar.penMode")} title={t("toolBar.penMode")}
isMobile isMobile
penDetected={appState.penDetected} penDetected={appState.penDetected}
+1 -1
View File
@@ -99,7 +99,7 @@ export const setCursorForShape = (
interactiveCanvas.style.cursor = `url(${url}), auto`; interactiveCanvas.style.cursor = `url(${url}), auto`;
} else if (!["image", "custom"].includes(appState.activeTool.type)) { } else if (!["image", "custom"].includes(appState.activeTool.type)) {
interactiveCanvas.style.cursor = CURSOR_TYPE.CROSSHAIR; interactiveCanvas.style.cursor = CURSOR_TYPE.CROSSHAIR;
} else if (appState.activeTool.type !== "image") { } else {
interactiveCanvas.style.cursor = CURSOR_TYPE.AUTO; interactiveCanvas.style.cursor = CURSOR_TYPE.AUTO;
} }
}; };
-12
View File
@@ -37,18 +37,6 @@ Please add the latest change on the top under the correct section.
- [`useDevice`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils#usedevice) hook's return value was changed to differentiate between `editor` and `viewport` breakpoints. [#7243](https://github.com/excalidraw/excalidraw/pull/7243) - [`useDevice`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils#usedevice) hook's return value was changed to differentiate between `editor` and `viewport` breakpoints. [#7243](https://github.com/excalidraw/excalidraw/pull/7243)
### Build
- Support Preact [#7255](https://github.com/excalidraw/excalidraw/pull/7255). The host needs to set `process.env.IS_PREACT` to `true`
When using vite, you will have to make sure the variable process.env.IS_PREACT is available at runtime since Vite removes it by default, so you can update the vite config to ensure its available
```json
define: {
"process.env.IS_PREACT": process.env.IS_PREACT,
}
```
## 0.16.1 (2023-09-21) ## 0.16.1 (2023-09-21)
## Excalidraw Library ## Excalidraw Library
+1 -7
View File
@@ -1,10 +1,4 @@
if (process.env.IS_PREACT === "true") { if (process.env.NODE_ENV === "production") {
if (process.env.NODE_ENV === "production") {
module.exports = require("./dist/excalidraw-with-preact.production.min.js");
} else {
module.exports = require("./dist/excalidraw-with-preact.development.js");
}
} else if (process.env.NODE_ENV === "production") {
module.exports = require("./dist/excalidraw.production.min.js"); module.exports = require("./dist/excalidraw.production.min.js");
} else { } else {
module.exports = require("./dist/excalidraw.development.js"); module.exports = require("./dist/excalidraw.development.js");
+1 -1
View File
@@ -78,7 +78,7 @@
"homepage": "https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw", "homepage": "https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw",
"scripts": { "scripts": {
"gen:types": "tsc --project ../../../tsconfig-types.json", "gen:types": "tsc --project ../../../tsconfig-types.json",
"build:umd": "rm -rf dist && cross-env NODE_ENV=production webpack --config webpack.prod.config.js && cross-env NODE_ENV=development webpack --config webpack.dev.config.js && NODE_ENV=development webpack --config webpack.preact.config.js && NODE_ENV=production webpack --config webpack.preact.config.js && yarn gen:types", "build:umd": "rm -rf dist && cross-env NODE_ENV=production webpack --config webpack.prod.config.js && cross-env NODE_ENV=development webpack --config webpack.dev.config.js && yarn gen:types",
"build:umd:withAnalyzer": "cross-env NODE_ENV=production ANALYZER=true webpack --config webpack.prod.config.js", "build:umd:withAnalyzer": "cross-env NODE_ENV=production ANALYZER=true webpack --config webpack.prod.config.js",
"pack": "yarn build:umd && yarn pack", "pack": "yarn build:umd && yarn pack",
"start": "webpack serve --config webpack.dev-server.config.js", "start": "webpack serve --config webpack.dev-server.config.js",
@@ -1,33 +0,0 @@
const { merge } = require("webpack-merge");
const prodConfig = require("./webpack.prod.config");
const devConfig = require("./webpack.dev.config");
const isProd = process.env.NODE_ENV === "production";
const config = isProd ? prodConfig : devConfig;
const outputFile = isProd
? "excalidraw-with-preact.production.min"
: "excalidraw-with-preact.development";
const preactWebpackConfig = {
entry: {
[outputFile]: "./entry.js",
},
externals: {
...config.externals,
"react-dom/client": {
root: "ReactDOMClient",
commonjs2: "react-dom/client",
commonjs: "react-dom/client",
amd: "react-dom/client",
},
"react/jsx-runtime": {
root: "ReactJSXRuntime",
commonjs2: "react/jsx-runtime",
commonjs: "react/jsx-runtime",
amd: "react/jsx-runtime",
},
},
};
module.exports = merge(config, preactWebpackConfig);
+37 -67
View File
@@ -11,7 +11,7 @@ import {
getElementAbsoluteCoords, getElementAbsoluteCoords,
} from "../element/bounds"; } from "../element/bounds";
import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene"; import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene";
import { cloneJSON, distance, getFontString } from "../utils"; import { distance, getFontString } from "../utils";
import { AppState, BinaryFiles } from "../types"; import { AppState, BinaryFiles } from "../types";
import { import {
DEFAULT_EXPORT_PADDING, DEFAULT_EXPORT_PADDING,
@@ -52,9 +52,8 @@ const __createSceneForElementsHack__ = (
// we can't duplicate elements to regenerate ids because we need the // we can't duplicate elements to regenerate ids because we need the
// orig ids when embedding. So we do another hack of not mapping element // orig ids when embedding. So we do another hack of not mapping element
// ids to Scene instances so that we don't override the editor elements // ids to Scene instances so that we don't override the editor elements
// mapping. // mapping
// We still need to clone the objects themselves to regen references. scene.replaceAllElements(elements, false);
scene.replaceAllElements(cloneJSON(elements), false);
return scene; return scene;
}; };
@@ -141,36 +140,6 @@ const getFrameRenderingConfig = (
}; };
}; };
const prepareElementsForRender = ({
elements,
exportingFrame,
frameRendering,
exportWithDarkMode,
}: {
elements: readonly ExcalidrawElement[];
exportingFrame: ExcalidrawFrameElement | null | undefined;
frameRendering: AppState["frameRendering"];
exportWithDarkMode: AppState["exportWithDarkMode"];
}) => {
let nextElements: readonly ExcalidrawElement[];
if (exportingFrame) {
nextElements = elementsOverlappingBBox({
elements,
bounds: exportingFrame,
type: "overlap",
});
} else if (frameRendering.enabled && frameRendering.name) {
nextElements = addFrameLabelsAsTextElements(elements, {
exportWithDarkMode,
});
} else {
nextElements = elements;
}
return nextElements;
};
export const exportToCanvas = async ( export const exportToCanvas = async (
elements: readonly NonDeletedExcalidrawElement[], elements: readonly NonDeletedExcalidrawElement[],
appState: AppState, appState: AppState,
@@ -199,24 +168,21 @@ export const exportToCanvas = async (
const tempScene = __createSceneForElementsHack__(elements); const tempScene = __createSceneForElementsHack__(elements);
elements = tempScene.getNonDeletedElements(); elements = tempScene.getNonDeletedElements();
const frameRendering = getFrameRenderingConfig( let nextElements: ExcalidrawElement[];
exportingFrame ?? null,
appState.frameRendering ?? null,
);
const elementsForRender = prepareElementsForRender({
elements,
exportingFrame,
exportWithDarkMode: appState.exportWithDarkMode,
frameRendering,
});
if (exportingFrame) { if (exportingFrame) {
exportPadding = 0; exportPadding = 0;
nextElements = elementsOverlappingBBox({
elements,
bounds: exportingFrame,
type: "overlap",
});
} else {
nextElements = addFrameLabelsAsTextElements(elements, appState);
} }
const [minX, minY, width, height] = getCanvasSize( const [minX, minY, width, height] = getCanvasSize(
exportingFrame ? [exportingFrame] : getRootElements(elementsForRender), exportingFrame ? [exportingFrame] : getRootElements(nextElements),
exportPadding, exportPadding,
); );
@@ -226,7 +192,7 @@ export const exportToCanvas = async (
const { imageCache } = await updateImageCache({ const { imageCache } = await updateImageCache({
imageCache: new Map(), imageCache: new Map(),
fileIds: getInitializedImageElements(elementsForRender).map( fileIds: getInitializedImageElements(nextElements).map(
(element) => element.fileId, (element) => element.fileId,
), ),
files, files,
@@ -235,12 +201,15 @@ export const exportToCanvas = async (
renderStaticScene({ renderStaticScene({
canvas, canvas,
rc: rough.canvas(canvas), rc: rough.canvas(canvas),
elements: elementsForRender, elements: nextElements,
visibleElements: elementsForRender, visibleElements: nextElements,
scale, scale,
appState: { appState: {
...appState, ...appState,
frameRendering, frameRendering: getFrameRenderingConfig(
exportingFrame ?? null,
appState.frameRendering ?? null,
),
viewBackgroundColor: exportBackground ? viewBackgroundColor : null, viewBackgroundColor: exportBackground ? viewBackgroundColor : null,
scrollX: -minX + exportPadding, scrollX: -minX + exportPadding,
scrollY: -minY + exportPadding, scrollY: -minY + exportPadding,
@@ -280,14 +249,8 @@ export const exportToSvg = async (
const tempScene = __createSceneForElementsHack__(elements); const tempScene = __createSceneForElementsHack__(elements);
elements = tempScene.getNonDeletedElements(); elements = tempScene.getNonDeletedElements();
const frameRendering = getFrameRenderingConfig(
opts?.exportingFrame ?? null,
appState.frameRendering ?? null,
);
let { let {
exportPadding = DEFAULT_EXPORT_PADDING, exportPadding = DEFAULT_EXPORT_PADDING,
exportWithDarkMode = false,
viewBackgroundColor, viewBackgroundColor,
exportScale = 1, exportScale = 1,
exportEmbedScene, exportEmbedScene,
@@ -295,15 +258,19 @@ export const exportToSvg = async (
const { exportingFrame = null } = opts || {}; const { exportingFrame = null } = opts || {};
const elementsForRender = prepareElementsForRender({ let nextElements: ExcalidrawElement[] = [];
elements,
exportingFrame,
exportWithDarkMode,
frameRendering,
});
if (exportingFrame) { if (exportingFrame) {
exportPadding = 0; exportPadding = 0;
nextElements = elementsOverlappingBBox({
elements,
bounds: exportingFrame,
type: "overlap",
});
} else {
nextElements = addFrameLabelsAsTextElements(elements, {
exportWithDarkMode: appState.exportWithDarkMode ?? false,
});
} }
let metadata = ""; let metadata = "";
@@ -327,7 +294,7 @@ export const exportToSvg = async (
} }
const [minX, minY, width, height] = getCanvasSize( const [minX, minY, width, height] = getCanvasSize(
exportingFrame ? [exportingFrame] : getRootElements(elementsForRender), exportingFrame ? [exportingFrame] : getRootElements(nextElements),
exportPadding, exportPadding,
); );
@@ -338,7 +305,7 @@ export const exportToSvg = async (
svgRoot.setAttribute("viewBox", `0 0 ${width} ${height}`); svgRoot.setAttribute("viewBox", `0 0 ${width} ${height}`);
svgRoot.setAttribute("width", `${width * exportScale}`); svgRoot.setAttribute("width", `${width * exportScale}`);
svgRoot.setAttribute("height", `${height * exportScale}`); svgRoot.setAttribute("height", `${height * exportScale}`);
if (exportWithDarkMode) { if (appState.exportWithDarkMode) {
svgRoot.setAttribute("filter", THEME_FILTER); svgRoot.setAttribute("filter", THEME_FILTER);
} }
@@ -413,12 +380,15 @@ export const exportToSvg = async (
} }
const rsvg = rough.svg(svgRoot); const rsvg = rough.svg(svgRoot);
renderSceneToSvg(elementsForRender, rsvg, svgRoot, files || {}, { renderSceneToSvg(nextElements, rsvg, svgRoot, files || {}, {
offsetX, offsetX,
offsetY, offsetY,
exportWithDarkMode, exportWithDarkMode: appState.exportWithDarkMode ?? false,
renderEmbeddables: opts?.renderEmbeddables ?? false, renderEmbeddables: opts?.renderEmbeddables ?? false,
frameRendering, frameRendering: getFrameRenderingConfig(
exportingFrame ?? null,
appState.frameRendering ?? null,
),
}); });
tempScene.destroy(); tempScene.destroy();