Flatten shapes to image(s) (#3933)
This PR adds some functionality for turning shapes into images. ![Kapture 2024-06-13 at 12 51 00](https://github.com/tldraw/tldraw/assets/23072548/78525e29-61b5-418f-889d-2f061f26f34d) It adds: - the `flattenShapesToImages` - the `useFlatten` hook - a `flatten-shapes-to-images` action (shift + f) - adds `flattenImageBoundsExpand` option - adds `flattenImageBoundsPadding` option ## Flatten shapes to images The `flattenShapesToImages` helper method will 1) create an image for the given shape ids, 2) add it to the canvas in the same location / size as the source shapes, and then 3) delete the original shapes. The new image will be placed correctly in the z index and in the correct rotation of the root-most ancestor of the given shape ids. ![image](https://github.com/tldraw/tldraw/assets/23072548/fe888980-05a5-4369-863f-90c142f9f8b9) It has an argument, `flattenImageBoundsExpand`, which if provided will chunk the given shapes into images based on their overlapping (expanded) bounding boxes. ![image](https://github.com/tldraw/tldraw/assets/23072548/c4799309-244d-4a2b-ac59-9c2fd100319c) By default, the flatten action uses the editor's `options.flattenImageBoundsExpand`. The `flattenImageBoundsPadding` option is used as a value for how much larger the image should be than the source image bounds (to account for large strokes, for example). ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `feature` — New feature ### Test Plan 1. Select shapes 2. Select context menu > edit > flatten - [ ] Unit Tests - [ ] End to end tests ### Release Notes - Add Flatten, a new menu item to flatten shapes into images
This commit is contained in:
parent
db2e88e5d5
commit
5bf05bbb3c
12 changed files with 290 additions and 21 deletions
|
@ -96,6 +96,7 @@
|
||||||
"action.toggle-grid.menu": "Show grid",
|
"action.toggle-grid.menu": "Show grid",
|
||||||
"action.toggle-grid": "Toggle grid",
|
"action.toggle-grid": "Toggle grid",
|
||||||
"action.toggle-lock": "Toggle locked",
|
"action.toggle-lock": "Toggle locked",
|
||||||
|
"action.flatten-to-image": "Flatten",
|
||||||
"action.toggle-snap-mode.menu": "Always snap",
|
"action.toggle-snap-mode.menu": "Always snap",
|
||||||
"action.toggle-snap-mode": "Toggle always snap",
|
"action.toggle-snap-mode": "Toggle always snap",
|
||||||
"action.toggle-tool-lock.menu": "Tool lock",
|
"action.toggle-tool-lock.menu": "Tool lock",
|
||||||
|
@ -232,6 +233,7 @@
|
||||||
"menu.language": "Language",
|
"menu.language": "Language",
|
||||||
"menu.preferences": "Preferences",
|
"menu.preferences": "Preferences",
|
||||||
"menu.view": "View",
|
"menu.view": "View",
|
||||||
|
"context-menu.edit": "Edit",
|
||||||
"context-menu.arrange": "Arrange",
|
"context-menu.arrange": "Arrange",
|
||||||
"context-menu.copy-as": "Copy as",
|
"context-menu.copy-as": "Copy as",
|
||||||
"context-menu.export-as": "Export as",
|
"context-menu.export-as": "Export as",
|
||||||
|
|
|
@ -681,6 +681,8 @@ export const defaultTldrawOptions: {
|
||||||
readonly dragDistanceSquared: 16;
|
readonly dragDistanceSquared: 16;
|
||||||
readonly edgeScrollDistance: 8;
|
readonly edgeScrollDistance: 8;
|
||||||
readonly edgeScrollSpeed: 20;
|
readonly edgeScrollSpeed: 20;
|
||||||
|
readonly flattenImageBoundsExpand: 64;
|
||||||
|
readonly flattenImageBoundsPadding: 16;
|
||||||
readonly followChaseViewportSnap: 2;
|
readonly followChaseViewportSnap: 2;
|
||||||
readonly gridSteps: readonly [{
|
readonly gridSteps: readonly [{
|
||||||
readonly mid: 0.15;
|
readonly mid: 0.15;
|
||||||
|
@ -2571,6 +2573,10 @@ export interface TldrawOptions {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
readonly edgeScrollSpeed: number;
|
readonly edgeScrollSpeed: number;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
readonly flattenImageBoundsExpand: number;
|
||||||
|
// (undocumented)
|
||||||
|
readonly flattenImageBoundsPadding: number;
|
||||||
|
// (undocumented)
|
||||||
readonly followChaseViewportSnap: number;
|
readonly followChaseViewportSnap: number;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
readonly gridSteps: readonly {
|
readonly gridSteps: readonly {
|
||||||
|
|
|
@ -45,6 +45,8 @@ export interface TldrawOptions {
|
||||||
readonly longPressDurationMs: number
|
readonly longPressDurationMs: number
|
||||||
readonly textShadowLod: number
|
readonly textShadowLod: number
|
||||||
readonly adjacentShapeMargin: number
|
readonly adjacentShapeMargin: number
|
||||||
|
readonly flattenImageBoundsExpand: number
|
||||||
|
readonly flattenImageBoundsPadding: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
@ -79,4 +81,6 @@ export const defaultTldrawOptions = {
|
||||||
longPressDurationMs: 500,
|
longPressDurationMs: 500,
|
||||||
textShadowLod: 0.35,
|
textShadowLod: 0.35,
|
||||||
adjacentShapeMargin: 10,
|
adjacentShapeMargin: 10,
|
||||||
|
flattenImageBoundsExpand: 64,
|
||||||
|
flattenImageBoundsPadding: 16,
|
||||||
} as const satisfies TldrawOptions
|
} as const satisfies TldrawOptions
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,18 +3,10 @@ import {
|
||||||
ArrangeMenuSubmenu,
|
ArrangeMenuSubmenu,
|
||||||
ClipboardMenuGroup,
|
ClipboardMenuGroup,
|
||||||
ConversionsMenuGroup,
|
ConversionsMenuGroup,
|
||||||
ConvertToBookmarkMenuItem,
|
EditMenuSubmenu,
|
||||||
ConvertToEmbedMenuItem,
|
|
||||||
EditLinkMenuItem,
|
|
||||||
FitFrameToContentMenuItem,
|
|
||||||
GroupMenuItem,
|
|
||||||
MoveToPageMenu,
|
MoveToPageMenu,
|
||||||
RemoveFrameMenuItem,
|
|
||||||
ReorderMenuSubmenu,
|
ReorderMenuSubmenu,
|
||||||
SelectAllMenuItem,
|
SelectAllMenuItem,
|
||||||
ToggleAutoSizeMenuItem,
|
|
||||||
ToggleLockMenuItem,
|
|
||||||
UngroupMenuItem,
|
|
||||||
} from '../menu-items'
|
} from '../menu-items'
|
||||||
import { TldrawUiMenuGroup } from '../primitives/menus/TldrawUiMenuGroup'
|
import { TldrawUiMenuGroup } from '../primitives/menus/TldrawUiMenuGroup'
|
||||||
|
|
||||||
|
@ -32,18 +24,8 @@ export function DefaultContextMenuContent() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TldrawUiMenuGroup id="misc">
|
|
||||||
<GroupMenuItem />
|
|
||||||
<UngroupMenuItem />
|
|
||||||
<EditLinkMenuItem />
|
|
||||||
<ToggleAutoSizeMenuItem />
|
|
||||||
<RemoveFrameMenuItem />
|
|
||||||
<FitFrameToContentMenuItem />
|
|
||||||
<ConvertToEmbedMenuItem />
|
|
||||||
<ConvertToBookmarkMenuItem />
|
|
||||||
<ToggleLockMenuItem />
|
|
||||||
</TldrawUiMenuGroup>
|
|
||||||
<TldrawUiMenuGroup id="modify">
|
<TldrawUiMenuGroup id="modify">
|
||||||
|
<EditMenuSubmenu />
|
||||||
<ArrangeMenuSubmenu />
|
<ArrangeMenuSubmenu />
|
||||||
<ReorderMenuSubmenu />
|
<ReorderMenuSubmenu />
|
||||||
<MoveToPageMenu />
|
<MoveToPageMenu />
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
ConvertToEmbedMenuItem,
|
ConvertToEmbedMenuItem,
|
||||||
EditLinkMenuItem,
|
EditLinkMenuItem,
|
||||||
FitFrameToContentMenuItem,
|
FitFrameToContentMenuItem,
|
||||||
|
FlattenMenuItem,
|
||||||
GroupMenuItem,
|
GroupMenuItem,
|
||||||
RemoveFrameMenuItem,
|
RemoveFrameMenuItem,
|
||||||
SelectAllMenuItem,
|
SelectAllMenuItem,
|
||||||
|
@ -101,6 +102,7 @@ export function MiscMenuGroup() {
|
||||||
<FitFrameToContentMenuItem />
|
<FitFrameToContentMenuItem />
|
||||||
<ConvertToEmbedMenuItem />
|
<ConvertToEmbedMenuItem />
|
||||||
<ConvertToBookmarkMenuItem />
|
<ConvertToBookmarkMenuItem />
|
||||||
|
<FlattenMenuItem />
|
||||||
</TldrawUiMenuGroup>
|
</TldrawUiMenuGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
TLBookmarkShape,
|
TLBookmarkShape,
|
||||||
TLEmbedShape,
|
TLEmbedShape,
|
||||||
TLFrameShape,
|
TLFrameShape,
|
||||||
|
TLImageShape,
|
||||||
TLPageId,
|
TLPageId,
|
||||||
useEditor,
|
useEditor,
|
||||||
useValue,
|
useValue,
|
||||||
|
@ -51,6 +52,27 @@ export function DuplicateMenuItem() {
|
||||||
return <TldrawUiMenuItem {...actions['duplicate']} />
|
return <TldrawUiMenuItem {...actions['duplicate']} />
|
||||||
}
|
}
|
||||||
/** @public @react */
|
/** @public @react */
|
||||||
|
export function FlattenMenuItem() {
|
||||||
|
const actions = useActions()
|
||||||
|
const editor = useEditor()
|
||||||
|
const shouldDisplay = useValue(
|
||||||
|
'should display flatten option',
|
||||||
|
() => {
|
||||||
|
const selectedShapeIds = editor.getSelectedShapeIds()
|
||||||
|
if (selectedShapeIds.length === 0) return false
|
||||||
|
const onlySelectedShape = editor.getOnlySelectedShape()
|
||||||
|
if (onlySelectedShape && editor.isShapeOfType<TLImageShape>(onlySelectedShape, 'image')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
[editor]
|
||||||
|
)
|
||||||
|
if (!shouldDisplay) return null
|
||||||
|
|
||||||
|
return <TldrawUiMenuItem {...actions['flatten-to-image']} />
|
||||||
|
}
|
||||||
|
/** @public @react */
|
||||||
export function GroupMenuItem() {
|
export function GroupMenuItem() {
|
||||||
const actions = useActions()
|
const actions = useActions()
|
||||||
const shouldDisplay = useAllowGroup()
|
const shouldDisplay = useAllowGroup()
|
||||||
|
@ -305,6 +327,25 @@ export function DeleteMenuItem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------- Modify --------------------- */
|
/* --------------------- Modify --------------------- */
|
||||||
|
|
||||||
|
/** @public @react */
|
||||||
|
export function EditMenuSubmenu() {
|
||||||
|
return (
|
||||||
|
<TldrawUiMenuSubmenu id="edit" label="context-menu.edit" size="small">
|
||||||
|
<GroupMenuItem />
|
||||||
|
<UngroupMenuItem />
|
||||||
|
<FlattenMenuItem />
|
||||||
|
<EditLinkMenuItem />
|
||||||
|
<FitFrameToContentMenuItem />
|
||||||
|
<RemoveFrameMenuItem />
|
||||||
|
<ConvertToEmbedMenuItem />
|
||||||
|
<ConvertToBookmarkMenuItem />
|
||||||
|
<ToggleAutoSizeMenuItem />
|
||||||
|
<ToggleLockMenuItem />
|
||||||
|
</TldrawUiMenuSubmenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/** @public @react */
|
/** @public @react */
|
||||||
export function ArrangeMenuSubmenu() {
|
export function ArrangeMenuSubmenu() {
|
||||||
const twoSelected = useUnlockedSelectedShapesCount(2)
|
const twoSelected = useUnlockedSelectedShapesCount(2)
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { EmbedDialog } from '../components/EmbedDialog'
|
||||||
import { useMenuClipboardEvents } from '../hooks/useClipboardEvents'
|
import { useMenuClipboardEvents } from '../hooks/useClipboardEvents'
|
||||||
import { useCopyAs } from '../hooks/useCopyAs'
|
import { useCopyAs } from '../hooks/useCopyAs'
|
||||||
import { useExportAs } from '../hooks/useExportAs'
|
import { useExportAs } from '../hooks/useExportAs'
|
||||||
|
import { flattenShapesToImages } from '../hooks/useFlatten'
|
||||||
import { useInsertMedia } from '../hooks/useInsertMedia'
|
import { useInsertMedia } from '../hooks/useInsertMedia'
|
||||||
import { usePrint } from '../hooks/usePrint'
|
import { usePrint } from '../hooks/usePrint'
|
||||||
import { TLUiTranslationKey } from '../hooks/useTranslation/TLUiTranslationKey'
|
import { TLUiTranslationKey } from '../hooks/useTranslation/TLUiTranslationKey'
|
||||||
|
@ -1341,6 +1342,28 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
||||||
trackEvent('set-style', { source, id: style.id, value: 'white' })
|
trackEvent('set-style', { source, id: style.id, value: 'white' })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'flatten-to-image',
|
||||||
|
label: 'action.flatten-to-image',
|
||||||
|
kbd: '!f',
|
||||||
|
onSelect: async (source) => {
|
||||||
|
const ids = editor.getSelectedShapeIds()
|
||||||
|
if (ids.length === 0) return
|
||||||
|
|
||||||
|
editor.mark('flattening to image')
|
||||||
|
trackEvent('flatten-to-image', { source })
|
||||||
|
|
||||||
|
const newShapeIds = await flattenShapesToImages(
|
||||||
|
editor,
|
||||||
|
ids,
|
||||||
|
editor.options.flattenImageBoundsExpand
|
||||||
|
)
|
||||||
|
|
||||||
|
if (newShapeIds?.length) {
|
||||||
|
editor.setSelectedShapes(newShapeIds)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const actions = makeActions(actionItems)
|
const actions = makeActions(actionItems)
|
||||||
|
|
|
@ -96,6 +96,7 @@ export interface TLUiEventMap {
|
||||||
'open-cursor-chat': null
|
'open-cursor-chat': null
|
||||||
'zoom-tool': null
|
'zoom-tool': null
|
||||||
'unlock-all': null
|
'unlock-all': null
|
||||||
|
'flatten-to-image': null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
|
202
packages/tldraw/src/lib/ui/hooks/useFlatten.ts
Normal file
202
packages/tldraw/src/lib/ui/hooks/useFlatten.ts
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
import {
|
||||||
|
AssetRecordType,
|
||||||
|
Box,
|
||||||
|
Editor,
|
||||||
|
IndexKey,
|
||||||
|
TLImageAsset,
|
||||||
|
TLImageShape,
|
||||||
|
TLShape,
|
||||||
|
TLShapeId,
|
||||||
|
Vec,
|
||||||
|
compact,
|
||||||
|
createShapeId,
|
||||||
|
isShapeId,
|
||||||
|
transact,
|
||||||
|
useEditor,
|
||||||
|
} from '@tldraw/editor'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export async function flattenShapesToImages(
|
||||||
|
editor: Editor,
|
||||||
|
shapeIds: TLShapeId[],
|
||||||
|
flattenImageBoundsExpand?: number
|
||||||
|
) {
|
||||||
|
const shapes = compact(
|
||||||
|
shapeIds.map((id) => {
|
||||||
|
const shape = editor.getShape(id)
|
||||||
|
if (!shape) return
|
||||||
|
const util = editor.getShapeUtil(shape.type)
|
||||||
|
// skip shapes that don't have a toSvg method
|
||||||
|
if (util.toSvg === undefined) return
|
||||||
|
return shape
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
if (shapes.length === 0) return
|
||||||
|
|
||||||
|
// Don't flatten if it's just one image
|
||||||
|
if (shapes.length === 1) {
|
||||||
|
const shape = shapes[0]
|
||||||
|
if (!shape) return
|
||||||
|
if (editor.isShapeOfType(shape, 'image')) return
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups: { shapes: TLShape[]; bounds: Box; asset?: TLImageAsset }[] = []
|
||||||
|
|
||||||
|
if (flattenImageBoundsExpand !== undefined) {
|
||||||
|
const expandedBounds = shapes.map((shape) => {
|
||||||
|
return {
|
||||||
|
shape,
|
||||||
|
bounds: editor.getShapeMaskedPageBounds(shape)!.clone().expandBy(flattenImageBoundsExpand),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let i = 0; i < expandedBounds.length; i++) {
|
||||||
|
const item = expandedBounds[i]
|
||||||
|
if (i === 0) {
|
||||||
|
groups[0] = {
|
||||||
|
shapes: [item.shape],
|
||||||
|
bounds: item.bounds,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let didLand = false
|
||||||
|
|
||||||
|
for (const group of groups) {
|
||||||
|
if (group.bounds.includes(item.bounds)) {
|
||||||
|
group.shapes.push(item.shape)
|
||||||
|
group.bounds.expand(item.bounds)
|
||||||
|
didLand = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!didLand) {
|
||||||
|
groups.push({
|
||||||
|
shapes: [item.shape],
|
||||||
|
bounds: item.bounds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const bounds = Box.Common(shapes.map((shape) => editor.getShapeMaskedPageBounds(shape)!))
|
||||||
|
groups.push({
|
||||||
|
shapes,
|
||||||
|
bounds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const padding = editor.options.flattenImageBoundsPadding
|
||||||
|
|
||||||
|
for (const group of groups) {
|
||||||
|
if (flattenImageBoundsExpand !== undefined) {
|
||||||
|
// shrink the bounds again, removing the expanded area
|
||||||
|
group.bounds.expandBy(-flattenImageBoundsExpand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get an image for the shapes
|
||||||
|
const svgResult = await editor.getSvgString(group.shapes, {
|
||||||
|
padding,
|
||||||
|
})
|
||||||
|
if (!svgResult?.svg) continue
|
||||||
|
|
||||||
|
// get an image asset for the image
|
||||||
|
const blob = new Blob([svgResult.svg], { type: 'image/svg+xml' })
|
||||||
|
const asset = (await editor.getAssetForExternalContent({
|
||||||
|
type: 'file',
|
||||||
|
file: new File([blob], 'asset.svg', { type: 'image/svg+xml' }),
|
||||||
|
})) as TLImageAsset
|
||||||
|
if (!asset) continue
|
||||||
|
|
||||||
|
// add it to the group
|
||||||
|
group.asset = asset
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdShapeIds: TLShapeId[] = []
|
||||||
|
|
||||||
|
transact(() => {
|
||||||
|
for (const group of groups) {
|
||||||
|
const { asset, bounds, shapes } = group
|
||||||
|
if (!asset) continue
|
||||||
|
|
||||||
|
const assetId = AssetRecordType.createId()
|
||||||
|
|
||||||
|
const commonAncestorId = editor.findCommonAncestor(shapes) ?? editor.getCurrentPageId()
|
||||||
|
if (!commonAncestorId) continue
|
||||||
|
|
||||||
|
let index: IndexKey = 'a1' as IndexKey
|
||||||
|
for (const shape of shapes) {
|
||||||
|
if (shape.parentId === commonAncestorId) {
|
||||||
|
if (shape.index > index) {
|
||||||
|
index = shape.index
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let x: number
|
||||||
|
let y: number
|
||||||
|
let rotation: number
|
||||||
|
|
||||||
|
if (isShapeId(commonAncestorId)) {
|
||||||
|
const commonAncestor = editor.getShape(commonAncestorId)
|
||||||
|
if (!commonAncestor) continue
|
||||||
|
// put the point in the parent's space
|
||||||
|
const point = editor.getPointInShapeSpace(commonAncestor, {
|
||||||
|
x: bounds.x,
|
||||||
|
y: bounds.y,
|
||||||
|
})
|
||||||
|
// get the parent's rotation
|
||||||
|
rotation = editor.getShapePageTransform(commonAncestorId).rotation()
|
||||||
|
// rotate the point against the parent's rotation
|
||||||
|
point.sub(new Vec(padding, padding).rot(-rotation))
|
||||||
|
x = point.x
|
||||||
|
y = point.y
|
||||||
|
} else {
|
||||||
|
// if the common ancestor is the page, then just adjust for the padding
|
||||||
|
x = bounds.x - padding
|
||||||
|
y = bounds.y - padding
|
||||||
|
rotation = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the shapes
|
||||||
|
editor.deleteShapes(shapes)
|
||||||
|
|
||||||
|
// create the asset
|
||||||
|
editor.createAssets([{ ...asset, id: assetId }])
|
||||||
|
|
||||||
|
const shapeId = createShapeId()
|
||||||
|
|
||||||
|
// create an image shape in the same place as the shapes
|
||||||
|
editor.createShape<TLImageShape>({
|
||||||
|
id: shapeId,
|
||||||
|
type: 'image',
|
||||||
|
index,
|
||||||
|
parentId: commonAncestorId,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
rotation: -rotation,
|
||||||
|
props: {
|
||||||
|
assetId,
|
||||||
|
w: bounds.w + padding * 2,
|
||||||
|
h: bounds.h + padding * 2,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
createdShapeIds.push(shapeId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return createdShapeIds
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFlatten() {
|
||||||
|
const editor = useEditor()
|
||||||
|
return useCallback(
|
||||||
|
(ids: TLShapeId[]) => {
|
||||||
|
return flattenShapesToImages(editor, ids)
|
||||||
|
},
|
||||||
|
[editor]
|
||||||
|
)
|
||||||
|
}
|
|
@ -100,6 +100,7 @@ export type TLUiTranslationKey =
|
||||||
| 'action.toggle-grid.menu'
|
| 'action.toggle-grid.menu'
|
||||||
| 'action.toggle-grid'
|
| 'action.toggle-grid'
|
||||||
| 'action.toggle-lock'
|
| 'action.toggle-lock'
|
||||||
|
| 'action.flatten-to-image'
|
||||||
| 'action.toggle-snap-mode.menu'
|
| 'action.toggle-snap-mode.menu'
|
||||||
| 'action.toggle-snap-mode'
|
| 'action.toggle-snap-mode'
|
||||||
| 'action.toggle-tool-lock.menu'
|
| 'action.toggle-tool-lock.menu'
|
||||||
|
@ -236,6 +237,7 @@ export type TLUiTranslationKey =
|
||||||
| 'menu.language'
|
| 'menu.language'
|
||||||
| 'menu.preferences'
|
| 'menu.preferences'
|
||||||
| 'menu.view'
|
| 'menu.view'
|
||||||
|
| 'context-menu.edit'
|
||||||
| 'context-menu.arrange'
|
| 'context-menu.arrange'
|
||||||
| 'context-menu.copy-as'
|
| 'context-menu.copy-as'
|
||||||
| 'context-menu.export-as'
|
| 'context-menu.export-as'
|
||||||
|
|
|
@ -100,6 +100,7 @@ export const DEFAULT_TRANSLATION = {
|
||||||
'action.toggle-grid.menu': 'Show grid',
|
'action.toggle-grid.menu': 'Show grid',
|
||||||
'action.toggle-grid': 'Toggle grid',
|
'action.toggle-grid': 'Toggle grid',
|
||||||
'action.toggle-lock': 'Toggle locked',
|
'action.toggle-lock': 'Toggle locked',
|
||||||
|
'action.flatten-to-image': 'Flatten',
|
||||||
'action.toggle-snap-mode.menu': 'Always snap',
|
'action.toggle-snap-mode.menu': 'Always snap',
|
||||||
'action.toggle-snap-mode': 'Toggle always snap',
|
'action.toggle-snap-mode': 'Toggle always snap',
|
||||||
'action.toggle-tool-lock.menu': 'Tool lock',
|
'action.toggle-tool-lock.menu': 'Tool lock',
|
||||||
|
@ -236,6 +237,7 @@ export const DEFAULT_TRANSLATION = {
|
||||||
'menu.language': 'Language',
|
'menu.language': 'Language',
|
||||||
'menu.preferences': 'Preferences',
|
'menu.preferences': 'Preferences',
|
||||||
'menu.view': 'View',
|
'menu.view': 'View',
|
||||||
|
'context-menu.edit': 'Edit',
|
||||||
'context-menu.arrange': 'Arrange',
|
'context-menu.arrange': 'Arrange',
|
||||||
'context-menu.copy-as': 'Copy as',
|
'context-menu.copy-as': 'Copy as',
|
||||||
'context-menu.export-as': 'Export as',
|
'context-menu.export-as': 'Export as',
|
||||||
|
|
Loading…
Reference in a new issue