diff --git a/excalidraw-app/App.tsx b/excalidraw-app/App.tsx index bb62a0e96..07e833c02 100644 --- a/excalidraw-app/App.tsx +++ b/excalidraw-app/App.tsx @@ -848,6 +848,13 @@ const ExcalidrawWrapper = () => { handleKeyboardGlobally={true} autoFocus={true} theme={editorTheme} + customOptions={{ + disableKeyEvents: true, + // hideMainToolbar: true, + // hideMenu: true, + // hideFooter: true, + hideContextMenu: true, + }} renderTopRightUI={(isMobile) => { if (isMobile || !collabAPI || isCollabDisabled) { return null; diff --git a/package.json b/package.json index 6f57f7e7c..8fa623852 100644 --- a/package.json +++ b/package.json @@ -78,8 +78,8 @@ "autorelease": "node scripts/autorelease.js", "prerelease:excalidraw": "node scripts/prerelease.js", "release:excalidraw": "node scripts/release.js", - "rm:build": "rm -rf excalidraw-app/{build,dist,dev-dist} && rm -rf packages/*/{dist,build} && rm -rf examples/*/{build,dist}", - "rm:node_modules": "rm -rf node_modules && rm -rf excalidraw-app/node_modules && rm -rf packages/*/node_modules", + "rm:build": "rimraf -rfexcalidraw-app/{build,dist,dev-dist} && rimraf -rfpackages/*/{dist,build} && rimraf -rfexamples/*/{build,dist}", + "rm:node_modules": "rimraf -rfnode_modules && rimraf -rfexcalidraw-app/node_modules && rimraf -rfpackages/*/node_modules", "clean-install": "yarn rm:node_modules && yarn install" }, "resolutions": { diff --git a/packages/common/package.json b/packages/common/package.json index 3e5f6413a..398212ff7 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -50,7 +50,7 @@ "bugs": "https://github.com/excalidraw/excalidraw/issues", "repository": "https://github.com/excalidraw/excalidraw", "scripts": { - "gen:types": "rm -rf types && tsc", - "build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types" + "gen:types": "rimraf -rf types && tsc", + "build:esm": "rimraf -rf dist && node ../../scripts/buildBase.js && yarn gen:types" } } diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index cd3bd7a15..a7da91434 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -438,6 +438,7 @@ export const TOOL_TYPE = { magicframe: "magicframe", embeddable: "embeddable", laser: "laser", + blur: "blur", } as const; export const EDITOR_LS_KEYS = { diff --git a/packages/common/src/keys.ts b/packages/common/src/keys.ts index 948e7f568..ba279a6d8 100644 --- a/packages/common/src/keys.ts +++ b/packages/common/src/keys.ts @@ -1,7 +1,10 @@ +import type { KeyboardModifiersObject } from "@excalidraw/excalidraw/types"; + import { isDarwin } from "./constants"; import type { ValueOf } from "./utility-types"; + export const CODES = { EQUAL: "Equal", MINUS: "Minus", @@ -140,12 +143,60 @@ export const isArrowKey = (key: string) => key === KEYS.ARROW_DOWN || key === KEYS.ARROW_UP; -export const shouldResizeFromCenter = (event: MouseEvent | KeyboardEvent) => +const shouldResizeFromCenterDefault = (event: MouseEvent | KeyboardEvent) => event.altKey; -export const shouldMaintainAspectRatio = (event: MouseEvent | KeyboardEvent) => +const shouldMaintainAspectRatioDefault = (event: MouseEvent | KeyboardEvent) => event.shiftKey; +const shouldRotateWithDiscreteAngleDefault = ( + event: MouseEvent | KeyboardEvent | React.PointerEvent, +) => event.shiftKey; + +const shouldSnappingDefault = (event: KeyboardModifiersObject) => + event[KEYS.CTRL_OR_CMD]; + +let shouldResizeFromCenterFunction = shouldResizeFromCenterDefault; +let shouldMaintainAspectRatioFunction = shouldMaintainAspectRatioDefault; +let shouldRotateWithDiscreteAngleFunction = + shouldRotateWithDiscreteAngleDefault; +let shouldSnappingFunction = shouldSnappingDefault; + +export const setShouldResizeFromCenter = ( + shouldResizeFromCenter: (event: MouseEvent | KeyboardEvent) => boolean, +) => { + shouldResizeFromCenterFunction = shouldResizeFromCenter; +}; + +export const setShouldMaintainAspectRatio = ( + shouldMaintainAspectRatio: (event: MouseEvent | KeyboardEvent) => boolean, +) => { + shouldMaintainAspectRatioFunction = shouldMaintainAspectRatio; +}; + +export const setShouldRotateWithDiscreteAngle = ( + shouldRotateWithDiscreteAngle: ( + event: MouseEvent | KeyboardEvent | React.PointerEvent, + ) => boolean, +) => { + shouldRotateWithDiscreteAngleFunction = shouldRotateWithDiscreteAngle; +}; + +export const setShouldSnapping = ( + shouldSnapping: (event: KeyboardModifiersObject) => boolean, +) => { + shouldSnappingFunction = shouldSnapping; +}; + +export const shouldResizeFromCenter = (event: MouseEvent | KeyboardEvent) => + shouldResizeFromCenterFunction(event); + +export const shouldMaintainAspectRatio = (event: MouseEvent | KeyboardEvent) => + shouldMaintainAspectRatioFunction(event); + export const shouldRotateWithDiscreteAngle = ( event: MouseEvent | KeyboardEvent | React.PointerEvent, -) => event.shiftKey; +) => shouldRotateWithDiscreteAngleFunction(event); + +export const shouldSnapping = (event: KeyboardModifiersObject) => + shouldSnappingFunction(event); diff --git a/packages/element/package.json b/packages/element/package.json index ae810e374..9de5dffa1 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -50,7 +50,7 @@ "bugs": "https://github.com/excalidraw/excalidraw/issues", "repository": "https://github.com/excalidraw/excalidraw", "scripts": { - "gen:types": "rm -rf types && tsc", - "build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types" + "gen:types": "rimraf -rf types && tsc", + "build:esm": "rimraf -rf dist && node ../../scripts/buildBase.js && yarn gen:types" } } diff --git a/packages/element/src/Shape.ts b/packages/element/src/Shape.ts index 4def41957..1f6de9ec8 100644 --- a/packages/element/src/Shape.ts +++ b/packages/element/src/Shape.ts @@ -98,6 +98,7 @@ export const generateRoughOptions = ( case "iframe": case "embeddable": case "diamond": + case "blur": case "ellipse": { options.fillStyle = element.fillStyle; options.fill = isTransparent(element.backgroundColor) @@ -326,6 +327,7 @@ export const _generateElementShape = ( switch (element.type) { case "rectangle": case "iframe": + case "blur": case "embeddable": { let shape: ElementShapes[typeof element.type]; // this is for rendering the stroke/bg of the embeddable, especially diff --git a/packages/element/src/comparisons.ts b/packages/element/src/comparisons.ts index 75fac889d..3aee07b05 100644 --- a/packages/element/src/comparisons.ts +++ b/packages/element/src/comparisons.ts @@ -10,7 +10,10 @@ export const hasBackground = (type: ElementOrToolType) => type === "freedraw"; export const hasStrokeColor = (type: ElementOrToolType) => - type !== "image" && type !== "frame" && type !== "magicframe"; + type !== "image" && + type !== "frame" && + type !== "magicframe" && + type !== "blur"; export const hasStrokeWidth = (type: ElementOrToolType) => type === "rectangle" || @@ -39,6 +42,10 @@ export const canChangeRoundness = (type: ElementOrToolType) => type === "diamond" || type === "image"; +export const canChangeBlur = (type: ElementOrToolType) => type === "blur"; + +export const canChangeLayer = (type: ElementOrToolType) => type !== "blur"; + export const toolIsArrow = (type: ElementOrToolType) => type === "arrow"; export const canHaveArrowheads = (type: ElementOrToolType) => type === "arrow"; diff --git a/packages/element/src/newElement.ts b/packages/element/src/newElement.ts index 53a2f05ae..5b9d252df 100644 --- a/packages/element/src/newElement.ts +++ b/packages/element/src/newElement.ts @@ -46,6 +46,7 @@ import type { ExcalidrawArrowElement, FixedSegment, ExcalidrawElbowArrowElement, + ExcalidrawBlurElement, } from "./types"; export type ElementConstructorOpts = MarkOptional< @@ -212,6 +213,25 @@ export const newMagicFrameElement = ( return frameElement; }; +export const newBlurElement = ( + opts: { + blur: number; + } & ElementConstructorOpts, +): NonDeleted => { + const blurElement = newElementWith( + { + ..._newElementBase("blur", opts), + type: "blur", + blur: opts.blur, + fillStyle: "solid", + backgroundColor: "#000000", + }, + {}, + ); + + return blurElement; +}; + /** computes element x/y offset based on textAlign/verticalAlign */ const getTextElementPositionOffsets = ( opts: { diff --git a/packages/element/src/renderElement.ts b/packages/element/src/renderElement.ts index c8091e8ed..36f6d3bdf 100644 --- a/packages/element/src/renderElement.ts +++ b/packages/element/src/renderElement.ts @@ -404,6 +404,8 @@ const drawElementOnCanvas = ( rc.draw(ShapeCache.get(element)!); break; } + case "blur": + break; case "arrow": case "line": { context.lineJoin = "round"; @@ -814,6 +816,7 @@ export const renderElement = ( case "image": case "text": case "iframe": + case "blur": case "embeddable": { // TODO investigate if we can do this in situ. Right now we need to call // beforehand because math helpers (such as getElementAbsoluteCoords) diff --git a/packages/element/src/shapes.ts b/packages/element/src/shapes.ts index 96542c538..cd690e728 100644 --- a/packages/element/src/shapes.ts +++ b/packages/element/src/shapes.ts @@ -58,6 +58,7 @@ export const getElementShape = ( case "embeddable": case "image": case "iframe": + case "blur": case "text": case "selection": return getPolygonShape(element); diff --git a/packages/element/src/typeChecks.ts b/packages/element/src/typeChecks.ts index 54619726d..9369b6c60 100644 --- a/packages/element/src/typeChecks.ts +++ b/packages/element/src/typeChecks.ts @@ -28,6 +28,7 @@ import type { PointBinding, FixedPointBinding, ExcalidrawFlowchartNodeElement, + ExcalidrawBlurElement, } from "./types"; export const isInitializedImageElement = ( @@ -107,6 +108,12 @@ export const isLinearElement = ( return element != null && isLinearElementType(element.type); }; +export const isBlurElement = ( + element?: ExcalidrawElement | null, +): element is ExcalidrawBlurElement => { + return element != null && isBlurElementType(element.type); +}; + export const isArrowElement = ( element?: ExcalidrawElement | null, ): element is ExcalidrawArrowElement => { @@ -127,6 +134,10 @@ export const isLinearElementType = ( ); }; +export const isBlurElementType = (elementType: ElementOrToolType): boolean => { + return elementType === "blur"; +}; + export const isBindingElement = ( element?: ExcalidrawElement | null, includeLocked = true, @@ -231,6 +242,7 @@ export const isExcalidrawElement = ( case "frame": case "magicframe": case "image": + case "blur": case "selection": { return true; } diff --git a/packages/element/src/types.ts b/packages/element/src/types.ts index 3b40135d5..fd00f8a28 100644 --- a/packages/element/src/types.ts +++ b/packages/element/src/types.ts @@ -89,6 +89,11 @@ export type ExcalidrawRectangleElement = _ExcalidrawElementBase & { type: "rectangle"; }; +export type ExcalidrawBlurElement = _ExcalidrawElementBase & { + type: "blur"; + blur: number; +}; + export type ExcalidrawDiamondElement = _ExcalidrawElementBase & { type: "diamond"; }; @@ -212,7 +217,8 @@ export type ExcalidrawElement = | ExcalidrawFrameElement | ExcalidrawMagicFrameElement | ExcalidrawIframeElement - | ExcalidrawEmbeddableElement; + | ExcalidrawEmbeddableElement + | ExcalidrawBlurElement; export type ExcalidrawNonSelectionElement = Exclude< ExcalidrawElement, diff --git a/packages/excalidraw/actions/actionAlign.tsx b/packages/excalidraw/actions/actionAlign.tsx index 0ef938c67..9d7049d82 100644 --- a/packages/excalidraw/actions/actionAlign.tsx +++ b/packages/excalidraw/actions/actionAlign.tsx @@ -8,6 +8,8 @@ import { KEYS, arrayToMap, getShortcutKey } from "@excalidraw/common"; import { alignElements } from "@excalidraw/element/align"; +import { useContext } from "react"; + import type { ExcalidrawElement } from "@excalidraw/element/types"; import type { Alignment } from "@excalidraw/element/align"; @@ -27,9 +29,14 @@ import { t } from "../i18n"; import { isSomeElementSelected } from "../scene"; import { CaptureUpdateAction } from "../store"; -import { register } from "./register"; +import { + ExcalidrawPropsCustomOptionsContext, + type AppClassProperties, + type AppState, + type UIAppState, +} from "../types"; -import type { AppClassProperties, AppState, UIAppState } from "../types"; +import { register } from "./register"; export const alignActionsPredicate = ( appState: UIAppState, @@ -81,19 +88,40 @@ export const actionAlignTop = register({ }, keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_UP, - PanelComponent: ({ elements, appState, updateData, app }) => ( -