From f2d8fae6eaa2b6990210f2d4c42ad976e11e207d Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 6 Jun 2023 17:15:12 +0100 Subject: [PATCH] hoist opacity out of props (#1526) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change hoists opacity out of props and changes it to a number instead of an enum. The change to a number is to make tldraw more flexible for library consumers who might want more expressivity with opacity than our 5 possible values allow. the tldraw editor will now happily respect any opacity between 0 and 1. The limit to our supported values is enforced only in the UI. I think this is limited enough that it's a reasonable tradeoff between in-app simplicity and giving external developers the flexibility they need. There's a new `opacityForNextShape` property on the instance. This works exactly the same way as propsForNextShape does, except... it's just for opacity. With this, there should be no user-facing changes to how opacity works in tldraw. There are also new `opacity`/`setOpacity` APIs in the editor that work with it/selections similar to how props do. @ds300 do you mind reviewing the migrations here? ### Change Type - [x] `major` — Breaking Change ### Test Plan - [x] Unit Tests - [ ] Webdriver tests ### Release Notes [internal only for now] --- .../examples/src/3-custom-config/CardShape.ts | 3 +- .../src/3-custom-config/CardShapeUtil.tsx | 1 - packages/editor/api-report.md | 20 +- packages/editor/src/lib/constants.ts | 7 - packages/editor/src/lib/editor/Editor.ts | 189 ++++++++++++------ .../ArrowShapeUtil/ArrowShapeUtil.tsx | 1 - .../BookmarkShapeUtil/BookmarkShapeUtil.tsx | 1 - .../DrawShapeUtil/DrawShapeUtil.tsx | 1 - .../EmbedShapeUtil/EmbedShapeUtil.tsx | 1 - .../FrameShapeUtil/FrameShapeUtil.tsx | 3 +- .../shapeutils/GeoShapeUtil/GeoShapeUtil.tsx | 1 - .../GroupShapeUtil/GroupShapeUtil.tsx | 2 +- .../HighlightShapeUtil/HighlightShapeUtil.tsx | 1 - .../ImageShapeUtil/ImageShapeUtil.tsx | 1 - .../LineShapeUtil/LineShapeUtil.tsx | 1 - .../__snapshots__/LineShapeUtil.test.ts.snap | 2 +- .../NoteShapeUtil/NoteShapeUtil.tsx | 1 - .../TextShapeUtil/TextShapeUtil.tsx | 1 - .../VideoShapeUtil/VideoShapeUtil.tsx | 1 - .../tools/ArrowShapeTool/ArrowShapeTool.ts | 1 - .../BaseBoxShapeTool/BaseBoxShapeTool.ts | 3 - .../tools/DrawShapeTool/DrawShapeTool.ts | 3 +- .../tools/FrameShapeTool/FrameShapeTool.ts | 3 - .../editor/tools/GeoShapeTool/GeoShapeTool.ts | 2 +- .../HighlightShapeTool/HighlightShapeTool.ts | 3 +- .../tools/LineShapeTool/LineShapeTool.ts | 2 +- .../tools/NoteShapeTool/NoteShapeTool.ts | 3 +- .../lib/editor/tools/SelectTool/SelectTool.ts | 2 +- .../editor/src/lib/editor/tools/StateNode.ts | 1 + .../tools/TextShapeTool/TextShapeTool.ts | 3 +- .../lib/test/{App.test.ts => Editor.test.tsx} | 93 +++++++++ .../editor/src/lib/test/TldrawEditor.test.tsx | 10 +- .../__snapshots__/packShapes.test.ts.snap | 12 +- .../lib/test/commands/createShapes.test.ts | 4 +- .../lib/test/commands/updateShapes.test.ts | 6 +- packages/editor/src/lib/test/jsx.tsx | 3 +- packages/editor/src/lib/test/props.test.ts | 23 --- .../src/lib/test/tools/TLSelectTool.test.ts | 3 +- .../tools/__snapshots__/resizing.test.ts.snap | 2 +- .../src/lib/test/tools/cropping.test.ts | 1 - .../editor/src/lib/test/tools/groups.test.ts | 4 +- packages/editor/src/lib/utils/assets.ts | 10 +- packages/tlschema/api-report.md | 31 +-- packages/tlschema/src/createTLSchema.ts | 2 +- packages/tlschema/src/index.ts | 7 +- packages/tlschema/src/migrations.test.ts | 68 ++++++- packages/tlschema/src/records/TLInstance.ts | 33 ++- packages/tlschema/src/records/TLShape.ts | 42 +++- packages/tlschema/src/shapes/TLArrowShape.ts | 3 - packages/tlschema/src/shapes/TLBaseShape.ts | 3 + .../tlschema/src/shapes/TLBookmarkShape.ts | 3 - packages/tlschema/src/shapes/TLDrawShape.ts | 3 - packages/tlschema/src/shapes/TLEmbedShape.ts | 3 - packages/tlschema/src/shapes/TLFrameShape.ts | 3 - packages/tlschema/src/shapes/TLGeoShape.ts | 3 - packages/tlschema/src/shapes/TLGroupShape.ts | 9 +- .../tlschema/src/shapes/TLHighlightShape.ts | 3 - packages/tlschema/src/shapes/TLIconShape.ts | 3 - packages/tlschema/src/shapes/TLImageShape.ts | 3 - packages/tlschema/src/shapes/TLLineShape.ts | 3 - packages/tlschema/src/shapes/TLNoteShape.ts | 3 - packages/tlschema/src/shapes/TLTextShape.ts | 3 - packages/tlschema/src/shapes/TLVideoShape.ts | 3 - packages/tlschema/src/styles/TLBaseStyle.ts | 1 - .../tlschema/src/styles/TLOpacityStyle.ts | 19 +- packages/tlschema/src/styles/style-types.ts | 4 - packages/tlschema/src/util-types.ts | 9 - .../lib/components/StylePanel/StylePanel.tsx | 48 +++-- .../hooks/clipboard/pasteExcalidrawContent.ts | 17 +- 69 files changed, 478 insertions(+), 289 deletions(-) rename packages/editor/src/lib/test/{App.test.ts => Editor.test.tsx} (78%) diff --git a/apps/examples/src/3-custom-config/CardShape.ts b/apps/examples/src/3-custom-config/CardShape.ts index 367c3b75f..356d586ff 100644 --- a/apps/examples/src/3-custom-config/CardShape.ts +++ b/apps/examples/src/3-custom-config/CardShape.ts @@ -1,9 +1,8 @@ -import { TLBaseShape, TLOpacityType } from '@tldraw/tldraw' +import { TLBaseShape } from '@tldraw/tldraw' export type CardShape = TLBaseShape< 'card', { - opacity: TLOpacityType // necessary for all shapes at the moment, others can be whatever you want! w: number h: number } diff --git a/apps/examples/src/3-custom-config/CardShapeUtil.tsx b/apps/examples/src/3-custom-config/CardShapeUtil.tsx index 7f92f5d5d..469ec28ba 100644 --- a/apps/examples/src/3-custom-config/CardShapeUtil.tsx +++ b/apps/examples/src/3-custom-config/CardShapeUtil.tsx @@ -13,7 +13,6 @@ export class CardShapeUtil extends BaseBoxShapeUtil { // Default props — used for shapes created with the tool override defaultProps(): CardShape['props'] { return { - opacity: '1', w: 300, h: 300, } diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index 1e9f93bdb..a4aa41db6 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -178,8 +178,6 @@ export abstract class BaseBoxShapeTool extends StateNode { static initial: string; // (undocumented) abstract shapeType: string; - // (undocumented) - styles: ("align" | "arrowheadEnd" | "arrowheadStart" | "color" | "dash" | "fill" | "font" | "geo" | "icon" | "labelColor" | "opacity" | "size" | "spline" | "verticalAlign")[]; } // @public (undocumented) @@ -623,6 +621,8 @@ export class Editor extends EventEmitter { description: string; }>; get onlySelectedShape(): null | TLShape; + // (undocumented) + get opacity(): null | number; get openMenus(): string[]; packShapes(ids?: TLShapeId[], padding?: number): this; get pages(): TLPage[]; @@ -717,6 +717,7 @@ export class Editor extends EventEmitter { setHoveredId(id?: null | TLShapeId): this; setInstancePageState(partial: Partial, ephemeral?: boolean): void; setLocale(locale: string): void; + setOpacity(opacity: number, ephemeral?: boolean, squashing?: boolean): this; // (undocumented) setPenMode(isPenMode: boolean): this; // @internal (undocumented) @@ -915,7 +916,6 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { fill: "none" | "pattern" | "semi" | "solid"; dash: "dashed" | "dotted" | "draw" | "solid"; size: "l" | "m" | "s" | "xl"; - opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1"; font: "draw" | "mono" | "sans" | "serif"; align: "end" | "middle" | "start"; verticalAlign: "end" | "middle" | "start"; @@ -931,6 +931,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { index: string; parentId: TLParentId; isLocked: boolean; + opacity: number; id: TLShapeId; typeName: "shape"; } | undefined; @@ -944,7 +945,6 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { fill: "none" | "pattern" | "semi" | "solid"; dash: "dashed" | "dotted" | "draw" | "solid"; size: "l" | "m" | "s" | "xl"; - opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1"; font: "draw" | "mono" | "sans" | "serif"; align: "end" | "middle" | "start"; verticalAlign: "end" | "middle" | "start"; @@ -960,6 +960,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { index: string; parentId: TLParentId; isLocked: boolean; + opacity: number; id: TLShapeId; typeName: "shape"; } | undefined; @@ -975,6 +976,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { index: string; parentId: TLParentId; isLocked: boolean; + opacity: number; id: TLShapeId; typeName: "shape"; } | { @@ -988,6 +990,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { index: string; parentId: TLParentId; isLocked: boolean; + opacity: number; id: TLShapeId; typeName: "shape"; } | undefined; @@ -1702,7 +1705,6 @@ export class NoteShapeUtil extends ShapeUtil { font: "draw" | "mono" | "sans" | "serif"; align: "end" | "middle" | "start"; verticalAlign: "end" | "middle" | "start"; - opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1"; url: string; text: string; }; @@ -1713,6 +1715,7 @@ export class NoteShapeUtil extends ShapeUtil { index: string; parentId: TLParentId; isLocked: boolean; + opacity: number; id: TLShapeId; typeName: "shape"; } | undefined; @@ -1725,7 +1728,6 @@ export class NoteShapeUtil extends ShapeUtil { font: "draw" | "mono" | "sans" | "serif"; align: "end" | "middle" | "start"; verticalAlign: "end" | "middle" | "start"; - opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1"; url: string; text: string; }; @@ -1736,6 +1738,7 @@ export class NoteShapeUtil extends ShapeUtil { index: string; parentId: TLParentId; isLocked: boolean; + opacity: number; id: TLShapeId; typeName: "shape"; } | undefined; @@ -1962,6 +1965,8 @@ export abstract class StateNode implements Partial { // (undocumented) path: Computed; // (undocumented) + shapeType?: string; + // (undocumented) readonly styles: TLStyleType[]; // (undocumented) transition(id: string, info: any): this; @@ -2024,6 +2029,7 @@ export class TextShapeUtil extends ShapeUtil { index: string; parentId: TLParentId; isLocked: boolean; + opacity: number; props: TLTextShapeProps; id: TLShapeId; typeName: "shape"; @@ -2038,7 +2044,6 @@ export class TextShapeUtil extends ShapeUtil { size: "l" | "m" | "s" | "xl"; font: "draw" | "mono" | "sans" | "serif"; align: "end" | "middle" | "start"; - opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1"; text: string; scale: number; autoSize: boolean; @@ -2048,6 +2053,7 @@ export class TextShapeUtil extends ShapeUtil { index: string; parentId: TLParentId; isLocked: boolean; + opacity: number; id: TLShapeId; typeName: "shape"; } | undefined; diff --git a/packages/editor/src/lib/constants.ts b/packages/editor/src/lib/constants.ts index ac23a2d5f..082df50a8 100644 --- a/packages/editor/src/lib/constants.ts +++ b/packages/editor/src/lib/constants.ts @@ -217,13 +217,6 @@ export const STYLES: TLStyleCollections = { { id: 'l', type: 'size', icon: 'size-large' }, { id: 'xl', type: 'size', icon: 'size-extra-large' }, ], - opacity: [ - { id: '0.1', type: 'opacity', icon: 'color' }, - { id: '0.25', type: 'opacity', icon: 'color' }, - { id: '0.5', type: 'opacity', icon: 'color' }, - { id: '0.75', type: 'opacity', icon: 'color' }, - { id: '1', type: 'opacity', icon: 'color' }, - ], font: [ { id: 'draw', type: 'font', icon: 'font-draw' }, { id: 'sans', type: 'font', icon: 'font-sans' }, diff --git a/packages/editor/src/lib/editor/Editor.ts b/packages/editor/src/lib/editor/Editor.ts index 5d0acb39f..9d8e2fd5a 100644 --- a/packages/editor/src/lib/editor/Editor.ts +++ b/packages/editor/src/lib/editor/Editor.ts @@ -1151,6 +1151,42 @@ export class Editor extends EventEmitter { return next } + @computed get opacity(): number | null { + if (this.isIn('select') && this.selectedIds.length > 0) { + const shapesToCheck: TLShape[] = [] + const addShape = (shapeId: TLShapeId) => { + const shape = this.getShapeById(shapeId) + if (!shape) return + // For groups, ignore the opacity of the group shape and instead include + // the opacity of the group's children. These are the shapes that would have + // their opacity changed if the user called `setOpacity` on the current selection. + if (shape.type === 'group') { + for (const childId of this.getSortedChildIds(shape.id)) { + addShape(childId) + } + } else { + shapesToCheck.push(shape) + } + } + for (const shapeId of this.selectedIds) { + addShape(shapeId) + } + + let opacity: number | null = null + for (const shape of shapesToCheck) { + if (opacity === null) { + opacity = shape.opacity + } else if (opacity !== shape.opacity) { + return null + } + } + + return opacity + } else { + return this.instanceState.opacityForNextShape + } + } + /** * An array of all of the shapes on the current page. * @@ -2242,8 +2278,7 @@ export class Editor extends EventEmitter { const shape = this.getShapeById(id) if (!shape) return - // todo: move opacity to a property of shape, rather than a property of props - let opacity = (+(shape.props as { opacity: string }).opacity ?? 1) * parentOpacity + let opacity = shape.opacity * parentOpacity let isShapeErasing = false if (!isAncestorErasing && erasingIdsSet?.has(id)) { @@ -4675,7 +4710,7 @@ export class Editor extends EventEmitter { // We then look up each key in the tab state's props; and if it's there, // we use the value from the tab state's props instead of the default. // Note that props will never include opacity. - const { propsForNextShape } = this.instanceState + const { propsForNextShape, opacityForNextShape } = this.instanceState for (const key in initialProps) { if (key in propsForNextShape) { if (key === 'url') continue @@ -4693,6 +4728,7 @@ export class Editor extends EventEmitter { ).create({ ...partial, index, + opacity: partial.opacity ?? opacityForNextShape, parentId: partial.parentId ?? focusLayerId, props: 'props' in partial ? { ...initialProps, ...partial.props } : initialProps, }) @@ -7757,11 +7793,75 @@ export class Editor extends EventEmitter { return this } + /** + * Set the current opacity. This will effect any selected shapes, or the + * next-created shape. + * + * @example + * ```ts + * editor.setOpacity(0.5) + * editor.setOpacity(0.5, true) + * ``` + * + * @param opacity - The opacity to set. Must be a number between 0 and 1 + * inclusive. + * @param ephemeral - Whether the opacity change is ephemeral. Ephemeral + * changes don't get added to the undo/redo stack. Defaults to false. + * @param squashing - Whether the opacity change will be squashed into the + * existing history entry rather than creating a new one. Defaults to false. + */ + setOpacity(opacity: number, ephemeral = false, squashing = false) { + this.history.batch(() => { + if (this.isIn('select')) { + const { + pageState: { selectedIds }, + } = this + + const shapesToUpdate: TLShape[] = [] + + // We can have many deep levels of grouped shape + // Making a recursive function to look through all the levels + const addShapeById = (id: TLShape['id']) => { + const shape = this.getShapeById(id) + if (!shape) return + if (this.isShapeOfType(shape, GroupShapeUtil)) { + const childIds = this.getSortedChildIds(id) + for (const childId of childIds) { + addShapeById(childId) + } + } else { + shapesToUpdate.push(shape) + } + } + + if (selectedIds.length > 0) { + for (const id of selectedIds) { + addShapeById(id) + } + + this.updateShapes( + shapesToUpdate.map((shape) => { + return { + id: shape.id, + type: shape.type, + opacity, + } + }), + ephemeral + ) + } + } + + this.updateInstanceState({ opacityForNextShape: opacity }, ephemeral, squashing) + }) + + return this + } + /** * Set the current props (generally styles). * * @example - * * ```ts * editor.setProp('color', 'red') * editor.setProp('color', 'red', true) @@ -7769,67 +7869,43 @@ export class Editor extends EventEmitter { * * @param key - The key to set. * @param value - The value to set. - * @param ephemeral - Whether the style is ephemeral. Defaults to false. + * @param ephemeral - Whether the style change is ephemeral. Ephemeral + * changes don't get added to the undo/redo stack. Defaults to false. + * @param squashing - Whether the style change will be squashed into the + * existing history entry rather than creating a new one. Defaults to false. * @public */ setProp(key: TLShapeProp, value: any, ephemeral = false, squashing = false) { - const children: (TLShape | undefined)[] = [] - // We can have many deep levels of grouped shape - // Making a recursive function to look through all the levels - const getChildProp = (id: TLShape['id']) => { - const childIds = this.getSortedChildIds(id) - for (const childId of childIds) { - const childShape = this.getShapeById(childId) - if (childShape?.type === 'group') { - getChildProp(childShape.id) - } - children.push(childShape) - } - } - this.history.batch(() => { - this.updateInstanceState( - { - propsForNextShape: setPropsForNextShape(this.instanceState.propsForNextShape, { - [key]: value, - }), - }, - ephemeral, - squashing - ) - if (this.isIn('select')) { const { pageState: { selectedIds }, } = this if (selectedIds.length > 0) { - const shapes = compact( - selectedIds.map((id) => { - const shape = this.getShapeById(id) - if (shape?.type === 'group') { - const childIds = this.getSortedChildIds(shape.id) - for (const childId of childIds) { - const childShape = this.getShapeById(childId) - if (childShape?.type === 'group') { - getChildProp(childShape.id) - } - children.push(childShape) - } - return children - } else { - return shape + const shapesToUpdate: TLShape[] = [] + + // We can have many deep levels of grouped shape + // Making a recursive function to look through all the levels + const addShapeById = (id: TLShape['id']) => { + const shape = this.getShapeById(id) + if (!shape) return + if (this.isShapeOfType(shape, GroupShapeUtil)) { + const childIds = this.getSortedChildIds(id) + for (const childId of childIds) { + addShapeById(childId) } - }) - ) - .flat() - .filter( - (shape) => - shape!.props[key as keyof TLShape['props']] !== undefined && shape?.type !== 'group' - ) as TLShape[] + } else if (shape!.props[key as keyof TLShape['props']] !== undefined) { + shapesToUpdate.push(shape) + } + } + + for (const id of selectedIds) { + addShapeById(id) + } this.updateShapes( - shapes.map((shape) => { + shapesToUpdate.map((shape) => { const props = { ...shape.props, [key]: value } if (key === 'color' && 'labelColor' in props) { props.labelColor = 'black' @@ -7844,10 +7920,10 @@ export class Editor extends EventEmitter { ephemeral ) - if (key !== 'color' && key !== 'opacity') { + if (key !== 'color') { const changes: TLShapePartial[] = [] - for (const shape of shapes) { + for (const shape of shapesToUpdate) { const currentShape = this.getShapeById(shape.id) if (!currentShape) continue const util = this.getShapeUtil(currentShape) @@ -8903,9 +8979,8 @@ export class Editor extends EventEmitter { index: highestIndex, x, y, - props: { - opacity: '1', - }, + opacity: 1, + props: {}, }, ]) this.reparentShapesById(sortedShapeIds, groupId) diff --git a/packages/editor/src/lib/editor/shapeutils/ArrowShapeUtil/ArrowShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/ArrowShapeUtil/ArrowShapeUtil.tsx index 80684675c..af516d977 100644 --- a/packages/editor/src/lib/editor/shapeutils/ArrowShapeUtil/ArrowShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/ArrowShapeUtil/ArrowShapeUtil.tsx @@ -69,7 +69,6 @@ export class ArrowShapeUtil extends ShapeUtil { override defaultProps(): TLArrowShape['props'] { return { - opacity: '1', dash: 'draw', size: 'm', fill: 'none', diff --git a/packages/editor/src/lib/editor/shapeutils/BookmarkShapeUtil/BookmarkShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/BookmarkShapeUtil/BookmarkShapeUtil.tsx index 01807d954..32fd9cb2c 100644 --- a/packages/editor/src/lib/editor/shapeutils/BookmarkShapeUtil/BookmarkShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/BookmarkShapeUtil/BookmarkShapeUtil.tsx @@ -27,7 +27,6 @@ export class BookmarkShapeUtil extends BaseBoxShapeUtil { override defaultProps(): TLBookmarkShape['props'] { return { - opacity: '1', url: '', w: DEFAULT_BOOKMARK_WIDTH, h: DEFAULT_BOOKMARK_HEIGHT, diff --git a/packages/editor/src/lib/editor/shapeutils/DrawShapeUtil/DrawShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/DrawShapeUtil/DrawShapeUtil.tsx index b876f7a89..05d7e5814 100644 --- a/packages/editor/src/lib/editor/shapeutils/DrawShapeUtil/DrawShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/DrawShapeUtil/DrawShapeUtil.tsx @@ -36,7 +36,6 @@ export class DrawShapeUtil extends ShapeUtil { fill: 'none', dash: 'draw', size: 'm', - opacity: '1', isComplete: false, isClosed: false, isPen: false, diff --git a/packages/editor/src/lib/editor/shapeutils/EmbedShapeUtil/EmbedShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/EmbedShapeUtil/EmbedShapeUtil.tsx index 68d3e6850..3439a9c43 100644 --- a/packages/editor/src/lib/editor/shapeutils/EmbedShapeUtil/EmbedShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/EmbedShapeUtil/EmbedShapeUtil.tsx @@ -42,7 +42,6 @@ export class EmbedShapeUtil extends BaseBoxShapeUtil { override defaultProps(): TLEmbedShape['props'] { return { - opacity: '1', w: 300, h: 300, url: '', diff --git a/packages/editor/src/lib/editor/shapeutils/FrameShapeUtil/FrameShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/FrameShapeUtil/FrameShapeUtil.tsx index abada471c..d4bf76ab3 100644 --- a/packages/editor/src/lib/editor/shapeutils/FrameShapeUtil/FrameShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/FrameShapeUtil/FrameShapeUtil.tsx @@ -18,7 +18,7 @@ export class FrameShapeUtil extends BaseBoxShapeUtil { override canEdit = () => true override defaultProps(): TLFrameShape['props'] { - return { opacity: '1', w: 160 * 2, h: 90 * 2, name: '' } + return { w: 160 * 2, h: 90 * 2, name: '' } } override render(shape: TLFrameShape) { @@ -56,7 +56,6 @@ export class FrameShapeUtil extends BaseBoxShapeUtil { rect.setAttribute('width', shape.props.w.toString()) rect.setAttribute('height', shape.props.h.toString()) rect.setAttribute('fill', colors.solid) - rect.setAttribute('opacity', shape.props.opacity) rect.setAttribute('stroke', colors.fill.black) rect.setAttribute('stroke-width', '1') rect.setAttribute('rx', '1') diff --git a/packages/editor/src/lib/editor/shapeutils/GeoShapeUtil/GeoShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/GeoShapeUtil/GeoShapeUtil.tsx index fdf819220..a1deda784 100644 --- a/packages/editor/src/lib/editor/shapeutils/GeoShapeUtil/GeoShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/GeoShapeUtil/GeoShapeUtil.tsx @@ -55,7 +55,6 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { fill: 'none', dash: 'draw', size: 'm', - opacity: '1', font: 'draw', text: '', align: 'middle', diff --git a/packages/editor/src/lib/editor/shapeutils/GroupShapeUtil/GroupShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/GroupShapeUtil/GroupShapeUtil.tsx index 8e6fd21b9..223992286 100644 --- a/packages/editor/src/lib/editor/shapeutils/GroupShapeUtil/GroupShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/GroupShapeUtil/GroupShapeUtil.tsx @@ -16,7 +16,7 @@ export class GroupShapeUtil extends ShapeUtil { canBind = () => false defaultProps(): TLGroupShape['props'] { - return { opacity: '1' } + return {} } getBounds(shape: TLGroupShape): Box2d { diff --git a/packages/editor/src/lib/editor/shapeutils/HighlightShapeUtil/HighlightShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/HighlightShapeUtil/HighlightShapeUtil.tsx index 3dc8a1c89..daa78a3ea 100644 --- a/packages/editor/src/lib/editor/shapeutils/HighlightShapeUtil/HighlightShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/HighlightShapeUtil/HighlightShapeUtil.tsx @@ -27,7 +27,6 @@ export class HighlightShapeUtil extends ShapeUtil { segments: [], color: 'black', size: 'm', - opacity: '1', isComplete: false, isPen: false, } diff --git a/packages/editor/src/lib/editor/shapeutils/ImageShapeUtil/ImageShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/ImageShapeUtil/ImageShapeUtil.tsx index 105741555..9654130f4 100644 --- a/packages/editor/src/lib/editor/shapeutils/ImageShapeUtil/ImageShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/ImageShapeUtil/ImageShapeUtil.tsx @@ -56,7 +56,6 @@ export class ImageShapeUtil extends BaseBoxShapeUtil { override defaultProps(): TLImageShape['props'] { return { - opacity: '1', w: 100, h: 100, assetId: null, diff --git a/packages/editor/src/lib/editor/shapeutils/LineShapeUtil/LineShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/LineShapeUtil/LineShapeUtil.tsx index 119d9eb8a..c634a0741 100644 --- a/packages/editor/src/lib/editor/shapeutils/LineShapeUtil/LineShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/LineShapeUtil/LineShapeUtil.tsx @@ -36,7 +36,6 @@ export class LineShapeUtil extends ShapeUtil { override defaultProps(): TLLineShape['props'] { return { - opacity: '1', dash: 'draw', size: 'm', color: 'black', diff --git a/packages/editor/src/lib/editor/shapeutils/LineShapeUtil/__snapshots__/LineShapeUtil.test.ts.snap b/packages/editor/src/lib/editor/shapeutils/LineShapeUtil/__snapshots__/LineShapeUtil.test.ts.snap index 6162b9de5..7e4a81449 100644 --- a/packages/editor/src/lib/editor/shapeutils/LineShapeUtil/__snapshots__/LineShapeUtil.test.ts.snap +++ b/packages/editor/src/lib/editor/shapeutils/LineShapeUtil/__snapshots__/LineShapeUtil.test.ts.snap @@ -5,6 +5,7 @@ Object { "id": "shape:line1", "index": "a1", "isLocked": false, + "opacity": 1, "parentId": "page:id50", "props": Object { "color": "black", @@ -27,7 +28,6 @@ Object { "y": 0, }, }, - "opacity": "1", "size": "m", "spline": "line", }, diff --git a/packages/editor/src/lib/editor/shapeutils/NoteShapeUtil/NoteShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/NoteShapeUtil/NoteShapeUtil.tsx index 6fb4e06df..6f07b1b39 100644 --- a/packages/editor/src/lib/editor/shapeutils/NoteShapeUtil/NoteShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/NoteShapeUtil/NoteShapeUtil.tsx @@ -21,7 +21,6 @@ export class NoteShapeUtil extends ShapeUtil { defaultProps(): TLNoteShape['props'] { return { - opacity: '1', color: 'black', size: 'm', text: '', diff --git a/packages/editor/src/lib/editor/shapeutils/TextShapeUtil/TextShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/TextShapeUtil/TextShapeUtil.tsx index 84b3d5529..e380a4d90 100644 --- a/packages/editor/src/lib/editor/shapeutils/TextShapeUtil/TextShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/TextShapeUtil/TextShapeUtil.tsx @@ -26,7 +26,6 @@ export class TextShapeUtil extends ShapeUtil { defaultProps(): TLTextShape['props'] { return { - opacity: '1', color: 'black', size: 'm', w: 8, diff --git a/packages/editor/src/lib/editor/shapeutils/VideoShapeUtil/VideoShapeUtil.tsx b/packages/editor/src/lib/editor/shapeutils/VideoShapeUtil/VideoShapeUtil.tsx index 6da318b90..159a1d6e2 100644 --- a/packages/editor/src/lib/editor/shapeutils/VideoShapeUtil/VideoShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapeutils/VideoShapeUtil/VideoShapeUtil.tsx @@ -18,7 +18,6 @@ export class VideoShapeUtil extends BaseBoxShapeUtil { override defaultProps(): TLVideoShape['props'] { return { - opacity: '1', w: 100, h: 100, assetId: null, diff --git a/packages/editor/src/lib/editor/tools/ArrowShapeTool/ArrowShapeTool.ts b/packages/editor/src/lib/editor/tools/ArrowShapeTool/ArrowShapeTool.ts index 47731a66d..b18db281b 100644 --- a/packages/editor/src/lib/editor/tools/ArrowShapeTool/ArrowShapeTool.ts +++ b/packages/editor/src/lib/editor/tools/ArrowShapeTool/ArrowShapeTool.ts @@ -12,7 +12,6 @@ export class ArrowShapeTool extends StateNode { styles = [ 'color', - 'opacity', 'dash', 'size', 'arrowheadStart', diff --git a/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts b/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts index 4f2a002a7..09e69f38f 100644 --- a/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts +++ b/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts @@ -1,4 +1,3 @@ -import { TLStyleType } from '@tldraw/tlschema' import { StateNode } from '../StateNode' import { Idle } from './children/Idle' import { Pointing } from './children/Pointing' @@ -10,6 +9,4 @@ export abstract class BaseBoxShapeTool extends StateNode { static children = () => [Idle, Pointing] abstract shapeType: string - - styles = ['opacity'] as TLStyleType[] } diff --git a/packages/editor/src/lib/editor/tools/DrawShapeTool/DrawShapeTool.ts b/packages/editor/src/lib/editor/tools/DrawShapeTool/DrawShapeTool.ts index 28ecf9b5d..eefefbd2e 100644 --- a/packages/editor/src/lib/editor/tools/DrawShapeTool/DrawShapeTool.ts +++ b/packages/editor/src/lib/editor/tools/DrawShapeTool/DrawShapeTool.ts @@ -9,7 +9,8 @@ export class DrawShapeTool extends StateNode { static initial = 'idle' static children = () => [Idle, Drawing] - styles = ['color', 'opacity', 'dash', 'fill', 'size'] as TLStyleType[] + styles = ['color', 'dash', 'fill', 'size'] as TLStyleType[] + shapeType = 'draw' onExit = () => { const drawingState = this.children!['drawing'] as Drawing diff --git a/packages/editor/src/lib/editor/tools/FrameShapeTool/FrameShapeTool.ts b/packages/editor/src/lib/editor/tools/FrameShapeTool/FrameShapeTool.ts index 6997b7fbc..4bd572d4d 100644 --- a/packages/editor/src/lib/editor/tools/FrameShapeTool/FrameShapeTool.ts +++ b/packages/editor/src/lib/editor/tools/FrameShapeTool/FrameShapeTool.ts @@ -1,4 +1,3 @@ -import { TLStyleType } from '@tldraw/tlschema' import { BaseBoxShapeTool } from '../BaseBoxShapeTool/BaseBoxShapeTool' export class FrameShapeTool extends BaseBoxShapeTool { @@ -6,6 +5,4 @@ export class FrameShapeTool extends BaseBoxShapeTool { static initial = 'idle' shapeType = 'frame' - - styles = ['opacity'] as TLStyleType[] } diff --git a/packages/editor/src/lib/editor/tools/GeoShapeTool/GeoShapeTool.ts b/packages/editor/src/lib/editor/tools/GeoShapeTool/GeoShapeTool.ts index c8b0b69d3..bacf4f422 100644 --- a/packages/editor/src/lib/editor/tools/GeoShapeTool/GeoShapeTool.ts +++ b/packages/editor/src/lib/editor/tools/GeoShapeTool/GeoShapeTool.ts @@ -11,7 +11,6 @@ export class GeoShapeTool extends StateNode { styles = [ 'color', - 'opacity', 'dash', 'fill', 'size', @@ -20,4 +19,5 @@ export class GeoShapeTool extends StateNode { 'align', 'verticalAlign', ] as TLStyleType[] + shapeType = 'geo' } diff --git a/packages/editor/src/lib/editor/tools/HighlightShapeTool/HighlightShapeTool.ts b/packages/editor/src/lib/editor/tools/HighlightShapeTool/HighlightShapeTool.ts index afd189998..d6c3506ff 100644 --- a/packages/editor/src/lib/editor/tools/HighlightShapeTool/HighlightShapeTool.ts +++ b/packages/editor/src/lib/editor/tools/HighlightShapeTool/HighlightShapeTool.ts @@ -10,7 +10,8 @@ export class HighlightShapeTool extends StateNode { static initial = 'idle' static children = () => [Idle, Drawing] - styles = ['color', 'opacity', 'size'] as TLStyleType[] + styles = ['color', 'size'] as TLStyleType[] + shapeType = 'highlight' onExit = () => { const drawingState = this.children!['drawing'] as Drawing diff --git a/packages/editor/src/lib/editor/tools/LineShapeTool/LineShapeTool.ts b/packages/editor/src/lib/editor/tools/LineShapeTool/LineShapeTool.ts index faeab4a0b..a093630a1 100644 --- a/packages/editor/src/lib/editor/tools/LineShapeTool/LineShapeTool.ts +++ b/packages/editor/src/lib/editor/tools/LineShapeTool/LineShapeTool.ts @@ -11,5 +11,5 @@ export class LineShapeTool extends StateNode { shapeType = 'line' - styles = ['color', 'opacity', 'dash', 'size', 'spline'] as TLStyleType[] + styles = ['color', 'dash', 'size', 'spline'] as TLStyleType[] } diff --git a/packages/editor/src/lib/editor/tools/NoteShapeTool/NoteShapeTool.ts b/packages/editor/src/lib/editor/tools/NoteShapeTool/NoteShapeTool.ts index 1d7b71b57..d74da183c 100644 --- a/packages/editor/src/lib/editor/tools/NoteShapeTool/NoteShapeTool.ts +++ b/packages/editor/src/lib/editor/tools/NoteShapeTool/NoteShapeTool.ts @@ -8,5 +8,6 @@ export class NoteShapeTool extends StateNode { static initial = 'idle' static children = () => [Idle, Pointing] - styles = ['color', 'opacity', 'size', 'align', 'verticalAlign', 'font'] as TLStyleType[] + styles = ['color', 'size', 'align', 'verticalAlign', 'font'] as TLStyleType[] + shapeType = 'note' } diff --git a/packages/editor/src/lib/editor/tools/SelectTool/SelectTool.ts b/packages/editor/src/lib/editor/tools/SelectTool/SelectTool.ts index da0f66464..5870dde62 100644 --- a/packages/editor/src/lib/editor/tools/SelectTool/SelectTool.ts +++ b/packages/editor/src/lib/editor/tools/SelectTool/SelectTool.ts @@ -42,7 +42,7 @@ export class SelectTool extends StateNode { DraggingHandle, ] - styles = ['color', 'opacity', 'dash', 'fill', 'size'] as TLStyleType[] + styles = ['color', 'dash', 'fill', 'size'] as TLStyleType[] onExit = () => { if (this.editor.pageState.editingId) { diff --git a/packages/editor/src/lib/editor/tools/StateNode.ts b/packages/editor/src/lib/editor/tools/StateNode.ts index ddf6bd4cf..d11442f49 100644 --- a/packages/editor/src/lib/editor/tools/StateNode.ts +++ b/packages/editor/src/lib/editor/tools/StateNode.ts @@ -70,6 +70,7 @@ export abstract class StateNode implements Partial { current: Atom type: TLStateNodeType readonly styles: TLStyleType[] = [] + shapeType?: string initial?: string children?: Record parent: StateNode diff --git a/packages/editor/src/lib/editor/tools/TextShapeTool/TextShapeTool.ts b/packages/editor/src/lib/editor/tools/TextShapeTool/TextShapeTool.ts index e249b99ad..66d46e992 100644 --- a/packages/editor/src/lib/editor/tools/TextShapeTool/TextShapeTool.ts +++ b/packages/editor/src/lib/editor/tools/TextShapeTool/TextShapeTool.ts @@ -9,5 +9,6 @@ export class TextShapeTool extends StateNode { static children = () => [Idle, Pointing] - styles = ['color', 'opacity', 'font', 'align', 'size'] as TLStyleType[] + styles = ['color', 'font', 'align', 'size'] as TLStyleType[] + shapeType = 'text' } diff --git a/packages/editor/src/lib/test/App.test.ts b/packages/editor/src/lib/test/Editor.test.tsx similarity index 78% rename from packages/editor/src/lib/test/App.test.ts rename to packages/editor/src/lib/test/Editor.test.tsx index 858dcabda..f12622b58 100644 --- a/packages/editor/src/lib/test/App.test.ts +++ b/packages/editor/src/lib/test/Editor.test.tsx @@ -1,6 +1,7 @@ import { PageRecordType, createShapeId } from '@tldraw/tlschema' import { structuredClone } from '@tldraw/utils' import { TestEditor } from './TestEditor' +import { TL } from './jsx' let editor: TestEditor @@ -156,6 +157,98 @@ describe('Editor.setProp', () => { }) }) +describe('Editor.opacity', () => { + it('should return the current opacity', () => { + expect(editor.opacity).toBe(1) + editor.setOpacity(0.5) + expect(editor.opacity).toBe(0.5) + }) + + it('should return opacity for a single selected shape', () => { + const { A } = editor.createShapesFromJsx() + editor.setSelectedIds([A]) + expect(editor.opacity).toBe(0.3) + }) + + it('should return opacity for multiple selected shapes', () => { + const { A, B } = editor.createShapesFromJsx([ + , + , + ]) + editor.setSelectedIds([A, B]) + expect(editor.opacity).toBe(0.3) + }) + + it('should return null when multiple selected shapes have different opacity', () => { + const { A, B } = editor.createShapesFromJsx([ + , + , + ]) + editor.setSelectedIds([A, B]) + expect(editor.opacity).toBe(null) + }) + + it('ignores the opacity of groups and returns the opacity of their children', () => { + const ids = editor.createShapesFromJsx([ + + + , + ]) + editor.setSelectedIds([ids.group]) + expect(editor.opacity).toBe(0.3) + }) +}) + +describe('Editor.setOpacity', () => { + it('should set opacity for selected shapes', () => { + const ids = editor.createShapesFromJsx([ + , + , + ]) + + editor.setSelectedIds([ids.A, ids.B]) + editor.setOpacity(0.5) + + expect(editor.getShapeById(ids.A)!.opacity).toBe(0.5) + expect(editor.getShapeById(ids.B)!.opacity).toBe(0.5) + }) + + it('should traverse into groups and set opacity in their children', () => { + const ids = editor.createShapesFromJsx([ + , + + + + + + + , + ]) + + editor.setSelectedIds([ids.groupA]) + editor.setOpacity(0.5) + + // a wasn't selected... + expect(editor.getShapeById(ids.boxA)!.opacity).toBe(1) + + // b, c, & d were within a selected group... + expect(editor.getShapeById(ids.boxB)!.opacity).toBe(0.5) + expect(editor.getShapeById(ids.boxC)!.opacity).toBe(0.5) + expect(editor.getShapeById(ids.boxD)!.opacity).toBe(0.5) + + // groups get skipped + expect(editor.getShapeById(ids.groupA)!.opacity).toBe(1) + expect(editor.getShapeById(ids.groupB)!.opacity).toBe(1) + }) + + it('stores opacity on opacityForNextShape', () => { + editor.setOpacity(0.5) + expect(editor.instanceState.opacityForNextShape).toBe(0.5) + editor.setOpacity(0.6) + expect(editor.instanceState.opacityForNextShape).toBe(0.6) + }) +}) + describe('Editor.TickManager', () => { it('Does not produce NaN values when elapsed is 0', () => { // a helper that calls update pointer velocity with a given elapsed time. diff --git a/packages/editor/src/lib/test/TldrawEditor.test.tsx b/packages/editor/src/lib/test/TldrawEditor.test.tsx index e42d2523e..dd8639f2f 100644 --- a/packages/editor/src/lib/test/TldrawEditor.test.tsx +++ b/packages/editor/src/lib/test/TldrawEditor.test.tsx @@ -1,5 +1,5 @@ import { act, render, screen } from '@testing-library/react' -import { TLBaseShape, TLOpacityType, createShapeId } from '@tldraw/tlschema' +import { TLBaseShape, createShapeId } from '@tldraw/tlschema' import { TldrawEditor } from '../TldrawEditor' import { Canvas } from '../components/Canvas' import { HTMLContainer } from '../components/HTMLContainer' @@ -137,7 +137,8 @@ describe('', () => { type: 'geo', x: 0, y: 0, - props: { geo: 'rectangle', w: 100, h: 100, opacity: '1' }, + opacity: 1, + props: { geo: 'rectangle', w: 100, h: 100 }, }) // Is the shape's component rendering? @@ -165,7 +166,6 @@ describe('Custom shapes', () => { { w: number h: number - opacity: TLOpacityType } > @@ -178,7 +178,6 @@ describe('Custom shapes', () => { override defaultProps(): CardShape['props'] { return { - opacity: '1', w: 300, h: 300, } @@ -262,7 +261,8 @@ describe('Custom shapes', () => { type: 'card', x: 0, y: 0, - props: { w: 100, h: 100, opacity: '1' }, + opacity: 1, + props: { w: 100, h: 100 }, }) // Is the shape's component rendering? diff --git a/packages/editor/src/lib/test/commands/__snapshots__/packShapes.test.ts.snap b/packages/editor/src/lib/test/commands/__snapshots__/packShapes.test.ts.snap index 3f9e6cc29..6e22955ae 100644 --- a/packages/editor/src/lib/test/commands/__snapshots__/packShapes.test.ts.snap +++ b/packages/editor/src/lib/test/commands/__snapshots__/packShapes.test.ts.snap @@ -6,6 +6,7 @@ Array [ "id": "shape:boxA", "index": "a1", "isLocked": false, + "opacity": 1, "parentId": "wahtever", "props": Object { "align": "middle", @@ -17,7 +18,6 @@ Array [ "growY": 0, "h": 100, "labelColor": "black", - "opacity": "1", "size": "m", "text": "", "url": "", @@ -34,6 +34,7 @@ Array [ "id": "shape:boxB", "index": "a2", "isLocked": false, + "opacity": 1, "parentId": "wahtever", "props": Object { "align": "middle", @@ -45,7 +46,6 @@ Array [ "growY": 0, "h": 100, "labelColor": "black", - "opacity": "1", "size": "m", "text": "", "url": "", @@ -62,6 +62,7 @@ Array [ "id": "shape:boxC", "index": "a3", "isLocked": false, + "opacity": 1, "parentId": "wahtever", "props": Object { "align": "middle", @@ -73,7 +74,6 @@ Array [ "growY": 0, "h": 100, "labelColor": "black", - "opacity": "1", "size": "m", "text": "", "url": "", @@ -95,6 +95,7 @@ Array [ "id": "shape:boxA", "index": "a1", "isLocked": false, + "opacity": 1, "parentId": "wahtever", "props": Object { "align": "middle", @@ -106,7 +107,6 @@ Array [ "growY": 0, "h": 100, "labelColor": "black", - "opacity": "1", "size": "m", "text": "", "url": "", @@ -123,6 +123,7 @@ Array [ "id": "shape:boxB", "index": "a2", "isLocked": false, + "opacity": 1, "parentId": "wahtever", "props": Object { "align": "middle", @@ -134,7 +135,6 @@ Array [ "growY": 0, "h": 100, "labelColor": "black", - "opacity": "1", "size": "m", "text": "", "url": "", @@ -151,6 +151,7 @@ Array [ "id": "shape:boxC", "index": "a3", "isLocked": false, + "opacity": 1, "parentId": "wahtever", "props": Object { "align": "middle", @@ -162,7 +163,6 @@ Array [ "growY": 0, "h": 100, "labelColor": "black", - "opacity": "1", "size": "m", "text": "", "url": "", diff --git a/packages/editor/src/lib/test/commands/createShapes.test.ts b/packages/editor/src/lib/test/commands/createShapes.test.ts index 9a30d06eb..b5474e02d 100644 --- a/packages/editor/src/lib/test/commands/createShapes.test.ts +++ b/packages/editor/src/lib/test/commands/createShapes.test.ts @@ -33,9 +33,9 @@ it('Creates shapes with the current style', () => { }) it('Creates shapes with the current opacity', () => { - editor.setProp('opacity', '0.5') + editor.setOpacity(0.5) editor.createShapes([{ id: ids.box3, type: 'geo' }]) - expect(editor.getShapeById(ids.box3)!.props.opacity).toEqual('0.5') + expect(editor.getShapeById(ids.box3)!.opacity).toEqual(0.5) }) it('Creates shapes at the correct index', () => { diff --git a/packages/editor/src/lib/test/commands/updateShapes.test.ts b/packages/editor/src/lib/test/commands/updateShapes.test.ts index acc7b5cca..5d30f0b59 100644 --- a/packages/editor/src/lib/test/commands/updateShapes.test.ts +++ b/packages/editor/src/lib/test/commands/updateShapes.test.ts @@ -30,13 +30,13 @@ it('updates shapes', () => { id: ids.box1, rotation: 0, type: 'geo', + opacity: 1, props: { h: 100, w: 100, color: 'black', dash: 'draw', fill: 'none', - opacity: '1', size: 'm', }, }) @@ -49,13 +49,13 @@ it('updates shapes', () => { id: ids.box1, rotation: 0, type: 'geo', + opacity: 1, props: { h: 100, w: 100, color: 'black', dash: 'draw', fill: 'none', - opacity: '1', size: 'm', }, }) @@ -68,13 +68,13 @@ it('updates shapes', () => { id: ids.box1, rotation: 0, type: 'geo', + opacity: 1, props: { h: 100, w: 100, color: 'black', dash: 'draw', fill: 'none', - opacity: '1', size: 'm', }, }) diff --git a/packages/editor/src/lib/test/jsx.tsx b/packages/editor/src/lib/test/jsx.tsx index db4df3d67..116f4564c 100644 --- a/packages/editor/src/lib/test/jsx.tsx +++ b/packages/editor/src/lib/test/jsx.tsx @@ -20,6 +20,7 @@ type CommonProps = { isLocked?: number ref?: string children?: JSX.Element | JSX.Element[] + opacity?: number } type ShapeByType = Extract @@ -89,7 +90,7 @@ export function shapesFromJsx(shapes: JSX.Element | Array) { if (key === 'x' || key === 'y' || key === 'ref' || key === 'id' || key === 'children') { continue } - if (key === 'rotation' || key === 'isLocked') { + if (key === 'rotation' || key === 'isLocked' || key === 'opacity') { shapePartial[key] = value as any continue } diff --git a/packages/editor/src/lib/test/props.test.ts b/packages/editor/src/lib/test/props.test.ts index 27cc58665..f7197e14e 100644 --- a/packages/editor/src/lib/test/props.test.ts +++ b/packages/editor/src/lib/test/props.test.ts @@ -16,7 +16,6 @@ describe('Editor.props', () => { dash: 'draw', fill: 'none', size: 'm', - opacity: '1', }) }) @@ -32,12 +31,6 @@ describe('Editor.props', () => { font: 'draw', geo: 'rectangle', verticalAlign: 'middle', - // h: 100, - // w: 100, - // growY: 0, - opacity: '1', - // text: '', - // url: '', }) }) @@ -53,12 +46,6 @@ describe('Editor.props', () => { font: 'draw', geo: 'rectangle', verticalAlign: 'middle', - // h: 100, // blacklisted - // w: 100, // blacklisted - // growY: 0, // blacklist - opacity: '1', - // text: '', // blacklisted - // url: '', // blacklisted }) }) @@ -82,13 +69,7 @@ describe('Editor.props', () => { size: 'm', font: 'draw', geo: 'rectangle', - opacity: '1', verticalAlign: 'middle', - // h: null, // mixed! but also blacklisted - // w: null, // mixed! but also blacklisted - // growY: 0, // blacklist - // text: '', // blacklisted - // url: '', // blacklist }) }) @@ -111,7 +92,6 @@ describe('Editor.props', () => { align: 'start', text: 'hello world this is a long sentence that should wrap', w: 100, - opacity: '0.5', url: 'https://aol.com', verticalAlign: 'start', }, @@ -128,10 +108,7 @@ describe('Editor.props', () => { geo: null, size: null, font: null, - opacity: null, verticalAlign: null, - // growY: null, // blacklist - // url: null, // blacklist }) }) }) diff --git a/packages/editor/src/lib/test/tools/TLSelectTool.test.ts b/packages/editor/src/lib/test/tools/TLSelectTool.test.ts index bfd3690dc..fcaeed352 100644 --- a/packages/editor/src/lib/test/tools/TLSelectTool.test.ts +++ b/packages/editor/src/lib/test/tools/TLSelectTool.test.ts @@ -405,7 +405,8 @@ describe('When in readonly mode', () => { type: 'embed', x: 100, y: 100, - props: { opacity: '1', w: 100, h: 100, url: '', doesResize: false }, + opacity: 1, + props: { w: 100, h: 100, url: '', doesResize: false }, }, ]) editor.setReadOnly(true) diff --git a/packages/editor/src/lib/test/tools/__snapshots__/resizing.test.ts.snap b/packages/editor/src/lib/test/tools/__snapshots__/resizing.test.ts.snap index fdfc17492..b16aaadaf 100644 --- a/packages/editor/src/lib/test/tools/__snapshots__/resizing.test.ts.snap +++ b/packages/editor/src/lib/test/tools/__snapshots__/resizing.test.ts.snap @@ -5,6 +5,7 @@ Object { "id": "shape:lineA", "index": "a3", "isLocked": false, + "opacity": 1, "parentId": "shape:boxA", "props": Object { "color": "black", @@ -13,7 +14,6 @@ Object { "isClosed": false, "isComplete": false, "isPen": false, - "opacity": "1", "segments": Array [ Object { "points": Array [ diff --git a/packages/editor/src/lib/test/tools/cropping.test.ts b/packages/editor/src/lib/test/tools/cropping.test.ts index de2299e66..f939daf88 100644 --- a/packages/editor/src/lib/test/tools/cropping.test.ts +++ b/packages/editor/src/lib/test/tools/cropping.test.ts @@ -20,7 +20,6 @@ const imageWidth = 1200 const imageHeight = 800 const imageProps = { - opacity: '1', assetId: null, playing: true, url: '', diff --git a/packages/editor/src/lib/test/tools/groups.test.ts b/packages/editor/src/lib/test/tools/groups.test.ts index 964dd6f09..ba2ed61c8 100644 --- a/packages/editor/src/lib/test/tools/groups.test.ts +++ b/packages/editor/src/lib/test/tools/groups.test.ts @@ -1896,10 +1896,10 @@ describe('Group opacity', () => { it("should set the group's opacity to max even if the selected style panel opacity is lower", () => { editor.createShapes([box(ids.boxA, 0, 0), box(ids.boxB, 20, 0)]) editor.select(ids.boxA, ids.boxB) - editor.setProp('opacity', '0.5') + editor.setOpacity(0.5) editor.groupShapes() const group = editor.getShapeById(onlySelectedId())! assert(editor.isShapeOfType(group, GroupShapeUtil)) - expect(group.props.opacity).toBe('1') + expect(group.opacity).toBe(1) }) }) diff --git a/packages/editor/src/lib/utils/assets.ts b/packages/editor/src/lib/utils/assets.ts index 38fd329ed..aab99ccbc 100644 --- a/packages/editor/src/lib/utils/assets.ts +++ b/packages/editor/src/lib/utils/assets.ts @@ -292,7 +292,6 @@ export async function createShapesFromFiles( props: { w: asset.props!.w, h: asset.props!.h, - opacity: '1', }, } @@ -403,7 +402,6 @@ export function createEmbedShapeAtPoint( h: props.height, doesResize: props.doesResize, url, - opacity: '1', }, }, ], @@ -430,10 +428,10 @@ export async function createBookmarkShapeAtPoint(editor: Editor, url: string, po type: 'bookmark', x: point.x - 150, y: point.y - 160, + opacity: 1, props: { assetId: existing.id, url: existing.props.src!, - opacity: '1', }, }, ]) @@ -450,9 +448,9 @@ export async function createBookmarkShapeAtPoint(editor: Editor, url: string, po type: 'bookmark', x: point.x, y: point.y, + opacity: 1, props: { url: url, - opacity: '1', }, }, ], @@ -480,9 +478,9 @@ export async function createBookmarkShapeAtPoint(editor: Editor, url: string, po { id: shapeId, type: 'bookmark', + opacity: 1, props: { assetId: assetId, - opacity: '1', }, }, ]) @@ -531,11 +529,11 @@ export async function createAssetShapeAtPoint( type: 'image', x: point.x - width / 2, y: point.y - height / 2, + opacity: 1, props: { assetId: asset.id, w: width, h: height, - opacity: '1', }, }, ], diff --git a/packages/tlschema/api-report.md b/packages/tlschema/api-report.md index 2f3c26734..751ecb19e 100644 --- a/packages/tlschema/api-report.md +++ b/packages/tlschema/api-report.md @@ -83,6 +83,7 @@ export function createShapeValidator( parentId: TLParentId; type: Type; isLocked: boolean; + opacity: number; props: Props; }>; @@ -450,7 +451,7 @@ export const LANGUAGES: readonly [{ }]; // @internal (undocumented) -export const opacityValidator: T.Validator<"0.1" | "0.25" | "0.5" | "0.75" | "1">; +export const opacityValidator: T.Validator; // @internal (undocumented) export const pageIdValidator: T.Validator; @@ -500,9 +501,6 @@ export const TL_FONT_TYPES: Set<"draw" | "mono" | "sans" | "serif">; // @public (undocumented) export const TL_GEO_TYPES: Set<"arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box">; -// @public (undocumented) -export const TL_OPACITY_TYPES: Set<"0.1" | "0.25" | "0.5" | "0.75" | "1">; - // @public (undocumented) export const TL_SIZE_TYPES: Set<"l" | "m" | "s" | "xl">; @@ -510,7 +508,7 @@ export const TL_SIZE_TYPES: Set<"l" | "m" | "s" | "xl">; export const TL_SPLINE_TYPES: Set<"cubic" | "line">; // @public (undocumented) -export const TL_STYLE_TYPES: Set<"align" | "arrowheadEnd" | "arrowheadStart" | "color" | "dash" | "fill" | "font" | "geo" | "icon" | "labelColor" | "opacity" | "size" | "spline" | "verticalAlign">; +export const TL_STYLE_TYPES: Set<"align" | "arrowheadEnd" | "arrowheadStart" | "color" | "dash" | "fill" | "font" | "geo" | "icon" | "labelColor" | "size" | "spline" | "verticalAlign">; // @public (undocumented) export interface TLAlignStyle extends TLBaseStyle { @@ -552,7 +550,6 @@ export type TLArrowShapeProps = { fill: TLFillType; dash: TLDashType; size: TLSizeType; - opacity: TLOpacityType; arrowheadStart: TLArrowheadType; arrowheadEnd: TLArrowheadType; font: TLFontType; @@ -612,6 +609,8 @@ export interface TLBaseShape extends // (undocumented) isLocked: boolean; // (undocumented) + opacity: TLOpacityType; + // (undocumented) parentId: TLParentId; // (undocumented) props: Props; @@ -816,7 +815,6 @@ export type TLImageShape = TLBaseShape<'image', TLImageShapeProps>; // @public (undocumented) export type TLImageShapeProps = { - opacity: TLOpacityType; url: string; playing: boolean; w: number; @@ -848,6 +846,8 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> { // (undocumented) isToolLocked: boolean; // (undocumented) + opacityForNextShape: TLOpacityType; + // (undocumented) propsForNextShape: TLInstancePropsForNextShape; // (undocumented) screenBounds: Box2dModel; @@ -938,15 +938,7 @@ export type TLNullableShapeProps = { }; // @public (undocumented) -export interface TLOpacityStyle extends TLBaseStyle { - // (undocumented) - id: TLOpacityType; - // (undocumented) - type: 'opacity'; -} - -// @public (undocumented) -export type TLOpacityType = SetValue; +export type TLOpacityType = number; // @public export interface TLPage extends BaseRecord<'page', TLPageId> { @@ -995,7 +987,7 @@ export type TLShapePartial = T extends T ? { export type TLShapeProp = keyof TLShapeProps; // @public (undocumented) -export type TLShapeProps = SmooshedUnionObject; +export type TLShapeProps = Identity>; // @public (undocumented) export interface TLSizeStyle extends TLBaseStyle { @@ -1052,8 +1044,6 @@ export interface TLStyleCollections { // (undocumented) geo: TLGeoStyle[]; // (undocumented) - opacity: TLOpacityStyle[]; - // (undocumented) size: TLSizeStyle[]; // (undocumented) spline: TLSplineStyle[]; @@ -1062,7 +1052,7 @@ export interface TLStyleCollections { } // @public (undocumented) -export type TLStyleItem = TLAlignStyle | TLArrowheadEndStyle | TLArrowheadStartStyle | TLColorStyle | TLDashStyle | TLFillStyle | TLFontStyle | TLGeoStyle | TLOpacityStyle | TLSizeStyle | TLSplineStyle | TLVerticalAlignStyle; +export type TLStyleItem = TLAlignStyle | TLArrowheadEndStyle | TLArrowheadStartStyle | TLColorStyle | TLDashStyle | TLFillStyle | TLFontStyle | TLGeoStyle | TLSizeStyle | TLSplineStyle | TLVerticalAlignStyle; // @public (undocumented) export type TLStyleProps = Pick; @@ -1079,7 +1069,6 @@ export type TLTextShapeProps = { size: TLSizeType; font: TLFontType; align: TLAlignType; - opacity: TLOpacityType; w: number; text: string; scale: number; diff --git a/packages/tlschema/src/createTLSchema.ts b/packages/tlschema/src/createTLSchema.ts index 4417ff890..d57d4333d 100644 --- a/packages/tlschema/src/createTLSchema.ts +++ b/packages/tlschema/src/createTLSchema.ts @@ -132,7 +132,7 @@ export function createTLSchema( ), }) ), - }).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false })) + }).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false, opacity: 1 })) return StoreSchema.create( { diff --git a/packages/tlschema/src/index.ts b/packages/tlschema/src/index.ts index dacb08652..16f4c630c 100644 --- a/packages/tlschema/src/index.ts +++ b/packages/tlschema/src/index.ts @@ -127,12 +127,7 @@ export { } from './styles/TLFontStyle' export { TL_GEO_TYPES, geoValidator, type TLGeoStyle, type TLGeoType } from './styles/TLGeoStyle' export { iconValidator, type TLIconStyle, type TLIconType } from './styles/TLIconStyle' -export { - TL_OPACITY_TYPES, - opacityValidator, - type TLOpacityStyle, - type TLOpacityType, -} from './styles/TLOpacityStyle' +export { opacityValidator, type TLOpacityType } from './styles/TLOpacityStyle' export { TL_SIZE_TYPES, sizeValidator, diff --git a/packages/tlschema/src/migrations.test.ts b/packages/tlschema/src/migrations.test.ts index 15382c427..2141af399 100644 --- a/packages/tlschema/src/migrations.test.ts +++ b/packages/tlschema/src/migrations.test.ts @@ -6,7 +6,7 @@ import { documentMigrations } from './records/TLDocument' import { instanceMigrations, instanceTypeVersions } from './records/TLInstance' import { instancePageStateMigrations, instancePageStateVersions } from './records/TLPageState' import { instancePresenceMigrations, instancePresenceVersions } from './records/TLPresence' -import { TLShape, rootShapeMigrations } from './records/TLShape' +import { TLShape, rootShapeMigrations, Versions as rootShapeVersions } from './records/TLShape' import { arrowShapeMigrations } from './shapes/TLArrowShape' import { bookmarkShapeMigrations } from './shapes/TLBookmarkShape' import { drawShapeMigrations } from './shapes/TLDrawShape' @@ -1044,6 +1044,72 @@ describe('Adds NoteShape vertical alignment', () => { }) }) +describe('hoist opacity', () => { + test('hoists opacity from a shape to another', () => { + const { up, down } = rootShapeMigrations.migrators[rootShapeVersions.HoistOpacity] + const before = { + type: 'myShape', + x: 0, + y: 0, + props: { + color: 'red', + opacity: '0.5', + }, + } + const after = { + type: 'myShape', + x: 0, + y: 0, + opacity: 0.5, + props: { + color: 'red', + }, + } + const afterWithNonMatchingOpacity = { + type: 'myShape', + x: 0, + y: 0, + opacity: 0.6, + props: { + color: 'red', + }, + } + + expect(up(before)).toEqual(after) + expect(down(after)).toEqual(before) + expect(down(afterWithNonMatchingOpacity)).toEqual(before) + }) + + test('hoists opacity from propsForNextShape', () => { + const { up, down } = instanceMigrations.migrators[instanceTypeVersions.HoistOpacity] + const before = { + isToolLocked: true, + propsForNextShape: { + color: 'black', + opacity: '0.5', + }, + } + const after = { + isToolLocked: true, + opacityForNextShape: 0.5, + propsForNextShape: { + color: 'black', + }, + } + const afterWithNonMatchingOpacity = { + isToolLocked: true, + opacityForNextShape: 0.6, + propsForNextShape: { + color: 'black', + }, + } + + expect(up(before)).toEqual(after) + expect(down(after)).toEqual(before) + expect(down(afterWithNonMatchingOpacity)).toEqual(before) + }) +}) + /* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */ for (const migrator of allMigrators) { diff --git a/packages/tlschema/src/records/TLInstance.ts b/packages/tlschema/src/records/TLInstance.ts index d05a8daf5..0d1875620 100644 --- a/packages/tlschema/src/records/TLInstance.ts +++ b/packages/tlschema/src/records/TLInstance.ts @@ -13,7 +13,7 @@ import { fillValidator } from '../styles/TLFillStyle' import { fontValidator } from '../styles/TLFontStyle' import { geoValidator } from '../styles/TLGeoStyle' import { iconValidator } from '../styles/TLIconStyle' -import { opacityValidator } from '../styles/TLOpacityStyle' +import { opacityValidator, TLOpacityType } from '../styles/TLOpacityStyle' import { sizeValidator } from '../styles/TLSizeStyle' import { splineValidator } from '../styles/TLSplineStyle' import { verticalAlignValidator } from '../styles/TLVerticalAlignStyle' @@ -34,6 +34,7 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> { currentPageId: TLPageId followingUserId: string | null brush: Box2dModel | null + opacityForNextShape: TLOpacityType propsForNextShape: TLInstancePropsForNextShape cursor: TLCursor scribble: TLScribble | null @@ -62,13 +63,13 @@ export const instanceTypeValidator: T.Validator = T.model( currentPageId: pageIdValidator, followingUserId: T.string.nullable(), brush: T.boxModel.nullable(), + opacityForNextShape: opacityValidator, propsForNextShape: T.object({ color: colorValidator, labelColor: colorValidator, dash: dashValidator, fill: fillValidator, size: sizeValidator, - opacity: opacityValidator, font: fontValidator, align: alignValidator, verticalAlign: verticalAlignValidator, @@ -104,13 +105,14 @@ const Versions = { AddScribbleDelay: 10, RemoveUserId: 11, AddIsPenModeAndIsGridMode: 12, + HoistOpacity: 13, } as const export { Versions as instanceTypeVersions } /** @public */ export const instanceMigrations = defineMigrations({ - currentVersion: Versions.AddIsPenModeAndIsGridMode, + currentVersion: Versions.HoistOpacity, migrators: { [Versions.AddTransparentExportBgs]: { up: (instance: TLInstance) => { @@ -256,6 +258,29 @@ export const instanceMigrations = defineMigrations({ return instance }, }, + [Versions.HoistOpacity]: { + up: ({ propsForNextShape: { opacity, ...propsForNextShape }, ...instance }: any) => { + return { ...instance, opacityForNextShape: Number(opacity ?? '1'), propsForNextShape } + }, + down: ({ opacityForNextShape: opacity, ...instance }: any) => { + return { + ...instance, + propsForNextShape: { + ...instance.propsForNextShape, + opacity: + opacity < 0.175 + ? '0.1' + : opacity < 0.375 + ? '0.25' + : opacity < 0.625 + ? '0.5' + : opacity < 0.875 + ? '0.75' + : '1', + }, + } + }, + }, }, }) @@ -267,8 +292,8 @@ export const InstanceRecordType = createRecordType('instance', { }).withDefaultProperties( (): Omit => ({ followingUserId: null, + opacityForNextShape: 1, propsForNextShape: { - opacity: '1', color: 'black', labelColor: 'black', dash: 'draw', diff --git a/packages/tlschema/src/records/TLShape.ts b/packages/tlschema/src/records/TLShape.ts index a2a60f4da..a28405cdf 100644 --- a/packages/tlschema/src/records/TLShape.ts +++ b/packages/tlschema/src/records/TLShape.ts @@ -15,7 +15,6 @@ import { TLLineShape } from '../shapes/TLLineShape' import { TLNoteShape } from '../shapes/TLNoteShape' import { TLTextShape } from '../shapes/TLTextShape' import { TLVideoShape } from '../shapes/TLVideoShape' -import { SmooshedUnionObject } from '../util-types' import { TLPageId } from './TLPage' /** @@ -64,8 +63,15 @@ export type TLShapePartial = T extends T /** @public */ export type TLShapeId = RecordId +// evil type shit that will get deleted in the next PR +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void + ? I + : never + +type Identity = { [K in keyof T]: T[K] } + /** @public */ -export type TLShapeProps = SmooshedUnionObject +export type TLShapeProps = Identity> /** @public */ export type TLShapeProp = keyof TLShapeProps @@ -76,13 +82,14 @@ export type TLParentId = TLPageId | TLShapeId /** @public */ export type TLNullableShapeProps = { [K in TLShapeProp]?: TLShapeProps[K] | null } -const Versions = { +export const Versions = { AddIsLocked: 1, + HoistOpacity: 2, } as const /** @internal */ export const rootShapeMigrations = defineMigrations({ - currentVersion: Versions.AddIsLocked, + currentVersion: Versions.HoistOpacity, migrators: { [Versions.AddIsLocked]: { up: (record) => { @@ -98,6 +105,33 @@ export const rootShapeMigrations = defineMigrations({ } }, }, + [Versions.HoistOpacity]: { + up: ({ props: { opacity, ...props }, ...record }) => { + return { + ...record, + opacity: Number(opacity ?? '1'), + props, + } + }, + down: ({ opacity, ...record }) => { + return { + ...record, + props: { + ...record.props, + opacity: + opacity < 0.175 + ? '0.1' + : opacity < 0.375 + ? '0.25' + : opacity < 0.625 + ? '0.5' + : opacity < 0.875 + ? '0.75' + : '1', + }, + } + }, + }, }, }) diff --git a/packages/tlschema/src/shapes/TLArrowShape.ts b/packages/tlschema/src/shapes/TLArrowShape.ts index 176f01168..60622224e 100644 --- a/packages/tlschema/src/shapes/TLArrowShape.ts +++ b/packages/tlschema/src/shapes/TLArrowShape.ts @@ -7,7 +7,6 @@ import { TLColorType, colorValidator } from '../styles/TLColorStyle' import { TLDashType, dashValidator } from '../styles/TLDashStyle' import { TLFillType, fillValidator } from '../styles/TLFillStyle' import { TLFontType, fontValidator } from '../styles/TLFontStyle' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle' import { SetValue } from '../util-types' import { TLBaseShape, createShapeValidator, shapeIdValidator } from './TLBaseShape' @@ -35,7 +34,6 @@ export type TLArrowShapeProps = { fill: TLFillType dash: TLDashType size: TLSizeType - opacity: TLOpacityType arrowheadStart: TLArrowheadType arrowheadEnd: TLArrowheadType font: TLFontType @@ -72,7 +70,6 @@ export const arrowShapeValidator: T.Validator = createShapeValidat fill: fillValidator, dash: dashValidator, size: sizeValidator, - opacity: opacityValidator, arrowheadStart: arrowheadValidator, arrowheadEnd: arrowheadValidator, font: fontValidator, diff --git a/packages/tlschema/src/shapes/TLBaseShape.ts b/packages/tlschema/src/shapes/TLBaseShape.ts index a3e3eb685..f378ddb30 100644 --- a/packages/tlschema/src/shapes/TLBaseShape.ts +++ b/packages/tlschema/src/shapes/TLBaseShape.ts @@ -2,6 +2,7 @@ import { BaseRecord } from '@tldraw/store' import { T } from '@tldraw/validate' import { idValidator } from '../misc/id-validator' import { TLParentId, TLShapeId } from '../records/TLShape' +import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' /** @public */ export interface TLBaseShape @@ -13,6 +14,7 @@ export interface TLBaseShape index: string parentId: TLParentId isLocked: boolean + opacity: TLOpacityType props: Props } @@ -42,6 +44,7 @@ export function createShapeValidator( parentId: parentIdValidator, type: T.literal(type), isLocked: T.boolean, + opacity: opacityValidator, props, }) } diff --git a/packages/tlschema/src/shapes/TLBookmarkShape.ts b/packages/tlschema/src/shapes/TLBookmarkShape.ts index 385809141..dc2bdfcb0 100644 --- a/packages/tlschema/src/shapes/TLBookmarkShape.ts +++ b/packages/tlschema/src/shapes/TLBookmarkShape.ts @@ -2,12 +2,10 @@ import { defineMigrations } from '@tldraw/store' import { T } from '@tldraw/validate' import { assetIdValidator } from '../assets/TLBaseAsset' import { TLAssetId } from '../records/TLAsset' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLBaseShape, createShapeValidator } from './TLBaseShape' /** @public */ export type TLBookmarkShapeProps = { - opacity: TLOpacityType w: number h: number assetId: TLAssetId | null @@ -21,7 +19,6 @@ export type TLBookmarkShape = TLBaseShape<'bookmark', TLBookmarkShapeProps> export const bookmarkShapeValidator: T.Validator = createShapeValidator( 'bookmark', T.object({ - opacity: opacityValidator, w: T.nonZeroNumber, h: T.nonZeroNumber, assetId: assetIdValidator.nullable(), diff --git a/packages/tlschema/src/shapes/TLDrawShape.ts b/packages/tlschema/src/shapes/TLDrawShape.ts index b516ada5a..a4b131eb2 100644 --- a/packages/tlschema/src/shapes/TLDrawShape.ts +++ b/packages/tlschema/src/shapes/TLDrawShape.ts @@ -4,7 +4,6 @@ import { Vec2dModel } from '../misc/geometry-types' import { TLColorType, colorValidator } from '../styles/TLColorStyle' import { TLDashType, dashValidator } from '../styles/TLDashStyle' import { TLFillType, fillValidator } from '../styles/TLFillStyle' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle' import { SetValue } from '../util-types' import { TLBaseShape, createShapeValidator } from './TLBaseShape' @@ -30,7 +29,6 @@ export type TLDrawShapeProps = { fill: TLFillType dash: TLDashType size: TLSizeType - opacity: TLOpacityType segments: TLDrawShapeSegment[] isComplete: boolean isClosed: boolean @@ -48,7 +46,6 @@ export const drawShapeValidator: T.Validator = createShapeValidator fill: fillValidator, dash: dashValidator, size: sizeValidator, - opacity: opacityValidator, segments: T.arrayOf(drawShapeSegmentValidator), isComplete: T.boolean, isClosed: T.boolean, diff --git a/packages/tlschema/src/shapes/TLEmbedShape.ts b/packages/tlschema/src/shapes/TLEmbedShape.ts index 75f70d832..8b198b8ff 100644 --- a/packages/tlschema/src/shapes/TLEmbedShape.ts +++ b/packages/tlschema/src/shapes/TLEmbedShape.ts @@ -1,6 +1,5 @@ import { defineMigrations } from '@tldraw/store' import { T } from '@tldraw/validate' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLBaseShape, createShapeValidator } from './TLBaseShape' // Only allow multiplayer embeds. If we add additional routes later for example '/help' this won't match @@ -549,7 +548,6 @@ export type TLEmbedShapePermissions = { [K in keyof typeof embedShapePermissionD /** @public */ export type TLEmbedShapeProps = { - opacity: TLOpacityType w: number h: number url: string @@ -566,7 +564,6 @@ export type TLEmbedShape = TLBaseShape<'embed', TLEmbedShapeProps> export const embedShapeTypeValidator: T.Validator = createShapeValidator( 'embed', T.object({ - opacity: opacityValidator, w: T.nonZeroNumber, h: T.nonZeroNumber, url: T.string, diff --git a/packages/tlschema/src/shapes/TLFrameShape.ts b/packages/tlschema/src/shapes/TLFrameShape.ts index 1ad53e552..33ebc0bbd 100644 --- a/packages/tlschema/src/shapes/TLFrameShape.ts +++ b/packages/tlschema/src/shapes/TLFrameShape.ts @@ -1,10 +1,8 @@ import { defineMigrations } from '@tldraw/store' import { T } from '@tldraw/validate' -import { opacityValidator, TLOpacityType } from '../styles/TLOpacityStyle' import { createShapeValidator, TLBaseShape } from './TLBaseShape' type TLFrameShapeProps = { - opacity: TLOpacityType w: number h: number name: string @@ -17,7 +15,6 @@ export type TLFrameShape = TLBaseShape<'frame', TLFrameShapeProps> export const frameShapeValidator: T.Validator = createShapeValidator( 'frame', T.object({ - opacity: opacityValidator, w: T.nonZeroNumber, h: T.nonZeroNumber, name: T.string, diff --git a/packages/tlschema/src/shapes/TLGeoShape.ts b/packages/tlschema/src/shapes/TLGeoShape.ts index 8d763da79..70cc8ea29 100644 --- a/packages/tlschema/src/shapes/TLGeoShape.ts +++ b/packages/tlschema/src/shapes/TLGeoShape.ts @@ -6,7 +6,6 @@ import { TLDashType, dashValidator } from '../styles/TLDashStyle' import { TLFillType, fillValidator } from '../styles/TLFillStyle' import { TLFontType, fontValidator } from '../styles/TLFontStyle' import { TLGeoType, geoValidator } from '../styles/TLGeoStyle' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle' import { TLVerticalAlignType, verticalAlignValidator } from '../styles/TLVerticalAlignStyle' import { TLBaseShape, createShapeValidator } from './TLBaseShape' @@ -19,7 +18,6 @@ export type TLGeoShapeProps = { fill: TLFillType dash: TLDashType size: TLSizeType - opacity: TLOpacityType font: TLFontType align: TLAlignType verticalAlign: TLVerticalAlignType @@ -43,7 +41,6 @@ export const geoShapeValidator: T.Validator = createShapeValidator( fill: fillValidator, dash: dashValidator, size: sizeValidator, - opacity: opacityValidator, font: fontValidator, align: alignValidator, verticalAlign: verticalAlignValidator, diff --git a/packages/tlschema/src/shapes/TLGroupShape.ts b/packages/tlschema/src/shapes/TLGroupShape.ts index 3dc2e65a1..3558b7824 100644 --- a/packages/tlschema/src/shapes/TLGroupShape.ts +++ b/packages/tlschema/src/shapes/TLGroupShape.ts @@ -1,12 +1,9 @@ import { defineMigrations } from '@tldraw/store' import { T } from '@tldraw/validate' -import { opacityValidator, TLOpacityType } from '../styles/TLOpacityStyle' import { createShapeValidator, TLBaseShape } from './TLBaseShape' /** @public */ -export type TLGroupShapeProps = { - opacity: TLOpacityType -} +export type TLGroupShapeProps = { [key in never]: undefined } /** @public */ export type TLGroupShape = TLBaseShape<'group', TLGroupShapeProps> @@ -14,9 +11,7 @@ export type TLGroupShape = TLBaseShape<'group', TLGroupShapeProps> /** @internal */ export const groupShapeValidator: T.Validator = createShapeValidator( 'group', - T.object({ - opacity: opacityValidator, - }) + T.object({}) ) /** @internal */ diff --git a/packages/tlschema/src/shapes/TLHighlightShape.ts b/packages/tlschema/src/shapes/TLHighlightShape.ts index 7a3ea550e..62069756f 100644 --- a/packages/tlschema/src/shapes/TLHighlightShape.ts +++ b/packages/tlschema/src/shapes/TLHighlightShape.ts @@ -1,7 +1,6 @@ import { defineMigrations } from '@tldraw/store' import { T } from '@tldraw/validate' import { TLColorType, colorValidator } from '../styles/TLColorStyle' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle' import { TLBaseShape, createShapeValidator } from './TLBaseShape' import { TLDrawShapeSegment, drawShapeSegmentValidator } from './TLDrawShape' @@ -10,7 +9,6 @@ import { TLDrawShapeSegment, drawShapeSegmentValidator } from './TLDrawShape' export type TLHighlightShapeProps = { color: TLColorType size: TLSizeType - opacity: TLOpacityType segments: TLDrawShapeSegment[] isComplete: boolean isPen: boolean @@ -25,7 +23,6 @@ export const highlightShapeValidator: T.Validator = createShap T.object({ color: colorValidator, size: sizeValidator, - opacity: opacityValidator, segments: T.arrayOf(drawShapeSegmentValidator), isComplete: T.boolean, isPen: T.boolean, diff --git a/packages/tlschema/src/shapes/TLIconShape.ts b/packages/tlschema/src/shapes/TLIconShape.ts index 3088e1dbb..79a355987 100644 --- a/packages/tlschema/src/shapes/TLIconShape.ts +++ b/packages/tlschema/src/shapes/TLIconShape.ts @@ -3,7 +3,6 @@ import { T } from '@tldraw/validate' import { TLColorType, colorValidator } from '../styles/TLColorStyle' import { TLDashType, dashValidator } from '../styles/TLDashStyle' import { TLIconType, iconValidator } from '../styles/TLIconStyle' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle' import { TLBaseShape, createShapeValidator } from './TLBaseShape' @@ -13,7 +12,6 @@ export type TLIconShapeProps = { icon: TLIconType dash: TLDashType color: TLColorType - opacity: TLOpacityType scale: number } @@ -28,7 +26,6 @@ export const iconShapeValidator: T.Validator = createShapeValidator icon: iconValidator, dash: dashValidator, color: colorValidator, - opacity: opacityValidator, scale: T.number, }) ) diff --git a/packages/tlschema/src/shapes/TLImageShape.ts b/packages/tlschema/src/shapes/TLImageShape.ts index bb8b1b093..1cca45e84 100644 --- a/packages/tlschema/src/shapes/TLImageShape.ts +++ b/packages/tlschema/src/shapes/TLImageShape.ts @@ -3,7 +3,6 @@ import { T } from '@tldraw/validate' import { assetIdValidator } from '../assets/TLBaseAsset' import { Vec2dModel } from '../misc/geometry-types' import { TLAssetId } from '../records/TLAsset' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLBaseShape, createShapeValidator } from './TLBaseShape' /** @public */ @@ -14,7 +13,6 @@ export type TLImageCrop = { /** @public */ export type TLImageShapeProps = { - opacity: TLOpacityType url: string playing: boolean w: number @@ -35,7 +33,6 @@ export const cropValidator = T.object({ export const imageShapeValidator: T.Validator = createShapeValidator( 'image', T.object({ - opacity: opacityValidator, w: T.nonZeroNumber, h: T.nonZeroNumber, playing: T.boolean, diff --git a/packages/tlschema/src/shapes/TLLineShape.ts b/packages/tlschema/src/shapes/TLLineShape.ts index a7e652a80..63858d0d6 100644 --- a/packages/tlschema/src/shapes/TLLineShape.ts +++ b/packages/tlschema/src/shapes/TLLineShape.ts @@ -3,7 +3,6 @@ import { T } from '@tldraw/validate' import { TLHandle, handleValidator } from '../misc/TLHandle' import { TLColorType, colorValidator } from '../styles/TLColorStyle' import { TLDashType, dashValidator } from '../styles/TLDashStyle' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle' import { TLSplineType, splineValidator } from '../styles/TLSplineStyle' import { TLBaseShape, createShapeValidator } from './TLBaseShape' @@ -13,7 +12,6 @@ export type TLLineShapeProps = { color: TLColorType dash: TLDashType size: TLSizeType - opacity: TLOpacityType spline: TLSplineType handles: { [key: string]: TLHandle @@ -30,7 +28,6 @@ export const lineShapeValidator: T.Validator = createShapeValidator color: colorValidator, dash: dashValidator, size: sizeValidator, - opacity: opacityValidator, spline: splineValidator, handles: T.dict(T.string, handleValidator), }) diff --git a/packages/tlschema/src/shapes/TLNoteShape.ts b/packages/tlschema/src/shapes/TLNoteShape.ts index 70caf8250..c40a413ea 100644 --- a/packages/tlschema/src/shapes/TLNoteShape.ts +++ b/packages/tlschema/src/shapes/TLNoteShape.ts @@ -3,7 +3,6 @@ import { T } from '@tldraw/validate' import { TLAlignType, alignValidator } from '../styles/TLAlignStyle' import { TLColorType, colorValidator } from '../styles/TLColorStyle' import { TLFontType, fontValidator } from '../styles/TLFontStyle' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle' import { TLVerticalAlignType, verticalAlignValidator } from '../styles/TLVerticalAlignStyle' import { TLBaseShape, createShapeValidator } from './TLBaseShape' @@ -15,7 +14,6 @@ export type TLNoteShapeProps = { font: TLFontType align: TLAlignType verticalAlign: TLVerticalAlignType - opacity: TLOpacityType growY: number url: string text: string @@ -33,7 +31,6 @@ export const noteShapeValidator: T.Validator = createShapeValidator font: fontValidator, align: alignValidator, verticalAlign: verticalAlignValidator, - opacity: opacityValidator, growY: T.positiveNumber, url: T.string, text: T.string, diff --git a/packages/tlschema/src/shapes/TLTextShape.ts b/packages/tlschema/src/shapes/TLTextShape.ts index f374e867f..3aeb8a7ad 100644 --- a/packages/tlschema/src/shapes/TLTextShape.ts +++ b/packages/tlschema/src/shapes/TLTextShape.ts @@ -3,7 +3,6 @@ import { T } from '@tldraw/validate' import { TLAlignType, alignValidator } from '../styles/TLAlignStyle' import { TLColorType, colorValidator } from '../styles/TLColorStyle' import { TLFontType, fontValidator } from '../styles/TLFontStyle' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle' import { TLBaseShape, createShapeValidator } from './TLBaseShape' @@ -13,7 +12,6 @@ export type TLTextShapeProps = { size: TLSizeType font: TLFontType align: TLAlignType - opacity: TLOpacityType w: number text: string scale: number @@ -31,7 +29,6 @@ export const textShapeValidator: T.Validator = createShapeValidator size: sizeValidator, font: fontValidator, align: alignValidator, - opacity: opacityValidator, w: T.nonZeroNumber, text: T.string, scale: T.nonZeroNumber, diff --git a/packages/tlschema/src/shapes/TLVideoShape.ts b/packages/tlschema/src/shapes/TLVideoShape.ts index b8282fac4..9f9545a6c 100644 --- a/packages/tlschema/src/shapes/TLVideoShape.ts +++ b/packages/tlschema/src/shapes/TLVideoShape.ts @@ -2,12 +2,10 @@ import { defineMigrations } from '@tldraw/store' import { T } from '@tldraw/validate' import { assetIdValidator } from '../assets/TLBaseAsset' import { TLAssetId } from '../records/TLAsset' -import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle' import { TLBaseShape, createShapeValidator } from './TLBaseShape' /** @public */ export type TLVideoShapeProps = { - opacity: TLOpacityType w: number h: number time: number @@ -23,7 +21,6 @@ export type TLVideoShape = TLBaseShape<'video', TLVideoShapeProps> export const videoShapeValidator: T.Validator = createShapeValidator( 'video', T.object({ - opacity: opacityValidator, w: T.nonZeroNumber, h: T.nonZeroNumber, time: T.number, diff --git a/packages/tlschema/src/styles/TLBaseStyle.ts b/packages/tlschema/src/styles/TLBaseStyle.ts index b75c80254..f2616866c 100644 --- a/packages/tlschema/src/styles/TLBaseStyle.ts +++ b/packages/tlschema/src/styles/TLBaseStyle.ts @@ -7,7 +7,6 @@ export const TL_STYLE_TYPES = new Set([ 'dash', 'fill', 'size', - 'opacity', 'font', 'align', 'verticalAlign', diff --git a/packages/tlschema/src/styles/TLOpacityStyle.ts b/packages/tlschema/src/styles/TLOpacityStyle.ts index a91a62b73..ce7ed7206 100644 --- a/packages/tlschema/src/styles/TLOpacityStyle.ts +++ b/packages/tlschema/src/styles/TLOpacityStyle.ts @@ -1,18 +1,11 @@ import { T } from '@tldraw/validate' -import { SetValue } from '../util-types' -import { TLBaseStyle } from './TLBaseStyle' /** @public */ -export const TL_OPACITY_TYPES = new Set(['0.1', '0.25', '0.5', '0.75', '1'] as const) - -/** @public */ -export type TLOpacityType = SetValue - -/** @public */ -export interface TLOpacityStyle extends TLBaseStyle { - id: TLOpacityType - type: 'opacity' -} +export type TLOpacityType = number /** @internal */ -export const opacityValidator = T.setEnum(TL_OPACITY_TYPES) +export const opacityValidator = T.number.check((n) => { + if (n < 0 || n > 1) { + throw new T.ValidationError('Opacity must be between 0 and 1') + } +}) diff --git a/packages/tlschema/src/styles/style-types.ts b/packages/tlschema/src/styles/style-types.ts index 6827aa04f..1bead3254 100644 --- a/packages/tlschema/src/styles/style-types.ts +++ b/packages/tlschema/src/styles/style-types.ts @@ -4,7 +4,6 @@ import { TLDashStyle, TLFillStyle, TLFontStyle, - TLOpacityStyle, TLSizeStyle, TLStyleType, } from '..' @@ -20,7 +19,6 @@ export type TLStyleItem = | TLFillStyle | TLDashStyle | TLSizeStyle - | TLOpacityStyle | TLFontStyle | TLAlignStyle | TLVerticalAlignStyle @@ -36,7 +34,6 @@ export interface TLStyleCollections { fill: TLFillStyle[] dash: TLDashStyle[] size: TLSizeStyle[] - opacity: TLOpacityStyle[] font: TLFontStyle[] align: TLAlignStyle[] verticalAlign: TLVerticalAlignStyle[] @@ -44,7 +41,6 @@ export interface TLStyleCollections { arrowheadStart: TLArrowheadStartStyle[] arrowheadEnd: TLArrowheadEndStyle[] spline: TLSplineStyle[] - // icon: TLIconStyle[] } /** @public */ diff --git a/packages/tlschema/src/util-types.ts b/packages/tlschema/src/util-types.ts index 80f2ae2b7..611ea7f69 100644 --- a/packages/tlschema/src/util-types.ts +++ b/packages/tlschema/src/util-types.ts @@ -1,11 +1,2 @@ -/** @public */ -export type SmooshedUnionObject = { - [K in T extends infer P ? keyof P : never]: T extends infer P - ? K extends keyof P - ? P[K] - : never - : never -} - /** @public */ export type SetValue> = T extends Set ? U : never diff --git a/packages/ui/src/lib/components/StylePanel/StylePanel.tsx b/packages/ui/src/lib/components/StylePanel/StylePanel.tsx index d741a3529..bbbb80eb2 100644 --- a/packages/ui/src/lib/components/StylePanel/StylePanel.tsx +++ b/packages/ui/src/lib/components/StylePanel/StylePanel.tsx @@ -1,6 +1,7 @@ import { Editor, TLNullableShapeProps, TLStyleItem, useEditor } from '@tldraw/editor' import React, { useCallback } from 'react' +import { minBy } from '@tldraw/utils' import { useValue } from 'signia-react' import { useTranslation } from '../../hooks/useTranslation/useTranslation' import { Button } from '../primitives/Button' @@ -18,6 +19,10 @@ export const StylePanel = function StylePanel({ isMobile }: StylePanelProps) { const editor = useEditor() const props = useValue('props', () => editor.props, [editor]) + const opacity = useValue('opacity', () => editor.opacity, [editor]) + const toolShapeType = useValue('toolShapeType', () => editor.root.current.value?.shapeType, [ + editor, + ]) const handlePointerOut = useCallback(() => { if (!isMobile) { @@ -25,9 +30,9 @@ export const StylePanel = function StylePanel({ isMobile }: StylePanelProps) { } }, [editor, isMobile]) - if (!props) return null + if (!props && !toolShapeType) return null - const { geo, arrowheadEnd, arrowheadStart, spline, font } = props + const { geo, arrowheadEnd, arrowheadStart, spline, font } = props ?? {} const hideGeo = geo === undefined const hideArrowHeads = arrowheadEnd === undefined && arrowheadStart === undefined @@ -36,13 +41,13 @@ export const StylePanel = function StylePanel({ isMobile }: StylePanelProps) { return (
- - {!hideText && } + + {!hideText && } {!(hideGeo && hideArrowHeads && hideSpline) && (
- - - + + +
)}
@@ -65,7 +70,15 @@ function useStyleChangeCallback() { ) } -function CommonStylePickerSet({ props }: { props: TLNullableShapeProps }) { +const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const + +function CommonStylePickerSet({ + props, + opacity, +}: { + props: TLNullableShapeProps + opacity: number | null +}) { const editor = useEditor() const msg = useTranslation() @@ -73,14 +86,14 @@ function CommonStylePickerSet({ props }: { props: TLNullableShapeProps }) { const handleOpacityValueChange = React.useCallback( (value: number, ephemeral: boolean) => { - const item = styles.opacity[value] - editor.setProp(item.type, item.id, ephemeral) + const item = tldrawSupportedOpacities[value] + editor.setOpacity(item, ephemeral) editor.isChangingStyle = true }, [editor] ) - const { color, fill, dash, size, opacity } = props + const { color, fill, dash, size } = props if ( color === undefined && @@ -94,7 +107,14 @@ function CommonStylePickerSet({ props }: { props: TLNullableShapeProps }) { const showPickers = fill !== undefined || dash !== undefined || size !== undefined - const opacityIndex = styles.opacity.findIndex((s) => s.id === opacity) + const opacityIndex = + opacity === null + ? -1 + : tldrawSupportedOpacities.indexOf( + minBy(tldrawSupportedOpacities, (supportedOpacity) => + Math.abs(supportedOpacity - opacity) + )! + ) return ( <> @@ -112,10 +132,10 @@ function CommonStylePickerSet({ props }: { props: TLNullableShapeProps }) { {opacity === undefined ? null : ( = 0 ? opacityIndex : styles.opacity.length - 1} + value={opacityIndex >= 0 ? opacityIndex : tldrawSupportedOpacities.length - 1} label={opacity ? `opacity-style.${opacity}` : 'style-panel.mixed'} onValueChange={handleOpacityValueChange} - steps={styles.opacity.length - 1} + steps={tldrawSupportedOpacities.length - 1} title={msg('style-panel.opacity')} /> )} diff --git a/packages/ui/src/lib/hooks/clipboard/pasteExcalidrawContent.ts b/packages/ui/src/lib/hooks/clipboard/pasteExcalidrawContent.ts index 8d6959403..6c6247891 100644 --- a/packages/ui/src/lib/hooks/clipboard/pasteExcalidrawContent.ts +++ b/packages/ui/src/lib/hooks/clipboard/pasteExcalidrawContent.ts @@ -79,6 +79,7 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi y: element.y, rotation: 0, isLocked: element.locked, + opacity: getOpacity(element.opacity), } as const if (element.angle !== 0) { @@ -121,7 +122,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi type: 'geo', props: { geo: element.type, - opacity: getOpacity(element.opacity), url: element.link ?? '', w: element.width, h: element.height, @@ -142,7 +142,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi props: { dash: getDash(element), size: strokeWidthsToSizes[element.strokeWidth], - opacity: getOpacity(element.opacity), color: colorsToColors[element.strokeColor] ?? 'black', segments: [ { @@ -169,7 +168,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi props: { dash: getDash(element), size: strokeWidthsToSizes[element.strokeWidth], - opacity: getOpacity(element.opacity), color: colorsToColors[element.strokeColor] ?? 'black', spline: element.roundness ? 'cubic' : 'line', handles: { @@ -234,7 +232,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi text, bend: getBend(element, start, end), dash: getDash(element), - opacity: getOpacity(element.opacity), size: strokeWidthsToSizes[element.strokeWidth] ?? 'm', color: colorsToColors[element.strokeColor] ?? 'black', start: startTargetId @@ -277,7 +274,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi size, scale, font: fontFamilyToFontType[element.fontFamily] ?? 'draw', - opacity: getOpacity(element.opacity), color: colorsToColors[element.strokeColor] ?? 'black', text: element.text, align: textAlignToAlignTypes[element.textAlign], @@ -308,7 +304,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi ...base, type: 'image', props: { - opacity: getOpacity(element.opacity), w: element.width, h: element.height, assetId, @@ -370,16 +365,16 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi const getOpacity = (opacity: number): TLOpacityType => { const t = opacity / 100 if (t < 0.2) { - return '0.1' + return 0.1 } else if (t < 0.4) { - return '0.25' + return 0.25 } else if (t < 0.6) { - return '0.5' + return 0.5 } else if (t < 0.8) { - return '0.75' + return 0.75 } - return '1' + return 1 } const strokeWidthsToSizes: Record = {