Remove helpers / extraneous API methods. (#1745)
This PR removes several extraneous computed values from the editor. It
adds some silly instance state onto the instance state record and
unifies a few methods which were inconsistent. This is fit and finish
work 🧽
## Computed Values
In general, where once we had a getter and setter for `isBlahMode`,
which really masked either an `_isBlahMode` atom on the editor or
`instanceState.isBlahMode`, these are merged into `instanceState`; they
can be accessed / updated via `editor.instanceState` /
`editor.updateInstanceState`.
## tldraw select tool specific things
This PR also removes some tldraw specific state checks and creates new
component overrides to allow us to include them in tldraw/tldraw.
### Change Type
- [x] `major` — Breaking change
### Test Plan
- [x] Unit Tests
- [x] End to end tests
### Release Notes
- [tldraw] rename `useReadonly` to `useReadOnly`
- [editor] remove `Editor.isDarkMode`
- [editor] remove `Editor.isChangingStyle`
- [editor] remove `Editor.isCoarsePointer`
- [editor] remove `Editor.isDarkMode`
- [editor] remove `Editor.isFocused`
- [editor] remove `Editor.isGridMode`
- [editor] remove `Editor.isPenMode`
- [editor] remove `Editor.isReadOnly`
- [editor] remove `Editor.isSnapMode`
- [editor] remove `Editor.isToolLocked`
- [editor] remove `Editor.locale`
- [editor] rename `Editor.pageState` to `Editor.currentPageState`
- [editor] add `Editor.pageStates`
- [editor] add `Editor.setErasingIds`
- [editor] add `Editor.setEditingId`
- [editor] add several new component overrides
This commit is contained in:
parent
a7d3a77cb0
commit
3e31ef2a7d
154 changed files with 1728 additions and 1706 deletions
|
@ -36,7 +36,7 @@ export async function setupPage(page: PlaywrightTestArgs['page']) {
|
|||
await page.goto('http://localhost:5420/end-to-end')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
await page.evaluate(() => {
|
||||
editor.animationSpeed = 0
|
||||
editor.user.updateUserPreferences({ animationSpeed: 0 })
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -172,26 +172,6 @@ test.describe('Keyboard Shortcuts', () => {
|
|||
data: { source: 'kbd' },
|
||||
})
|
||||
|
||||
// distribute horizontal
|
||||
await page.keyboard.press('Control+a')
|
||||
await page.mouse.click(200, 200, { button: 'right' })
|
||||
await page.getByTestId('menu-item.arrange').click()
|
||||
await page.getByTestId('menu-item.distribute-horizontal').click()
|
||||
expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({
|
||||
name: 'distribute-shapes',
|
||||
data: { operation: 'horizontal', source: 'context-menu' },
|
||||
})
|
||||
|
||||
// distribute vertical — Shift+Alt+V
|
||||
await page.keyboard.press('Control+a')
|
||||
await page.mouse.click(200, 200, { button: 'right' })
|
||||
await page.getByTestId('menu-item.arrange').click()
|
||||
await page.getByTestId('menu-item.distribute-vertical').click()
|
||||
expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({
|
||||
name: 'distribute-shapes',
|
||||
data: { operation: 'vertical', source: 'context-menu' },
|
||||
})
|
||||
|
||||
// flip-h — Shift+H
|
||||
await page.keyboard.press('Shift+h')
|
||||
expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({
|
||||
|
@ -328,6 +308,37 @@ test.describe('Keyboard Shortcuts', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test.describe('Context menu', async () => {
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await setupPage(page)
|
||||
})
|
||||
|
||||
test('distribute horizontal', async () => {
|
||||
// distribute horizontal
|
||||
await page.keyboard.press('Control+a')
|
||||
await page.mouse.click(200, 200, { button: 'right' })
|
||||
await page.getByTestId('menu-item.arrange').click()
|
||||
await page.getByTestId('menu-item.distribute-horizontal').click()
|
||||
expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({
|
||||
name: 'distribute-shapes',
|
||||
data: { operation: 'horizontal', source: 'context-menu' },
|
||||
})
|
||||
})
|
||||
|
||||
test('distribute vertical', async () => {
|
||||
// distribute vertical — Shift+Alt+V
|
||||
await page.keyboard.press('Control+a')
|
||||
await page.mouse.click(200, 200, { button: 'right' })
|
||||
await page.getByTestId('menu-item.arrange').click()
|
||||
await page.getByTestId('menu-item.distribute-vertical').click()
|
||||
expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({
|
||||
name: 'distribute-shapes',
|
||||
data: { operation: 'vertical', source: 'context-menu' },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Delete bug', () => {
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
|
|
|
@ -56,7 +56,7 @@ export class CardShapeUtil extends BaseBoxShapeUtil<CardShape> {
|
|||
|
||||
component(shape: CardShape) {
|
||||
const bounds = this.editor.getBounds(shape)
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
|
||||
return (
|
||||
<HTMLContainer
|
||||
|
|
|
@ -43,7 +43,7 @@ export class CardShapeUtil extends ShapeUtil<ICardShape> {
|
|||
// Render method — the React component that will be rendered for the shape
|
||||
component(shape: ICardShape) {
|
||||
const bounds = this.editor.getBounds(shape)
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
|
||||
// Unfortunately eslint will think this is a class components
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
import { Tldraw } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/tldraw.css'
|
||||
;(window as any).__tldraw_ui_event = { id: 'NOTHING_YET' }
|
||||
;(window as any).__tldraw_editor_events = []
|
||||
|
||||
export default function EndToEnd() {
|
||||
;(window as any).__tldraw_editor_events = []
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
autoFocus
|
||||
onUiEvent={(name, data) => {
|
||||
;(window as any).__tldraw_ui_event = { name, data }
|
||||
}}
|
||||
onMount={(editor) => {
|
||||
editor.on('event', (info) => {
|
||||
;(window as any).__tldraw_editor_events.push(info)
|
||||
})
|
||||
}}
|
||||
onUiEvent={(name, data) => {
|
||||
;(window as any).__tldraw_ui_event = { name, data }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -32,7 +32,7 @@ class IdleState extends StateNode {
|
|||
if (editor.inputs.shiftKey) {
|
||||
editor.select(...editor.selectedIds, info.shape.id)
|
||||
} else {
|
||||
if (!editor.isSelected(info.shape.id)) {
|
||||
if (!editor.selectedIds.includes(info.shape.id)) {
|
||||
editor.select(info.shape.id)
|
||||
}
|
||||
this.parent.transition('pointing', info)
|
||||
|
|
|
@ -39,7 +39,6 @@ import { TLAssetPartial } from '@tldraw/tlschema';
|
|||
import { TLBaseShape } from '@tldraw/tlschema';
|
||||
import { TLBookmarkAsset } from '@tldraw/tlschema';
|
||||
import { TLCamera } from '@tldraw/tlschema';
|
||||
import { TLCursor } from '@tldraw/tlschema';
|
||||
import { TLCursorType } from '@tldraw/tlschema';
|
||||
import { TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema';
|
||||
import { TLDocument } from '@tldraw/tlschema';
|
||||
|
@ -354,8 +353,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}): this;
|
||||
animateToShape(shapeId: TLShapeId, opts?: TLAnimationOptions): this;
|
||||
animateToUser(userId: string): void;
|
||||
get animationSpeed(): number;
|
||||
set animationSpeed(animationSpeed: number);
|
||||
// @internal (undocumented)
|
||||
annotateError(error: unknown, { origin, willCrashApp, tags, extras, }: {
|
||||
origin: string;
|
||||
|
@ -368,17 +365,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
bailToMark(id: string): this;
|
||||
batch(fn: () => void): this;
|
||||
// (undocumented)
|
||||
blur: () => boolean;
|
||||
blur: () => void;
|
||||
bringForward(ids?: TLShapeId[]): this;
|
||||
bringToFront(ids?: TLShapeId[]): this;
|
||||
get brush(): Box2dModel | null;
|
||||
set brush(brush: Box2dModel | null);
|
||||
get camera(): TLCamera;
|
||||
get cameraState(): "idle" | "moving";
|
||||
cancel(): this;
|
||||
cancelDoubleClick(): void;
|
||||
get canMoveCamera(): boolean;
|
||||
set canMoveCamera(canMove: boolean);
|
||||
get canRedo(): boolean;
|
||||
get canUndo(): boolean;
|
||||
// @internal (undocumented)
|
||||
|
@ -412,17 +405,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
get currentPage(): TLPage;
|
||||
get currentPageId(): TLPageId;
|
||||
get currentPageShapeIds(): Set<TLShapeId>;
|
||||
get currentPageState(): TLInstancePageState;
|
||||
get currentTool(): StateNode | undefined;
|
||||
get currentToolId(): string;
|
||||
get cursor(): TLCursor;
|
||||
set cursor(cursor: TLCursor);
|
||||
deleteAssets(ids: TLAssetId[]): this;
|
||||
deleteOpenMenu(id: string): this;
|
||||
deletePage(id: TLPageId): void;
|
||||
deletePage(id: TLPageId): this;
|
||||
deleteShapes(ids?: TLShapeId[]): this;
|
||||
deselect(...ids: TLShapeId[]): this;
|
||||
get devicePixelRatio(): number;
|
||||
set devicePixelRatio(dpr: number);
|
||||
dispatch(info: TLEventInfo): this;
|
||||
readonly disposables: Set<() => void>;
|
||||
dispose(): void;
|
||||
|
@ -431,9 +421,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
duplicatePage(id?: TLPageId, createId?: TLPageId): this;
|
||||
duplicateShapes(ids?: TLShapeId[], offset?: VecLike): this;
|
||||
get editingId(): null | TLShapeId;
|
||||
set editingId(id: null | TLShapeId);
|
||||
get erasingIds(): TLShapeId[];
|
||||
set erasingIds(ids: TLShapeId[]);
|
||||
get erasingIdsSet(): Set<TLShapeId>;
|
||||
// @internal (undocumented)
|
||||
externalAssetContentHandlers: {
|
||||
|
@ -455,7 +443,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
findCommonAncestor(shapes: TLShape[], predicate?: (shape: TLShape) => boolean): TLShapeId | undefined;
|
||||
flipShapes(operation: 'horizontal' | 'vertical', ids?: TLShapeId[]): this;
|
||||
// (undocumented)
|
||||
focus: () => boolean;
|
||||
focus: () => void;
|
||||
get focusLayerId(): TLPageId | TLShapeId;
|
||||
set focusLayerId(next: TLPageId | TLShapeId);
|
||||
getAncestorPageId(shape?: TLShape): TLPageId | undefined;
|
||||
|
@ -468,7 +456,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
handleId: "end" | "start";
|
||||
}[];
|
||||
getAssetById(id: TLAssetId): TLAsset | undefined;
|
||||
getAssetBySrc(src: string): TLBookmarkAsset | TLImageAsset | TLVideoAsset | undefined;
|
||||
getAssetForExternalContent(info: TLExternalAssetContent_2): Promise<TLAsset | undefined>;
|
||||
getBounds<T extends TLShape>(shape: T): Box2d;
|
||||
getBoundsById<T extends TLShape>(id: T['id']): Box2d | undefined;
|
||||
|
@ -495,12 +482,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
getPageCenter(shape: TLShape): null | Vec2d;
|
||||
getPageCenterById(id: TLShapeId): null | Vec2d;
|
||||
getPageCorners(shape: TLShape): Vec2d[];
|
||||
getPageInfoById(id: TLPage['id']): TLPage | undefined;
|
||||
getPageMaskById(id: TLShapeId): undefined | VecLike[];
|
||||
getPagePointById(id: TLShapeId): undefined | Vec2d;
|
||||
getPageRotation(shape: TLShape): number;
|
||||
getPageRotationById(id: TLShapeId): number;
|
||||
getPageStateByPageId(id: TLPageId): TLInstancePageState | undefined;
|
||||
getPageTransform(shape: TLShape): Matrix2d | undefined;
|
||||
getPageTransformById(id: TLShapeId): Matrix2d | undefined;
|
||||
getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShape['type']): TLPageId | TLShapeId;
|
||||
|
@ -562,40 +547,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
interrupt(): this;
|
||||
isAncestorSelected(id: TLShapeId): boolean;
|
||||
readonly isAndroid: boolean;
|
||||
get isChangingStyle(): boolean;
|
||||
set isChangingStyle(v: boolean);
|
||||
readonly isChromeForIos: boolean;
|
||||
get isCoarsePointer(): boolean;
|
||||
set isCoarsePointer(v: boolean);
|
||||
get isDarkMode(): boolean;
|
||||
set isDarkMode(isDarkMode: boolean);
|
||||
readonly isFirefox: boolean;
|
||||
get isFocused(): boolean;
|
||||
set isFocused(isFocused: boolean);
|
||||
get isFocusMode(): boolean;
|
||||
set isFocusMode(isFocusMode: boolean);
|
||||
get isGridMode(): boolean;
|
||||
set isGridMode(isGridMode: boolean);
|
||||
isIn(path: string): boolean;
|
||||
isInAny(...paths: string[]): boolean;
|
||||
readonly isIos: boolean;
|
||||
get isMenuOpen(): boolean;
|
||||
get isPenMode(): boolean;
|
||||
set isPenMode(isPenMode: boolean);
|
||||
isPointInShape(point: VecLike, shape: TLShape): boolean;
|
||||
get isReadOnly(): boolean;
|
||||
set isReadOnly(isReadOnly: boolean);
|
||||
readonly isSafari: boolean;
|
||||
isSelected(id: TLShapeId): boolean;
|
||||
isShapeInPage(shape: TLShape, pageId?: TLPageId): boolean;
|
||||
isShapeOfType<T extends TLUnknownShape>(shape: TLUnknownShape, type: T['type']): shape is T;
|
||||
isShapeOrAncestorLocked(shape?: TLShape): boolean;
|
||||
get isSnapMode(): boolean;
|
||||
set isSnapMode(isSnapMode: boolean);
|
||||
get isToolLocked(): boolean;
|
||||
set isToolLocked(isToolLocked: boolean);
|
||||
get locale(): string;
|
||||
set locale(locale: string);
|
||||
mark(markId?: string, onUndo?: boolean, onRedo?: boolean): string;
|
||||
moveShapesToPage(ids: TLShapeId[], pageId: TLPageId): this;
|
||||
nudgeShapes(ids: TLShapeId[], direction: Vec2dModel, major?: boolean, ephemeral?: boolean): this;
|
||||
|
@ -603,7 +565,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
get openMenus(): string[];
|
||||
packShapes(ids?: TLShapeId[], padding?: number): this;
|
||||
get pages(): TLPage[];
|
||||
get pageState(): TLInstancePageState;
|
||||
get pageStates(): TLInstancePageState[];
|
||||
pageToScreen(x: number, y: number, z?: number, camera?: Vec2dModel): {
|
||||
x: number;
|
||||
y: number;
|
||||
|
@ -612,9 +574,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
pan(dx: number, dy: number, opts?: TLAnimationOptions): this;
|
||||
panZoomIntoView(ids: TLShapeId[], opts?: TLAnimationOptions): this;
|
||||
popFocusLayer(): this;
|
||||
// @internal (undocumented)
|
||||
get projectName(): string;
|
||||
set projectName(name: string);
|
||||
putContent(content: TLContent, options?: {
|
||||
point?: VecLike;
|
||||
select?: boolean;
|
||||
|
@ -659,12 +618,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
y: number;
|
||||
z: number;
|
||||
};
|
||||
get scribble(): null | TLScribble;
|
||||
set scribble(scribble: null | TLScribble);
|
||||
select(...ids: TLShapeId[]): this;
|
||||
selectAll(): this;
|
||||
get selectedIds(): TLShapeId[];
|
||||
get selectedIdsSet(): ReadonlySet<TLShapeId>;
|
||||
get selectedPageBounds(): Box2d | null;
|
||||
get selectedShapes(): TLShape[];
|
||||
get selectionBounds(): Box2d | undefined;
|
||||
|
@ -676,8 +632,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
setCamera(x: number, y: number, z?: number, { stopFollowing }?: TLViewportOptions): this;
|
||||
setCurrentPageId(pageId: TLPageId, { stopFollowing }?: TLViewportOptions): this;
|
||||
setCurrentTool(id: string, info?: {}): this;
|
||||
// (undocumented)
|
||||
setEditingId(id: null | TLShapeId): void;
|
||||
// (undocumented)
|
||||
setErasingIds(ids: TLShapeId[]): void;
|
||||
setOpacity(opacity: number, ephemeral?: boolean, squashing?: boolean): this;
|
||||
setPageState(partial: Partial<TLInstancePageState>, ephemeral?: boolean): void;
|
||||
setSelectedIds(ids: TLShapeId[], squashing?: boolean): this;
|
||||
setStyle<T>(style: StyleProp<T>, value: T, ephemeral?: boolean, squashing?: boolean): this;
|
||||
get shapesArray(): TLShape[];
|
||||
|
@ -709,6 +668,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
undo(): HistoryManager<this>;
|
||||
ungroupShapes(ids?: TLShapeId[]): this;
|
||||
updateAssets(assets: TLAssetPartial[]): this;
|
||||
updateCurrentPageState(partial: Partial<Omit<TLInstancePageState, 'editingId' | 'focusLayerId' | 'pageId' | 'selectedIds'>>, ephemeral?: boolean): this;
|
||||
updateDocumentSettings(settings: Partial<TLDocument>): this;
|
||||
updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId'>>, ephemeral?: boolean, squashing?: boolean): this;
|
||||
updatePage(partial: RequiredKeys<TLPage, 'id'>, squashing?: boolean): this;
|
||||
|
@ -722,8 +682,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
get viewportScreenBounds(): Box2d;
|
||||
get viewportScreenCenter(): Vec2d;
|
||||
visitDescendants(parentId: TLParentId, visitor: (id: TLShapeId) => false | void): void;
|
||||
get zoomBrush(): Box2dModel | null;
|
||||
set zoomBrush(zoomBrush: Box2dModel | null);
|
||||
zoomIn(point?: Vec2d, opts?: TLAnimationOptions): this;
|
||||
get zoomLevel(): number;
|
||||
zoomOut(point?: Vec2d, opts?: TLAnimationOptions): this;
|
||||
|
@ -1276,6 +1234,14 @@ export function setRuntimeOverrides(input: Partial<typeof runtime>): void;
|
|||
// @public (undocumented)
|
||||
export function setUserPreferences(user: TLUserPreferences): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ShapeIndicator: React_3.NamedExoticComponent<{
|
||||
id: TLShapeId;
|
||||
color?: string | undefined;
|
||||
opacity?: number | undefined;
|
||||
className?: string | undefined;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
||||
constructor(editor: Editor);
|
||||
|
@ -1567,6 +1533,9 @@ export type TLAnimationOptions = Partial<{
|
|||
// @public (undocumented)
|
||||
export type TLAnyShapeUtilConstructor = TLShapeUtilConstructor<any>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBackgroundComponent = ComponentType;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBaseBoxShape = TLBaseShape<string, {
|
||||
w: number;
|
||||
|
@ -1585,6 +1554,14 @@ export interface TLBaseEventInfo {
|
|||
type: UiEventType;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBrushComponent = ComponentType<{
|
||||
brush: Box2dModel;
|
||||
color?: string;
|
||||
opacity?: number;
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLCancelEvent = (info: TLCancelEventInfo) => void;
|
||||
|
||||
|
@ -1610,6 +1587,16 @@ export type TLClickEventInfo = TLBaseEventInfo & {
|
|||
// @public (undocumented)
|
||||
export type TLCLickEventName = 'double_click' | 'quadruple_click' | 'triple_click';
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLCollaboratorHintComponent = ComponentType<{
|
||||
className?: string;
|
||||
point: Vec2dModel;
|
||||
viewport: Box2d;
|
||||
zoom: number;
|
||||
opacity?: number;
|
||||
color: string;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLCommand<Name extends string = any, Data = any> = {
|
||||
type: 'command';
|
||||
|
@ -1648,6 +1635,16 @@ export interface TLContent {
|
|||
shapes: TLShape[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLCursorComponent = ComponentType<{
|
||||
className?: string;
|
||||
point: null | Vec2dModel;
|
||||
zoom: number;
|
||||
color?: string;
|
||||
name: null | string;
|
||||
chatMessage: string;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TldrawEditor: React_2.NamedExoticComponent<TldrawEditorProps>;
|
||||
|
||||
|
@ -1831,6 +1828,27 @@ export type TLExternalContent = {
|
|||
point?: VecLike;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLGridComponent = ComponentType<{
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
size: number;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLHandleComponent = ComponentType<{
|
||||
shapeId: TLShapeId;
|
||||
handle: TLHandle;
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLHandlesComponent = ComponentType<{
|
||||
className?: string;
|
||||
children: any;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLHistoryEntry = TLCommand | TLHistoryMark;
|
||||
|
||||
|
@ -1842,6 +1860,11 @@ export type TLHistoryMark = {
|
|||
onRedo: boolean;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLHoveredShapeIndicatorComponent = ComponentType<{
|
||||
shapeId: TLShapeId;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLInterruptEvent = (info: TLInterruptEventInfo) => void;
|
||||
|
||||
|
@ -2003,6 +2026,21 @@ export type TLRotationSnapshot = {
|
|||
}[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLScribbleComponent = ComponentType<{
|
||||
scribble: TLScribble;
|
||||
zoom: number;
|
||||
color?: string;
|
||||
opacity?: number;
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLSelectionBackgroundComponent = React_3.ComponentType<object>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLSelectionForegroundComponent = ComponentType<object>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLSelectionHandle = RotateCorner | SelectionCorner | SelectionEdge;
|
||||
|
||||
|
@ -2035,6 +2073,14 @@ export interface TLSessionStateSnapshot {
|
|||
version: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLShapeIndicatorComponent = React_3.ComponentType<{
|
||||
id: TLShapeId;
|
||||
color?: string | undefined;
|
||||
opacity?: number;
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLShapeUtilCanvasSvgDef {
|
||||
// (undocumented)
|
||||
|
@ -2058,6 +2104,16 @@ export interface TLShapeUtilConstructor<T extends TLUnknownShape, U extends Shap
|
|||
// @public (undocumented)
|
||||
export type TLShapeUtilFlag<T> = (shape: T) => boolean;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLSnapLineComponent = React_3.ComponentType<{
|
||||
className?: string;
|
||||
line: SnapLine;
|
||||
zoom: number;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLSpinnerComponent = ComponentType<object>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLStateNodeConstructor {
|
||||
// (undocumented)
|
||||
|
@ -2107,6 +2163,9 @@ export type TLStoreWithStatus = {
|
|||
readonly error?: undefined;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLSvgDefsComponent = React.ComponentType;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLTickEvent = (elapsed: number) => void;
|
||||
|
||||
|
|
|
@ -42,7 +42,22 @@ export {
|
|||
export { HTMLContainer, type HTMLContainerProps } from './lib/components/HTMLContainer'
|
||||
export { PositionedOnCanvas } from './lib/components/PositionedOnCanvas'
|
||||
export { SVGContainer, type SVGContainerProps } from './lib/components/SVGContainer'
|
||||
export { ShapeIndicator, type TLShapeIndicatorComponent } from './lib/components/ShapeIndicator'
|
||||
export { type TLBackgroundComponent } from './lib/components/default-components/DefaultBackground'
|
||||
export { type TLBrushComponent } from './lib/components/default-components/DefaultBrush'
|
||||
export { type TLCollaboratorHintComponent } from './lib/components/default-components/DefaultCollaboratorHint'
|
||||
export { type TLCursorComponent } from './lib/components/default-components/DefaultCursor'
|
||||
export { DefaultErrorFallback } from './lib/components/default-components/DefaultErrorFallback'
|
||||
export { type TLGridComponent } from './lib/components/default-components/DefaultGrid'
|
||||
export { type TLHandleComponent } from './lib/components/default-components/DefaultHandle'
|
||||
export { type TLHandlesComponent } from './lib/components/default-components/DefaultHandles'
|
||||
export { type TLHoveredShapeIndicatorComponent } from './lib/components/default-components/DefaultHoveredShapeIndicator'
|
||||
export { type TLScribbleComponent } from './lib/components/default-components/DefaultScribble'
|
||||
export { type TLSelectionBackgroundComponent } from './lib/components/default-components/DefaultSelectionBackground'
|
||||
export { type TLSelectionForegroundComponent } from './lib/components/default-components/DefaultSelectionForeground'
|
||||
export { type TLSnapLineComponent } from './lib/components/default-components/DefaultSnapLine'
|
||||
export { type TLSpinnerComponent } from './lib/components/default-components/DefaultSpinner'
|
||||
export { type TLSvgDefsComponent } from './lib/components/default-components/DefaultSvgDefs'
|
||||
export {
|
||||
TAB_ID,
|
||||
createSessionStateSnapshotSignal,
|
||||
|
|
|
@ -261,7 +261,9 @@ function TldrawEditorWithReadyStore({
|
|||
}, [container, shapeUtils, tools, store, user, initialState])
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (editor && autoFocus) editor.isFocused = true
|
||||
if (editor && autoFocus) {
|
||||
editor.focus()
|
||||
}
|
||||
}, [editor, autoFocus])
|
||||
|
||||
const onMountEvent = useEvent((editor: Editor) => {
|
||||
|
|
|
@ -117,59 +117,53 @@ export const Canvas = track(function Canvas() {
|
|||
)
|
||||
})
|
||||
|
||||
const GridWrapper = track(function GridWrapper() {
|
||||
function GridWrapper() {
|
||||
const editor = useEditor()
|
||||
const gridSize = useValue('gridSize', () => editor.documentSettings.gridSize, [editor])
|
||||
const { x, y, z } = useValue('camera', () => editor.camera, [editor])
|
||||
const isGridMode = useValue('isGridMode', () => editor.instanceState.isGridMode, [editor])
|
||||
const { Grid } = useEditorComponents()
|
||||
|
||||
// get grid from context
|
||||
|
||||
const { gridSize } = editor.documentSettings
|
||||
const { x, y, z } = editor.camera
|
||||
const isGridMode = editor.isGridMode
|
||||
|
||||
if (!(Grid && isGridMode)) return null
|
||||
|
||||
return <Grid x={x} y={y} z={z} size={gridSize} />
|
||||
})
|
||||
}
|
||||
|
||||
const ScribbleWrapper = track(function ScribbleWrapper() {
|
||||
function ScribbleWrapper() {
|
||||
const editor = useEditor()
|
||||
const scribble = editor.scribble
|
||||
const zoom = editor.zoomLevel
|
||||
|
||||
const scribble = useValue('scribble', () => editor.instanceState.scribble, [editor])
|
||||
const zoomLevel = useValue('zoomLevel', () => editor.zoomLevel, [editor])
|
||||
const { Scribble } = useEditorComponents()
|
||||
|
||||
if (!(Scribble && scribble)) return null
|
||||
|
||||
return <Scribble className="tl-user-scribble" scribble={scribble} zoom={zoom} />
|
||||
})
|
||||
return <Scribble className="tl-user-scribble" scribble={scribble} zoom={zoomLevel} />
|
||||
}
|
||||
|
||||
const BrushWrapper = track(function BrushWrapper() {
|
||||
function BrushWrapper() {
|
||||
const editor = useEditor()
|
||||
const { brush } = editor
|
||||
const brush = useValue('brush', () => editor.instanceState.brush, [editor])
|
||||
const { Brush } = useEditorComponents()
|
||||
|
||||
if (!(Brush && brush)) return null
|
||||
|
||||
return <Brush className="tl-user-brush" brush={brush} />
|
||||
})
|
||||
}
|
||||
|
||||
export const ZoomBrushWrapper = track(function Zoom() {
|
||||
function ZoomBrushWrapper() {
|
||||
const editor = useEditor()
|
||||
const { zoomBrush } = editor
|
||||
const zoomBrush = useValue('zoomBrush', () => editor.instanceState.zoomBrush, [editor])
|
||||
const { ZoomBrush } = useEditorComponents()
|
||||
|
||||
if (!(ZoomBrush && zoomBrush)) return null
|
||||
|
||||
return <ZoomBrush className="tl-user-brush" brush={zoomBrush} />
|
||||
})
|
||||
}
|
||||
|
||||
export const SnapLinesWrapper = track(function SnapLines() {
|
||||
function SnapLinesWrapper() {
|
||||
const editor = useEditor()
|
||||
const {
|
||||
snaps: { lines },
|
||||
zoomLevel,
|
||||
} = editor
|
||||
const lines = useValue('snapLines', () => editor.snaps.lines, [editor])
|
||||
const zoomLevel = useValue('zoomLevel', () => editor.zoomLevel, [editor])
|
||||
const { SnapLine } = useEditorComponents()
|
||||
|
||||
if (!(SnapLine && lines.length > 0)) return null
|
||||
|
@ -181,24 +175,22 @@ export const SnapLinesWrapper = track(function SnapLines() {
|
|||
))}
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const MIN_HANDLE_DISTANCE = 48
|
||||
|
||||
const HandlesWrapper = track(function HandlesWrapper() {
|
||||
function HandlesWrapper() {
|
||||
const editor = useEditor()
|
||||
const { Handles } = useEditorComponents()
|
||||
|
||||
const zoom = editor.zoomLevel
|
||||
const isChangingStyle = editor.isChangingStyle
|
||||
const zoomLevel = useValue('zoomLevel', () => editor.zoomLevel, [editor])
|
||||
const onlySelectedShape = useValue('onlySelectedShape', () => editor.onlySelectedShape, [editor])
|
||||
const isChangingStyle = useValue('isChangingStyle', () => editor.instanceState.isChangingStyle, [
|
||||
editor,
|
||||
])
|
||||
const isReadOnly = useValue('isChangingStyle', () => editor.instanceState.isReadOnly, [editor])
|
||||
|
||||
const onlySelectedShape = editor.onlySelectedShape
|
||||
|
||||
const shouldDisplayHandles =
|
||||
editor.isInAny('select.idle', 'select.pointing_handle') &&
|
||||
!isChangingStyle &&
|
||||
!editor.isReadOnly
|
||||
|
||||
if (!(onlySelectedShape && shouldDisplayHandles)) return null
|
||||
if (!Handles || !onlySelectedShape || isChangingStyle || isReadOnly) return null
|
||||
|
||||
const handles = editor.getHandles(onlySelectedShape)
|
||||
|
||||
|
@ -216,7 +208,7 @@ const HandlesWrapper = track(function HandlesWrapper() {
|
|||
const prev = handles[i - 1]
|
||||
const next = handles[i + 1]
|
||||
if (prev && next) {
|
||||
if (Math.hypot(prev.y - next.y, prev.x - next.x) < MIN_HANDLE_DISTANCE / zoom) {
|
||||
if (Math.hypot(prev.y - next.y, prev.x - next.x) < MIN_HANDLE_DISTANCE / zoomLevel) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -228,17 +220,17 @@ const HandlesWrapper = track(function HandlesWrapper() {
|
|||
handlesToDisplay.sort((a) => (a.type === 'vertex' ? 1 : -1))
|
||||
|
||||
return (
|
||||
<svg className="tl-user-handles tl-overlays__item">
|
||||
<Handles>
|
||||
<g transform={Matrix2d.toCssString(transform)}>
|
||||
{handlesToDisplay.map((handle) => {
|
||||
return <HandleWrapper key={handle.id} shapeId={onlySelectedShape.id} handle={handle} />
|
||||
})}
|
||||
</g>
|
||||
</svg>
|
||||
</Handles>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const HandleWrapper = ({ shapeId, handle }: { shapeId: TLShapeId; handle: TLHandle }) => {
|
||||
function HandleWrapper({ shapeId, handle }: { shapeId: TLShapeId; handle: TLHandle }) {
|
||||
const events = useHandleEvents(shapeId, handle.id)
|
||||
const { Handle } = useEditorComponents()
|
||||
|
||||
|
@ -279,43 +271,45 @@ const ShapesToDisplay = track(function ShapesToDisplay() {
|
|||
)
|
||||
})
|
||||
|
||||
const SelectedIdIndicators = track(function SelectedIdIndicators() {
|
||||
function SelectedIdIndicators() {
|
||||
const editor = useEditor()
|
||||
|
||||
const shouldDisplay =
|
||||
editor.isInAny(
|
||||
'select.idle',
|
||||
'select.brushing',
|
||||
'select.scribble_brushing',
|
||||
'select.pointing_shape',
|
||||
'select.pointing_selection',
|
||||
'select.pointing_handle'
|
||||
) && !editor.isChangingStyle
|
||||
const selectedIds = useValue('selectedIds', () => editor.currentPageState.selectedIds, [editor])
|
||||
const shouldDisplay = useValue(
|
||||
'should display selected ids',
|
||||
() => {
|
||||
// todo: move to tldraw selected ids wrapper
|
||||
return (
|
||||
editor.isInAny(
|
||||
'select.idle',
|
||||
'select.brushing',
|
||||
'select.scribble_brushing',
|
||||
'select.pointing_shape',
|
||||
'select.pointing_selection',
|
||||
'select.pointing_handle'
|
||||
) && !editor.instanceState.isChangingStyle
|
||||
)
|
||||
},
|
||||
[editor]
|
||||
)
|
||||
|
||||
if (!shouldDisplay) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
{editor.selectedIds.map((id) => (
|
||||
{selectedIds.map((id) => (
|
||||
<ShapeIndicator key={id + '_indicator'} className="tl-user-indicator__selected" id={id} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const HoveredShapeIndicator = function HoveredShapeIndicator() {
|
||||
const editor = useEditor()
|
||||
const { HoveredShapeIndicator } = useEditorComponents()
|
||||
const hoveredId = useValue('hovered id', () => editor.currentPageState.hoveredId, [editor])
|
||||
if (!hoveredId || !HoveredShapeIndicator) return null
|
||||
|
||||
const displayingHoveredId = useValue(
|
||||
'hovered id and should display',
|
||||
() =>
|
||||
editor.isInAny('select.idle', 'select.editing_shape') ? editor.pageState.hoveredId : null,
|
||||
[editor]
|
||||
)
|
||||
|
||||
if (!displayingHoveredId) return null
|
||||
|
||||
return <ShapeIndicator className="tl-user-indicator__hovered" id={displayingHoveredId} />
|
||||
return <HoveredShapeIndicator shapeId={hoveredId} />
|
||||
}
|
||||
|
||||
const HintedShapeIndicator = track(function HintedShapeIndicator() {
|
||||
|
|
|
@ -45,12 +45,13 @@ export const InnerIndicator = ({ editor, id }: { editor: Editor; id: TLShapeId }
|
|||
)
|
||||
}
|
||||
|
||||
export type TLShapeIndicatorComponent = (props: {
|
||||
/** @public */
|
||||
export type TLShapeIndicatorComponent = React.ComponentType<{
|
||||
id: TLShapeId
|
||||
color?: string | undefined
|
||||
opacity?: number
|
||||
className?: string
|
||||
}) => JSX.Element | null
|
||||
}>
|
||||
|
||||
const _ShapeIndicator: TLShapeIndicatorComponent = ({ id, className, color, opacity }) => {
|
||||
const editor = useEditor()
|
||||
|
@ -79,4 +80,5 @@ const _ShapeIndicator: TLShapeIndicatorComponent = ({ id, className, color, opac
|
|||
)
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const ShapeIndicator = React.memo(_ShapeIndicator)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ComponentType } from 'react'
|
||||
|
||||
/** @public */
|
||||
export type TLBackgroundComponent = ComponentType<object> | null
|
||||
export type TLBackgroundComponent = ComponentType
|
||||
|
||||
export function DefaultBackground() {
|
||||
return <div className="tl-background" />
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Box2d } from '../../primitives/Box2d'
|
|||
import { Vec2d } from '../../primitives/Vec2d'
|
||||
import { clamp } from '../../primitives/utils'
|
||||
|
||||
/** @public */
|
||||
export type TLCollaboratorHintComponent = ComponentType<{
|
||||
className?: string
|
||||
point: Vec2dModel
|
||||
|
|
|
@ -31,7 +31,7 @@ export const DefaultErrorFallback: TLErrorFallbackComponent = ({ error, editor }
|
|||
() => {
|
||||
try {
|
||||
if (editor) {
|
||||
return editor.isDarkMode
|
||||
return editor.user.isDarkMode
|
||||
}
|
||||
} catch {
|
||||
// we're in a funky error state so this might not work for spooky
|
||||
|
|
|
@ -2,6 +2,7 @@ import { TLHandle, TLShapeId } from '@tldraw/tlschema'
|
|||
import classNames from 'classnames'
|
||||
import { ComponentType } from 'react'
|
||||
|
||||
/** @public */
|
||||
export type TLHandleComponent = ComponentType<{
|
||||
shapeId: TLShapeId
|
||||
handle: TLHandle
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import { ComponentType } from 'react'
|
||||
|
||||
/** @public */
|
||||
export type TLHandlesComponent = ComponentType<{
|
||||
className?: string
|
||||
children: any
|
||||
}>
|
||||
|
||||
export const DefaultHandles: TLHandlesComponent = ({ children }) => {
|
||||
return <svg className="tl-user-handles tl-overlays__item">{children}</svg>
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { TLShapeId } from '@tldraw/tlschema'
|
||||
import { ComponentType } from 'react'
|
||||
import { ShapeIndicator } from '../ShapeIndicator'
|
||||
|
||||
/** @public */
|
||||
export type TLHoveredShapeIndicatorComponent = ComponentType<{
|
||||
shapeId: TLShapeId
|
||||
}>
|
||||
|
||||
export const DefaultHoveredShapeIndicator: TLHoveredShapeIndicatorComponent = ({ shapeId }) => {
|
||||
return <ShapeIndicator className="tl-user-indicator__hovered" id={shapeId} />
|
||||
}
|
|
@ -153,6 +153,7 @@ function GapsSnapLine({ gaps, direction, zoom }: { zoom: number } & GapsSnapLine
|
|||
)
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type TLSnapLineComponent = React.ComponentType<{
|
||||
className?: string
|
||||
line: SnapLine
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ComponentType } from 'react'
|
||||
|
||||
/** @public */
|
||||
export type TLSpinnerComponent = ComponentType<object>
|
||||
|
||||
export const DefaultSpinner: TLSpinnerComponent = () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/** @public */
|
||||
export type TLSvgDefsComponent = () => any
|
||||
export type TLSvgDefsComponent = React.ComponentType
|
||||
|
||||
export const DefaultSvgDefs = () => {
|
||||
return null
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -227,7 +227,7 @@ export class ClickManager {
|
|||
this._clickState !== 'idle' &&
|
||||
this._clickScreenPoint &&
|
||||
this._clickScreenPoint.dist(this.editor.inputs.currentScreenPoint) >
|
||||
(this.editor.isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE)
|
||||
(this.editor.instanceState.isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE)
|
||||
) {
|
||||
this.cancelDoubleClickTimeout()
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { computed } from '@tldraw/state'
|
||||
import { TLUserPreferences } from '../../config/TLUserPreferences'
|
||||
import { TLUser } from '../../config/createTLUser'
|
||||
|
||||
|
@ -6,40 +7,40 @@ export class UserPreferencesManager {
|
|||
|
||||
updateUserPreferences = (userPreferences: Partial<TLUserPreferences>) => {
|
||||
this.user.setUserPreferences({
|
||||
...this.user.userPreferences.value,
|
||||
...this.userPreferences,
|
||||
...userPreferences,
|
||||
})
|
||||
}
|
||||
|
||||
get userPreferences() {
|
||||
return this.user.userPreferences
|
||||
@computed get userPreferences() {
|
||||
return this.user.userPreferences.value
|
||||
}
|
||||
|
||||
get isDarkMode() {
|
||||
return this.user.userPreferences.value.isDarkMode
|
||||
@computed get isDarkMode() {
|
||||
return this.userPreferences.isDarkMode
|
||||
}
|
||||
|
||||
get animationSpeed() {
|
||||
return this.user.userPreferences.value.animationSpeed
|
||||
@computed get animationSpeed() {
|
||||
return this.userPreferences.animationSpeed
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.user.userPreferences.value.id
|
||||
@computed get id() {
|
||||
return this.userPreferences.id
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.user.userPreferences.value.name
|
||||
@computed get name() {
|
||||
return this.userPreferences.name
|
||||
}
|
||||
|
||||
get locale() {
|
||||
return this.user.userPreferences.value.locale
|
||||
@computed get locale() {
|
||||
return this.userPreferences.locale
|
||||
}
|
||||
|
||||
get color() {
|
||||
return this.user.userPreferences.value.color
|
||||
@computed get color() {
|
||||
return this.userPreferences.color
|
||||
}
|
||||
|
||||
get isSnapMode() {
|
||||
return this.user.userPreferences.value.isSnapMode
|
||||
@computed get isSnapMode() {
|
||||
return this.userPreferences.isSnapMode
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { atom } from '@tldraw/state'
|
||||
import { getAtomManager } from './getRecordManager'
|
||||
|
||||
describe('atom manager', () => {
|
||||
it('manages an atom object', () => {
|
||||
const cb = jest.fn()
|
||||
const A = atom('abc', { a: 1, b: 2, c: 3 })
|
||||
const manager = getAtomManager(A, cb)
|
||||
|
||||
expect(A.lastChangedEpoch).toBe(0)
|
||||
|
||||
manager.a = 2
|
||||
expect(manager.a).toBe(2)
|
||||
expect(A.lastChangedEpoch).toBe(1)
|
||||
|
||||
manager.b = 4
|
||||
expect(manager.b).toBe(4)
|
||||
expect(A.lastChangedEpoch).toBe(2)
|
||||
|
||||
manager.b
|
||||
expect(A.value).toMatchObject({ a: 2, b: 4, c: 3 })
|
||||
|
||||
expect(cb).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
28
packages/editor/src/lib/editor/managers/getRecordManager.ts
Normal file
28
packages/editor/src/lib/editor/managers/getRecordManager.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Atom, computed } from '@tldraw/state'
|
||||
|
||||
export function getAtomManager<T extends { [key: string]: any }>(
|
||||
atom: Atom<T>,
|
||||
transform?: (prev: T, next: T) => T
|
||||
): T {
|
||||
const update = (value: Partial<T>) => {
|
||||
const curr = atom.value
|
||||
const next = { ...curr, ...value }
|
||||
const final = transform?.(atom.value, atom.value) ?? next
|
||||
atom.set(final)
|
||||
}
|
||||
|
||||
return Object.defineProperties(
|
||||
{} as T,
|
||||
Object.keys(atom.value).reduce((acc, key) => {
|
||||
acc[key as keyof T] = computed(atom, key, {
|
||||
get() {
|
||||
return atom.value[key as keyof T]
|
||||
},
|
||||
set(value: T[keyof T]) {
|
||||
update({ [key]: value } as any)
|
||||
},
|
||||
})
|
||||
return acc
|
||||
}, {} as { [key in keyof T]: PropertyDescriptor })
|
||||
)
|
||||
}
|
|
@ -40,7 +40,7 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
|
|||
// Not a class component, but eslint can't tell that :(
|
||||
const {
|
||||
erasingIdsSet,
|
||||
pageState: { hintingIds, focusLayerId },
|
||||
currentPageState: { hintingIds, focusLayerId },
|
||||
zoomLevel,
|
||||
} = this.editor
|
||||
|
||||
|
@ -87,13 +87,13 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
|
|||
override onChildrenChange: TLOnChildrenChangeHandler<TLGroupShape> = (group) => {
|
||||
const children = this.editor.getSortedChildIds(group.id)
|
||||
if (children.length === 0) {
|
||||
if (this.editor.pageState.focusLayerId === group.id) {
|
||||
if (this.editor.currentPageState.focusLayerId === group.id) {
|
||||
this.editor.popFocusLayer()
|
||||
}
|
||||
this.editor.deleteShapes([group.id])
|
||||
return
|
||||
} else if (children.length === 1) {
|
||||
if (this.editor.pageState.focusLayerId === group.id) {
|
||||
if (this.editor.currentPageState.focusLayerId === group.id) {
|
||||
this.editor.popFocusLayer()
|
||||
}
|
||||
this.editor.reparentShapesById(children, group.parentId)
|
||||
|
|
|
@ -9,7 +9,7 @@ export class Idle extends StateNode {
|
|||
}
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.cursor = { type: 'cross', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onCancel = () => {
|
||||
|
|
21
packages/editor/src/lib/editor/types/editor-state.ts
Normal file
21
packages/editor/src/lib/editor/types/editor-state.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { T } from '@tldraw/validate'
|
||||
|
||||
export type TLinstanceState = {
|
||||
canMoveCamera: boolean
|
||||
isFocused: boolean
|
||||
devicePixelRatio: number
|
||||
isCoarsePointer: boolean
|
||||
openMenus: string[]
|
||||
isChangingStyle: boolean
|
||||
isReadOnly: boolean
|
||||
}
|
||||
|
||||
export const instanceStateValidator = T.object<TLinstanceState>({
|
||||
canMoveCamera: T.boolean,
|
||||
isFocused: T.boolean,
|
||||
devicePixelRatio: T.number,
|
||||
isCoarsePointer: T.boolean,
|
||||
openMenus: T.arrayOf(T.string),
|
||||
isChangingStyle: T.boolean,
|
||||
isReadOnly: T.boolean,
|
||||
})
|
|
@ -8,13 +8,13 @@ export function useCoarsePointer() {
|
|||
// detect coarse VS fine pointer. For now, let's assume that you have a fine
|
||||
// pointer if you're on Firefox on desktop.
|
||||
if (editor.isFirefox && !editor.isAndroid && !editor.isIos) {
|
||||
editor.isCoarsePointer = false
|
||||
editor.updateInstanceState({ isCoarsePointer: false })
|
||||
return
|
||||
}
|
||||
if (window.matchMedia) {
|
||||
const mql = window.matchMedia('(pointer: coarse)')
|
||||
const handler = () => {
|
||||
editor.isCoarsePointer = mql.matches
|
||||
editor.updateInstanceState({ isCoarsePointer: !!mql.matches })
|
||||
}
|
||||
handler()
|
||||
if (mql) {
|
||||
|
|
|
@ -77,7 +77,7 @@ export function useCursor() {
|
|||
useQuickReactor(
|
||||
'useCursor',
|
||||
() => {
|
||||
const { type, rotation } = editor.cursor
|
||||
const { type, rotation } = editor.instanceState.cursor
|
||||
|
||||
if (STATIC_CURSORS.includes(type)) {
|
||||
container.style.setProperty('--tl-cursor', `var(--tl-cursor-${type})`)
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useEditor } from './useEditor'
|
|||
export function useDarkMode() {
|
||||
const editor = useEditor()
|
||||
const container = useContainer()
|
||||
const isDarkMode = useValue('isDarkMode', () => editor.isDarkMode, [editor])
|
||||
const isDarkMode = useValue('isDarkMode', () => editor.user.isDarkMode, [editor])
|
||||
const forceSrgb = useValue(debugFlags.forceSrgb)
|
||||
|
||||
React.useEffect(() => {
|
||||
|
|
|
@ -9,13 +9,13 @@ export function useDocumentEvents() {
|
|||
const editor = useEditor()
|
||||
const container = useContainer()
|
||||
|
||||
const isAppFocused = useValue('isFocused', () => editor.isFocused, [editor])
|
||||
const isAppFocused = useValue('isFocused', () => editor.instanceState.isFocused, [editor])
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof matchMedia !== undefined) return
|
||||
|
||||
function updateDevicePixelRatio() {
|
||||
editor.devicePixelRatio = window.devicePixelRatio
|
||||
editor.updateInstanceState({ devicePixelRatio: window.devicePixelRatio })
|
||||
}
|
||||
|
||||
const MM = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`)
|
||||
|
@ -78,7 +78,7 @@ export function useDocumentEvents() {
|
|||
if (!editor.inputs.keys.has('Comma')) {
|
||||
const { x, y, z } = editor.inputs.currentScreenPoint
|
||||
const {
|
||||
pageState: { hoveredId },
|
||||
currentPageState: { hoveredId },
|
||||
} = editor
|
||||
editor.inputs.keys.add('Comma')
|
||||
|
||||
|
@ -91,7 +91,7 @@ export function useDocumentEvents() {
|
|||
ctrlKey: e.metaKey || e.ctrlKey,
|
||||
pointerId: 0,
|
||||
button: 0,
|
||||
isPen: editor.isPenMode,
|
||||
isPen: editor.instanceState.isPenMode,
|
||||
...(hoveredId
|
||||
? {
|
||||
target: 'shape',
|
||||
|
@ -156,7 +156,7 @@ export function useDocumentEvents() {
|
|||
if (editor.inputs.keys.has(e.code)) {
|
||||
const { x, y, z } = editor.inputs.currentScreenPoint
|
||||
const {
|
||||
pageState: { hoveredId },
|
||||
currentPageState: { hoveredId },
|
||||
} = editor
|
||||
|
||||
editor.inputs.keys.delete(e.code)
|
||||
|
@ -170,7 +170,7 @@ export function useDocumentEvents() {
|
|||
ctrlKey: e.metaKey || e.ctrlKey,
|
||||
pointerId: 0,
|
||||
button: 0,
|
||||
isPen: editor.isPenMode,
|
||||
isPen: editor.instanceState.isPenMode,
|
||||
...(hoveredId
|
||||
? {
|
||||
target: 'shape',
|
||||
|
|
|
@ -16,6 +16,11 @@ import {
|
|||
} from '../components/default-components/DefaultErrorFallback'
|
||||
import { DefaultGrid, TLGridComponent } from '../components/default-components/DefaultGrid'
|
||||
import { DefaultHandle, TLHandleComponent } from '../components/default-components/DefaultHandle'
|
||||
import { DefaultHandles, TLHandlesComponent } from '../components/default-components/DefaultHandles'
|
||||
import {
|
||||
DefaultHoveredShapeIndicator,
|
||||
TLHoveredShapeIndicatorComponent,
|
||||
} from '../components/default-components/DefaultHoveredShapeIndicator'
|
||||
import {
|
||||
DefaultScribble,
|
||||
TLScribbleComponent,
|
||||
|
@ -57,10 +62,12 @@ interface BaseEditorComponents {
|
|||
Scribble: TLScribbleComponent
|
||||
CollaboratorScribble: TLScribbleComponent
|
||||
SnapLine: TLSnapLineComponent
|
||||
Handles: TLHandlesComponent
|
||||
Handle: TLHandleComponent
|
||||
Spinner: TLSpinnerComponent
|
||||
SelectionForeground: TLSelectionForegroundComponent
|
||||
SelectionBackground: TLSelectionBackgroundComponent
|
||||
HoveredShapeIndicator: TLHoveredShapeIndicatorComponent
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -96,6 +103,7 @@ export function EditorComponentsProvider({ overrides, children }: ComponentsCont
|
|||
Grid: DefaultGrid,
|
||||
Scribble: DefaultScribble,
|
||||
SnapLine: DefaultSnapLine,
|
||||
Handles: DefaultHandles,
|
||||
Handle: DefaultHandle,
|
||||
CollaboratorScribble: DefaultScribble,
|
||||
ErrorFallback: DefaultErrorFallback,
|
||||
|
@ -104,6 +112,7 @@ export function EditorComponentsProvider({ overrides, children }: ComponentsCont
|
|||
Spinner: DefaultSpinner,
|
||||
SelectionBackground: DefaultSelectionBackground,
|
||||
SelectionForeground: DefaultSelectionForeground,
|
||||
HoveredShapeIndicator: DefaultHoveredShapeIndicator,
|
||||
...overrides,
|
||||
}),
|
||||
[overrides]
|
||||
|
|
|
@ -48,7 +48,7 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
|
|||
let pinchState = null as null | 'zooming' | 'panning'
|
||||
|
||||
const onWheel: Handler<'wheel', WheelEvent> = ({ event }) => {
|
||||
if (!editor.isFocused) {
|
||||
if (!editor.instanceState.isFocused) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { useEditor } from './useEditor'
|
|||
|
||||
const pointerEventHandler = (editor: Editor, shapeId: TLShapeId, name: TLPointerEventName) => {
|
||||
return (e: React.PointerEvent) => {
|
||||
if (name !== 'pointer_move' && editor.pageState.editingId === shapeId)
|
||||
if (name !== 'pointer_move' && editor.currentPageState.editingId === shapeId)
|
||||
(e as any).isKilled = true
|
||||
if ((e as any).isKilled) return
|
||||
|
||||
|
|
24
packages/editor/src/lib/test/user.test.ts
Normal file
24
packages/editor/src/lib/test/user.test.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { createTLStore } from '../config/createTLStore'
|
||||
import { Editor } from '../editor/Editor'
|
||||
|
||||
let editor: Editor
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new Editor({
|
||||
shapeUtils: [],
|
||||
tools: [],
|
||||
store: createTLStore({ shapeUtils: [] }),
|
||||
getContainer: () => document.body,
|
||||
})
|
||||
})
|
||||
|
||||
describe('user', () => {
|
||||
it('gets a user with the correct color', () => {
|
||||
expect(editor.user.isDarkMode).toBe(false)
|
||||
})
|
||||
|
||||
it('gets a user with the correct', () => {
|
||||
editor.user.updateUserPreferences({ isDarkMode: true })
|
||||
expect(editor.user.isDarkMode).toBe(true)
|
||||
})
|
||||
})
|
|
@ -808,7 +808,7 @@ export function useMenuSchema(): TLUiMenuSchema;
|
|||
export function useNativeClipboardEvents(): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function useReadonly(): boolean;
|
||||
export function useReadOnly(): boolean;
|
||||
|
||||
// @public (undocumented)
|
||||
export function useToasts(): TLUiToastsContextType;
|
||||
|
|
|
@ -83,7 +83,7 @@ export {
|
|||
type TLUiMenuSchemaContextType,
|
||||
type TLUiMenuSchemaProviderProps,
|
||||
} from './lib/ui/hooks/useMenuSchema'
|
||||
export { useReadonly } from './lib/ui/hooks/useReadonly'
|
||||
export { useReadOnly } from './lib/ui/hooks/useReadOnly'
|
||||
export {
|
||||
useToasts,
|
||||
type TLUiToast,
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
TldrawEditorProps,
|
||||
} from '@tldraw/editor'
|
||||
import { useMemo } from 'react'
|
||||
import { TldrawHandles } from './canvas/TldrawHandles'
|
||||
import { TldrawHoveredShapeIndicator } from './canvas/TldrawHoveredShapeIndicator'
|
||||
import { TldrawScribble } from './canvas/TldrawScribble'
|
||||
import { TldrawSelectionForeground } from './canvas/TldrawSelectionForeground'
|
||||
import { defaultShapeTools } from './defaultShapeTools'
|
||||
|
@ -37,6 +39,8 @@ export function Tldraw(
|
|||
() => ({
|
||||
Scribble: TldrawScribble,
|
||||
SelectionForeground: TldrawSelectionForeground,
|
||||
Handles: TldrawHandles,
|
||||
HoveredShapeIndicator: TldrawHoveredShapeIndicator,
|
||||
...rest.components,
|
||||
}),
|
||||
[rest.components]
|
||||
|
|
14
packages/tldraw/src/lib/canvas/TldrawHandles.tsx
Normal file
14
packages/tldraw/src/lib/canvas/TldrawHandles.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { TLHandlesComponent, useEditor, useValue } from '@tldraw/editor'
|
||||
|
||||
export const TldrawHandles: TLHandlesComponent = ({ children }) => {
|
||||
const editor = useEditor()
|
||||
const shouldDisplayHandles = useValue(
|
||||
'shouldDisplayHandles',
|
||||
() => editor.isInAny('select.idle', 'select.pointing_handle'),
|
||||
[editor]
|
||||
)
|
||||
|
||||
if (!shouldDisplayHandles) return null
|
||||
|
||||
return <svg className="tl-user-handles tl-overlays__item">{children}</svg>
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import {
|
||||
ShapeIndicator,
|
||||
TLHoveredShapeIndicatorComponent,
|
||||
useEditor,
|
||||
useValue,
|
||||
} from '@tldraw/editor'
|
||||
|
||||
export const TldrawHoveredShapeIndicator: TLHoveredShapeIndicatorComponent = ({ shapeId }) => {
|
||||
const editor = useEditor()
|
||||
const hideHoveredShapeIndicator = useValue(
|
||||
'hide hovered',
|
||||
() => editor.isInAny('select.idle', 'select.editing_shape'),
|
||||
[editor]
|
||||
)
|
||||
if (hideHoveredShapeIndicator) return null
|
||||
return <ShapeIndicator className="tl-user-indicator__hovered" id={shapeId} />
|
||||
}
|
||||
|
||||
//
|
|
@ -1,16 +1,7 @@
|
|||
import { EASINGS, TLScribble, getSvgPathFromPoints } from '@tldraw/editor'
|
||||
import { EASINGS, TLScribbleComponent, getSvgPathFromPoints } from '@tldraw/editor'
|
||||
import classNames from 'classnames'
|
||||
import { getStroke } from '../shapes/shared/freehand/getStroke'
|
||||
|
||||
/** @public */
|
||||
export type TLScribbleComponent = (props: {
|
||||
scribble: TLScribble
|
||||
zoom: number
|
||||
color?: string
|
||||
opacity?: number
|
||||
className?: string
|
||||
}) => any
|
||||
|
||||
export const TldrawScribble: TLScribbleComponent = ({
|
||||
scribble,
|
||||
zoom,
|
||||
|
@ -18,7 +9,7 @@ export const TldrawScribble: TLScribbleComponent = ({
|
|||
opacity,
|
||||
className,
|
||||
}) => {
|
||||
if (!scribble.points.length) return
|
||||
if (!scribble.points.length) return null
|
||||
|
||||
const d = getSvgPathFromPoints(
|
||||
getStroke(scribble.points, {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
RotateCorner,
|
||||
TLEmbedShape,
|
||||
TLSelectionForegroundComponent,
|
||||
TLTextShape,
|
||||
getCursor,
|
||||
toDomPrecision,
|
||||
|
@ -11,6 +12,7 @@ import {
|
|||
} from '@tldraw/editor'
|
||||
import classNames from 'classnames'
|
||||
import { useRef } from 'react'
|
||||
import { useReadOnly } from '../ui/hooks/useReadOnly'
|
||||
import { CropHandles } from './CropHandles'
|
||||
|
||||
const IS_FIREFOX =
|
||||
|
@ -18,437 +20,440 @@ const IS_FIREFOX =
|
|||
navigator.userAgent &&
|
||||
navigator.userAgent.toLowerCase().indexOf('firefox') > -1
|
||||
|
||||
export const TldrawSelectionForeground = track(function SelectionFg() {
|
||||
const editor = useEditor()
|
||||
const rSvg = useRef<SVGSVGElement>(null)
|
||||
export const TldrawSelectionForeground: TLSelectionForegroundComponent = track(
|
||||
function SelectionFg() {
|
||||
const editor = useEditor()
|
||||
const rSvg = useRef<SVGSVGElement>(null)
|
||||
|
||||
const isReadonlyMode = editor.isReadOnly
|
||||
const topEvents = useSelectionEvents('top')
|
||||
const rightEvents = useSelectionEvents('right')
|
||||
const bottomEvents = useSelectionEvents('bottom')
|
||||
const leftEvents = useSelectionEvents('left')
|
||||
const topLeftEvents = useSelectionEvents('top_left')
|
||||
const topRightEvents = useSelectionEvents('top_right')
|
||||
const bottomRightEvents = useSelectionEvents('bottom_right')
|
||||
const bottomLeftEvents = useSelectionEvents('bottom_left')
|
||||
const isReadonlyMode = useReadOnly()
|
||||
const topEvents = useSelectionEvents('top')
|
||||
const rightEvents = useSelectionEvents('right')
|
||||
const bottomEvents = useSelectionEvents('bottom')
|
||||
const leftEvents = useSelectionEvents('left')
|
||||
const topLeftEvents = useSelectionEvents('top_left')
|
||||
const topRightEvents = useSelectionEvents('top_right')
|
||||
const bottomRightEvents = useSelectionEvents('bottom_right')
|
||||
const bottomLeftEvents = useSelectionEvents('bottom_left')
|
||||
|
||||
const isDefaultCursor = !editor.isMenuOpen && editor.cursor.type === 'default'
|
||||
const isCoarsePointer = editor.isCoarsePointer
|
||||
const isDefaultCursor = !editor.isMenuOpen && editor.instanceState.cursor.type === 'default'
|
||||
const isCoarsePointer = editor.instanceState.isCoarsePointer
|
||||
|
||||
let bounds = editor.selectionBounds
|
||||
const shapes = editor.selectedShapes
|
||||
const onlyShape = editor.onlySelectedShape
|
||||
const isLockedShape = onlyShape && editor.isShapeOrAncestorLocked(onlyShape)
|
||||
let bounds = editor.selectionBounds
|
||||
const shapes = editor.selectedShapes
|
||||
const onlyShape = editor.onlySelectedShape
|
||||
const isLockedShape = onlyShape && editor.isShapeOrAncestorLocked(onlyShape)
|
||||
|
||||
// if all shapes have an expandBy for the selection outline, we can expand by the l
|
||||
const expandOutlineBy = onlyShape
|
||||
? editor.getShapeUtil(onlyShape).expandSelectionOutlinePx(onlyShape)
|
||||
: 0
|
||||
// if all shapes have an expandBy for the selection outline, we can expand by the l
|
||||
const expandOutlineBy = onlyShape
|
||||
? editor.getShapeUtil(onlyShape).expandSelectionOutlinePx(onlyShape)
|
||||
: 0
|
||||
|
||||
useTransform(rSvg, bounds?.x, bounds?.y, 1, editor.selectionRotation, {
|
||||
x: -expandOutlineBy,
|
||||
y: -expandOutlineBy,
|
||||
})
|
||||
useTransform(rSvg, bounds?.x, bounds?.y, 1, editor.selectionRotation, {
|
||||
x: -expandOutlineBy,
|
||||
y: -expandOutlineBy,
|
||||
})
|
||||
|
||||
if (!bounds) return null
|
||||
bounds = bounds.clone().expandBy(expandOutlineBy)
|
||||
if (!bounds) return null
|
||||
bounds = bounds.clone().expandBy(expandOutlineBy)
|
||||
|
||||
const zoom = editor.zoomLevel
|
||||
const rotation = editor.selectionRotation
|
||||
const isChangingStyles = editor.isChangingStyle
|
||||
const zoom = editor.zoomLevel
|
||||
const rotation = editor.selectionRotation
|
||||
const isChangingStyle = editor.instanceState.isChangingStyle
|
||||
|
||||
const width = Math.max(1, bounds.width)
|
||||
const height = Math.max(1, bounds.height)
|
||||
const width = Math.max(1, bounds.width)
|
||||
const height = Math.max(1, bounds.height)
|
||||
|
||||
const size = 8 / zoom
|
||||
const isTinyX = width < size * 2
|
||||
const isTinyY = height < size * 2
|
||||
const size = 8 / zoom
|
||||
const isTinyX = width < size * 2
|
||||
const isTinyY = height < size * 2
|
||||
|
||||
const isSmallX = width < size * 4
|
||||
const isSmallY = height < size * 4
|
||||
const isSmallCropX = width < size * 5
|
||||
const isSmallCropY = height < size * 5
|
||||
const isSmallX = width < size * 4
|
||||
const isSmallY = height < size * 4
|
||||
const isSmallCropX = width < size * 5
|
||||
const isSmallCropY = height < size * 5
|
||||
|
||||
const mobileHandleMultiplier = isCoarsePointer ? 1.75 : 1
|
||||
const targetSize = (6 / zoom) * mobileHandleMultiplier
|
||||
const mobileHandleMultiplier = isCoarsePointer ? 1.75 : 1
|
||||
const targetSize = (6 / zoom) * mobileHandleMultiplier
|
||||
|
||||
const targetSizeX = (isSmallX ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75)
|
||||
const targetSizeY = (isSmallY ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75)
|
||||
const targetSizeX = (isSmallX ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75)
|
||||
const targetSizeY = (isSmallY ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75)
|
||||
|
||||
const showSelectionBounds =
|
||||
(onlyShape ? !editor.getShapeUtil(onlyShape).hideSelectionBoundsFg(onlyShape) : true) &&
|
||||
!isChangingStyles
|
||||
const showSelectionBounds =
|
||||
(onlyShape ? !editor.getShapeUtil(onlyShape).hideSelectionBoundsFg(onlyShape) : true) &&
|
||||
!isChangingStyle
|
||||
|
||||
let shouldDisplayBox =
|
||||
(showSelectionBounds &&
|
||||
let shouldDisplayBox =
|
||||
(showSelectionBounds &&
|
||||
editor.isInAny(
|
||||
'select.idle',
|
||||
'select.brushing',
|
||||
'select.scribble_brushing',
|
||||
'select.pointing_canvas',
|
||||
'select.pointing_selection',
|
||||
'select.pointing_shape',
|
||||
'select.crop.idle',
|
||||
'select.crop.pointing_crop',
|
||||
'select.pointing_resize_handle',
|
||||
'select.pointing_crop_handle',
|
||||
'select.editing_shape'
|
||||
)) ||
|
||||
(showSelectionBounds &&
|
||||
editor.isIn('select.resizing') &&
|
||||
onlyShape &&
|
||||
editor.isShapeOfType<TLTextShape>(onlyShape, 'text'))
|
||||
|
||||
if (
|
||||
onlyShape &&
|
||||
editor.isShapeOfType<TLEmbedShape>(onlyShape, 'embed') &&
|
||||
shouldDisplayBox &&
|
||||
IS_FIREFOX
|
||||
) {
|
||||
shouldDisplayBox = false
|
||||
}
|
||||
|
||||
const showCropHandles =
|
||||
editor.isInAny(
|
||||
'select.pointing_crop_handle',
|
||||
'select.crop.idle',
|
||||
'select.crop.pointing_crop'
|
||||
) &&
|
||||
!isChangingStyle &&
|
||||
!isReadonlyMode
|
||||
|
||||
const shouldDisplayControls =
|
||||
editor.isInAny(
|
||||
'select.idle',
|
||||
'select.brushing',
|
||||
'select.scribble_brushing',
|
||||
'select.pointing_canvas',
|
||||
'select.pointing_selection',
|
||||
'select.pointing_shape',
|
||||
'select.crop.idle',
|
||||
'select.crop.pointing_crop',
|
||||
'select.pointing_resize_handle',
|
||||
'select.pointing_crop_handle',
|
||||
'select.editing_shape'
|
||||
)) ||
|
||||
(showSelectionBounds &&
|
||||
editor.isIn('select.resizing') &&
|
||||
'select.crop.idle'
|
||||
) &&
|
||||
!isChangingStyle &&
|
||||
!isReadonlyMode
|
||||
|
||||
const showCornerRotateHandles =
|
||||
!isCoarsePointer &&
|
||||
!(isTinyX || isTinyY) &&
|
||||
(shouldDisplayControls || showCropHandles) &&
|
||||
(onlyShape ? !editor.getShapeUtil(onlyShape).hideRotateHandle(onlyShape) : true) &&
|
||||
!isLockedShape
|
||||
|
||||
const showMobileRotateHandle =
|
||||
isCoarsePointer &&
|
||||
(!isSmallX || !isSmallY) &&
|
||||
(shouldDisplayControls || showCropHandles) &&
|
||||
(onlyShape ? !editor.getShapeUtil(onlyShape).hideRotateHandle(onlyShape) : true) &&
|
||||
!isLockedShape
|
||||
|
||||
const showResizeHandles =
|
||||
shouldDisplayControls &&
|
||||
(onlyShape
|
||||
? editor.getShapeUtil(onlyShape).canResize(onlyShape) &&
|
||||
!editor.getShapeUtil(onlyShape).hideResizeHandles(onlyShape)
|
||||
: true) &&
|
||||
!showCropHandles &&
|
||||
!isLockedShape
|
||||
|
||||
const hideAlternateCornerHandles = isTinyX || isTinyY
|
||||
const showOnlyOneHandle = isTinyX && isTinyY
|
||||
const hideAlternateCropHandles = isSmallCropX || isSmallCropY
|
||||
|
||||
const showHandles = showResizeHandles || showCropHandles
|
||||
const hideRotateCornerHandles = !showCornerRotateHandles
|
||||
const hideMobileRotateHandle = !shouldDisplayControls || !showMobileRotateHandle
|
||||
const hideTopLeftCorner = !shouldDisplayControls || !showHandles
|
||||
const hideTopRightCorner = !shouldDisplayControls || !showHandles || hideAlternateCornerHandles
|
||||
const hideBottomLeftCorner =
|
||||
!shouldDisplayControls || !showHandles || hideAlternateCornerHandles
|
||||
const hideBottomRightCorner =
|
||||
!shouldDisplayControls || !showHandles || (showOnlyOneHandle && !showCropHandles)
|
||||
|
||||
let hideEdgeTargetsDueToCoarsePointer = isCoarsePointer
|
||||
|
||||
if (
|
||||
hideEdgeTargetsDueToCoarsePointer &&
|
||||
shapes.every((shape) => editor.getShapeUtil(shape).isAspectRatioLocked(shape))
|
||||
) {
|
||||
hideEdgeTargetsDueToCoarsePointer = false
|
||||
}
|
||||
|
||||
// If we're showing crop handles, then show the edges too.
|
||||
// If we're showing resize handles, then show the edges only
|
||||
// if we're not hiding them for some other reason
|
||||
let hideEdgeTargets = true
|
||||
|
||||
if (showCropHandles) {
|
||||
hideEdgeTargets = hideAlternateCropHandles
|
||||
} else if (showResizeHandles) {
|
||||
hideEdgeTargets =
|
||||
hideAlternateCornerHandles || showOnlyOneHandle || hideEdgeTargetsDueToCoarsePointer
|
||||
}
|
||||
|
||||
const textHandleHeight = Math.min(24 / zoom, height - targetSizeY * 3)
|
||||
const showTextResizeHandles =
|
||||
shouldDisplayControls &&
|
||||
isCoarsePointer &&
|
||||
onlyShape &&
|
||||
editor.isShapeOfType<TLTextShape>(onlyShape, 'text'))
|
||||
editor.isShapeOfType<TLTextShape>(onlyShape, 'text') &&
|
||||
textHandleHeight * zoom >= 4
|
||||
|
||||
if (
|
||||
onlyShape &&
|
||||
editor.isShapeOfType<TLEmbedShape>(onlyShape, 'embed') &&
|
||||
shouldDisplayBox &&
|
||||
IS_FIREFOX
|
||||
) {
|
||||
shouldDisplayBox = false
|
||||
}
|
||||
|
||||
const showCropHandles =
|
||||
editor.isInAny(
|
||||
'select.pointing_crop_handle',
|
||||
'select.crop.idle',
|
||||
'select.crop.pointing_crop'
|
||||
) &&
|
||||
!isChangingStyles &&
|
||||
!isReadonlyMode
|
||||
|
||||
const shouldDisplayControls =
|
||||
editor.isInAny(
|
||||
'select.idle',
|
||||
'select.pointing_selection',
|
||||
'select.pointing_shape',
|
||||
'select.crop.idle'
|
||||
) &&
|
||||
!isChangingStyles &&
|
||||
!isReadonlyMode
|
||||
|
||||
const showCornerRotateHandles =
|
||||
!isCoarsePointer &&
|
||||
!(isTinyX || isTinyY) &&
|
||||
(shouldDisplayControls || showCropHandles) &&
|
||||
(onlyShape ? !editor.getShapeUtil(onlyShape).hideRotateHandle(onlyShape) : true) &&
|
||||
!isLockedShape
|
||||
|
||||
const showMobileRotateHandle =
|
||||
isCoarsePointer &&
|
||||
(!isSmallX || !isSmallY) &&
|
||||
(shouldDisplayControls || showCropHandles) &&
|
||||
(onlyShape ? !editor.getShapeUtil(onlyShape).hideRotateHandle(onlyShape) : true) &&
|
||||
!isLockedShape
|
||||
|
||||
const showResizeHandles =
|
||||
shouldDisplayControls &&
|
||||
(onlyShape
|
||||
? editor.getShapeUtil(onlyShape).canResize(onlyShape) &&
|
||||
!editor.getShapeUtil(onlyShape).hideResizeHandles(onlyShape)
|
||||
: true) &&
|
||||
!showCropHandles &&
|
||||
!isLockedShape
|
||||
|
||||
const hideAlternateCornerHandles = isTinyX || isTinyY
|
||||
const showOnlyOneHandle = isTinyX && isTinyY
|
||||
const hideAlternateCropHandles = isSmallCropX || isSmallCropY
|
||||
|
||||
const showHandles = showResizeHandles || showCropHandles
|
||||
const hideRotateCornerHandles = !showCornerRotateHandles
|
||||
const hideMobileRotateHandle = !shouldDisplayControls || !showMobileRotateHandle
|
||||
const hideTopLeftCorner = !shouldDisplayControls || !showHandles
|
||||
const hideTopRightCorner = !shouldDisplayControls || !showHandles || hideAlternateCornerHandles
|
||||
const hideBottomLeftCorner = !shouldDisplayControls || !showHandles || hideAlternateCornerHandles
|
||||
const hideBottomRightCorner =
|
||||
!shouldDisplayControls || !showHandles || (showOnlyOneHandle && !showCropHandles)
|
||||
|
||||
let hideEdgeTargetsDueToCoarsePointer = isCoarsePointer
|
||||
|
||||
if (
|
||||
hideEdgeTargetsDueToCoarsePointer &&
|
||||
shapes.every((shape) => editor.getShapeUtil(shape).isAspectRatioLocked(shape))
|
||||
) {
|
||||
hideEdgeTargetsDueToCoarsePointer = false
|
||||
}
|
||||
|
||||
// If we're showing crop handles, then show the edges too.
|
||||
// If we're showing resize handles, then show the edges only
|
||||
// if we're not hiding them for some other reason
|
||||
let hideEdgeTargets = true
|
||||
|
||||
if (showCropHandles) {
|
||||
hideEdgeTargets = hideAlternateCropHandles
|
||||
} else if (showResizeHandles) {
|
||||
hideEdgeTargets =
|
||||
hideAlternateCornerHandles || showOnlyOneHandle || hideEdgeTargetsDueToCoarsePointer
|
||||
}
|
||||
|
||||
const textHandleHeight = Math.min(24 / zoom, height - targetSizeY * 3)
|
||||
const showTextResizeHandles =
|
||||
shouldDisplayControls &&
|
||||
isCoarsePointer &&
|
||||
onlyShape &&
|
||||
editor.isShapeOfType<TLTextShape>(onlyShape, 'text') &&
|
||||
textHandleHeight * zoom >= 4
|
||||
|
||||
return (
|
||||
<svg
|
||||
ref={rSvg}
|
||||
className="tl-overlays__item tl-selection__fg"
|
||||
data-testid="selection-foreground"
|
||||
>
|
||||
{shouldDisplayBox && (
|
||||
return (
|
||||
<svg
|
||||
ref={rSvg}
|
||||
className="tl-overlays__item tl-selection__fg"
|
||||
data-testid="selection-foreground"
|
||||
>
|
||||
{shouldDisplayBox && (
|
||||
<rect
|
||||
className={classNames('tl-selection__fg__outline')}
|
||||
width={toDomPrecision(width)}
|
||||
height={toDomPrecision(height)}
|
||||
/>
|
||||
)}
|
||||
<RotateCornerHandle
|
||||
data-testid="selection.rotate.top-left"
|
||||
cx={0}
|
||||
cy={0}
|
||||
targetSize={targetSize}
|
||||
corner="top_left_rotate"
|
||||
cursor={isDefaultCursor ? getCursor('nwse-rotate', rotation) : undefined}
|
||||
isHidden={hideRotateCornerHandles}
|
||||
/>
|
||||
<RotateCornerHandle
|
||||
data-testid="selection.rotate.top-right"
|
||||
cx={width + targetSize * 3}
|
||||
cy={0}
|
||||
targetSize={targetSize}
|
||||
corner="top_right_rotate"
|
||||
cursor={isDefaultCursor ? getCursor('nesw-rotate', rotation) : undefined}
|
||||
isHidden={hideRotateCornerHandles}
|
||||
/>
|
||||
<RotateCornerHandle
|
||||
data-testid="selection.rotate.bottom-left"
|
||||
cx={0}
|
||||
cy={height + targetSize * 3}
|
||||
targetSize={targetSize}
|
||||
corner="bottom_left_rotate"
|
||||
cursor={isDefaultCursor ? getCursor('swne-rotate', rotation) : undefined}
|
||||
isHidden={hideRotateCornerHandles}
|
||||
/>
|
||||
<RotateCornerHandle
|
||||
data-testid="selection.rotate.bottom-right"
|
||||
cx={width + targetSize * 3}
|
||||
cy={height + targetSize * 3}
|
||||
targetSize={targetSize}
|
||||
corner="bottom_right_rotate"
|
||||
cursor={isDefaultCursor ? getCursor('senw-rotate', rotation) : undefined}
|
||||
isHidden={hideRotateCornerHandles}
|
||||
/>{' '}
|
||||
<MobileRotateHandle
|
||||
data-testid="selection.rotate.mobile"
|
||||
cx={isSmallX ? -targetSize * 1.5 : width / 2}
|
||||
cy={isSmallX ? height / 2 : -targetSize * 1.5}
|
||||
size={size}
|
||||
isHidden={hideMobileRotateHandle}
|
||||
/>
|
||||
{/* Targets */}
|
||||
<rect
|
||||
className={classNames('tl-selection__fg__outline')}
|
||||
width={toDomPrecision(width)}
|
||||
height={toDomPrecision(height)}
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideEdgeTargets,
|
||||
})}
|
||||
data-testid="selection.resize.top"
|
||||
aria-label="top target"
|
||||
pointerEvents="all"
|
||||
x={0}
|
||||
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY))}
|
||||
width={toDomPrecision(Math.max(1, width))}
|
||||
height={toDomPrecision(Math.max(1, targetSizeY * 2))}
|
||||
style={isDefaultCursor ? { cursor: getCursor('ns-resize', rotation) } : undefined}
|
||||
{...topEvents}
|
||||
/>
|
||||
)}
|
||||
<RotateCornerHandle
|
||||
data-testid="selection.rotate.top-left"
|
||||
cx={0}
|
||||
cy={0}
|
||||
targetSize={targetSize}
|
||||
corner="top_left_rotate"
|
||||
cursor={isDefaultCursor ? getCursor('nwse-rotate', rotation) : undefined}
|
||||
isHidden={hideRotateCornerHandles}
|
||||
/>
|
||||
<RotateCornerHandle
|
||||
data-testid="selection.rotate.top-right"
|
||||
cx={width + targetSize * 3}
|
||||
cy={0}
|
||||
targetSize={targetSize}
|
||||
corner="top_right_rotate"
|
||||
cursor={isDefaultCursor ? getCursor('nesw-rotate', rotation) : undefined}
|
||||
isHidden={hideRotateCornerHandles}
|
||||
/>
|
||||
<RotateCornerHandle
|
||||
data-testid="selection.rotate.bottom-left"
|
||||
cx={0}
|
||||
cy={height + targetSize * 3}
|
||||
targetSize={targetSize}
|
||||
corner="bottom_left_rotate"
|
||||
cursor={isDefaultCursor ? getCursor('swne-rotate', rotation) : undefined}
|
||||
isHidden={hideRotateCornerHandles}
|
||||
/>
|
||||
<RotateCornerHandle
|
||||
data-testid="selection.rotate.bottom-right"
|
||||
cx={width + targetSize * 3}
|
||||
cy={height + targetSize * 3}
|
||||
targetSize={targetSize}
|
||||
corner="bottom_right_rotate"
|
||||
cursor={isDefaultCursor ? getCursor('senw-rotate', rotation) : undefined}
|
||||
isHidden={hideRotateCornerHandles}
|
||||
/>{' '}
|
||||
<MobileRotateHandle
|
||||
data-testid="selection.rotate.mobile"
|
||||
cx={isSmallX ? -targetSize * 1.5 : width / 2}
|
||||
cy={isSmallX ? height / 2 : -targetSize * 1.5}
|
||||
size={size}
|
||||
isHidden={hideMobileRotateHandle}
|
||||
/>
|
||||
{/* Targets */}
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideEdgeTargets,
|
||||
})}
|
||||
data-testid="selection.resize.top"
|
||||
aria-label="top target"
|
||||
pointerEvents="all"
|
||||
x={0}
|
||||
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY))}
|
||||
width={toDomPrecision(Math.max(1, width))}
|
||||
height={toDomPrecision(Math.max(1, targetSizeY * 2))}
|
||||
style={isDefaultCursor ? { cursor: getCursor('ns-resize', rotation) } : undefined}
|
||||
{...topEvents}
|
||||
/>
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideEdgeTargets,
|
||||
})}
|
||||
data-testid="selection.resize.right"
|
||||
aria-label="right target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(width - (isSmallX ? 0 : targetSizeX))}
|
||||
y={0}
|
||||
height={toDomPrecision(Math.max(1, height))}
|
||||
width={toDomPrecision(Math.max(1, targetSizeX * 2))}
|
||||
style={isDefaultCursor ? { cursor: getCursor('ew-resize', rotation) } : undefined}
|
||||
{...rightEvents}
|
||||
/>
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideEdgeTargets,
|
||||
})}
|
||||
data-testid="selection.resize.bottom"
|
||||
aria-label="bottom target"
|
||||
pointerEvents="all"
|
||||
x={0}
|
||||
y={toDomPrecision(height - (isSmallY ? 0 : targetSizeY))}
|
||||
width={toDomPrecision(Math.max(1, width))}
|
||||
height={toDomPrecision(Math.max(1, targetSizeY * 2))}
|
||||
style={isDefaultCursor ? { cursor: getCursor('ns-resize', rotation) } : undefined}
|
||||
{...bottomEvents}
|
||||
/>
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideEdgeTargets,
|
||||
})}
|
||||
data-testid="selection.resize.left"
|
||||
aria-label="left target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 2 : targetSizeX))}
|
||||
y={0}
|
||||
height={toDomPrecision(Math.max(1, height))}
|
||||
width={toDomPrecision(Math.max(1, targetSizeX * 2))}
|
||||
style={isDefaultCursor ? { cursor: getCursor('ew-resize', rotation) } : undefined}
|
||||
{...leftEvents}
|
||||
/>
|
||||
{/* Corner Targets */}
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideTopLeftCorner,
|
||||
})}
|
||||
data-testid="selection.target.top-left"
|
||||
aria-label="top-left target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 2 : targetSizeX * 1.5))}
|
||||
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY * 1.5))}
|
||||
width={toDomPrecision(targetSizeX * 3)}
|
||||
height={toDomPrecision(targetSizeY * 3)}
|
||||
style={isDefaultCursor ? { cursor: getCursor('nwse-resize', rotation) } : undefined}
|
||||
{...topLeftEvents}
|
||||
/>
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideTopRightCorner,
|
||||
})}
|
||||
data-testid="selection.target.top-right"
|
||||
aria-label="top-right target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(width - (isSmallX ? 0 : targetSizeX * 1.5))}
|
||||
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY * 1.5))}
|
||||
width={toDomPrecision(targetSizeX * 3)}
|
||||
height={toDomPrecision(targetSizeY * 3)}
|
||||
style={isDefaultCursor ? { cursor: getCursor('nesw-resize', rotation) } : undefined}
|
||||
{...topRightEvents}
|
||||
/>
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideBottomRightCorner,
|
||||
})}
|
||||
data-testid="selection.target.bottom-right"
|
||||
aria-label="bottom-right target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(width - (isSmallX ? targetSizeX : targetSizeX * 1.5))}
|
||||
y={toDomPrecision(height - (isSmallY ? targetSizeY : targetSizeY * 1.5))}
|
||||
width={toDomPrecision(targetSizeX * 3)}
|
||||
height={toDomPrecision(targetSizeY * 3)}
|
||||
style={isDefaultCursor ? { cursor: getCursor('nwse-resize', rotation) } : undefined}
|
||||
{...bottomRightEvents}
|
||||
/>
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideBottomLeftCorner,
|
||||
})}
|
||||
data-testid="selection.target.bottom-left"
|
||||
aria-label="bottom-left target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 3 : targetSizeX * 1.5))}
|
||||
y={toDomPrecision(height - (isSmallY ? 0 : targetSizeY * 1.5))}
|
||||
width={toDomPrecision(targetSizeX * 3)}
|
||||
height={toDomPrecision(targetSizeY * 3)}
|
||||
style={isDefaultCursor ? { cursor: getCursor('nesw-resize', rotation) } : undefined}
|
||||
{...bottomLeftEvents}
|
||||
/>
|
||||
{/* Resize Handles */}
|
||||
{showResizeHandles && (
|
||||
<>
|
||||
<rect
|
||||
data-testid="selection.resize.top-left"
|
||||
className={classNames('tl-corner-handle', {
|
||||
'tl-hidden': hideTopLeftCorner,
|
||||
})}
|
||||
aria-label="top_left handle"
|
||||
x={toDomPrecision(0 - size / 2)}
|
||||
y={toDomPrecision(0 - size / 2)}
|
||||
width={toDomPrecision(size)}
|
||||
height={toDomPrecision(size)}
|
||||
/>
|
||||
<rect
|
||||
data-testid="selection.resize.top-right"
|
||||
className={classNames('tl-corner-handle', {
|
||||
'tl-hidden': hideTopRightCorner,
|
||||
})}
|
||||
aria-label="top_right handle"
|
||||
x={toDomPrecision(width - size / 2)}
|
||||
y={toDomPrecision(0 - size / 2)}
|
||||
width={toDomPrecision(size)}
|
||||
height={toDomPrecision(size)}
|
||||
/>
|
||||
<rect
|
||||
data-testid="selection.resize.bottom-right"
|
||||
className={classNames('tl-corner-handle', {
|
||||
'tl-hidden': hideBottomRightCorner,
|
||||
})}
|
||||
aria-label="bottom_right handle"
|
||||
x={toDomPrecision(width - size / 2)}
|
||||
y={toDomPrecision(height - size / 2)}
|
||||
width={toDomPrecision(size)}
|
||||
height={toDomPrecision(size)}
|
||||
/>
|
||||
<rect
|
||||
data-testid="selection.resize.bottom-left"
|
||||
className={classNames('tl-corner-handle', {
|
||||
'tl-hidden': hideBottomLeftCorner,
|
||||
})}
|
||||
aria-label="bottom_left handle"
|
||||
x={toDomPrecision(0 - size / 2)}
|
||||
y={toDomPrecision(height - size / 2)}
|
||||
width={toDomPrecision(size)}
|
||||
height={toDomPrecision(size)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showTextResizeHandles && (
|
||||
<>
|
||||
<rect
|
||||
data-testid="selection.text-resize.left.handle"
|
||||
className="tl-text-handle"
|
||||
aria-label="bottom_left handle"
|
||||
x={toDomPrecision(0 - size / 4)}
|
||||
y={toDomPrecision(height / 2 - textHandleHeight / 2)}
|
||||
rx={size / 4}
|
||||
width={toDomPrecision(size / 2)}
|
||||
height={toDomPrecision(textHandleHeight)}
|
||||
/>
|
||||
<rect
|
||||
data-testid="selection.text-resize.right.handle"
|
||||
className="tl-text-handle"
|
||||
aria-label="bottom_left handle"
|
||||
rx={size / 4}
|
||||
x={toDomPrecision(width - size / 4)}
|
||||
y={toDomPrecision(height / 2 - textHandleHeight / 2)}
|
||||
width={toDomPrecision(size / 2)}
|
||||
height={toDomPrecision(textHandleHeight)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{/* Crop Handles */}
|
||||
{showCropHandles && (
|
||||
<CropHandles
|
||||
{...{
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
hideAlternateHandles: hideAlternateCropHandles,
|
||||
}}
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideEdgeTargets,
|
||||
})}
|
||||
data-testid="selection.resize.right"
|
||||
aria-label="right target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(width - (isSmallX ? 0 : targetSizeX))}
|
||||
y={0}
|
||||
height={toDomPrecision(Math.max(1, height))}
|
||||
width={toDomPrecision(Math.max(1, targetSizeX * 2))}
|
||||
style={isDefaultCursor ? { cursor: getCursor('ew-resize', rotation) } : undefined}
|
||||
{...rightEvents}
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
)
|
||||
})
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideEdgeTargets,
|
||||
})}
|
||||
data-testid="selection.resize.bottom"
|
||||
aria-label="bottom target"
|
||||
pointerEvents="all"
|
||||
x={0}
|
||||
y={toDomPrecision(height - (isSmallY ? 0 : targetSizeY))}
|
||||
width={toDomPrecision(Math.max(1, width))}
|
||||
height={toDomPrecision(Math.max(1, targetSizeY * 2))}
|
||||
style={isDefaultCursor ? { cursor: getCursor('ns-resize', rotation) } : undefined}
|
||||
{...bottomEvents}
|
||||
/>
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideEdgeTargets,
|
||||
})}
|
||||
data-testid="selection.resize.left"
|
||||
aria-label="left target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 2 : targetSizeX))}
|
||||
y={0}
|
||||
height={toDomPrecision(Math.max(1, height))}
|
||||
width={toDomPrecision(Math.max(1, targetSizeX * 2))}
|
||||
style={isDefaultCursor ? { cursor: getCursor('ew-resize', rotation) } : undefined}
|
||||
{...leftEvents}
|
||||
/>
|
||||
{/* Corner Targets */}
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideTopLeftCorner,
|
||||
})}
|
||||
data-testid="selection.target.top-left"
|
||||
aria-label="top-left target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 2 : targetSizeX * 1.5))}
|
||||
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY * 1.5))}
|
||||
width={toDomPrecision(targetSizeX * 3)}
|
||||
height={toDomPrecision(targetSizeY * 3)}
|
||||
style={isDefaultCursor ? { cursor: getCursor('nwse-resize', rotation) } : undefined}
|
||||
{...topLeftEvents}
|
||||
/>
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideTopRightCorner,
|
||||
})}
|
||||
data-testid="selection.target.top-right"
|
||||
aria-label="top-right target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(width - (isSmallX ? 0 : targetSizeX * 1.5))}
|
||||
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY * 1.5))}
|
||||
width={toDomPrecision(targetSizeX * 3)}
|
||||
height={toDomPrecision(targetSizeY * 3)}
|
||||
style={isDefaultCursor ? { cursor: getCursor('nesw-resize', rotation) } : undefined}
|
||||
{...topRightEvents}
|
||||
/>
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideBottomRightCorner,
|
||||
})}
|
||||
data-testid="selection.target.bottom-right"
|
||||
aria-label="bottom-right target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(width - (isSmallX ? targetSizeX : targetSizeX * 1.5))}
|
||||
y={toDomPrecision(height - (isSmallY ? targetSizeY : targetSizeY * 1.5))}
|
||||
width={toDomPrecision(targetSizeX * 3)}
|
||||
height={toDomPrecision(targetSizeY * 3)}
|
||||
style={isDefaultCursor ? { cursor: getCursor('nwse-resize', rotation) } : undefined}
|
||||
{...bottomRightEvents}
|
||||
/>
|
||||
<rect
|
||||
className={classNames('tl-transparent', {
|
||||
'tl-hidden': hideBottomLeftCorner,
|
||||
})}
|
||||
data-testid="selection.target.bottom-left"
|
||||
aria-label="bottom-left target"
|
||||
pointerEvents="all"
|
||||
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 3 : targetSizeX * 1.5))}
|
||||
y={toDomPrecision(height - (isSmallY ? 0 : targetSizeY * 1.5))}
|
||||
width={toDomPrecision(targetSizeX * 3)}
|
||||
height={toDomPrecision(targetSizeY * 3)}
|
||||
style={isDefaultCursor ? { cursor: getCursor('nesw-resize', rotation) } : undefined}
|
||||
{...bottomLeftEvents}
|
||||
/>
|
||||
{/* Resize Handles */}
|
||||
{showResizeHandles && (
|
||||
<>
|
||||
<rect
|
||||
data-testid="selection.resize.top-left"
|
||||
className={classNames('tl-corner-handle', {
|
||||
'tl-hidden': hideTopLeftCorner,
|
||||
})}
|
||||
aria-label="top_left handle"
|
||||
x={toDomPrecision(0 - size / 2)}
|
||||
y={toDomPrecision(0 - size / 2)}
|
||||
width={toDomPrecision(size)}
|
||||
height={toDomPrecision(size)}
|
||||
/>
|
||||
<rect
|
||||
data-testid="selection.resize.top-right"
|
||||
className={classNames('tl-corner-handle', {
|
||||
'tl-hidden': hideTopRightCorner,
|
||||
})}
|
||||
aria-label="top_right handle"
|
||||
x={toDomPrecision(width - size / 2)}
|
||||
y={toDomPrecision(0 - size / 2)}
|
||||
width={toDomPrecision(size)}
|
||||
height={toDomPrecision(size)}
|
||||
/>
|
||||
<rect
|
||||
data-testid="selection.resize.bottom-right"
|
||||
className={classNames('tl-corner-handle', {
|
||||
'tl-hidden': hideBottomRightCorner,
|
||||
})}
|
||||
aria-label="bottom_right handle"
|
||||
x={toDomPrecision(width - size / 2)}
|
||||
y={toDomPrecision(height - size / 2)}
|
||||
width={toDomPrecision(size)}
|
||||
height={toDomPrecision(size)}
|
||||
/>
|
||||
<rect
|
||||
data-testid="selection.resize.bottom-left"
|
||||
className={classNames('tl-corner-handle', {
|
||||
'tl-hidden': hideBottomLeftCorner,
|
||||
})}
|
||||
aria-label="bottom_left handle"
|
||||
x={toDomPrecision(0 - size / 2)}
|
||||
y={toDomPrecision(height - size / 2)}
|
||||
width={toDomPrecision(size)}
|
||||
height={toDomPrecision(size)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showTextResizeHandles && (
|
||||
<>
|
||||
<rect
|
||||
data-testid="selection.text-resize.left.handle"
|
||||
className="tl-text-handle"
|
||||
aria-label="bottom_left handle"
|
||||
x={toDomPrecision(0 - size / 4)}
|
||||
y={toDomPrecision(height / 2 - textHandleHeight / 2)}
|
||||
rx={size / 4}
|
||||
width={toDomPrecision(size / 2)}
|
||||
height={toDomPrecision(textHandleHeight)}
|
||||
/>
|
||||
<rect
|
||||
data-testid="selection.text-resize.right.handle"
|
||||
className="tl-text-handle"
|
||||
aria-label="bottom_left handle"
|
||||
rx={size / 4}
|
||||
x={toDomPrecision(width - size / 4)}
|
||||
y={toDomPrecision(height / 2 - textHandleHeight / 2)}
|
||||
width={toDomPrecision(size / 2)}
|
||||
height={toDomPrecision(textHandleHeight)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{/* Crop Handles */}
|
||||
{showCropHandles && (
|
||||
<CropHandles
|
||||
{...{
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
hideAlternateHandles: hideAlternateCropHandles,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export const RotateCornerHandle = function RotateCornerHandle({
|
||||
cx,
|
||||
|
|
|
@ -110,7 +110,7 @@ describe('When dragging the arrow', () => {
|
|||
})
|
||||
|
||||
it('returns to arrow.idle, keeping shape, on pointer up when tool lock is active', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
const shapesBefore = editor.shapesArray.length
|
||||
editor
|
||||
.setCurrentTool('arrow')
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
TLOnHandleChangeHandler,
|
||||
TLOnResizeHandler,
|
||||
TLOnTranslateStartHandler,
|
||||
TLShapeId,
|
||||
TLShapePartial,
|
||||
TLShapeUtilCanvasSvgDef,
|
||||
TLShapeUtilFlag,
|
||||
|
@ -370,26 +369,24 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
}
|
||||
|
||||
override onTranslateStart: TLOnTranslateStartHandler<TLArrowShape> = (shape) => {
|
||||
let startBinding: TLShapeId | null =
|
||||
const startBindingId =
|
||||
shape.props.start.type === 'binding' ? shape.props.start.boundShapeId : null
|
||||
let endBinding: TLShapeId | null =
|
||||
shape.props.end.type === 'binding' ? shape.props.end.boundShapeId : null
|
||||
const endBindingId = shape.props.end.type === 'binding' ? shape.props.end.boundShapeId : null
|
||||
|
||||
// If at least one bound shape is in the selection, do nothing;
|
||||
// If no bound shapes are in the selection, unbind any bound shapes
|
||||
|
||||
const { selectedIds } = this.editor
|
||||
|
||||
if (
|
||||
(startBinding &&
|
||||
(this.editor.isSelected(startBinding) || this.editor.isAncestorSelected(startBinding))) ||
|
||||
(endBinding &&
|
||||
(this.editor.isSelected(endBinding) || this.editor.isAncestorSelected(endBinding)))
|
||||
(startBindingId &&
|
||||
(selectedIds.includes(startBindingId) || this.editor.isAncestorSelected(startBindingId))) ||
|
||||
(endBindingId &&
|
||||
(selectedIds.includes(endBindingId) || this.editor.isAncestorSelected(endBindingId)))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
startBinding = null
|
||||
endBinding = null
|
||||
|
||||
const { start, end } = getArrowTerminalsInArrowSpace(this.editor, shape)
|
||||
|
||||
return {
|
||||
|
@ -560,7 +557,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
'select.pointing_handle',
|
||||
'select.dragging_handle',
|
||||
'arrow.dragging'
|
||||
) && !this.editor.isReadOnly
|
||||
) && !this.editor.instanceState.isReadOnly
|
||||
|
||||
const info = this.editor.getArrowInfo(shape)
|
||||
const bounds = this.editor.getBounds(shape)
|
||||
|
@ -914,7 +911,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
}
|
||||
|
||||
override toSvg(shape: TLArrowShape, ctx: SvgExportContext) {
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
ctx.addExportDef(getFillDefForExport(shape.props.fill, theme))
|
||||
|
||||
const color = theme[shape.props.color].solid
|
||||
|
|
|
@ -8,7 +8,7 @@ export class Idle extends StateNode {
|
|||
}
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.cursor = { type: 'cross', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onCancel = () => {
|
||||
|
|
|
@ -221,7 +221,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
}
|
||||
|
||||
override toSvg(shape: TLDrawShape, ctx: SvgExportContext) {
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
ctx.addExportDef(getFillDefForExport(shape.props.fill, theme))
|
||||
|
||||
const { color } = shape.props
|
||||
|
|
|
@ -459,7 +459,7 @@ export class Drawing extends StateNode {
|
|||
let didSnap = false
|
||||
let snapSegment: TLDrawShapeSegment | undefined = undefined
|
||||
|
||||
const shouldSnap = this.editor.isSnapMode ? !ctrlKey : ctrlKey
|
||||
const shouldSnap = this.editor.user.isSnapMode ? !ctrlKey : ctrlKey
|
||||
|
||||
if (shouldSnap) {
|
||||
if (newSegments.length > 2) {
|
||||
|
|
|
@ -8,7 +8,7 @@ export class Idle extends StateNode {
|
|||
}
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.cursor = { type: 'cross', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onCancel = () => {
|
||||
|
|
|
@ -84,7 +84,7 @@ export class EmbedShapeUtil extends BaseBoxShapeUtil<TLEmbedShape> {
|
|||
const isHoveringWhileEditingSameShape = useValue(
|
||||
'is hovering',
|
||||
() => {
|
||||
const { editingId, hoveredId } = this.editor.pageState
|
||||
const { editingId, hoveredId } = this.editor.currentPageState
|
||||
|
||||
if (editingId && hoveredId !== editingId) {
|
||||
const editingShape = this.editor.getShapeById(editingId)
|
||||
|
|
|
@ -123,7 +123,7 @@ describe('When in the pointing state', () => {
|
|||
})
|
||||
|
||||
it('Creates a frame and returns to frame.idle on pointer up if tool lock is enabled', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
expect(editor.shapesArray.length).toBe(0)
|
||||
editor.setCurrentTool('frame')
|
||||
editor.pointerDown(50, 50)
|
||||
|
@ -152,7 +152,7 @@ describe('When in the resizing state', () => {
|
|||
})
|
||||
|
||||
it('Returns to frame.idle on complete if tool lock is enabled', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
editor.setCurrentTool('frame')
|
||||
editor.pointerDown(50, 50)
|
||||
editor.pointerMove(100, 100)
|
||||
|
|
|
@ -67,7 +67,7 @@ export class FrameShapeUtil extends BaseBoxShapeUtil<TLFrameShape> {
|
|||
}
|
||||
|
||||
override toSvg(shape: TLFrameShape): SVGElement | Promise<SVGElement> {
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
||||
|
||||
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
|
||||
|
|
|
@ -15,7 +15,7 @@ export const FrameLabelInput = forwardRef<
|
|||
// and sending us back into edit mode
|
||||
e.stopPropagation()
|
||||
e.currentTarget.blur()
|
||||
editor.editingId = null
|
||||
editor.setEditingId(null)
|
||||
}
|
||||
},
|
||||
[editor]
|
||||
|
|
|
@ -152,7 +152,7 @@ describe('When in the pointing state', () => {
|
|||
})
|
||||
|
||||
it('Creates a geo and returns to geo.idle on pointer up if tool lock is enabled', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
expect(editor.shapesArray.length).toBe(0)
|
||||
editor.setCurrentTool('geo')
|
||||
editor.pointerDown(50, 50)
|
||||
|
@ -181,7 +181,7 @@ describe('When in the resizing state while creating a geo shape', () => {
|
|||
})
|
||||
|
||||
it('Returns to geo.idle on complete if tool lock is enabled', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
editor.setCurrentTool('geo')
|
||||
editor.pointerDown(50, 50)
|
||||
editor.pointerMove(100, 100)
|
||||
|
|
|
@ -572,7 +572,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
override toSvg(shape: TLGeoShape, ctx: SvgExportContext) {
|
||||
const { id, props } = shape
|
||||
const strokeWidth = STROKE_SIZES[props.size]
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
ctx.addExportDef(getFillDefForExport(shape.props.fill, theme))
|
||||
|
||||
let svgElm: SVGElement
|
||||
|
|
|
@ -8,7 +8,7 @@ export class Idle extends StateNode {
|
|||
}
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.cursor = { type: 'cross', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => {
|
||||
|
@ -17,7 +17,7 @@ export class Idle extends StateNode {
|
|||
if (shape && this.editor.isShapeOfType<TLGeoShape>(shape, 'geo')) {
|
||||
// todo: ensure that this only works with the most recently created shape, not just any geo shape that happens to be selected at the time
|
||||
this.editor.mark('editing shape')
|
||||
this.editor.editingId = shape.id
|
||||
this.editor.setEditingId(shape.id)
|
||||
this.editor.setCurrentTool('select.editing_shape', {
|
||||
...info,
|
||||
target: 'shape',
|
||||
|
|
|
@ -161,12 +161,12 @@ export class HighlightShapeUtil extends ShapeUtil<TLHighlightShape> {
|
|||
}
|
||||
|
||||
override toSvg(shape: TLHighlightShape) {
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
return highlighterToSvg(getStrokeWidth(shape), shape, OVERLAY_OPACITY, theme)
|
||||
}
|
||||
|
||||
override toBackgroundSvg(shape: TLHighlightShape) {
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
return highlighterToSvg(getStrokeWidth(shape), shape, UNDERLAY_OPACITY, theme)
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ describe('When dragging the line', () => {
|
|||
})
|
||||
|
||||
it('returns to line.idle, keeping shape, on pointer up if tool lock is enabled', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
const shapesBefore = editor.shapesArray.length
|
||||
editor
|
||||
.setCurrentTool('line')
|
||||
|
@ -116,7 +116,7 @@ describe('When dragging the line', () => {
|
|||
|
||||
describe('When extending the line with the shift-key in tool-lock mode', () => {
|
||||
it('extends a line by joining-the-dots', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
editor
|
||||
.setCurrentTool('line')
|
||||
.pointerDown(0, 0, { target: 'canvas' })
|
||||
|
@ -133,7 +133,7 @@ describe('When extending the line with the shift-key in tool-lock mode', () => {
|
|||
})
|
||||
|
||||
it('extends a line after a click by shift-click dragging', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
editor
|
||||
.setCurrentTool('line')
|
||||
.pointerDown(0, 0, { target: 'canvas' })
|
||||
|
@ -150,7 +150,7 @@ describe('When extending the line with the shift-key in tool-lock mode', () => {
|
|||
})
|
||||
|
||||
it('extends a line by shift-click dragging', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
editor
|
||||
.setCurrentTool('line')
|
||||
.pointerDown(0, 0, { target: 'canvas' })
|
||||
|
@ -168,7 +168,7 @@ describe('When extending the line with the shift-key in tool-lock mode', () => {
|
|||
})
|
||||
|
||||
it('extends a line by shift-clicking even after canceling a pointerdown', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
editor
|
||||
.setCurrentTool('line')
|
||||
.pointerDown(0, 0, { target: 'canvas' })
|
||||
|
@ -188,7 +188,7 @@ describe('When extending the line with the shift-key in tool-lock mode', () => {
|
|||
})
|
||||
|
||||
it('extends a line by shift-clicking even after canceling a pointermove', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
editor
|
||||
.setCurrentTool('line')
|
||||
.pointerDown(0, 0, { target: 'canvas' })
|
||||
|
|
|
@ -348,7 +348,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
}
|
||||
|
||||
override toSvg(shape: TLLineShape) {
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
const color = theme[shape.props.color].solid
|
||||
const spline = getSplineForLineShape(shape)
|
||||
return getLineSvg(shape, spline, color, STROKE_SIZES[shape.props.size])
|
||||
|
|
|
@ -7,7 +7,7 @@ export class Idle extends StateNode {
|
|||
|
||||
override onEnter = (info: { shapeId: TLShapeId }) => {
|
||||
this.shapeId = info.shapeId
|
||||
this.editor.cursor = { type: 'cross', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onPointerDown: TLEventHandlers['onPointerDown'] = () => {
|
||||
|
|
|
@ -110,7 +110,7 @@ describe('When in the pointing state', () => {
|
|||
})
|
||||
|
||||
it('Returns to the note tool on complete from translating when tool lock is enabled', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
editor.setCurrentTool('note')
|
||||
editor.pointerDown(50, 50)
|
||||
editor.pointerMove(55, 55)
|
||||
|
@ -135,7 +135,7 @@ describe('When in the pointing state', () => {
|
|||
})
|
||||
|
||||
it('Creates a frame and returns to frame.idle on pointer up if tool lock is enabled', () => {
|
||||
editor.isToolLocked = true
|
||||
editor.updateInstanceState({ isToolLocked: true })
|
||||
expect(editor.shapesArray.length).toBe(0)
|
||||
editor.setCurrentTool('note')
|
||||
editor.pointerDown(50, 50)
|
||||
|
|
|
@ -122,7 +122,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
|
||||
override toSvg(shape: TLNoteShape, ctx: SvgExportContext) {
|
||||
ctx.addExportDef(getFontDefForExport(shape.props.font))
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
const bounds = this.getBounds(shape)
|
||||
|
||||
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
||||
|
|
|
@ -8,7 +8,7 @@ export class Idle extends StateNode {
|
|||
}
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.cursor = { type: 'cross', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onCancel = () => {
|
||||
|
|
|
@ -68,7 +68,7 @@ export class Pointing extends StateNode {
|
|||
} else {
|
||||
if (!shape) return
|
||||
|
||||
this.editor.editingId = shape.id
|
||||
this.editor.setEditingId(shape.id)
|
||||
this.editor.setCurrentTool('select.editing_shape', {
|
||||
...this.info,
|
||||
target: 'shape',
|
||||
|
|
|
@ -17,7 +17,7 @@ export interface ShapeFillProps {
|
|||
|
||||
export function useDefaultColorTheme() {
|
||||
const editor = useEditor()
|
||||
return getDefaultColorTheme(editor)
|
||||
return getDefaultColorTheme({ isDarkMode: editor.user.isDarkMode })
|
||||
}
|
||||
|
||||
export const ShapeFill = React.memo(function ShapeFill({ d, color, fill }: ShapeFillProps) {
|
||||
|
@ -42,7 +42,7 @@ const PatternFill = function PatternFill({ d, color }: ShapeFillProps) {
|
|||
const editor = useEditor()
|
||||
const theme = useDefaultColorTheme()
|
||||
const zoomLevel = useValue('zoomLevel', () => editor.zoomLevel, [editor])
|
||||
const isDarkMode = useValue('isDarkMode', () => editor.isDarkMode, [editor])
|
||||
const isDarkMode = useValue('isDarkMode', () => editor.user.isDarkMode, [editor])
|
||||
|
||||
const intZoom = Math.ceil(zoomLevel)
|
||||
const teenyTiny = editor.zoomLevel <= 0.18
|
||||
|
|
|
@ -184,7 +184,7 @@ const getDefaultPatterns = () => {
|
|||
|
||||
function usePattern() {
|
||||
const editor = useEditor()
|
||||
const dpr = editor.devicePixelRatio
|
||||
const dpr = editor.instanceState.devicePixelRatio
|
||||
const [isReady, setIsReady] = useState(false)
|
||||
const defaultPatterns = useMemo(() => getDefaultPatterns(), [])
|
||||
const [backgroundUrls, setBackgroundUrls] = useState<PatternDef[]>(defaultPatterns)
|
||||
|
|
|
@ -20,7 +20,10 @@ export function useEditableText<T extends Extract<TLShape, { props: { text: stri
|
|||
|
||||
const rInput = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
const isEditing = useValue('isEditing', () => editor.pageState.editingId === id, [editor, id])
|
||||
const isEditing = useValue('isEditing', () => editor.currentPageState.editingId === id, [
|
||||
editor,
|
||||
id,
|
||||
])
|
||||
|
||||
const rSkipSelectOnFocus = useRef(false)
|
||||
const rSelectionRanges = useRef<Range[] | null>()
|
||||
|
|
|
@ -77,7 +77,7 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
|
|||
props: { text, color },
|
||||
} = shape
|
||||
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
const { width, height } = this.getMinDimensions(shape)
|
||||
|
||||
const {
|
||||
|
@ -151,7 +151,7 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
|
|||
override toSvg(shape: TLTextShape, ctx: SvgExportContext) {
|
||||
ctx.addExportDef(getFontDefForExport(shape.props.font))
|
||||
|
||||
const theme = getDefaultColorTheme(this.editor)
|
||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.isDarkMode })
|
||||
const bounds = this.getBounds(shape)
|
||||
const text = shape.props.text
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ export class Idle extends StateNode {
|
|||
if (this.editor.isShapeOfType<TLTextShape>(shape, 'text')) {
|
||||
requestAnimationFrame(() => {
|
||||
this.editor.setSelectedIds([shape.id])
|
||||
this.editor.editingId = shape.id
|
||||
this.editor.setEditingId(shape.id)
|
||||
this.editor.setCurrentTool('select.editing_shape', {
|
||||
...info,
|
||||
target: 'shape',
|
||||
|
@ -56,7 +56,7 @@ export class Idle extends StateNode {
|
|||
}
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.cursor = { type: 'cross', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
|
||||
|
@ -64,7 +64,7 @@ export class Idle extends StateNode {
|
|||
const shape = this.editor.selectedShapes[0]
|
||||
if (shape && this.editor.isShapeOfType<TLGeoShape>(shape, 'geo')) {
|
||||
this.editor.setCurrentTool('select')
|
||||
this.editor.editingId = shape.id
|
||||
this.editor.setEditingId(shape.id)
|
||||
this.editor.root.current.value!.transition('editing_shape', {
|
||||
...info,
|
||||
target: 'shape',
|
||||
|
|
|
@ -86,7 +86,7 @@ export class Pointing extends StateNode {
|
|||
true
|
||||
)
|
||||
|
||||
this.editor.editingId = id
|
||||
this.editor.setEditingId(id)
|
||||
this.editor.setCurrentTool('select')
|
||||
this.editor.root.current.value?.transition('editing_shape', {})
|
||||
}
|
||||
|
|
|
@ -9,6 +9,6 @@ export class EraserTool extends StateNode {
|
|||
static override children = () => [Idle, Pointing, Erasing]
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.cursor = { type: 'cross', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,12 +60,12 @@ export class Erasing extends StateNode {
|
|||
}
|
||||
|
||||
private onScribbleUpdate = (scribble: TLScribble) => {
|
||||
this.editor.scribble = scribble
|
||||
this.editor.updateInstanceState({ scribble })
|
||||
}
|
||||
|
||||
private onScribbleComplete = () => {
|
||||
this.editor.off('tick', this.scribble.tick)
|
||||
this.editor.scribble = null
|
||||
this.editor.updateInstanceState({ scribble: null })
|
||||
}
|
||||
|
||||
override onExit = () => {
|
||||
|
@ -124,17 +124,17 @@ export class Erasing extends StateNode {
|
|||
// Remove the hit shapes, except if they're in the list of excluded shapes
|
||||
// (these excluded shapes will be any frames or groups the pointer was inside of
|
||||
// when the user started erasing)
|
||||
this.editor.erasingIds = [...erasing].filter((id) => !excludedShapeIds.has(id))
|
||||
this.editor.setErasingIds([...erasing].filter((id) => !excludedShapeIds.has(id)))
|
||||
}
|
||||
|
||||
complete() {
|
||||
this.editor.deleteShapes(this.editor.pageState.erasingIds)
|
||||
this.editor.erasingIds = []
|
||||
this.editor.deleteShapes(this.editor.currentPageState.erasingIds)
|
||||
this.editor.setErasingIds([])
|
||||
this.parent.transition('idle', {})
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.editor.erasingIds = []
|
||||
this.editor.setErasingIds([])
|
||||
this.editor.bailToMark(this.markId)
|
||||
this.parent.transition('idle', this.info)
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ export class Pointing extends StateNode {
|
|||
}
|
||||
}
|
||||
|
||||
this.editor.erasingIds = [...erasing]
|
||||
this.editor.setErasingIds([...erasing])
|
||||
}
|
||||
|
||||
override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => {
|
||||
|
@ -61,12 +61,12 @@ export class Pointing extends StateNode {
|
|||
this.editor.deleteShapes(erasingIds)
|
||||
}
|
||||
|
||||
this.editor.erasingIds = []
|
||||
this.editor.setErasingIds([])
|
||||
this.parent.transition('idle', {})
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.editor.erasingIds = []
|
||||
this.editor.setErasingIds([])
|
||||
this.parent.transition('idle', {})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ export class Idle extends StateNode {
|
|||
static override id = 'idle'
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.cursor = { type: 'grab', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'grab', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => {
|
||||
|
|
|
@ -5,7 +5,7 @@ export class Pointing extends StateNode {
|
|||
|
||||
override onEnter = () => {
|
||||
this.editor.stopCameraAnimation()
|
||||
this.editor.cursor = { type: 'grabbing', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'grabbing', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => {
|
||||
|
|
|
@ -8,6 +8,6 @@ export class LaserTool extends StateNode {
|
|||
static override children = () => [Idle, Lasering]
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.cursor = { type: 'cross', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ export class Lasering extends StateNode {
|
|||
}
|
||||
|
||||
override onExit = () => {
|
||||
this.editor.erasingIds = []
|
||||
this.editor.setErasingIds([])
|
||||
this.scribble.stop()
|
||||
}
|
||||
|
||||
|
@ -47,12 +47,12 @@ export class Lasering extends StateNode {
|
|||
}
|
||||
|
||||
private onScribbleUpdate = (scribble: TLScribble) => {
|
||||
this.editor.scribble = scribble
|
||||
this.editor.updateInstanceState({ scribble })
|
||||
}
|
||||
|
||||
private onScribbleComplete = () => {
|
||||
this.editor.off('tick', this.scribble.tick)
|
||||
this.editor.scribble = null
|
||||
this.editor.updateInstanceState({ scribble: null })
|
||||
}
|
||||
|
||||
override onCancel: TLEventHandlers['onCancel'] = () => {
|
||||
|
|
|
@ -41,8 +41,8 @@ export class SelectTool extends StateNode {
|
|||
]
|
||||
|
||||
override onExit = () => {
|
||||
if (this.editor.pageState.editingId) {
|
||||
this.editor.editingId = null
|
||||
if (this.editor.currentPageState.editingId) {
|
||||
this.editor.setEditingId(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ export class Brushing extends StateNode {
|
|||
|
||||
override onExit = () => {
|
||||
this.initialSelectedIds = []
|
||||
this.editor.brush = null
|
||||
this.editor.updateInstanceState({ brush: null })
|
||||
}
|
||||
|
||||
override onPointerMove = () => {
|
||||
|
@ -168,12 +168,12 @@ export class Brushing extends StateNode {
|
|||
}
|
||||
}
|
||||
|
||||
this.editor.brush = { ...this.brush.toJson() }
|
||||
this.editor.updateInstanceState({ brush: { ...this.brush.toJson() } })
|
||||
this.editor.setSelectedIds(Array.from(results), true)
|
||||
}
|
||||
|
||||
override onInterrupt: TLInterruptEvent = () => {
|
||||
this.editor.brush = null
|
||||
this.editor.updateInstanceState({ brush: null })
|
||||
}
|
||||
|
||||
private handleHit(
|
||||
|
|
|
@ -5,7 +5,7 @@ export class Idle extends StateNode {
|
|||
static override id = 'idle'
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
|
||||
const { onlySelectedShape } = this.editor
|
||||
|
||||
|
@ -22,7 +22,7 @@ export class Idle extends StateNode {
|
|||
}
|
||||
|
||||
override onExit: TLExitEventHandler = () => {
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
|
||||
this.editor.off('change-history', this.cleanupCroppingState)
|
||||
}
|
||||
|
|
|
@ -27,12 +27,12 @@ export class TranslatingCrop extends StateNode {
|
|||
this.snapshot = this.createSnapshot()
|
||||
|
||||
this.editor.mark(this.markId)
|
||||
this.editor.cursor = { type: 'move', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'move', rotation: 0 } }, true)
|
||||
this.updateShapes()
|
||||
}
|
||||
|
||||
override onExit = () => {
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onPointerMove = () => {
|
||||
|
|
|
@ -63,10 +63,12 @@ export class Cropping extends StateNode {
|
|||
if (!selectedShape) return
|
||||
|
||||
const cursorType = CursorTypeMap[this.info.handle!]
|
||||
this.editor.cursor = {
|
||||
type: cursorType,
|
||||
rotation: selectedShape.rotation,
|
||||
}
|
||||
this.editor.updateInstanceState({
|
||||
cursor: {
|
||||
type: cursorType,
|
||||
rotation: selectedShape.rotation,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private getDefaultCrop = (): TLImageShapeCrop => ({
|
||||
|
|
|
@ -56,7 +56,10 @@ export class DraggingHandle extends StateNode {
|
|||
this.initialPageTransform = this.editor.getPageTransform(shape)!
|
||||
this.initialPageRotation = this.editor.getPageRotation(shape)!
|
||||
|
||||
this.editor.cursor = { type: isCreating ? 'cross' : 'grabbing', rotation: 0 }
|
||||
this.editor.updateInstanceState(
|
||||
{ cursor: { type: isCreating ? 'cross' : 'grabbing', rotation: 0 } },
|
||||
true
|
||||
)
|
||||
|
||||
// <!-- Only relevant to arrows
|
||||
const handles = this.editor.getHandles(shape)!.sort(sortByIndex)
|
||||
|
@ -159,7 +162,7 @@ export class DraggingHandle extends StateNode {
|
|||
this.parent.currentToolIdMask = undefined
|
||||
this.editor.hintingIds = []
|
||||
this.editor.snaps.clear()
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
private complete() {
|
||||
|
@ -195,7 +198,7 @@ export class DraggingHandle extends StateNode {
|
|||
const { editor, shapeId } = this
|
||||
const { initialHandle, initialPageRotation, initialAdjacentHandle } = this
|
||||
const {
|
||||
isSnapMode,
|
||||
user: { isSnapMode },
|
||||
hintingIds,
|
||||
snaps,
|
||||
inputs: { currentPagePoint, originPagePoint, shiftKey, ctrlKey, altKey, pointerVelocity },
|
||||
|
|
|
@ -29,12 +29,12 @@ export class EditingShape extends StateNode {
|
|||
}
|
||||
|
||||
override onExit = () => {
|
||||
if (!this.editor.pageState.editingId) return
|
||||
const { editingId } = this.editor.pageState
|
||||
if (!this.editor.currentPageState.editingId) return
|
||||
const { editingId } = this.editor.currentPageState
|
||||
if (!editingId) return
|
||||
|
||||
// Clear the editing shape
|
||||
this.editor.editingId = null
|
||||
this.editor.setEditingId(null)
|
||||
|
||||
const shape = this.editor.getShapeById(editingId)!
|
||||
const util = this.editor.getShapeUtil(shape)
|
||||
|
@ -48,7 +48,7 @@ export class EditingShape extends StateNode {
|
|||
case 'shape': {
|
||||
const { shape } = info
|
||||
|
||||
const { editingId } = this.editor.pageState
|
||||
const { editingId } = this.editor.currentPageState
|
||||
|
||||
if (editingId) {
|
||||
if (shape.id === editingId) {
|
||||
|
@ -70,7 +70,7 @@ export class EditingShape extends StateNode {
|
|||
util.canEdit?.(shape) &&
|
||||
!this.editor.isShapeOrAncestorLocked(shape)
|
||||
) {
|
||||
this.editor.editingId = shape.id
|
||||
this.editor.setEditingId(shape.id)
|
||||
this.editor.hoveredId = shape.id
|
||||
this.editor.setSelectedIds([shape.id])
|
||||
return
|
||||
|
|
|
@ -38,10 +38,10 @@ export class Idle extends StateNode {
|
|||
if (hoveringShape.type !== 'geo') break
|
||||
const cursorType = (hoveringShape as TLGeoShape).props.text
|
||||
try {
|
||||
this.editor.cursor = { type: cursorType, rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: cursorType, rotation: 0 } }, true)
|
||||
} catch (e) {
|
||||
console.error(`Cursor type not recognized: '${cursorType}'`)
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ export class Idle extends StateNode {
|
|||
break
|
||||
}
|
||||
case 'handle': {
|
||||
if (this.editor.isReadOnly) break
|
||||
if (this.editor.instanceState.isReadOnly) break
|
||||
if (this.editor.inputs.altKey) {
|
||||
this.parent.transition('pointing_shape', info)
|
||||
} else {
|
||||
|
@ -142,12 +142,12 @@ export class Idle extends StateNode {
|
|||
switch (info.target) {
|
||||
case 'canvas': {
|
||||
// Create text shape and transition to editing_shape
|
||||
if (this.editor.isReadOnly) break
|
||||
if (this.editor.instanceState.isReadOnly) break
|
||||
this.handleDoubleClickOnCanvas(info)
|
||||
break
|
||||
}
|
||||
case 'selection': {
|
||||
if (this.editor.isReadOnly) break
|
||||
if (this.editor.instanceState.isReadOnly) break
|
||||
|
||||
const { onlySelectedShape } = this.editor
|
||||
if (onlySelectedShape) {
|
||||
|
@ -188,7 +188,12 @@ export class Idle extends StateNode {
|
|||
const util = this.editor.getShapeUtil(shape)
|
||||
|
||||
// Allow playing videos and embeds
|
||||
if (shape.type !== 'video' && shape.type !== 'embed' && this.editor.isReadOnly) break
|
||||
if (
|
||||
shape.type !== 'video' &&
|
||||
shape.type !== 'embed' &&
|
||||
this.editor.instanceState.isReadOnly
|
||||
)
|
||||
break
|
||||
|
||||
if (util.onDoubleClick) {
|
||||
// Call the shape's double click handler
|
||||
|
@ -216,7 +221,7 @@ export class Idle extends StateNode {
|
|||
break
|
||||
}
|
||||
case 'handle': {
|
||||
if (this.editor.isReadOnly) break
|
||||
if (this.editor.instanceState.isReadOnly) break
|
||||
const { shape, handle } = info
|
||||
|
||||
const util = this.editor.getShapeUtil(shape)
|
||||
|
@ -242,12 +247,12 @@ export class Idle extends StateNode {
|
|||
break
|
||||
}
|
||||
case 'shape': {
|
||||
const { selectedIds } = this.editor.pageState
|
||||
const { selectedIds } = this.editor.currentPageState
|
||||
const { shape } = info
|
||||
|
||||
const targetShape = this.editor.getOutermostSelectableShape(
|
||||
shape,
|
||||
(parent) => !this.editor.isSelected(parent.id)
|
||||
(parent) => !selectedIds.includes(parent.id)
|
||||
)
|
||||
|
||||
if (!selectedIds.includes(targetShape.id)) {
|
||||
|
@ -261,7 +266,7 @@ export class Idle extends StateNode {
|
|||
|
||||
override onEnter = () => {
|
||||
this.editor.hoveredId = null
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
this.parent.currentToolIdMask = undefined
|
||||
}
|
||||
|
||||
|
@ -302,7 +307,7 @@ export class Idle extends StateNode {
|
|||
}
|
||||
|
||||
override onKeyUp = (info: TLKeyboardEventInfo) => {
|
||||
if (this.editor.isReadOnly) {
|
||||
if (this.editor.instanceState.isReadOnly) {
|
||||
switch (info.code) {
|
||||
case 'Enter': {
|
||||
if (this.shouldStartEditingShape() && this.editor.onlySelectedShape) {
|
||||
|
@ -376,7 +381,7 @@ export class Idle extends StateNode {
|
|||
private startEditingShape(shape: TLShape, info: TLClickEventInfo | TLKeyboardEventInfo) {
|
||||
if (this.editor.isShapeOrAncestorLocked(shape) && shape.type !== 'embed') return
|
||||
this.editor.mark('editing shape')
|
||||
this.editor.editingId = shape.id
|
||||
this.editor.setEditingId(shape.id)
|
||||
this.parent.transition('editing_shape', info)
|
||||
}
|
||||
|
||||
|
@ -416,7 +421,7 @@ export class Idle extends StateNode {
|
|||
},
|
||||
])
|
||||
|
||||
this.editor.editingId = id
|
||||
this.editor.setEditingId(id)
|
||||
this.editor.select(id)
|
||||
this.parent.transition('editing_shape', info)
|
||||
}
|
||||
|
|
|
@ -14,10 +14,12 @@ export class PointingCropHandle extends StateNode {
|
|||
|
||||
private updateCursor(shape: TLShape) {
|
||||
const cursorType = CursorTypeMap[this.info.handle!]
|
||||
this.editor.cursor = {
|
||||
type: cursorType,
|
||||
rotation: shape.rotation,
|
||||
}
|
||||
this.editor.updateInstanceState({
|
||||
cursor: {
|
||||
type: cursorType,
|
||||
rotation: shape.rotation,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
override onEnter = (info: TLPointingCropHandleInfo) => {
|
||||
|
@ -31,7 +33,7 @@ export class PointingCropHandle extends StateNode {
|
|||
}
|
||||
|
||||
override onExit = () => {
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
this.parent.currentToolIdMask = undefined
|
||||
}
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@ export class PointingHandle extends StateNode {
|
|||
this.editor.hintingIds = [initialTerminal.boundShapeId]
|
||||
}
|
||||
|
||||
this.editor.cursor = { type: 'grabbing', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'grabbing', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onExit = () => {
|
||||
this.editor.hintingIds = []
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onPointerUp: TLEventHandlers['onPointerUp'] = () => {
|
||||
|
|
|
@ -34,10 +34,9 @@ export class PointingResizeHandle extends StateNode {
|
|||
private updateCursor() {
|
||||
const selected = this.editor.selectedShapes
|
||||
const cursorType = CursorTypeMap[this.info.handle!]
|
||||
this.editor.cursor = {
|
||||
type: cursorType,
|
||||
rotation: selected.length === 1 ? selected[0].rotation : 0,
|
||||
}
|
||||
this.editor.updateInstanceState({
|
||||
cursor: { type: cursorType, rotation: selected.length === 1 ? selected[0].rotation : 0 },
|
||||
})
|
||||
}
|
||||
|
||||
override onEnter = (info: PointingResizeHandleInfo) => {
|
||||
|
|
|
@ -12,10 +12,12 @@ export class PointingRotateHandle extends StateNode {
|
|||
|
||||
private updateCursor() {
|
||||
const { selectionRotation } = this.editor
|
||||
this.editor.cursor = {
|
||||
type: CursorTypeMap[this.info.handle as RotateCorner],
|
||||
rotation: selectionRotation,
|
||||
}
|
||||
this.editor.updateInstanceState({
|
||||
cursor: {
|
||||
type: CursorTypeMap[this.info.handle as RotateCorner],
|
||||
rotation: selectionRotation,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
override onEnter = (info: PointingRotateHandleInfo) => {
|
||||
|
@ -26,7 +28,7 @@ export class PointingRotateHandle extends StateNode {
|
|||
|
||||
override onExit = () => {
|
||||
this.parent.currentToolIdMask = undefined
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
override onPointerMove = () => {
|
||||
|
|
|
@ -18,7 +18,7 @@ export class PointingSelection extends StateNode {
|
|||
|
||||
override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => {
|
||||
if (this.editor.inputs.isDragging) {
|
||||
if (this.editor.isReadOnly) return
|
||||
if (this.editor.instanceState.isReadOnly) return
|
||||
this.parent.transition('translating', info)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ export class PointingShape extends StateNode {
|
|||
}
|
||||
|
||||
const isWithinSelection =
|
||||
this.editor.isSelected(this.selectingShape.id) ||
|
||||
this.editor.selectedIds.includes(this.selectingShape.id) ||
|
||||
this.editor.isAncestorSelected(this.selectingShape.id)
|
||||
|
||||
const isBehindSelectionBounds =
|
||||
|
@ -79,11 +79,12 @@ export class PointingShape extends StateNode {
|
|||
// if the shape has an ancestor which is a focusable layer and it is not focused but it is selected
|
||||
// then we should focus the layer and select the shape
|
||||
|
||||
const { selectedIds } = this.editor
|
||||
const targetShape = this.editor.getOutermostSelectableShape(
|
||||
this.eventTargetShape,
|
||||
// if a group is selected, we want to stop before reaching that group
|
||||
// so we can drill down into the group
|
||||
(parent) => !this.editor.isSelected(parent.id)
|
||||
(parent) => !selectedIds.includes(parent.id)
|
||||
)
|
||||
|
||||
if (this.editor.selectedIds.includes(targetShape.id)) {
|
||||
|
@ -120,7 +121,7 @@ export class PointingShape extends StateNode {
|
|||
|
||||
override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => {
|
||||
if (this.editor.inputs.isDragging) {
|
||||
if (this.editor.isReadOnly) return
|
||||
if (this.editor.instanceState.isReadOnly) return
|
||||
this.parent.transition('translating', info)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ export class Resizing extends StateNode {
|
|||
this.creationCursorOffset = creationCursorOffset
|
||||
|
||||
if (info.isCreating) {
|
||||
this.editor.cursor = { type: 'cross', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'cross', rotation: 0 } }, true)
|
||||
}
|
||||
|
||||
this.snapshot = this._createSnapshot()
|
||||
|
@ -103,7 +103,7 @@ export class Resizing extends StateNode {
|
|||
this.handleResizeEnd()
|
||||
|
||||
if (this.editAfterComplete && this.editor.onlySelectedShape) {
|
||||
this.editor.editingId = this.editor.onlySelectedShape.id
|
||||
this.editor.setEditingId(this.editor.onlySelectedShape.id)
|
||||
this.editor.setCurrentTool('select')
|
||||
this.editor.root.current.value!.transition('editing_shape', {})
|
||||
return
|
||||
|
@ -208,7 +208,7 @@ export class Resizing extends StateNode {
|
|||
.sub(this.creationCursorOffset)
|
||||
const originPagePoint = this.editor.inputs.originPagePoint.clone().sub(cursorHandleOffset)
|
||||
|
||||
if (this.editor.isGridMode && !ctrlKey) {
|
||||
if (this.editor.instanceState.isGridMode && !ctrlKey) {
|
||||
const { gridSize } = this.editor.documentSettings
|
||||
currentPagePoint.snapToGrid(gridSize)
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ export class Resizing extends StateNode {
|
|||
|
||||
this.editor.snaps.clear()
|
||||
|
||||
const shouldSnap = this.editor.isSnapMode ? !ctrlKey : ctrlKey
|
||||
const shouldSnap = this.editor.user.isSnapMode ? !ctrlKey : ctrlKey
|
||||
|
||||
if (shouldSnap && selectionRotation % TAU === 0) {
|
||||
const { nudge } = this.editor.snaps.snapResize({
|
||||
|
@ -320,7 +320,7 @@ export class Resizing extends StateNode {
|
|||
isFlippedY: boolean
|
||||
rotation: number
|
||||
}) {
|
||||
const nextCursor = { ...this.editor.cursor }
|
||||
const nextCursor = { ...this.editor.instanceState.cursor }
|
||||
|
||||
switch (dragHandle) {
|
||||
case 'top_left':
|
||||
|
@ -343,12 +343,12 @@ export class Resizing extends StateNode {
|
|||
|
||||
nextCursor.rotation = rotation
|
||||
|
||||
this.editor.cursor = nextCursor
|
||||
this.editor.updateInstanceState({ cursor: nextCursor })
|
||||
}
|
||||
|
||||
override onExit = () => {
|
||||
this.parent.currentToolIdMask = undefined
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
this.editor.snaps.clear()
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ export class Rotating extends StateNode {
|
|||
}
|
||||
|
||||
override onExit = () => {
|
||||
this.editor.cursor = { type: 'none', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'none', rotation: 0 } }, true)
|
||||
this.parent.currentToolIdMask = undefined
|
||||
|
||||
this.snapshot = {} as TLRotationSnapshot
|
||||
|
@ -85,10 +85,12 @@ export class Rotating extends StateNode {
|
|||
})
|
||||
|
||||
// Update cursor
|
||||
this.editor.cursor = {
|
||||
type: CursorTypeMap[this.info.handle as RotateCorner],
|
||||
rotation: newSelectionRotation + this.snapshot.initialSelectionRotation,
|
||||
}
|
||||
this.editor.updateInstanceState({
|
||||
cursor: {
|
||||
type: CursorTypeMap[this.info.handle as RotateCorner],
|
||||
rotation: newSelectionRotation + this.snapshot.initialSelectionRotation,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private cancel = () => {
|
||||
|
@ -127,10 +129,12 @@ export class Rotating extends StateNode {
|
|||
})
|
||||
|
||||
// Update cursor
|
||||
this.editor.cursor = {
|
||||
type: CursorTypeMap[this.info.handle as RotateCorner],
|
||||
rotation: newSelectionRotation + this.snapshot.initialSelectionRotation,
|
||||
}
|
||||
this.editor.updateInstanceState({
|
||||
cursor: {
|
||||
type: CursorTypeMap[this.info.handle as RotateCorner],
|
||||
rotation: newSelectionRotation + this.snapshot.initialSelectionRotation,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
_getRotationFromPointerPosition({ snapToNearestDegree }: { snapToNearestDegree: boolean }) {
|
||||
|
@ -150,7 +154,7 @@ export class Rotating extends StateNode {
|
|||
} else if (snapToNearestDegree) {
|
||||
newSelectionRotation = Math.round(newSelectionRotation / EPSILON) * EPSILON
|
||||
|
||||
if (this.editor.isCoarsePointer) {
|
||||
if (this.editor.instanceState.isCoarsePointer) {
|
||||
const snappedToRightAngle = snapAngle(newSelectionRotation, 4)
|
||||
const angleToRightAngle = angleDelta(newSelectionRotation, snappedToRightAngle)
|
||||
if (Math.abs(angleToRightAngle) < degreesToRadians(5)) {
|
||||
|
|
|
@ -36,12 +36,12 @@ export class ScribbleBrushing extends StateNode {
|
|||
|
||||
this.updateBrushSelection()
|
||||
requestAnimationFrame(() => {
|
||||
this.editor.brush = null
|
||||
this.editor.updateInstanceState({ brush: null })
|
||||
})
|
||||
}
|
||||
|
||||
override onExit = () => {
|
||||
this.editor.erasingIds = []
|
||||
this.editor.setErasingIds([])
|
||||
this.scribble.stop()
|
||||
}
|
||||
|
||||
|
@ -87,12 +87,12 @@ export class ScribbleBrushing extends StateNode {
|
|||
}
|
||||
|
||||
private onScribbleUpdate = (scribble: TLScribble) => {
|
||||
this.editor.scribble = scribble
|
||||
this.editor.updateInstanceState({ scribble })
|
||||
}
|
||||
|
||||
private onScribbleComplete = () => {
|
||||
this.editor.off('tick', this.scribble.tick)
|
||||
this.editor.scribble = null
|
||||
this.editor.updateInstanceState({ scribble: null })
|
||||
}
|
||||
|
||||
private updateBrushSelection() {
|
||||
|
|
|
@ -64,7 +64,7 @@ export class Translating extends StateNode {
|
|||
this.selectionSnapshot = {} as any
|
||||
this.snapshot = {} as any
|
||||
this.editor.snaps.clear()
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'default', rotation: 0 } }, true)
|
||||
this.dragAndDropManager.clear()
|
||||
}
|
||||
|
||||
|
@ -146,7 +146,7 @@ export class Translating extends StateNode {
|
|||
if (this.editAfterComplete) {
|
||||
const onlySelected = this.editor.onlySelectedShape
|
||||
if (onlySelected) {
|
||||
this.editor.editingId = onlySelected.id
|
||||
this.editor.setEditingId(onlySelected.id)
|
||||
this.editor.setCurrentTool('select')
|
||||
this.editor.root.current.value!.transition('editing_shape', {})
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ export class Translating extends StateNode {
|
|||
this.isCloning = false
|
||||
this.info = info
|
||||
|
||||
this.editor.cursor = { type: 'move', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'move', rotation: 0 } }, true)
|
||||
this.selectionSnapshot = getTranslatingSnapshot(this.editor)
|
||||
|
||||
// Don't clone on create; otherwise clone on altKey
|
||||
|
@ -344,7 +344,7 @@ export function moveShapesToPoint({
|
|||
}) {
|
||||
const {
|
||||
inputs,
|
||||
isGridMode,
|
||||
instanceState: { isGridMode },
|
||||
documentSettings: { gridSize },
|
||||
} = editor
|
||||
|
||||
|
@ -366,7 +366,7 @@ export function moveShapesToPoint({
|
|||
editor.snaps.clear()
|
||||
|
||||
const shouldSnap =
|
||||
(editor.isSnapMode ? !inputs.ctrlKey : inputs.ctrlKey) &&
|
||||
(editor.user.isSnapMode ? !inputs.ctrlKey : inputs.ctrlKey) &&
|
||||
editor.inputs.pointerVelocity.len() < 0.5 // ...and if the user is not dragging fast
|
||||
|
||||
if (shouldSnap) {
|
||||
|
|
|
@ -18,8 +18,10 @@ export class ZoomTool extends StateNode {
|
|||
|
||||
override onExit = () => {
|
||||
this.currentToolIdMask = undefined
|
||||
this.editor.zoomBrush = null
|
||||
this.editor.cursor = { type: 'default', rotation: 0 }
|
||||
this.editor.updateInstanceState(
|
||||
{ zoomBrush: null, cursor: { type: 'default', rotation: 0 } },
|
||||
true
|
||||
)
|
||||
this.currentToolIdMask = undefined
|
||||
}
|
||||
|
||||
|
@ -50,9 +52,9 @@ export class ZoomTool extends StateNode {
|
|||
|
||||
private updateCursor() {
|
||||
if (this.editor.inputs.altKey) {
|
||||
this.editor.cursor = { type: 'zoom-out', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'zoom-out', rotation: 0 } }, true)
|
||||
} else {
|
||||
this.editor.cursor = { type: 'zoom-in', rotation: 0 }
|
||||
this.editor.updateInstanceState({ cursor: { type: 'zoom-in', rotation: 0 } }, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ export class ZoomBrushing extends StateNode {
|
|||
}
|
||||
|
||||
override onExit = () => {
|
||||
this.editor.zoomBrush = null
|
||||
this.editor.updateInstanceState({ zoomBrush: null })
|
||||
}
|
||||
|
||||
override onPointerMove = () => {
|
||||
|
@ -34,7 +34,7 @@ export class ZoomBrushing extends StateNode {
|
|||
} = this.editor
|
||||
|
||||
this.zoomBrush.setTo(Box2d.FromPoints([originPagePoint, currentPagePoint]))
|
||||
this.editor.zoomBrush = this.zoomBrush.toJson()
|
||||
this.editor.updateInstanceState({ zoomBrush: this.zoomBrush.toJson() })
|
||||
}
|
||||
|
||||
private cancel() {
|
||||
|
|
|
@ -119,7 +119,7 @@ const TldrawUiContent = React.memo(function TldrawUI({
|
|||
const editor = useEditor()
|
||||
const msg = useTranslation()
|
||||
const breakpoint = useBreakpoint()
|
||||
const isReadonlyMode = useValue('isReadOnlyMode', () => editor.isReadOnly, [editor])
|
||||
const isReadonlyMode = useValue('isReadOnlyMode', () => editor.instanceState.isReadOnly, [editor])
|
||||
const isFocusMode = useValue('focus', () => editor.instanceState.isFocusMode, [editor])
|
||||
const isDebugMode = useValue('debug', () => editor.instanceState.isDebugMode, [editor])
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useContainer } from '@tldraw/editor'
|
|||
import { memo } from 'react'
|
||||
import { TLUiMenuChild } from '../hooks/menuHelpers'
|
||||
import { useActionsMenuSchema } from '../hooks/useActionsMenuSchema'
|
||||
import { useReadonly } from '../hooks/useReadonly'
|
||||
import { useReadOnly } from '../hooks/useReadOnly'
|
||||
import { useTranslation } from '../hooks/useTranslation/useTranslation'
|
||||
import { Button } from './primitives/Button'
|
||||
import { Popover, PopoverTrigger } from './primitives/Popover'
|
||||
|
@ -13,7 +13,7 @@ export const ActionsMenu = memo(function ActionsMenu() {
|
|||
const msg = useTranslation()
|
||||
const container = useContainer()
|
||||
const menuSchema = useActionsMenuSchema()
|
||||
const isReadonly = useReadonly()
|
||||
const isReadonly = useReadOnly()
|
||||
|
||||
function getActionMenuItem(item: TLUiMenuChild) {
|
||||
if (isReadonly && !item.readonlyOk) return null
|
||||
|
|
|
@ -6,7 +6,7 @@ import { TLUiMenuChild } from '../hooks/menuHelpers'
|
|||
import { useBreakpoint } from '../hooks/useBreakpoint'
|
||||
import { useContextMenuSchema } from '../hooks/useContextMenuSchema'
|
||||
import { useMenuIsOpen } from '../hooks/useMenuIsOpen'
|
||||
import { useReadonly } from '../hooks/useReadonly'
|
||||
import { useReadOnly } from '../hooks/useReadOnly'
|
||||
import { useTranslation } from '../hooks/useTranslation/useTranslation'
|
||||
import { MoveToPageMenu } from './MoveToPageMenu'
|
||||
import { Button } from './primitives/Button'
|
||||
|
@ -34,7 +34,7 @@ export const ContextMenu = function ContextMenu({ children }: { children: any })
|
|||
}
|
||||
} else {
|
||||
// Weird route: selecting locked shapes on long press
|
||||
if (editor.isCoarsePointer) {
|
||||
if (editor.instanceState.isCoarsePointer) {
|
||||
const {
|
||||
selectedShapes,
|
||||
inputs: { currentPagePoint },
|
||||
|
@ -66,7 +66,7 @@ export const ContextMenu = function ContextMenu({ children }: { children: any })
|
|||
const [_, handleOpenChange] = useMenuIsOpen('context menu', cb)
|
||||
|
||||
// If every item in the menu is readonly, then we don't want to show the menu
|
||||
const isReadonly = useReadonly()
|
||||
const isReadonly = useReadOnly()
|
||||
|
||||
const noItemsToShow =
|
||||
contextTLUiMenuSchema.length === 0 ||
|
||||
|
@ -98,7 +98,7 @@ function ContextMenuContent() {
|
|||
const menuSchema = useContextMenuSchema()
|
||||
const [_, handleSubOpenChange] = useMenuIsOpen('context menu sub')
|
||||
|
||||
const isReadonly = useReadonly()
|
||||
const isReadonly = useReadOnly()
|
||||
const breakpoint = useBreakpoint()
|
||||
const container = useContainer()
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue