kopia lustrzana https://github.com/Tldraw/Tldraw
Merge dfc5ceae21
into 7442456d85
commit
0b5ad72ed1
|
@ -56,17 +56,18 @@ export default function AfterCreateUpdateShapeExample() {
|
||||||
|
|
||||||
// create some shapes to demonstrate the side-effects we added
|
// create some shapes to demonstrate the side-effects we added
|
||||||
function createDemoShapes(editor: Editor) {
|
function createDemoShapes(editor: Editor) {
|
||||||
editor
|
const result = editor.createShapes(
|
||||||
.createShapes(
|
'there can only be one red shape'.split(' ').map((word, i) => ({
|
||||||
'there can only be one red shape'.split(' ').map((word, i) => ({
|
id: createShapeId(),
|
||||||
id: createShapeId(),
|
type: 'text',
|
||||||
type: 'text',
|
y: i * 30,
|
||||||
y: i * 30,
|
props: {
|
||||||
props: {
|
color: i === 5 ? 'red' : 'black',
|
||||||
color: i === 5 ? 'red' : 'black',
|
text: word,
|
||||||
text: word,
|
},
|
||||||
},
|
}))
|
||||||
}))
|
)
|
||||||
)
|
if (result.ok) {
|
||||||
.zoomToContent({ duration: 0 })
|
editor.zoomToContent({ duration: 0 })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,25 +24,26 @@ export default function BeforeDeleteShapeExample() {
|
||||||
|
|
||||||
// create some shapes to demonstrate the side-effect we added
|
// create some shapes to demonstrate the side-effect we added
|
||||||
function createDemoShapes(editor: Editor) {
|
function createDemoShapes(editor: Editor) {
|
||||||
editor
|
const result = editor.createShapes([
|
||||||
.createShapes([
|
{
|
||||||
{
|
id: createShapeId(),
|
||||||
id: createShapeId(),
|
type: 'text',
|
||||||
type: 'text',
|
props: {
|
||||||
props: {
|
text: "Red shapes can't be deleted",
|
||||||
text: "Red shapes can't be deleted",
|
color: 'red',
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
id: createShapeId(),
|
{
|
||||||
type: 'text',
|
id: createShapeId(),
|
||||||
y: 30,
|
type: 'text',
|
||||||
props: {
|
y: 30,
|
||||||
text: 'but other shapes can',
|
props: {
|
||||||
color: 'black',
|
text: 'but other shapes can',
|
||||||
},
|
color: 'black',
|
||||||
},
|
},
|
||||||
])
|
},
|
||||||
.zoomToContent({ duration: 0 })
|
])
|
||||||
|
if (result.ok) {
|
||||||
|
editor.zoomToContent({ duration: 0 })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -633,8 +633,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
createPage(page: Partial<TLPage>): this;
|
createPage(page: Partial<TLPage>): this;
|
||||||
createShape<T extends TLUnknownShape>(shape: OptionalKeys<TLShapePartial<T>, 'id'>): this;
|
createShape<T extends TLUnknownShape>(shape: OptionalKeys<TLShapePartial<T>, 'id'>): EditorResult<void, CreateShapeError>;
|
||||||
createShapes<T extends TLUnknownShape>(shapes: OptionalKeys<TLShapePartial<T>, 'id'>[]): this;
|
createShapes<T extends TLUnknownShape>(shapes: OptionalKeys<TLShapePartial<T>, 'id'>[]): EditorResult<void, CreateShapeError>;
|
||||||
deleteAssets(assets: TLAsset[] | TLAssetId[]): this;
|
deleteAssets(assets: TLAsset[] | TLAssetId[]): this;
|
||||||
deleteOpenMenu(id: string): this;
|
deleteOpenMenu(id: string): this;
|
||||||
deletePage(page: TLPage | TLPageId): this;
|
deletePage(page: TLPage | TLPageId): this;
|
||||||
|
@ -2192,6 +2192,16 @@ export interface TLErrorBoundaryProps {
|
||||||
onError?: ((error: unknown) => void) | null;
|
onError?: ((error: unknown) => void) | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export type TLErrorEvent = {
|
||||||
|
type: 'max-shapes-reached';
|
||||||
|
value: [{
|
||||||
|
count: number;
|
||||||
|
name: string;
|
||||||
|
pageId: TLPageId;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface TLEventHandlers {
|
export interface TLEventHandlers {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -2235,12 +2245,6 @@ export type TLEventInfo = TLCancelEventInfo | TLClickEventInfo | TLCompleteEvent
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface TLEventMap {
|
export interface TLEventMap {
|
||||||
// (undocumented)
|
|
||||||
'max-shapes': [{
|
|
||||||
count: number;
|
|
||||||
name: string;
|
|
||||||
pageId: TLPageId;
|
|
||||||
}];
|
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
'select-all-text': [{
|
'select-all-text': [{
|
||||||
shapeId: TLShapeId;
|
shapeId: TLShapeId;
|
||||||
|
@ -2256,6 +2260,8 @@ export interface TLEventMap {
|
||||||
error: unknown;
|
error: unknown;
|
||||||
}];
|
}];
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
error: [TLErrorEvent];
|
||||||
|
// (undocumented)
|
||||||
event: [TLEventInfo];
|
event: [TLEventInfo];
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
frame: [number];
|
frame: [number];
|
||||||
|
|
|
@ -196,7 +196,11 @@ export {
|
||||||
type SvgExportDef,
|
type SvgExportDef,
|
||||||
} from './lib/editor/types/SvgExportContext'
|
} from './lib/editor/types/SvgExportContext'
|
||||||
export { type TLContent } from './lib/editor/types/clipboard-types'
|
export { type TLContent } from './lib/editor/types/clipboard-types'
|
||||||
export { type TLEventMap, type TLEventMapHandler } from './lib/editor/types/emit-types'
|
export {
|
||||||
|
type TLErrorEvent,
|
||||||
|
type TLEventMap,
|
||||||
|
type TLEventMapHandler,
|
||||||
|
} from './lib/editor/types/emit-types'
|
||||||
export {
|
export {
|
||||||
EVENT_NAME_MAP,
|
EVENT_NAME_MAP,
|
||||||
type TLBaseEventInfo,
|
type TLBaseEventInfo,
|
||||||
|
|
|
@ -120,6 +120,14 @@ import { getStraightArrowInfo } from './shapes/shared/arrow/straight-arrow'
|
||||||
import { RootState } from './tools/RootState'
|
import { RootState } from './tools/RootState'
|
||||||
import { StateNode, TLStateNodeConstructor } from './tools/StateNode'
|
import { StateNode, TLStateNodeConstructor } from './tools/StateNode'
|
||||||
import { TLContent } from './types/clipboard-types'
|
import { TLContent } from './types/clipboard-types'
|
||||||
|
import {
|
||||||
|
CreateShapeError,
|
||||||
|
EditorResult,
|
||||||
|
MAX_SHAPES_REACHED_ERROR_ERROR,
|
||||||
|
NOT_ARRAY_OF_SHAPES_ERROR,
|
||||||
|
NO_SHAPES_PROVIDED_ERROR,
|
||||||
|
READONLY_ROOM_ERROR,
|
||||||
|
} from './types/editor-result-types'
|
||||||
import { TLEventMap } from './types/emit-types'
|
import { TLEventMap } from './types/emit-types'
|
||||||
import {
|
import {
|
||||||
TLEventInfo,
|
TLEventInfo,
|
||||||
|
@ -6223,9 +6231,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
createShape<T extends TLUnknownShape>(shape: OptionalKeys<TLShapePartial<T>, 'id'>): this {
|
createShape<T extends TLUnknownShape>(shape: OptionalKeys<TLShapePartial<T>, 'id'>) {
|
||||||
this.createShapes([shape])
|
return this.createShapes([shape])
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6242,12 +6249,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
createShapes<T extends TLUnknownShape>(shapes: OptionalKeys<TLShapePartial<T>, 'id'>[]): this {
|
createShapes<T extends TLUnknownShape>(
|
||||||
|
shapes: OptionalKeys<TLShapePartial<T>, 'id'>[]
|
||||||
|
): EditorResult<void, CreateShapeError> {
|
||||||
if (!Array.isArray(shapes)) {
|
if (!Array.isArray(shapes)) {
|
||||||
throw Error('Editor.createShapes: must provide an array of shapes or shape partials')
|
return EditorResult.error(NOT_ARRAY_OF_SHAPES_ERROR)
|
||||||
}
|
}
|
||||||
if (this.getInstanceState().isReadonly) return this
|
if (this.getInstanceState().isReadonly) return EditorResult.error(READONLY_ROOM_ERROR)
|
||||||
if (shapes.length <= 0) return this
|
if (shapes.length <= 0) return EditorResult.error(NO_SHAPES_PROVIDED_ERROR)
|
||||||
|
|
||||||
const currentPageShapeIds = this.getCurrentPageShapeIds()
|
const currentPageShapeIds = this.getCurrentPageShapeIds()
|
||||||
|
|
||||||
|
@ -6256,12 +6265,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
if (maxShapesReached) {
|
if (maxShapesReached) {
|
||||||
// can't create more shapes than fit on the page
|
// can't create more shapes than fit on the page
|
||||||
alertMaxShapes(this)
|
alertMaxShapes(this)
|
||||||
return this
|
return EditorResult.error(MAX_SHAPES_REACHED_ERROR_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
const focusedGroupId = this.getFocusedGroupId()
|
const focusedGroupId = this.getFocusedGroupId()
|
||||||
|
|
||||||
return this.batch(() => {
|
this.batch(() => {
|
||||||
// 1. Parents
|
// 1. Parents
|
||||||
|
|
||||||
// Make sure that each partial will become the child of either the
|
// Make sure that each partial will become the child of either the
|
||||||
|
@ -6419,6 +6428,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
|
|
||||||
this.store.put(shapeRecordsToCreate)
|
this.store.put(shapeRecordsToCreate)
|
||||||
})
|
})
|
||||||
|
return EditorResult.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
private animatingShapes = new Map<TLShapeId, string>()
|
private animatingShapes = new Map<TLShapeId, string>()
|
||||||
|
@ -8509,7 +8519,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
|
|
||||||
function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) {
|
function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) {
|
||||||
const name = editor.getPage(pageId)!.name
|
const name = editor.getPage(pageId)!.name
|
||||||
editor.emit('max-shapes', { name, pageId, count: MAX_SHAPES_PER_PAGE })
|
editor.emit('error', {
|
||||||
|
type: 'max-shapes-reached',
|
||||||
|
value: [{ name, pageId, count: MAX_SHAPES_PER_PAGE }],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyPartialToShape<T extends TLShape>(prev: T, partial?: TLShapePartial<T>): T {
|
function applyPartialToShape<T extends TLShape>(prev: T, partial?: TLShapePartial<T>): T {
|
||||||
|
|
|
@ -28,20 +28,24 @@ export class Pointing extends StateNode {
|
||||||
|
|
||||||
this.editor.mark(this.markId)
|
this.editor.mark(this.markId)
|
||||||
|
|
||||||
this.editor
|
const result = this.editor.createShapes<TLBaseBoxShape>([
|
||||||
.createShapes<TLBaseBoxShape>([
|
{
|
||||||
{
|
id,
|
||||||
id,
|
type: shapeType,
|
||||||
type: shapeType,
|
x: originPagePoint.x,
|
||||||
x: originPagePoint.x,
|
y: originPagePoint.y,
|
||||||
y: originPagePoint.y,
|
props: {
|
||||||
props: {
|
w: 1,
|
||||||
w: 1,
|
h: 1,
|
||||||
h: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
])
|
},
|
||||||
.select(id)
|
])
|
||||||
|
if (!result.ok) {
|
||||||
|
this.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editor.select(id)
|
||||||
this.editor.setCurrentTool('select.resizing', {
|
this.editor.setCurrentTool('select.resizing', {
|
||||||
...info,
|
...info,
|
||||||
target: 'selection',
|
target: 'selection',
|
||||||
|
@ -85,7 +89,7 @@ export class Pointing extends StateNode {
|
||||||
|
|
||||||
this.editor.mark(this.markId)
|
this.editor.mark(this.markId)
|
||||||
|
|
||||||
this.editor.createShapes<TLBaseBoxShape>([
|
const result = this.editor.createShapes<TLBaseBoxShape>([
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
type: shapeType,
|
type: shapeType,
|
||||||
|
@ -93,6 +97,10 @@ export class Pointing extends StateNode {
|
||||||
y: originPagePoint.y,
|
y: originPagePoint.y,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
if (!result.ok) {
|
||||||
|
this.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const shape = this.editor.getShape<TLBaseBoxShape>(id)!
|
const shape = this.editor.getShape<TLBaseBoxShape>(id)!
|
||||||
const { w, h } = this.editor.getShapeUtil(shape).getDefaultProps() as TLBaseBoxShape['props']
|
const { w, h } = this.editor.getShapeUtil(shape).getDefaultProps() as TLBaseBoxShape['props']
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Result types
|
||||||
|
/** @public */
|
||||||
|
export type Ok = { readonly ok: true }
|
||||||
|
/** @public */
|
||||||
|
export type OkWithValue<T> = { readonly ok: true; readonly value: T }
|
||||||
|
/** @public */
|
||||||
|
export type Error<E> = { readonly ok: false; readonly error: E }
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export type EditorResult<T, E> = Error<E> | Ok | OkWithValue<T>
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export const EditorResult = {
|
||||||
|
ok(): Ok {
|
||||||
|
return { ok: true }
|
||||||
|
},
|
||||||
|
okWithValue<T>(value: T): OkWithValue<T> {
|
||||||
|
return { ok: true, value }
|
||||||
|
},
|
||||||
|
error<E>(error: E): Error<E> {
|
||||||
|
return { ok: false, error }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// All errors
|
||||||
|
/** @public */
|
||||||
|
export type TLEditorErrorType = CreateShapeErrorType | (typeof READONLY_ROOM_ERROR)['type']
|
||||||
|
|
||||||
|
// General errors
|
||||||
|
/** @public */
|
||||||
|
export const READONLY_ROOM_ERROR = { type: 'readonly-room' as const, message: 'Room is readonly' }
|
||||||
|
|
||||||
|
// Create shape errors
|
||||||
|
/** @public */
|
||||||
|
export const NOT_ARRAY_OF_SHAPES_ERROR = {
|
||||||
|
type: 'not-array' as const,
|
||||||
|
message: 'Expected an array',
|
||||||
|
}
|
||||||
|
/** @public */
|
||||||
|
export const NO_SHAPES_PROVIDED_ERROR = {
|
||||||
|
type: 'no-shapes-provided' as const,
|
||||||
|
message: 'No shapes provided',
|
||||||
|
}
|
||||||
|
/** @public */
|
||||||
|
export const MAX_SHAPES_REACHED_ERROR_ERROR = {
|
||||||
|
type: 'max-shapes-reached' as const,
|
||||||
|
message: 'Max shapes reached',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export type CreateShapeErrorType =
|
||||||
|
| (typeof READONLY_ROOM_ERROR)['type']
|
||||||
|
| (typeof NOT_ARRAY_OF_SHAPES_ERROR)['type']
|
||||||
|
| (typeof NO_SHAPES_PROVIDED_ERROR)['type']
|
||||||
|
| (typeof MAX_SHAPES_REACHED_ERROR_ERROR)['type']
|
||||||
|
/** @public */
|
||||||
|
export type CreateShapeError = { type: CreateShapeErrorType; message: string }
|
|
@ -2,11 +2,16 @@ import { HistoryEntry } from '@tldraw/store'
|
||||||
import { TLPageId, TLRecord, TLShapeId } from '@tldraw/tlschema'
|
import { TLPageId, TLRecord, TLShapeId } from '@tldraw/tlschema'
|
||||||
import { TLEventInfo } from './event-types'
|
import { TLEventInfo } from './event-types'
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
export type TLErrorEvent = {
|
||||||
|
type: 'max-shapes-reached'
|
||||||
|
value: [{ name: string; pageId: TLPageId; count: number }]
|
||||||
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface TLEventMap {
|
export interface TLEventMap {
|
||||||
// Lifecycle / Internal
|
// Lifecycle / Internal
|
||||||
mount: []
|
mount: []
|
||||||
'max-shapes': [{ name: string; pageId: TLPageId; count: number }]
|
|
||||||
change: [HistoryEntry<TLRecord>]
|
change: [HistoryEntry<TLRecord>]
|
||||||
update: []
|
update: []
|
||||||
crash: [{ error: unknown }]
|
crash: [{ error: unknown }]
|
||||||
|
@ -16,6 +21,7 @@ export interface TLEventMap {
|
||||||
tick: [number]
|
tick: [number]
|
||||||
frame: [number]
|
frame: [number]
|
||||||
'select-all-text': [{ shapeId: TLShapeId }]
|
'select-all-text': [{ shapeId: TLShapeId }]
|
||||||
|
error: [TLErrorEvent]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
|
|
@ -203,7 +203,10 @@ export function registerDefaultExternalContentHandlers(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.createShapes([shapePartial]).select(id)
|
const result = editor.createShapes([shapePartial])
|
||||||
|
if (result.ok) {
|
||||||
|
editor.select(id)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// files
|
// files
|
||||||
|
@ -483,7 +486,10 @@ export async function createShapesForAssets(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the shapes
|
// Create the shapes
|
||||||
editor.createShapes(partials).select(...partials.map((p) => p.id))
|
const result = editor.createShapes(partials)
|
||||||
|
if (!result.ok) return
|
||||||
|
|
||||||
|
editor.select(...partials.map((p) => p.id))
|
||||||
|
|
||||||
// Re-position shapes so that the center of the group is at the provided point
|
// Re-position shapes so that the center of the group is at the provided point
|
||||||
centerSelectionAroundPoint(editor, position)
|
centerSelectionAroundPoint(editor, position)
|
||||||
|
@ -539,7 +545,9 @@ export function createEmptyBookmarkShape(
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.batch(() => {
|
editor.batch(() => {
|
||||||
editor.createShapes([partial]).select(partial.id)
|
const result = editor.createShapes([partial])
|
||||||
|
if (!result.ok) return
|
||||||
|
editor.select(partial.id)
|
||||||
centerSelectionAroundPoint(editor, position)
|
centerSelectionAroundPoint(editor, position)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -294,8 +294,8 @@ describe('Other cases when arrow are moved', () => {
|
||||||
{ id: ids.box3, type: 'geo', x: 0, y: 300, props: { w: 100, h: 100 } },
|
{ id: ids.box3, type: 'geo', x: 0, y: 300, props: { w: 100, h: 100 } },
|
||||||
{ id: ids.box4, type: 'geo', x: 0, y: 600, props: { w: 100, h: 100 } },
|
{ id: ids.box4, type: 'geo', x: 0, y: 600, props: { w: 100, h: 100 } },
|
||||||
])
|
])
|
||||||
.selectAll()
|
|
||||||
.groupShapes(editor.getSelectedShapeIds())
|
editor.selectAll().groupShapes(editor.getSelectedShapeIds())
|
||||||
|
|
||||||
editor.setCurrentTool('arrow').pointerDown(1000, 1000).pointerMove(50, 350).pointerUp(50, 350)
|
editor.setCurrentTool('arrow').pointerDown(1000, 1000).pointerMove(50, 350).pointerUp(50, 350)
|
||||||
let arrow = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1]
|
let arrow = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1]
|
||||||
|
|
|
@ -26,29 +26,31 @@ export class Pointing extends StateNode {
|
||||||
|
|
||||||
this.editor.mark(this.markId)
|
this.editor.mark(this.markId)
|
||||||
|
|
||||||
this.editor
|
const result = this.editor.createShapes<TLGeoShape>([
|
||||||
.createShapes<TLGeoShape>([
|
{
|
||||||
{
|
id,
|
||||||
id,
|
type: 'geo',
|
||||||
type: 'geo',
|
x: originPagePoint.x,
|
||||||
x: originPagePoint.x,
|
y: originPagePoint.y,
|
||||||
y: originPagePoint.y,
|
props: {
|
||||||
props: {
|
w: 1,
|
||||||
w: 1,
|
h: 1,
|
||||||
h: 1,
|
geo: this.editor.getStyleForNextShape(GeoShapeGeoStyle),
|
||||||
geo: this.editor.getStyleForNextShape(GeoShapeGeoStyle),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
])
|
},
|
||||||
.select(id)
|
])
|
||||||
.setCurrentTool('select.resizing', {
|
if (!result.ok) {
|
||||||
...info,
|
this.cancel()
|
||||||
target: 'selection',
|
return
|
||||||
handle: 'bottom_right',
|
}
|
||||||
isCreating: true,
|
this.editor.select(id).setCurrentTool('select.resizing', {
|
||||||
creationCursorOffset: { x: 1, y: 1 },
|
...info,
|
||||||
onInteractionEnd: 'geo',
|
target: 'selection',
|
||||||
})
|
handle: 'bottom_right',
|
||||||
|
isCreating: true,
|
||||||
|
creationCursorOffset: { x: 1, y: 1 },
|
||||||
|
onInteractionEnd: 'geo',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +75,7 @@ export class Pointing extends StateNode {
|
||||||
|
|
||||||
this.editor.mark(this.markId)
|
this.editor.mark(this.markId)
|
||||||
|
|
||||||
this.editor.createShapes<TLGeoShape>([
|
const result = this.editor.createShapes<TLGeoShape>([
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
type: 'geo',
|
type: 'geo',
|
||||||
|
@ -86,6 +88,10 @@ export class Pointing extends StateNode {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
if (!result.ok) {
|
||||||
|
this.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const shape = this.editor.getShape<TLGeoShape>(id)!
|
const shape = this.editor.getShape<TLGeoShape>(id)!
|
||||||
if (!shape) return
|
if (!shape) return
|
||||||
|
|
|
@ -190,8 +190,8 @@ describe('Grid placement helpers', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Falls into a sticky pit when empty', () => {
|
it('Falls into a sticky pit when empty', () => {
|
||||||
|
editor.createShape({ type: 'note', x: 0, y: 0 })
|
||||||
editor
|
editor
|
||||||
.createShape({ type: 'note', x: 0, y: 0 })
|
|
||||||
.setCurrentTool('note')
|
.setCurrentTool('note')
|
||||||
.pointerMove(324, 104)
|
.pointerMove(324, 104)
|
||||||
.click()
|
.click()
|
||||||
|
@ -204,9 +204,9 @@ describe('Grid placement helpers', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Does not create a new sticky note in a sticky pit if a note is already there', () => {
|
it('Does not create a new sticky note in a sticky pit if a note is already there', () => {
|
||||||
|
editor.createShape({ type: 'note', x: 0, y: 0 })
|
||||||
|
editor.createShape({ type: 'note', x: 330, y: 8 }) // make a shape kinda there already!
|
||||||
editor
|
editor
|
||||||
.createShape({ type: 'note', x: 0, y: 0 })
|
|
||||||
.createShape({ type: 'note', x: 330, y: 8 }) // make a shape kinda there already!
|
|
||||||
.setCurrentTool('note')
|
.setCurrentTool('note')
|
||||||
.pointerMove(300, 104)
|
.pointerMove(300, 104)
|
||||||
.click()
|
.click()
|
||||||
|
@ -241,7 +241,8 @@ describe('Grid placement helpers', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Falls into correct pit below notes with growY', () => {
|
it('Falls into correct pit below notes with growY', () => {
|
||||||
editor.createShape({ type: 'note', x: 0, y: 0 }).updateShape({
|
editor.createShape({ type: 'note', x: 0, y: 0 })
|
||||||
|
editor.updateShape({
|
||||||
...editor.getLastCreatedShape(),
|
...editor.getLastCreatedShape(),
|
||||||
props: { growY: 100 },
|
props: { growY: 100 },
|
||||||
})
|
})
|
||||||
|
|
|
@ -40,7 +40,12 @@ export class Pointing extends StateNode {
|
||||||
if (offset) {
|
if (offset) {
|
||||||
center.sub(offset)
|
center.sub(offset)
|
||||||
}
|
}
|
||||||
this.shape = createSticky(this.editor, id, center)
|
const result = createSticky(this.editor, id, center)
|
||||||
|
if (!result) {
|
||||||
|
this.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.shape = result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +58,12 @@ export class Pointing extends StateNode {
|
||||||
if (offset) {
|
if (offset) {
|
||||||
center.sub(offset)
|
center.sub(offset)
|
||||||
}
|
}
|
||||||
this.shape = createSticky(this.editor, id, center)
|
const result = createSticky(this.editor, id, center)
|
||||||
|
if (!result) {
|
||||||
|
this.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.shape = result
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.setCurrentTool('select.translating', {
|
this.editor.setCurrentTool('select.translating', {
|
||||||
|
@ -123,14 +133,17 @@ export function getNotePitOffset(editor: Editor, center: Vec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSticky(editor: Editor, id: TLShapeId, center: Vec) {
|
export function createSticky(editor: Editor, id: TLShapeId, center: Vec) {
|
||||||
editor
|
const result = editor.createShape({
|
||||||
.createShape({
|
id,
|
||||||
id,
|
type: 'note',
|
||||||
type: 'note',
|
x: center.x,
|
||||||
x: center.x,
|
y: center.y,
|
||||||
y: center.y,
|
})
|
||||||
})
|
if (!result.ok) {
|
||||||
.select(id)
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.select(id)
|
||||||
|
|
||||||
const shape = editor.getShape<TLNoteShape>(id)!
|
const shape = editor.getShape<TLNoteShape>(id)!
|
||||||
const bounds = editor.getShapeGeometry(shape).bounds
|
const bounds = editor.getShapeGeometry(shape).bounds
|
||||||
|
|
|
@ -78,21 +78,24 @@ export class Pointing extends StateNode {
|
||||||
this.editor.mark('creating text shape')
|
this.editor.mark('creating text shape')
|
||||||
const id = createShapeId()
|
const id = createShapeId()
|
||||||
const { x, y } = this.editor.inputs.currentPagePoint
|
const { x, y } = this.editor.inputs.currentPagePoint
|
||||||
this.editor
|
const result = this.editor.createShapes([
|
||||||
.createShapes([
|
{
|
||||||
{
|
id,
|
||||||
id,
|
type: 'text',
|
||||||
type: 'text',
|
x,
|
||||||
x,
|
y,
|
||||||
y,
|
props: {
|
||||||
props: {
|
text: '',
|
||||||
text: '',
|
autoSize: true,
|
||||||
autoSize: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
])
|
},
|
||||||
.select(id)
|
])
|
||||||
|
if (!result.ok) {
|
||||||
|
this.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editor.select(id)
|
||||||
this.editor.setEditingShape(id)
|
this.editor.setEditingShape(id)
|
||||||
this.editor.setCurrentTool('select')
|
this.editor.setCurrentTool('select')
|
||||||
this.editor.root.getCurrent()?.transition('editing_shape')
|
this.editor.root.getCurrent()?.transition('editing_shape')
|
||||||
|
|
|
@ -295,6 +295,7 @@ function createNShapes(editor: Editor, n: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.batch(() => {
|
editor.batch(() => {
|
||||||
editor.createShapes(shapesToCreate).setSelectedShapes(shapesToCreate.map((s) => s.id))
|
editor.createShapes(shapesToCreate)
|
||||||
|
editor.setSelectedShapes(shapesToCreate.map((s) => s.id))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEditor } from '@tldraw/editor'
|
import { TLErrorEvent, useEditor } from '@tldraw/editor'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useToasts } from '../context/toasts'
|
import { useToasts } from '../context/toasts'
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ export function useEditorEvents() {
|
||||||
const { addToast } = useToasts()
|
const { addToast } = useToasts()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleMaxShapes({ name, count }: { name: string; pageId: string; count: number }) {
|
function handleMaxShapes(error: TLErrorEvent) {
|
||||||
|
if (error.type !== 'max-shapes-reached') return
|
||||||
|
const [{ name, count }] = error.value
|
||||||
addToast({
|
addToast({
|
||||||
title: 'Maximum Shapes Reached',
|
title: 'Maximum Shapes Reached',
|
||||||
description: `You've reached the maximum number of shapes allowed on ${name} (${count}). Please delete some shapes or move to a different page to continue.`,
|
description: `You've reached the maximum number of shapes allowed on ${name} (${count}). Please delete some shapes or move to a different page to continue.`,
|
||||||
|
@ -16,9 +18,9 @@ export function useEditorEvents() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.addListener('max-shapes', handleMaxShapes)
|
editor.addListener('error', handleMaxShapes)
|
||||||
return () => {
|
return () => {
|
||||||
editor.removeListener('max-shapes', handleMaxShapes)
|
editor.removeListener('error', handleMaxShapes)
|
||||||
}
|
}
|
||||||
}, [editor, addToast])
|
}, [editor, addToast])
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,6 +292,7 @@ describe('When double clicking a shape', () => {
|
||||||
.deleteShapes(editor.getSelectedShapeIds())
|
.deleteShapes(editor.getSelectedShapeIds())
|
||||||
.selectNone()
|
.selectNone()
|
||||||
.createShapes([{ id: createShapeId(), type: 'geo' }])
|
.createShapes([{ id: createShapeId(), type: 'geo' }])
|
||||||
|
editor
|
||||||
.doubleClick(50, 50, { target: 'shape', shape: editor.getCurrentPageShapes()[0] })
|
.doubleClick(50, 50, { target: 'shape', shape: editor.getCurrentPageShapes()[0] })
|
||||||
.expectToBeIn('select.editing_shape')
|
.expectToBeIn('select.editing_shape')
|
||||||
})
|
})
|
||||||
|
@ -305,9 +306,7 @@ describe('When pressing enter on a selected shape', () => {
|
||||||
.deleteShapes(editor.getSelectedShapeIds())
|
.deleteShapes(editor.getSelectedShapeIds())
|
||||||
.selectNone()
|
.selectNone()
|
||||||
.createShapes([{ id, type: 'geo' }])
|
.createShapes([{ id, type: 'geo' }])
|
||||||
.select(id)
|
editor.select(id).keyUp('Enter').expectToBeIn('select.editing_shape')
|
||||||
.keyUp('Enter')
|
|
||||||
.expectToBeIn('select.editing_shape')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -336,8 +335,7 @@ describe('When double clicking the selection edge', () => {
|
||||||
.deleteShapes(editor.getSelectedShapeIds())
|
.deleteShapes(editor.getSelectedShapeIds())
|
||||||
.selectNone()
|
.selectNone()
|
||||||
.createShapes([{ id, type: 'text', x: 100, y: 100, props: { scale: 2, text: 'hello' } }])
|
.createShapes([{ id, type: 'text', x: 100, y: 100, props: { scale: 2, text: 'hello' } }])
|
||||||
.select(id)
|
editor.select(id).doubleClick(100, 100, { target: 'selection', handle: 'left' })
|
||||||
.doubleClick(100, 100, { target: 'selection', handle: 'left' })
|
|
||||||
|
|
||||||
editor.expectShapeToMatch({ id, props: { scale: 1 } })
|
editor.expectShapeToMatch({ id, props: { scale: 1 } })
|
||||||
})
|
})
|
||||||
|
@ -355,8 +353,7 @@ describe('When double clicking the selection edge', () => {
|
||||||
props: { scale: 2, autoSize: false, w: 200, text: 'hello' },
|
props: { scale: 2, autoSize: false, w: 200, text: 'hello' },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.select(id)
|
editor.select(id).doubleClick(100, 100, { target: 'selection', handle: 'left' })
|
||||||
.doubleClick(100, 100, { target: 'selection', handle: 'left' })
|
|
||||||
|
|
||||||
editor.expectShapeToMatch({ id, props: { scale: 2, autoSize: true } })
|
editor.expectShapeToMatch({ id, props: { scale: 2, autoSize: true } })
|
||||||
|
|
||||||
|
@ -378,6 +375,7 @@ describe('When double clicking the selection edge', () => {
|
||||||
props: { scale: 2, autoSize: false, w: 200, text: 'hello' },
|
props: { scale: 2, autoSize: false, w: 200, text: 'hello' },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
editor
|
||||||
.select(id)
|
.select(id)
|
||||||
.doubleClick(100, 100, { target: 'selection', handle: 'left' })
|
.doubleClick(100, 100, { target: 'selection', handle: 'left' })
|
||||||
.doubleClick(100, 100, { target: 'selection', handle: 'left' })
|
.doubleClick(100, 100, { target: 'selection', handle: 'left' })
|
||||||
|
@ -402,7 +400,7 @@ describe('When double clicking the selection edge', () => {
|
||||||
type: 'geo',
|
type: 'geo',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.select(id)
|
editor.select(id)
|
||||||
expect(editor.getEditingShapeId()).toBe(null)
|
expect(editor.getEditingShapeId()).toBe(null)
|
||||||
|
|
||||||
editor.doubleClick(100, 100, { target: 'selection', handle: 'left' })
|
editor.doubleClick(100, 100, { target: 'selection', handle: 'left' })
|
||||||
|
@ -565,8 +563,8 @@ describe('When in readonly mode', () => {
|
||||||
|
|
||||||
// This should be end to end, the problem is the blur handler of the react component
|
// This should be end to end, the problem is the blur handler of the react component
|
||||||
it('goes into pointing canvas', () => {
|
it('goes into pointing canvas', () => {
|
||||||
|
editor.createShape({ type: 'note' })
|
||||||
editor
|
editor
|
||||||
.createShape({ type: 'note' })
|
|
||||||
.pointerMove(50, 50)
|
.pointerMove(50, 50)
|
||||||
.doubleClick()
|
.doubleClick()
|
||||||
.expectToBeIn('select.editing_shape')
|
.expectToBeIn('select.editing_shape')
|
||||||
|
|
|
@ -9,13 +9,15 @@ beforeEach(() => {
|
||||||
|
|
||||||
it('Sets shape meta by default to an empty object', () => {
|
it('Sets shape meta by default to an empty object', () => {
|
||||||
const id = createShapeId()
|
const id = createShapeId()
|
||||||
editor.createShapes([{ id, type: 'geo' }]).select(id)
|
editor.createShapes([{ id, type: 'geo' }])
|
||||||
|
editor.select(id)
|
||||||
expect(editor.getOnlySelectedShape()!.meta).toStrictEqual({})
|
expect(editor.getOnlySelectedShape()!.meta).toStrictEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Sets shape meta', () => {
|
it('Sets shape meta', () => {
|
||||||
editor.getInitialMetaForShape = (shape) => ({ firstThreeCharactersOfId: shape.id.slice(0, 3) })
|
editor.getInitialMetaForShape = (shape) => ({ firstThreeCharactersOfId: shape.id.slice(0, 3) })
|
||||||
const id = createShapeId()
|
const id = createShapeId()
|
||||||
editor.createShapes([{ id, type: 'geo' }]).select(id)
|
editor.createShapes([{ id, type: 'geo' }])
|
||||||
|
editor.select(id)
|
||||||
expect(editor.getOnlySelectedShape()!.meta).toStrictEqual({ firstThreeCharactersOfId: 'sha' })
|
expect(editor.getOnlySelectedShape()!.meta).toStrictEqual({ firstThreeCharactersOfId: 'sha' })
|
||||||
})
|
})
|
||||||
|
|
|
@ -182,7 +182,8 @@ describe('When duplicating shapes that include arrows', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Preserves the same selection bounds', () => {
|
it('Preserves the same selection bounds', () => {
|
||||||
editor.selectAll().deleteShapes(editor.getSelectedShapeIds()).createShapes(shapes).selectAll()
|
editor.selectAll().deleteShapes(editor.getSelectedShapeIds()).createShapes(shapes)
|
||||||
|
editor.selectAll()
|
||||||
|
|
||||||
const boundsBefore = editor.getSelectionRotatedPageBounds()!
|
const boundsBefore = editor.getSelectionRotatedPageBounds()!
|
||||||
editor.duplicateShapes(editor.getSelectedShapeIds())
|
editor.duplicateShapes(editor.getSelectedShapeIds())
|
||||||
|
@ -190,16 +191,13 @@ describe('When duplicating shapes that include arrows', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Preserves the same selection bounds when only duplicating the arrows', () => {
|
it('Preserves the same selection bounds when only duplicating the arrows', () => {
|
||||||
editor
|
editor.selectAll().deleteShapes(editor.getSelectedShapeIds()).createShapes(shapes)
|
||||||
.selectAll()
|
editor.select(
|
||||||
.deleteShapes(editor.getSelectedShapeIds())
|
...editor
|
||||||
.createShapes(shapes)
|
.getCurrentPageShapes()
|
||||||
.select(
|
.filter((s) => editor.isShapeOfType<TLArrowShape>(s, 'arrow'))
|
||||||
...editor
|
.map((s) => s.id)
|
||||||
.getCurrentPageShapes()
|
)
|
||||||
.filter((s) => editor.isShapeOfType<TLArrowShape>(s, 'arrow'))
|
|
||||||
.map((s) => s.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
const boundsBefore = editor.getSelectionRotatedPageBounds()!
|
const boundsBefore = editor.getSelectionRotatedPageBounds()!
|
||||||
editor.duplicateShapes(editor.getSelectedShapeIds())
|
editor.duplicateShapes(editor.getSelectedShapeIds())
|
||||||
|
|
|
@ -177,6 +177,7 @@ describe('frame shapes', () => {
|
||||||
editor
|
editor
|
||||||
// Create a frame
|
// Create a frame
|
||||||
.createShapes([{ id: frameId, type: 'frame', x: 100, y: 100, props: { w: 100, h: 100 } }])
|
.createShapes([{ id: frameId, type: 'frame', x: 100, y: 100, props: { w: 100, h: 100 } }])
|
||||||
|
editor
|
||||||
.select(frameId)
|
.select(frameId)
|
||||||
// Rotate it by PI/2
|
// Rotate it by PI/2
|
||||||
.rotateSelection(Math.PI / 2)
|
.rotateSelection(Math.PI / 2)
|
||||||
|
|
|
@ -395,7 +395,7 @@ describe('When pasting into frames...', () => {
|
||||||
y: 500,
|
y: 500,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.setScreenBounds({ x: 0, y: 0, w: 1000, h: 1000 })
|
editor.setScreenBounds({ x: 0, y: 0, w: 1000, h: 1000 })
|
||||||
|
|
||||||
// put frame2 inside frame1
|
// put frame2 inside frame1
|
||||||
editor.reparentShapes([ids.frame2], ids.frame1)
|
editor.reparentShapes([ids.frame2], ids.frame1)
|
||||||
|
@ -455,20 +455,19 @@ describe('When pasting into frames...', () => {
|
||||||
editor.deleteShapes(editor.getSelectedShapeIds())
|
editor.deleteShapes(editor.getSelectedShapeIds())
|
||||||
|
|
||||||
// create a big frame away from the origin, the size of the viewport
|
// create a big frame away from the origin, the size of the viewport
|
||||||
editor
|
editor.createShapes([
|
||||||
.createShapes([
|
{
|
||||||
{
|
id: ids.frame1,
|
||||||
id: ids.frame1,
|
type: 'frame',
|
||||||
type: 'frame',
|
x: editor.getViewportScreenBounds().w,
|
||||||
x: editor.getViewportScreenBounds().w,
|
y: editor.getViewportScreenBounds().h,
|
||||||
y: editor.getViewportScreenBounds().h,
|
props: {
|
||||||
props: {
|
w: editor.getViewportScreenBounds().w,
|
||||||
w: editor.getViewportScreenBounds().w,
|
h: editor.getViewportScreenBounds().h,
|
||||||
h: editor.getViewportScreenBounds().h,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
])
|
},
|
||||||
.selectAll()
|
])
|
||||||
|
editor.selectAll()
|
||||||
// rotate the frame for hard mode
|
// rotate the frame for hard mode
|
||||||
editor.rotateSelection(45)
|
editor.rotateSelection(45)
|
||||||
// center on the center of the frame
|
// center on the center of the frame
|
||||||
|
|
|
@ -912,7 +912,7 @@ describe('When resizing a shape with children', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.select(ids.boxB, ids.lineA)
|
editor.select(ids.boxB, ids.lineA)
|
||||||
|
|
||||||
editor
|
editor
|
||||||
.pointerDown(10, 10, {
|
.pointerDown(10, 10, {
|
||||||
|
|
|
@ -680,26 +680,25 @@ describe('when a frame has multiple children', () => {
|
||||||
let box1: TLGeoShape
|
let box1: TLGeoShape
|
||||||
let box2: TLGeoShape
|
let box2: TLGeoShape
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
editor
|
editor.createShape<TLFrameShape>({ id: ids.frame1, type: 'frame', props: { w: 100, h: 100 } })
|
||||||
.createShape<TLFrameShape>({ id: ids.frame1, type: 'frame', props: { w: 100, h: 100 } })
|
editor.createShape<TLGeoShape>({
|
||||||
.createShape<TLGeoShape>({
|
id: ids.box1,
|
||||||
id: ids.box1,
|
parentId: ids.frame1,
|
||||||
parentId: ids.frame1,
|
type: 'geo',
|
||||||
type: 'geo',
|
x: 25,
|
||||||
x: 25,
|
y: 25,
|
||||||
y: 25,
|
})
|
||||||
})
|
editor.createShape<TLGeoShape>({
|
||||||
.createShape<TLGeoShape>({
|
id: ids.box2,
|
||||||
id: ids.box2,
|
parentId: ids.frame1,
|
||||||
parentId: ids.frame1,
|
type: 'geo',
|
||||||
type: 'geo',
|
x: 50,
|
||||||
x: 50,
|
y: 50,
|
||||||
y: 50,
|
props: {
|
||||||
props: {
|
w: 80,
|
||||||
w: 80,
|
h: 80,
|
||||||
h: 80,
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
box1 = editor.getShape<TLGeoShape>(ids.box1)!
|
box1 = editor.getShape<TLGeoShape>(ids.box1)!
|
||||||
box2 = editor.getShape<TLGeoShape>(ids.box2)!
|
box2 = editor.getShape<TLGeoShape>(ids.box2)!
|
||||||
})
|
})
|
||||||
|
@ -1132,13 +1131,12 @@ describe('Selects inside of groups', () => {
|
||||||
|
|
||||||
describe('when selecting behind selection', () => {
|
describe('when selecting behind selection', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
editor
|
editor.createShapes([
|
||||||
.createShapes([
|
{ id: ids.box1, type: 'geo', x: 100, y: 0, props: { fill: 'solid' } },
|
||||||
{ id: ids.box1, type: 'geo', x: 100, y: 0, props: { fill: 'solid' } },
|
{ id: ids.box2, type: 'geo', x: 0, y: 0 },
|
||||||
{ id: ids.box2, type: 'geo', x: 0, y: 0 },
|
{ id: ids.box3, type: 'geo', x: 200, y: 0 },
|
||||||
{ id: ids.box3, type: 'geo', x: 200, y: 0 },
|
])
|
||||||
])
|
editor.select(ids.box2, ids.box3)
|
||||||
.select(ids.box2, ids.box3)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not select on pointer down, only on pointer up', () => {
|
it('does not select on pointer down, only on pointer up', () => {
|
||||||
|
@ -1165,13 +1163,12 @@ describe('when selecting behind selection', () => {
|
||||||
|
|
||||||
describe('when shift+selecting', () => {
|
describe('when shift+selecting', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
editor
|
editor.createShapes([
|
||||||
.createShapes([
|
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
||||||
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
||||||
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
{ id: ids.box3, type: 'geo', x: 400, y: 0, props: { fill: 'solid' } },
|
||||||
{ id: ids.box3, type: 'geo', x: 400, y: 0, props: { fill: 'solid' } },
|
])
|
||||||
])
|
editor.select(ids.box1)
|
||||||
.select(ids.box1)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds solid shape to selection on pointer down', () => {
|
it('adds solid shape to selection on pointer down', () => {
|
||||||
|
@ -1281,15 +1278,13 @@ describe('when shift+selecting', () => {
|
||||||
|
|
||||||
describe('when shift+selecting a group', () => {
|
describe('when shift+selecting a group', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
editor
|
editor.createShapes([
|
||||||
.createShapes([
|
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
||||||
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
||||||
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
{ id: ids.box3, type: 'geo', x: 400, y: 0, props: { fill: 'solid' } },
|
||||||
{ id: ids.box3, type: 'geo', x: 400, y: 0, props: { fill: 'solid' } },
|
{ id: ids.box4, type: 'geo', x: 600, y: 0 },
|
||||||
{ id: ids.box4, type: 'geo', x: 600, y: 0 },
|
])
|
||||||
])
|
editor.groupShapes([ids.box2, ids.box3], ids.group1).select(ids.box1)
|
||||||
.groupShapes([ids.box2, ids.box3], ids.group1)
|
|
||||||
.select(ids.box1)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not add group to selection when pointing empty space in the group', () => {
|
it('does not add group to selection when pointing empty space in the group', () => {
|
||||||
|
@ -1362,14 +1357,14 @@ describe('when shift+selecting a group', () => {
|
||||||
|
|
||||||
describe('When children / descendants of a group are selected', () => {
|
describe('When children / descendants of a group are selected', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
editor.createShapes([
|
||||||
|
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
||||||
|
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
||||||
|
{ id: ids.box3, type: 'geo', x: 400, y: 0, props: { fill: 'solid' } },
|
||||||
|
{ id: ids.box4, type: 'geo', x: 600, y: 0 },
|
||||||
|
{ id: ids.box5, type: 'geo', x: 800, y: 0 },
|
||||||
|
])
|
||||||
editor
|
editor
|
||||||
.createShapes([
|
|
||||||
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
|
||||||
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
|
||||||
{ id: ids.box3, type: 'geo', x: 400, y: 0, props: { fill: 'solid' } },
|
|
||||||
{ id: ids.box4, type: 'geo', x: 600, y: 0 },
|
|
||||||
{ id: ids.box5, type: 'geo', x: 800, y: 0 },
|
|
||||||
])
|
|
||||||
.groupShapes([ids.box1, ids.box2], ids.group1)
|
.groupShapes([ids.box1, ids.box2], ids.group1)
|
||||||
.groupShapes([ids.box3, ids.box4], ids.group2)
|
.groupShapes([ids.box3, ids.box4], ids.group2)
|
||||||
.groupShapes([ids.group1, ids.group2], ids.group3)
|
.groupShapes([ids.group1, ids.group2], ids.group3)
|
||||||
|
@ -1437,14 +1432,14 @@ describe('When children / descendants of a group are selected', () => {
|
||||||
|
|
||||||
describe('When pressing the enter key with groups selected', () => {
|
describe('When pressing the enter key with groups selected', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
editor.createShapes([
|
||||||
|
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
||||||
|
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
||||||
|
{ id: ids.box3, type: 'geo', x: 400, y: 0, props: { fill: 'solid' } },
|
||||||
|
{ id: ids.box4, type: 'geo', x: 600, y: 0 },
|
||||||
|
{ id: ids.box5, type: 'geo', x: 800, y: 0 },
|
||||||
|
])
|
||||||
editor
|
editor
|
||||||
.createShapes([
|
|
||||||
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
|
||||||
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
|
||||||
{ id: ids.box3, type: 'geo', x: 400, y: 0, props: { fill: 'solid' } },
|
|
||||||
{ id: ids.box4, type: 'geo', x: 600, y: 0 },
|
|
||||||
{ id: ids.box5, type: 'geo', x: 800, y: 0 },
|
|
||||||
])
|
|
||||||
.groupShapes([ids.box1, ids.box2], ids.group1)
|
.groupShapes([ids.box1, ids.box2], ids.group1)
|
||||||
.groupShapes([ids.box3, ids.box4], ids.group2)
|
.groupShapes([ids.box3, ids.box4], ids.group2)
|
||||||
})
|
})
|
||||||
|
@ -1545,14 +1540,13 @@ describe('When double clicking an editable shape', () => {
|
||||||
describe('shift brushes to add to the selection', () => {
|
describe('shift brushes to add to the selection', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
editor.user.updateUserPreferences({ isWrapMode: false })
|
editor.user.updateUserPreferences({ isWrapMode: false })
|
||||||
editor
|
editor.createShapes([
|
||||||
.createShapes([
|
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
||||||
{ id: ids.box1, type: 'geo', x: 0, y: 0 },
|
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
||||||
{ id: ids.box2, type: 'geo', x: 200, y: 0 },
|
{ id: ids.box3, type: 'geo', x: 400, y: 0 },
|
||||||
{ id: ids.box3, type: 'geo', x: 400, y: 0 },
|
{ id: ids.box4, type: 'geo', x: 600, y: 200 },
|
||||||
{ id: ids.box4, type: 'geo', x: 600, y: 200 },
|
])
|
||||||
])
|
editor.groupShapes([ids.box3, ids.box4], ids.group1)
|
||||||
.groupShapes([ids.box3, ids.box4], ids.group1)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not select when brushing into margin', () => {
|
it('does not select when brushing into margin', () => {
|
||||||
|
|
|
@ -1954,9 +1954,9 @@ const defaultPitLocations = [
|
||||||
|
|
||||||
describe('Note shape grid helper positions / pits', () => {
|
describe('Note shape grid helper positions / pits', () => {
|
||||||
it('Snaps to pits', () => {
|
it('Snaps to pits', () => {
|
||||||
|
editor.createShape({ type: 'note' })
|
||||||
|
editor.createShape({ type: 'note', x: 500, y: 500 })
|
||||||
editor
|
editor
|
||||||
.createShape({ type: 'note' })
|
|
||||||
.createShape({ type: 'note', x: 500, y: 500 })
|
|
||||||
.pointerMove(600, 600)
|
.pointerMove(600, 600)
|
||||||
// start translating
|
// start translating
|
||||||
.pointerDown()
|
.pointerDown()
|
||||||
|
@ -1971,9 +1971,9 @@ describe('Note shape grid helper positions / pits', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Does not snap to pit if shape has a different rotation', () => {
|
it('Does not snap to pit if shape has a different rotation', () => {
|
||||||
|
editor.createShape({ type: 'note', rotation: 0.001 })
|
||||||
|
editor.createShape({ type: 'note', x: 500, y: 500 })
|
||||||
editor
|
editor
|
||||||
.createShape({ type: 'note', rotation: 0.001 })
|
|
||||||
.createShape({ type: 'note', x: 500, y: 500 })
|
|
||||||
.pointerMove(600, 600)
|
.pointerMove(600, 600)
|
||||||
// start translating
|
// start translating
|
||||||
.pointerDown()
|
.pointerDown()
|
||||||
|
@ -1989,9 +1989,9 @@ describe('Note shape grid helper positions / pits', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Snaps to pit if shape has the same rotation', () => {
|
it('Snaps to pit if shape has the same rotation', () => {
|
||||||
|
editor.createShape({ type: 'note', rotation: 0.001 })
|
||||||
|
editor.createShape({ type: 'note', x: 500, y: 500, rotation: 0.001 })
|
||||||
editor
|
editor
|
||||||
.createShape({ type: 'note', rotation: 0.001 })
|
|
||||||
.createShape({ type: 'note', x: 500, y: 500, rotation: 0.001 })
|
|
||||||
.pointerMove(600, 600)
|
.pointerMove(600, 600)
|
||||||
// start translating
|
// start translating
|
||||||
.pointerDown()
|
.pointerDown()
|
||||||
|
@ -2008,9 +2008,9 @@ describe('Note shape grid helper positions / pits', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Snaps correctly to the top when the translating shape has growY', () => {
|
it('Snaps correctly to the top when the translating shape has growY', () => {
|
||||||
|
editor.createShape({ type: 'note' })
|
||||||
|
editor.createShape({ type: 'note', x: 500, y: 500 })
|
||||||
editor
|
editor
|
||||||
.createShape({ type: 'note' })
|
|
||||||
.createShape({ type: 'note', x: 500, y: 500 })
|
|
||||||
.updateShape({ ...editor.getLastCreatedShape(), props: { growY: 100 } })
|
.updateShape({ ...editor.getLastCreatedShape(), props: { growY: 100 } })
|
||||||
.pointerMove(600, 600)
|
.pointerMove(600, 600)
|
||||||
// start translating
|
// start translating
|
||||||
|
@ -2028,10 +2028,10 @@ describe('Note shape grid helper positions / pits', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Snaps correctly to the bottom when the not-translating shape has growY', () => {
|
it('Snaps correctly to the bottom when the not-translating shape has growY', () => {
|
||||||
|
editor.createShape({ type: 'note' })
|
||||||
|
editor.updateShape({ ...editor.getLastCreatedShape(), props: { growY: 100 } })
|
||||||
|
editor.createShape({ type: 'note', x: 500, y: 500 })
|
||||||
editor
|
editor
|
||||||
.createShape({ type: 'note' })
|
|
||||||
.updateShape({ ...editor.getLastCreatedShape(), props: { growY: 100 } })
|
|
||||||
.createShape({ type: 'note', x: 500, y: 500 })
|
|
||||||
.pointerMove(600, 600)
|
.pointerMove(600, 600)
|
||||||
// start translating
|
// start translating
|
||||||
.pointerDown()
|
.pointerDown()
|
||||||
|
|
Ładowanie…
Reference in New Issue