Merge branch 'main' into test/commands
This commit is contained in:
commit
c904200fc7
28 changed files with 577 additions and 278 deletions
|
@ -1,8 +1,9 @@
|
||||||
{
|
{
|
||||||
"version": "0.0.59",
|
"version": "0.0.79",
|
||||||
"registry": "https://registry.npmjs.org/",
|
"registry": "https://registry.npmjs.org/",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public",
|
||||||
|
"directory": "dist"
|
||||||
},
|
},
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true
|
"useWorkspaces": true
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"url": "git+https://github.com/tldraw/tldraw.git"
|
"url": "git+https://github.com/tldraw/tldraw.git"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "0.0.36",
|
"version": "0.0.74",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/core",
|
"packages/core",
|
||||||
"packages/tldraw",
|
"packages/tldraw",
|
||||||
|
@ -24,7 +24,8 @@
|
||||||
"build": "yarn build:packages && cd packages/www && yarn build",
|
"build": "yarn build:packages && cd packages/www && yarn build",
|
||||||
"build:packages": "cd packages/core && yarn build && cd ../tldraw && yarn build",
|
"build:packages": "cd packages/core && yarn build && cd ../tldraw && yarn build",
|
||||||
"publish:patch": "yarn build:packages && lerna publish patch",
|
"publish:patch": "yarn build:packages && lerna publish patch",
|
||||||
"docs": "lerna run docs --stream"
|
"docs": "lerna run docs --stream",
|
||||||
|
"docs:watch": "lerna run docs:watch --stream"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.15.0",
|
"@babel/core": "^7.15.0",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "@tldraw/core",
|
"name": "@tldraw/core",
|
||||||
"version": "0.0.59",
|
"version": "0.0.79",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A tiny little drawing app (core).",
|
"description": "A tiny little drawing app (core)",
|
||||||
"author": "@steveruizok",
|
"author": "@steveruizok",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -11,6 +11,9 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
|
"files": [
|
||||||
|
"dist/**/*"
|
||||||
|
],
|
||||||
"main": "./dist/cjs/index.js",
|
"main": "./dist/cjs/index.js",
|
||||||
"module": "./dist/esm/index.js",
|
"module": "./dist/esm/index.js",
|
||||||
"types": "./dist/types/index.d.ts",
|
"types": "./dist/types/index.d.ts",
|
||||||
|
@ -19,13 +22,14 @@
|
||||||
"start:pre": "node scripts/pre-dev && yarn types:pre",
|
"start:pre": "node scripts/pre-dev && yarn types:pre",
|
||||||
"start": "node scripts/dev & yarn types:dev",
|
"start": "node scripts/dev & yarn types:dev",
|
||||||
"build": "yarn clean && node scripts/build && yarn types:build",
|
"build": "yarn clean && node scripts/build && yarn types:build",
|
||||||
"types:pre": "tsc ",
|
"types:pre": "tsc",
|
||||||
"types:dev": "tsc --watch ",
|
"types:dev": "tsc --watch ",
|
||||||
"types:build": "tsc --project tsconfig.build.json",
|
"types:build": "tsc --project tsconfig.build.json",
|
||||||
"lint": "eslint src/ --ext .ts,.tsx",
|
"lint": "eslint src/ --ext .ts,.tsx",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"ts-node": "ts-node",
|
"ts-node": "ts-node",
|
||||||
"docs": "typedoc --entryPoints src/index.ts"
|
"docs": "typedoc",
|
||||||
|
"docs:watch": "typedoc --watch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.15.5",
|
"@babel/core": "^7.15.5",
|
||||||
|
@ -53,6 +57,5 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
"react-use-gesture": "^9.1.3"
|
"react-use-gesture": "^9.1.3"
|
||||||
},
|
}
|
||||||
"gitHead": "838fabdbff1a66d4d7ee8aa5c5d117bc55acbff2"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,12 +36,6 @@ async function main() {
|
||||||
external: ['react', 'react-dom'],
|
external: ['react', 'react-dom'],
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const file of ['package.json', 'README.md']) {
|
|
||||||
fs.copyFile(file, `dist/${file}`, fs.constants.COPYFILE_EXCL, (err) => {
|
|
||||||
if (err) throw err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`✔ ${name}: Built package.`)
|
console.log(`✔ ${name}: Built package.`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`× ${name}: Build failed due to an error.`)
|
console.log(`× ${name}: Build failed due to an error.`)
|
||||||
|
|
|
@ -7,9 +7,14 @@
|
||||||
"**/*.spec.tsx",
|
"**/*.spec.tsx",
|
||||||
"**/*.spec.ts",
|
"**/*.spec.ts",
|
||||||
"src/test",
|
"src/test",
|
||||||
"dist"
|
"dist",
|
||||||
|
"docs"
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"incremental": false,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"declarationMap": false,
|
||||||
|
"sourceMap": false,
|
||||||
"outDir": "./dist/types"
|
"outDir": "./dist/types"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules", "dist"],
|
"exclude": ["node_modules", "dist", "docs"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"emitDeclarationOnly": true,
|
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"outDir": "./dist/types",
|
"outDir": "./dist/types",
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "@tldraw/dev",
|
"name": "@tldraw/dev",
|
||||||
"version": "0.0.59",
|
"version": "0.0.79",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A tiny little drawing app (core).",
|
"description": "A tiny little drawing app (dev)",
|
||||||
"author": "@steveruizok",
|
"author": "@steveruizok",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
"src"
|
"src"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tldraw/tldraw": "^0.0.59",
|
"@tldraw/tldraw": "^0.0.64",
|
||||||
"idb": "^6.1.2",
|
"idb": "^6.1.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^17.0.2"
|
||||||
|
|
|
@ -22,11 +22,7 @@ Import the `TLDraw` React component and use it in your app.
|
||||||
import { TLDraw } from '@tldraw/tldraw'
|
import { TLDraw } from '@tldraw/tldraw'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return <TLDraw />
|
||||||
<div>
|
|
||||||
<TLDraw />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -38,6 +34,7 @@ The `TLDraw` React component is the [tldraw](https://tldraw.com) editor exported
|
||||||
|
|
||||||
| Prop | Type | Description |
|
| Prop | Type | Description |
|
||||||
| --------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `id` | `string` | (optional) An id under which to persist the component's state. |
|
||||||
| `document` | `TLDrawDocument` | (optional) An initial [`TLDrawDocument`](#tldrawdocument) object. |
|
| `document` | `TLDrawDocument` | (optional) An initial [`TLDrawDocument`](#tldrawdocument) object. |
|
||||||
| `currentPageId` | `string` | (optional) A current page id, referencing the `TLDrawDocument` object provided via the `document` prop. |
|
| `currentPageId` | `string` | (optional) A current page id, referencing the `TLDrawDocument` object provided via the `document` prop. |
|
||||||
| `onMount` | `(TLDrawState) => void` | (optional) A callback function that will be called when the editor first mounts, receiving the current `TLDrawState`. |
|
| `onMount` | `(TLDrawState) => void` | (optional) A callback function that will be called when the editor first mounts, receiving the current `TLDrawState`. |
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "@tldraw/tldraw",
|
"name": "@tldraw/tldraw",
|
||||||
"version": "0.0.59",
|
"version": "0.0.79",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A tiny little drawing app (editor).",
|
"description": "A tiny little drawing app (editor)",
|
||||||
"author": "@steveruizok",
|
"author": "@steveruizok",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -11,6 +11,9 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
|
"files": [
|
||||||
|
"dist/**/*"
|
||||||
|
],
|
||||||
"main": "./dist/cjs/index.js",
|
"main": "./dist/cjs/index.js",
|
||||||
"module": "./dist/esm/index.js",
|
"module": "./dist/esm/index.js",
|
||||||
"types": "./dist/types/index.d.ts",
|
"types": "./dist/types/index.d.ts",
|
||||||
|
@ -25,7 +28,8 @@
|
||||||
"lint": "eslint src/ --ext .ts,.tsx",
|
"lint": "eslint src/ --ext .ts,.tsx",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"ts-node": "ts-node",
|
"ts-node": "ts-node",
|
||||||
"docs": "typedoc --entryPoints src/index.ts"
|
"docs": "typedoc",
|
||||||
|
"docs:watch": "typedoc --watch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.15.5",
|
"@babel/core": "^7.15.5",
|
||||||
|
@ -60,10 +64,9 @@
|
||||||
"@radix-ui/react-radio-group": "^0.0.18",
|
"@radix-ui/react-radio-group": "^0.0.18",
|
||||||
"@radix-ui/react-tooltip": "^0.0.20",
|
"@radix-ui/react-tooltip": "^0.0.20",
|
||||||
"@stitches/react": "^1.0.0",
|
"@stitches/react": "^1.0.0",
|
||||||
"@tldraw/core": "^0.0.59",
|
"@tldraw/core": "^0.0.79",
|
||||||
"perfect-freehand": "^0.5.3",
|
"perfect-freehand": "^0.5.3",
|
||||||
"react-hotkeys-hook": "^3.4.0",
|
"react-hotkeys-hook": "^3.4.0",
|
||||||
"rko": "^0.5.20"
|
"rko": "^0.5.20"
|
||||||
},
|
}
|
||||||
"gitHead": "838fabdbff1a66d4d7ee8aa5c5d117bc55acbff2"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,12 +36,6 @@ async function main() {
|
||||||
external: ['react', 'react-dom'],
|
external: ['react', 'react-dom'],
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const file of ['package.json', 'README.md']) {
|
|
||||||
fs.copyFile(file, `dist/${file}`, fs.constants.COPYFILE_EXCL, (err) => {
|
|
||||||
if (err) throw err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`✔ ${name}: Built package.`)
|
console.log(`✔ ${name}: Built package.`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`× ${name}: Build failed due to an error.`)
|
console.log(`× ${name}: Build failed due to an error.`)
|
||||||
|
|
|
@ -114,4 +114,6 @@ describe('Delete command', () => {
|
||||||
expect(tlstate.getShape('newGroup')).toBeUndefined()
|
expect(tlstate.getShape('newGroup')).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.todo('Does not delete uneffected bindings.')
|
||||||
})
|
})
|
||||||
|
|
|
@ -170,4 +170,6 @@ describe('Duplicate command', () => {
|
||||||
expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length + 1)
|
expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length + 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.todo('Does not delete uneffected bindings.')
|
||||||
})
|
})
|
||||||
|
|
|
@ -83,12 +83,14 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Which ids did we end up duplicating?
|
// Which ids did we end up duplicating?
|
||||||
const duplicatedShapeIds = Object.keys(duplicateMap)
|
const dupedShapeIds = new Set(Object.keys(duplicateMap))
|
||||||
|
|
||||||
// Handle bindings that effect duplicated shapes
|
// Handle bindings that effect duplicated shapes
|
||||||
Object.values(page.bindings).forEach((binding) => {
|
Object.values(page.bindings)
|
||||||
if (duplicatedShapeIds.includes(binding.fromId)) {
|
.filter((binding) => dupedShapeIds.has(binding.fromId) || dupedShapeIds.has(binding.toId))
|
||||||
if (duplicatedShapeIds.includes(binding.toId)) {
|
.forEach((binding) => {
|
||||||
|
if (dupedShapeIds.has(binding.fromId)) {
|
||||||
|
if (dupedShapeIds.has(binding.toId)) {
|
||||||
// If the binding is between two duplicating shapes then
|
// If the binding is between two duplicating shapes then
|
||||||
// duplicate the binding, too
|
// duplicate the binding, too
|
||||||
const duplicatedBindingId = Utils.uniqueId()
|
const duplicatedBindingId = Utils.uniqueId()
|
||||||
|
@ -142,7 +144,9 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
||||||
[currentPageId]: after,
|
[currentPageId]: after,
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[currentPageId]: { selectedIds: duplicatedShapeIds.map((id) => duplicateMap[id]) },
|
[currentPageId]: {
|
||||||
|
selectedIds: Array.from(dupedShapeIds.values()).map((id) => duplicateMap[id]),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,8 @@ import type { Patch } from 'rko'
|
||||||
export function group(
|
export function group(
|
||||||
data: Data,
|
data: Data,
|
||||||
ids: string[],
|
ids: string[],
|
||||||
groupId = Utils.uniqueId()
|
groupId: string,
|
||||||
|
pageId: string
|
||||||
): TLDrawCommand | undefined {
|
): TLDrawCommand | undefined {
|
||||||
const beforeShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
const beforeShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
||||||
const afterShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
const afterShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
||||||
|
@ -15,8 +16,6 @@ export function group(
|
||||||
const beforeBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
const beforeBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
||||||
const afterBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
const afterBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
||||||
|
|
||||||
const { currentPageId } = data.appState
|
|
||||||
|
|
||||||
const idsToGroup = [...ids]
|
const idsToGroup = [...ids]
|
||||||
const shapesToGroup: TLDrawShape[] = []
|
const shapesToGroup: TLDrawShape[] = []
|
||||||
const deletedGroupIds: string[] = []
|
const deletedGroupIds: string[] = []
|
||||||
|
@ -24,13 +23,13 @@ export function group(
|
||||||
|
|
||||||
// Collect all of the shapes to group (and their ids)
|
// Collect all of the shapes to group (and their ids)
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
const shape = TLDR.getShape(data, id, currentPageId)
|
const shape = TLDR.getShape(data, id, pageId)
|
||||||
if (shape.children === undefined) {
|
if (shape.children === undefined) {
|
||||||
shapesToGroup.push(shape)
|
shapesToGroup.push(shape)
|
||||||
} else {
|
} else {
|
||||||
otherEffectedGroups.push(shape)
|
otherEffectedGroups.push(shape)
|
||||||
idsToGroup.push(...shape.children)
|
idsToGroup.push(...shape.children)
|
||||||
shapesToGroup.push(...shape.children.map((id) => TLDR.getShape(data, id, currentPageId)))
|
shapesToGroup.push(...shape.children.map((id) => TLDR.getShape(data, id, pageId)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +38,8 @@ export function group(
|
||||||
// Do the shapes have the same parent?
|
// Do the shapes have the same parent?
|
||||||
if (shapesToGroup.every((shape) => shape.parentId === shapesToGroup[0].parentId)) {
|
if (shapesToGroup.every((shape) => shape.parentId === shapesToGroup[0].parentId)) {
|
||||||
// Is the common parent a shape (not the page)?
|
// Is the common parent a shape (not the page)?
|
||||||
if (shapesToGroup[0].parentId !== currentPageId) {
|
if (shapesToGroup[0].parentId !== pageId) {
|
||||||
const commonParent = TLDR.getShape(data, shapesToGroup[0].parentId, currentPageId)
|
const commonParent = TLDR.getShape(data, shapesToGroup[0].parentId, pageId)
|
||||||
// Are all of the common parent's shapes selected?
|
// Are all of the common parent's shapes selected?
|
||||||
if (commonParent.children?.length === idsToGroup.length) {
|
if (commonParent.children?.length === idsToGroup.length) {
|
||||||
// Don't create a group if that group would be the same as the
|
// Don't create a group if that group would be the same as the
|
||||||
|
@ -51,7 +50,7 @@ export function group(
|
||||||
}
|
}
|
||||||
|
|
||||||
// A flattened array of shapes from the page
|
// A flattened array of shapes from the page
|
||||||
const flattenedShapes = TLDR.flattenPage(data, currentPageId)
|
const flattenedShapes = TLDR.flattenPage(data, pageId)
|
||||||
|
|
||||||
// A map of shapes to their index in flattendShapes
|
// A map of shapes to their index in flattendShapes
|
||||||
const shapeIndexMap = Object.fromEntries(
|
const shapeIndexMap = Object.fromEntries(
|
||||||
|
@ -62,13 +61,13 @@ export function group(
|
||||||
const sortedShapes = shapesToGroup.sort((a, b) => shapeIndexMap[a.id] - shapeIndexMap[b.id])
|
const sortedShapes = shapesToGroup.sort((a, b) => shapeIndexMap[a.id] - shapeIndexMap[b.id])
|
||||||
|
|
||||||
// The parentId is always the current page
|
// The parentId is always the current page
|
||||||
const groupParentId = currentPageId // sortedShapes[0].parentId
|
const groupParentId = pageId // sortedShapes[0].parentId
|
||||||
|
|
||||||
// The childIndex should be the lowest index of the selected shapes
|
// The childIndex should be the lowest index of the selected shapes
|
||||||
// with a parent that is the current page; or else the child index
|
// with a parent that is the current page; or else the child index
|
||||||
// of the lowest selected shape.
|
// of the lowest selected shape.
|
||||||
const groupChildIndex = (
|
const groupChildIndex = (
|
||||||
sortedShapes.filter((shape) => shape.parentId === currentPageId)[0] || sortedShapes[0]
|
sortedShapes.filter((shape) => shape.parentId === pageId)[0] || sortedShapes[0]
|
||||||
).childIndex
|
).childIndex
|
||||||
|
|
||||||
// The shape's point is the min point of its childrens' common bounds
|
// The shape's point is the min point of its childrens' common bounds
|
||||||
|
@ -89,8 +88,8 @@ export function group(
|
||||||
// Reparent shapes to the new group
|
// Reparent shapes to the new group
|
||||||
sortedShapes.forEach((shape, index) => {
|
sortedShapes.forEach((shape, index) => {
|
||||||
// If the shape is part of a different group, mark the parent shape for cleanup
|
// If the shape is part of a different group, mark the parent shape for cleanup
|
||||||
if (shape.parentId !== currentPageId) {
|
if (shape.parentId !== pageId) {
|
||||||
const parentShape = TLDR.getShape(data, shape.parentId, currentPageId)
|
const parentShape = TLDR.getShape(data, shape.parentId, pageId)
|
||||||
otherEffectedGroups.push(parentShape)
|
otherEffectedGroups.push(parentShape)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +123,9 @@ export function group(
|
||||||
|
|
||||||
// And if that parent is part of a different group, mark it for cleanup
|
// And if that parent is part of a different group, mark it for cleanup
|
||||||
// (This is necessary only when we implement nested groups.)
|
// (This is necessary only when we implement nested groups.)
|
||||||
if (shape.parentId !== currentPageId) {
|
if (shape.parentId !== pageId) {
|
||||||
deletedGroupIds.push(shape.id)
|
deletedGroupIds.push(shape.id)
|
||||||
otherEffectedGroups.push(TLDR.getShape(data, shape.parentId, currentPageId))
|
otherEffectedGroups.push(TLDR.getShape(data, shape.parentId, pageId))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
beforeShapes[shape.id] = {
|
beforeShapes[shape.id] = {
|
||||||
|
@ -143,7 +142,7 @@ export function group(
|
||||||
|
|
||||||
// TODO: This code is copied from delete.command. Create a shared helper!
|
// TODO: This code is copied from delete.command. Create a shared helper!
|
||||||
|
|
||||||
const page = TLDR.getPage(data, currentPageId)
|
const page = TLDR.getPage(data, pageId)
|
||||||
|
|
||||||
// We also need to delete bindings that reference the deleted shapes
|
// We also need to delete bindings that reference the deleted shapes
|
||||||
Object.values(page.bindings).forEach((binding) => {
|
Object.values(page.bindings).forEach((binding) => {
|
||||||
|
@ -155,7 +154,7 @@ export function group(
|
||||||
afterBindings[binding.id] = undefined
|
afterBindings[binding.id] = undefined
|
||||||
|
|
||||||
// Let's also look each the bound shape...
|
// Let's also look each the bound shape...
|
||||||
const shape = TLDR.getShape(data, id, currentPageId)
|
const shape = TLDR.getShape(data, id, pageId)
|
||||||
|
|
||||||
// If the bound shape has a handle that references the deleted binding...
|
// If the bound shape has a handle that references the deleted binding...
|
||||||
if (shape.handles) {
|
if (shape.handles) {
|
||||||
|
@ -193,14 +192,14 @@ export function group(
|
||||||
before: {
|
before: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[currentPageId]: {
|
[pageId]: {
|
||||||
shapes: beforeShapes,
|
shapes: beforeShapes,
|
||||||
bindings: beforeBindings,
|
bindings: beforeBindings,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[currentPageId]: {
|
[pageId]: {
|
||||||
selectedIds: TLDR.getSelectedIds(data, currentPageId),
|
selectedIds: TLDR.getSelectedIds(data, pageId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -208,13 +207,13 @@ export function group(
|
||||||
after: {
|
after: {
|
||||||
document: {
|
document: {
|
||||||
pages: {
|
pages: {
|
||||||
[currentPageId]: {
|
[pageId]: {
|
||||||
shapes: afterShapes,
|
shapes: afterShapes,
|
||||||
bindings: beforeBindings,
|
bindings: beforeBindings,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageStates: {
|
pageStates: {
|
||||||
[currentPageId]: {
|
[pageId]: {
|
||||||
selectedIds: [groupId],
|
selectedIds: [groupId],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,7 @@ export * from './duplicate-page'
|
||||||
export * from './duplicate'
|
export * from './duplicate'
|
||||||
export * from './flip'
|
export * from './flip'
|
||||||
export * from './group'
|
export * from './group'
|
||||||
|
export * from './move-to-page'
|
||||||
export * from './move'
|
export * from './move'
|
||||||
export * from './rename-page'
|
export * from './rename-page'
|
||||||
export * from './rotate'
|
export * from './rotate'
|
||||||
|
@ -17,5 +18,5 @@ export * from './style'
|
||||||
export * from './toggle-decoration'
|
export * from './toggle-decoration'
|
||||||
export * from './toggle'
|
export * from './toggle'
|
||||||
export * from './translate'
|
export * from './translate'
|
||||||
|
export * from './ungroup'
|
||||||
export * from './update'
|
export * from './update'
|
||||||
export * from './move-to-page'
|
|
||||||
|
|
|
@ -236,4 +236,6 @@ describe('Move to page command', () => {
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.todo('Does not delete uneffected bindings.')
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,36 +1,31 @@
|
||||||
import { TLDrawState } from '~state'
|
import { TLDrawState } from '~state'
|
||||||
import { mockDocument } from '~test'
|
import { Data, TLDrawShapeType } from '~types'
|
||||||
import { Utils } from '@tldraw/core'
|
|
||||||
import type { Data } from '~types'
|
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
const doc = Utils.deepClone(mockDocument)
|
const tlstate = new TLDrawState().createShapes(
|
||||||
|
{
|
||||||
doc.pages.page1.shapes['a'] = {
|
type: TLDrawShapeType.Rectangle,
|
||||||
...doc.pages.page1.shapes['rect1'],
|
|
||||||
id: 'a',
|
id: 'a',
|
||||||
childIndex: 1,
|
childIndex: 1.0,
|
||||||
}
|
},
|
||||||
doc.pages.page1.shapes['b'] = {
|
{
|
||||||
...doc.pages.page1.shapes['rect1'],
|
type: TLDrawShapeType.Rectangle,
|
||||||
id: 'b',
|
id: 'b',
|
||||||
childIndex: 2,
|
childIndex: 2.0,
|
||||||
}
|
},
|
||||||
doc.pages.page1.shapes['c'] = {
|
{
|
||||||
...doc.pages.page1.shapes['rect1'],
|
type: TLDrawShapeType.Rectangle,
|
||||||
id: 'c',
|
id: 'c',
|
||||||
childIndex: 3,
|
childIndex: 3,
|
||||||
}
|
},
|
||||||
doc.pages.page1.shapes['d'] = {
|
{
|
||||||
...doc.pages.page1.shapes['rect1'],
|
type: TLDrawShapeType.Rectangle,
|
||||||
id: 'd',
|
id: 'd',
|
||||||
childIndex: 4,
|
childIndex: 4,
|
||||||
}
|
}
|
||||||
doc.pageStates.page1.selectedIds = ['a']
|
)
|
||||||
|
|
||||||
delete doc.pages.page1.shapes['rect1']
|
const doc = { ...tlstate.document }
|
||||||
delete doc.pages.page1.shapes['rect2']
|
|
||||||
delete doc.pages.page1.shapes['rect3']
|
|
||||||
|
|
||||||
function getSortedShapeIds(data: Data) {
|
function getSortedShapeIds(data: Data) {
|
||||||
return TLDR.getShapes(data, data.appState.currentPageId)
|
return TLDR.getShapes(data, data.appState.currentPageId)
|
||||||
|
@ -39,9 +34,14 @@ function getSortedShapeIds(data: Data) {
|
||||||
.join('')
|
.join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Move command', () => {
|
function getSortedIndices(data: Data) {
|
||||||
const tlstate = new TLDrawState()
|
return TLDR.getShapes(data, data.appState.currentPageId)
|
||||||
|
.sort((a, b) => a.childIndex - b.childIndex)
|
||||||
|
.map((shape) => shape.childIndex.toFixed(2))
|
||||||
|
.join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Move command', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
})
|
})
|
||||||
|
@ -72,18 +72,21 @@ describe('Move command', () => {
|
||||||
tlstate.select('b')
|
tlstate.select('b')
|
||||||
tlstate.moveToBack()
|
tlstate.moveToBack()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('bacd')
|
expect(getSortedShapeIds(tlstate.state)).toBe('bacd')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('0.50,1.00,3.00,4.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings to back', () => {
|
it('moves two adjacent siblings to back', () => {
|
||||||
tlstate.select('b', 'c')
|
tlstate.select('b', 'c')
|
||||||
tlstate.moveToBack()
|
tlstate.moveToBack()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('bcad')
|
expect(getSortedShapeIds(tlstate.state)).toBe('bcad')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('0.33,0.67,1.00,4.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings to back', () => {
|
it('moves two non-adjacent siblings to back', () => {
|
||||||
tlstate.select('b', 'd')
|
tlstate.select('b', 'd')
|
||||||
tlstate.moveToBack()
|
tlstate.moveToBack()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('bdac')
|
expect(getSortedShapeIds(tlstate.state)).toBe('bdac')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('0.33,0.67,1.00,3.00')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -92,30 +95,35 @@ describe('Move command', () => {
|
||||||
tlstate.select('c')
|
tlstate.select('c')
|
||||||
tlstate.moveBackward()
|
tlstate.moveBackward()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('acbd')
|
expect(getSortedShapeIds(tlstate.state)).toBe('acbd')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('1.00,1.50,2.00,4.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves a shape at first index backward', () => {
|
it('moves a shape at first index backward', () => {
|
||||||
tlstate.select('a')
|
tlstate.select('a')
|
||||||
tlstate.moveBackward()
|
tlstate.moveBackward()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
|
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('1.00,2.00,3.00,4.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings backward', () => {
|
it('moves two adjacent siblings backward', () => {
|
||||||
tlstate.select('c', 'd')
|
tlstate.select('c', 'd')
|
||||||
tlstate.moveBackward()
|
tlstate.moveBackward()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('acdb')
|
expect(getSortedShapeIds(tlstate.state)).toBe('acdb')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('1.00,1.50,1.67,2.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings backward', () => {
|
it('moves two non-adjacent siblings backward', () => {
|
||||||
tlstate.select('b', 'd')
|
tlstate.select('b', 'd')
|
||||||
tlstate.moveBackward()
|
tlstate.moveBackward()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('badc')
|
expect(getSortedShapeIds(tlstate.state)).toBe('badc')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('0.50,1.00,2.50,3.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings backward at zero index', () => {
|
it('moves two adjacent siblings backward at zero index', () => {
|
||||||
tlstate.select('a', 'b')
|
tlstate.select('a', 'b')
|
||||||
tlstate.moveBackward()
|
tlstate.moveBackward()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
|
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('1.00,2.00,3.00,4.00')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -124,6 +132,7 @@ describe('Move command', () => {
|
||||||
tlstate.select('c')
|
tlstate.select('c')
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('abdc')
|
expect(getSortedShapeIds(tlstate.state)).toBe('abdc')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('1.00,2.00,4.00,5.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves a shape forward at the top index', () => {
|
it('moves a shape forward at the top index', () => {
|
||||||
|
@ -132,24 +141,28 @@ describe('Move command', () => {
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('acdb')
|
expect(getSortedShapeIds(tlstate.state)).toBe('acdb')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('1.00,3.00,4.00,5.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings forward', () => {
|
it('moves two adjacent siblings forward', () => {
|
||||||
tlstate.select('a', 'b')
|
tlstate.select('a', 'b')
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('cabd')
|
expect(getSortedShapeIds(tlstate.state)).toBe('cabd')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('3.00,3.33,3.50,4.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings forward', () => {
|
it('moves two non-adjacent siblings forward', () => {
|
||||||
tlstate.select('a', 'c')
|
tlstate.select('a', 'c')
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('badc')
|
expect(getSortedShapeIds(tlstate.state)).toBe('badc')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('2.00,2.50,4.00,5.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings forward at top index', () => {
|
it('moves two adjacent siblings forward at top index', () => {
|
||||||
tlstate.select('c', 'd')
|
tlstate.select('c', 'd')
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
|
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('1.00,2.00,3.00,4.00')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -158,24 +171,28 @@ describe('Move command', () => {
|
||||||
tlstate.select('b')
|
tlstate.select('b')
|
||||||
tlstate.moveToFront()
|
tlstate.moveToFront()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('acdb')
|
expect(getSortedShapeIds(tlstate.state)).toBe('acdb')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('1.00,3.00,4.00,5.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings to front', () => {
|
it('moves two adjacent siblings to front', () => {
|
||||||
tlstate.select('a', 'b')
|
tlstate.select('a', 'b')
|
||||||
tlstate.moveToFront()
|
tlstate.moveToFront()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('cdab')
|
expect(getSortedShapeIds(tlstate.state)).toBe('cdab')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('3.00,4.00,5.00,6.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings to front', () => {
|
it('moves two non-adjacent siblings to front', () => {
|
||||||
tlstate.select('a', 'c')
|
tlstate.select('a', 'c')
|
||||||
tlstate.moveToFront()
|
tlstate.moveToFront()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('bdac')
|
expect(getSortedShapeIds(tlstate.state)).toBe('bdac')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('2.00,4.00,5.00,6.00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves siblings already at front to front', () => {
|
it('moves siblings already at front to front', () => {
|
||||||
tlstate.select('c', 'd')
|
tlstate.select('c', 'd')
|
||||||
tlstate.moveToFront()
|
tlstate.moveToFront()
|
||||||
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
|
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
|
||||||
|
expect(getSortedIndices(tlstate.state)).toBe('1.00,2.00,3.00,4.00')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -122,13 +122,26 @@ export function move(data: Data, ids: string[], type: MoveType): TLDrawCommand {
|
||||||
// i = the index of the first closed spot
|
// i = the index of the first closed spot
|
||||||
// j = the index of the first open spot
|
// j = the index of the first open spot
|
||||||
|
|
||||||
startChildIndex =
|
const endChildIndex = sortedChildren[j].childIndex
|
||||||
j === 0 ? sortedChildren[j].childIndex / 2 : sortedChildren[j - 1].childIndex
|
let startChildIndex: number
|
||||||
|
let step: number
|
||||||
|
|
||||||
const step = (sortedChildren[j].childIndex - startChildIndex) / (i - j + 1)
|
if (j === 0) {
|
||||||
|
// We're moving below the first child, start from
|
||||||
|
// half of its child index.
|
||||||
|
|
||||||
|
startChildIndex = endChildIndex / 2
|
||||||
|
step = endChildIndex / 2 / (i - j + 1)
|
||||||
|
} else {
|
||||||
|
// Start from the child index of the child below the
|
||||||
|
// child above.
|
||||||
|
startChildIndex = sortedChildren[j - 1].childIndex
|
||||||
|
step = (endChildIndex - startChildIndex) / (i - j + 1)
|
||||||
|
startChildIndex += step
|
||||||
|
}
|
||||||
|
|
||||||
for (let k = 0; k < i - j; k++) {
|
for (let k = 0; k < i - j; k++) {
|
||||||
indexMap[sortedChildren[j + k + 1].id] = startChildIndex + step * (k + 1)
|
indexMap[sortedChildren[j + k + 1].id] = startChildIndex + step * k
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
1
packages/tldraw/src/state/command/ungroup/index.ts
Normal file
1
packages/tldraw/src/state/command/ungroup/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './ungroup.command'
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
|
import { TLDrawState } from '~state'
|
||||||
|
import { mockDocument } from '~test'
|
||||||
|
import { GroupShape, TLDrawShapeType } from '~types'
|
||||||
|
|
||||||
|
describe('Ungroup command', () => {
|
||||||
|
const tlstate = new TLDrawState()
|
||||||
|
|
||||||
|
it('does, undoes and redoes command', () => {
|
||||||
|
tlstate
|
||||||
|
.loadDocument(mockDocument)
|
||||||
|
.group(['rect1', 'rect2'], 'groupA')
|
||||||
|
.select('groupA')
|
||||||
|
.ungroup()
|
||||||
|
|
||||||
|
expect(tlstate.getShape<GroupShape>('groupA')).toBeUndefined()
|
||||||
|
expect(tlstate.getShape('rect1').parentId).toBe('page1')
|
||||||
|
expect(tlstate.getShape('rect2').parentId).toBe('page1')
|
||||||
|
|
||||||
|
tlstate.undo()
|
||||||
|
|
||||||
|
expect(tlstate.getShape<GroupShape>('groupA')).toBeDefined()
|
||||||
|
expect(tlstate.getShape<GroupShape>('groupA').children).toStrictEqual(['rect1', 'rect2'])
|
||||||
|
expect(tlstate.getShape('rect1').parentId).toBe('groupA')
|
||||||
|
expect(tlstate.getShape('rect2').parentId).toBe('groupA')
|
||||||
|
|
||||||
|
tlstate.redo()
|
||||||
|
|
||||||
|
expect(tlstate.getShape<GroupShape>('groupA')).toBeUndefined()
|
||||||
|
expect(tlstate.getShape('rect1').parentId).toBe('page1')
|
||||||
|
expect(tlstate.getShape('rect2').parentId).toBe('page1')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When ungrouping', () => {
|
||||||
|
it('Ungroups shapes on any page', () => {
|
||||||
|
tlstate
|
||||||
|
.loadDocument(mockDocument)
|
||||||
|
.group(['rect1', 'rect2'], 'groupA')
|
||||||
|
.createPage('page2')
|
||||||
|
.ungroup('groupA', 'page1')
|
||||||
|
|
||||||
|
expect(tlstate.getShape('groupA', 'page1')).toBeUndefined()
|
||||||
|
tlstate.undo()
|
||||||
|
expect(tlstate.getShape('groupA', 'page1')).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Does not ungroup if a group shape is not selected', () => {
|
||||||
|
tlstate.loadDocument(mockDocument).select('rect1')
|
||||||
|
const before = tlstate.state
|
||||||
|
tlstate.group()
|
||||||
|
// State should not have changed
|
||||||
|
expect(tlstate.state).toStrictEqual(before)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Reparents shapes to the page at the correct childIndex', () => {
|
||||||
|
const tlstate = new TLDrawState()
|
||||||
|
.createShapes(
|
||||||
|
{
|
||||||
|
id: 'rect1',
|
||||||
|
type: TLDrawShapeType.Rectangle,
|
||||||
|
childIndex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rect2',
|
||||||
|
type: TLDrawShapeType.Rectangle,
|
||||||
|
childIndex: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rect3',
|
||||||
|
type: TLDrawShapeType.Rectangle,
|
||||||
|
childIndex: 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.group(['rect1', 'rect2'], 'groupA')
|
||||||
|
|
||||||
|
const { childIndex } = tlstate.getShape<GroupShape>('groupA')
|
||||||
|
|
||||||
|
expect(childIndex).toBe(1)
|
||||||
|
expect(tlstate.getShape('rect1').childIndex).toBe(1)
|
||||||
|
expect(tlstate.getShape('rect2').childIndex).toBe(2)
|
||||||
|
expect(tlstate.getShape('rect3').childIndex).toBe(3)
|
||||||
|
|
||||||
|
tlstate.ungroup('groupA')
|
||||||
|
|
||||||
|
expect(tlstate.getShape('rect1').childIndex).toBe(1)
|
||||||
|
expect(tlstate.getShape('rect2').childIndex).toBe(2)
|
||||||
|
expect(tlstate.getShape('rect3').childIndex).toBe(3)
|
||||||
|
})
|
||||||
|
it.todo('Deletes any bindings to the group')
|
||||||
|
})
|
||||||
|
})
|
134
packages/tldraw/src/state/command/ungroup/ungroup.command.ts
Normal file
134
packages/tldraw/src/state/command/ungroup/ungroup.command.ts
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import type { GroupShape, TLDrawBinding, TLDrawShape } from '~types'
|
||||||
|
import type { Data, TLDrawCommand } from '~types'
|
||||||
|
import { TLDR } from '~state/tldr'
|
||||||
|
import type { Patch } from 'rko'
|
||||||
|
|
||||||
|
export function ungroup(data: Data, groupId: string, pageId: string): TLDrawCommand | undefined {
|
||||||
|
const beforeShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
||||||
|
const afterShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
||||||
|
|
||||||
|
const beforeBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
||||||
|
const afterBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
||||||
|
|
||||||
|
// The group shape
|
||||||
|
const groupShape = TLDR.getShape<GroupShape>(data, groupId, pageId)
|
||||||
|
|
||||||
|
const idsToUngroup = groupShape.children
|
||||||
|
const shapesToUngroup: TLDrawShape[] = []
|
||||||
|
const deletedGroupIds: string[] = []
|
||||||
|
|
||||||
|
// Collect all of the shapes to group (and their ids)
|
||||||
|
for (const id of idsToUngroup) {
|
||||||
|
const shape = TLDR.getShape(data, id, pageId)
|
||||||
|
shapesToUngroup.push(shape)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll start placing the shapes at this childIndex
|
||||||
|
const startingChildIndex = groupShape.childIndex
|
||||||
|
|
||||||
|
// And we'll need to fit them under this child index
|
||||||
|
const endingChildIndex = TLDR.getChildIndexAbove(data, groupShape.id, pageId)
|
||||||
|
|
||||||
|
const step = (endingChildIndex - startingChildIndex) / shapesToUngroup.length
|
||||||
|
|
||||||
|
// An array of shapes in order by their child index
|
||||||
|
const sortedShapes = shapesToUngroup.sort((a, b) => a.childIndex - b.childIndex)
|
||||||
|
|
||||||
|
// Remove the group shape
|
||||||
|
beforeShapes[groupId] = groupShape
|
||||||
|
afterShapes[groupId] = undefined
|
||||||
|
|
||||||
|
// Reparent shapes to the page
|
||||||
|
sortedShapes.forEach((shape, index) => {
|
||||||
|
beforeShapes[shape.id] = {
|
||||||
|
parentId: shape.parentId,
|
||||||
|
childIndex: shape.childIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
afterShapes[shape.id] = {
|
||||||
|
parentId: pageId,
|
||||||
|
childIndex: startingChildIndex + step * index,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const page = TLDR.getPage(data, pageId)
|
||||||
|
|
||||||
|
// We also need to delete bindings that reference the deleted shapes
|
||||||
|
Object.values(page.bindings)
|
||||||
|
.filter((binding) => binding.toId === groupId || binding.fromId === groupId)
|
||||||
|
.forEach((binding) => {
|
||||||
|
for (const id of [binding.toId, binding.fromId]) {
|
||||||
|
// If the binding references the deleted group...
|
||||||
|
if (afterShapes[id] === undefined) {
|
||||||
|
// Delete the binding
|
||||||
|
beforeBindings[binding.id] = binding
|
||||||
|
afterBindings[binding.id] = undefined
|
||||||
|
|
||||||
|
// Let's also look each the bound shape...
|
||||||
|
const shape = TLDR.getShape(data, id, pageId)
|
||||||
|
|
||||||
|
// If the bound shape has a handle that references the deleted binding...
|
||||||
|
if (shape.handles) {
|
||||||
|
Object.values(shape.handles)
|
||||||
|
.filter((handle) => handle.bindingId === binding.id)
|
||||||
|
.forEach((handle) => {
|
||||||
|
// Save the binding reference in the before patch
|
||||||
|
beforeShapes[id] = {
|
||||||
|
...beforeShapes[id],
|
||||||
|
handles: {
|
||||||
|
...beforeShapes[id]?.handles,
|
||||||
|
[handle.id]: { bindingId: binding.id },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unless we're currently deleting the shape, remove the
|
||||||
|
// binding reference from the after patch
|
||||||
|
if (!deletedGroupIds.includes(id)) {
|
||||||
|
afterShapes[id] = {
|
||||||
|
...afterShapes[id],
|
||||||
|
handles: {
|
||||||
|
...afterShapes[id]?.handles,
|
||||||
|
[handle.id]: { bindingId: undefined },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: 'ungroup_shapes',
|
||||||
|
before: {
|
||||||
|
document: {
|
||||||
|
pages: {
|
||||||
|
[pageId]: {
|
||||||
|
shapes: beforeShapes,
|
||||||
|
bindings: beforeBindings,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pageStates: {
|
||||||
|
[pageId]: {
|
||||||
|
selectedIds: [groupId],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
document: {
|
||||||
|
pages: {
|
||||||
|
[pageId]: {
|
||||||
|
shapes: afterShapes,
|
||||||
|
bindings: beforeBindings,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pageStates: {
|
||||||
|
[pageId]: {
|
||||||
|
selectedIds: idsToUngroup,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,7 +52,9 @@ export function removeShapesFromPage(data: Data, ids: string[], pageId: string)
|
||||||
const page = TLDR.getPage(data, pageId)
|
const page = TLDR.getPage(data, pageId)
|
||||||
|
|
||||||
// We also need to delete bindings that reference the deleted shapes
|
// We also need to delete bindings that reference the deleted shapes
|
||||||
Object.values(page.bindings).forEach((binding) => {
|
Object.values(page.bindings)
|
||||||
|
.filter((binding) => deletedIds.has(binding.fromId) || deletedIds.has(binding.toId))
|
||||||
|
.forEach((binding) => {
|
||||||
for (const id of [binding.toId, binding.fromId]) {
|
for (const id of [binding.toId, binding.fromId]) {
|
||||||
// If the binding references a deleted shape...
|
// If the binding references a deleted shape...
|
||||||
if (after.shapes[id] === undefined) {
|
if (after.shapes[id] === undefined) {
|
||||||
|
@ -82,7 +84,10 @@ export function removeShapesFromPage(data: Data, ids: string[], pageId: string)
|
||||||
if (!deletedIds.has(id)) {
|
if (!deletedIds.has(id)) {
|
||||||
after.shapes[id] = {
|
after.shapes[id] = {
|
||||||
...after.shapes[id],
|
...after.shapes[id],
|
||||||
handles: { ...after.shapes[id]?.handles, [handle.id]: { bindingId: undefined } },
|
handles: {
|
||||||
|
...after.shapes[id]?.handles,
|
||||||
|
[handle.id]: { bindingId: undefined },
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -382,15 +382,17 @@ export function getTranslateSnapshot(data: Data) {
|
||||||
|
|
||||||
// Potentially confusing name here: these are the ids of the
|
// Potentially confusing name here: these are the ids of the
|
||||||
// original shapes that were cloned, not their clones' ids.
|
// original shapes that were cloned, not their clones' ids.
|
||||||
const clonedShapeIds = Object.keys(cloneMap)
|
const clonedShapeIds = new Set(Object.keys(cloneMap))
|
||||||
|
|
||||||
const bindingsToDelete: TLDrawBinding[] = []
|
const bindingsToDelete: TLDrawBinding[] = []
|
||||||
|
|
||||||
// Create cloned bindings for shapes where both to and from shapes are selected
|
// Create cloned bindings for shapes where both to and from shapes are selected
|
||||||
// (if the user clones, then we will create a new binding for the clones)
|
// (if the user clones, then we will create a new binding for the clones)
|
||||||
Object.values(page.bindings).forEach((binding) => {
|
Object.values(page.bindings)
|
||||||
if (clonedShapeIds.includes(binding.fromId)) {
|
.filter((binding) => clonedShapeIds.has(binding.fromId) || clonedShapeIds.has(binding.toId))
|
||||||
if (clonedShapeIds.includes(binding.toId)) {
|
.forEach((binding) => {
|
||||||
|
if (clonedShapeIds.has(binding.fromId)) {
|
||||||
|
if (clonedShapeIds.has(binding.toId)) {
|
||||||
const cloneId = Utils.uniqueId()
|
const cloneId = Utils.uniqueId()
|
||||||
const cloneBinding = {
|
const cloneBinding = {
|
||||||
...Utils.deepClone(binding),
|
...Utils.deepClone(binding),
|
||||||
|
|
|
@ -374,8 +374,7 @@ export class TLDR {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getChildIndexAbove(data: Data, id: string, pageId: string): number {
|
static getChildIndexAbove(data: Data, id: string, pageId: string): number {
|
||||||
const page = this.getPage(data, pageId)
|
const page = data.document.pages[pageId]
|
||||||
|
|
||||||
const shape = page.shapes[id]
|
const shape = page.shapes[id]
|
||||||
|
|
||||||
let siblings: TLDrawShape[]
|
let siblings: TLDrawShape[]
|
||||||
|
@ -398,7 +397,7 @@ export class TLDR {
|
||||||
|
|
||||||
if (!nextSibling) return shape.childIndex + 1
|
if (!nextSibling) return shape.childIndex + 1
|
||||||
|
|
||||||
return (shape.childIndex + nextSibling.childIndex) / 2
|
return nextSibling.childIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
|
|
|
@ -333,7 +333,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle pen mode.
|
* Toggle pen mode.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
togglePenMode = (): this => {
|
togglePenMode = (): this => {
|
||||||
if (this.session) return this
|
if (this.session) return this
|
||||||
|
@ -349,7 +348,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle dark mode.
|
* Toggle dark mode.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
toggleDarkMode = (): this => {
|
toggleDarkMode = (): this => {
|
||||||
if (this.session) return this
|
if (this.session) return this
|
||||||
|
@ -363,7 +361,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle zoom snap.
|
* Toggle zoom snap.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
toggleZoomSnap = () => {
|
toggleZoomSnap = () => {
|
||||||
if (this.session) return this
|
if (this.session) return this
|
||||||
|
@ -377,7 +374,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle debug mode.
|
* Toggle debug mode.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
toggleDebugMode = () => {
|
toggleDebugMode = () => {
|
||||||
if (this.session) return this
|
if (this.session) return this
|
||||||
|
@ -391,7 +387,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the style panel.
|
* Toggle the style panel.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
toggleStylePanel = (): this => {
|
toggleStylePanel = (): this => {
|
||||||
if (this.session) return this
|
if (this.session) return this
|
||||||
|
@ -405,8 +400,7 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a tool.
|
* Select a tool.
|
||||||
* @param tool The tool to select.
|
* @param tool The tool to select, or "select".
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
selectTool = (tool: TLDrawShapeType | 'select'): this => {
|
selectTool = (tool: TLDrawShapeType | 'select'): this => {
|
||||||
if (this.session) return this
|
if (this.session) return this
|
||||||
|
@ -426,7 +420,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the tool lock option.
|
* Toggle the tool lock option.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
toggleToolLock = (): this => {
|
toggleToolLock = (): this => {
|
||||||
if (this.session) return this
|
if (this.session) return this
|
||||||
|
@ -446,7 +439,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the document to a blank state.
|
* Reset the document to a blank state.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
resetDocument = (): this => {
|
resetDocument = (): this => {
|
||||||
if (this.session) return this
|
if (this.session) return this
|
||||||
|
@ -483,8 +475,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Load a new document.
|
* Load a new document.
|
||||||
* @param document The document to load
|
* @param document The document to load
|
||||||
* @param onChange (optional) A callback to call when the document changes
|
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
loadDocument = (document: TLDrawDocument): this => {
|
loadDocument = (document: TLDrawDocument): this => {
|
||||||
this.deselectAll()
|
this.deselectAll()
|
||||||
|
@ -532,18 +522,38 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new project.
|
||||||
|
* Should move to the www layer.
|
||||||
|
* @todo
|
||||||
|
*/
|
||||||
newProject = () => {
|
newProject = () => {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the current project.
|
||||||
|
* Should move to the www layer.
|
||||||
|
* @todo
|
||||||
|
*/
|
||||||
saveProject = () => {
|
saveProject = () => {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a project from the filesystem.
|
||||||
|
* Should move to the www layer.
|
||||||
|
* @todo
|
||||||
|
*/
|
||||||
loadProject = () => {
|
loadProject = () => {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign out of the current account.
|
||||||
|
* Should move to the www layer.
|
||||||
|
* @todo
|
||||||
|
*/
|
||||||
signOut = () => {
|
signOut = () => {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
@ -551,7 +561,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current app state.
|
* Get the current app state.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
getAppState = (): Data['appState'] => {
|
getAppState = (): Data['appState'] => {
|
||||||
return this.appState
|
return this.appState
|
||||||
|
@ -560,7 +569,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Get a page.
|
* Get a page.
|
||||||
* @param pageId (optional) The page's id.
|
* @param pageId (optional) The page's id.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
getPage = (pageId = this.currentPageId): TLDrawPage => {
|
getPage = (pageId = this.currentPageId): TLDrawPage => {
|
||||||
return TLDR.getPage(this.state, pageId || this.currentPageId)
|
return TLDR.getPage(this.state, pageId || this.currentPageId)
|
||||||
|
@ -569,7 +577,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Get the shapes (as an array) from a given page.
|
* Get the shapes (as an array) from a given page.
|
||||||
* @param pageId (optional) The page's id.
|
* @param pageId (optional) The page's id.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
getShapes = (pageId = this.currentPageId): TLDrawShape[] => {
|
getShapes = (pageId = this.currentPageId): TLDrawShape[] => {
|
||||||
return TLDR.getShapes(this.state, pageId || this.currentPageId)
|
return TLDR.getShapes(this.state, pageId || this.currentPageId)
|
||||||
|
@ -578,7 +585,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Get the bindings from a given page.
|
* Get the bindings from a given page.
|
||||||
* @param pageId (optional) The page's id.
|
* @param pageId (optional) The page's id.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
getBindings = (pageId = this.currentPageId): TLDrawBinding[] => {
|
getBindings = (pageId = this.currentPageId): TLDrawBinding[] => {
|
||||||
return TLDR.getBindings(this.state, pageId || this.currentPageId)
|
return TLDR.getBindings(this.state, pageId || this.currentPageId)
|
||||||
|
@ -588,7 +594,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Get a shape from a given page.
|
* Get a shape from a given page.
|
||||||
* @param id The shape's id.
|
* @param id The shape's id.
|
||||||
* @param pageId (optional) The page's id.
|
* @param pageId (optional) The page's id.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
getShape = <T extends TLDrawShape = TLDrawShape>(id: string, pageId = this.currentPageId): T => {
|
getShape = <T extends TLDrawShape = TLDrawShape>(id: string, pageId = this.currentPageId): T => {
|
||||||
return TLDR.getShape<T>(this.state, id, pageId)
|
return TLDR.getShape<T>(this.state, id, pageId)
|
||||||
|
@ -598,7 +603,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Get a binding from a given page.
|
* Get a binding from a given page.
|
||||||
* @param id The binding's id.
|
* @param id The binding's id.
|
||||||
* @param pageId (optional) The page's id.
|
* @param pageId (optional) The page's id.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
getBinding = (id: string, pageId = this.currentPageId): TLDrawBinding => {
|
getBinding = (id: string, pageId = this.currentPageId): TLDrawBinding => {
|
||||||
return TLDR.getBinding(this.state, id, pageId)
|
return TLDR.getBinding(this.state, id, pageId)
|
||||||
|
@ -607,7 +611,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Get the page state for a given page.
|
* Get the page state for a given page.
|
||||||
* @param pageId (optional) The page's id.
|
* @param pageId (optional) The page's id.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
getPageState = (pageId = this.currentPageId): TLPageState => {
|
getPageState = (pageId = this.currentPageId): TLPageState => {
|
||||||
return TLDR.getPageState(this.state, pageId || this.currentPageId)
|
return TLDR.getPageState(this.state, pageId || this.currentPageId)
|
||||||
|
@ -617,7 +620,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Turn a screen point into a point on the page.
|
* Turn a screen point into a point on the page.
|
||||||
* @param point The screen point
|
* @param point The screen point
|
||||||
* @param pageId (optional) The page to use
|
* @param pageId (optional) The page to use
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
getPagePoint = (point: number[], pageId = this.currentPageId): number[] => {
|
getPagePoint = (point: number[], pageId = this.currentPageId): number[] => {
|
||||||
const { camera } = this.getPageState(pageId)
|
const { camera } = this.getPageState(pageId)
|
||||||
|
@ -685,9 +687,8 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new page page.
|
* Create a new page.
|
||||||
* @param pageId (optional) The new page's id.
|
* @param pageId (optional) The new page's id.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
createPage = (id?: string): this => {
|
createPage = (id?: string): this => {
|
||||||
return this.setState(Commands.createPage(this.state, id))
|
return this.setState(Commands.createPage(this.state, id))
|
||||||
|
@ -696,7 +697,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Change the current page.
|
* Change the current page.
|
||||||
* @param pageId The new current page's id.
|
* @param pageId The new current page's id.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
changePage = (pageId: string): this => {
|
changePage = (pageId: string): this => {
|
||||||
return this.setState(Commands.changePage(this.state, pageId))
|
return this.setState(Commands.changePage(this.state, pageId))
|
||||||
|
@ -706,7 +706,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Rename a page.
|
* Rename a page.
|
||||||
* @param pageId The id of the page to rename.
|
* @param pageId The id of the page to rename.
|
||||||
* @param name The page's new name
|
* @param name The page's new name
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
renamePage = (pageId: string, name: string): this => {
|
renamePage = (pageId: string, name: string): this => {
|
||||||
return this.setState(Commands.renamePage(this.state, pageId, name))
|
return this.setState(Commands.renamePage(this.state, pageId, name))
|
||||||
|
@ -715,7 +714,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Duplicate a page.
|
* Duplicate a page.
|
||||||
* @param pageId The id of the page to duplicate.
|
* @param pageId The id of the page to duplicate.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
duplicatePage = (pageId: string): this => {
|
duplicatePage = (pageId: string): this => {
|
||||||
return this.setState(Commands.duplicatePage(this.state, pageId))
|
return this.setState(Commands.duplicatePage(this.state, pageId))
|
||||||
|
@ -724,7 +722,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Delete a page.
|
* Delete a page.
|
||||||
* @param pageId The id of the page to delete.
|
* @param pageId The id of the page to delete.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
deletePage = (pageId?: string): this => {
|
deletePage = (pageId?: string): this => {
|
||||||
if (Object.values(this.document.pages).length <= 1) return this
|
if (Object.values(this.document.pages).length <= 1) return this
|
||||||
|
@ -738,7 +735,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Copy one or more shapes to the clipboard.
|
* Copy one or more shapes to the clipboard.
|
||||||
* @param ids The ids of the shapes to copy.
|
* @param ids The ids of the shapes to copy.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
copy = (ids = this.selectedIds): this => {
|
copy = (ids = this.selectedIds): this => {
|
||||||
this.clipboard = ids
|
this.clipboard = ids
|
||||||
|
@ -760,7 +756,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Paste shapes (or text) from clipboard to a certain point.
|
* Paste shapes (or text) from clipboard to a certain point.
|
||||||
* @param point
|
* @param point
|
||||||
* @param string
|
* @param string
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
paste = (point?: number[], string?: string): this => {
|
paste = (point?: number[], string?: string): this => {
|
||||||
if (string) {
|
if (string) {
|
||||||
|
@ -890,7 +885,7 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy one or more shapes as JSON
|
* Copy one or more shapes as JSON.
|
||||||
* @param ids The ids of the shapes to copy.
|
* @param ids The ids of the shapes to copy.
|
||||||
* @param pageId The page from which to copy the shapes.
|
* @param pageId The page from which to copy the shapes.
|
||||||
* @returns A string containing the JSON.
|
* @returns A string containing the JSON.
|
||||||
|
@ -911,7 +906,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* @param point The camera point (top left of the viewport).
|
* @param point The camera point (top left of the viewport).
|
||||||
* @param zoom The zoom level.
|
* @param zoom The zoom level.
|
||||||
* @param reason Why did the camera change?
|
* @param reason Why did the camera change?
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
setCamera = (point: number[], zoom: number, reason: string): this => {
|
setCamera = (point: number[], zoom: number, reason: string): this => {
|
||||||
return this.patchState(
|
return this.patchState(
|
||||||
|
@ -928,7 +922,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the camera to the default position
|
* Reset the camera to the default position
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
resetCamera = (): this => {
|
resetCamera = (): this => {
|
||||||
return this.setCamera(
|
return this.setCamera(
|
||||||
|
@ -941,7 +934,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Pan the camera
|
* Pan the camera
|
||||||
* @param delta
|
* @param delta
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
pan = (delta: number[]): this => {
|
pan = (delta: number[]): this => {
|
||||||
const { camera } = this.pageState
|
const { camera } = this.pageState
|
||||||
|
@ -953,7 +945,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* @param point The current point under the cursor.
|
* @param point The current point under the cursor.
|
||||||
* @param delta The movement delta.
|
* @param delta The movement delta.
|
||||||
* @param zoomDelta The zoom detal
|
* @param zoomDelta The zoom detal
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
pinchZoom = (point: number[], delta: number[], zoomDelta: number): this => {
|
pinchZoom = (point: number[], delta: number[], zoomDelta: number): this => {
|
||||||
const { camera } = this.pageState
|
const { camera } = this.pageState
|
||||||
|
@ -967,7 +958,7 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Zoom to a new zoom level, keeping the point under the cursor in the same position
|
* Zoom to a new zoom level, keeping the point under the cursor in the same position
|
||||||
* @param next The new zoom level.
|
* @param next The new zoom level.
|
||||||
* @returns this
|
* @param center The point to zoom towards (defaults to screen center).
|
||||||
*/
|
*/
|
||||||
zoomTo = (next: number, center = [window.innerWidth / 2, window.innerHeight / 2]): this => {
|
zoomTo = (next: number, center = [window.innerWidth / 2, window.innerHeight / 2]): this => {
|
||||||
const { zoom, point } = this.pageState.camera
|
const { zoom, point } = this.pageState.camera
|
||||||
|
@ -978,7 +969,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom out by 25%
|
* Zoom out by 25%
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
zoomIn = (): this => {
|
zoomIn = (): this => {
|
||||||
const i = Math.round((this.pageState.camera.zoom * 100) / 25)
|
const i = Math.round((this.pageState.camera.zoom * 100) / 25)
|
||||||
|
@ -988,7 +978,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom in by 25%.
|
* Zoom in by 25%.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
zoomOut = (): this => {
|
zoomOut = (): this => {
|
||||||
const i = Math.round((this.pageState.camera.zoom * 100) / 25)
|
const i = Math.round((this.pageState.camera.zoom * 100) / 25)
|
||||||
|
@ -998,7 +987,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom to fit the page's shapes.
|
* Zoom to fit the page's shapes.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
zoomToFit = (): this => {
|
zoomToFit = (): this => {
|
||||||
const shapes = this.getShapes()
|
const shapes = this.getShapes()
|
||||||
|
@ -1025,7 +1013,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom to the selected shapes.
|
* Zoom to the selected shapes.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
zoomToSelection = (): this => {
|
zoomToSelection = (): this => {
|
||||||
if (this.selectedIds.length === 0) return this
|
if (this.selectedIds.length === 0) return this
|
||||||
|
@ -1050,7 +1037,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom back to content when the canvas is empty.
|
* Zoom back to content when the canvas is empty.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
zoomToContent = (): this => {
|
zoomToContent = (): this => {
|
||||||
const shapes = this.getShapes()
|
const shapes = this.getShapes()
|
||||||
|
@ -1073,7 +1059,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom the camera to 100%.
|
* Zoom the camera to 100%.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
zoomToActual = (): this => {
|
zoomToActual = (): this => {
|
||||||
return this.zoomTo(1)
|
return this.zoomTo(1)
|
||||||
|
@ -1081,12 +1066,13 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom the camera by a certain delta.
|
* Zoom the camera by a certain delta.
|
||||||
* @returns this
|
* @param delta The zoom delta.
|
||||||
|
* @param center The point to zoom toward.
|
||||||
*/
|
*/
|
||||||
zoom = Utils.throttle((delta: number): this => {
|
zoom = Utils.throttle((delta: number, center?: number[]): this => {
|
||||||
const { zoom } = this.pageState.camera
|
const { zoom } = this.pageState.camera
|
||||||
const nextZoom = TLDR.getCameraZoom(zoom - delta * zoom)
|
const nextZoom = TLDR.getCameraZoom(zoom - delta * zoom)
|
||||||
return this.zoomTo(nextZoom)
|
return this.zoomTo(nextZoom, center)
|
||||||
}, 16)
|
}, 16)
|
||||||
|
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
|
@ -1118,7 +1104,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Set the current selection.
|
* Set the current selection.
|
||||||
* @param ids The ids to select
|
* @param ids The ids to select
|
||||||
* @param push Whether to add the ids to the current selection instead.
|
* @param push Whether to add the ids to the current selection instead.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
private setSelectedIds = (ids: string[], push = false): this => {
|
private setSelectedIds = (ids: string[], push = false): this => {
|
||||||
return this.patchState(
|
return this.patchState(
|
||||||
|
@ -1141,7 +1126,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Undo the most recent selection.
|
* Undo the most recent selection.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
undoSelect = (): this => {
|
undoSelect = (): this => {
|
||||||
if (this.selectHistory.pointer > 0) {
|
if (this.selectHistory.pointer > 0) {
|
||||||
|
@ -1153,7 +1137,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redo the previous selection.
|
* Redo the previous selection.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
redoSelect = (): this => {
|
redoSelect = (): this => {
|
||||||
if (this.selectHistory.pointer < this.selectHistory.stack.length - 1) {
|
if (this.selectHistory.pointer < this.selectHistory.stack.length - 1) {
|
||||||
|
@ -1166,7 +1149,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Select one or more shapes.
|
* Select one or more shapes.
|
||||||
* @param ids The shape ids to select.
|
* @param ids The shape ids to select.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
select = (...ids: string[]): this => {
|
select = (...ids: string[]): this => {
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
|
@ -1181,7 +1163,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select all shapes on the page.
|
* Select all shapes on the page.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
selectAll = (): this => {
|
selectAll = (): this => {
|
||||||
if (this.session) return this
|
if (this.session) return this
|
||||||
|
@ -1195,7 +1176,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deselect any selected shapes.
|
* Deselect any selected shapes.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
deselectAll = (): this => {
|
deselectAll = (): this => {
|
||||||
this.setSelectedIds([])
|
this.setSelectedIds([])
|
||||||
|
@ -1211,7 +1191,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Start a new session.
|
* Start a new session.
|
||||||
* @param session The new session
|
* @param session The new session
|
||||||
* @param args arguments of the session's start method.
|
* @param args arguments of the session's start method.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
startSession = <T extends Session>(
|
startSession = <T extends Session>(
|
||||||
session: T,
|
session: T,
|
||||||
|
@ -1243,7 +1222,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Update the current session.
|
* Update the current session.
|
||||||
* @param args The arguments of the current session's update method.
|
* @param args The arguments of the current session's update method.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
updateSession = <T extends Session>(...args: ParametersExceptFirst<T['update']>): this => {
|
updateSession = <T extends Session>(...args: ParametersExceptFirst<T['update']>): this => {
|
||||||
const { session } = this
|
const { session } = this
|
||||||
|
@ -1256,7 +1234,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Cancel the current session.
|
* Cancel the current session.
|
||||||
* @param args The arguments of the current session's cancel method.
|
* @param args The arguments of the current session's cancel method.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
cancelSession = <T extends Session>(...args: ParametersExceptFirst<T['cancel']>): this => {
|
cancelSession = <T extends Session>(...args: ParametersExceptFirst<T['cancel']>): this => {
|
||||||
const { session } = this
|
const { session } = this
|
||||||
|
@ -1312,7 +1289,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Complete the current session.
|
* Complete the current session.
|
||||||
* @param args The arguments of the current session's complete method.
|
* @param args The arguments of the current session's complete method.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
completeSession = <T extends Session>(...args: ParametersExceptFirst<T['complete']>): this => {
|
completeSession = <T extends Session>(...args: ParametersExceptFirst<T['complete']>): this => {
|
||||||
const { session } = this
|
const { session } = this
|
||||||
|
@ -1433,23 +1409,51 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------- Sessions -------------------- */
|
/* -------------------------------------------------- */
|
||||||
|
/* Sessions */
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a brush session.
|
||||||
|
* @param point
|
||||||
|
*/
|
||||||
startBrushSession = (point: number[]): this => {
|
startBrushSession = (point: number[]): this => {
|
||||||
return this.startSession(new Sessions.BrushSession(this.state, point))
|
return this.startSession(new Sessions.BrushSession(this.state, point))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a brush session.
|
||||||
|
* @param point
|
||||||
|
* @param metaKey
|
||||||
|
*/
|
||||||
updateBrushSession = (point: number[], metaKey = false): this => {
|
updateBrushSession = (point: number[], metaKey = false): this => {
|
||||||
return this.updateSession<Sessions.BrushSession>(point, metaKey)
|
return this.updateSession<Sessions.BrushSession>(point, metaKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a translate session.
|
||||||
|
* @param point
|
||||||
|
*/
|
||||||
startTranslateSession = (point: number[]): this => {
|
startTranslateSession = (point: number[]): this => {
|
||||||
return this.startSession(new Sessions.TranslateSession(this.state, point))
|
return this.startSession(new Sessions.TranslateSession(this.state, point))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a translate session.
|
||||||
|
* @param point
|
||||||
|
* @param shiftKey
|
||||||
|
* @param altKey
|
||||||
|
*/
|
||||||
updateTranslateSession = (point: number[], shiftKey = false, altKey = false): this => {
|
updateTranslateSession = (point: number[], shiftKey = false, altKey = false): this => {
|
||||||
return this.updateSession<Sessions.TranslateSession>(point, shiftKey, altKey)
|
return this.updateSession<Sessions.TranslateSession>(point, shiftKey, altKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a transform session
|
||||||
|
* @param point
|
||||||
|
* @param handle
|
||||||
|
* @param commandId
|
||||||
|
*/
|
||||||
startTransformSession = (
|
startTransformSession = (
|
||||||
point: number[],
|
point: number[],
|
||||||
handle: TLBoundsCorner | TLBoundsEdge | 'rotate',
|
handle: TLBoundsCorner | TLBoundsEdge | 'rotate',
|
||||||
|
@ -1480,6 +1484,9 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a transform session.
|
||||||
|
*/
|
||||||
updateTransformSession = (point: number[], shiftKey = false, altKey = false): this => {
|
updateTransformSession = (point: number[], shiftKey = false, altKey = false): this => {
|
||||||
return this.updateSession<Sessions.TransformSingleSession | Sessions.TransformSession>(
|
return this.updateSession<Sessions.TransformSingleSession | Sessions.TransformSession>(
|
||||||
point,
|
point,
|
||||||
|
@ -1488,22 +1495,47 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a text session.
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
startTextSession = (id: string): this => {
|
startTextSession = (id: string): this => {
|
||||||
return this.startSession(new Sessions.TextSession(this.state, id))
|
return this.startSession(new Sessions.TextSession(this.state, id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a text session.
|
||||||
|
* @param text
|
||||||
|
*/
|
||||||
updateTextSession = (text: string): this => {
|
updateTextSession = (text: string): this => {
|
||||||
return this.updateSession<Sessions.TextSession>(text)
|
return this.updateSession<Sessions.TextSession>(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a draw session.
|
||||||
|
* @param id
|
||||||
|
* @param point
|
||||||
|
*/
|
||||||
startDrawSession = (id: string, point: number[]): this => {
|
startDrawSession = (id: string, point: number[]): this => {
|
||||||
return this.startSession(new Sessions.DrawSession(this.state, id, point))
|
return this.startSession(new Sessions.DrawSession(this.state, id, point))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a draw session.
|
||||||
|
* @param point
|
||||||
|
* @param pressure
|
||||||
|
* @param shiftKey
|
||||||
|
*/
|
||||||
updateDrawSession = (point: number[], pressure: number, shiftKey = false): this => {
|
updateDrawSession = (point: number[], pressure: number, shiftKey = false): this => {
|
||||||
return this.updateSession<Sessions.DrawSession>(point, pressure, shiftKey)
|
return this.updateSession<Sessions.DrawSession>(point, pressure, shiftKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a handle session.
|
||||||
|
* @param point
|
||||||
|
* @param handleId
|
||||||
|
* @param commandId
|
||||||
|
*/
|
||||||
startHandleSession = (point: number[], handleId: string, commandId?: string): this => {
|
startHandleSession = (point: number[], handleId: string, commandId?: string): this => {
|
||||||
const selectedShape = this.page.shapes[this.selectedIds[0]]
|
const selectedShape = this.page.shapes[this.selectedIds[0]]
|
||||||
if (selectedShape.type === TLDrawShapeType.Arrow) {
|
if (selectedShape.type === TLDrawShapeType.Arrow) {
|
||||||
|
@ -1517,6 +1549,13 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a handle session.
|
||||||
|
* @param point
|
||||||
|
* @param shiftKey
|
||||||
|
* @param altKey
|
||||||
|
* @param metaKey
|
||||||
|
*/
|
||||||
updateHandleSession = (
|
updateHandleSession = (
|
||||||
point: number[],
|
point: number[],
|
||||||
shiftKey = false,
|
shiftKey = false,
|
||||||
|
@ -1539,7 +1578,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Manually create shapes on the page.
|
* Manually create shapes on the page.
|
||||||
* @param shapes An array of shape partials, containing the initial props for the shapes.
|
* @param shapes An array of shape partials, containing the initial props for the shapes.
|
||||||
* @command
|
* @command
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
createShapes = (
|
createShapes = (
|
||||||
...shapes: ({ id: string; type: TLDrawShapeType } & Partial<TLDrawShape>)[]
|
...shapes: ({ id: string; type: TLDrawShapeType } & Partial<TLDrawShape>)[]
|
||||||
|
@ -1559,7 +1597,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Manually update a set of shapes.
|
* Manually update a set of shapes.
|
||||||
* @param shapes An array of shape partials, containing the changes to be made to each shape.
|
* @param shapes An array of shape partials, containing the changes to be made to each shape.
|
||||||
* @command
|
* @command
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
updateShapes = (...shapes: ({ id: string } & Partial<TLDrawShape>)[]): this => {
|
updateShapes = (...shapes: ({ id: string } & Partial<TLDrawShape>)[]): this => {
|
||||||
if (shapes.length === 0) return this
|
if (shapes.length === 0) return this
|
||||||
|
@ -1570,7 +1607,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Create one or more shapes.
|
* Create one or more shapes.
|
||||||
* @param shapes An array of shapes.
|
* @param shapes An array of shapes.
|
||||||
* @command
|
* @command
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
create = (...shapes: TLDrawShape[]): this => {
|
create = (...shapes: TLDrawShape[]): this => {
|
||||||
if (shapes.length === 0) return this
|
if (shapes.length === 0) return this
|
||||||
|
@ -1581,7 +1617,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Delete one or more shapes.
|
* Delete one or more shapes.
|
||||||
* @param ids The ids of the shapes to delete.
|
* @param ids The ids of the shapes to delete.
|
||||||
* @command
|
* @command
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
delete = (ids = this.selectedIds): this => {
|
delete = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1590,7 +1625,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete all shapes on the page.
|
* Delete all shapes on the page.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
clear = (): this => {
|
clear = (): this => {
|
||||||
this.selectAll()
|
this.selectAll()
|
||||||
|
@ -1602,7 +1636,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Change the style for one or more shapes.
|
* Change the style for one or more shapes.
|
||||||
* @param style A style partial to apply to the shapes.
|
* @param style A style partial to apply to the shapes.
|
||||||
* @param ids The ids of the shapes to change (defaults to selection).
|
* @param ids The ids of the shapes to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
style = (style: Partial<ShapeStyles>, ids = this.selectedIds): this => {
|
style = (style: Partial<ShapeStyles>, ids = this.selectedIds): this => {
|
||||||
return this.setState(Commands.style(this.state, ids, style))
|
return this.setState(Commands.style(this.state, ids, style))
|
||||||
|
@ -1612,7 +1645,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Align one or more shapes.
|
* Align one or more shapes.
|
||||||
* @param direction Whether to align horizontally or vertically.
|
* @param direction Whether to align horizontally or vertically.
|
||||||
* @param ids The ids of the shapes to change (defaults to selection).
|
* @param ids The ids of the shapes to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
align = (type: AlignType, ids = this.selectedIds): this => {
|
align = (type: AlignType, ids = this.selectedIds): this => {
|
||||||
if (ids.length < 2) return this
|
if (ids.length < 2) return this
|
||||||
|
@ -1623,7 +1655,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Distribute one or more shapes.
|
* Distribute one or more shapes.
|
||||||
* @param direction Whether to distribute horizontally or vertically..
|
* @param direction Whether to distribute horizontally or vertically..
|
||||||
* @param ids The ids of the shapes to change (defaults to selection).
|
* @param ids The ids of the shapes to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
distribute = (direction: DistributeType, ids = this.selectedIds): this => {
|
distribute = (direction: DistributeType, ids = this.selectedIds): this => {
|
||||||
if (ids.length < 3) return this
|
if (ids.length < 3) return this
|
||||||
|
@ -1634,7 +1665,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Stretch one or more shapes to their common bounds.
|
* Stretch one or more shapes to their common bounds.
|
||||||
* @param direction Whether to stretch horizontally or vertically.
|
* @param direction Whether to stretch horizontally or vertically.
|
||||||
* @param ids The ids of the shapes to change (defaults to selection).
|
* @param ids The ids of the shapes to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
stretch = (direction: StretchType, ids = this.selectedIds): this => {
|
stretch = (direction: StretchType, ids = this.selectedIds): this => {
|
||||||
if (ids.length < 2) return this
|
if (ids.length < 2) return this
|
||||||
|
@ -1644,7 +1674,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Flip one or more shapes horizontally.
|
* Flip one or more shapes horizontally.
|
||||||
* @param ids The ids of the shapes to change (defaults to selection).
|
* @param ids The ids of the shapes to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
flipHorizontal = (ids = this.selectedIds): this => {
|
flipHorizontal = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1654,7 +1683,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Flip one or more shapes vertically.
|
* Flip one or more shapes vertically.
|
||||||
* @param ids The ids of the shapes to change (defaults to selection).
|
* @param ids The ids of the shapes to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
flipVertical = (ids = this.selectedIds): this => {
|
flipVertical = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1663,11 +1691,9 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move one or more shapes to a new page. Will also break or move bindings.
|
* Move one or more shapes to a new page. Will also break or move bindings.
|
||||||
* @param toPage The id of the page to move the shapes to.
|
* @param toPageId The id of the page to move the shapes to.
|
||||||
* @param fromPage The id of the page to move the shapes from
|
* @param fromPageId The id of the page to move the shapes from (defaults to current page).
|
||||||
*(defaults to current page).
|
|
||||||
* @param ids The ids of the shapes to move (defaults to selection).
|
* @param ids The ids of the shapes to move (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
moveToPage = (
|
moveToPage = (
|
||||||
toPageId: string,
|
toPageId: string,
|
||||||
|
@ -1682,7 +1708,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Move one or more shapes to the back of the page.
|
* Move one or more shapes to the back of the page.
|
||||||
* @param ids The ids of the shapes to change (defaults to selection).
|
* @param ids The ids of the shapes to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
moveToBack = (ids = this.selectedIds): this => {
|
moveToBack = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1692,7 +1717,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Move one or more shapes backward on of the page.
|
* Move one or more shapes backward on of the page.
|
||||||
* @param ids The ids of the shapes to change (defaults to selection).
|
* @param ids The ids of the shapes to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
moveBackward = (ids = this.selectedIds): this => {
|
moveBackward = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1702,7 +1726,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Move one or more shapes forward on the page.
|
* Move one or more shapes forward on the page.
|
||||||
* @param ids The ids of the shapes to change (defaults to selection).
|
* @param ids The ids of the shapes to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
moveForward = (ids = this.selectedIds): this => {
|
moveForward = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1712,7 +1735,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Move one or more shapes to the front of the page.
|
* Move one or more shapes to the front of the page.
|
||||||
* @param ids The ids of the shapes to change (defaults to selection).
|
* @param ids The ids of the shapes to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
moveToFront = (ids = this.selectedIds): this => {
|
moveToFront = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1724,7 +1746,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* @param delta The direction to nudge the shapes.
|
* @param delta The direction to nudge the shapes.
|
||||||
* @param isMajor Whether this is a major (i.e. shift) nudge.
|
* @param isMajor Whether this is a major (i.e. shift) nudge.
|
||||||
* @param ids The ids to change (defaults to selection).
|
* @param ids The ids to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
nudge = (delta: number[], isMajor = false, ids = this.selectedIds): this => {
|
nudge = (delta: number[], isMajor = false, ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1734,7 +1755,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Duplicate one or more shapes.
|
* Duplicate one or more shapes.
|
||||||
* @param ids The ids to duplicate (defaults to selection).
|
* @param ids The ids to duplicate (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
duplicate = (ids = this.selectedIds): this => {
|
duplicate = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1744,7 +1764,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Toggle the hidden property of one or more shapes.
|
* Toggle the hidden property of one or more shapes.
|
||||||
* @param ids The ids to change (defaults to selection).
|
* @param ids The ids to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
toggleHidden = (ids = this.selectedIds): this => {
|
toggleHidden = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1754,7 +1773,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Toggle the locked property of one or more shapes.
|
* Toggle the locked property of one or more shapes.
|
||||||
* @param ids The ids to change (defaults to selection).
|
* @param ids The ids to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
toggleLocked = (ids = this.selectedIds): this => {
|
toggleLocked = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1764,7 +1782,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
/**
|
/**
|
||||||
* Toggle the fixed-aspect-ratio property of one or more shapes.
|
* Toggle the fixed-aspect-ratio property of one or more shapes.
|
||||||
* @param ids The ids to change (defaults to selection).
|
* @param ids The ids to change (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
toggleAspectRatioLocked = (ids = this.selectedIds): this => {
|
toggleAspectRatioLocked = (ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1775,7 +1792,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Toggle the decoration at a handle of one or more shapes.
|
* Toggle the decoration at a handle of one or more shapes.
|
||||||
* @param handleId The handle to toggle.
|
* @param handleId The handle to toggle.
|
||||||
* @param ids The ids of the shapes to toggle the decoration on.
|
* @param ids The ids of the shapes to toggle the decoration on.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
toggleDecoration = (handleId: string, ids = this.selectedIds): this => {
|
toggleDecoration = (handleId: string, ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0 || !(handleId === 'start' || handleId === 'end')) return this
|
if (ids.length === 0 || !(handleId === 'start' || handleId === 'end')) return this
|
||||||
|
@ -1786,7 +1802,6 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* Rotate one or more shapes by a delta.
|
* Rotate one or more shapes by a delta.
|
||||||
* @param delta The delta in radians.
|
* @param delta The delta in radians.
|
||||||
* @param ids The ids to rotate (defaults to selection).
|
* @param ids The ids to rotate (defaults to selection).
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
rotate = (delta = Math.PI * -0.5, ids = this.selectedIds): this => {
|
rotate = (delta = Math.PI * -0.5, ids = this.selectedIds): this => {
|
||||||
if (ids.length === 0) return this
|
if (ids.length === 0) return this
|
||||||
|
@ -1795,29 +1810,35 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Group the selected shapes.
|
* Group the selected shapes.
|
||||||
* @returns this
|
* @param ids The ids to group (defaults to selection).
|
||||||
* @todo
|
* @param groupId The new group's id.
|
||||||
*/
|
*/
|
||||||
group = (ids = this.selectedIds, groupId = Utils.uniqueId()): this => {
|
group = (
|
||||||
|
ids = this.selectedIds,
|
||||||
|
groupId = Utils.uniqueId(),
|
||||||
|
pageId = this.currentPageId
|
||||||
|
): this => {
|
||||||
if (ids.length < 2) return this
|
if (ids.length < 2) return this
|
||||||
const command = Commands.group(this.state, ids, groupId)
|
const command = Commands.group(this.state, ids, groupId, pageId)
|
||||||
if (!command) return this
|
if (!command) return this
|
||||||
return this.setState(command)
|
return this.setState(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ungroup the selected groups.
|
* Ungroup the selected groups.
|
||||||
* @returns this
|
|
||||||
* @todo
|
* @todo
|
||||||
*/
|
*/
|
||||||
ungroup = (): this => {
|
ungroup = (groupId = this.selectedIds[0], pageId = this.currentPageId): this => {
|
||||||
// TODO
|
const shape = this.getShape(groupId, pageId)
|
||||||
return this
|
if (shape.type !== TLDrawShapeType.Group) return this
|
||||||
|
|
||||||
|
const command = Commands.ungroup(this.state, groupId, pageId)
|
||||||
|
if (!command) return this
|
||||||
|
return this.setState(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel the current session.
|
* Cancel the current session.
|
||||||
* @returns this
|
|
||||||
*/
|
*/
|
||||||
cancel = (): this => {
|
cancel = (): this => {
|
||||||
switch (this.state.appState.status.current) {
|
switch (this.state.appState.status.current) {
|
||||||
|
@ -1927,6 +1948,11 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new shape based on the active tool.
|
||||||
|
* @param point The point at which to create the shape
|
||||||
|
* @param id (optional) The new shape's id.
|
||||||
|
*/
|
||||||
createActiveToolShape = (point: number[], id = Utils.uniqueId()): this => {
|
createActiveToolShape = (point: number[], id = Utils.uniqueId()): this => {
|
||||||
const pagePoint = Vec.round(this.getPagePoint(point))
|
const pagePoint = Vec.round(this.getPagePoint(point))
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,14 @@
|
||||||
"**/*.spec.tsx",
|
"**/*.spec.tsx",
|
||||||
"**/*.spec.ts",
|
"**/*.spec.ts",
|
||||||
"src/test",
|
"src/test",
|
||||||
"dist"
|
"dist",
|
||||||
|
"docs"
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"incremental": false,
|
||||||
|
"sourceMap": false,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"declarationMap": false,
|
||||||
"outDir": "./dist/types"
|
"outDir": "./dist/types"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules", "dist"],
|
"exclude": ["node_modules", "dist", "docs"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"emitDeclarationOnly": true,
|
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"outDir": "./dist/types",
|
"outDir": "./dist/types",
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "www",
|
"name": "www",
|
||||||
"version": "0.0.59",
|
"version": "0.0.79",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A tiny little drawing app (site).",
|
"description": "A tiny little drawing app (site).",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@stitches/react": "^1.0.0",
|
"@stitches/react": "^1.0.0",
|
||||||
"@tldraw/tldraw": "^0.0.59",
|
"@tldraw/tldraw": "^0.0.64",
|
||||||
"next": "11.1.2",
|
"next": "11.1.2",
|
||||||
"next-auth": "3.29.0",
|
"next-auth": "3.29.0",
|
||||||
"next-pwa": "^5.2.23",
|
"next-pwa": "^5.2.23",
|
||||||
|
|
Loading…
Reference in a new issue