From 83a391b46bffae8489d0090222f1f633fe37fff4 Mon Sep 17 00:00:00 2001 From: David Sheldrick Date: Fri, 7 Jul 2023 16:32:08 +0100 Subject: [PATCH] Add cloud shape (#1708) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![Kapture 2023-07-04 at 16 36 31](https://github.com/tldraw/tldraw/assets/1242537/bcb19959-ac66-46fa-92ea-50fe4692a96c) ### Change Type - [x] `minor` — New feature [^1]: publishes a `patch` release, for devDependencies use `internal` [^2]: will not publish a new version ### Test Plan 1. Make some cloud shapes, try different sizes, colors, fills. 2. Export cloud shapes to images. - [ ] Unit Tests - [ ] End to end tests ### Release Notes - Adds a cloud shape. --- .github/workflows/publish-manual.yml | 2 +- apps/examples/e2e/tests/test-shapes.spec.ts | 4 +- assets/icons/icon/geo-cloud.svg | 6 + assets/translations/main.json | 1 + packages/assets/imports.js | 2 + packages/assets/selfHosted.js | 1 + packages/assets/types.d.ts | 1 + packages/assets/urls.js | 4 + packages/editor/api-report.md | 4 +- .../editor/shapes/frame/FrameShapeUtil.tsx | 4 +- .../shapes/frame/components/FrameHeading.tsx | 4 +- .../lib/editor/shapes/geo/GeoShapeUtil.tsx | 105 +++++- .../src/lib/editor/shapes/geo/cloudOutline.ts | 301 ++++++++++++++++++ .../shapes/geo/components/DashStyleCloud.tsx | 120 +++++++ .../shapes/geo/components/DrawStyleCloud.tsx | 65 ++++ .../shapes/geo/components/SolidStyleCloud.tsx | 65 ++++ .../editor/shapes/geo/toolStates/Pointing.ts | 6 +- .../src/lib/test/tools/resizing.test.ts | 10 +- packages/editor/src/lib/utils/rotation.ts | 4 +- packages/primitives/api-report.md | 4 +- packages/primitives/src/index.ts | 2 +- packages/primitives/src/lib/Vec2d.ts | 4 + packages/primitives/src/lib/utils.ts | 2 +- packages/tlschema/api-report.md | 4 +- packages/tlschema/src/migrations.test.ts | 18 +- packages/tlschema/src/shapes/TLGeoShape.ts | 22 +- packages/ui/api-report.md | 4 +- .../src/lib/components/StylePanel/styles.tsx | 1 + .../ui/src/lib/hooks/useToolbarSchema.tsx | 2 +- .../useTranslation/TLUiTranslationKey.ts | 1 + .../useTranslation/defaultTranslation.ts | 1 + packages/ui/src/lib/icon-types.ts | 2 + scripts/publish-new.ts | 8 +- 33 files changed, 752 insertions(+), 32 deletions(-) create mode 100644 assets/icons/icon/geo-cloud.svg create mode 100644 packages/editor/src/lib/editor/shapes/geo/cloudOutline.ts create mode 100644 packages/editor/src/lib/editor/shapes/geo/components/DashStyleCloud.tsx create mode 100644 packages/editor/src/lib/editor/shapes/geo/components/DrawStyleCloud.tsx create mode 100644 packages/editor/src/lib/editor/shapes/geo/components/SolidStyleCloud.tsx diff --git a/.github/workflows/publish-manual.yml b/.github/workflows/publish-manual.yml index 013af0f61..0aa20f510 100644 --- a/.github/workflows/publish-manual.yml +++ b/.github/workflows/publish-manual.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest-16-cores-open steps: - - name: Check out code + - name: Check out code uses: actions/checkout@v3 - name: Setup Node.js environment diff --git a/apps/examples/e2e/tests/test-shapes.spec.ts b/apps/examples/e2e/tests/test-shapes.spec.ts index 47e1ec0e7..e828bff69 100644 --- a/apps/examples/e2e/tests/test-shapes.spec.ts +++ b/apps/examples/e2e/tests/test-shapes.spec.ts @@ -14,7 +14,7 @@ const clickableShapeCreators = [ { tool: 'ellipse', shape: 'geo' }, { tool: 'triangle', shape: 'geo' }, { tool: 'diamond', shape: 'geo' }, - { tool: 'pentagon', shape: 'geo' }, + { tool: 'cloud', shape: 'geo' }, { tool: 'hexagon', shape: 'geo' }, // { tool: 'octagon', shape: 'geo' }, { tool: 'star', shape: 'geo' }, @@ -40,7 +40,7 @@ const draggableShapeCreators = [ { tool: 'ellipse', shape: 'geo' }, { tool: 'triangle', shape: 'geo' }, { tool: 'diamond', shape: 'geo' }, - { tool: 'pentagon', shape: 'geo' }, + { tool: 'cloud', shape: 'geo' }, { tool: 'hexagon', shape: 'geo' }, // { tool: 'octagon', shape: 'geo' }, { tool: 'star', shape: 'geo' }, diff --git a/assets/icons/icon/geo-cloud.svg b/assets/icons/icon/geo-cloud.svg new file mode 100644 index 000000000..d976f8989 --- /dev/null +++ b/assets/icons/icon/geo-cloud.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/translations/main.json b/assets/translations/main.json index b1ae03357..31809206f 100644 --- a/assets/translations/main.json +++ b/assets/translations/main.json @@ -141,6 +141,7 @@ "geo-style.hexagon": "Hexagon", "geo-style.octagon": "Octagon", "geo-style.oval": "Oval", + "geo-style.cloud": "Cloud", "geo-style.pentagon": "Pentagon", "geo-style.rectangle": "Rectangle", "geo-style.rhombus-2": "Rhombus 2", diff --git a/packages/assets/imports.js b/packages/assets/imports.js index 65b4ddb4e..b7017d862 100644 --- a/packages/assets/imports.js +++ b/packages/assets/imports.js @@ -101,6 +101,7 @@ import iconsGeoArrowLeft from './icons/icon/geo-arrow-left.svg' import iconsGeoArrowRight from './icons/icon/geo-arrow-right.svg' import iconsGeoArrowUp from './icons/icon/geo-arrow-up.svg' import iconsGeoCheckBox from './icons/icon/geo-check-box.svg' +import iconsGeoCloud from './icons/icon/geo-cloud.svg' import iconsGeoDiamond from './icons/icon/geo-diamond.svg' import iconsGeoEllipse from './icons/icon/geo-ellipse.svg' import iconsGeoHexagon from './icons/icon/geo-hexagon.svg' @@ -311,6 +312,7 @@ export function getAssetUrlsByImport(opts) { 'geo-arrow-right': formatAssetUrl(iconsGeoArrowRight, opts), 'geo-arrow-up': formatAssetUrl(iconsGeoArrowUp, opts), 'geo-check-box': formatAssetUrl(iconsGeoCheckBox, opts), + 'geo-cloud': formatAssetUrl(iconsGeoCloud, opts), 'geo-diamond': formatAssetUrl(iconsGeoDiamond, opts), 'geo-ellipse': formatAssetUrl(iconsGeoEllipse, opts), 'geo-hexagon': formatAssetUrl(iconsGeoHexagon, opts), diff --git a/packages/assets/selfHosted.js b/packages/assets/selfHosted.js index 0b1fbfa5a..ec7dede4d 100644 --- a/packages/assets/selfHosted.js +++ b/packages/assets/selfHosted.js @@ -97,6 +97,7 @@ export function getAssetUrls(opts) { 'geo-arrow-right': formatAssetUrl('./icons/icon/geo-arrow-right.svg', opts), 'geo-arrow-up': formatAssetUrl('./icons/icon/geo-arrow-up.svg', opts), 'geo-check-box': formatAssetUrl('./icons/icon/geo-check-box.svg', opts), + 'geo-cloud': formatAssetUrl('./icons/icon/geo-cloud.svg', opts), 'geo-diamond': formatAssetUrl('./icons/icon/geo-diamond.svg', opts), 'geo-ellipse': formatAssetUrl('./icons/icon/geo-ellipse.svg', opts), 'geo-hexagon': formatAssetUrl('./icons/icon/geo-hexagon.svg', opts), diff --git a/packages/assets/types.d.ts b/packages/assets/types.d.ts index b0f121e0a..8b56c4165 100644 --- a/packages/assets/types.d.ts +++ b/packages/assets/types.d.ts @@ -87,6 +87,7 @@ export type AssetUrls = { 'geo-arrow-right': string 'geo-arrow-up': string 'geo-check-box': string + 'geo-cloud': string 'geo-diamond': string 'geo-ellipse': string 'geo-hexagon': string diff --git a/packages/assets/urls.js b/packages/assets/urls.js index 154f0efb4..26a3bbdfd 100644 --- a/packages/assets/urls.js +++ b/packages/assets/urls.js @@ -286,6 +286,10 @@ export function getAssetUrlsByMetaUrl(opts) { new URL('./icons/icon/geo-check-box.svg', import.meta.url).href, opts ), + 'geo-cloud': formatAssetUrl( + new URL('./icons/icon/geo-cloud.svg', import.meta.url).href, + opts + ), 'geo-diamond': formatAssetUrl( new URL('./icons/icon/geo-diamond.svg', import.meta.url).href, opts diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index 4abc45d69..c62066b44 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -863,7 +863,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { onBeforeCreate: (shape: TLGeoShape) => { props: { growY: number; - geo: "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"; + geo: "arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "cloud" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box"; labelColor: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; fill: "none" | "pattern" | "semi" | "solid"; @@ -893,7 +893,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { onBeforeUpdate: (prev: TLGeoShape, next: TLGeoShape) => { props: { growY: number; - geo: "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"; + geo: "arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "cloud" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box"; labelColor: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; fill: "none" | "pattern" | "semi" | "solid"; diff --git a/packages/editor/src/lib/editor/shapes/frame/FrameShapeUtil.tsx b/packages/editor/src/lib/editor/shapes/frame/FrameShapeUtil.tsx index 12b71e120..7acafd240 100644 --- a/packages/editor/src/lib/editor/shapes/frame/FrameShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapes/frame/FrameShapeUtil.tsx @@ -1,4 +1,4 @@ -import { canolicalizeRotation, SelectionEdge, toDomPrecision } from '@tldraw/primitives' +import { canonicalizeRotation, SelectionEdge, toDomPrecision } from '@tldraw/primitives' import { getDefaultColorTheme, TLFrameShape, @@ -69,7 +69,7 @@ export class FrameShapeUtil extends BaseBoxShapeUtil { g.appendChild(rect) // Text label - const pageRotation = canolicalizeRotation(this.editor.getPageRotationById(shape.id)) + const pageRotation = canonicalizeRotation(this.editor.getPageRotationById(shape.id)) // rotate right 45 deg const offsetRotation = pageRotation + Math.PI / 4 const scaledRotation = (offsetRotation * (2 / Math.PI) + 4) % 4 diff --git a/packages/editor/src/lib/editor/shapes/frame/components/FrameHeading.tsx b/packages/editor/src/lib/editor/shapes/frame/components/FrameHeading.tsx index b0b094295..12f0e80a5 100644 --- a/packages/editor/src/lib/editor/shapes/frame/components/FrameHeading.tsx +++ b/packages/editor/src/lib/editor/shapes/frame/components/FrameHeading.tsx @@ -1,4 +1,4 @@ -import { canolicalizeRotation, SelectionEdge, toDomPrecision } from '@tldraw/primitives' +import { canonicalizeRotation, SelectionEdge, toDomPrecision } from '@tldraw/primitives' import { TLShapeId } from '@tldraw/tlschema' import { useEffect, useRef } from 'react' import { useEditor } from '../../../../hooks/useEditor' @@ -18,7 +18,7 @@ export const FrameHeading = function FrameHeading({ }) { const editor = useEditor() - const pageRotation = canolicalizeRotation(editor.getPageRotationById(id)) + const pageRotation = canonicalizeRotation(editor.getPageRotationById(id)) const isEditing = useIsEditing(id) const rInput = useRef(null) diff --git a/packages/editor/src/lib/editor/shapes/geo/GeoShapeUtil.tsx b/packages/editor/src/lib/editor/shapes/geo/GeoShapeUtil.tsx index e086e4e98..d5ed348b9 100644 --- a/packages/editor/src/lib/editor/shapes/geo/GeoShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapes/geo/GeoShapeUtil.tsx @@ -38,11 +38,15 @@ import { HyperlinkButton } from '../shared/HyperlinkButton' import { SvgExportContext } from '../shared/SvgExportContext' import { TextLabel } from '../shared/TextLabel' import { useForceSolid } from '../shared/useForceSolid' +import { cloudOutline, cloudSvgPath } from './cloudOutline' +import { DashStyleCloud, DashStyleCloudSvg } from './components/DashStyleCloud' import { DashStyleEllipse, DashStyleEllipseSvg } from './components/DashStyleEllipse' import { DashStyleOval, DashStyleOvalSvg } from './components/DashStyleOval' import { DashStylePolygon, DashStylePolygonSvg } from './components/DashStylePolygon' +import { DrawStyleCloud, DrawStyleCloudSvg } from './components/DrawStyleCloud' import { DrawStyleEllipseSvg, getEllipseIndicatorPath } from './components/DrawStyleEllipse' import { DrawStylePolygon, DrawStylePolygonSvg } from './components/DrawStylePolygon' +import { SolidStyleCloud, SolidStyleCloudSvg } from './components/SolidStyleCloud' import { SolidStyleEllipse, SolidStyleEllipseSvg } from './components/SolidStyleEllipse' import { getOvalIndicatorPath, @@ -142,6 +146,9 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { const cy = h / 2 switch (shape.props.geo) { + case 'cloud': { + return cloudOutline(w, h, shape.id, shape.props.size) + } case 'triangle': { return [new Vec2d(cx, 0), new Vec2d(w, h), new Vec2d(0, h)] } @@ -358,6 +365,48 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { const h = props.h + growY switch (props.geo) { + case 'cloud': { + if (dash === 'solid' || (dash === 'draw' && forceSolid)) { + return ( + + ) + } else if (dash === 'dashed' || dash === 'dotted') { + return ( + + ) + } else if (dash === 'draw') { + return ( + + ) + } + + break + } case 'ellipse': { if (dash === 'solid' || (dash === 'draw' && forceSolid)) { return ( @@ -471,7 +520,8 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { indicator(shape: TLGeoShape) { const { id, props } = shape - const { w, h, growY, size } = props + const { w, size } = props + const h = props.h + props.growY const forceSolid = useForceSolid() const strokeWidth = STROKE_SIZES[size] @@ -479,13 +529,16 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { switch (props.geo) { case 'ellipse': { if (props.dash === 'draw' && !forceSolid) { - return + return } - return + return } case 'oval': { - return + return + } + case 'cloud': { + return } default: { @@ -602,6 +655,50 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { } break } + + case 'cloud': { + switch (props.dash) { + case 'draw': + svgElm = DrawStyleCloudSvg({ + id, + strokeWidth, + w: props.w, + h: props.h, + color: props.color, + fill: props.fill, + size: props.size, + theme, + }) + break + + case 'solid': + svgElm = SolidStyleCloudSvg({ + strokeWidth, + w: props.w, + h: props.h, + color: props.color, + fill: props.fill, + size: props.size, + id, + theme, + }) + break + + default: + svgElm = DashStyleCloudSvg({ + id, + strokeWidth, + w: props.w, + h: props.h, + dash: props.dash, + color: props.color, + fill: props.fill, + theme, + size: props.size, + }) + } + break + } default: { const outline = this.editor.getOutline(shape) const lines = getLines(shape.props, strokeWidth) diff --git a/packages/editor/src/lib/editor/shapes/geo/cloudOutline.ts b/packages/editor/src/lib/editor/shapes/geo/cloudOutline.ts new file mode 100644 index 000000000..9dd2f5890 --- /dev/null +++ b/packages/editor/src/lib/editor/shapes/geo/cloudOutline.ts @@ -0,0 +1,301 @@ +import { PI, Vec2d, getPointOnCircle, shortAngleDist } from '@tldraw/primitives' +import { TLDefaultSizeStyle, Vec2dModel } from '@tldraw/tlschema' +import { rng } from '@tldraw/utils' + +function getPillCircumference(width: number, height: number) { + const radius = Math.min(width, height) / 2 + const longSide = Math.max(width, height) - radius * 2 + + return Math.PI * (radius * 2) + 2 * longSide +} + +type PillSection = + | { + type: 'straight' + start: Vec2dModel + delta: Vec2dModel + } + | { + type: 'arc' + center: Vec2dModel + startAngle: number + } + +export function getPillPoints(width: number, height: number, numPoints: number) { + const radius = Math.min(width, height) / 2 + const longSide = Math.max(width, height) - radius * 2 + + const circumference = Math.PI * (radius * 2) + 2 * longSide + + const spacing = circumference / numPoints + + const sections: PillSection[] = + width > height + ? [ + { + type: 'straight', + start: new Vec2d(radius, 0), + delta: new Vec2d(1, 0), + }, + { + type: 'arc', + center: new Vec2d(width - radius, radius), + startAngle: -PI / 2, + }, + { + type: 'straight', + start: new Vec2d(width - radius, height), + delta: new Vec2d(-1, 0), + }, + { + type: 'arc', + center: new Vec2d(radius, radius), + startAngle: PI / 2, + }, + ] + : [ + { + type: 'straight', + start: new Vec2d(width, radius), + delta: new Vec2d(0, 1), + }, + { + type: 'arc', + center: new Vec2d(radius, height - radius), + startAngle: 0, + }, + { + type: 'straight', + start: new Vec2d(0, height - radius), + delta: new Vec2d(0, -1), + }, + { + type: 'arc', + center: new Vec2d(radius, radius), + startAngle: PI, + }, + ] + + let sectionOffset = 0 + + const points: Vec2d[] = [] + for (let i = 0; i < numPoints; i++) { + const section = sections[0] + if (section.type === 'straight') { + points.push(Vec2d.Add(section.start, Vec2d.Mul(section.delta, sectionOffset))) + } else { + points.push( + getPointOnCircle( + section.center.x, + section.center.y, + radius, + section.startAngle + sectionOffset / radius + ) + ) + } + sectionOffset += spacing + let sectionLength = section.type === 'straight' ? longSide : PI * radius + while (sectionOffset > sectionLength) { + sectionOffset -= sectionLength + sections.push(sections.shift()!) + sectionLength = sections[0].type === 'straight' ? longSide : PI * radius + } + } + + return points +} + +const switchSize = (size: TLDefaultSizeStyle, s: T, m: T, l: T, xl: T) => { + switch (size) { + case 's': + return s + case 'm': + return m + case 'l': + return l + case 'xl': + return xl + } +} + +export function getCloudArcs( + width: number, + height: number, + seed: string, + size: TLDefaultSizeStyle +) { + const getRandom = rng(seed) + const pillCircumference = getPillCircumference(width, height) + const numBumps = Math.max(Math.ceil(pillCircumference / switchSize(size, 50, 70, 100, 130)), 6) + const targetBumpProtrusion = (pillCircumference / numBumps) * 0.2 + + // if the aspect ratio is high, innerWidth should be smaller + const innerWidth = Math.max(width - targetBumpProtrusion * 2, 1) + const innerHeight = Math.max(height - targetBumpProtrusion * 2, 1) + const paddingX = (width - innerWidth) / 2 + const paddingY = (height - innerHeight) / 2 + + const bumpPoints = getPillPoints(innerWidth, innerHeight, numBumps).map((p) => { + return p.addXY(paddingX, paddingY) + }) + + const maxWiggle = targetBumpProtrusion * 0.3 + + const adjustedBumpPoints = bumpPoints.map((p) => { + return Vec2d.AddXY(p, getRandom() * maxWiggle, getRandom() * maxWiggle) + }) + + const arcs: Arc[] = [] + + for (let i = 0; i < adjustedBumpPoints.length; i++) { + const leftPoint = adjustedBumpPoints[i] + const rightPoint = adjustedBumpPoints[i === adjustedBumpPoints.length - 1 ? 0 : i + 1] + + arcs.push(getCloudArc(leftPoint, rightPoint, Math.max(paddingX, paddingY), width, height)) + } + + return arcs +} + +export function getCloudArc( + leftPoint: Vec2d, + rightPoint: Vec2d, + padding: number, + width: number, + height: number +) { + const midPoint = Vec2d.Average([leftPoint, rightPoint]) + const offsetAngle = Vec2d.Angle(leftPoint, rightPoint) - Math.PI / 2 + const arcPoint = Vec2d.Add(midPoint, Vec2d.FromAngle(offsetAngle, padding)) + if (arcPoint.x < 0) { + arcPoint.x = 0 + } else if (arcPoint.x > width) { + arcPoint.x = width + } + if (arcPoint.y < 0) { + arcPoint.y = 0 + } else if (arcPoint.y > height) { + arcPoint.y = height + } + + const center = getCenterOfCircleGivenThreePoints(leftPoint, rightPoint, arcPoint) + const radius = Vec2d.Dist(center, leftPoint) + + return { + leftPoint, + rightPoint, + center, + radius, + } +} + +type Arc = ReturnType + +function getCenterOfCircleGivenThreePoints(a: Vec2d, b: Vec2d, c: Vec2d) { + const A = a.x * (b.y - c.y) - a.y * (b.x - c.x) + b.x * c.y - c.x * b.y + const B = + (a.x * a.x + a.y * a.y) * (c.y - b.y) + + (b.x * b.x + b.y * b.y) * (a.y - c.y) + + (c.x * c.x + c.y * c.y) * (b.y - a.y) + const C = + (a.x * a.x + a.y * a.y) * (b.x - c.x) + + (b.x * b.x + b.y * b.y) * (c.x - a.x) + + (c.x * c.x + c.y * c.y) * (a.x - b.x) + + const x = -B / (2 * A) + const y = -C / (2 * A) + + // handle situations where the points are colinear (this happens when the cloud is very small) + if (!Number.isFinite(x) || !Number.isFinite(y)) { + return Vec2d.Average([a, b, c]) + } + + return new Vec2d(x, y) +} + +export function cloudOutline( + width: number, + height: number, + seed: string, + size: TLDefaultSizeStyle +) { + const path: Vec2d[] = [] + + const arcs = getCloudArcs(width, height, seed, size) + + for (const { center, radius, leftPoint, rightPoint } of arcs) { + path.push(...pointsOnArc(leftPoint, rightPoint, center, radius, 10)) + } + + return path +} + +export function cloudSvgPath( + width: number, + height: number, + seed: string, + size: TLDefaultSizeStyle +) { + const arcs = getCloudArcs(width, height, seed, size) + let path = `M${arcs[0].leftPoint.x},${arcs[0].leftPoint.y}` + + // now draw arcs for each circle, starting where it intersects the previous circle, and ending where it intersects the next circle + for (const { rightPoint, radius } of arcs) { + path += ` A${radius},${radius} 0 0,1 ${rightPoint.x},${rightPoint.y}` + } + + path += ' Z' + return path +} + +export function inkyCloudSvgPath( + width: number, + height: number, + seed: string, + size: TLDefaultSizeStyle +) { + const getRandom = rng(seed) + const mut = (n: number) => { + const multiplier = size === 's' ? 0.5 : size === 'm' ? 0.7 : size === 'l' ? 0.9 : 1.6 + return n + getRandom() * multiplier * 2 + } + const arcs = getCloudArcs(width, height, seed, size) + let pathA = `M${arcs[0].leftPoint.x},${arcs[0].leftPoint.y}` + let pathB = `M${mut(arcs[0].leftPoint.x)},${mut(arcs[0].leftPoint.y)}` + + // now draw arcs for each circle, starting where it intersects the previous circle, and ending where it intersects the next circle + for (const { rightPoint, radius, center } of arcs) { + pathA += ` A${radius},${radius} 0 0,1 ${rightPoint.x},${rightPoint.y}` + const mutX = mut(rightPoint.x) + const mutY = mut(rightPoint.y) + const mutRadius = Vec2d.Dist(center, { x: mutX, y: mutY }) + + pathB += ` A${mutRadius},${mutRadius} 0 0,1 ${mutX},${mutY}` + } + + return pathA + pathB + ' Z' +} + +export function pointsOnArc( + startPoint: Vec2dModel, + endPoint: Vec2dModel, + center: Vec2dModel, + radius: number, + numPoints: number +): Vec2d[] { + const results: Vec2d[] = [] + + const startAngle = Vec2d.Angle(center, startPoint) + const endAngle = Vec2d.Angle(center, endPoint) + + const l = shortAngleDist(startAngle, endAngle) + + for (let i = 0; i < numPoints; i++) { + const t = i / (numPoints - 1) + const angle = startAngle + l * t + const point = getPointOnCircle(center.x, center.y, radius, angle) + results.push(point) + } + + return results +} diff --git a/packages/editor/src/lib/editor/shapes/geo/components/DashStyleCloud.tsx b/packages/editor/src/lib/editor/shapes/geo/components/DashStyleCloud.tsx new file mode 100644 index 000000000..f7a85ed6c --- /dev/null +++ b/packages/editor/src/lib/editor/shapes/geo/components/DashStyleCloud.tsx @@ -0,0 +1,120 @@ +import { Vec2d, canonicalizeRotation } from '@tldraw/primitives' +import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/tlschema' +import * as React from 'react' +import { + ShapeFill, + getShapeFillSvg, + getSvgWithShapeFill, + useDefaultColorTheme, +} from '../../shared/ShapeFill' +import { getPerfectDashProps } from '../../shared/getPerfectDashProps' +import { cloudSvgPath, getCloudArcs } from '../cloudOutline' + +export const DashStyleCloud = React.memo(function DashStylePolygon({ + dash, + fill, + color, + strokeWidth, + w, + h, + id, + size, +}: Pick & { + strokeWidth: number + id: TLShapeId +}) { + const theme = useDefaultColorTheme() + const innerPath = cloudSvgPath(w, h, id, size) + const arcs = getCloudArcs(w, h, id, size) + + return ( + <> + + + {arcs.map(({ leftPoint, rightPoint, center, radius }, i) => { + const angle = canonicalizeRotation( + canonicalizeRotation(Vec2d.Angle(center, rightPoint)) - + canonicalizeRotation(Vec2d.Angle(center, leftPoint)) + ) + const arcLength = radius * angle + + const { strokeDasharray, strokeDashoffset } = getPerfectDashProps( + arcLength, + strokeWidth, + { + style: dash, + start: 'outset', + end: 'outset', + } + ) + + return ( + + ) + })} + + + ) +}) + +export function DashStyleCloudSvg({ + dash, + fill, + color, + theme, + strokeWidth, + w, + h, + id, + size, +}: Pick & { + id: TLShapeId + strokeWidth: number + theme: TLDefaultColorTheme +}) { + const innerPath = cloudSvgPath(w, h, id, size) + const arcs = getCloudArcs(w, h, id, size) + + const strokeElement = document.createElementNS('http://www.w3.org/2000/svg', 'g') + strokeElement.setAttribute('stroke-width', strokeWidth.toString()) + strokeElement.setAttribute('stroke', theme[color].solid) + strokeElement.setAttribute('fill', 'none') + + for (const { leftPoint, rightPoint, center, radius } of arcs) { + const angle = canonicalizeRotation( + canonicalizeRotation(Vec2d.Angle(center, rightPoint)) - + canonicalizeRotation(Vec2d.Angle(center, leftPoint)) + ) + const arcLength = radius * angle + + const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(arcLength, strokeWidth, { + style: dash, + start: 'outset', + end: 'outset', + }) + + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path') + path.setAttribute( + 'd', + `M${leftPoint.x},${leftPoint.y}A${radius},${radius},0,0,1,${rightPoint.x},${rightPoint.y}` + ) + path.setAttribute('stroke-dasharray', strokeDasharray.toString()) + path.setAttribute('stroke-dashoffset', strokeDashoffset.toString()) + strokeElement.appendChild(path) + } + + // Get the fill element, if any + const fillElement = getShapeFillSvg({ + d: innerPath, + fill, + color, + theme, + }) + + return getSvgWithShapeFill(strokeElement, fillElement) +} diff --git a/packages/editor/src/lib/editor/shapes/geo/components/DrawStyleCloud.tsx b/packages/editor/src/lib/editor/shapes/geo/components/DrawStyleCloud.tsx new file mode 100644 index 000000000..6bcfd8d0e --- /dev/null +++ b/packages/editor/src/lib/editor/shapes/geo/components/DrawStyleCloud.tsx @@ -0,0 +1,65 @@ +import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/tlschema' +import * as React from 'react' +import { + ShapeFill, + getShapeFillSvg, + getSvgWithShapeFill, + useDefaultColorTheme, +} from '../../shared/ShapeFill' +import { inkyCloudSvgPath } from '../cloudOutline' + +export const DrawStyleCloud = React.memo(function StyleCloud({ + fill, + color, + strokeWidth, + w, + h, + id, + size, +}: Pick & { + strokeWidth: number + id: TLShapeId +}) { + const theme = useDefaultColorTheme() + const path = inkyCloudSvgPath(w, h, id, size) + + return ( + <> + + + + ) +}) + +export function DrawStyleCloudSvg({ + fill, + color, + strokeWidth, + theme, + w, + h, + id, + size, +}: Pick & { + strokeWidth: number + theme: TLDefaultColorTheme + id: TLShapeId +}) { + const pathData = inkyCloudSvgPath(w, h, id, size) + + const strokeElement = document.createElementNS('http://www.w3.org/2000/svg', 'path') + strokeElement.setAttribute('d', pathData) + strokeElement.setAttribute('stroke-width', strokeWidth.toString()) + strokeElement.setAttribute('stroke', theme[color].solid) + strokeElement.setAttribute('fill', 'none') + + // Get the fill element, if any + const fillElement = getShapeFillSvg({ + d: pathData, + fill, + color, + theme, + }) + + return getSvgWithShapeFill(strokeElement, fillElement) +} diff --git a/packages/editor/src/lib/editor/shapes/geo/components/SolidStyleCloud.tsx b/packages/editor/src/lib/editor/shapes/geo/components/SolidStyleCloud.tsx new file mode 100644 index 000000000..5814dabc7 --- /dev/null +++ b/packages/editor/src/lib/editor/shapes/geo/components/SolidStyleCloud.tsx @@ -0,0 +1,65 @@ +import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/tlschema' +import * as React from 'react' +import { + ShapeFill, + getShapeFillSvg, + getSvgWithShapeFill, + useDefaultColorTheme, +} from '../../shared/ShapeFill' +import { cloudSvgPath } from '../cloudOutline' + +export const SolidStyleCloud = React.memo(function SolidStyleCloud({ + fill, + color, + strokeWidth, + w, + h, + id, + size, +}: Pick & { + strokeWidth: number + id: TLShapeId +}) { + const theme = useDefaultColorTheme() + const path = cloudSvgPath(w, h, id, size) + + return ( + <> + + + + ) +}) + +export function SolidStyleCloudSvg({ + fill, + color, + strokeWidth, + theme, + w, + h, + id, + size, +}: Pick & { + strokeWidth: number + theme: TLDefaultColorTheme + id: TLShapeId +}) { + const pathData = cloudSvgPath(w, h, id, size) + + const strokeElement = document.createElementNS('http://www.w3.org/2000/svg', 'path') + strokeElement.setAttribute('d', pathData) + strokeElement.setAttribute('stroke-width', strokeWidth.toString()) + strokeElement.setAttribute('stroke', theme[color].solid) + strokeElement.setAttribute('fill', 'none') + + // Get the fill element, if any + const fillElement = getShapeFillSvg({ + d: pathData, + fill, + color, + theme, + }) + + return getSvgWithShapeFill(strokeElement, fillElement) +} diff --git a/packages/editor/src/lib/editor/shapes/geo/toolStates/Pointing.ts b/packages/editor/src/lib/editor/shapes/geo/toolStates/Pointing.ts index 4de3076bd..0534d00bb 100644 --- a/packages/editor/src/lib/editor/shapes/geo/toolStates/Pointing.ts +++ b/packages/editor/src/lib/editor/shapes/geo/toolStates/Pointing.ts @@ -81,7 +81,11 @@ export class Pointing extends StateNode { if (!shape) return const bounds = - shape.props.geo === 'star' ? getStarBounds(5, 200, 200) : new Box2d(0, 0, 200, 200) + shape.props.geo === 'star' + ? getStarBounds(5, 200, 200) + : shape.props.geo === 'cloud' + ? new Box2d(0, 0, 300, 180) + : new Box2d(0, 0, 200, 200) const delta = this.editor.getDeltaInParentSpace(shape, bounds.center) this.editor.select(id) diff --git a/packages/editor/src/lib/test/tools/resizing.test.ts b/packages/editor/src/lib/test/tools/resizing.test.ts index b6eb287ad..33927686b 100644 --- a/packages/editor/src/lib/test/tools/resizing.test.ts +++ b/packages/editor/src/lib/test/tools/resizing.test.ts @@ -1,5 +1,5 @@ import { - canolicalizeRotation, + canonicalizeRotation, EPSILON, PI, PI2, @@ -332,8 +332,8 @@ describe('When resizing mulitple shapes...', () => { .pointerMove(rotateEnd.x, rotateEnd.y) .pointerUp() - expect(canolicalizeRotation(shapeA.rotation) % Math.PI).toBeCloseTo( - canolicalizeRotation(rotation) % Math.PI + expect(canonicalizeRotation(shapeA.rotation) % Math.PI).toBeCloseTo( + canonicalizeRotation(rotation) % Math.PI ) expect(editor.getPageRotation(shapeB)).toBeCloseTo(rotation + rotationB) expect(editor.getPageRotation(shapeC)).toBeCloseTo(rotation + rotationB) @@ -589,7 +589,7 @@ describe('Reisizing a selection of multiple shapes', () => { editor.pointerUp(20, 20, { shiftKey: false }) jest.advanceTimersByTime(200) - expect(editor.getShapeById(ids.boxB)!.rotation).toBeCloseTo(canolicalizeRotation(-PI / 2)) + expect(editor.getShapeById(ids.boxB)!.rotation).toBeCloseTo(canonicalizeRotation(-PI / 2)) editor.select(ids.boxA, ids.boxB) // shrink @@ -2326,7 +2326,7 @@ describe('snapping while resizing a shape that has been rotated by multiples of expect(editor.getPageBoundsById(ids.boxX)!.w).toBeCloseTo(60) expect(editor.getPageBoundsById(ids.boxX)!.h).toBeCloseTo(60) expect(editor.getShapeById(ids.boxX)!.rotation).toEqual( - canolicalizeRotation(((PI / 2) * times) % (PI * 2)) + canonicalizeRotation(((PI / 2) * times) % (PI * 2)) ) } diff --git a/packages/editor/src/lib/utils/rotation.ts b/packages/editor/src/lib/utils/rotation.ts index d90134f50..e83338dfe 100644 --- a/packages/editor/src/lib/utils/rotation.ts +++ b/packages/editor/src/lib/utils/rotation.ts @@ -1,4 +1,4 @@ -import { canolicalizeRotation, Matrix2d, Vec2d } from '@tldraw/primitives' +import { canonicalizeRotation, Matrix2d, Vec2d } from '@tldraw/primitives' import { isShapeId, TLShape, TLShapePartial } from '@tldraw/tlschema' import { structuredClone } from '@tldraw/utils' import { Editor } from '../editor/Editor' @@ -83,7 +83,7 @@ export function applyRotationToSnapshotShapes({ Matrix2d.Inverse(parentTransform), newPagePoint ) - const newRotation = canolicalizeRotation(shape.rotation + delta) + const newRotation = canonicalizeRotation(shape.rotation + delta) return { id: shape.id, diff --git a/packages/primitives/api-report.md b/packages/primitives/api-report.md index 58304116a..686f39ff2 100644 --- a/packages/primitives/api-report.md +++ b/packages/primitives/api-report.md @@ -130,7 +130,7 @@ export class Box2d { } // @public (undocumented) -export function canolicalizeRotation(a: number): number; +export function canonicalizeRotation(a: number): number; // @public export function clamp(n: number, min: number): number; @@ -718,6 +718,8 @@ export class Vec2d { // (undocumented) static From({ x, y, z }: Vec2dModel): Vec2d; // (undocumented) + static FromAngle(r: number, length?: number): Vec2d; + // (undocumented) static FromArray(v: number[]): Vec2d; // (undocumented) static Len(A: VecLike): number; diff --git a/packages/primitives/src/index.ts b/packages/primitives/src/index.ts index 6e273e92c..4b3c14b83 100644 --- a/packages/primitives/src/index.ts +++ b/packages/primitives/src/index.ts @@ -55,7 +55,7 @@ export { angleDelta, approximately, areAnglesCompatible, - canolicalizeRotation, + canonicalizeRotation, clamp, clampRadians, degreesToRadians, diff --git a/packages/primitives/src/lib/Vec2d.ts b/packages/primitives/src/lib/Vec2d.ts index 555108090..c75dca31e 100644 --- a/packages/primitives/src/lib/Vec2d.ts +++ b/packages/primitives/src/lib/Vec2d.ts @@ -484,6 +484,10 @@ export class Vec2d { return r } + static FromAngle(r: number, length = 1) { + return new Vec2d(Math.cos(r) * length, Math.sin(r) * length) + } + static ToArray(A: VecLike) { return [A.x, A.y, A.z!] } diff --git a/packages/primitives/src/lib/utils.ts b/packages/primitives/src/lib/utils.ts index ae7058bd2..a789724f8 100644 --- a/packages/primitives/src/lib/utils.ts +++ b/packages/primitives/src/lib/utils.ts @@ -88,7 +88,7 @@ export function perimeterOfEllipse(rx: number, ry: number): number { * @returns A number between 0 and 2 * PI * @public */ -export function canolicalizeRotation(a: number) { +export function canonicalizeRotation(a: number) { a = a % PI2 if (a < 0) { a = a + PI2 diff --git a/packages/tlschema/api-report.md b/packages/tlschema/api-report.md index da2871c06..136a8d59f 100644 --- a/packages/tlschema/api-report.md +++ b/packages/tlschema/api-report.md @@ -457,14 +457,14 @@ export const frameShapeProps: { }; // @public (undocumented) -export const GeoShapeGeoStyle: EnumStyleProp<"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">; +export const GeoShapeGeoStyle: EnumStyleProp<"arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "cloud" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box">; // @internal (undocumented) export const geoShapeMigrations: Migrations; // @public (undocumented) export const geoShapeProps: { - geo: EnumStyleProp<"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">; + geo: EnumStyleProp<"arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "cloud" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box">; labelColor: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">; color: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">; fill: EnumStyleProp<"none" | "pattern" | "semi" | "solid">; diff --git a/packages/tlschema/src/migrations.test.ts b/packages/tlschema/src/migrations.test.ts index 8c5ec752e..963a1ae9f 100644 --- a/packages/tlschema/src/migrations.test.ts +++ b/packages/tlschema/src/migrations.test.ts @@ -15,7 +15,7 @@ import { arrowShapeMigrations } from './shapes/TLArrowShape' import { bookmarkShapeMigrations } from './shapes/TLBookmarkShape' import { drawShapeMigrations } from './shapes/TLDrawShape' import { embedShapeMigrations } from './shapes/TLEmbedShape' -import { geoShapeMigrations } from './shapes/TLGeoShape' +import { GeoShapeVersions, geoShapeMigrations } from './shapes/TLGeoShape' import { imageShapeMigrations } from './shapes/TLImageShape' import { noteShapeMigrations } from './shapes/TLNoteShape' import { textShapeMigrations } from './shapes/TLTextShape' @@ -707,6 +707,22 @@ describe('Migrate GeoShape legacy horizontal alignment', () => { }) }) +describe('adding cloud shape', () => { + const { up, down } = geoShapeMigrations.migrators[GeoShapeVersions.AddCloud] + + test('up does nothing', () => { + expect(up({ props: { geo: 'rectangle' } })).toEqual({ + props: { geo: 'rectangle' }, + }) + }) + + test('down converts clouds to rectangles', () => { + expect(down({ props: { geo: 'cloud' } })).toEqual({ + props: { geo: 'rectangle' }, + }) + }) +}) + describe('Migrate NoteShape legacy horizontal alignment', () => { const { up, down } = noteShapeMigrations.migrators[3] diff --git a/packages/tlschema/src/shapes/TLGeoShape.ts b/packages/tlschema/src/shapes/TLGeoShape.ts index 49ccf585e..1580d9929 100644 --- a/packages/tlschema/src/shapes/TLGeoShape.ts +++ b/packages/tlschema/src/shapes/TLGeoShape.ts @@ -17,6 +17,7 @@ import { ShapePropsType, TLBaseShape } from './TLBaseShape' export const GeoShapeGeoStyle = StyleProp.defineEnum('tldraw:geo', { defaultValue: 'rectangle', values: [ + 'cloud', 'rectangle', 'ellipse', 'triangle', @@ -72,11 +73,14 @@ const Versions = { AddCheckBox: 4, AddVerticalAlign: 5, MigrateLegacyAlign: 6, + AddCloud: 7, } as const +export { Versions as GeoShapeVersions } + /** @internal */ export const geoShapeMigrations = defineMigrations({ - currentVersion: Versions.MigrateLegacyAlign, + currentVersion: Versions.AddCloud, migrators: { [Versions.AddUrlProp]: { up: (shape) => { @@ -202,5 +206,21 @@ export const geoShapeMigrations = defineMigrations({ } }, }, + [Versions.AddCloud]: { + up: (shape) => { + return shape + }, + down: (shape) => { + if (shape.props.geo === 'cloud') { + return { + ...shape, + props: { + ...shape.props, + geo: 'rectangle', + }, + } + } + }, + }, }, }) diff --git a/packages/ui/api-report.md b/packages/ui/api-report.md index 0f6cd0405..41fcb58b0 100644 --- a/packages/ui/api-report.md +++ b/packages/ui/api-report.md @@ -370,7 +370,7 @@ export interface TLUiIconProps extends React.HTMLProps { } // @public (undocumented) -export type TLUiIconType = 'align-bottom-center' | 'align-bottom-left' | 'align-bottom-right' | 'align-bottom' | 'align-center-center' | 'align-center-horizontal' | 'align-center-left' | 'align-center-right' | 'align-center-vertical' | 'align-left' | 'align-right' | 'align-top-center' | 'align-top-left' | 'align-top-right' | 'align-top' | 'arrow-left' | 'arrowhead-arrow' | 'arrowhead-bar' | 'arrowhead-diamond' | 'arrowhead-dot' | 'arrowhead-none' | 'arrowhead-square' | 'arrowhead-triangle-inverted' | 'arrowhead-triangle' | 'aspect-ratio' | 'avatar' | 'blob' | 'bring-forward' | 'bring-to-front' | 'check' | 'checkbox-checked' | 'checkbox-empty' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'chevrons-ne' | 'chevrons-sw' | 'clipboard-copied' | 'clipboard-copy' | 'code' | 'collab' | 'color' | 'comment' | 'cross-2' | 'cross' | 'dash-dashed' | 'dash-dotted' | 'dash-draw' | 'dash-solid' | 'discord' | 'distribute-horizontal' | 'distribute-vertical' | 'dot' | 'dots-horizontal' | 'dots-vertical' | 'drag-handle-dots' | 'duplicate' | 'edit' | 'external-link' | 'file' | 'fill-none' | 'fill-pattern' | 'fill-semi' | 'fill-solid' | 'follow' | 'following' | 'font-draw' | 'font-mono' | 'font-sans' | 'font-serif' | 'geo-arrow-down' | 'geo-arrow-left' | 'geo-arrow-right' | 'geo-arrow-up' | 'geo-check-box' | 'geo-diamond' | 'geo-ellipse' | 'geo-hexagon' | 'geo-octagon' | 'geo-oval' | 'geo-pentagon' | 'geo-rectangle' | 'geo-rhombus-2' | 'geo-rhombus' | 'geo-star' | 'geo-trapezoid' | 'geo-triangle' | 'geo-x-box' | 'github' | 'group' | 'hidden' | 'image' | 'info-circle' | 'leading' | 'link' | 'lock-small' | 'lock' | 'menu' | 'minus' | 'mixed' | 'pack' | 'page' | 'plus' | 'question-mark-circle' | 'question-mark' | 'redo' | 'reset-zoom' | 'rotate-ccw' | 'rotate-cw' | 'ruler' | 'search' | 'send-backward' | 'send-to-back' | 'settings-horizontal' | 'settings-vertical-1' | 'settings-vertical' | 'share-1' | 'share-2' | 'size-extra-large' | 'size-large' | 'size-medium' | 'size-small' | 'spline-cubic' | 'spline-line' | 'stack-horizontal' | 'stack-vertical' | 'stretch-horizontal' | 'stretch-vertical' | 'text-align-center' | 'text-align-justify' | 'text-align-left' | 'text-align-right' | 'tool-arrow' | 'tool-embed' | 'tool-eraser' | 'tool-frame' | 'tool-hand' | 'tool-highlight' | 'tool-laser' | 'tool-line' | 'tool-media' | 'tool-note' | 'tool-pencil' | 'tool-pointer' | 'tool-text' | 'trash' | 'triangle-down' | 'triangle-up' | 'twitter' | 'undo' | 'ungroup' | 'unlock-small' | 'unlock' | 'vertical-align-center' | 'vertical-align-end' | 'vertical-align-start' | 'visible' | 'warning-triangle' | 'zoom-in' | 'zoom-out'; +export type TLUiIconType = 'align-bottom-center' | 'align-bottom-left' | 'align-bottom-right' | 'align-bottom' | 'align-center-center' | 'align-center-horizontal' | 'align-center-left' | 'align-center-right' | 'align-center-vertical' | 'align-left' | 'align-right' | 'align-top-center' | 'align-top-left' | 'align-top-right' | 'align-top' | 'arrow-left' | 'arrowhead-arrow' | 'arrowhead-bar' | 'arrowhead-diamond' | 'arrowhead-dot' | 'arrowhead-none' | 'arrowhead-square' | 'arrowhead-triangle-inverted' | 'arrowhead-triangle' | 'aspect-ratio' | 'avatar' | 'blob' | 'bring-forward' | 'bring-to-front' | 'check' | 'checkbox-checked' | 'checkbox-empty' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'chevrons-ne' | 'chevrons-sw' | 'clipboard-copied' | 'clipboard-copy' | 'code' | 'collab' | 'color' | 'comment' | 'cross-2' | 'cross' | 'dash-dashed' | 'dash-dotted' | 'dash-draw' | 'dash-solid' | 'discord' | 'distribute-horizontal' | 'distribute-vertical' | 'dot' | 'dots-horizontal' | 'dots-vertical' | 'drag-handle-dots' | 'duplicate' | 'edit' | 'external-link' | 'file' | 'fill-none' | 'fill-pattern' | 'fill-semi' | 'fill-solid' | 'follow' | 'following' | 'font-draw' | 'font-mono' | 'font-sans' | 'font-serif' | 'geo-arrow-down' | 'geo-arrow-left' | 'geo-arrow-right' | 'geo-arrow-up' | 'geo-check-box' | 'geo-cloud' | 'geo-diamond' | 'geo-ellipse' | 'geo-hexagon' | 'geo-octagon' | 'geo-oval' | 'geo-pentagon' | 'geo-rectangle' | 'geo-rhombus-2' | 'geo-rhombus' | 'geo-star' | 'geo-trapezoid' | 'geo-triangle' | 'geo-x-box' | 'github' | 'group' | 'hidden' | 'image' | 'info-circle' | 'leading' | 'link' | 'lock-small' | 'lock' | 'menu' | 'minus' | 'mixed' | 'pack' | 'page' | 'plus' | 'question-mark-circle' | 'question-mark' | 'redo' | 'reset-zoom' | 'rotate-ccw' | 'rotate-cw' | 'ruler' | 'search' | 'send-backward' | 'send-to-back' | 'settings-horizontal' | 'settings-vertical-1' | 'settings-vertical' | 'share-1' | 'share-2' | 'size-extra-large' | 'size-large' | 'size-medium' | 'size-small' | 'spline-cubic' | 'spline-line' | 'stack-horizontal' | 'stack-vertical' | 'stretch-horizontal' | 'stretch-vertical' | 'text-align-center' | 'text-align-justify' | 'text-align-left' | 'text-align-right' | 'tool-arrow' | 'tool-embed' | 'tool-eraser' | 'tool-frame' | 'tool-hand' | 'tool-highlight' | 'tool-laser' | 'tool-line' | 'tool-media' | 'tool-note' | 'tool-pencil' | 'tool-pointer' | 'tool-text' | 'trash' | 'triangle-down' | 'triangle-up' | 'twitter' | 'undo' | 'ungroup' | 'unlock-small' | 'unlock' | 'vertical-align-center' | 'vertical-align-end' | 'vertical-align-start' | 'visible' | 'warning-triangle' | 'zoom-in' | 'zoom-out'; // @public (undocumented) export interface TLUiInputProps { @@ -585,7 +585,7 @@ export type TLUiTranslation = { export type TLUiTranslationContextType = TLUiTranslation; // @public (undocumented) -export type TLUiTranslationKey = 'action.align-bottom' | 'action.align-center-horizontal.short' | 'action.align-center-horizontal' | 'action.align-center-vertical.short' | 'action.align-center-vertical' | 'action.align-left' | 'action.align-right' | 'action.align-top' | 'action.back-to-content' | 'action.bring-forward' | 'action.bring-to-front' | 'action.convert-to-bookmark' | 'action.convert-to-embed' | 'action.copy-as-json.short' | 'action.copy-as-json' | 'action.copy-as-png.short' | 'action.copy-as-png' | 'action.copy-as-svg.short' | 'action.copy-as-svg' | 'action.copy' | 'action.cut' | 'action.delete' | 'action.distribute-horizontal.short' | 'action.distribute-horizontal' | 'action.distribute-vertical.short' | 'action.distribute-vertical' | 'action.duplicate' | 'action.edit-link' | 'action.exit-pen-mode' | 'action.export-as-json.short' | 'action.export-as-json' | 'action.export-as-png.short' | 'action.export-as-png' | 'action.export-as-svg.short' | 'action.export-as-svg' | 'action.flip-horizontal.short' | 'action.flip-horizontal' | 'action.flip-vertical.short' | 'action.flip-vertical' | 'action.fork-project' | 'action.group' | 'action.insert-embed' | 'action.insert-media' | 'action.leave-shared-project' | 'action.new-project' | 'action.new-shared-project' | 'action.open-cursor-chat' | 'action.open-embed-link' | 'action.open-file' | 'action.pack' | 'action.paste' | 'action.print' | 'action.redo' | 'action.rotate-ccw' | 'action.rotate-cw' | 'action.save-copy' | 'action.select-all' | 'action.select-none' | 'action.send-backward' | 'action.send-to-back' | 'action.share-project' | 'action.stack-horizontal.short' | 'action.stack-horizontal' | 'action.stack-vertical.short' | 'action.stack-vertical' | 'action.stop-following' | 'action.stretch-horizontal.short' | 'action.stretch-horizontal' | 'action.stretch-vertical.short' | 'action.stretch-vertical' | 'action.toggle-auto-size' | 'action.toggle-dark-mode.menu' | 'action.toggle-dark-mode' | 'action.toggle-debug-mode.menu' | 'action.toggle-debug-mode' | 'action.toggle-focus-mode.menu' | 'action.toggle-focus-mode' | 'action.toggle-grid.menu' | 'action.toggle-grid' | 'action.toggle-lock' | 'action.toggle-reduce-motion.menu' | 'action.toggle-reduce-motion' | 'action.toggle-snap-mode.menu' | 'action.toggle-snap-mode' | 'action.toggle-tool-lock.menu' | 'action.toggle-tool-lock' | 'action.toggle-transparent.context-menu' | 'action.toggle-transparent.menu' | 'action.toggle-transparent' | 'action.undo' | 'action.ungroup' | 'action.zoom-in' | 'action.zoom-out' | 'action.zoom-to-100' | 'action.zoom-to-fit' | 'action.zoom-to-selection' | 'actions-menu.title' | 'align-style.end' | 'align-style.justify' | 'align-style.middle' | 'align-style.start' | 'arrowheadEnd-style.arrow' | 'arrowheadEnd-style.bar' | 'arrowheadEnd-style.diamond' | 'arrowheadEnd-style.dot' | 'arrowheadEnd-style.inverted' | 'arrowheadEnd-style.none' | 'arrowheadEnd-style.pipe' | 'arrowheadEnd-style.square' | 'arrowheadEnd-style.triangle' | 'arrowheadStart-style.arrow' | 'arrowheadStart-style.bar' | 'arrowheadStart-style.diamond' | 'arrowheadStart-style.dot' | 'arrowheadStart-style.inverted' | 'arrowheadStart-style.none' | 'arrowheadStart-style.pipe' | 'arrowheadStart-style.square' | 'arrowheadStart-style.triangle' | 'color-style.black' | 'color-style.blue' | 'color-style.green' | 'color-style.grey' | 'color-style.light-blue' | 'color-style.light-green' | 'color-style.light-red' | 'color-style.light-violet' | 'color-style.orange' | 'color-style.red' | 'color-style.violet' | 'color-style.yellow' | 'context-menu.arrange' | 'context-menu.copy-as' | 'context-menu.export-as' | 'context-menu.move-to-page' | 'context-menu.reorder' | 'context.pages.new-page' | 'cursor-chat.type-to-chat' | 'dash-style.dashed' | 'dash-style.dotted' | 'dash-style.draw' | 'dash-style.solid' | 'debug-panel.more' | 'edit-link-dialog.cancel' | 'edit-link-dialog.clear' | 'edit-link-dialog.detail' | 'edit-link-dialog.invalid-url' | 'edit-link-dialog.save' | 'edit-link-dialog.title' | 'edit-link-dialog.url' | 'edit-pages-dialog.move-down' | 'edit-pages-dialog.move-up' | 'embed-dialog.back' | 'embed-dialog.cancel' | 'embed-dialog.create' | 'embed-dialog.instruction' | 'embed-dialog.invalid-url' | 'embed-dialog.title' | 'embed-dialog.url' | 'file-system.confirm-clear.cancel' | 'file-system.confirm-clear.continue' | 'file-system.confirm-clear.description' | 'file-system.confirm-clear.dont-show-again' | 'file-system.confirm-clear.title' | 'file-system.confirm-open.cancel' | 'file-system.confirm-open.description' | 'file-system.confirm-open.dont-show-again' | 'file-system.confirm-open.open' | 'file-system.confirm-open.title' | 'file-system.file-open-error.file-format-version-too-new' | 'file-system.file-open-error.generic-corrupted-file' | 'file-system.file-open-error.not-a-tldraw-file' | 'file-system.file-open-error.title' | 'file-system.shared-document-file-open-error.description' | 'file-system.shared-document-file-open-error.title' | 'fill-style.none' | 'fill-style.pattern' | 'fill-style.semi' | 'fill-style.solid' | 'focus-mode.toggle-focus-mode' | 'font-style.draw' | 'font-style.mono' | 'font-style.sans' | 'font-style.serif' | 'geo-style.arrow-down' | 'geo-style.arrow-left' | 'geo-style.arrow-right' | 'geo-style.arrow-up' | 'geo-style.check-box' | 'geo-style.diamond' | 'geo-style.ellipse' | 'geo-style.hexagon' | 'geo-style.octagon' | 'geo-style.oval' | 'geo-style.pentagon' | 'geo-style.rectangle' | 'geo-style.rhombus-2' | 'geo-style.rhombus' | 'geo-style.star' | 'geo-style.trapezoid' | 'geo-style.triangle' | 'geo-style.x-box' | 'help-menu.about' | 'help-menu.discord' | 'help-menu.github' | 'help-menu.keyboard-shortcuts' | 'help-menu.title' | 'help-menu.twitter' | 'home-project-dialog.description' | 'home-project-dialog.ok' | 'home-project-dialog.title' | 'menu.copy-as' | 'menu.edit' | 'menu.export-as' | 'menu.file' | 'menu.language' | 'menu.preferences' | 'menu.title' | 'menu.view' | 'navigation-zone.toggle-minimap' | 'navigation-zone.zoom' | 'opacity-style.0.1' | 'opacity-style.0.25' | 'opacity-style.0.5' | 'opacity-style.0.75' | 'opacity-style.1' | 'page-menu.create-new-page' | 'page-menu.edit-done' | 'page-menu.edit-start' | 'page-menu.go-to-page' | 'page-menu.max-page-count-reached' | 'page-menu.new-page-initial-name' | 'page-menu.submenu.delete' | 'page-menu.submenu.duplicate-page' | 'page-menu.submenu.move-down' | 'page-menu.submenu.move-up' | 'page-menu.submenu.rename' | 'page-menu.submenu.title' | 'page-menu.title' | 'people-menu.change-color' | 'people-menu.change-name' | 'people-menu.follow' | 'people-menu.following' | 'people-menu.invite' | 'people-menu.leading' | 'people-menu.title' | 'people-menu.user' | 'rename-project-dialog.cancel' | 'rename-project-dialog.rename' | 'rename-project-dialog.title' | 'share-menu.copy-link-note' | 'share-menu.copy-link' | 'share-menu.copy-readonly-link-note' | 'share-menu.copy-readonly-link' | 'share-menu.create-snapshot-link' | 'share-menu.default-project-name' | 'share-menu.fork-note' | 'share-menu.offline-note' | 'share-menu.project-too-large' | 'share-menu.readonly-link' | 'share-menu.save-note' | 'share-menu.share-project' | 'share-menu.snapshot-link-note' | 'share-menu.title' | 'share-menu.upload-failed' | 'sharing.confirm-leave.cancel' | 'sharing.confirm-leave.description' | 'sharing.confirm-leave.dont-show-again' | 'sharing.confirm-leave.leave' | 'sharing.confirm-leave.title' | 'shortcuts-dialog.collaboration' | 'shortcuts-dialog.edit' | 'shortcuts-dialog.file' | 'shortcuts-dialog.preferences' | 'shortcuts-dialog.title' | 'shortcuts-dialog.tools' | 'shortcuts-dialog.transform' | 'shortcuts-dialog.view' | 'size-style.l' | 'size-style.m' | 'size-style.s' | 'size-style.xl' | 'spline-style.cubic' | 'spline-style.line' | 'style-panel.align' | 'style-panel.arrowhead-end' | 'style-panel.arrowhead-start' | 'style-panel.arrowheads' | 'style-panel.color' | 'style-panel.dash' | 'style-panel.fill' | 'style-panel.font' | 'style-panel.geo' | 'style-panel.mixed' | 'style-panel.opacity' | 'style-panel.position' | 'style-panel.size' | 'style-panel.spline' | 'style-panel.title' | 'style-panel.vertical-align' | 'toast.close' | 'toast.error.copy-fail.desc' | 'toast.error.copy-fail.title' | 'toast.error.export-fail.desc' | 'toast.error.export-fail.title' | 'tool-panel.drawing' | 'tool-panel.more' | 'tool-panel.shapes' | 'tool.arrow-down' | 'tool.arrow-left' | 'tool.arrow-right' | 'tool.arrow-up' | 'tool.arrow' | 'tool.asset' | 'tool.check-box' | 'tool.diamond' | 'tool.draw' | 'tool.ellipse' | 'tool.embed' | 'tool.eraser' | 'tool.frame' | 'tool.hand' | 'tool.hexagon' | 'tool.highlight' | 'tool.laser' | 'tool.line' | 'tool.note' | 'tool.octagon' | 'tool.oval' | 'tool.pentagon' | 'tool.rectangle' | 'tool.rhombus' | 'tool.select' | 'tool.star' | 'tool.text' | 'tool.trapezoid' | 'tool.triangle' | 'tool.x-box' | 'vscode.file-open.backup-failed' | 'vscode.file-open.backup-saved' | 'vscode.file-open.backup' | 'vscode.file-open.desc' | 'vscode.file-open.dont-show-again' | 'vscode.file-open.open'; +export type TLUiTranslationKey = 'action.align-bottom' | 'action.align-center-horizontal.short' | 'action.align-center-horizontal' | 'action.align-center-vertical.short' | 'action.align-center-vertical' | 'action.align-left' | 'action.align-right' | 'action.align-top' | 'action.back-to-content' | 'action.bring-forward' | 'action.bring-to-front' | 'action.convert-to-bookmark' | 'action.convert-to-embed' | 'action.copy-as-json.short' | 'action.copy-as-json' | 'action.copy-as-png.short' | 'action.copy-as-png' | 'action.copy-as-svg.short' | 'action.copy-as-svg' | 'action.copy' | 'action.cut' | 'action.delete' | 'action.distribute-horizontal.short' | 'action.distribute-horizontal' | 'action.distribute-vertical.short' | 'action.distribute-vertical' | 'action.duplicate' | 'action.edit-link' | 'action.exit-pen-mode' | 'action.export-as-json.short' | 'action.export-as-json' | 'action.export-as-png.short' | 'action.export-as-png' | 'action.export-as-svg.short' | 'action.export-as-svg' | 'action.flip-horizontal.short' | 'action.flip-horizontal' | 'action.flip-vertical.short' | 'action.flip-vertical' | 'action.fork-project' | 'action.group' | 'action.insert-embed' | 'action.insert-media' | 'action.leave-shared-project' | 'action.new-project' | 'action.new-shared-project' | 'action.open-cursor-chat' | 'action.open-embed-link' | 'action.open-file' | 'action.pack' | 'action.paste' | 'action.print' | 'action.redo' | 'action.rotate-ccw' | 'action.rotate-cw' | 'action.save-copy' | 'action.select-all' | 'action.select-none' | 'action.send-backward' | 'action.send-to-back' | 'action.share-project' | 'action.stack-horizontal.short' | 'action.stack-horizontal' | 'action.stack-vertical.short' | 'action.stack-vertical' | 'action.stop-following' | 'action.stretch-horizontal.short' | 'action.stretch-horizontal' | 'action.stretch-vertical.short' | 'action.stretch-vertical' | 'action.toggle-auto-size' | 'action.toggle-dark-mode.menu' | 'action.toggle-dark-mode' | 'action.toggle-debug-mode.menu' | 'action.toggle-debug-mode' | 'action.toggle-focus-mode.menu' | 'action.toggle-focus-mode' | 'action.toggle-grid.menu' | 'action.toggle-grid' | 'action.toggle-lock' | 'action.toggle-reduce-motion.menu' | 'action.toggle-reduce-motion' | 'action.toggle-snap-mode.menu' | 'action.toggle-snap-mode' | 'action.toggle-tool-lock.menu' | 'action.toggle-tool-lock' | 'action.toggle-transparent.context-menu' | 'action.toggle-transparent.menu' | 'action.toggle-transparent' | 'action.undo' | 'action.ungroup' | 'action.zoom-in' | 'action.zoom-out' | 'action.zoom-to-100' | 'action.zoom-to-fit' | 'action.zoom-to-selection' | 'actions-menu.title' | 'align-style.end' | 'align-style.justify' | 'align-style.middle' | 'align-style.start' | 'arrowheadEnd-style.arrow' | 'arrowheadEnd-style.bar' | 'arrowheadEnd-style.diamond' | 'arrowheadEnd-style.dot' | 'arrowheadEnd-style.inverted' | 'arrowheadEnd-style.none' | 'arrowheadEnd-style.pipe' | 'arrowheadEnd-style.square' | 'arrowheadEnd-style.triangle' | 'arrowheadStart-style.arrow' | 'arrowheadStart-style.bar' | 'arrowheadStart-style.diamond' | 'arrowheadStart-style.dot' | 'arrowheadStart-style.inverted' | 'arrowheadStart-style.none' | 'arrowheadStart-style.pipe' | 'arrowheadStart-style.square' | 'arrowheadStart-style.triangle' | 'color-style.black' | 'color-style.blue' | 'color-style.green' | 'color-style.grey' | 'color-style.light-blue' | 'color-style.light-green' | 'color-style.light-red' | 'color-style.light-violet' | 'color-style.orange' | 'color-style.red' | 'color-style.violet' | 'color-style.yellow' | 'context-menu.arrange' | 'context-menu.copy-as' | 'context-menu.export-as' | 'context-menu.move-to-page' | 'context-menu.reorder' | 'context.pages.new-page' | 'cursor-chat.type-to-chat' | 'dash-style.dashed' | 'dash-style.dotted' | 'dash-style.draw' | 'dash-style.solid' | 'debug-panel.more' | 'edit-link-dialog.cancel' | 'edit-link-dialog.clear' | 'edit-link-dialog.detail' | 'edit-link-dialog.invalid-url' | 'edit-link-dialog.save' | 'edit-link-dialog.title' | 'edit-link-dialog.url' | 'edit-pages-dialog.move-down' | 'edit-pages-dialog.move-up' | 'embed-dialog.back' | 'embed-dialog.cancel' | 'embed-dialog.create' | 'embed-dialog.instruction' | 'embed-dialog.invalid-url' | 'embed-dialog.title' | 'embed-dialog.url' | 'file-system.confirm-clear.cancel' | 'file-system.confirm-clear.continue' | 'file-system.confirm-clear.description' | 'file-system.confirm-clear.dont-show-again' | 'file-system.confirm-clear.title' | 'file-system.confirm-open.cancel' | 'file-system.confirm-open.description' | 'file-system.confirm-open.dont-show-again' | 'file-system.confirm-open.open' | 'file-system.confirm-open.title' | 'file-system.file-open-error.file-format-version-too-new' | 'file-system.file-open-error.generic-corrupted-file' | 'file-system.file-open-error.not-a-tldraw-file' | 'file-system.file-open-error.title' | 'file-system.shared-document-file-open-error.description' | 'file-system.shared-document-file-open-error.title' | 'fill-style.none' | 'fill-style.pattern' | 'fill-style.semi' | 'fill-style.solid' | 'focus-mode.toggle-focus-mode' | 'font-style.draw' | 'font-style.mono' | 'font-style.sans' | 'font-style.serif' | 'geo-style.arrow-down' | 'geo-style.arrow-left' | 'geo-style.arrow-right' | 'geo-style.arrow-up' | 'geo-style.check-box' | 'geo-style.cloud' | 'geo-style.diamond' | 'geo-style.ellipse' | 'geo-style.hexagon' | 'geo-style.octagon' | 'geo-style.oval' | 'geo-style.pentagon' | 'geo-style.rectangle' | 'geo-style.rhombus-2' | 'geo-style.rhombus' | 'geo-style.star' | 'geo-style.trapezoid' | 'geo-style.triangle' | 'geo-style.x-box' | 'help-menu.about' | 'help-menu.discord' | 'help-menu.github' | 'help-menu.keyboard-shortcuts' | 'help-menu.title' | 'help-menu.twitter' | 'home-project-dialog.description' | 'home-project-dialog.ok' | 'home-project-dialog.title' | 'menu.copy-as' | 'menu.edit' | 'menu.export-as' | 'menu.file' | 'menu.language' | 'menu.preferences' | 'menu.title' | 'menu.view' | 'navigation-zone.toggle-minimap' | 'navigation-zone.zoom' | 'opacity-style.0.1' | 'opacity-style.0.25' | 'opacity-style.0.5' | 'opacity-style.0.75' | 'opacity-style.1' | 'page-menu.create-new-page' | 'page-menu.edit-done' | 'page-menu.edit-start' | 'page-menu.go-to-page' | 'page-menu.max-page-count-reached' | 'page-menu.new-page-initial-name' | 'page-menu.submenu.delete' | 'page-menu.submenu.duplicate-page' | 'page-menu.submenu.move-down' | 'page-menu.submenu.move-up' | 'page-menu.submenu.rename' | 'page-menu.submenu.title' | 'page-menu.title' | 'people-menu.change-color' | 'people-menu.change-name' | 'people-menu.follow' | 'people-menu.following' | 'people-menu.invite' | 'people-menu.leading' | 'people-menu.title' | 'people-menu.user' | 'rename-project-dialog.cancel' | 'rename-project-dialog.rename' | 'rename-project-dialog.title' | 'share-menu.copy-link-note' | 'share-menu.copy-link' | 'share-menu.copy-readonly-link-note' | 'share-menu.copy-readonly-link' | 'share-menu.create-snapshot-link' | 'share-menu.default-project-name' | 'share-menu.fork-note' | 'share-menu.offline-note' | 'share-menu.project-too-large' | 'share-menu.readonly-link' | 'share-menu.save-note' | 'share-menu.share-project' | 'share-menu.snapshot-link-note' | 'share-menu.title' | 'share-menu.upload-failed' | 'sharing.confirm-leave.cancel' | 'sharing.confirm-leave.description' | 'sharing.confirm-leave.dont-show-again' | 'sharing.confirm-leave.leave' | 'sharing.confirm-leave.title' | 'shortcuts-dialog.collaboration' | 'shortcuts-dialog.edit' | 'shortcuts-dialog.file' | 'shortcuts-dialog.preferences' | 'shortcuts-dialog.title' | 'shortcuts-dialog.tools' | 'shortcuts-dialog.transform' | 'shortcuts-dialog.view' | 'size-style.l' | 'size-style.m' | 'size-style.s' | 'size-style.xl' | 'spline-style.cubic' | 'spline-style.line' | 'style-panel.align' | 'style-panel.arrowhead-end' | 'style-panel.arrowhead-start' | 'style-panel.arrowheads' | 'style-panel.color' | 'style-panel.dash' | 'style-panel.fill' | 'style-panel.font' | 'style-panel.geo' | 'style-panel.mixed' | 'style-panel.opacity' | 'style-panel.position' | 'style-panel.size' | 'style-panel.spline' | 'style-panel.title' | 'style-panel.vertical-align' | 'toast.close' | 'toast.error.copy-fail.desc' | 'toast.error.copy-fail.title' | 'toast.error.export-fail.desc' | 'toast.error.export-fail.title' | 'tool-panel.drawing' | 'tool-panel.more' | 'tool-panel.shapes' | 'tool.arrow-down' | 'tool.arrow-left' | 'tool.arrow-right' | 'tool.arrow-up' | 'tool.arrow' | 'tool.asset' | 'tool.check-box' | 'tool.diamond' | 'tool.draw' | 'tool.ellipse' | 'tool.embed' | 'tool.eraser' | 'tool.frame' | 'tool.hand' | 'tool.hexagon' | 'tool.highlight' | 'tool.laser' | 'tool.line' | 'tool.note' | 'tool.octagon' | 'tool.oval' | 'tool.pentagon' | 'tool.rectangle' | 'tool.rhombus' | 'tool.select' | 'tool.star' | 'tool.text' | 'tool.trapezoid' | 'tool.triangle' | 'tool.x-box' | 'vscode.file-open.backup-failed' | 'vscode.file-open.backup-saved' | 'vscode.file-open.backup' | 'vscode.file-open.desc' | 'vscode.file-open.dont-show-again' | 'vscode.file-open.open'; // @public (undocumented) export function toolbarItem(toolItem: TLUiToolItem): TLUiToolbarItem; diff --git a/packages/ui/src/lib/components/StylePanel/styles.tsx b/packages/ui/src/lib/components/StylePanel/styles.tsx index f611f50b2..4c8ed7be9 100644 --- a/packages/ui/src/lib/components/StylePanel/styles.tsx +++ b/packages/ui/src/lib/components/StylePanel/styles.tsx @@ -55,6 +55,7 @@ export const STYLES = { geo: [ { value: 'rectangle', icon: 'geo-rectangle' }, { value: 'ellipse', icon: 'geo-ellipse' }, + { value: 'cloud', icon: 'geo-cloud' }, { value: 'triangle', icon: 'geo-triangle' }, { value: 'diamond', icon: 'geo-diamond' }, { value: 'pentagon', icon: 'geo-pentagon' }, diff --git a/packages/ui/src/lib/hooks/useToolbarSchema.tsx b/packages/ui/src/lib/hooks/useToolbarSchema.tsx index 0a1787c2e..558ac1577 100644 --- a/packages/ui/src/lib/hooks/useToolbarSchema.tsx +++ b/packages/ui/src/lib/hooks/useToolbarSchema.tsx @@ -61,8 +61,8 @@ export function ToolbarSchemaProvider({ overrides, children }: TLUiToolbarSchema toolbarItem(tools['triangle']), toolbarItem(tools['trapezoid']), toolbarItem(tools['rhombus']), - toolbarItem(tools['pentagon']), toolbarItem(tools['hexagon']), + toolbarItem(tools['cloud']), // toolbarItem(tools['octagon']), toolbarItem(tools['star']), toolbarItem(tools['oval']), diff --git a/packages/ui/src/lib/hooks/useTranslation/TLUiTranslationKey.ts b/packages/ui/src/lib/hooks/useTranslation/TLUiTranslationKey.ts index 6eb40c997..a645faab1 100644 --- a/packages/ui/src/lib/hooks/useTranslation/TLUiTranslationKey.ts +++ b/packages/ui/src/lib/hooks/useTranslation/TLUiTranslationKey.ts @@ -145,6 +145,7 @@ export type TLUiTranslationKey = | 'geo-style.hexagon' | 'geo-style.octagon' | 'geo-style.oval' + | 'geo-style.cloud' | 'geo-style.pentagon' | 'geo-style.rectangle' | 'geo-style.rhombus-2' diff --git a/packages/ui/src/lib/hooks/useTranslation/defaultTranslation.ts b/packages/ui/src/lib/hooks/useTranslation/defaultTranslation.ts index 912ba5471..a27c1452b 100644 --- a/packages/ui/src/lib/hooks/useTranslation/defaultTranslation.ts +++ b/packages/ui/src/lib/hooks/useTranslation/defaultTranslation.ts @@ -145,6 +145,7 @@ export const DEFAULT_TRANSLATION = { 'geo-style.hexagon': 'Hexagon', 'geo-style.octagon': 'Octagon', 'geo-style.oval': 'Oval', + 'geo-style.cloud': 'Cloud', 'geo-style.pentagon': 'Pentagon', 'geo-style.rectangle': 'Rectangle', 'geo-style.rhombus-2': 'Rhombus 2', diff --git a/packages/ui/src/lib/icon-types.ts b/packages/ui/src/lib/icon-types.ts index 92ff2e824..2f5e5df12 100644 --- a/packages/ui/src/lib/icon-types.ts +++ b/packages/ui/src/lib/icon-types.ts @@ -79,6 +79,7 @@ export type TLUiIconType = | 'geo-arrow-right' | 'geo-arrow-up' | 'geo-check-box' + | 'geo-cloud' | 'geo-diamond' | 'geo-ellipse' | 'geo-hexagon' @@ -243,6 +244,7 @@ export const iconTypes = [ 'geo-arrow-right', 'geo-arrow-up', 'geo-check-box', + 'geo-cloud', 'geo-diamond', 'geo-ellipse', 'geo-hexagon', diff --git a/scripts/publish-new.ts b/scripts/publish-new.ts index d1cd74e3e..e0ffd4ecd 100644 --- a/scripts/publish-new.ts +++ b/scripts/publish-new.ts @@ -3,6 +3,7 @@ import fetch from 'cross-fetch' import { assert } from 'node:console' import { parse } from 'semver' import { exec } from './lib/exec' +import { BUBLIC_ROOT } from './lib/file' import { nicelog } from './lib/nicelog' import { getLatestVersion, publish, setAllVersions } from './lib/publishing' import { getAllWorkspacePackages } from './lib/workspace' @@ -57,7 +58,12 @@ async function main() { packageJsonFilesToAdd.push(`${workspace.relativePath}/package.json`) } } - await exec('git', ['add', 'lerna.json', ...packageJsonFilesToAdd]) + await exec('git', [ + 'add', + 'lerna.json', + ...packageJsonFilesToAdd, + BUBLIC_ROOT + '/packages/*/src/version.ts', + ]) // this creates a new commit await auto.changelog({