Custom Tools DX + screenshot example (#2198)
This PR adds a custom tool example, the `Screenshot Tool`. It demonstrates how a user can create a custom tool together with custom tool UI. ### Change Type - [x] `minor` — New feature ### Test Plan 1. Use the screenshot example ### Release Notes - adds ScreenshotTool custom tool example - improvements and new exports related to copying and exporting images / files - loosens up types around icons and translations - moving `StateNode.isActive` into an atom - adding `Editor.path`
This commit is contained in:
parent
d683cc0943
commit
14e8d19a71
116 changed files with 2559 additions and 1519 deletions
|
@ -1,249 +1,234 @@
|
||||||
export {}
|
import test, { Page, expect } from '@playwright/test'
|
||||||
|
import { Editor, TLShapeId, TLShapePartial } from '@tldraw/tldraw'
|
||||||
|
import assert from 'assert'
|
||||||
|
import { rename, writeFile } from 'fs/promises'
|
||||||
|
import { setupPage } from '../shared-e2e'
|
||||||
|
|
||||||
// import test, { Page, expect } from '@playwright/test'
|
declare const editor: Editor
|
||||||
// import { Editor, TLShapeId, TLShapePartial } from '@tldraw/tldraw'
|
|
||||||
// import assert from 'assert'
|
|
||||||
// import { rename, writeFile } from 'fs/promises'
|
|
||||||
// import { setupPage } from '../shared-e2e'
|
|
||||||
|
|
||||||
// declare const editor: Editor
|
test.describe('Export snapshots', () => {
|
||||||
|
const snapshots = {
|
||||||
|
'Exports geo text with leading line breaks': [
|
||||||
|
{
|
||||||
|
id: 'shape:testShape' as TLShapeId,
|
||||||
|
type: 'geo',
|
||||||
|
props: {
|
||||||
|
w: 100,
|
||||||
|
h: 30,
|
||||||
|
text: '\n\n\n\n\n\ntext',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'Exports geo text with trailing line breaks': [
|
||||||
|
{
|
||||||
|
id: 'shape:testShape' as TLShapeId,
|
||||||
|
type: 'geo',
|
||||||
|
props: {
|
||||||
|
w: 100,
|
||||||
|
h: 30,
|
||||||
|
text: 'text\n\n\n\n\n\n',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as Record<string, TLShapePartial[]>
|
||||||
|
|
||||||
// test.describe('Export snapshots', () => {
|
for (const fill of ['none', 'semi', 'solid', 'pattern']) {
|
||||||
// const snapshots = {
|
snapshots[`geo fill=${fill}`] = [
|
||||||
// 'Exports geo text with leading line breaks': [
|
{
|
||||||
// {
|
id: 'shape:testShape' as TLShapeId,
|
||||||
// id: 'shape:testShape' as TLShapeId,
|
type: 'geo',
|
||||||
// type: 'geo',
|
props: {
|
||||||
// props: {
|
fill,
|
||||||
// w: 100,
|
color: 'green',
|
||||||
// h: 30,
|
w: 100,
|
||||||
// text: '\n\n\n\n\n\ntext',
|
h: 100,
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// ],
|
]
|
||||||
// 'Exports geo text with trailing line breaks': [
|
|
||||||
// {
|
|
||||||
// id: 'shape:testShape' as TLShapeId,
|
|
||||||
// type: 'geo',
|
|
||||||
// props: {
|
|
||||||
// w: 100,
|
|
||||||
// h: 30,
|
|
||||||
// text: 'text\n\n\n\n\n\n',
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// } as Record<string, TLShapePartial[]>
|
|
||||||
|
|
||||||
// for (const fill of ['none', 'semi', 'solid', 'pattern']) {
|
snapshots[`arrow fill=${fill}`] = [
|
||||||
// snapshots[`geo fill=${fill}`] = [
|
{
|
||||||
// {
|
id: 'shape:testShape' as TLShapeId,
|
||||||
// id: 'shape:testShape' as TLShapeId,
|
type: 'arrow',
|
||||||
// type: 'geo',
|
props: {
|
||||||
// props: {
|
color: 'light-green',
|
||||||
// fill,
|
fill: fill,
|
||||||
// color: 'green',
|
arrowheadStart: 'square',
|
||||||
// w: 100,
|
arrowheadEnd: 'dot',
|
||||||
// h: 100,
|
start: { type: 'point', x: 0, y: 0 },
|
||||||
// },
|
end: { type: 'point', x: 100, y: 100 },
|
||||||
// },
|
bend: 20,
|
||||||
// ]
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
// snapshots[`arrow fill=${fill}`] = [
|
snapshots[`draw fill=${fill}`] = [
|
||||||
// {
|
{
|
||||||
// id: 'shape:testShape' as TLShapeId,
|
id: 'shape:testShape' as TLShapeId,
|
||||||
// type: 'arrow',
|
type: 'draw',
|
||||||
// props: {
|
props: {
|
||||||
// color: 'light-green',
|
color: 'light-violet',
|
||||||
// fill: fill,
|
fill: fill,
|
||||||
// arrowheadStart: 'square',
|
segments: [
|
||||||
// arrowheadEnd: 'dot',
|
{
|
||||||
// start: { type: 'point', x: 0, y: 0 },
|
type: 'straight',
|
||||||
// end: { type: 'point', x: 100, y: 100 },
|
points: [{ x: 0, y: 0 }],
|
||||||
// bend: 20,
|
},
|
||||||
// },
|
{
|
||||||
// },
|
type: 'straight',
|
||||||
// ]
|
points: [
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{ x: 100, y: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'straight',
|
||||||
|
points: [
|
||||||
|
{ x: 100, y: 0 },
|
||||||
|
{ x: 0, y: 100 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'straight',
|
||||||
|
points: [
|
||||||
|
{ x: 0, y: 100 },
|
||||||
|
{ x: 100, y: 100 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'straight',
|
||||||
|
points: [
|
||||||
|
{ x: 100, y: 100 },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isClosed: true,
|
||||||
|
isComplete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// snapshots[`draw fill=${fill}`] = [
|
for (const font of ['draw', 'sans', 'serif', 'mono']) {
|
||||||
// {
|
snapshots[`geo font=${font}`] = [
|
||||||
// id: 'shape:testShape' as TLShapeId,
|
{
|
||||||
// type: 'draw',
|
id: 'shape:testShape' as TLShapeId,
|
||||||
// props: {
|
type: 'geo',
|
||||||
// color: 'light-violet',
|
props: {
|
||||||
// fill: fill,
|
text: 'test',
|
||||||
// segments: [
|
color: 'blue',
|
||||||
// {
|
font,
|
||||||
// type: 'straight',
|
w: 100,
|
||||||
// points: [{ x: 0, y: 0 }],
|
h: 100,
|
||||||
// },
|
},
|
||||||
// {
|
},
|
||||||
// type: 'straight',
|
]
|
||||||
// points: [
|
|
||||||
// { x: 0, y: 0 },
|
|
||||||
// { x: 100, y: 0 },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// type: 'straight',
|
|
||||||
// points: [
|
|
||||||
// { x: 100, y: 0 },
|
|
||||||
// { x: 0, y: 100 },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// type: 'straight',
|
|
||||||
// points: [
|
|
||||||
// { x: 0, y: 100 },
|
|
||||||
// { x: 100, y: 100 },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// type: 'straight',
|
|
||||||
// points: [
|
|
||||||
// { x: 100, y: 100 },
|
|
||||||
// { x: 0, y: 0 },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// isClosed: true,
|
|
||||||
// isComplete: true,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for (const font of ['draw', 'sans', 'serif', 'mono']) {
|
snapshots[`arrow font=${font}`] = [
|
||||||
// snapshots[`geo font=${font}`] = [
|
{
|
||||||
// {
|
id: 'shape:testShape' as TLShapeId,
|
||||||
// id: 'shape:testShape' as TLShapeId,
|
type: 'arrow',
|
||||||
// type: 'geo',
|
props: {
|
||||||
// props: {
|
color: 'blue',
|
||||||
// text: 'test',
|
fill: 'solid',
|
||||||
// color: 'blue',
|
arrowheadStart: 'square',
|
||||||
// font,
|
arrowheadEnd: 'arrow',
|
||||||
// w: 100,
|
font,
|
||||||
// h: 100,
|
start: { type: 'point', x: 0, y: 0 },
|
||||||
// },
|
end: { type: 'point', x: 100, y: 100 },
|
||||||
// },
|
bend: 20,
|
||||||
// ]
|
text: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
// snapshots[`arrow font=${font}`] = [
|
snapshots[`arrow font=${font}`] = [
|
||||||
// {
|
{
|
||||||
// id: 'shape:testShape' as TLShapeId,
|
id: 'shape:testShape' as TLShapeId,
|
||||||
// type: 'arrow',
|
type: 'arrow',
|
||||||
// props: {
|
props: {
|
||||||
// color: 'blue',
|
color: 'blue',
|
||||||
// fill: 'solid',
|
fill: 'solid',
|
||||||
// arrowheadStart: 'square',
|
arrowheadStart: 'square',
|
||||||
// arrowheadEnd: 'arrow',
|
arrowheadEnd: 'arrow',
|
||||||
// font,
|
font,
|
||||||
// start: { type: 'point', x: 0, y: 0 },
|
start: { type: 'point', x: 0, y: 0 },
|
||||||
// end: { type: 'point', x: 100, y: 100 },
|
end: { type: 'point', x: 100, y: 100 },
|
||||||
// bend: 20,
|
bend: 20,
|
||||||
// text: 'test',
|
text: 'test',
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// ]
|
]
|
||||||
|
|
||||||
// snapshots[`arrow font=${font}`] = [
|
snapshots[`note font=${font}`] = [
|
||||||
// {
|
{
|
||||||
// id: 'shape:testShape' as TLShapeId,
|
id: 'shape:testShape' as TLShapeId,
|
||||||
// type: 'arrow',
|
type: 'note',
|
||||||
// props: {
|
props: {
|
||||||
// color: 'blue',
|
color: 'violet',
|
||||||
// fill: 'solid',
|
font,
|
||||||
// arrowheadStart: 'square',
|
text: 'test',
|
||||||
// arrowheadEnd: 'arrow',
|
},
|
||||||
// font,
|
},
|
||||||
// start: { type: 'point', x: 0, y: 0 },
|
]
|
||||||
// end: { type: 'point', x: 100, y: 100 },
|
|
||||||
// bend: 20,
|
|
||||||
// text: 'test',
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// ]
|
|
||||||
|
|
||||||
// snapshots[`note font=${font}`] = [
|
snapshots[`text font=${font}`] = [
|
||||||
// {
|
{
|
||||||
// id: 'shape:testShape' as TLShapeId,
|
id: 'shape:testShape' as TLShapeId,
|
||||||
// type: 'note',
|
type: 'text',
|
||||||
// props: {
|
props: {
|
||||||
// color: 'violet',
|
color: 'red',
|
||||||
// font,
|
font,
|
||||||
// text: 'test',
|
text: 'test',
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// ]
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// snapshots[`text font=${font}`] = [
|
const snapshotsToTest = Object.entries(snapshots)
|
||||||
// {
|
const filteredSnapshots = snapshotsToTest // maybe we filter these down, there are a lot of them
|
||||||
// id: 'shape:testShape' as TLShapeId,
|
|
||||||
// type: 'text',
|
|
||||||
// props: {
|
|
||||||
// color: 'red',
|
|
||||||
// font,
|
|
||||||
// text: 'test',
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for (const [name, shapes] of Object.entries(snapshots)) {
|
for (const [name, shapes] of filteredSnapshots) {
|
||||||
// test(`Exports with ${name}`, async ({ browser }) => {
|
test(`Exports with ${name} in dark mode`, async ({ browser }) => {
|
||||||
// const page = await browser.newPage()
|
const page = await browser.newPage()
|
||||||
// await setupPage(page)
|
await setupPage(page)
|
||||||
// await page.evaluate((shapes) => {
|
await page.evaluate((shapes) => {
|
||||||
// editor
|
editor.user.updateUserPreferences({ isDarkMode: true })
|
||||||
// .updateInstanceState({ exportBackground: false })
|
editor
|
||||||
// .selectAll()
|
.updateInstanceState({ exportBackground: false })
|
||||||
// .deleteShapes(editor.selectedShapeIds)
|
.selectAll()
|
||||||
// .createShapes(shapes)
|
.deleteShapes(editor.selectedShapeIds)
|
||||||
// }, shapes as any)
|
.createShapes(shapes)
|
||||||
|
}, shapes as any)
|
||||||
|
|
||||||
// await snapshotTest(page)
|
await snapshotTest(page)
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
|
|
||||||
// for (const [name, shapes] of Object.entries(snapshots)) {
|
async function snapshotTest(page: Page) {
|
||||||
// test(`Exports with ${name} in dark mode`, async ({ browser }) => {
|
page.waitForEvent('download').then(async (download) => {
|
||||||
// const page = await browser.newPage()
|
const path = (await download.path()) as string
|
||||||
// await setupPage(page)
|
assert(path)
|
||||||
// await page.evaluate((shapes) => {
|
await rename(path, path + '.svg')
|
||||||
// editor.user.updateUserPreferences({ isDarkMode: true })
|
await writeFile(
|
||||||
// editor
|
path + '.html',
|
||||||
// .updateInstanceState({ exportBackground: false })
|
`
|
||||||
// .selectAll()
|
<!DOCTYPE html>
|
||||||
// .deleteShapes(editor.selectedShapeIds)
|
<meta charset="utf-8" />
|
||||||
// .createShapes(shapes)
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
// }, shapes as any)
|
<img src="${path}.svg" />
|
||||||
|
`,
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
|
||||||
// await snapshotTest(page)
|
await page.goto(`file://${path}.html`)
|
||||||
// })
|
const clip = await page.$eval('img', (img) => img.getBoundingClientRect())
|
||||||
// }
|
await expect(page).toHaveScreenshot({
|
||||||
// })
|
omitBackground: true,
|
||||||
|
clip,
|
||||||
// async function snapshotTest(page: Page) {
|
})
|
||||||
// page.waitForEvent('download').then(async (download) => {
|
})
|
||||||
// const path = (await download.path()) as string
|
await page.evaluate(() => (window as any)['tldraw-export']())
|
||||||
// assert(path)
|
}
|
||||||
// await rename(path, path + '.svg')
|
})
|
||||||
// await writeFile(
|
|
||||||
// path + '.html',
|
|
||||||
// `
|
|
||||||
// <!DOCTYPE html>
|
|
||||||
// <meta charset="utf-8" />
|
|
||||||
// <meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
// <img src="${path}.svg" />
|
|
||||||
// `,
|
|
||||||
// 'utf-8'
|
|
||||||
// )
|
|
||||||
|
|
||||||
// await page.goto(`file://${path}.html`)
|
|
||||||
// const clip = await page.$eval('img', (img) => img.getBoundingClientRect())
|
|
||||||
// await expect(page).toHaveScreenshot({
|
|
||||||
// omitBackground: true,
|
|
||||||
// clip,
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// await page.evaluate(() => (window as any)['tldraw-export']())
|
|
||||||
// }
|
|
||||||
|
|
5
apps/examples/public/tool-screenshot.svg
Normal file
5
apps/examples/public/tool-screenshot.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 15C20 17.7614 17.7614 20 15 20C12.2386 20 10 17.7614 10 15C10 12.2386 12.2386 10 15 10C17.7614 10 20 12.2386 20 15Z" stroke="black" stroke-width="2"/>
|
||||||
|
<rect x="21" y="5" width="4" height="4" rx="2" fill="black"/>
|
||||||
|
<path d="M5 3H25C26.1046 3 27 3.89543 27 5V25C27 26.1046 26.1046 27 25 27H5C3.89543 27 3 26.1046 3 25V5C3 3.89543 3.89543 3 5 3Z" stroke="black" stroke-width="2"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 493 B |
|
@ -1,7 +1,7 @@
|
||||||
import { Tldraw, TLEditorComponents } from '@tldraw/tldraw'
|
import { Tldraw, TLEditorComponents } from '@tldraw/tldraw'
|
||||||
import '@tldraw/tldraw/tldraw.css'
|
import '@tldraw/tldraw/tldraw.css'
|
||||||
|
|
||||||
const components: Partial<TLEditorComponents> = {
|
const components: TLEditorComponents = {
|
||||||
Brush: function MyBrush({ brush }) {
|
Brush: function MyBrush({ brush }) {
|
||||||
return (
|
return (
|
||||||
<svg className="tl-overlays__item">
|
<svg className="tl-overlays__item">
|
||||||
|
|
|
@ -8,7 +8,7 @@ export const uiOverrides: TLUiOverrides = {
|
||||||
tools.card = {
|
tools.card = {
|
||||||
id: 'card',
|
id: 'card',
|
||||||
icon: 'color',
|
icon: 'color',
|
||||||
label: 'Card' as any,
|
label: 'Card',
|
||||||
kbd: 'c',
|
kbd: 'c',
|
||||||
readonlyOk: false,
|
readonlyOk: false,
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
|
|
|
@ -72,7 +72,7 @@ const MyComponentInFront = track(() => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const components: Partial<TLEditorComponents> = {
|
const components: TLEditorComponents = {
|
||||||
OnTheCanvas: MyComponent,
|
OnTheCanvas: MyComponent,
|
||||||
InFrontOfTheCanvas: MyComponentInFront,
|
InFrontOfTheCanvas: MyComponentInFront,
|
||||||
SnapLine: null,
|
SnapLine: null,
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { StateNode, TLCancelEvent, TLInterruptEvent } from '@tldraw/tldraw'
|
||||||
|
import { ScreenshotDragging } from './childStates/Dragging'
|
||||||
|
import { ScreenshotIdle } from './childStates/Idle'
|
||||||
|
import { ScreenshotPointing } from './childStates/Pointing'
|
||||||
|
|
||||||
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
|
export class ScreenshotTool extends StateNode {
|
||||||
|
// [1]
|
||||||
|
static override id = 'screenshot'
|
||||||
|
static override initial = 'idle'
|
||||||
|
static override children = () => [ScreenshotIdle, ScreenshotPointing, ScreenshotDragging]
|
||||||
|
|
||||||
|
// [2]
|
||||||
|
override onEnter = () => {
|
||||||
|
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExit = () => {
|
||||||
|
this.editor.setCursor({ type: 'default', rotation: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// [3]
|
||||||
|
override onInterrupt: TLInterruptEvent = () => {
|
||||||
|
this.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
override onCancel: TLCancelEvent = () => {
|
||||||
|
this.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private complete() {
|
||||||
|
this.parent.transition('select', {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This file contains our screenshot tool. The tool is a StateNode with the `id` "screenshot".
|
||||||
|
|
||||||
|
[1]
|
||||||
|
It has three child state nodes, ScreenshotIdle, ScreenshotPointing, and ScreenshotDragging.
|
||||||
|
Its initial state is `idle`.
|
||||||
|
|
||||||
|
[2]
|
||||||
|
When the screenshot tool is entered, we set the cursor to a crosshair. When it is exited, we
|
||||||
|
set the cursor back to the default cursor.
|
||||||
|
|
||||||
|
[3]
|
||||||
|
When the screenshot tool is interrupted or cancelled, we transition back to the select tool.
|
||||||
|
*/
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { Box2d, StateNode, atom, copyAs, exportAs } from '@tldraw/tldraw'
|
||||||
|
|
||||||
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
|
export class ScreenshotDragging extends StateNode {
|
||||||
|
static override id = 'dragging'
|
||||||
|
|
||||||
|
// [1]
|
||||||
|
screenshotBox = atom('screenshot brush', new Box2d())
|
||||||
|
|
||||||
|
// [2]
|
||||||
|
override onEnter = () => {
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
override onPointerMove = () => {
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
override onKeyDown = () => {
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
override onKeyUp = () => {
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
const {
|
||||||
|
inputs: { shiftKey, altKey, originPagePoint, currentPagePoint },
|
||||||
|
} = this.editor
|
||||||
|
|
||||||
|
const box = Box2d.FromPoints([originPagePoint, currentPagePoint])
|
||||||
|
|
||||||
|
if (shiftKey) {
|
||||||
|
if (box.w > box.h * (16 / 9)) {
|
||||||
|
box.h = box.w * (9 / 16)
|
||||||
|
} else {
|
||||||
|
box.w = box.h * (16 / 9)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPagePoint.x < originPagePoint.x) {
|
||||||
|
box.x = originPagePoint.x - box.w
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPagePoint.y < originPagePoint.y) {
|
||||||
|
box.y = originPagePoint.y - box.h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (altKey) {
|
||||||
|
box.w *= 2
|
||||||
|
box.h *= 2
|
||||||
|
box.x = originPagePoint.x - box.w / 2
|
||||||
|
box.y = originPagePoint.y - box.h / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
this.screenshotBox.set(box)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [3]
|
||||||
|
override onPointerUp = () => {
|
||||||
|
const { editor } = this
|
||||||
|
const box = this.screenshotBox.value
|
||||||
|
|
||||||
|
// get all shapes contained by or intersecting the box
|
||||||
|
const shapes = editor.currentPageShapes.filter((s) => {
|
||||||
|
const pageBounds = editor.getShapeMaskedPageBounds(s)
|
||||||
|
if (!pageBounds) return false
|
||||||
|
return box.includes(pageBounds)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (shapes.length) {
|
||||||
|
if (editor.inputs.ctrlKey) {
|
||||||
|
// Copy the shapes to the clipboard
|
||||||
|
copyAs(
|
||||||
|
editor,
|
||||||
|
shapes.map((s) => s.id),
|
||||||
|
'png',
|
||||||
|
{ bounds: box, background: editor.instanceState.exportBackground }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Export the shapes as a png
|
||||||
|
exportAs(
|
||||||
|
editor,
|
||||||
|
shapes.map((s) => s.id),
|
||||||
|
'png',
|
||||||
|
{ bounds: box, background: editor.instanceState.exportBackground }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editor.setCurrentTool('select')
|
||||||
|
}
|
||||||
|
|
||||||
|
// [4]
|
||||||
|
override onCancel = () => {
|
||||||
|
this.editor.setCurrentTool('select')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
[1]
|
||||||
|
This state has a reactive property (an Atom) called "screenshotBox". This is the box
|
||||||
|
that the user is drawing on the screen as they drag their pointer. We use an Atom here
|
||||||
|
so that our UI can subscribe to this property using `useValue` (see the ScreenshotBox
|
||||||
|
component in ScreenshotToolExample).
|
||||||
|
|
||||||
|
[2]
|
||||||
|
When the user enters this state, or when they move their pointer, we update the
|
||||||
|
screenshotBox property to be drawn between the place where the user started pointing
|
||||||
|
and the place where their pointer is now. If the user is holding Shift, then we modify
|
||||||
|
the dimensions of this box so that it is in a 16:9 aspect ratio.
|
||||||
|
|
||||||
|
[3]
|
||||||
|
When the user makes a pointer up and stops dragging, we export the shapes contained by
|
||||||
|
the screenshot box as a png. If the user is holding the ctrl key, we copy the shapes
|
||||||
|
to the clipboard instead.
|
||||||
|
|
||||||
|
[4]
|
||||||
|
When the user cancels (esc key) or makes a pointer up event, we transition back to the
|
||||||
|
select tool.
|
||||||
|
*/
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { StateNode, TLEventHandlers } from '@tldraw/tldraw'
|
||||||
|
|
||||||
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
|
export class ScreenshotIdle extends StateNode {
|
||||||
|
static override id = 'idle'
|
||||||
|
|
||||||
|
// [1]
|
||||||
|
override onPointerDown: TLEventHandlers['onPointerUp'] = () => {
|
||||||
|
this.parent.transition('pointing')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
[1]
|
||||||
|
When we the user makes a pointer down event, we transition to the pointing state.
|
||||||
|
*/
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { StateNode, TLEventHandlers } from '@tldraw/tldraw'
|
||||||
|
|
||||||
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
|
export class ScreenshotPointing extends StateNode {
|
||||||
|
static override id = 'pointing'
|
||||||
|
|
||||||
|
// [1]
|
||||||
|
override onPointerMove: TLEventHandlers['onPointerUp'] = () => {
|
||||||
|
if (this.editor.inputs.isDragging) {
|
||||||
|
this.parent.transition('dragging')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [2]
|
||||||
|
override onPointerUp: TLEventHandlers['onPointerUp'] = () => {
|
||||||
|
this.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
override onCancel: TLEventHandlers['onCancel'] = () => {
|
||||||
|
this.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private complete() {
|
||||||
|
this.parent.transition('idle')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
[1]
|
||||||
|
When the user makes a pointer move event, we check if they are dragging. If they are,
|
||||||
|
we transition to the dragging state. If they are not yet dragging, we stay in this state.
|
||||||
|
|
||||||
|
[2]
|
||||||
|
When the user cancelles or makes a pointer up event (while this state is still active,
|
||||||
|
so after the user has started pointing but before they've moved their pointer far enough
|
||||||
|
to start dragging), then we transition back to the idle state.
|
||||||
|
*/
|
|
@ -0,0 +1,157 @@
|
||||||
|
import {
|
||||||
|
Box2d,
|
||||||
|
TLEditorComponents,
|
||||||
|
TLUiAssetUrlOverrides,
|
||||||
|
TLUiOverrides,
|
||||||
|
Tldraw,
|
||||||
|
toolbarItem,
|
||||||
|
useEditor,
|
||||||
|
useValue,
|
||||||
|
} from '@tldraw/tldraw'
|
||||||
|
import '@tldraw/tldraw/tldraw.css'
|
||||||
|
import { ScreenshotTool } from './ScreenshotTool/ScreenshotTool'
|
||||||
|
import { ScreenshotDragging } from './ScreenshotTool/childStates/Dragging'
|
||||||
|
|
||||||
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
|
// [1]
|
||||||
|
const customTools = [ScreenshotTool]
|
||||||
|
|
||||||
|
// [2]
|
||||||
|
const customUiOverrides: TLUiOverrides = {
|
||||||
|
tools: (editor, tools) => {
|
||||||
|
return {
|
||||||
|
...tools,
|
||||||
|
screenshot: {
|
||||||
|
id: 'screenshot',
|
||||||
|
label: 'Screenshot',
|
||||||
|
readonlyOk: false,
|
||||||
|
icon: 'tool-screenshot',
|
||||||
|
kbd: 'j',
|
||||||
|
onSelect() {
|
||||||
|
editor.setCurrentTool('screenshot')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toolbar: (_editor, toolbarItems, { tools }) => {
|
||||||
|
toolbarItems.splice(4, 0, toolbarItem(tools.screenshot))
|
||||||
|
return toolbarItems
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// [3]
|
||||||
|
const customAssetUrls: TLUiAssetUrlOverrides = {
|
||||||
|
icons: {
|
||||||
|
'tool-screenshot': '/tool-screenshot.svg',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// [4]
|
||||||
|
function ScreenshotBox() {
|
||||||
|
const editor = useEditor()
|
||||||
|
|
||||||
|
const screenshotBrush = useValue(
|
||||||
|
'screenshot brush',
|
||||||
|
() => {
|
||||||
|
// Check whether the screenshot tool (and its dragging state) is active
|
||||||
|
if (editor.getPath() !== 'screenshot.dragging') return null
|
||||||
|
|
||||||
|
// Get screenshot.dragging state node
|
||||||
|
const draggingState = editor.getStateDescendant<ScreenshotDragging>('screenshot.dragging')!
|
||||||
|
|
||||||
|
// Get the box from the screenshot.dragging state node
|
||||||
|
const box = draggingState.screenshotBox.get()
|
||||||
|
|
||||||
|
// The box is in "page space", i.e. panned and zoomed with the canvas, but we
|
||||||
|
// want to show it in front of the canvas, so we'll need to convert it to
|
||||||
|
// "page space", i.e. uneffected by scale, and relative to the tldraw
|
||||||
|
// page's top left corner.
|
||||||
|
const { zoomLevel } = editor
|
||||||
|
const { x, y } = editor.pageToScreen({ x: box.x, y: box.y })
|
||||||
|
return new Box2d(x, y, box.w * zoomLevel, box.h * zoomLevel)
|
||||||
|
},
|
||||||
|
[editor]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!screenshotBrush) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
transform: `translate(${screenshotBrush.x}px, ${screenshotBrush.y}px)`,
|
||||||
|
width: screenshotBrush.w,
|
||||||
|
height: screenshotBrush.h,
|
||||||
|
border: '1px solid var(--color-text-0)',
|
||||||
|
zIndex: 999,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const customComponents: TLEditorComponents = {
|
||||||
|
InFrontOfTheCanvas: () => {
|
||||||
|
return <ScreenshotBox />
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// [5]
|
||||||
|
export default function ScreenshotToolExample() {
|
||||||
|
return (
|
||||||
|
<div className="tldraw__editor">
|
||||||
|
<Tldraw
|
||||||
|
persistenceKey="tldraw_screenshot_example"
|
||||||
|
tools={customTools}
|
||||||
|
overrides={customUiOverrides}
|
||||||
|
assetUrls={customAssetUrls}
|
||||||
|
components={customComponents}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Introduction:
|
||||||
|
|
||||||
|
This example shows how to create a custom tool. In tldraw, tools are parts of the
|
||||||
|
tldraw state chart. While the most common use for tools is creating shapes, you can
|
||||||
|
use tools to create other types of interactions too! In this example, we create a
|
||||||
|
"screenshot tool" that lets the user draw a box on the canvas. When the user finishes
|
||||||
|
drawing their box, we'll export (or copy) a screenshot of that area.
|
||||||
|
|
||||||
|
[1]
|
||||||
|
Our custom tool is a class that extends the StateNode class. See the ScreenshotTool
|
||||||
|
files for more about the too. We define an array (outside of any React component)
|
||||||
|
to hold the custom tools. We'll pass this into the Tldraw component's `tools` prop.
|
||||||
|
|
||||||
|
[2]
|
||||||
|
Here we add our custom tool to the toolbar. We do this by providing a custom
|
||||||
|
toolbar override to the Tldraw component. This override is a function that takes
|
||||||
|
the current editor, the default toolbar items, and the default tools. It returns
|
||||||
|
the new toolbar items. We use the toolbarItem helper to create a new toolbar item
|
||||||
|
for our custom tool. We then splice it into the toolbar items array at the 4th index.
|
||||||
|
This puts it after the eraser tool. We'll pass our overrides object into the
|
||||||
|
Tldraw component's `overrides` prop.
|
||||||
|
|
||||||
|
[3]
|
||||||
|
Our toolbar item is using a custom icon, so we need to provide the asset url for it.
|
||||||
|
We do this by providing a custom assetUrls object to the Tldraw component.
|
||||||
|
This object is a map of icon ids to their urls. The icon ids are the same as the
|
||||||
|
icon prop on the toolbar item. We'll pass our assetUrls object into the Tldraw
|
||||||
|
component's `assetUrls` prop.
|
||||||
|
|
||||||
|
[4]
|
||||||
|
We want to show a box on the canvas when the screenshot tool is active. We do this
|
||||||
|
by providing an override to the InFrontOfTheCanvas component. This component will be shown
|
||||||
|
in front of the canvas but behind any other UI elements, such as menus and the toolbar.
|
||||||
|
We'll pass our components object into the Tldraw component's `components` prop.
|
||||||
|
|
||||||
|
[5]
|
||||||
|
Finally we pass all of our customizations into the Tldraw component. It's important
|
||||||
|
that the customizations are defined outside of the React component, otherwise they
|
||||||
|
will cause the Tldraw component to see them as new values on every render, which may
|
||||||
|
produce unexpected results.
|
||||||
|
*/
|
|
@ -30,6 +30,7 @@ import MultipleExample from './examples/MultipleExample'
|
||||||
import OnTheCanvasExample from './examples/OnTheCanvas'
|
import OnTheCanvasExample from './examples/OnTheCanvas'
|
||||||
import PersistenceExample from './examples/PersistenceExample'
|
import PersistenceExample from './examples/PersistenceExample'
|
||||||
import ReadOnlyExample from './examples/ReadOnlyExample'
|
import ReadOnlyExample from './examples/ReadOnlyExample'
|
||||||
|
import ScreenshotToolExample from './examples/ScreenshotToolExample/ScreenshotToolExample'
|
||||||
import ScrollExample from './examples/ScrollExample'
|
import ScrollExample from './examples/ScrollExample'
|
||||||
import ShapeMetaExample from './examples/ShapeMetaExample'
|
import ShapeMetaExample from './examples/ShapeMetaExample'
|
||||||
import SnapshotExample from './examples/SnapshotExample/SnapshotExample'
|
import SnapshotExample from './examples/SnapshotExample/SnapshotExample'
|
||||||
|
@ -116,6 +117,11 @@ export const allExamples: Example[] = [
|
||||||
path: 'custom-ui',
|
path: 'custom-ui',
|
||||||
element: <CustomUiExample />,
|
element: <CustomUiExample />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Custom Tool (Screenshot)',
|
||||||
|
path: 'screenshot-tool',
|
||||||
|
element: <ScreenshotToolExample />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Hide UI',
|
title: 'Hide UI',
|
||||||
path: 'hide-ui',
|
path: 'hide-ui',
|
||||||
|
|
|
@ -58,6 +58,7 @@ const menuOverrides = {
|
||||||
schema.forEach((item) => {
|
schema.forEach((item) => {
|
||||||
if (item.id === 'menu' && item.type === 'group') {
|
if (item.id === 'menu' && item.type === 'group') {
|
||||||
item.children = item.children.filter((menuItem) => {
|
item.children = item.children.filter((menuItem) => {
|
||||||
|
if (!menuItem) return false
|
||||||
if (menuItem.id === 'file' && menuItem.type === 'submenu') {
|
if (menuItem.id === 'file' && menuItem.type === 'submenu') {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,6 +275,9 @@ export class Box2d {
|
||||||
zeroFix(): this;
|
zeroFix(): this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export type BoxLike = Box2d | Box2dModel;
|
||||||
|
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
export const CAMERA_SLIDE_FRICTION = 0.09;
|
export const CAMERA_SLIDE_FRICTION = 0.09;
|
||||||
|
|
||||||
|
@ -625,7 +628,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
// @deprecated (undocumented)
|
// @deprecated (undocumented)
|
||||||
get currentPageState(): TLInstancePageState;
|
get currentPageState(): TLInstancePageState;
|
||||||
// @deprecated (undocumented)
|
// @deprecated (undocumented)
|
||||||
get currentTool(): StateNode | undefined;
|
get currentTool(): StateNode;
|
||||||
// @deprecated (undocumented)
|
// @deprecated (undocumented)
|
||||||
get currentToolId(): string;
|
get currentToolId(): string;
|
||||||
deleteAssets(assets: TLAsset[] | TLAssetId[]): this;
|
deleteAssets(assets: TLAsset[] | TLAssetId[]): this;
|
||||||
|
@ -701,7 +704,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
getCurrentPageShapes(): TLShape[];
|
getCurrentPageShapes(): TLShape[];
|
||||||
getCurrentPageShapesSorted(): TLShape[];
|
getCurrentPageShapesSorted(): TLShape[];
|
||||||
getCurrentPageState(): TLInstancePageState;
|
getCurrentPageState(): TLInstancePageState;
|
||||||
getCurrentTool(): StateNode | undefined;
|
getCurrentTool(): StateNode;
|
||||||
getCurrentToolId(): string;
|
getCurrentToolId(): string;
|
||||||
getDocumentSettings(): TLDocument;
|
getDocumentSettings(): TLDocument;
|
||||||
getDroppingOverShape(point: VecLike, droppingShapes?: TLShape[]): TLUnknownShape | undefined;
|
getDroppingOverShape(point: VecLike, droppingShapes?: TLShape[]): TLUnknownShape | undefined;
|
||||||
|
@ -783,16 +786,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
getSharedOpacity(): SharedStyle<number>;
|
getSharedOpacity(): SharedStyle<number>;
|
||||||
getSharedStyles(): ReadonlySharedStyleMap;
|
getSharedStyles(): ReadonlySharedStyleMap;
|
||||||
getSortedChildIdsForParent(parent: TLPage | TLParentId | TLShape): TLShapeId[];
|
getSortedChildIdsForParent(parent: TLPage | TLParentId | TLShape): TLShapeId[];
|
||||||
getStateDescendant(path: string): StateNode | undefined;
|
getStateDescendant<T extends StateNode>(path: string): T | undefined;
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
getStyleForNextShape<T>(style: StyleProp<T>): T;
|
getStyleForNextShape<T>(style: StyleProp<T>): T;
|
||||||
getSvg(shapes: TLShape[] | TLShapeId[], opts?: Partial<{
|
getSvg(shapes: TLShape[] | TLShapeId[], opts?: Partial<TLSvgOptions>): Promise<SVGSVGElement | undefined>;
|
||||||
scale: number;
|
|
||||||
background: boolean;
|
|
||||||
padding: number;
|
|
||||||
darkMode?: boolean | undefined;
|
|
||||||
preserveAspectRatio: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
|
|
||||||
}>): Promise<SVGSVGElement | undefined>;
|
|
||||||
getViewportPageBounds(): Box2d;
|
getViewportPageBounds(): Box2d;
|
||||||
getViewportPageCenter(): Vec2d;
|
getViewportPageCenter(): Vec2d;
|
||||||
getViewportScreenBounds(): Box2d;
|
getViewportScreenBounds(): Box2d;
|
||||||
|
@ -2128,7 +2125,7 @@ export interface TldrawEditorBaseProps {
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
children?: any;
|
children?: any;
|
||||||
className?: string;
|
className?: string;
|
||||||
components?: Partial<TLEditorComponents>;
|
components?: TLEditorComponents;
|
||||||
inferDarkMode?: boolean;
|
inferDarkMode?: boolean;
|
||||||
initialState?: string;
|
initialState?: string;
|
||||||
onMount?: TLOnMountHandler;
|
onMount?: TLOnMountHandler;
|
||||||
|
@ -2150,13 +2147,9 @@ export type TldrawEditorProps = TldrawEditorBaseProps & ({
|
||||||
});
|
});
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLEditorComponents = {
|
export type TLEditorComponents = Partial<{
|
||||||
[K in keyof BaseEditorComponents]: BaseEditorComponents[K] | null;
|
[K in keyof BaseEditorComponents]: BaseEditorComponents[K] | null;
|
||||||
} & {
|
} & ErrorComponents>;
|
||||||
ErrorFallback: TLErrorFallbackComponent;
|
|
||||||
ShapeErrorFallback: TLShapeErrorFallbackComponent;
|
|
||||||
ShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallbackComponent;
|
|
||||||
};
|
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface TLEditorOptions {
|
export interface TLEditorOptions {
|
||||||
|
@ -2682,6 +2675,16 @@ export type TLStoreWithStatus = {
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLSvgDefsComponent = React.ComponentType;
|
export type TLSvgDefsComponent = React.ComponentType;
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export type TLSvgOptions = {
|
||||||
|
bounds: Box2d;
|
||||||
|
scale: number;
|
||||||
|
background: boolean;
|
||||||
|
padding: number;
|
||||||
|
darkMode?: boolean;
|
||||||
|
preserveAspectRatio: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
|
||||||
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLTickEvent = (elapsed: number) => void;
|
export type TLTickEvent = (elapsed: number) => void;
|
||||||
|
|
||||||
|
|
|
@ -3770,6 +3770,42 @@
|
||||||
],
|
],
|
||||||
"implementsTokenRanges": []
|
"implementsTokenRanges": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "TypeAlias",
|
||||||
|
"canonicalReference": "@tldraw/editor!BoxLike:type",
|
||||||
|
"docComment": "/**\n * @public\n */\n",
|
||||||
|
"excerptTokens": [
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "export type BoxLike = "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "Box2d",
|
||||||
|
"canonicalReference": "@tldraw/editor!Box2d:class"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": " | "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "Box2dModel",
|
||||||
|
"canonicalReference": "@tldraw/tlschema!Box2dModel:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ";"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileUrlPath": "packages/editor/src/lib/primitives/Box2d.ts",
|
||||||
|
"releaseTag": "Public",
|
||||||
|
"name": "BoxLike",
|
||||||
|
"typeTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "Function",
|
"kind": "Function",
|
||||||
"canonicalReference": "@tldraw/editor!canonicalizeRotation:function(1)",
|
"canonicalReference": "@tldraw/editor!canonicalizeRotation:function(1)",
|
||||||
|
@ -8256,10 +8292,6 @@
|
||||||
"text": "StateNode",
|
"text": "StateNode",
|
||||||
"canonicalReference": "@tldraw/editor!StateNode:class"
|
"canonicalReference": "@tldraw/editor!StateNode:class"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": " | undefined"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": ";"
|
"text": ";"
|
||||||
|
@ -8271,7 +8303,7 @@
|
||||||
"name": "currentTool",
|
"name": "currentTool",
|
||||||
"propertyTypeTokenRange": {
|
"propertyTypeTokenRange": {
|
||||||
"startIndex": 1,
|
"startIndex": 1,
|
||||||
"endIndex": 3
|
"endIndex": 2
|
||||||
},
|
},
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"isProtected": false,
|
"isProtected": false,
|
||||||
|
@ -10447,10 +10479,6 @@
|
||||||
"text": "StateNode",
|
"text": "StateNode",
|
||||||
"canonicalReference": "@tldraw/editor!StateNode:class"
|
"canonicalReference": "@tldraw/editor!StateNode:class"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": " | undefined"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": ";"
|
"text": ";"
|
||||||
|
@ -10459,7 +10487,7 @@
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"returnTypeTokenRange": {
|
"returnTypeTokenRange": {
|
||||||
"startIndex": 1,
|
"startIndex": 1,
|
||||||
"endIndex": 3
|
"endIndex": 2
|
||||||
},
|
},
|
||||||
"releaseTag": "Public",
|
"releaseTag": "Public",
|
||||||
"isProtected": false,
|
"isProtected": false,
|
||||||
|
@ -13658,7 +13686,16 @@
|
||||||
"excerptTokens": [
|
"excerptTokens": [
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": "getStateDescendant(path: "
|
"text": "getStateDescendant<T extends "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "StateNode",
|
||||||
|
"canonicalReference": "@tldraw/editor!StateNode:class"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ">(path: "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
|
@ -13668,24 +13705,32 @@
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": "): "
|
"text": "): "
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"kind": "Reference",
|
|
||||||
"text": "StateNode",
|
|
||||||
"canonicalReference": "@tldraw/editor!StateNode:class"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": " | undefined"
|
"text": "T | undefined"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": ";"
|
"text": ";"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"typeParameters": [
|
||||||
|
{
|
||||||
|
"typeParameterName": "T",
|
||||||
|
"constraintTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 2
|
||||||
|
},
|
||||||
|
"defaultTypeTokenRange": {
|
||||||
|
"startIndex": 0,
|
||||||
|
"endIndex": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"returnTypeTokenRange": {
|
"returnTypeTokenRange": {
|
||||||
"startIndex": 3,
|
"startIndex": 5,
|
||||||
"endIndex": 5
|
"endIndex": 6
|
||||||
},
|
},
|
||||||
"releaseTag": "Public",
|
"releaseTag": "Public",
|
||||||
"isProtected": false,
|
"isProtected": false,
|
||||||
|
@ -13694,8 +13739,8 @@
|
||||||
{
|
{
|
||||||
"parameterName": "path",
|
"parameterName": "path",
|
||||||
"parameterTypeTokenRange": {
|
"parameterTypeTokenRange": {
|
||||||
"startIndex": 1,
|
"startIndex": 3,
|
||||||
"endIndex": 2
|
"endIndex": 4
|
||||||
},
|
},
|
||||||
"isOptional": false
|
"isOptional": false
|
||||||
}
|
}
|
||||||
|
@ -13740,27 +13785,18 @@
|
||||||
"text": "Partial",
|
"text": "Partial",
|
||||||
"canonicalReference": "!Partial:type"
|
"canonicalReference": "!Partial:type"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": "<{\n scale: number;\n background: boolean;\n padding: number;\n darkMode?: boolean | undefined;\n preserveAspectRatio: "
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Reference",
|
|
||||||
"text": "React.SVGAttributes",
|
|
||||||
"canonicalReference": "@types/react!React.SVGAttributes:interface"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": "<"
|
"text": "<"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Reference",
|
"kind": "Reference",
|
||||||
"text": "SVGSVGElement",
|
"text": "TLSvgOptions",
|
||||||
"canonicalReference": "!SVGSVGElement:interface"
|
"canonicalReference": "@tldraw/editor!TLSvgOptions:type"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": ">['preserveAspectRatio'];\n }>"
|
"text": ">"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
|
@ -13791,8 +13827,8 @@
|
||||||
],
|
],
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"returnTypeTokenRange": {
|
"returnTypeTokenRange": {
|
||||||
"startIndex": 13,
|
"startIndex": 11,
|
||||||
"endIndex": 17
|
"endIndex": 15
|
||||||
},
|
},
|
||||||
"releaseTag": "Public",
|
"releaseTag": "Public",
|
||||||
"isProtected": false,
|
"isProtected": false,
|
||||||
|
@ -13810,7 +13846,7 @@
|
||||||
"parameterName": "opts",
|
"parameterName": "opts",
|
||||||
"parameterTypeTokenRange": {
|
"parameterTypeTokenRange": {
|
||||||
"startIndex": 6,
|
"startIndex": 6,
|
||||||
"endIndex": 12
|
"endIndex": 10
|
||||||
},
|
},
|
||||||
"isOptional": true
|
"isOptional": true
|
||||||
}
|
}
|
||||||
|
@ -37739,24 +37775,11 @@
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": "components?: "
|
"text": "components?: "
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"kind": "Reference",
|
|
||||||
"text": "Partial",
|
|
||||||
"canonicalReference": "!Partial:type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": "<"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"kind": "Reference",
|
"kind": "Reference",
|
||||||
"text": "TLEditorComponents",
|
"text": "TLEditorComponents",
|
||||||
"canonicalReference": "@tldraw/editor!TLEditorComponents:type"
|
"canonicalReference": "@tldraw/editor!TLEditorComponents:type"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": ">"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": ";"
|
"text": ";"
|
||||||
|
@ -37768,7 +37791,7 @@
|
||||||
"name": "components",
|
"name": "components",
|
||||||
"propertyTypeTokenRange": {
|
"propertyTypeTokenRange": {
|
||||||
"startIndex": 1,
|
"startIndex": 1,
|
||||||
"endIndex": 5
|
"endIndex": 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -38050,9 +38073,14 @@
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": "export type TLEditorComponents = "
|
"text": "export type TLEditorComponents = "
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "Partial",
|
||||||
|
"canonicalReference": "!Partial:type"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": "{\n [K in keyof "
|
"text": "<{\n [K in keyof "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Reference",
|
"kind": "Reference",
|
||||||
|
@ -38070,34 +38098,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": "[K] | null;\n} & {\n ErrorFallback: "
|
"text": "[K] | null;\n} & "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Reference",
|
"kind": "Reference",
|
||||||
"text": "TLErrorFallbackComponent",
|
"text": "ErrorComponents",
|
||||||
"canonicalReference": "@tldraw/editor!~TLErrorFallbackComponent:type"
|
"canonicalReference": "@tldraw/editor!~ErrorComponents:type"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": ";\n ShapeErrorFallback: "
|
"text": ">"
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Reference",
|
|
||||||
"text": "TLShapeErrorFallbackComponent",
|
|
||||||
"canonicalReference": "@tldraw/editor!~TLShapeErrorFallbackComponent:type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": ";\n ShapeIndicatorErrorFallback: "
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Reference",
|
|
||||||
"text": "TLShapeIndicatorErrorFallbackComponent",
|
|
||||||
"canonicalReference": "@tldraw/editor!~TLShapeIndicatorErrorFallbackComponent:type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": ";\n}"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
|
@ -38109,7 +38119,7 @@
|
||||||
"name": "TLEditorComponents",
|
"name": "TLEditorComponents",
|
||||||
"typeTokenRange": {
|
"typeTokenRange": {
|
||||||
"startIndex": 1,
|
"startIndex": 1,
|
||||||
"endIndex": 12
|
"endIndex": 9
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -42979,6 +42989,59 @@
|
||||||
"endIndex": 2
|
"endIndex": 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "TypeAlias",
|
||||||
|
"canonicalReference": "@tldraw/editor!TLSvgOptions:type",
|
||||||
|
"docComment": "/**\n * @public\n */\n",
|
||||||
|
"excerptTokens": [
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "export type TLSvgOptions = "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "{\n bounds: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "Box2d",
|
||||||
|
"canonicalReference": "@tldraw/editor!Box2d:class"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ";\n scale: number;\n background: boolean;\n padding: number;\n darkMode?: boolean;\n preserveAspectRatio: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "React.SVGAttributes",
|
||||||
|
"canonicalReference": "@types/react!React.SVGAttributes:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "<"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "SVGSVGElement",
|
||||||
|
"canonicalReference": "!SVGSVGElement:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ">['preserveAspectRatio'];\n}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ";"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileUrlPath": "packages/editor/src/lib/editor/types/misc-types.ts",
|
||||||
|
"releaseTag": "Public",
|
||||||
|
"name": "TLSvgOptions",
|
||||||
|
"typeTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "TypeAlias",
|
"kind": "TypeAlias",
|
||||||
"canonicalReference": "@tldraw/editor!TLTickEvent:type",
|
"canonicalReference": "@tldraw/editor!TLTickEvent:type",
|
||||||
|
|
|
@ -326,6 +326,11 @@ input,
|
||||||
fill: var(--color-brush-fill);
|
fill: var(--color-brush-fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tl-screenshot-brush {
|
||||||
|
stroke: var(--color-text-0);
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------- Scribble -------------------- */
|
/* -------------------- Scribble -------------------- */
|
||||||
|
|
||||||
.tl-scribble {
|
.tl-scribble {
|
||||||
|
@ -1558,7 +1563,6 @@ it from receiving any pointer events or affecting the cursor. */
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
max-height: 320px;
|
max-height: 320px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl-error-boundary__content button {
|
.tl-error-boundary__content button {
|
||||||
|
|
|
@ -241,7 +241,7 @@ export {
|
||||||
type TLHistoryEntry,
|
type TLHistoryEntry,
|
||||||
type TLHistoryMark,
|
type TLHistoryMark,
|
||||||
} from './lib/editor/types/history-types'
|
} from './lib/editor/types/history-types'
|
||||||
export { type RequiredKeys } from './lib/editor/types/misc-types'
|
export { type RequiredKeys, type TLSvgOptions } from './lib/editor/types/misc-types'
|
||||||
export { type TLResizeHandle, type TLSelectionHandle } from './lib/editor/types/selection-types'
|
export { type TLResizeHandle, type TLSelectionHandle } from './lib/editor/types/selection-types'
|
||||||
export { useContainer } from './lib/hooks/useContainer'
|
export { useContainer } from './lib/hooks/useContainer'
|
||||||
export { getCursor } from './lib/hooks/useCursor'
|
export { getCursor } from './lib/hooks/useCursor'
|
||||||
|
@ -260,6 +260,7 @@ export {
|
||||||
Box2d,
|
Box2d,
|
||||||
ROTATE_CORNER_TO_SELECTION_CORNER,
|
ROTATE_CORNER_TO_SELECTION_CORNER,
|
||||||
rotateSelectionHandle,
|
rotateSelectionHandle,
|
||||||
|
type BoxLike,
|
||||||
type RotateCorner,
|
type RotateCorner,
|
||||||
type SelectionCorner,
|
type SelectionCorner,
|
||||||
type SelectionEdge,
|
type SelectionEdge,
|
||||||
|
|
|
@ -86,7 +86,7 @@ export interface TldrawEditorBaseProps {
|
||||||
/**
|
/**
|
||||||
* Overrides for the editor's components, such as handles, collaborator cursors, etc.
|
* Overrides for the editor's components, such as handles, collaborator cursors, etc.
|
||||||
*/
|
*/
|
||||||
components?: Partial<TLEditorComponents>
|
components?: TLEditorComponents
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the editor has mounted.
|
* Called when the editor has mounted.
|
||||||
|
|
|
@ -184,7 +184,7 @@ function ZoomBrushWrapper() {
|
||||||
|
|
||||||
if (!(ZoomBrush && zoomBrush)) return null
|
if (!(ZoomBrush && zoomBrush)) return null
|
||||||
|
|
||||||
return <ZoomBrush className="tl-user-brush" brush={zoomBrush} />
|
return <ZoomBrush className="tl-user-brush tl-zoom-brush" brush={zoomBrush} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function SnapLinesWrapper() {
|
function SnapLinesWrapper() {
|
||||||
|
|
|
@ -12,7 +12,7 @@ export type TLBrushComponent = ComponentType<{
|
||||||
}>
|
}>
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export const DefaultBrush: TLBrushComponent = ({ brush, color, opacity }) => {
|
export const DefaultBrush: TLBrushComponent = ({ brush, color, opacity, className }) => {
|
||||||
const rSvg = useRef<SVGSVGElement>(null)
|
const rSvg = useRef<SVGSVGElement>(null)
|
||||||
useTransform(rSvg, brush.x, brush.y)
|
useTransform(rSvg, brush.x, brush.y)
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export const DefaultBrush: TLBrushComponent = ({ brush, color, opacity }) => {
|
||||||
<rect width={w} height={h} fill="none" stroke={color} opacity={0.1} />
|
<rect width={w} height={h} fill="none" stroke={color} opacity={0.1} />
|
||||||
</g>
|
</g>
|
||||||
) : (
|
) : (
|
||||||
<rect className="tl-brush tl-brush__default" width={w} height={h} />
|
<rect className={`tl-brush tl-brush__default ${className}`} width={w} height={h} />
|
||||||
)}
|
)}
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -129,7 +129,7 @@ import {
|
||||||
} from './types/event-types'
|
} from './types/event-types'
|
||||||
import { TLExternalAssetContent, TLExternalContent } from './types/external-content'
|
import { TLExternalAssetContent, TLExternalContent } from './types/external-content'
|
||||||
import { TLCommandHistoryOptions } from './types/history-types'
|
import { TLCommandHistoryOptions } from './types/history-types'
|
||||||
import { OptionalKeys, RequiredKeys } from './types/misc-types'
|
import { OptionalKeys, RequiredKeys, TLSvgOptions } from './types/misc-types'
|
||||||
import { TLResizeHandle } from './types/selection-types'
|
import { TLResizeHandle } from './types/selection-types'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
@ -1127,13 +1127,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
this.root.transition(id, info)
|
this.root.transition(id, info)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current selected tool.
|
* The current selected tool.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@computed getCurrentTool(): StateNode | undefined {
|
@computed getCurrentTool(): StateNode {
|
||||||
return this.root.getCurrent()
|
return this.root.getCurrent()!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1175,17 +1176,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
getStateDescendant(path: string): StateNode | undefined {
|
getStateDescendant<T extends StateNode>(path: string): T | undefined {
|
||||||
const ids = path.split('.').reverse()
|
const ids = path.split('.').reverse()
|
||||||
let state = this.root as StateNode
|
let state = this.root as StateNode
|
||||||
while (ids.length > 0) {
|
while (ids.length > 0) {
|
||||||
const id = ids.pop()
|
const id = ids.pop()
|
||||||
if (!id) return state
|
if (!id) return state as T
|
||||||
const childState = state.children?.[id]
|
const childState = state.children?.[id]
|
||||||
if (!childState) return undefined
|
if (!childState) return undefined
|
||||||
state = childState
|
state = childState
|
||||||
}
|
}
|
||||||
return state
|
return state as T
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- Document Settings --------------- */
|
/* ---------------- Document Settings --------------- */
|
||||||
|
@ -7550,6 +7551,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
// Otherwise, just return an empty map.
|
// Otherwise, just return an empty map.
|
||||||
const currentTool = this.root.getCurrent()!
|
const currentTool = this.root.getCurrent()!
|
||||||
const styles = new SharedStyleMap()
|
const styles = new SharedStyleMap()
|
||||||
|
|
||||||
|
if (!currentTool) return styles
|
||||||
|
|
||||||
if (currentTool.shapeType) {
|
if (currentTool.shapeType) {
|
||||||
for (const style of this.styleProps[currentTool.shapeType].keys()) {
|
for (const style of this.styleProps[currentTool.shapeType].keys()) {
|
||||||
styles.applyValue(style, this.getStyleForNextShape(style))
|
styles.applyValue(style, this.getStyleForNextShape(style))
|
||||||
|
@ -8370,16 +8374,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
async getSvg(
|
async getSvg(shapes: TLShapeId[] | TLShape[], opts = {} as Partial<TLSvgOptions>) {
|
||||||
shapes: TLShapeId[] | TLShape[],
|
|
||||||
opts = {} as Partial<{
|
|
||||||
scale: number
|
|
||||||
background: boolean
|
|
||||||
padding: number
|
|
||||||
darkMode?: boolean
|
|
||||||
preserveAspectRatio: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio']
|
|
||||||
}>
|
|
||||||
) {
|
|
||||||
const ids =
|
const ids =
|
||||||
typeof shapes[0] === 'string'
|
typeof shapes[0] === 'string'
|
||||||
? (shapes as TLShapeId[])
|
? (shapes as TLShapeId[])
|
||||||
|
@ -8406,6 +8401,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
|
|
||||||
// --- Common bounding box of all shapes
|
// --- Common bounding box of all shapes
|
||||||
let bbox = null
|
let bbox = null
|
||||||
|
if (opts.bounds) {
|
||||||
|
bbox = opts.bounds
|
||||||
|
} else {
|
||||||
for (const { maskedPageBounds } of renderingShapes) {
|
for (const { maskedPageBounds } of renderingShapes) {
|
||||||
if (!maskedPageBounds) continue
|
if (!maskedPageBounds) continue
|
||||||
if (bbox) {
|
if (bbox) {
|
||||||
|
@ -8414,6 +8412,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
bbox = maskedPageBounds.clone()
|
bbox = maskedPageBounds.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// no unmasked shapes to export
|
// no unmasked shapes to export
|
||||||
if (!bbox) return
|
if (!bbox) return
|
||||||
|
|
|
@ -12,12 +12,10 @@ export class RootState extends StateNode {
|
||||||
case 'KeyZ': {
|
case 'KeyZ': {
|
||||||
if (!(info.shiftKey || info.ctrlKey)) {
|
if (!(info.shiftKey || info.ctrlKey)) {
|
||||||
const currentTool = this.getCurrent()
|
const currentTool = this.getCurrent()
|
||||||
if (currentTool && currentTool.getCurrent()?.id === 'idle') {
|
if (currentTool && currentTool.getCurrent()?.id === 'idle' && this.children!['zoom']) {
|
||||||
if (this.children!['zoom']) {
|
|
||||||
this.editor.setCurrentTool('zoom', { ...info, onInteractionEnd: currentTool.id })
|
this.editor.setCurrentTool('zoom', { ...info, onInteractionEnd: currentTool.id })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
|
import { Box2d } from '../../primitives/Box2d'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type RequiredKeys<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>
|
export type RequiredKeys<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>
|
||||||
/** @public */
|
/** @public */
|
||||||
export type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
|
export type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export type TLSvgOptions = {
|
||||||
|
bounds: Box2d
|
||||||
|
scale: number
|
||||||
|
background: boolean
|
||||||
|
padding: number
|
||||||
|
darkMode?: boolean
|
||||||
|
preserveAspectRatio: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio']
|
||||||
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ export function useDocumentEvents() {
|
||||||
if (
|
if (
|
||||||
e.altKey &&
|
e.altKey &&
|
||||||
// todo: When should we allow the alt key to be used? Perhaps states should declare which keys matter to them?
|
// todo: When should we allow the alt key to be used? Perhaps states should declare which keys matter to them?
|
||||||
(editor.isIn('zoom') || !editor.root.getPath().endsWith('.idle')) &&
|
(editor.isIn('zoom') || !editor.getPath().endsWith('.idle')) &&
|
||||||
!isFocusingInput()
|
!isFocusingInput()
|
||||||
) {
|
) {
|
||||||
// On windows the alt key opens the menu bar.
|
// On windows the alt key opens the menu bar.
|
||||||
|
|
|
@ -74,19 +74,24 @@ export interface BaseEditorComponents {
|
||||||
InFrontOfTheCanvas: TLInFrontOfTheCanvas
|
InFrontOfTheCanvas: TLInFrontOfTheCanvas
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
// These will always have defaults
|
||||||
export type TLEditorComponents = {
|
type ErrorComponents = {
|
||||||
[K in keyof BaseEditorComponents]: BaseEditorComponents[K] | null
|
|
||||||
} & {
|
|
||||||
ErrorFallback: TLErrorFallbackComponent
|
ErrorFallback: TLErrorFallbackComponent
|
||||||
ShapeErrorFallback: TLShapeErrorFallbackComponent
|
ShapeErrorFallback: TLShapeErrorFallbackComponent
|
||||||
ShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallbackComponent
|
ShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallbackComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditorComponentsContext = createContext({} as TLEditorComponents)
|
/** @public */
|
||||||
|
export type TLEditorComponents = Partial<
|
||||||
|
{
|
||||||
|
[K in keyof BaseEditorComponents]: BaseEditorComponents[K] | null
|
||||||
|
} & ErrorComponents
|
||||||
|
>
|
||||||
|
|
||||||
|
const EditorComponentsContext = createContext({} as TLEditorComponents & ErrorComponents)
|
||||||
|
|
||||||
type ComponentsContextProviderProps = {
|
type ComponentsContextProviderProps = {
|
||||||
overrides?: Partial<TLEditorComponents>
|
overrides?: TLEditorComponents
|
||||||
children: any
|
children: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +104,7 @@ export function EditorComponentsProvider({ overrides, children }: ComponentsCont
|
||||||
SvgDefs: DefaultSvgDefs,
|
SvgDefs: DefaultSvgDefs,
|
||||||
Brush: DefaultBrush,
|
Brush: DefaultBrush,
|
||||||
ZoomBrush: DefaultBrush,
|
ZoomBrush: DefaultBrush,
|
||||||
|
ScreenshotBrush: DefaultBrush,
|
||||||
CollaboratorBrush: DefaultBrush,
|
CollaboratorBrush: DefaultBrush,
|
||||||
Cursor: DefaultCursor,
|
Cursor: DefaultCursor,
|
||||||
CollaboratorCursor: DefaultCursor,
|
CollaboratorCursor: DefaultCursor,
|
||||||
|
|
|
@ -2,6 +2,9 @@ import { Box2dModel } from '@tldraw/tlschema'
|
||||||
import { Vec2d, VecLike } from './Vec2d'
|
import { Vec2d, VecLike } from './Vec2d'
|
||||||
import { PI, PI2, toPrecision } from './utils'
|
import { PI, PI2, toPrecision } from './utils'
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export type BoxLike = Box2dModel | Box2d
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type SelectionEdge = 'top' | 'right' | 'bottom' | 'left'
|
export type SelectionEdge = 'top' | 'right' | 'bottom' | 'left'
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,7 @@ import { TLShapeUtilCanvasSvgDef } from '@tldraw/editor';
|
||||||
import { TLShapeUtilFlag } from '@tldraw/editor';
|
import { TLShapeUtilFlag } from '@tldraw/editor';
|
||||||
import { TLStore } from '@tldraw/editor';
|
import { TLStore } from '@tldraw/editor';
|
||||||
import { TLStoreWithStatus } from '@tldraw/editor';
|
import { TLStoreWithStatus } from '@tldraw/editor';
|
||||||
|
import { TLSvgOptions } from '@tldraw/editor';
|
||||||
import { TLTextShape } from '@tldraw/editor';
|
import { TLTextShape } from '@tldraw/editor';
|
||||||
import { TLUnknownShape } from '@tldraw/editor';
|
import { TLUnknownShape } from '@tldraw/editor';
|
||||||
import { TLVideoShape } from '@tldraw/editor';
|
import { TLVideoShape } from '@tldraw/editor';
|
||||||
|
@ -286,6 +287,9 @@ export const ContextMenu: ({ children }: {
|
||||||
children: any;
|
children: any;
|
||||||
}) => JSX.Element;
|
}) => JSX.Element;
|
||||||
|
|
||||||
|
// @public
|
||||||
|
export function copyAs(editor: Editor, ids: TLShapeId[], format?: TLCopyType, opts?: Partial<TLSvgOptions>): Promise<void>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const DEFAULT_ACCEPTED_IMG_TYPE: string[];
|
export const DEFAULT_ACCEPTED_IMG_TYPE: string[];
|
||||||
|
|
||||||
|
@ -461,8 +465,11 @@ export type EventsProviderProps = {
|
||||||
children: any;
|
children: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @public
|
||||||
|
export function exportAs(editor: Editor, ids: TLShapeId[], format?: TLExportType, opts?: Partial<TLSvgOptions>): Promise<void>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function findMenuItem(menu: TLUiMenuSchema, path: string[]): TLUiMenuChild;
|
export function findMenuItem(menu: TLUiMenuSchema, path: string[]): TLUiCustomMenuItem | TLUiMenuGroup | TLUiMenuItem | TLUiSubMenu<string>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
function Footer({ className, children }: {
|
function Footer({ className, children }: {
|
||||||
|
@ -906,7 +913,7 @@ export function menuCustom(id: string, opts?: Partial<{
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function menuGroup(id: string, ...children: (false | null | TLUiMenuChild)[]): null | TLUiMenuGroup;
|
export function menuGroup(id: string, ...children: (false | TLUiMenuChild)[]): null | TLUiMenuGroup;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function menuItem(actionItem: TLUiActionItem | TLUiToolItem, opts?: Partial<{
|
export function menuItem(actionItem: TLUiActionItem | TLUiToolItem, opts?: Partial<{
|
||||||
|
@ -915,7 +922,7 @@ export function menuItem(actionItem: TLUiActionItem | TLUiToolItem, opts?: Parti
|
||||||
}>): TLUiMenuItem;
|
}>): TLUiMenuItem;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function menuSubmenu(id: string, label: TLUiTranslationKey, ...children: (false | null | TLUiMenuChild)[]): null | TLUiSubMenu;
|
export function menuSubmenu(id: string, label: Exclude<string, TLUiTranslationKey> | TLUiTranslationKey, ...children: (false | TLUiMenuChild)[]): null | TLUiSubMenu;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export class NoteShapeTool extends StateNode {
|
export class NoteShapeTool extends StateNode {
|
||||||
|
@ -1020,7 +1027,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
||||||
export function OfflineIndicator(): JSX.Element;
|
export function OfflineIndicator(): JSX.Element;
|
||||||
|
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
export function parseAndLoadDocument(editor: Editor, document: string, msg: (id: TLUiTranslationKey) => string, addToast: TLUiToastsContextType['addToast'], onV1FileLoad?: () => void, forceDarkMode?: boolean): Promise<void>;
|
export function parseAndLoadDocument(editor: Editor, document: string, msg: (id: Exclude<string, TLUiTranslationKey> | TLUiTranslationKey) => string, addToast: TLUiToastsContextType['addToast'], onV1FileLoad?: () => void, forceDarkMode?: boolean): Promise<void>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function parseTldrawJsonFile({ json, schema, }: {
|
export function parseTldrawJsonFile({ json, schema, }: {
|
||||||
|
@ -1081,7 +1088,7 @@ function SubContent({ alignOffset, sideOffset, children, }: {
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
function SubTrigger({ label, 'data-testid': testId, 'data-direction': dataDirection, }: {
|
function SubTrigger({ label, 'data-testid': testId, 'data-direction': dataDirection, }: {
|
||||||
label: TLUiTranslationKey;
|
label: Exclude<string, TLUiTranslationKey> | TLUiTranslationKey;
|
||||||
'data-testid'?: string;
|
'data-testid'?: string;
|
||||||
'data-direction'?: 'left' | 'right';
|
'data-direction'?: 'left' | 'right';
|
||||||
}): JSX.Element;
|
}): JSX.Element;
|
||||||
|
@ -1211,17 +1218,7 @@ function Title({ className, children }: {
|
||||||
}): JSX.Element;
|
}): JSX.Element;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function Tldraw(props: TldrawEditorBaseProps & ({
|
export function Tldraw(props: TldrawProps): JSX.Element;
|
||||||
store: TLStore | TLStoreWithStatus;
|
|
||||||
} | {
|
|
||||||
store?: undefined;
|
|
||||||
persistenceKey?: string;
|
|
||||||
sessionId?: string;
|
|
||||||
defaultName?: string;
|
|
||||||
snapshot?: StoreSnapshot<TLRecord>;
|
|
||||||
}) & TldrawUiProps & Partial<TLExternalContentProps> & {
|
|
||||||
assetUrls?: RecursivePartial<TLEditorAssetUrls>;
|
|
||||||
}): JSX.Element;
|
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const TLDRAW_FILE_EXTENSION: ".tldr";
|
export const TLDRAW_FILE_EXTENSION: ".tldr";
|
||||||
|
@ -1257,6 +1254,17 @@ export const TldrawHandles: TLHandlesComponent;
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const TldrawHoveredShapeIndicator: TLHoveredShapeIndicatorComponent;
|
export const TldrawHoveredShapeIndicator: TLHoveredShapeIndicatorComponent;
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export type TldrawProps = TldrawEditorBaseProps & ({
|
||||||
|
store: TLStore | TLStoreWithStatus;
|
||||||
|
} | {
|
||||||
|
store?: undefined;
|
||||||
|
persistenceKey?: string;
|
||||||
|
sessionId?: string;
|
||||||
|
defaultName?: string;
|
||||||
|
snapshot?: StoreSnapshot<TLRecord>;
|
||||||
|
}) & TldrawUiProps & Partial<TLExternalContentProps>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const TldrawScribble: TLScribbleComponent;
|
export const TldrawScribble: TLScribbleComponent;
|
||||||
|
|
||||||
|
@ -1271,6 +1279,7 @@ export const TldrawUi: React_2.NamedExoticComponent<TldrawUiProps>;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export interface TldrawUiBaseProps {
|
export interface TldrawUiBaseProps {
|
||||||
|
assetUrls?: TLUiAssetUrlOverrides;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
hideUi?: boolean;
|
hideUi?: boolean;
|
||||||
renderDebugMenuItems?: () => React_2.ReactNode;
|
renderDebugMenuItems?: () => React_2.ReactNode;
|
||||||
|
@ -1295,27 +1304,27 @@ export interface TldrawUiContextProviderProps {
|
||||||
export type TldrawUiProps = TldrawUiBaseProps & TldrawUiContextProviderProps;
|
export type TldrawUiProps = TldrawUiBaseProps & TldrawUiContextProviderProps;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface TLUiActionItem {
|
export interface TLUiActionItem<TransationKey extends string = string, IconType extends string = string> {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
checkbox?: boolean;
|
checkbox?: boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
contextMenuLabel?: TLUiTranslationKey;
|
contextMenuLabel?: TransationKey;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
icon?: TLUiIconType;
|
icon?: IconType;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
id: string;
|
id: string;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
kbd?: string;
|
kbd?: string;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
label?: TLUiTranslationKey;
|
label?: TransationKey;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
menuLabel?: TLUiTranslationKey;
|
menuLabel?: TransationKey;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
onSelect: (source: TLUiEventSource) => Promise<void> | void;
|
onSelect: (source: TLUiEventSource) => Promise<void> | void;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
readonlyOk: boolean;
|
readonlyOk: boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
shortcutsLabel?: TLUiTranslationKey;
|
shortcutsLabel?: TransationKey;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
@ -1326,14 +1335,17 @@ export type TLUiActionsContextType = Record<string, TLUiActionItem>;
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLUiActionsMenuSchemaContextType = TLUiMenuSchema;
|
export type TLUiActionsMenuSchemaContextType = TLUiMenuSchema;
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export type TLUiAssetUrlOverrides = RecursivePartial<TLUiAssetUrls>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface TLUiButtonProps extends React_3.HTMLAttributes<HTMLButtonElement> {
|
export interface TLUiButtonProps extends React_3.HTMLAttributes<HTMLButtonElement> {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
icon?: TLUiIconType;
|
icon?: Exclude<string, TLUiIconType> | TLUiIconType;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
iconLeft?: TLUiIconType;
|
iconLeft?: Exclude<string, TLUiIconType> | TLUiIconType;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
invertIcon?: boolean;
|
invertIcon?: boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -1341,7 +1353,7 @@ export interface TLUiButtonProps extends React_3.HTMLAttributes<HTMLButtonElemen
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
kbd?: string;
|
kbd?: string;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
label?: TLUiTranslationKey;
|
label?: Exclude<string, TLUiTranslationKey> | TLUiTranslationKey;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -1419,7 +1431,7 @@ export interface TLUiIconProps extends React.HTMLProps<HTMLDivElement> {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
crossOrigin?: 'anonymous' | 'use-credentials';
|
crossOrigin?: 'anonymous' | 'use-credentials';
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
icon: TLUiIconType;
|
icon: Exclude<string, TLUiIconType> | TLUiIconType;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
invertIcon?: boolean;
|
invertIcon?: boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -1444,11 +1456,11 @@ export interface TLUiInputProps {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
icon?: TLUiIconType;
|
icon?: Exclude<string, TLUiIconType> | TLUiIconType;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
iconLeft?: TLUiIconType;
|
iconLeft?: Exclude<string, TLUiIconType> | TLUiIconType;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
label?: TLUiTranslationKey;
|
label?: Exclude<string, TLUiTranslationKey> | TLUiTranslationKey;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
onBlur?: (value: string) => void;
|
onBlur?: (value: string) => void;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -1477,7 +1489,7 @@ export type TLUiKeyboardShortcutsSchemaProviderProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLUiMenuChild = TLUiCustomMenuItem | TLUiMenuGroup | TLUiMenuItem | TLUiSubMenu;
|
export type TLUiMenuChild<TranslationKey extends string = string> = null | TLUiCustomMenuItem | TLUiMenuGroup | TLUiMenuItem | TLUiSubMenu<TranslationKey>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLUiMenuGroup = {
|
export type TLUiMenuGroup = {
|
||||||
|
@ -1518,32 +1530,23 @@ export type TLUiMenuSchemaProviderProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface TLUiOverrides {
|
export type TLUiOverrides = Partial<{
|
||||||
// (undocumented)
|
actionsMenu: WithDefaultHelpers<NonNullable<ActionsMenuSchemaProviderProps['overrides']>>;
|
||||||
actions?: WithDefaultHelpers<NonNullable<ActionsProviderProps['overrides']>>;
|
actions: WithDefaultHelpers<NonNullable<ActionsProviderProps['overrides']>>;
|
||||||
// (undocumented)
|
contextMenu: WithDefaultHelpers<NonNullable<TLUiContextMenuSchemaProviderProps['overrides']>>;
|
||||||
actionsMenu?: WithDefaultHelpers<NonNullable<ActionsMenuSchemaProviderProps['overrides']>>;
|
helpMenu: WithDefaultHelpers<NonNullable<TLUiHelpMenuSchemaProviderProps['overrides']>>;
|
||||||
// (undocumented)
|
menu: WithDefaultHelpers<NonNullable<TLUiMenuSchemaProviderProps['overrides']>>;
|
||||||
contextMenu?: WithDefaultHelpers<NonNullable<TLUiContextMenuSchemaProviderProps['overrides']>>;
|
toolbar: WithDefaultHelpers<NonNullable<TLUiToolbarSchemaProviderProps['overrides']>>;
|
||||||
// (undocumented)
|
keyboardShortcutsMenu: WithDefaultHelpers<NonNullable<TLUiKeyboardShortcutsSchemaProviderProps['overrides']>>;
|
||||||
helpMenu?: WithDefaultHelpers<NonNullable<TLUiHelpMenuSchemaProviderProps['overrides']>>;
|
tools: WithDefaultHelpers<NonNullable<TLUiToolsProviderProps['overrides']>>;
|
||||||
// (undocumented)
|
translations: TLUiTranslationProviderProps['overrides'];
|
||||||
keyboardShortcutsMenu?: WithDefaultHelpers<NonNullable<TLUiKeyboardShortcutsSchemaProviderProps['overrides']>>;
|
}>;
|
||||||
// (undocumented)
|
|
||||||
menu?: WithDefaultHelpers<NonNullable<TLUiMenuSchemaProviderProps['overrides']>>;
|
|
||||||
// (undocumented)
|
|
||||||
toolbar?: WithDefaultHelpers<NonNullable<TLUiToolbarSchemaProviderProps['overrides']>>;
|
|
||||||
// (undocumented)
|
|
||||||
tools?: WithDefaultHelpers<NonNullable<TLUiToolsProviderProps['overrides']>>;
|
|
||||||
// (undocumented)
|
|
||||||
translations?: TLUiTranslationProviderProps['overrides'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLUiSubMenu = {
|
export type TLUiSubMenu<TranslationKey extends string = string> = {
|
||||||
id: string;
|
id: string;
|
||||||
type: 'submenu';
|
type: 'submenu';
|
||||||
label: TLUiTranslationKey;
|
label: TranslationKey;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
readonlyOk: boolean;
|
readonlyOk: boolean;
|
||||||
children: TLUiMenuChild[];
|
children: TLUiMenuChild[];
|
||||||
|
@ -1599,15 +1602,15 @@ export type TLUiToolbarItem = {
|
||||||
export type TLUiToolbarSchemaContextType = TLUiToolbarItem[];
|
export type TLUiToolbarSchemaContextType = TLUiToolbarItem[];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface TLUiToolItem {
|
export interface TLUiToolItem<TranslationKey extends string = string, IconType extends string = string> {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
icon: TLUiIconType;
|
icon: IconType;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
id: string;
|
id: string;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
kbd?: string;
|
kbd?: string;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
label: TLUiTranslationKey;
|
label: TranslationKey;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
meta?: {
|
meta?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
@ -1617,7 +1620,7 @@ export interface TLUiToolItem {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
readonlyOk: boolean;
|
readonlyOk: boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
shortcutsLabel?: TLUiTranslationKey;
|
shortcutsLabel?: TranslationKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
@ -1681,7 +1684,7 @@ export function useCanUndo(): boolean;
|
||||||
export function useContextMenuSchema(): TLUiMenuSchema;
|
export function useContextMenuSchema(): TLUiMenuSchema;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useCopyAs(): (ids?: TLShapeId[], format?: TLCopyType) => void;
|
export function useCopyAs(): (ids: TLShapeId[], format?: TLCopyType) => void;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useDefaultHelpers(): {
|
export function useDefaultHelpers(): {
|
||||||
|
@ -1696,7 +1699,7 @@ export function useDefaultHelpers(): {
|
||||||
clearDialogs: () => void;
|
clearDialogs: () => void;
|
||||||
removeDialog: (id: string) => string;
|
removeDialog: (id: string) => string;
|
||||||
updateDialog: (id: string, newDialogData: Partial<TLUiDialog>) => string;
|
updateDialog: (id: string, newDialogData: Partial<TLUiDialog>) => string;
|
||||||
msg: (id: TLUiTranslationKey) => string;
|
msg: (id: string) => string;
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1704,7 +1707,7 @@ export function useDefaultHelpers(): {
|
||||||
export function useDialogs(): TLUiDialogsContextType;
|
export function useDialogs(): TLUiDialogsContextType;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useExportAs(): (ids?: TLShapeId[], format?: TLExportType) => Promise<void>;
|
export function useExportAs(): (ids: TLShapeId[], format?: TLExportType) => void;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useHelpMenuSchema(): TLUiMenuSchema;
|
export function useHelpMenuSchema(): TLUiMenuSchema;
|
||||||
|
@ -1747,7 +1750,7 @@ export function useToolbarSchema(): TLUiToolbarSchemaContextType;
|
||||||
export function useTools(): TLUiToolsContextType;
|
export function useTools(): TLUiToolsContextType;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function useTranslation(): (id: TLUiTranslationKey) => string;
|
export function useTranslation(): (id: Exclude<string, TLUiTranslationKey> | string) => string;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useUiEvents(): TLUiEventContextType;
|
export function useUiEvents(): TLUiEventContextType;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
// eslint-disable-next-line local/no-export-star
|
// eslint-disable-next-line local/no-export-star
|
||||||
export * from '@tldraw/editor'
|
export * from '@tldraw/editor'
|
||||||
export { Tldraw } from './lib/Tldraw'
|
export { Tldraw, type TldrawProps } from './lib/Tldraw'
|
||||||
export { TldrawCropHandles, type TldrawCropHandlesProps } from './lib/canvas/TldrawCropHandles'
|
export { TldrawCropHandles, type TldrawCropHandlesProps } from './lib/canvas/TldrawCropHandles'
|
||||||
export { TldrawHandles } from './lib/canvas/TldrawHandles'
|
export { TldrawHandles } from './lib/canvas/TldrawHandles'
|
||||||
export { TldrawHoveredShapeIndicator } from './lib/canvas/TldrawHoveredShapeIndicator'
|
export { TldrawHoveredShapeIndicator } from './lib/canvas/TldrawHoveredShapeIndicator'
|
||||||
|
@ -43,7 +43,7 @@ export {
|
||||||
TldrawUiContextProvider,
|
TldrawUiContextProvider,
|
||||||
type TldrawUiContextProviderProps,
|
type TldrawUiContextProviderProps,
|
||||||
} from './lib/ui/TldrawUiContextProvider'
|
} from './lib/ui/TldrawUiContextProvider'
|
||||||
export { setDefaultUiAssetUrls } from './lib/ui/assetUrls'
|
export { setDefaultUiAssetUrls, type TLUiAssetUrlOverrides } from './lib/ui/assetUrls'
|
||||||
export { ContextMenu, type TLUiContextMenuProps } from './lib/ui/components/ContextMenu'
|
export { ContextMenu, type TLUiContextMenuProps } from './lib/ui/components/ContextMenu'
|
||||||
export { OfflineIndicator } from './lib/ui/components/OfflineIndicator/OfflineIndicator'
|
export { OfflineIndicator } from './lib/ui/components/OfflineIndicator/OfflineIndicator'
|
||||||
export { Spinner } from './lib/ui/components/Spinner'
|
export { Spinner } from './lib/ui/components/Spinner'
|
||||||
|
@ -142,16 +142,22 @@ export {
|
||||||
} from './lib/ui/hooks/useTranslation/useTranslation'
|
} from './lib/ui/hooks/useTranslation/useTranslation'
|
||||||
export { type TLUiIconType } from './lib/ui/icon-types'
|
export { type TLUiIconType } from './lib/ui/icon-types'
|
||||||
export { useDefaultHelpers, type TLUiOverrides } from './lib/ui/overrides'
|
export { useDefaultHelpers, type TLUiOverrides } from './lib/ui/overrides'
|
||||||
export { setDefaultEditorAssetUrls } from './lib/utils/assetUrls'
|
|
||||||
export {
|
export {
|
||||||
DEFAULT_ACCEPTED_IMG_TYPE,
|
DEFAULT_ACCEPTED_IMG_TYPE,
|
||||||
DEFAULT_ACCEPTED_VID_TYPE,
|
DEFAULT_ACCEPTED_VID_TYPE,
|
||||||
containBoxSize,
|
containBoxSize,
|
||||||
getResizedImageDataUrl,
|
getResizedImageDataUrl,
|
||||||
isGifAnimated,
|
isGifAnimated,
|
||||||
} from './lib/utils/assets'
|
} from './lib/utils/assets/assets'
|
||||||
export { buildFromV1Document, type LegacyTldrawDocument } from './lib/utils/buildFromV1Document'
|
export { getEmbedInfo } from './lib/utils/embeds/embeds'
|
||||||
export { getEmbedInfo } from './lib/utils/embeds'
|
export { copyAs } from './lib/utils/export/copyAs'
|
||||||
|
export { exportAs } from './lib/utils/export/exportAs'
|
||||||
|
export { setDefaultEditorAssetUrls } from './lib/utils/static-assets/assetUrls'
|
||||||
|
export { truncateStringWithEllipsis } from './lib/utils/text/text'
|
||||||
|
export {
|
||||||
|
buildFromV1Document,
|
||||||
|
type LegacyTldrawDocument,
|
||||||
|
} from './lib/utils/tldr/buildFromV1Document'
|
||||||
export {
|
export {
|
||||||
TLDRAW_FILE_EXTENSION,
|
TLDRAW_FILE_EXTENSION,
|
||||||
parseAndLoadDocument,
|
parseAndLoadDocument,
|
||||||
|
@ -159,8 +165,7 @@ export {
|
||||||
serializeTldrawJson,
|
serializeTldrawJson,
|
||||||
serializeTldrawJsonBlob,
|
serializeTldrawJsonBlob,
|
||||||
type TldrawFile,
|
type TldrawFile,
|
||||||
} from './lib/utils/file'
|
} from './lib/utils/tldr/file'
|
||||||
export { truncateStringWithEllipsis } from './lib/utils/text'
|
|
||||||
export { Dialog, DropdownMenu }
|
export { Dialog, DropdownMenu }
|
||||||
import * as Dialog from './lib/ui/components/primitives/Dialog'
|
import * as Dialog from './lib/ui/components/primitives/Dialog'
|
||||||
import * as DropdownMenu from './lib/ui/components/primitives/DropdownMenu'
|
import * as DropdownMenu from './lib/ui/components/primitives/DropdownMenu'
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {
|
||||||
Editor,
|
Editor,
|
||||||
ErrorScreen,
|
ErrorScreen,
|
||||||
LoadingScreen,
|
LoadingScreen,
|
||||||
RecursivePartial,
|
|
||||||
StoreSnapshot,
|
StoreSnapshot,
|
||||||
TLOnMountHandler,
|
TLOnMountHandler,
|
||||||
TLRecord,
|
TLRecord,
|
||||||
|
@ -31,12 +30,11 @@ import { registerDefaultSideEffects } from './defaultSideEffects'
|
||||||
import { defaultTools } from './defaultTools'
|
import { defaultTools } from './defaultTools'
|
||||||
import { TldrawUi, TldrawUiProps } from './ui/TldrawUi'
|
import { TldrawUi, TldrawUiProps } from './ui/TldrawUi'
|
||||||
import { ContextMenu } from './ui/components/ContextMenu'
|
import { ContextMenu } from './ui/components/ContextMenu'
|
||||||
import { TLEditorAssetUrls, useDefaultEditorAssetsWithOverrides } from './utils/assetUrls'
|
import { usePreloadAssets } from './ui/hooks/usePreloadAssets'
|
||||||
import { usePreloadAssets } from './utils/usePreloadAssets'
|
import { useDefaultEditorAssetsWithOverrides } from './utils/static-assets/assetUrls'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export function Tldraw(
|
export type TldrawProps = TldrawEditorBaseProps &
|
||||||
props: TldrawEditorBaseProps &
|
|
||||||
(
|
(
|
||||||
| {
|
| {
|
||||||
store: TLStore | TLStoreWithStatus
|
store: TLStore | TLStoreWithStatus
|
||||||
|
@ -53,13 +51,10 @@ export function Tldraw(
|
||||||
}
|
}
|
||||||
) &
|
) &
|
||||||
TldrawUiProps &
|
TldrawUiProps &
|
||||||
Partial<TLExternalContentProps> & {
|
Partial<TLExternalContentProps>
|
||||||
/**
|
|
||||||
* Urls for the editor to find fonts and other assets.
|
/** @public */
|
||||||
*/
|
export function Tldraw(props: TldrawProps) {
|
||||||
assetUrls?: RecursivePartial<TLEditorAssetUrls>
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
maxImageDimension,
|
maxImageDimension,
|
||||||
|
|
|
@ -17,9 +17,9 @@ import {
|
||||||
getHashForString,
|
getHashForString,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from './shapes/shared/default-shape-constants'
|
import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from './shapes/shared/default-shape-constants'
|
||||||
import { containBoxSize, getResizedImageDataUrl, isGifAnimated } from './utils/assets'
|
import { containBoxSize, getResizedImageDataUrl, isGifAnimated } from './utils/assets/assets'
|
||||||
import { getEmbedInfo } from './utils/embeds'
|
import { getEmbedInfo } from './utils/embeds/embeds'
|
||||||
import { cleanupText, isRightToLeftLanguage, truncateStringWithEllipsis } from './utils/text'
|
import { cleanupText, isRightToLeftLanguage, truncateStringWithEllipsis } from './utils/text/text'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type TLExternalContentProps = {
|
export type TLExternalContentProps = {
|
||||||
|
|
|
@ -26,7 +26,7 @@ export class Idle extends StateNode {
|
||||||
) {
|
) {
|
||||||
this.editor.setCurrentTool('select')
|
this.editor.setCurrentTool('select')
|
||||||
this.editor.setEditingShape(onlySelectedShape.id)
|
this.editor.setEditingShape(onlySelectedShape.id)
|
||||||
this.editor.root.getCurrent()!.transition('editing_shape', {
|
this.editor.root.getCurrent()?.transition('editing_shape', {
|
||||||
...info,
|
...info,
|
||||||
target: 'shape',
|
target: 'shape',
|
||||||
shape: onlySelectedShape,
|
shape: onlySelectedShape,
|
||||||
|
|
|
@ -16,9 +16,9 @@ import {
|
||||||
stopEventPropagation,
|
stopEventPropagation,
|
||||||
toDomPrecision,
|
toDomPrecision,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import { getRotatedBoxShadow } from '../../utils/rotated-box-shadow'
|
import { truncateStringWithEllipsis } from '../../utils/text/text'
|
||||||
import { truncateStringWithEllipsis } from '../../utils/text'
|
|
||||||
import { HyperlinkButton } from '../shared/HyperlinkButton'
|
import { HyperlinkButton } from '../shared/HyperlinkButton'
|
||||||
|
import { getRotatedBoxShadow } from '../shared/rotated-box-shadow'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
|
export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
|
||||||
|
|
|
@ -15,9 +15,9 @@ import {
|
||||||
useValue,
|
useValue,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { getEmbedInfo, getEmbedInfoUnsafely } from '../../utils/embeds'
|
import { getEmbedInfo, getEmbedInfoUnsafely } from '../../utils/embeds/embeds'
|
||||||
import { getRotatedBoxShadow } from '../../utils/rotated-box-shadow'
|
|
||||||
import { resizeBox } from '../shared/resizeBox'
|
import { resizeBox } from '../shared/resizeBox'
|
||||||
|
import { getRotatedBoxShadow } from '../shared/rotated-box-shadow'
|
||||||
|
|
||||||
const getSandboxPermissions = (permissions: TLEmbedShapePermissions) => {
|
const getSandboxPermissions = (permissions: TLEmbedShapePermissions) => {
|
||||||
return Object.entries(permissions)
|
return Object.entries(permissions)
|
||||||
|
|
|
@ -23,7 +23,7 @@ export class Idle extends StateNode {
|
||||||
) {
|
) {
|
||||||
this.editor.setCurrentTool('select')
|
this.editor.setCurrentTool('select')
|
||||||
this.editor.setEditingShape(onlySelectedShape.id)
|
this.editor.setEditingShape(onlySelectedShape.id)
|
||||||
this.editor.root.getCurrent()!.transition('editing_shape', {
|
this.editor.root.getCurrent()?.transition('editing_shape', {
|
||||||
...info,
|
...info,
|
||||||
target: 'shape',
|
target: 'shape',
|
||||||
shape: onlySelectedShape,
|
shape: onlySelectedShape,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Vec2d } from '@tldraw/editor'
|
import { Vec2d } from '@tldraw/editor'
|
||||||
|
|
||||||
export const ROTATING_BOX_SHADOWS = [
|
const ROTATING_BOX_SHADOWS = [
|
||||||
{
|
{
|
||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
offsetY: 2,
|
offsetY: 2,
|
|
@ -32,7 +32,7 @@ export class Idle extends StateNode {
|
||||||
) {
|
) {
|
||||||
this.editor.setCurrentTool('select')
|
this.editor.setCurrentTool('select')
|
||||||
this.editor.setEditingShape(onlySelectedShape.id)
|
this.editor.setEditingShape(onlySelectedShape.id)
|
||||||
this.editor.root.getCurrent()!.transition('editing_shape', {
|
this.editor.root.getCurrent()?.transition('editing_shape', {
|
||||||
...info,
|
...info,
|
||||||
target: 'shape',
|
target: 'shape',
|
||||||
shape: onlySelectedShape,
|
shape: onlySelectedShape,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { StateNode } from '@tldraw/editor'
|
import { StateNode } from '@tldraw/editor'
|
||||||
import { Erasing } from './children/Erasing'
|
import { Erasing } from './childStates/Erasing'
|
||||||
import { Idle } from './children/Idle'
|
import { Idle } from './childStates/Idle'
|
||||||
import { Pointing } from './children/Pointing'
|
import { Pointing } from './childStates/Pointing'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class EraserTool extends StateNode {
|
export class EraserTool extends StateNode {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { EASINGS, StateNode, TLClickEvent } from '@tldraw/editor'
|
import { EASINGS, StateNode, TLClickEvent } from '@tldraw/editor'
|
||||||
import { Dragging } from './children/Dragging'
|
import { Dragging } from './childStates/Dragging'
|
||||||
import { Idle } from './children/Idle'
|
import { Idle } from './childStates/Idle'
|
||||||
import { Pointing } from './children/Pointing'
|
import { Pointing } from './childStates/Pointing'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class HandTool extends StateNode {
|
export class HandTool extends StateNode {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { StateNode } from '@tldraw/editor'
|
import { StateNode } from '@tldraw/editor'
|
||||||
import { Idle } from './children/Idle'
|
import { Idle } from './childStates/Idle'
|
||||||
import { Lasering } from './children/Lasering'
|
import { Lasering } from './childStates/Lasering'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class LaserTool extends StateNode {
|
export class LaserTool extends StateNode {
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import { StateNode } from '@tldraw/editor'
|
import { StateNode } from '@tldraw/editor'
|
||||||
import { Brushing } from './children/Brushing'
|
import { Brushing } from './childStates/Brushing'
|
||||||
import { Crop } from './children/Crop/Crop'
|
import { Crop } from './childStates/Crop/Crop'
|
||||||
import { Cropping } from './children/Cropping'
|
import { Cropping } from './childStates/Cropping'
|
||||||
import { DraggingHandle } from './children/DraggingHandle'
|
import { DraggingHandle } from './childStates/DraggingHandle'
|
||||||
import { EditingShape } from './children/EditingShape'
|
import { EditingShape } from './childStates/EditingShape'
|
||||||
import { Idle } from './children/Idle'
|
import { Idle } from './childStates/Idle'
|
||||||
import { PointingCanvas } from './children/PointingCanvas'
|
import { PointingCanvas } from './childStates/PointingCanvas'
|
||||||
import { PointingCropHandle } from './children/PointingCropHandle'
|
import { PointingCropHandle } from './childStates/PointingCropHandle'
|
||||||
import { PointingHandle } from './children/PointingHandle'
|
import { PointingHandle } from './childStates/PointingHandle'
|
||||||
import { PointingResizeHandle } from './children/PointingResizeHandle'
|
import { PointingResizeHandle } from './childStates/PointingResizeHandle'
|
||||||
import { PointingRotateHandle } from './children/PointingRotateHandle'
|
import { PointingRotateHandle } from './childStates/PointingRotateHandle'
|
||||||
import { PointingSelection } from './children/PointingSelection'
|
import { PointingSelection } from './childStates/PointingSelection'
|
||||||
import { PointingShape } from './children/PointingShape'
|
import { PointingShape } from './childStates/PointingShape'
|
||||||
import { Resizing } from './children/Resizing'
|
import { Resizing } from './childStates/Resizing'
|
||||||
import { Rotating } from './children/Rotating'
|
import { Rotating } from './childStates/Rotating'
|
||||||
import { ScribbleBrushing } from './children/ScribbleBrushing'
|
import { ScribbleBrushing } from './childStates/ScribbleBrushing'
|
||||||
import { Translating } from './children/Translating'
|
import { Translating } from './childStates/Translating'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class SelectTool extends StateNode {
|
export class SelectTool extends StateNode {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { StateNode, TLInterruptEvent, TLKeyboardEvent, TLPointerEventInfo } from '@tldraw/editor'
|
import { StateNode, TLInterruptEvent, TLKeyboardEvent, TLPointerEventInfo } from '@tldraw/editor'
|
||||||
import { Idle } from './children/Idle'
|
import { Idle } from './childStates/Idle'
|
||||||
import { Pointing } from './children/Pointing'
|
import { Pointing } from './childStates/Pointing'
|
||||||
import { ZoomBrushing } from './children/ZoomBrushing'
|
import { ZoomBrushing } from './childStates/ZoomBrushing'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class ZoomTool extends StateNode {
|
export class ZoomTool extends StateNode {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useEditor, useValue } from '@tldraw/editor'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import React, { ReactNode } from 'react'
|
import React, { ReactNode } from 'react'
|
||||||
import { TldrawUiContextProvider, TldrawUiContextProviderProps } from './TldrawUiContextProvider'
|
import { TldrawUiContextProvider, TldrawUiContextProviderProps } from './TldrawUiContextProvider'
|
||||||
|
import { TLUiAssetUrlOverrides } from './assetUrls'
|
||||||
import { BackToContent } from './components/BackToContent'
|
import { BackToContent } from './components/BackToContent'
|
||||||
import { DebugPanel } from './components/DebugPanel'
|
import { DebugPanel } from './components/DebugPanel'
|
||||||
import { Dialogs } from './components/Dialogs'
|
import { Dialogs } from './components/Dialogs'
|
||||||
|
@ -61,6 +62,9 @@ export interface TldrawUiBaseProps {
|
||||||
* Additional items to add to the debug menu (will be deprecated)
|
* Additional items to add to the debug menu (will be deprecated)
|
||||||
*/
|
*/
|
||||||
renderDebugMenuItems?: () => React.ReactNode
|
renderDebugMenuItems?: () => React.ReactNode
|
||||||
|
|
||||||
|
/** Asset URL override. */
|
||||||
|
assetUrls?: TLUiAssetUrlOverrides
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import { EMBED_DEFINITIONS, LANGUAGES, RecursivePartial } from '@tldraw/editor'
|
import { EMBED_DEFINITIONS, LANGUAGES, RecursivePartial } from '@tldraw/editor'
|
||||||
import { version } from '../ui/version'
|
import { version } from '../ui/version'
|
||||||
import { TLEditorAssetUrls, defaultEditorAssetUrls } from '../utils/assetUrls'
|
import { TLEditorAssetUrls, defaultEditorAssetUrls } from '../utils/static-assets/assetUrls'
|
||||||
import { TLUiIconType, iconTypes } from './icon-types'
|
import { TLUiIconType, iconTypes } from './icon-types'
|
||||||
|
|
||||||
export type TLUiAssetUrls = TLEditorAssetUrls & {
|
export type TLUiAssetUrls = TLEditorAssetUrls & {
|
||||||
icons: Record<TLUiIconType, string>
|
icons: Record<TLUiIconType | Exclude<string, TLUiIconType>, string>
|
||||||
translations: Record<(typeof LANGUAGES)[number]['locale'], string>
|
translations: Record<(typeof LANGUAGES)[number]['locale'], string>
|
||||||
embedIcons: Record<(typeof EMBED_DEFINITIONS)[number]['type'], string>
|
embedIcons: Record<(typeof EMBED_DEFINITIONS)[number]['type'], string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export type TLUiAssetUrlOverrides = RecursivePartial<TLUiAssetUrls>
|
||||||
|
|
||||||
export let defaultUiAssetUrls: TLUiAssetUrls = {
|
export let defaultUiAssetUrls: TLUiAssetUrls = {
|
||||||
...defaultEditorAssetUrls,
|
...defaultEditorAssetUrls,
|
||||||
icons: Object.fromEntries(
|
icons: Object.fromEntries(
|
||||||
|
|
|
@ -16,6 +16,7 @@ export const ActionsMenu = memo(function ActionsMenu() {
|
||||||
const isReadonly = useReadonly()
|
const isReadonly = useReadonly()
|
||||||
|
|
||||||
function getActionMenuItem(item: TLUiMenuChild) {
|
function getActionMenuItem(item: TLUiMenuChild) {
|
||||||
|
if (!item) return null
|
||||||
if (isReadonly && !item.readonlyOk) return null
|
if (isReadonly && !item.readonlyOk) return null
|
||||||
|
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
|
|
|
@ -7,7 +7,9 @@ import { useBreakpoint } from '../hooks/useBreakpoint'
|
||||||
import { useContextMenuSchema } from '../hooks/useContextMenuSchema'
|
import { useContextMenuSchema } from '../hooks/useContextMenuSchema'
|
||||||
import { useMenuIsOpen } from '../hooks/useMenuIsOpen'
|
import { useMenuIsOpen } from '../hooks/useMenuIsOpen'
|
||||||
import { useReadonly } from '../hooks/useReadonly'
|
import { useReadonly } from '../hooks/useReadonly'
|
||||||
|
import { TLUiTranslationKey } from '../hooks/useTranslation/TLUiTranslationKey'
|
||||||
import { useTranslation } from '../hooks/useTranslation/useTranslation'
|
import { useTranslation } from '../hooks/useTranslation/useTranslation'
|
||||||
|
import { TLUiIconType } from '../icon-types'
|
||||||
import { MoveToPageMenu } from './MoveToPageMenu'
|
import { MoveToPageMenu } from './MoveToPageMenu'
|
||||||
import { Button } from './primitives/Button'
|
import { Button } from './primitives/Button'
|
||||||
import { Icon } from './primitives/Icon'
|
import { Icon } from './primitives/Icon'
|
||||||
|
@ -116,6 +118,7 @@ const ContextMenuContent = forwardRef(function ContextMenuContent() {
|
||||||
parent: TLUiMenuChild | null,
|
parent: TLUiMenuChild | null,
|
||||||
depth: number
|
depth: number
|
||||||
) {
|
) {
|
||||||
|
if (!item) return null
|
||||||
if (isReadonly && !item.readonlyOk) return null
|
if (isReadonly && !item.readonlyOk) return null
|
||||||
|
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
|
@ -147,7 +150,7 @@ const ContextMenuContent = forwardRef(function ContextMenuContent() {
|
||||||
<_ContextMenu.SubTrigger dir="ltr" disabled={item.disabled} asChild>
|
<_ContextMenu.SubTrigger dir="ltr" disabled={item.disabled} asChild>
|
||||||
<Button
|
<Button
|
||||||
type="menu"
|
type="menu"
|
||||||
label={item.label}
|
label={item.label as TLUiTranslationKey}
|
||||||
data-testid={`menu-item.${item.id}`}
|
data-testid={`menu-item.${item.id}`}
|
||||||
icon="chevron-right"
|
icon="chevron-right"
|
||||||
/>
|
/>
|
||||||
|
@ -165,7 +168,7 @@ const ContextMenuContent = forwardRef(function ContextMenuContent() {
|
||||||
|
|
||||||
const { id, checkbox, contextMenuLabel, label, onSelect, kbd, icon } = item.actionItem
|
const { id, checkbox, contextMenuLabel, label, onSelect, kbd, icon } = item.actionItem
|
||||||
const labelToUse = contextMenuLabel ?? label
|
const labelToUse = contextMenuLabel ?? label
|
||||||
const labelStr = labelToUse ? msg(labelToUse) : undefined
|
const labelStr = labelToUse ? msg(labelToUse as TLUiTranslationKey) : undefined
|
||||||
|
|
||||||
if (checkbox) {
|
if (checkbox) {
|
||||||
// Item is in a checkbox group
|
// Item is in a checkbox group
|
||||||
|
@ -199,9 +202,9 @@ const ContextMenuContent = forwardRef(function ContextMenuContent() {
|
||||||
type="menu"
|
type="menu"
|
||||||
data-testid={`menu-item.${id}`}
|
data-testid={`menu-item.${id}`}
|
||||||
kbd={kbd}
|
kbd={kbd}
|
||||||
label={labelToUse}
|
label={labelToUse as TLUiTranslationKey}
|
||||||
disabled={item.disabled}
|
disabled={item.disabled}
|
||||||
iconLeft={breakpoint < 3 && depth > 2 ? icon : undefined}
|
iconLeft={breakpoint < 3 && depth > 2 ? (icon as TLUiIconType) : undefined}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (disableClicks) {
|
if (disableClicks) {
|
||||||
setDisableClicks(false)
|
setDisableClicks(false)
|
||||||
|
|
|
@ -65,7 +65,7 @@ export const DebugPanel = React.memo(function DebugPanel({
|
||||||
|
|
||||||
const CurrentState = track(function CurrentState() {
|
const CurrentState = track(function CurrentState() {
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
return <div className="tlui-debug-panel__current-state">{editor.root.getPath()}</div>
|
return <div className="tlui-debug-panel__current-state">{editor.getPath()}</div>
|
||||||
})
|
})
|
||||||
|
|
||||||
const ShapeCount = function ShapeCount() {
|
const ShapeCount = function ShapeCount() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { EMBED_DEFINITIONS, EmbedDefinition, track, useEditor } from '@tldraw/editor'
|
import { EMBED_DEFINITIONS, EmbedDefinition, track, useEditor } from '@tldraw/editor'
|
||||||
import { useRef, useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { TLEmbedResult, getEmbedInfo } from '../../utils/embeds'
|
import { TLEmbedResult, getEmbedInfo } from '../../utils/embeds/embeds'
|
||||||
import { useAssetUrls } from '../hooks/useAssetUrls'
|
import { useAssetUrls } from '../hooks/useAssetUrls'
|
||||||
import { TLUiDialogProps } from '../hooks/useDialogsProvider'
|
import { TLUiDialogProps } from '../hooks/useDialogsProvider'
|
||||||
import { untranslated, useTranslation } from '../hooks/useTranslation/useTranslation'
|
import { untranslated, useTranslation } from '../hooks/useTranslation/useTranslation'
|
||||||
|
|
|
@ -13,8 +13,8 @@ import { Button } from './primitives/Button'
|
||||||
import * as M from './primitives/DropdownMenu'
|
import * as M from './primitives/DropdownMenu'
|
||||||
|
|
||||||
interface HelpMenuLink {
|
interface HelpMenuLink {
|
||||||
label: TLUiTranslationKey
|
label: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
|
||||||
icon: TLUiIconType
|
icon: TLUiIconType | Exclude<string, TLUiIconType>
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ function HelpMenuContent() {
|
||||||
const isReadonly = useReadonly()
|
const isReadonly = useReadonly()
|
||||||
|
|
||||||
function getHelpMenuItem(item: TLUiMenuChild) {
|
function getHelpMenuItem(item: TLUiMenuChild) {
|
||||||
|
if (!item) return null
|
||||||
if (isReadonly && !item.readonlyOk) return null
|
if (isReadonly && !item.readonlyOk) return null
|
||||||
|
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ export const KeyboardShortcutsDialog = () => {
|
||||||
const shortcutsItems = useKeyboardShortcutsSchema()
|
const shortcutsItems = useKeyboardShortcutsSchema()
|
||||||
|
|
||||||
function getKeyboardShortcutItem(item: TLUiMenuChild) {
|
function getKeyboardShortcutItem(item: TLUiMenuChild) {
|
||||||
|
if (!item) return null
|
||||||
if (isReadonly && !item.readonlyOk) return null
|
if (isReadonly && !item.readonlyOk) return null
|
||||||
|
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
|
@ -23,7 +24,7 @@ export const KeyboardShortcutsDialog = () => {
|
||||||
</h2>
|
</h2>
|
||||||
<div className="tlui-shortcuts-dialog__group__content">
|
<div className="tlui-shortcuts-dialog__group__content">
|
||||||
{item.children
|
{item.children
|
||||||
.filter((item) => item.type === 'item' && item.actionItem.kbd)
|
.filter((item) => item && item.type === 'item' && item.actionItem.kbd)
|
||||||
.map(getKeyboardShortcutItem)}
|
.map(getKeyboardShortcutItem)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -45,6 +45,7 @@ function MenuContent() {
|
||||||
parent: TLUiMenuChild | null,
|
parent: TLUiMenuChild | null,
|
||||||
depth: number
|
depth: number
|
||||||
) {
|
) {
|
||||||
|
if (!item) return null
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'custom': {
|
case 'custom': {
|
||||||
if (isReadonly && !item.readonlyOk) return null
|
if (isReadonly && !item.readonlyOk) return null
|
||||||
|
|
|
@ -11,9 +11,9 @@ import { StyleValuesForUi } from './styles'
|
||||||
interface DoubleDropdownPickerProps<T extends string> {
|
interface DoubleDropdownPickerProps<T extends string> {
|
||||||
uiTypeA: string
|
uiTypeA: string
|
||||||
uiTypeB: string
|
uiTypeB: string
|
||||||
label: TLUiTranslationKey
|
label: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
|
||||||
labelA: TLUiTranslationKey
|
labelA: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
|
||||||
labelB: TLUiTranslationKey
|
labelB: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
|
||||||
itemsA: StyleValuesForUi<T>
|
itemsA: StyleValuesForUi<T>
|
||||||
itemsB: StyleValuesForUi<T>
|
itemsB: StyleValuesForUi<T>
|
||||||
styleA: StyleProp<T>
|
styleA: StyleProp<T>
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { StyleValuesForUi } from './styles'
|
||||||
|
|
||||||
interface DropdownPickerProps<T extends string> {
|
interface DropdownPickerProps<T extends string> {
|
||||||
id: string
|
id: string
|
||||||
label?: TLUiTranslationKey
|
label?: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
|
||||||
uiType: string
|
uiType: string
|
||||||
style: StyleProp<T>
|
style: StyleProp<T>
|
||||||
value: SharedStyle<T>
|
value: SharedStyle<T>
|
||||||
|
|
|
@ -11,10 +11,10 @@ import { Kbd } from './Kbd'
|
||||||
export interface TLUiButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
export interface TLUiButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||||
loading?: boolean // TODO: loading spinner
|
loading?: boolean // TODO: loading spinner
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
label?: TLUiTranslationKey
|
label?: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
|
||||||
icon?: TLUiIconType
|
icon?: TLUiIconType | Exclude<string, TLUiIconType>
|
||||||
spinner?: boolean
|
spinner?: boolean
|
||||||
iconLeft?: TLUiIconType
|
iconLeft?: TLUiIconType | Exclude<string, TLUiIconType>
|
||||||
smallIcon?: boolean
|
smallIcon?: boolean
|
||||||
kbd?: string
|
kbd?: string
|
||||||
isChecked?: boolean
|
isChecked?: boolean
|
||||||
|
|
|
@ -96,7 +96,7 @@ export function SubTrigger({
|
||||||
'data-testid': testId,
|
'data-testid': testId,
|
||||||
'data-direction': dataDirection,
|
'data-direction': dataDirection,
|
||||||
}: {
|
}: {
|
||||||
label: TLUiTranslationKey
|
label: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
|
||||||
'data-testid'?: string
|
'data-testid'?: string
|
||||||
'data-direction'?: 'left' | 'right'
|
'data-direction'?: 'left' | 'right'
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { TLUiIconType } from '../../icon-types'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface TLUiIconProps extends React.HTMLProps<HTMLDivElement> {
|
export interface TLUiIconProps extends React.HTMLProps<HTMLDivElement> {
|
||||||
icon: TLUiIconType
|
icon: TLUiIconType | Exclude<string, TLUiIconType>
|
||||||
small?: boolean
|
small?: boolean
|
||||||
color?: string
|
color?: string
|
||||||
children?: undefined
|
children?: undefined
|
||||||
|
@ -23,17 +23,21 @@ export const Icon = memo(function Icon({
|
||||||
...props
|
...props
|
||||||
}: TLUiIconProps) {
|
}: TLUiIconProps) {
|
||||||
const assetUrls = useAssetUrls()
|
const assetUrls = useAssetUrls()
|
||||||
const asset = assetUrls.icons[icon]
|
const asset = assetUrls.icons[icon as TLUiIconType] ?? assetUrls.icons['question-mark-circle']
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
if (!asset) {
|
||||||
|
console.error(`Icon not found: ${icon}. Add it to the assetUrls.icons object.`)
|
||||||
|
}
|
||||||
|
|
||||||
if (ref?.current) {
|
if (ref?.current) {
|
||||||
// HACK: Fix for <https://linear.app/tldraw/issue/TLD-1700/dragging-around-with-the-handtool-makes-lots-of-requests-for-icons>
|
// HACK: Fix for <https://linear.app/tldraw/issue/TLD-1700/dragging-around-with-the-handtool-makes-lots-of-requests-for-icons>
|
||||||
// It seems that passing `WebkitMask` to react will cause a render on each call, no idea why... but this appears to be the fix.
|
// It seems that passing `WebkitMask` to react will cause a render on each call, no idea why... but this appears to be the fix.
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
ref.current.style.webkitMask = `url(${asset}) center 100% / 100% no-repeat`
|
ref.current.style.webkitMask = `url(${asset}) center 100% / 100% no-repeat`
|
||||||
}
|
}
|
||||||
}, [ref, asset])
|
}, [ref, asset, icon])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -9,9 +9,9 @@ import { Icon } from './Icon'
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface TLUiInputProps {
|
export interface TLUiInputProps {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
label?: TLUiTranslationKey
|
label?: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
|
||||||
icon?: TLUiIconType
|
icon?: TLUiIconType | Exclude<string, TLUiIconType>
|
||||||
iconLeft?: TLUiIconType
|
iconLeft?: TLUiIconType | Exclude<string, TLUiIconType>
|
||||||
autofocus?: boolean
|
autofocus?: boolean
|
||||||
autoselect?: boolean
|
autoselect?: boolean
|
||||||
children?: any
|
children?: any
|
||||||
|
|
|
@ -13,7 +13,12 @@ import { TLUiToolItem } from './useTools'
|
||||||
import { TLUiTranslationKey } from './useTranslation/TLUiTranslationKey'
|
import { TLUiTranslationKey } from './useTranslation/TLUiTranslationKey'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type TLUiMenuChild = TLUiMenuItem | TLUiSubMenu | TLUiMenuGroup | TLUiCustomMenuItem
|
export type TLUiMenuChild<TranslationKey extends string = string> =
|
||||||
|
| TLUiMenuItem
|
||||||
|
| TLUiSubMenu<TranslationKey>
|
||||||
|
| TLUiMenuGroup
|
||||||
|
| TLUiCustomMenuItem
|
||||||
|
| null
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type TLUiCustomMenuItem = {
|
export type TLUiCustomMenuItem = {
|
||||||
|
@ -44,10 +49,10 @@ export type TLUiMenuGroup = {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type TLUiSubMenu = {
|
export type TLUiSubMenu<TranslationKey extends string = string> = {
|
||||||
id: string
|
id: string
|
||||||
type: 'submenu'
|
type: 'submenu'
|
||||||
label: TLUiTranslationKey
|
label: TranslationKey
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
readonlyOk: boolean
|
readonlyOk: boolean
|
||||||
children: TLUiMenuChild[]
|
children: TLUiMenuChild[]
|
||||||
|
@ -64,7 +69,7 @@ export function compactMenuItems<T>(arr: T[]): Exclude<T, null | false | undefin
|
||||||
/** @public */
|
/** @public */
|
||||||
export function menuGroup(
|
export function menuGroup(
|
||||||
id: string,
|
id: string,
|
||||||
...children: (TLUiMenuChild | null | false)[]
|
...children: (TLUiMenuChild | false)[]
|
||||||
): TLUiMenuGroup | null {
|
): TLUiMenuGroup | null {
|
||||||
const childItems = compactMenuItems(children)
|
const childItems = compactMenuItems(children)
|
||||||
|
|
||||||
|
@ -83,8 +88,8 @@ export function menuGroup(
|
||||||
/** @public */
|
/** @public */
|
||||||
export function menuSubmenu(
|
export function menuSubmenu(
|
||||||
id: string,
|
id: string,
|
||||||
label: TLUiTranslationKey,
|
label: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>,
|
||||||
...children: (TLUiMenuChild | null | false)[]
|
...children: (TLUiMenuChild | false)[]
|
||||||
): TLUiSubMenu | null {
|
): TLUiSubMenu | null {
|
||||||
const childItems = compactMenuItems(children)
|
const childItems = compactMenuItems(children)
|
||||||
if (childItems.length === 0) return null
|
if (childItems.length === 0) return null
|
||||||
|
@ -216,14 +221,11 @@ export function findMenuItem(menu: TLUiMenuSchema, path: string[]) {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
function _findMenuItem(
|
function _findMenuItem(menu: TLUiMenuSchema | TLUiMenuChild[], path: string[]): TLUiMenuChild {
|
||||||
menu: TLUiMenuSchema | TLUiMenuChild[],
|
|
||||||
path: string[]
|
|
||||||
): TLUiMenuChild | null {
|
|
||||||
const [next, ...rest] = path
|
const [next, ...rest] = path
|
||||||
if (!next) return null
|
if (!next) return null
|
||||||
|
|
||||||
const item = menu.find((item) => item.id === next)
|
const item = menu.find((item) => item?.id === next)
|
||||||
if (!item) return null
|
if (!item) return null
|
||||||
|
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
useEditor,
|
useEditor,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { getEmbedInfo } from '../../utils/embeds'
|
import { getEmbedInfo } from '../../utils/embeds/embeds'
|
||||||
import { EditLinkDialog } from '../components/EditLinkDialog'
|
import { EditLinkDialog } from '../components/EditLinkDialog'
|
||||||
import { EmbedDialog } from '../components/EmbedDialog'
|
import { EmbedDialog } from '../components/EmbedDialog'
|
||||||
import { TLUiIconType } from '../icon-types'
|
import { TLUiIconType } from '../icon-types'
|
||||||
|
@ -32,15 +32,18 @@ import { useToasts } from './useToastsProvider'
|
||||||
import { TLUiTranslationKey } from './useTranslation/TLUiTranslationKey'
|
import { TLUiTranslationKey } from './useTranslation/TLUiTranslationKey'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface TLUiActionItem {
|
export interface TLUiActionItem<
|
||||||
icon?: TLUiIconType
|
TransationKey extends string = string,
|
||||||
|
IconType extends string = string
|
||||||
|
> {
|
||||||
|
icon?: IconType
|
||||||
id: string
|
id: string
|
||||||
kbd?: string
|
kbd?: string
|
||||||
title?: string
|
title?: string
|
||||||
label?: TLUiTranslationKey
|
label?: TransationKey
|
||||||
menuLabel?: TLUiTranslationKey
|
menuLabel?: TransationKey
|
||||||
shortcutsLabel?: TLUiTranslationKey
|
shortcutsLabel?: TransationKey
|
||||||
contextMenuLabel?: TLUiTranslationKey
|
contextMenuLabel?: TransationKey
|
||||||
readonlyOk: boolean
|
readonlyOk: boolean
|
||||||
checkbox?: boolean
|
checkbox?: boolean
|
||||||
onSelect: (source: TLUiEventSource) => Promise<void> | void
|
onSelect: (source: TLUiEventSource) => Promise<void> | void
|
||||||
|
@ -98,7 +101,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
return editor.getSelectedShapeIds().length > 0
|
return editor.getSelectedShapeIds().length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions = makeActions([
|
const actionItems: TLUiActionItem<TLUiTranslationKey, TLUiIconType>[] = [
|
||||||
{
|
{
|
||||||
id: 'edit-link',
|
id: 'edit-link',
|
||||||
label: 'action.edit-link',
|
label: 'action.edit-link',
|
||||||
|
@ -1094,7 +1097,9 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
editor.toggleLock(editor.getSelectedShapeIds())
|
editor.toggleLock(editor.getSelectedShapeIds())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
]
|
||||||
|
|
||||||
|
const actions = makeActions(actionItems)
|
||||||
|
|
||||||
if (overrides) {
|
if (overrides) {
|
||||||
return overrides(editor, actions, undefined)
|
return overrides(editor, actions, undefined)
|
||||||
|
@ -1102,9 +1107,9 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
}, [
|
}, [
|
||||||
|
editor,
|
||||||
trackEvent,
|
trackEvent,
|
||||||
overrides,
|
overrides,
|
||||||
editor,
|
|
||||||
addDialog,
|
addDialog,
|
||||||
insertMedia,
|
insertMedia,
|
||||||
exportAs,
|
exportAs,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Editor, TLShapeId, useEditor } from '@tldraw/editor'
|
import { TLShapeId, useEditor } from '@tldraw/editor'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { TLCopyType, getSvgAsImage, getSvgAsString } from '../../utils/export'
|
import { TLCopyType, copyAs } from '../../utils/export/copyAs'
|
||||||
import { useToasts } from './useToastsProvider'
|
import { useToasts } from './useToastsProvider'
|
||||||
import { useTranslation } from './useTranslation/useTranslation'
|
import { useTranslation } from './useTranslation/useTranslation'
|
||||||
|
|
||||||
|
@ -11,140 +11,16 @@ export function useCopyAs() {
|
||||||
const msg = useTranslation()
|
const msg = useTranslation()
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
// it's important that this function itself isn't async - we need to
|
(ids: TLShapeId[], format: TLCopyType = 'svg') => {
|
||||||
// create the relevant `ClipboardItem`s synchronously to make sure
|
copyAs(editor, ids, format).catch(() => {
|
||||||
// safari knows that the user _wants_ to copy:
|
|
||||||
// https://bugs.webkit.org/show_bug.cgi?id=222262
|
|
||||||
//
|
|
||||||
// this is fine for navigator.clipboard.write, but for fallbacks it's a
|
|
||||||
// little awkward.
|
|
||||||
function copyAs(ids: TLShapeId[] = editor.getSelectedShapeIds(), format: TLCopyType = 'svg') {
|
|
||||||
if (ids.length === 0) {
|
|
||||||
ids = [...editor.currentPageShapeIds]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ids.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case 'svg': {
|
|
||||||
if (window.navigator.clipboard) {
|
|
||||||
if (window.navigator.clipboard.write) {
|
|
||||||
window.navigator.clipboard.write([
|
|
||||||
new ClipboardItem({
|
|
||||||
'text/plain': getExportedSvgBlob(editor, ids),
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
fallbackWriteTextAsync(async () =>
|
|
||||||
getSvgAsString(await getExportSvgElement(editor, ids))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'jpeg':
|
|
||||||
case 'png': {
|
|
||||||
const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png'
|
|
||||||
const blobPromise = getExportedImageBlob(editor, ids, format).then((blob) => {
|
|
||||||
if (blob) {
|
|
||||||
if (window.navigator.clipboard) {
|
|
||||||
return blob
|
|
||||||
}
|
|
||||||
throw new Error('Copy not supported')
|
|
||||||
} else {
|
|
||||||
addToast({
|
addToast({
|
||||||
id: 'copy-fail',
|
id: 'copy-fail',
|
||||||
icon: 'warning-triangle',
|
icon: 'warning-triangle',
|
||||||
title: msg('toast.error.copy-fail.title'),
|
title: msg('toast.error.copy-fail.title'),
|
||||||
description: msg('toast.error.copy-fail.desc'),
|
description: msg('toast.error.copy-fail.desc'),
|
||||||
})
|
})
|
||||||
throw new Error('Copy not possible')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
window.navigator.clipboard
|
|
||||||
.write([
|
|
||||||
new ClipboardItem({
|
|
||||||
// Note: This needs to use the promise based approach for safari/ios to not bail on a permissions error.
|
|
||||||
[mimeType]: blobPromise,
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
.catch((err: any) => {
|
|
||||||
// Firefox will fail with the above if `dom.events.asyncClipboard.clipboardItem` is enabled.
|
|
||||||
// See <https://github.com/tldraw/tldraw/issues/1325>
|
|
||||||
if (!err.toString().match(/^TypeError: DOMString not supported/)) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
blobPromise.then((blob) => {
|
|
||||||
window.navigator.clipboard.write([
|
|
||||||
new ClipboardItem({
|
|
||||||
// Note: This needs to use the promise based approach for safari/ios to not bail on a permissions error.
|
|
||||||
[mimeType]: blob,
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'json': {
|
|
||||||
const data = editor.getContentFromCurrentPage(ids)
|
|
||||||
|
|
||||||
if (window.navigator.clipboard) {
|
|
||||||
const jsonStr = JSON.stringify(data)
|
|
||||||
|
|
||||||
if (window.navigator.clipboard.write) {
|
|
||||||
window.navigator.clipboard.write([
|
|
||||||
new ClipboardItem({
|
|
||||||
'text/plain': new Blob([jsonStr], { type: 'text/plain' }),
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
fallbackWriteTextAsync(async () => jsonStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Copy type ${format} not supported.`)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[editor, addToast, msg]
|
[editor, addToast, msg]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getExportSvgElement(editor: Editor, ids: TLShapeId[]) {
|
|
||||||
const svg = await editor.getSvg(ids, {
|
|
||||||
scale: 1,
|
|
||||||
background: editor.getInstanceState().exportBackground,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!svg) throw new Error('Could not construct SVG.')
|
|
||||||
|
|
||||||
return svg
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getExportedSvgBlob(editor: Editor, ids: TLShapeId[]) {
|
|
||||||
return new Blob([getSvgAsString(await getExportSvgElement(editor, ids))], {
|
|
||||||
type: 'text/plain',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getExportedImageBlob(editor: Editor, ids: TLShapeId[], format: 'png' | 'jpeg') {
|
|
||||||
return await getSvgAsImage(await getExportSvgElement(editor, ids), editor.environment.isSafari, {
|
|
||||||
type: format,
|
|
||||||
quality: 1,
|
|
||||||
scale: 2,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fallbackWriteTextAsync(getText: () => Promise<string>) {
|
|
||||||
if (!(navigator && navigator.clipboard)) return
|
|
||||||
navigator.clipboard.writeText(await getText())
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
import { TLFrameShape, TLShapeId, useEditor } from '@tldraw/editor'
|
import { TLShapeId, useEditor } from '@tldraw/editor'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import {
|
import { TLExportType, exportAs } from '../../utils/export/exportAs'
|
||||||
TLExportType,
|
|
||||||
downloadDataURLAsFile,
|
|
||||||
getSvgAsDataUrl,
|
|
||||||
getSvgAsImage,
|
|
||||||
} from '../../utils/export'
|
|
||||||
import { useToasts } from './useToastsProvider'
|
import { useToasts } from './useToastsProvider'
|
||||||
import { useTranslation } from './useTranslation/useTranslation'
|
import { useTranslation } from './useTranslation/useTranslation'
|
||||||
|
|
||||||
|
@ -16,97 +11,20 @@ export function useExportAs() {
|
||||||
const msg = useTranslation()
|
const msg = useTranslation()
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
async function exportAs(
|
(ids: TLShapeId[], format: TLExportType = 'png') => {
|
||||||
ids: TLShapeId[] = editor.getSelectedShapeIds(),
|
exportAs(editor, ids, format, {
|
||||||
format: TLExportType = 'png'
|
|
||||||
) {
|
|
||||||
if (ids.length === 0) {
|
|
||||||
ids = [...editor.currentPageShapeIds]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ids.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const svg = await editor.getSvg(ids, {
|
|
||||||
scale: 1,
|
scale: 1,
|
||||||
background: editor.getInstanceState().exportBackground,
|
background: editor.instanceState.exportBackground,
|
||||||
})
|
}).catch((e) => {
|
||||||
|
console.error(e.message)
|
||||||
if (!svg) throw new Error('Could not construct SVG.')
|
|
||||||
|
|
||||||
let name = 'shapes' + getTimestamp()
|
|
||||||
|
|
||||||
if (ids.length === 1) {
|
|
||||||
const first = editor.getShape(ids[0])!
|
|
||||||
if (editor.isShapeOfType<TLFrameShape>(first, 'frame')) {
|
|
||||||
name = first.props.name ?? 'frame'
|
|
||||||
} else {
|
|
||||||
name = first.id.replace(/:/, '_')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case 'svg': {
|
|
||||||
const dataURL = await getSvgAsDataUrl(svg)
|
|
||||||
downloadDataURLAsFile(dataURL, `${name}.svg`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case 'webp':
|
|
||||||
case 'png': {
|
|
||||||
const image = await getSvgAsImage(svg, editor.environment.isSafari, {
|
|
||||||
type: format,
|
|
||||||
quality: 1,
|
|
||||||
scale: 2,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!image) {
|
|
||||||
addToast({
|
addToast({
|
||||||
id: 'export-fail',
|
id: 'export-fail',
|
||||||
// icon: 'error',
|
// icon: 'error',
|
||||||
title: msg('toast.error.export-fail.title'),
|
title: msg('toast.error.export-fail.title'),
|
||||||
description: msg('toast.error.export-fail.desc'),
|
description: msg('toast.error.export-fail.desc'),
|
||||||
})
|
})
|
||||||
return
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const dataURL = URL.createObjectURL(image)
|
|
||||||
|
|
||||||
downloadDataURLAsFile(dataURL, `${name}.${format}`)
|
|
||||||
|
|
||||||
URL.revokeObjectURL(dataURL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'json': {
|
|
||||||
const data = editor.getContentFromCurrentPage(ids)
|
|
||||||
const dataURL = URL.createObjectURL(
|
|
||||||
new Blob([JSON.stringify(data, null, 4)], { type: 'application/json' })
|
|
||||||
)
|
|
||||||
|
|
||||||
downloadDataURLAsFile(dataURL, `${name || 'shapes'}.json`)
|
|
||||||
|
|
||||||
URL.revokeObjectURL(dataURL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Export type ${format} not supported.`)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[editor, addToast, msg]
|
[editor, addToast, msg]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTimestamp() {
|
|
||||||
const now = new Date()
|
|
||||||
|
|
||||||
const year = String(now.getFullYear()).slice(2)
|
|
||||||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
||||||
const day = String(now.getDate()).padStart(2, '0')
|
|
||||||
const hours = String(now.getHours()).padStart(2, '0')
|
|
||||||
const minutes = String(now.getMinutes()).padStart(2, '0')
|
|
||||||
const seconds = String(now.getSeconds()).padStart(2, '0')
|
|
||||||
|
|
||||||
return ` at ${year}-${month}-${day} ${hours}.${minutes}.${seconds}`
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Editor, TLBookmarkShape, TLEmbedShape, useEditor, useValue } from '@tldraw/editor'
|
import { Editor, TLBookmarkShape, TLEmbedShape, useEditor, useValue } from '@tldraw/editor'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { getEmbedInfo } from '../../utils/embeds'
|
import { getEmbedInfo } from '../../utils/embeds/embeds'
|
||||||
import {
|
import {
|
||||||
TLUiMenuSchema,
|
TLUiMenuSchema,
|
||||||
compactMenuItems,
|
compactMenuItems,
|
||||||
|
@ -111,7 +111,7 @@ export function TLUiMenuSchemaProvider({ overrides, children }: TLUiMenuSchemaPr
|
||||||
)
|
)
|
||||||
|
|
||||||
const menuSchema = useMemo<TLUiMenuSchema>(() => {
|
const menuSchema = useMemo<TLUiMenuSchema>(() => {
|
||||||
const menuSchema = compactMenuItems([
|
const menuSchema: TLUiMenuSchema = compactMenuItems([
|
||||||
menuGroup(
|
menuGroup(
|
||||||
'menu',
|
'menu',
|
||||||
menuSubmenu(
|
menuSubmenu(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { TLEditorAssetUrls } from './assetUrls'
|
import { TLEditorAssetUrls } from '../../utils/static-assets/assetUrls'
|
||||||
|
|
||||||
export type TLTypeFace = {
|
export type TLTypeFace = {
|
||||||
url: string
|
url: string
|
|
@ -69,9 +69,9 @@ export function ToolbarSchemaProvider({ overrides, children }: TLUiToolbarSchema
|
||||||
toolbarItem(tools['arrow-up']),
|
toolbarItem(tools['arrow-up']),
|
||||||
toolbarItem(tools['arrow-down']),
|
toolbarItem(tools['arrow-down']),
|
||||||
toolbarItem(tools['arrow-right']),
|
toolbarItem(tools['arrow-right']),
|
||||||
toolbarItem(tools.frame),
|
|
||||||
toolbarItem(tools.line),
|
toolbarItem(tools.line),
|
||||||
toolbarItem(tools.highlight),
|
toolbarItem(tools.highlight),
|
||||||
|
toolbarItem(tools.frame),
|
||||||
toolbarItem(tools.laser),
|
toolbarItem(tools.laser),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,14 @@ import { useInsertMedia } from './useInsertMedia'
|
||||||
import { TLUiTranslationKey } from './useTranslation/TLUiTranslationKey'
|
import { TLUiTranslationKey } from './useTranslation/TLUiTranslationKey'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface TLUiToolItem {
|
export interface TLUiToolItem<
|
||||||
|
TranslationKey extends string = string,
|
||||||
|
IconType extends string = string
|
||||||
|
> {
|
||||||
id: string
|
id: string
|
||||||
label: TLUiTranslationKey
|
label: TranslationKey
|
||||||
shortcutsLabel?: TLUiTranslationKey
|
shortcutsLabel?: TranslationKey
|
||||||
icon: TLUiIconType
|
icon: IconType
|
||||||
onSelect: (source: TLUiEventSource) => void
|
onSelect: (source: TLUiEventSource) => void
|
||||||
kbd?: string
|
kbd?: string
|
||||||
readonlyOk: boolean
|
readonlyOk: boolean
|
||||||
|
@ -46,7 +49,7 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
||||||
const insertMedia = useInsertMedia()
|
const insertMedia = useInsertMedia()
|
||||||
|
|
||||||
const tools = React.useMemo<TLUiToolsContextType>(() => {
|
const tools = React.useMemo<TLUiToolsContextType>(() => {
|
||||||
const toolsArray: TLUiToolItem[] = [
|
const toolsArray: TLUiToolItem<TLUiTranslationKey, TLUiIconType>[] = [
|
||||||
{
|
{
|
||||||
id: 'select',
|
id: 'select',
|
||||||
label: 'tool.select',
|
label: 'tool.select',
|
||||||
|
|
|
@ -105,8 +105,8 @@ export const TranslationProvider = track(function TranslationProvider({
|
||||||
export function useTranslation() {
|
export function useTranslation() {
|
||||||
const translation = useCurrentTranslation()
|
const translation = useCurrentTranslation()
|
||||||
return React.useCallback(
|
return React.useCallback(
|
||||||
function msg(id: TLUiTranslationKey) {
|
function msg(id: Exclude<string, TLUiTranslationKey> | string) {
|
||||||
return translation.messages[id] ?? id
|
return translation.messages[id as TLUiTranslationKey] ?? id
|
||||||
},
|
},
|
||||||
[translation]
|
[translation]
|
||||||
)
|
)
|
||||||
|
|
|
@ -58,31 +58,31 @@ type WithDefaultHelpers<T extends TLUiOverride<any, any>> = T extends TLUiOverri
|
||||||
: never
|
: never
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface TLUiOverrides {
|
export type TLUiOverrides = Partial<{
|
||||||
actionsMenu?: WithDefaultHelpers<NonNullable<ActionsMenuSchemaProviderProps['overrides']>>
|
actionsMenu: WithDefaultHelpers<NonNullable<ActionsMenuSchemaProviderProps['overrides']>>
|
||||||
actions?: WithDefaultHelpers<NonNullable<ActionsProviderProps['overrides']>>
|
actions: WithDefaultHelpers<NonNullable<ActionsProviderProps['overrides']>>
|
||||||
contextMenu?: WithDefaultHelpers<NonNullable<TLUiContextMenuSchemaProviderProps['overrides']>>
|
contextMenu: WithDefaultHelpers<NonNullable<TLUiContextMenuSchemaProviderProps['overrides']>>
|
||||||
helpMenu?: WithDefaultHelpers<NonNullable<TLUiHelpMenuSchemaProviderProps['overrides']>>
|
helpMenu: WithDefaultHelpers<NonNullable<TLUiHelpMenuSchemaProviderProps['overrides']>>
|
||||||
menu?: WithDefaultHelpers<NonNullable<TLUiMenuSchemaProviderProps['overrides']>>
|
menu: WithDefaultHelpers<NonNullable<TLUiMenuSchemaProviderProps['overrides']>>
|
||||||
toolbar?: WithDefaultHelpers<NonNullable<TLUiToolbarSchemaProviderProps['overrides']>>
|
toolbar: WithDefaultHelpers<NonNullable<TLUiToolbarSchemaProviderProps['overrides']>>
|
||||||
keyboardShortcutsMenu?: WithDefaultHelpers<
|
keyboardShortcutsMenu: WithDefaultHelpers<
|
||||||
NonNullable<TLUiKeyboardShortcutsSchemaProviderProps['overrides']>
|
NonNullable<TLUiKeyboardShortcutsSchemaProviderProps['overrides']>
|
||||||
>
|
>
|
||||||
tools?: WithDefaultHelpers<NonNullable<TLUiToolsProviderProps['overrides']>>
|
tools: WithDefaultHelpers<NonNullable<TLUiToolsProviderProps['overrides']>>
|
||||||
translations?: TLUiTranslationProviderProps['overrides']
|
translations: TLUiTranslationProviderProps['overrides']
|
||||||
}
|
}>
|
||||||
|
|
||||||
export interface TLUiOverridesWithoutDefaults {
|
export type TLUiOverridesWithoutDefaults = Partial<{
|
||||||
actionsMenu?: ActionsMenuSchemaProviderProps['overrides']
|
actionsMenu: ActionsMenuSchemaProviderProps['overrides']
|
||||||
actions?: ActionsProviderProps['overrides']
|
actions: ActionsProviderProps['overrides']
|
||||||
contextMenu?: TLUiContextMenuSchemaProviderProps['overrides']
|
contextMenu: TLUiContextMenuSchemaProviderProps['overrides']
|
||||||
helpMenu?: TLUiHelpMenuSchemaProviderProps['overrides']
|
helpMenu: TLUiHelpMenuSchemaProviderProps['overrides']
|
||||||
menu?: TLUiMenuSchemaProviderProps['overrides']
|
menu: TLUiMenuSchemaProviderProps['overrides']
|
||||||
toolbar?: TLUiToolbarSchemaProviderProps['overrides']
|
toolbar: TLUiToolbarSchemaProviderProps['overrides']
|
||||||
keyboardShortcutsMenu?: TLUiKeyboardShortcutsSchemaProviderProps['overrides']
|
keyboardShortcutsMenu: TLUiKeyboardShortcutsSchemaProviderProps['overrides']
|
||||||
tools?: TLUiToolsProviderProps['overrides']
|
tools: TLUiToolsProviderProps['overrides']
|
||||||
translations?: TLUiTranslationProviderProps['overrides']
|
translations: TLUiTranslationProviderProps['overrides']
|
||||||
}
|
}>
|
||||||
|
|
||||||
export function mergeOverrides(
|
export function mergeOverrides(
|
||||||
overrides: TLUiOverrides[],
|
overrides: TLUiOverrides[],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import downscale from 'downscale'
|
import downscale from 'downscale'
|
||||||
import { getBrowserCanvasMaxSize } from '../shapes/shared/getBrowserCanvasMaxSize'
|
import { getBrowserCanvasMaxSize } from '../shapes/shared/getBrowserCanvasMaxSize'
|
||||||
import { isAnimated } from './is-gif-animated'
|
import { isAnimated } from './assets/is-gif-animated'
|
||||||
|
|
||||||
type BoxWidthHeight = {
|
type BoxWidthHeight = {
|
||||||
w: number
|
w: number
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue