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({