2023-04-25 11:01:25 +00:00
|
|
|
import {
|
2023-05-26 13:37:59 +00:00
|
|
|
AssetRecordType,
|
2023-06-02 15:21:45 +00:00
|
|
|
Editor,
|
2023-05-26 13:37:59 +00:00
|
|
|
PageRecordType,
|
2023-04-25 11:01:25 +00:00
|
|
|
TLArrowShape,
|
2023-06-16 10:33:47 +00:00
|
|
|
TLArrowShapeArrowheadStyle,
|
|
|
|
TLArrowShapeTerminal,
|
2023-04-25 11:01:25 +00:00
|
|
|
TLAsset,
|
|
|
|
TLAssetId,
|
2023-06-16 10:33:47 +00:00
|
|
|
TLDefaultColorStyle,
|
|
|
|
TLDefaultDashStyle,
|
|
|
|
TLDefaultFontStyle,
|
|
|
|
TLDefaultHorizontalAlignStyle,
|
|
|
|
TLDefaultSizeStyle,
|
2023-04-25 11:01:25 +00:00
|
|
|
TLDrawShape,
|
|
|
|
TLGeoShape,
|
|
|
|
TLImageShape,
|
|
|
|
TLNoteShape,
|
|
|
|
TLPageId,
|
|
|
|
TLShapeId,
|
|
|
|
TLTextShape,
|
|
|
|
TLVideoShape,
|
2024-01-03 12:13:15 +00:00
|
|
|
Vec,
|
|
|
|
VecModel,
|
tldraw zero - package shuffle (#1710)
This PR moves code between our packages so that:
- @tldraw/editor is a “core” library with the engine and canvas but no
shapes, tools, or other things
- @tldraw/tldraw contains everything particular to the experience we’ve
built for tldraw
At first look, this might seem like a step away from customization and
configuration, however I believe it greatly increases the configuration
potential of the @tldraw/editor while also providing a more accurate
reflection of what configuration options actually exist for
@tldraw/tldraw.
## Library changes
@tldraw/editor re-exports its dependencies and @tldraw/tldraw re-exports
@tldraw/editor.
- users of @tldraw/editor WITHOUT @tldraw/tldraw should almost always
only import things from @tldraw/editor.
- users of @tldraw/tldraw should almost always only import things from
@tldraw/tldraw.
- @tldraw/polyfills is merged into @tldraw/editor
- @tldraw/indices is merged into @tldraw/editor
- @tldraw/primitives is merged mostly into @tldraw/editor, partially
into @tldraw/tldraw
- @tldraw/file-format is merged into @tldraw/tldraw
- @tldraw/ui is merged into @tldraw/tldraw
Many (many) utils and other code is moved from the editor to tldraw. For
example, embeds now are entirely an feature of @tldraw/tldraw. The only
big chunk of code left in core is related to arrow handling.
## API Changes
The editor can now be used without tldraw's assets. We load them in
@tldraw/tldraw instead, so feel free to use whatever fonts or images or
whatever that you like with the editor.
All tools and shapes (except for the `Group` shape) are moved to
@tldraw/tldraw. This includes the `select` tool.
You should use the editor with at least one tool, however, so you now
also need to send in an `initialState` prop to the Editor /
<TldrawEditor> component indicating which state the editor should begin
in.
The `components` prop now also accepts `SelectionForeground`.
The complex selection component that we use for tldraw is moved to
@tldraw/tldraw. The default component is quite basic but can easily be
replaced via the `components` prop. We pass down our tldraw-flavored
SelectionFg via `components`.
Likewise with the `Scribble` component: the `DefaultScribble` no longer
uses our freehand tech and is a simple path instead. We pass down the
tldraw-flavored scribble via `components`.
The `ExternalContentManager` (`Editor.externalContentManager`) is
removed and replaced with a mapping of types to handlers.
- Register new content handlers with
`Editor.registerExternalContentHandler`.
- Register new asset creation handlers (for files and URLs) with
`Editor.registerExternalAssetHandler`
### Change Type
- [x] `major` — Breaking change
### Test Plan
- [x] Unit Tests
- [x] End to end tests
### Release Notes
- [@tldraw/editor] lots, wip
- [@tldraw/ui] gone, merged to tldraw/tldraw
- [@tldraw/polyfills] gone, merged to tldraw/editor
- [@tldraw/primitives] gone, merged to tldraw/editor / tldraw/tldraw
- [@tldraw/indices] gone, merged to tldraw/editor
- [@tldraw/file-format] gone, merged to tldraw/tldraw
---------
Co-authored-by: alex <alex@dytry.ch>
2023-07-17 21:22:34 +00:00
|
|
|
clamp,
|
2023-06-03 20:46:53 +00:00
|
|
|
createShapeId,
|
2023-06-02 09:38:13 +00:00
|
|
|
} from '@tldraw/editor'
|
2024-04-27 18:07:01 +00:00
|
|
|
import { tldrawConstants } from '../../tldraw-constants'
|
|
|
|
|
|
|
|
const { MAX_SHAPES_PER_PAGE } = tldrawConstants
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
const TLDRAW_V1_VERSION = 15.5
|
|
|
|
|
|
|
|
/** @internal */
|
2023-06-02 15:21:45 +00:00
|
|
|
export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocument) {
|
|
|
|
editor.batch(() => {
|
2023-04-25 11:01:25 +00:00
|
|
|
document = migrate(document, TLDRAW_V1_VERSION)
|
|
|
|
// Cancel any interactions / states
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.cancel().cancel().cancel().cancel()
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-14 15:20:59 +00:00
|
|
|
const firstPageId = editor.getPages()[0].id
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// Set the current page to the first page
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
editor.setCurrentPage(firstPageId)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// Delete all pages except first page
|
2023-11-14 15:20:59 +00:00
|
|
|
for (const page of editor.getPages().slice(1)) {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.deletePage(page.id)
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delete all of the shapes on the current page
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.selectAll()
|
2023-11-13 12:42:07 +00:00
|
|
|
editor.deleteShapes(editor.getSelectedShapeIds())
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// Create assets
|
|
|
|
const v1AssetIdsToV2AssetIds = new Map<string, TLAssetId>()
|
|
|
|
|
|
|
|
Object.values(document.assets ?? {}).forEach((v1Asset) => {
|
|
|
|
switch (v1Asset.type) {
|
|
|
|
case TDAssetType.Image: {
|
2023-05-26 13:37:59 +00:00
|
|
|
const assetId: TLAssetId = AssetRecordType.createId()
|
2023-04-25 11:01:25 +00:00
|
|
|
v1AssetIdsToV2AssetIds.set(v1Asset.id, assetId)
|
|
|
|
const placeholderAsset: TLAsset = {
|
|
|
|
id: assetId,
|
|
|
|
typeName: 'asset',
|
|
|
|
type: 'image',
|
|
|
|
props: {
|
|
|
|
w: coerceDimension(v1Asset.size[0]),
|
|
|
|
h: coerceDimension(v1Asset.size[1]),
|
|
|
|
name: v1Asset.fileName ?? 'Untitled',
|
|
|
|
isAnimated: false,
|
|
|
|
mimeType: null,
|
|
|
|
src: v1Asset.src,
|
|
|
|
},
|
2023-06-28 14:24:05 +00:00
|
|
|
meta: {},
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createAssets([placeholderAsset])
|
|
|
|
tryMigrateAsset(editor, placeholderAsset)
|
2023-04-25 11:01:25 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
case TDAssetType.Video:
|
|
|
|
{
|
2023-05-26 13:37:59 +00:00
|
|
|
const assetId: TLAssetId = AssetRecordType.createId()
|
2023-04-25 11:01:25 +00:00
|
|
|
v1AssetIdsToV2AssetIds.set(v1Asset.id, assetId)
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createAssets([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: assetId,
|
|
|
|
typeName: 'asset',
|
|
|
|
type: 'video',
|
|
|
|
props: {
|
|
|
|
w: coerceDimension(v1Asset.size[0]),
|
|
|
|
h: coerceDimension(v1Asset.size[1]),
|
|
|
|
name: v1Asset.fileName ?? 'Untitled',
|
|
|
|
isAnimated: true,
|
|
|
|
mimeType: null,
|
|
|
|
src: v1Asset.src,
|
|
|
|
},
|
2023-06-28 14:24:05 +00:00
|
|
|
meta: {},
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
|
|
|
])
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// Create pages
|
|
|
|
|
|
|
|
const v1PageIdsToV2PageIds = new Map<string, TLPageId>()
|
|
|
|
|
|
|
|
Object.values(document.pages ?? {})
|
|
|
|
.sort((a, b) => ((a.childIndex ?? 1) < (b.childIndex ?? 1) ? -1 : 1))
|
|
|
|
.forEach((v1Page, i) => {
|
|
|
|
if (i === 0) {
|
2023-11-16 15:34:56 +00:00
|
|
|
v1PageIdsToV2PageIds.set(v1Page.id, editor.getCurrentPageId())
|
2023-04-25 11:01:25 +00:00
|
|
|
} else {
|
2023-05-26 13:37:59 +00:00
|
|
|
const pageId = PageRecordType.createId()
|
2023-04-25 11:01:25 +00:00
|
|
|
v1PageIdsToV2PageIds.set(v1Page.id, pageId)
|
2023-08-05 11:21:07 +00:00
|
|
|
editor.createPage({ name: v1Page.name ?? 'Page', id: pageId })
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
Object.values(document.pages ?? {})
|
|
|
|
.sort((a, b) => ((a.childIndex ?? 1) < (b.childIndex ?? 1) ? -1 : 1))
|
|
|
|
.forEach((v1Page) => {
|
|
|
|
// Set the current page id to the current page
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
editor.setCurrentPage(v1PageIdsToV2PageIds.get(v1Page.id)!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
const v1ShapeIdsToV2ShapeIds = new Map<string, TLShapeId>()
|
|
|
|
const v1GroupShapeIdsToV1ChildIds = new Map<string, string[]>()
|
|
|
|
|
|
|
|
const v1Shapes = Object.values(v1Page.shapes ?? {})
|
|
|
|
.sort((a, b) => (a.childIndex < b.childIndex ? -1 : 1))
|
|
|
|
.slice(0, MAX_SHAPES_PER_PAGE)
|
|
|
|
|
|
|
|
// Groups only
|
|
|
|
v1Shapes.forEach((v1Shape) => {
|
|
|
|
if (v1Shape.type !== TDShapeType.Group) return
|
|
|
|
|
2023-06-03 20:46:53 +00:00
|
|
|
const shapeId = createShapeId()
|
2023-04-25 11:01:25 +00:00
|
|
|
v1ShapeIdsToV2ShapeIds.set(v1Shape.id, shapeId)
|
|
|
|
v1GroupShapeIdsToV1ChildIds.set(v1Shape.id, [])
|
|
|
|
})
|
|
|
|
|
|
|
|
function decideNotToCreateShape(v1Shape: TDShape) {
|
|
|
|
v1ShapeIdsToV2ShapeIds.delete(v1Shape.id)
|
|
|
|
const v1GroupParent = v1GroupShapeIdsToV1ChildIds.has(v1Shape.parentId)
|
|
|
|
if (v1GroupParent) {
|
|
|
|
const ids = v1GroupShapeIdsToV1ChildIds
|
|
|
|
.get(v1Shape.parentId)!
|
|
|
|
.filter((id) => id !== v1Shape.id)
|
|
|
|
v1GroupShapeIdsToV1ChildIds.set(v1Shape.parentId, ids)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Non-groups only
|
|
|
|
v1Shapes.forEach((v1Shape) => {
|
|
|
|
// Skip groups for now, we'll create groups via the app's API
|
|
|
|
if (v1Shape.type === TDShapeType.Group) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-03 20:46:53 +00:00
|
|
|
const shapeId = createShapeId()
|
2023-04-25 11:01:25 +00:00
|
|
|
v1ShapeIdsToV2ShapeIds.set(v1Shape.id, shapeId)
|
|
|
|
|
|
|
|
if (v1Shape.parentId !== v1Page.id) {
|
|
|
|
// If the parent is a group, then add the shape to the group's children
|
|
|
|
if (v1GroupShapeIdsToV1ChildIds.has(v1Shape.parentId)) {
|
|
|
|
v1GroupShapeIdsToV1ChildIds.get(v1Shape.parentId)!.push(v1Shape.id)
|
|
|
|
} else {
|
|
|
|
console.warn('parent does not exist', v1Shape)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// First, try to find the shape's parent among the existing groups
|
|
|
|
const parentId = v1PageIdsToV2PageIds.get(v1Page.id)!
|
|
|
|
|
|
|
|
const inCommon = {
|
|
|
|
id: shapeId,
|
|
|
|
parentId,
|
|
|
|
x: coerceNumber(v1Shape.point[0]),
|
|
|
|
y: coerceNumber(v1Shape.point[1]),
|
|
|
|
rotation: 0,
|
|
|
|
isLocked: !!v1Shape.isLocked,
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (v1Shape.type) {
|
|
|
|
case TDShapeType.Sticky: {
|
2023-06-13 18:02:17 +00:00
|
|
|
editor.createShapes<TLNoteShape>([
|
|
|
|
{
|
|
|
|
...inCommon,
|
|
|
|
type: 'note',
|
|
|
|
props: {
|
|
|
|
text: v1Shape.text ?? '',
|
|
|
|
color: getV2Color(v1Shape.style.color),
|
|
|
|
size: getV2Size(v1Shape.style.size),
|
|
|
|
font: getV2Font(v1Shape.style.font),
|
|
|
|
align: getV2Align(v1Shape.style.textAlign),
|
|
|
|
},
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
2023-06-13 18:02:17 +00:00
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
case TDShapeType.Rectangle: {
|
2023-06-13 18:02:17 +00:00
|
|
|
editor.createShapes<TLGeoShape>([
|
|
|
|
{
|
|
|
|
...inCommon,
|
|
|
|
type: 'geo',
|
|
|
|
props: {
|
|
|
|
geo: 'rectangle',
|
|
|
|
w: coerceDimension(v1Shape.size[0]),
|
|
|
|
h: coerceDimension(v1Shape.size[1]),
|
|
|
|
text: v1Shape.label ?? '',
|
|
|
|
fill: getV2Fill(v1Shape.style.isFilled, v1Shape.style.color),
|
|
|
|
labelColor: getV2Color(v1Shape.style.color),
|
|
|
|
color: getV2Color(v1Shape.style.color),
|
|
|
|
size: getV2Size(v1Shape.style.size),
|
|
|
|
font: getV2Font(v1Shape.style.font),
|
|
|
|
dash: getV2Dash(v1Shape.style.dash),
|
|
|
|
align: 'middle',
|
|
|
|
},
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
2023-06-13 18:02:17 +00:00
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-08-02 18:12:25 +00:00
|
|
|
const pageBoundsBeforeLabel = editor.getShapePageBounds(inCommon.id)!
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: inCommon.id,
|
|
|
|
type: 'geo',
|
|
|
|
props: {
|
|
|
|
text: v1Shape.label ?? '',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
|
|
|
|
if (pageBoundsBeforeLabel.width === pageBoundsBeforeLabel.height) {
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
const shape = editor.getShape<TLGeoShape>(inCommon.id)!
|
2023-04-25 11:01:25 +00:00
|
|
|
const { growY } = shape.props
|
|
|
|
const w = coerceDimension(shape.props.w)
|
|
|
|
const h = coerceDimension(shape.props.h)
|
|
|
|
const newW = w + growY / 2
|
|
|
|
const newH = h + growY / 2
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: inCommon.id,
|
|
|
|
type: 'geo',
|
|
|
|
x: coerceNumber(shape.x) - (newW - w) / 2,
|
|
|
|
y: coerceNumber(shape.y) - (newH - h) / 2,
|
|
|
|
props: {
|
|
|
|
w: newW,
|
|
|
|
h: newH,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case TDShapeType.Triangle: {
|
2023-06-13 18:02:17 +00:00
|
|
|
editor.createShapes<TLGeoShape>([
|
|
|
|
{
|
|
|
|
...inCommon,
|
|
|
|
type: 'geo',
|
|
|
|
props: {
|
|
|
|
geo: 'triangle',
|
|
|
|
w: coerceDimension(v1Shape.size[0]),
|
|
|
|
h: coerceDimension(v1Shape.size[1]),
|
|
|
|
fill: getV2Fill(v1Shape.style.isFilled, v1Shape.style.color),
|
|
|
|
labelColor: getV2Color(v1Shape.style.color),
|
|
|
|
color: getV2Color(v1Shape.style.color),
|
|
|
|
size: getV2Size(v1Shape.style.size),
|
|
|
|
font: getV2Font(v1Shape.style.font),
|
|
|
|
dash: getV2Dash(v1Shape.style.dash),
|
|
|
|
align: 'middle',
|
|
|
|
},
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
2023-06-13 18:02:17 +00:00
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-08-02 18:12:25 +00:00
|
|
|
const pageBoundsBeforeLabel = editor.getShapePageBounds(inCommon.id)!
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: inCommon.id,
|
|
|
|
type: 'geo',
|
|
|
|
props: {
|
|
|
|
text: v1Shape.label ?? '',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
|
|
|
|
if (pageBoundsBeforeLabel.width === pageBoundsBeforeLabel.height) {
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
const shape = editor.getShape<TLGeoShape>(inCommon.id)!
|
2023-04-25 11:01:25 +00:00
|
|
|
const { growY } = shape.props
|
|
|
|
const w = coerceDimension(shape.props.w)
|
|
|
|
const h = coerceDimension(shape.props.h)
|
|
|
|
const newW = w + growY / 2
|
|
|
|
const newH = h + growY / 2
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: inCommon.id,
|
|
|
|
type: 'geo',
|
|
|
|
x: coerceNumber(shape.x) - (newW - w) / 2,
|
|
|
|
y: coerceNumber(shape.y) - (newH - h) / 2,
|
|
|
|
props: {
|
|
|
|
w: newW,
|
|
|
|
h: newH,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case TDShapeType.Ellipse: {
|
2023-06-13 18:02:17 +00:00
|
|
|
editor.createShapes<TLGeoShape>([
|
|
|
|
{
|
|
|
|
...inCommon,
|
|
|
|
type: 'geo',
|
|
|
|
props: {
|
|
|
|
geo: 'ellipse',
|
|
|
|
w: coerceDimension(v1Shape.radius[0]) * 2,
|
|
|
|
h: coerceDimension(v1Shape.radius[1]) * 2,
|
|
|
|
fill: getV2Fill(v1Shape.style.isFilled, v1Shape.style.color),
|
|
|
|
labelColor: getV2Color(v1Shape.style.color),
|
|
|
|
color: getV2Color(v1Shape.style.color),
|
|
|
|
size: getV2Size(v1Shape.style.size),
|
|
|
|
font: getV2Font(v1Shape.style.font),
|
|
|
|
dash: getV2Dash(v1Shape.style.dash),
|
|
|
|
align: 'middle',
|
|
|
|
},
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
2023-06-13 18:02:17 +00:00
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-08-02 18:12:25 +00:00
|
|
|
const pageBoundsBeforeLabel = editor.getShapePageBounds(inCommon.id)!
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: inCommon.id,
|
|
|
|
type: 'geo',
|
|
|
|
props: {
|
|
|
|
text: v1Shape.label ?? '',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
|
|
|
|
if (pageBoundsBeforeLabel.width === pageBoundsBeforeLabel.height) {
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
const shape = editor.getShape<TLGeoShape>(inCommon.id)!
|
2023-04-25 11:01:25 +00:00
|
|
|
const { growY } = shape.props
|
|
|
|
const w = coerceDimension(shape.props.w)
|
|
|
|
const h = coerceDimension(shape.props.h)
|
|
|
|
const newW = w + growY / 2
|
|
|
|
const newH = h + growY / 2
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: inCommon.id,
|
|
|
|
type: 'geo',
|
|
|
|
x: coerceNumber(shape.x) - (newW - w) / 2,
|
|
|
|
y: coerceNumber(shape.y) - (newH - h) / 2,
|
|
|
|
props: {
|
|
|
|
w: newW,
|
|
|
|
h: newH,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case TDShapeType.Draw: {
|
|
|
|
if (v1Shape.points.length === 0) {
|
|
|
|
decideNotToCreateShape(v1Shape)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2023-06-13 18:02:17 +00:00
|
|
|
editor.createShapes<TLDrawShape>([
|
|
|
|
{
|
|
|
|
...inCommon,
|
|
|
|
type: 'draw',
|
|
|
|
props: {
|
|
|
|
fill: getV2Fill(v1Shape.style.isFilled, v1Shape.style.color),
|
|
|
|
color: getV2Color(v1Shape.style.color),
|
|
|
|
size: getV2Size(v1Shape.style.size),
|
|
|
|
dash: getV2Dash(v1Shape.style.dash),
|
|
|
|
isPen: false,
|
|
|
|
isComplete: v1Shape.isComplete,
|
|
|
|
segments: [{ type: 'free', points: v1Shape.points.map(getV2Point) }],
|
|
|
|
},
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
2023-06-13 18:02:17 +00:00
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
case TDShapeType.Arrow: {
|
|
|
|
const v1Bend = coerceNumber(v1Shape.bend)
|
|
|
|
const v1Start = getV2Point(v1Shape.handles.start.point)
|
|
|
|
const v1End = getV2Point(v1Shape.handles.end.point)
|
2024-01-03 12:13:15 +00:00
|
|
|
const dist = Vec.Dist(v1Start, v1End)
|
2023-04-25 11:01:25 +00:00
|
|
|
const v2Bend = (dist * -v1Bend) / 2
|
|
|
|
|
|
|
|
// Could also be a line... but we'll use it as an arrow anyway
|
2023-06-13 18:02:17 +00:00
|
|
|
editor.createShapes<TLArrowShape>([
|
|
|
|
{
|
|
|
|
...inCommon,
|
|
|
|
type: 'arrow',
|
|
|
|
props: {
|
|
|
|
text: v1Shape.label ?? '',
|
|
|
|
color: getV2Color(v1Shape.style.color),
|
|
|
|
labelColor: getV2Color(v1Shape.style.color),
|
|
|
|
size: getV2Size(v1Shape.style.size),
|
|
|
|
font: getV2Font(v1Shape.style.font),
|
|
|
|
dash: getV2Dash(v1Shape.style.dash),
|
|
|
|
arrowheadStart: getV2Arrowhead(v1Shape.decorations?.start),
|
|
|
|
arrowheadEnd: getV2Arrowhead(v1Shape.decorations?.end),
|
|
|
|
start: {
|
|
|
|
type: 'point',
|
|
|
|
x: coerceNumber(v1Shape.handles.start.point[0]),
|
|
|
|
y: coerceNumber(v1Shape.handles.start.point[1]),
|
|
|
|
},
|
|
|
|
end: {
|
|
|
|
type: 'point',
|
|
|
|
x: coerceNumber(v1Shape.handles.end.point[0]),
|
|
|
|
y: coerceNumber(v1Shape.handles.end.point[1]),
|
|
|
|
},
|
|
|
|
bend: v2Bend,
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
|
|
|
},
|
2023-06-13 18:02:17 +00:00
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case TDShapeType.Text: {
|
2023-06-13 18:02:17 +00:00
|
|
|
editor.createShapes<TLTextShape>([
|
|
|
|
{
|
|
|
|
...inCommon,
|
|
|
|
type: 'text',
|
|
|
|
props: {
|
|
|
|
text: v1Shape.text ?? ' ',
|
|
|
|
color: getV2Color(v1Shape.style.color),
|
|
|
|
size: getV2TextSize(v1Shape.style.size),
|
|
|
|
font: getV2Font(v1Shape.style.font),
|
|
|
|
align: getV2Align(v1Shape.style.textAlign),
|
|
|
|
scale: v1Shape.style.scale ?? 1,
|
|
|
|
},
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
2023-06-13 18:02:17 +00:00
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
case TDShapeType.Image: {
|
|
|
|
const assetId = v1AssetIdsToV2AssetIds.get(v1Shape.assetId)
|
|
|
|
|
|
|
|
if (!assetId) {
|
|
|
|
console.warn('Could not find asset id', v1Shape.assetId)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-13 18:02:17 +00:00
|
|
|
editor.createShapes<TLImageShape>([
|
|
|
|
{
|
|
|
|
...inCommon,
|
|
|
|
type: 'image',
|
|
|
|
props: {
|
|
|
|
w: coerceDimension(v1Shape.size[0]),
|
|
|
|
h: coerceDimension(v1Shape.size[1]),
|
|
|
|
assetId,
|
|
|
|
},
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
2023-06-13 18:02:17 +00:00
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
case TDShapeType.Video: {
|
|
|
|
const assetId = v1AssetIdsToV2AssetIds.get(v1Shape.assetId)
|
|
|
|
|
|
|
|
if (!assetId) {
|
|
|
|
console.warn('Could not find asset id', v1Shape.assetId)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-13 18:02:17 +00:00
|
|
|
editor.createShapes<TLVideoShape>([
|
|
|
|
{
|
|
|
|
...inCommon,
|
|
|
|
type: 'video',
|
|
|
|
props: {
|
|
|
|
w: coerceDimension(v1Shape.size[0]),
|
|
|
|
h: coerceDimension(v1Shape.size[1]),
|
|
|
|
assetId,
|
|
|
|
},
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
2023-06-13 18:02:17 +00:00
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const rotation = coerceNumber(v1Shape.rotation)
|
|
|
|
|
|
|
|
if (rotation !== 0) {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(shapeId)
|
|
|
|
editor.rotateShapesBy([shapeId], rotation)
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// Create groups
|
|
|
|
v1GroupShapeIdsToV1ChildIds.forEach((v1ChildIds, v1GroupId) => {
|
|
|
|
const v2ChildShapeIds = v1ChildIds.map((id) => v1ShapeIdsToV2ShapeIds.get(id)!)
|
|
|
|
const v2GroupId = v1ShapeIdsToV2ShapeIds.get(v1GroupId)!
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.groupShapes(v2ChildShapeIds, v2GroupId)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
const v1Group = v1Page.shapes[v1GroupId]
|
|
|
|
const rotation = coerceNumber(v1Group.rotation)
|
|
|
|
|
|
|
|
if (rotation !== 0) {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(v2GroupId)
|
|
|
|
editor.rotateShapesBy([v2GroupId], rotation)
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// Bind arrows to shapes
|
|
|
|
|
|
|
|
v1Shapes.forEach((v1Shape) => {
|
|
|
|
if (v1Shape.type !== TDShapeType.Arrow) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const v2ShapeId = v1ShapeIdsToV2ShapeIds.get(v1Shape.id)!
|
2023-07-07 13:56:31 +00:00
|
|
|
const util = editor.getShapeUtil<TLArrowShape>('arrow')
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// dumb but necessary
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.inputs.ctrlKey = false
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
for (const handleId of ['start', 'end'] as const) {
|
|
|
|
const bindingId = v1Shape.handles[handleId].bindingId
|
|
|
|
if (bindingId) {
|
|
|
|
const binding = v1Page.bindings[bindingId]
|
|
|
|
if (!binding) {
|
|
|
|
// arrow has a reference to a binding that no longer exists
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
const targetId = v1ShapeIdsToV2ShapeIds.get(binding.toId)!
|
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
const targetShape = editor.getShape(targetId)!
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// (unexpected) We didn't create the target shape
|
|
|
|
if (!targetShape) continue
|
|
|
|
|
|
|
|
if (targetId) {
|
2023-08-02 18:12:25 +00:00
|
|
|
const bounds = editor.getShapePageBounds(targetId)!
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
const v2ShapeFresh = editor.getShape<TLArrowShape>(v2ShapeId)!
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
const nx = clamp((coerceNumber(binding.point[0]) + 0.5) / 2, 0.2, 0.8)
|
|
|
|
const ny = clamp((coerceNumber(binding.point[1]) + 0.5) / 2, 0.2, 0.8)
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
const point = editor.getPointInShapeSpace(v2ShapeFresh, {
|
2023-04-25 11:01:25 +00:00
|
|
|
x: bounds.minX + bounds.width * nx,
|
|
|
|
y: bounds.minY + bounds.height * ny,
|
|
|
|
})
|
|
|
|
|
2023-08-02 18:12:25 +00:00
|
|
|
const handles = editor.getShapeHandles(v2ShapeFresh)!
|
2024-01-24 10:19:20 +00:00
|
|
|
const change = util.onHandleDrag!(v2ShapeFresh, {
|
2023-04-25 11:01:25 +00:00
|
|
|
handle: {
|
|
|
|
...handles.find((h) => h.id === handleId)!,
|
|
|
|
x: point.x,
|
|
|
|
y: point.y,
|
|
|
|
},
|
|
|
|
isPrecise: point.x !== 0.5 || point.y !== 0.5,
|
|
|
|
})
|
|
|
|
|
|
|
|
if (change) {
|
|
|
|
if (change.props?.[handleId]) {
|
2023-06-16 10:33:47 +00:00
|
|
|
const terminal = change.props?.[handleId] as TLArrowShapeTerminal
|
2023-04-25 11:01:25 +00:00
|
|
|
if (terminal.type === 'binding') {
|
|
|
|
terminal.isExact = binding.distance === 0
|
|
|
|
|
|
|
|
if (terminal.boundShapeId !== targetId) {
|
|
|
|
console.warn('Hit the wrong shape!')
|
|
|
|
terminal.boundShapeId = targetId
|
|
|
|
terminal.normalizedAnchor = { x: 0.5, y: 0.5 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([change])
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
// Set the current page to the first page again
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
editor.setCurrentPage(firstPageId)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.history.clear()
|
|
|
|
editor.selectNone()
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-14 16:32:27 +00:00
|
|
|
const bounds = editor.getCurrentPageBounds()
|
2023-04-25 11:01:25 +00:00
|
|
|
if (bounds) {
|
Add component for viewing an image of a snapshot (#2804)
This PR adds the `TldrawImage` component that displays a tldraw snapshot
as an SVG image.
![2024-02-15 at 12 29 52 - Coral
Cod](https://github.com/tldraw/tldraw/assets/15892272/14140e9e-7d6d-4dd3-88a3-86a6786325c5)
## Why
We've seen requests for this kind of thing from users. eg: GitBook, and
on discord:
<img width="710" alt="image"
src="https://github.com/tldraw/tldraw/assets/15892272/3d3a3e9d-66b9-42e7-81de-a70aa7165bdc">
The component provides a way to do that.
This PR also untangles various bits of editor state from image
exporting, which makes it easier for library users to export images more
agnostically. (ie: they can now export any shapes on any page in any
theme. previously, they had to change the user's state to do that).
## What else
- This PR also adds an **Image snapshot** example to demonstrate the new
component.
- We now pass an `isDarkMode` property to the `toSvg` method (inside the
`ctx` argument). This means that `toSvg` doesn't have to rely on editor
state anymore. I updated all our `toSvg` methods to use it.
- See code comments for more info.
## Any issues?
When you toggle to editing mode in the new example, text measurements
are initially wrong (until you edit the size of a text shape). Click on
the text shape to see how its indicator is wrong. Not sure why this is,
or if it's even related. Does it ring a bell with anyone? If not, I'll
take a closer look. (fixed, see comments --steve)
## Future work
Now that we've untangled image exporting from editor state, we could
expose some more helpful helpers for making this easier.
Fixes tld-2122
### Change Type
- [x] `minor` — New feature
[^1]: publishes a `patch` release, for devDependencies use `internal`
[^2]: will not publish a new version
### Test Plan
1. Open the **Image snapshot** example.
2. Try editing the image, saving the image, and making sure the image
updates.
- [ ] Unit Tests
- [ ] End to end tests
### Release Notes
- Dev: Added the `TldrawImage` component.
---------
Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
2024-02-16 13:54:48 +00:00
|
|
|
editor.zoomToBounds(bounds, { targetZoom: 1 })
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function coerceNumber(n: unknown): number {
|
|
|
|
if (typeof n !== 'number') return 0
|
|
|
|
if (Number.isNaN(n)) return 0
|
|
|
|
if (!Number.isFinite(n)) return 0
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
function coerceDimension(d: unknown): number {
|
|
|
|
const n = coerceNumber(d)
|
|
|
|
if (n <= 0) return 1
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* We want to move assets over to our new S3 bucket & extract any relevant metadata. That process is
|
|
|
|
* async though, where the rest of our migration is synchronous.
|
|
|
|
*
|
|
|
|
* We'll write placeholder assets to the app using the old asset URLs, then kick off a process async
|
|
|
|
* to try and download the real assets, extract the metadata, and upload them to our new bucket.
|
|
|
|
* It's not a big deal if this fails though.
|
|
|
|
*/
|
2023-06-02 15:21:45 +00:00
|
|
|
async function tryMigrateAsset(editor: Editor, placeholderAsset: TLAsset) {
|
2023-04-25 11:01:25 +00:00
|
|
|
try {
|
|
|
|
if (placeholderAsset.type === 'bookmark' || !placeholderAsset.props.src) return
|
|
|
|
|
|
|
|
const response = await fetch(placeholderAsset.props.src)
|
|
|
|
if (!response.ok) return
|
|
|
|
|
|
|
|
const file = new File([await response.blob()], placeholderAsset.props.name, {
|
|
|
|
type: response.headers.get('content-type') ?? placeholderAsset.props.mimeType ?? undefined,
|
|
|
|
})
|
|
|
|
|
tldraw zero - package shuffle (#1710)
This PR moves code between our packages so that:
- @tldraw/editor is a “core” library with the engine and canvas but no
shapes, tools, or other things
- @tldraw/tldraw contains everything particular to the experience we’ve
built for tldraw
At first look, this might seem like a step away from customization and
configuration, however I believe it greatly increases the configuration
potential of the @tldraw/editor while also providing a more accurate
reflection of what configuration options actually exist for
@tldraw/tldraw.
## Library changes
@tldraw/editor re-exports its dependencies and @tldraw/tldraw re-exports
@tldraw/editor.
- users of @tldraw/editor WITHOUT @tldraw/tldraw should almost always
only import things from @tldraw/editor.
- users of @tldraw/tldraw should almost always only import things from
@tldraw/tldraw.
- @tldraw/polyfills is merged into @tldraw/editor
- @tldraw/indices is merged into @tldraw/editor
- @tldraw/primitives is merged mostly into @tldraw/editor, partially
into @tldraw/tldraw
- @tldraw/file-format is merged into @tldraw/tldraw
- @tldraw/ui is merged into @tldraw/tldraw
Many (many) utils and other code is moved from the editor to tldraw. For
example, embeds now are entirely an feature of @tldraw/tldraw. The only
big chunk of code left in core is related to arrow handling.
## API Changes
The editor can now be used without tldraw's assets. We load them in
@tldraw/tldraw instead, so feel free to use whatever fonts or images or
whatever that you like with the editor.
All tools and shapes (except for the `Group` shape) are moved to
@tldraw/tldraw. This includes the `select` tool.
You should use the editor with at least one tool, however, so you now
also need to send in an `initialState` prop to the Editor /
<TldrawEditor> component indicating which state the editor should begin
in.
The `components` prop now also accepts `SelectionForeground`.
The complex selection component that we use for tldraw is moved to
@tldraw/tldraw. The default component is quite basic but can easily be
replaced via the `components` prop. We pass down our tldraw-flavored
SelectionFg via `components`.
Likewise with the `Scribble` component: the `DefaultScribble` no longer
uses our freehand tech and is a simple path instead. We pass down the
tldraw-flavored scribble via `components`.
The `ExternalContentManager` (`Editor.externalContentManager`) is
removed and replaced with a mapping of types to handlers.
- Register new content handlers with
`Editor.registerExternalContentHandler`.
- Register new asset creation handlers (for files and URLs) with
`Editor.registerExternalAssetHandler`
### Change Type
- [x] `major` — Breaking change
### Test Plan
- [x] Unit Tests
- [x] End to end tests
### Release Notes
- [@tldraw/editor] lots, wip
- [@tldraw/ui] gone, merged to tldraw/tldraw
- [@tldraw/polyfills] gone, merged to tldraw/editor
- [@tldraw/primitives] gone, merged to tldraw/editor / tldraw/tldraw
- [@tldraw/indices] gone, merged to tldraw/editor
- [@tldraw/file-format] gone, merged to tldraw/tldraw
---------
Co-authored-by: alex <alex@dytry.ch>
2023-07-17 21:22:34 +00:00
|
|
|
const newAsset = await editor.getAssetForExternalContent({ type: 'file', file })
|
|
|
|
if (!newAsset) throw new Error('Could not get asset for external content')
|
2023-04-25 11:01:25 +00:00
|
|
|
if (newAsset.type === 'bookmark') return
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateAssets([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: placeholderAsset.id,
|
|
|
|
type: placeholderAsset.type,
|
|
|
|
props: {
|
|
|
|
...newAsset.props,
|
|
|
|
name: placeholderAsset.props.name,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
} catch (err) {
|
|
|
|
// not a big deal, we'll just keep the placeholder asset
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function migrate(document: LegacyTldrawDocument, newVersion: number): LegacyTldrawDocument {
|
|
|
|
const { version = 0 } = document
|
|
|
|
|
|
|
|
if (!document.assets) {
|
|
|
|
document.assets = {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove unused assets when loading a document
|
|
|
|
const assetIdsInUse = new Set<string>()
|
|
|
|
|
|
|
|
Object.values(document.pages).forEach((page) =>
|
|
|
|
Object.values(page.shapes).forEach((shape) => {
|
|
|
|
const { parentId, children, assetId } = shape
|
|
|
|
|
|
|
|
if (assetId) {
|
|
|
|
assetIdsInUse.add(assetId)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix missing parent bug
|
|
|
|
if (parentId !== page.id && !page.shapes[parentId]) {
|
|
|
|
console.warn('Encountered a shape with a missing parent!')
|
|
|
|
shape.parentId = page.id
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shape.type === TDShapeType.Group && children) {
|
|
|
|
children.forEach((childId) => {
|
|
|
|
if (!page.shapes[childId]) {
|
|
|
|
console.warn('Encountered a parent with a missing child!', shape.id, childId)
|
|
|
|
children?.splice(children.indexOf(childId), 1)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// TODO: Remove the shape if it has no children
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
Object.keys(document.assets).forEach((assetId) => {
|
|
|
|
if (!assetIdsInUse.has(assetId)) {
|
|
|
|
delete document.assets[assetId]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if (version !== newVersion) {
|
|
|
|
if (version < 14) {
|
|
|
|
Object.values(document.pages).forEach((page) => {
|
|
|
|
Object.values(page.shapes)
|
|
|
|
.filter((shape) => shape.type === TDShapeType.Text)
|
|
|
|
.forEach((shape) => {
|
|
|
|
if ((shape as TextShape).style.font === undefined) {
|
|
|
|
;(shape as TextShape).style.font === FontStyle.Script
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lowercase styles, move binding meta to binding
|
|
|
|
if (version <= 13) {
|
|
|
|
Object.values(document.pages).forEach((page) => {
|
|
|
|
Object.values(page.bindings).forEach((binding) => {
|
|
|
|
Object.assign(binding, (binding as any).meta)
|
|
|
|
})
|
|
|
|
|
|
|
|
Object.values(page.shapes).forEach((shape) => {
|
|
|
|
Object.entries(shape.style).forEach(([id, style]) => {
|
|
|
|
if (typeof style === 'string') {
|
|
|
|
// @ts-ignore
|
|
|
|
shape.style[id] = style.toLowerCase()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if (shape.type === TDShapeType.Arrow) {
|
|
|
|
if (shape.decorations) {
|
|
|
|
Object.entries(shape.decorations).forEach(([id, decoration]) => {
|
|
|
|
if ((decoration as unknown) === 'Arrow') {
|
|
|
|
shape.decorations = {
|
|
|
|
...shape.decorations,
|
|
|
|
[id]: Decoration.Arrow,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add document name and file system handle
|
|
|
|
if (version <= 13.1 && document.name == null) {
|
|
|
|
document.name = 'New Document'
|
|
|
|
}
|
|
|
|
|
|
|
|
if (version < 15 && document.assets == null) {
|
|
|
|
document.assets = {}
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.values(document.pages).forEach((page) => {
|
|
|
|
Object.values(page.shapes).forEach((shape) => {
|
|
|
|
if (version < 15.2) {
|
|
|
|
if (
|
|
|
|
(shape.type === TDShapeType.Image || shape.type === TDShapeType.Video) &&
|
|
|
|
shape.style.isFilled == null
|
|
|
|
) {
|
|
|
|
shape.style.isFilled = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (version < 15.3) {
|
|
|
|
if (
|
|
|
|
shape.type === TDShapeType.Rectangle ||
|
|
|
|
shape.type === TDShapeType.Triangle ||
|
|
|
|
shape.type === TDShapeType.Ellipse ||
|
|
|
|
shape.type === TDShapeType.Arrow
|
|
|
|
) {
|
|
|
|
if ('text' in shape && typeof shape.text === 'string') {
|
|
|
|
shape.label = shape.text
|
|
|
|
}
|
|
|
|
if (!shape.label) {
|
|
|
|
shape.label = ''
|
|
|
|
}
|
|
|
|
if (!shape.labelPoint) {
|
|
|
|
shape.labelPoint = [0.5, 0.5]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup
|
|
|
|
Object.values(document.pageStates).forEach((pageState) => {
|
2023-07-26 09:24:21 +00:00
|
|
|
pageState.selectedIds = pageState.selectedIds.filter((id) => {
|
2023-04-25 11:01:25 +00:00
|
|
|
return document.pages[pageState.id].shapes[id] !== undefined
|
|
|
|
})
|
|
|
|
pageState.bindingId = undefined
|
2023-07-26 09:24:21 +00:00
|
|
|
pageState.editingId = undefined
|
|
|
|
pageState.hoveredId = undefined
|
2023-04-25 11:01:25 +00:00
|
|
|
pageState.pointedId = undefined
|
|
|
|
})
|
|
|
|
|
|
|
|
document.version = newVersion
|
|
|
|
|
|
|
|
return document
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------- TLV1 Types -------------------- */
|
|
|
|
|
|
|
|
interface TLV1Handle {
|
|
|
|
id: string
|
|
|
|
index: number
|
|
|
|
point: number[]
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TLV1Binding {
|
|
|
|
id: string
|
|
|
|
toId: string
|
|
|
|
fromId: string
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TLV1Shape {
|
|
|
|
id: string
|
|
|
|
type: string
|
|
|
|
parentId: string
|
|
|
|
childIndex: number
|
|
|
|
name: string
|
|
|
|
point: number[]
|
|
|
|
assetId?: string
|
|
|
|
rotation?: number
|
|
|
|
children?: string[]
|
|
|
|
handles?: Record<string, TLV1Handle>
|
|
|
|
isGhost?: boolean
|
|
|
|
isHidden?: boolean
|
|
|
|
isLocked?: boolean
|
|
|
|
isGenerated?: boolean
|
|
|
|
isAspectRatioLocked?: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
enum TDShapeType {
|
|
|
|
Sticky = 'sticky',
|
|
|
|
Ellipse = 'ellipse',
|
|
|
|
Rectangle = 'rectangle',
|
|
|
|
Triangle = 'triangle',
|
|
|
|
Draw = 'draw',
|
|
|
|
Arrow = 'arrow',
|
|
|
|
Text = 'text',
|
|
|
|
Group = 'group',
|
|
|
|
Image = 'image',
|
|
|
|
Video = 'video',
|
|
|
|
}
|
|
|
|
|
|
|
|
enum ColorStyle {
|
|
|
|
White = 'white',
|
|
|
|
LightGray = 'lightGray',
|
|
|
|
Gray = 'gray',
|
|
|
|
Black = 'black',
|
|
|
|
Green = 'green',
|
|
|
|
Cyan = 'cyan',
|
|
|
|
Blue = 'blue',
|
|
|
|
Indigo = 'indigo',
|
|
|
|
Violet = 'violet',
|
|
|
|
Red = 'red',
|
|
|
|
Orange = 'orange',
|
|
|
|
Yellow = 'yellow',
|
|
|
|
}
|
|
|
|
|
|
|
|
enum SizeStyle {
|
|
|
|
Small = 'small',
|
|
|
|
Medium = 'medium',
|
|
|
|
Large = 'large',
|
|
|
|
}
|
|
|
|
|
|
|
|
enum DashStyle {
|
|
|
|
Draw = 'draw',
|
|
|
|
Solid = 'solid',
|
|
|
|
Dashed = 'dashed',
|
|
|
|
Dotted = 'dotted',
|
|
|
|
}
|
|
|
|
|
|
|
|
enum AlignStyle {
|
|
|
|
Start = 'start',
|
|
|
|
Middle = 'middle',
|
|
|
|
End = 'end',
|
|
|
|
Justify = 'justify',
|
|
|
|
}
|
|
|
|
|
|
|
|
enum FontStyle {
|
|
|
|
Script = 'script',
|
|
|
|
Sans = 'sans',
|
|
|
|
Serif = 'serif',
|
|
|
|
Mono = 'mono',
|
|
|
|
}
|
|
|
|
|
|
|
|
type ShapeStyles = {
|
|
|
|
color: ColorStyle
|
|
|
|
size: SizeStyle
|
|
|
|
dash: DashStyle
|
|
|
|
font?: FontStyle
|
|
|
|
textAlign?: AlignStyle
|
|
|
|
isFilled?: boolean
|
|
|
|
scale?: number
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TDBaseShape extends TLV1Shape {
|
|
|
|
style: ShapeStyles
|
|
|
|
type: TDShapeType
|
|
|
|
label?: string
|
|
|
|
handles?: Record<string, TDHandle>
|
|
|
|
}
|
|
|
|
|
|
|
|
interface DrawShape extends TDBaseShape {
|
|
|
|
type: TDShapeType.Draw
|
|
|
|
points: number[][]
|
|
|
|
isComplete: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
// The extended handle (used for arrows)
|
|
|
|
interface TDHandle extends TLV1Handle {
|
|
|
|
canBind?: boolean
|
|
|
|
bindingId?: string
|
|
|
|
}
|
|
|
|
|
|
|
|
interface RectangleShape extends TDBaseShape {
|
|
|
|
type: TDShapeType.Rectangle
|
|
|
|
size: number[]
|
|
|
|
label?: string
|
|
|
|
labelPoint?: number[]
|
|
|
|
}
|
|
|
|
|
|
|
|
interface EllipseShape extends TDBaseShape {
|
|
|
|
type: TDShapeType.Ellipse
|
|
|
|
radius: number[]
|
|
|
|
label?: string
|
|
|
|
labelPoint?: number[]
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TriangleShape extends TDBaseShape {
|
|
|
|
type: TDShapeType.Triangle
|
|
|
|
size: number[]
|
|
|
|
label?: string
|
|
|
|
labelPoint?: number[]
|
|
|
|
}
|
|
|
|
|
|
|
|
enum Decoration {
|
|
|
|
Arrow = 'arrow',
|
|
|
|
}
|
|
|
|
|
|
|
|
// The shape created with the arrow tool
|
|
|
|
interface ArrowShape extends TDBaseShape {
|
|
|
|
type: TDShapeType.Arrow
|
|
|
|
bend: number
|
|
|
|
handles: {
|
|
|
|
start: TDHandle
|
|
|
|
bend: TDHandle
|
|
|
|
end: TDHandle
|
|
|
|
}
|
|
|
|
decorations?: {
|
|
|
|
start?: Decoration
|
|
|
|
end?: Decoration
|
|
|
|
middle?: Decoration
|
|
|
|
}
|
|
|
|
label?: string
|
|
|
|
labelPoint?: number[]
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ArrowBinding extends TLV1Binding {
|
|
|
|
handleId: keyof ArrowShape['handles']
|
|
|
|
distance: number
|
|
|
|
point: number[]
|
|
|
|
}
|
|
|
|
|
|
|
|
type TDBinding = ArrowBinding
|
|
|
|
|
|
|
|
interface ImageShape extends TDBaseShape {
|
|
|
|
type: TDShapeType.Image
|
|
|
|
size: number[]
|
|
|
|
assetId: string
|
|
|
|
}
|
|
|
|
|
|
|
|
interface VideoShape extends TDBaseShape {
|
|
|
|
type: TDShapeType.Video
|
|
|
|
size: number[]
|
|
|
|
assetId: string
|
|
|
|
isPlaying: boolean
|
|
|
|
currentTime: number
|
|
|
|
}
|
|
|
|
|
|
|
|
// The shape created by the text tool
|
|
|
|
interface TextShape extends TDBaseShape {
|
|
|
|
type: TDShapeType.Text
|
|
|
|
text: string
|
|
|
|
}
|
|
|
|
|
|
|
|
// The shape created by the sticky tool
|
|
|
|
interface StickyShape extends TDBaseShape {
|
|
|
|
type: TDShapeType.Sticky
|
|
|
|
size: number[]
|
|
|
|
text: string
|
|
|
|
}
|
|
|
|
|
|
|
|
// The shape created when multiple shapes are grouped
|
|
|
|
interface GroupShape extends TDBaseShape {
|
|
|
|
type: TDShapeType.Group
|
|
|
|
size: number[]
|
|
|
|
children: string[]
|
|
|
|
}
|
|
|
|
|
|
|
|
type TDShape =
|
|
|
|
| RectangleShape
|
|
|
|
| EllipseShape
|
|
|
|
| TriangleShape
|
|
|
|
| DrawShape
|
|
|
|
| ArrowShape
|
|
|
|
| TextShape
|
|
|
|
| GroupShape
|
|
|
|
| StickyShape
|
|
|
|
| ImageShape
|
|
|
|
| VideoShape
|
|
|
|
|
|
|
|
type TDPage = {
|
|
|
|
id: string
|
|
|
|
name?: string
|
|
|
|
childIndex?: number
|
|
|
|
shapes: Record<string, TDShape>
|
|
|
|
bindings: Record<string, TDBinding>
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TLV1Bounds {
|
|
|
|
minX: number
|
|
|
|
minY: number
|
|
|
|
maxX: number
|
|
|
|
maxY: number
|
|
|
|
width: number
|
|
|
|
height: number
|
|
|
|
rotation?: number
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TLV1PageState {
|
|
|
|
id: string
|
2023-07-26 09:24:21 +00:00
|
|
|
selectedIds: string[]
|
2023-04-25 11:01:25 +00:00
|
|
|
camera: {
|
|
|
|
point: number[]
|
|
|
|
zoom: number
|
|
|
|
}
|
|
|
|
brush?: TLV1Bounds | null
|
|
|
|
pointedId?: string | null
|
2023-07-26 09:24:21 +00:00
|
|
|
hoveredId?: string | null
|
|
|
|
editingId?: string | null
|
2023-04-25 11:01:25 +00:00
|
|
|
bindingId?: string | null
|
|
|
|
}
|
|
|
|
|
|
|
|
enum TDAssetType {
|
|
|
|
Image = 'image',
|
|
|
|
Video = 'video',
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TDImageAsset extends TLV1Asset {
|
|
|
|
type: TDAssetType.Image
|
|
|
|
fileName: string
|
|
|
|
src: string
|
|
|
|
size: number[]
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TDVideoAsset extends TLV1Asset {
|
|
|
|
type: TDAssetType.Video
|
|
|
|
fileName: string
|
|
|
|
src: string
|
|
|
|
size: number[]
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TLV1Asset {
|
|
|
|
id: string
|
|
|
|
type: string
|
|
|
|
}
|
|
|
|
|
|
|
|
type TDAsset = TDImageAsset | TDVideoAsset
|
|
|
|
|
|
|
|
type TDAssets = Record<string, TDAsset>
|
|
|
|
|
|
|
|
/** @internal */
|
|
|
|
export interface LegacyTldrawDocument {
|
|
|
|
id: string
|
|
|
|
name: string
|
|
|
|
version: number
|
|
|
|
pages: Record<string, TDPage>
|
|
|
|
pageStates: Record<string, TLV1PageState>
|
|
|
|
assets: TDAssets
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ------------------ Translations ------------------ */
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
const v1ColorsToV2Colors: Record<ColorStyle, TLDefaultColorStyle> = {
|
2023-04-25 11:01:25 +00:00
|
|
|
[ColorStyle.White]: 'black',
|
|
|
|
[ColorStyle.Black]: 'black',
|
|
|
|
[ColorStyle.LightGray]: 'grey',
|
|
|
|
[ColorStyle.Gray]: 'grey',
|
|
|
|
[ColorStyle.Green]: 'light-green',
|
|
|
|
[ColorStyle.Cyan]: 'green',
|
|
|
|
[ColorStyle.Blue]: 'light-blue',
|
|
|
|
[ColorStyle.Indigo]: 'blue',
|
|
|
|
[ColorStyle.Orange]: 'orange',
|
|
|
|
[ColorStyle.Yellow]: 'yellow',
|
|
|
|
[ColorStyle.Red]: 'red',
|
|
|
|
[ColorStyle.Violet]: 'light-violet',
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
const v1FontsToV2Fonts: Record<FontStyle, TLDefaultFontStyle> = {
|
2023-04-25 11:01:25 +00:00
|
|
|
[FontStyle.Mono]: 'mono',
|
|
|
|
[FontStyle.Sans]: 'sans',
|
|
|
|
[FontStyle.Script]: 'draw',
|
|
|
|
[FontStyle.Serif]: 'serif',
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
const v1AlignsToV2Aligns: Record<AlignStyle, TLDefaultHorizontalAlignStyle> = {
|
2023-04-25 11:01:25 +00:00
|
|
|
[AlignStyle.Start]: 'start',
|
|
|
|
[AlignStyle.Middle]: 'middle',
|
|
|
|
[AlignStyle.End]: 'end',
|
|
|
|
[AlignStyle.Justify]: 'start',
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
const v1TextSizesToV2TextSizes: Record<SizeStyle, TLDefaultSizeStyle> = {
|
2023-04-25 11:01:25 +00:00
|
|
|
[SizeStyle.Small]: 's',
|
|
|
|
[SizeStyle.Medium]: 'l',
|
|
|
|
[SizeStyle.Large]: 'xl',
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
const v1SizesToV2Sizes: Record<SizeStyle, TLDefaultSizeStyle> = {
|
2023-04-25 11:01:25 +00:00
|
|
|
[SizeStyle.Small]: 'm',
|
|
|
|
[SizeStyle.Medium]: 'l',
|
|
|
|
[SizeStyle.Large]: 'xl',
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
const v1DashesToV2Dashes: Record<DashStyle, TLDefaultDashStyle> = {
|
2023-04-25 11:01:25 +00:00
|
|
|
[DashStyle.Solid]: 'solid',
|
|
|
|
[DashStyle.Dashed]: 'dashed',
|
|
|
|
[DashStyle.Dotted]: 'dotted',
|
|
|
|
[DashStyle.Draw]: 'draw',
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
function getV2Color(color: ColorStyle | undefined): TLDefaultColorStyle {
|
2023-04-25 11:01:25 +00:00
|
|
|
return color ? v1ColorsToV2Colors[color] ?? 'black' : 'black'
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
function getV2Font(font: FontStyle | undefined): TLDefaultFontStyle {
|
2023-04-25 11:01:25 +00:00
|
|
|
return font ? v1FontsToV2Fonts[font] ?? 'draw' : 'draw'
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
function getV2Align(align: AlignStyle | undefined): TLDefaultHorizontalAlignStyle {
|
2023-04-25 11:01:25 +00:00
|
|
|
return align ? v1AlignsToV2Aligns[align] ?? 'middle' : 'middle'
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
function getV2TextSize(size: SizeStyle | undefined): TLDefaultSizeStyle {
|
2023-04-25 11:01:25 +00:00
|
|
|
return size ? v1TextSizesToV2TextSizes[size] ?? 'm' : 'm'
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
function getV2Size(size: SizeStyle | undefined): TLDefaultSizeStyle {
|
2023-04-25 11:01:25 +00:00
|
|
|
return size ? v1SizesToV2Sizes[size] ?? 'l' : 'l'
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
function getV2Dash(dash: DashStyle | undefined): TLDefaultDashStyle {
|
2023-04-25 11:01:25 +00:00
|
|
|
return dash ? v1DashesToV2Dashes[dash] ?? 'draw' : 'draw'
|
|
|
|
}
|
|
|
|
|
2024-01-03 12:13:15 +00:00
|
|
|
function getV2Point(point: number[]): VecModel {
|
2023-04-25 11:01:25 +00:00
|
|
|
return {
|
|
|
|
x: coerceNumber(point[0]),
|
|
|
|
y: coerceNumber(point[1]),
|
|
|
|
z: point[2] == null ? 0.5 : coerceNumber(point[2]),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:33:47 +00:00
|
|
|
function getV2Arrowhead(decoration: Decoration | undefined): TLArrowShapeArrowheadStyle {
|
2023-04-25 11:01:25 +00:00
|
|
|
return decoration === Decoration.Arrow ? 'arrow' : 'none'
|
|
|
|
}
|
|
|
|
|
|
|
|
function getV2Fill(isFilled: boolean | undefined, color: ColorStyle) {
|
|
|
|
return isFilled
|
|
|
|
? color === ColorStyle.Black || color === ColorStyle.White
|
|
|
|
? 'semi'
|
|
|
|
: 'solid'
|
|
|
|
: 'none'
|
|
|
|
}
|