mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 04:48:53 +00:00
init: the first public commit for AFFiNE
This commit is contained in:
45
libs/components/board-tools/src/arrow-tool/arrow-tool.ts
Normal file
45
libs/components/board-tools/src/arrow-tool/arrow-tool.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Utils, TLPointerEventHandler } from '@tldraw/core';
|
||||
import Vec from '@tldraw/vec';
|
||||
import { Arrow } from '@toeverything/components/board-shapes';
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool, BaseToolStatus } from '@toeverything/components/board-state';
|
||||
import { services } from '@toeverything/datasource/db-service';
|
||||
|
||||
export class ArrowTool extends BaseTool {
|
||||
override type = TDShapeType.Arrow as const;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== BaseToolStatus.Idle) return;
|
||||
|
||||
const {
|
||||
currentPoint,
|
||||
currentGrid,
|
||||
settings: { showGrid },
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const newShape = Arrow.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: showGrid
|
||||
? Vec.snap(currentPoint, currentGrid)
|
||||
: currentPoint,
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
|
||||
this.app.patchCreate([newShape]);
|
||||
|
||||
this.app.startSession(SessionType.Arrow, newShape.id, 'end', true);
|
||||
|
||||
this.set_status(BaseToolStatus.Creating);
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/arrow-tool/index.ts
Normal file
1
libs/components/board-tools/src/arrow-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './arrow-tool';
|
||||
86
libs/components/board-tools/src/draw-tool/draw-tool.ts
Normal file
86
libs/components/board-tools/src/draw-tool/draw-tool.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Utils, TLPointerEventHandler } from '@tldraw/core';
|
||||
import { Draw } from '@toeverything/components/board-shapes';
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool } from '@toeverything/components/board-state';
|
||||
|
||||
enum Status {
|
||||
Idle = 'idle',
|
||||
Creating = 'creating',
|
||||
Extending = 'extending',
|
||||
Pinching = 'pinching',
|
||||
}
|
||||
|
||||
export class DrawTool extends BaseTool {
|
||||
override type = TDShapeType.Draw as const;
|
||||
|
||||
private last_shape_id?: string;
|
||||
|
||||
override onEnter = () => {
|
||||
this.last_shape_id = undefined;
|
||||
};
|
||||
|
||||
override onCancel = () => {
|
||||
switch (this.status) {
|
||||
case Status.Idle: {
|
||||
this.app.selectTool('select');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this.set_status(Status.Idle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.app.cancelSession();
|
||||
};
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = info => {
|
||||
if (this.status !== Status.Idle) return;
|
||||
const {
|
||||
currentPoint,
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
const previous =
|
||||
this.last_shape_id && this.app.getShape(this.last_shape_id);
|
||||
if (info.shiftKey && previous) {
|
||||
// Extend the previous shape
|
||||
this.app.startSession(SessionType.Draw, previous.id);
|
||||
this.set_status(Status.Extending);
|
||||
} else {
|
||||
// Create a new shape
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const newShape = Draw.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: currentPoint,
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
this.last_shape_id = id;
|
||||
this.app.patchCreate([newShape]);
|
||||
this.app.startSession(SessionType.Draw, id);
|
||||
this.set_status(Status.Creating);
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerMove: TLPointerEventHandler = () => {
|
||||
switch (this.status) {
|
||||
case Status.Extending:
|
||||
case Status.Creating: {
|
||||
this.app.updateSession();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerUp: TLPointerEventHandler = () => {
|
||||
this.app.completeSession();
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/draw-tool/index.ts
Normal file
1
libs/components/board-tools/src/draw-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './draw-tool';
|
||||
61
libs/components/board-tools/src/editor-tool/editor-tool.ts
Normal file
61
libs/components/board-tools/src/editor-tool/editor-tool.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import { TLPointerEventHandler } from '@tldraw/core';
|
||||
import Vec from '@tldraw/vec';
|
||||
import { Editor } from '@toeverything/components/board-shapes';
|
||||
import { TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool, BaseToolStatus } from '@toeverything/components/board-state';
|
||||
import { services } from '@toeverything/datasource/db-service';
|
||||
|
||||
export class EditorTool extends BaseTool {
|
||||
override type = TDShapeType.Editor as const;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
// It is not allowed to drag and drop the size when it is created, it is directly treated as a click event, and the Group is created asynchronously
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== BaseToolStatus.Idle) return;
|
||||
|
||||
const {
|
||||
currentPoint,
|
||||
currentGrid,
|
||||
settings: { showGrid },
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
|
||||
const createGroup = async () => {
|
||||
this.set_status(BaseToolStatus.Creating);
|
||||
const group = await services.api.editorBlock.create({
|
||||
workspace,
|
||||
type: 'group',
|
||||
parentId: currentPageId,
|
||||
});
|
||||
await services.api.editorBlock.create({
|
||||
workspace,
|
||||
type: 'text',
|
||||
parentId: group.id,
|
||||
});
|
||||
const newShape = Editor.create({
|
||||
id: group.id,
|
||||
rootBlockId: group.id,
|
||||
affineId: group.id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: showGrid
|
||||
? Vec.snap(currentPoint, currentGrid)
|
||||
: currentPoint,
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
// In order to make the cursor just positioned at the beginning of the first line, it needs to be adjusted according to the padding newShape.point = Vec.sub(newShape.point, [50, 30]);
|
||||
this.app.patchCreate([newShape]);
|
||||
this.app.select(group.id);
|
||||
this.app.setEditingId(group.id);
|
||||
this.set_status(BaseToolStatus.Idle);
|
||||
this.app.selectTool('select');
|
||||
};
|
||||
createGroup();
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/editor-tool/index.ts
Normal file
1
libs/components/board-tools/src/editor-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './editor-tool';
|
||||
49
libs/components/board-tools/src/ellipse-tool/ellipse-tool.ts
Normal file
49
libs/components/board-tools/src/ellipse-tool/ellipse-tool.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Utils, TLPointerEventHandler, TLBoundsCorner } from '@tldraw/core';
|
||||
import Vec from '@tldraw/vec';
|
||||
import { Ellipse } from '@toeverything/components/board-shapes';
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool, BaseToolStatus } from '@toeverything/components/board-state';
|
||||
|
||||
export class EllipseTool extends BaseTool {
|
||||
override type = TDShapeType.Ellipse as const;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== BaseToolStatus.Idle) return;
|
||||
|
||||
const {
|
||||
currentPoint,
|
||||
currentGrid,
|
||||
settings: { showGrid },
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const newShape = Ellipse.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: showGrid
|
||||
? Vec.snap(currentPoint, currentGrid)
|
||||
: currentPoint,
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
|
||||
this.app.patchCreate([newShape]);
|
||||
|
||||
this.app.startSession(
|
||||
SessionType.TransformSingle,
|
||||
newShape.id,
|
||||
TLBoundsCorner.BottomRight,
|
||||
true
|
||||
);
|
||||
|
||||
this.set_status(BaseToolStatus.Creating);
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/ellipse-tool/index.ts
Normal file
1
libs/components/board-tools/src/ellipse-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './ellipse-tool';
|
||||
91
libs/components/board-tools/src/erase-tool/erase-tool.ts
Normal file
91
libs/components/board-tools/src/erase-tool/erase-tool.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import Vec from '@tldraw/vec';
|
||||
import type { TLPointerEventHandler } from '@tldraw/core';
|
||||
import { SessionType, DEAD_ZONE } from '@toeverything/components/board-types';
|
||||
import { BaseTool } from '@toeverything/components/board-state';
|
||||
|
||||
enum Status {
|
||||
Idle = 'idle',
|
||||
Pointing = 'pointing',
|
||||
Erasing = 'erasing',
|
||||
}
|
||||
|
||||
export class EraseTool extends BaseTool {
|
||||
override type = 'erase' as const;
|
||||
|
||||
override status: Status = Status.Idle;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== Status.Idle) return;
|
||||
|
||||
this.set_status(Status.Pointing);
|
||||
};
|
||||
|
||||
override onPointerMove: TLPointerEventHandler = info => {
|
||||
switch (this.status) {
|
||||
case Status.Pointing: {
|
||||
if (Vec.dist(info.origin, info.point) > DEAD_ZONE) {
|
||||
this.app.startSession(SessionType.Erase);
|
||||
this.app.updateSession();
|
||||
this.set_status(Status.Erasing);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Status.Erasing: {
|
||||
this.app.updateSession();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerUp: TLPointerEventHandler = () => {
|
||||
switch (this.status) {
|
||||
case Status.Pointing: {
|
||||
const shapeIdsAtPoint = this.app.shapes
|
||||
.filter(shape => !shape.isLocked)
|
||||
.filter(shape =>
|
||||
this.app
|
||||
.getShapeUtil(shape)
|
||||
.hitTestPoint(shape, this.app.currentPoint)
|
||||
)
|
||||
.flatMap(shape =>
|
||||
shape.children
|
||||
? [shape.id, ...shape.children]
|
||||
: shape.id
|
||||
);
|
||||
|
||||
this.app.delete(shapeIdsAtPoint);
|
||||
|
||||
break;
|
||||
}
|
||||
case Status.Erasing: {
|
||||
this.app.completeSession();
|
||||
|
||||
// Should the app go back to the previous state, the select
|
||||
// state, or stay in the eraser state?
|
||||
|
||||
// if (this.previous) {
|
||||
// this.app.selectTool(this.previous)
|
||||
// } else {
|
||||
// this.app.selectTool('select')
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
|
||||
override onCancel = () => {
|
||||
if (this.status === Status.Idle) {
|
||||
if (this.previous) {
|
||||
this.app.selectTool(this.previous);
|
||||
} else {
|
||||
this.app.selectTool('select');
|
||||
}
|
||||
} else {
|
||||
this.set_status(Status.Idle);
|
||||
}
|
||||
|
||||
this.app.cancelSession();
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/erase-tool/index.ts
Normal file
1
libs/components/board-tools/src/erase-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './erase-tool';
|
||||
52
libs/components/board-tools/src/frame-tool/frame-tool.ts
Normal file
52
libs/components/board-tools/src/frame-tool/frame-tool.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
Utils,
|
||||
TLPointerEventHandler,
|
||||
TLBoundsCorner,
|
||||
TLBoundsHandleEventHandler,
|
||||
} from '@tldraw/core';
|
||||
import Vec from '@tldraw/vec';
|
||||
import { Frame } from '@toeverything/components/board-shapes';
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool, BaseToolStatus } from '@toeverything/components/board-state';
|
||||
|
||||
export class FrameTool extends BaseTool {
|
||||
override type = TDShapeType.Frame as const;
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== BaseToolStatus.Idle) return;
|
||||
const {
|
||||
currentPoint,
|
||||
currentGrid,
|
||||
settings: { showGrid },
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
|
||||
const childIndex = 0;
|
||||
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const newShape = Frame.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: showGrid
|
||||
? Vec.snap(currentPoint, currentGrid)
|
||||
: currentPoint,
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
|
||||
this.app.patchCreate([newShape]);
|
||||
|
||||
this.app.startSession(
|
||||
SessionType.TransformSingle,
|
||||
newShape.id,
|
||||
TLBoundsCorner.BottomRight,
|
||||
true
|
||||
);
|
||||
|
||||
this.set_status(BaseToolStatus.Creating);
|
||||
};
|
||||
}
|
||||
0
libs/components/board-tools/src/frame-tool/index.ts
Normal file
0
libs/components/board-tools/src/frame-tool/index.ts
Normal file
51
libs/components/board-tools/src/hand-draw/hand-draw-tool.ts
Normal file
51
libs/components/board-tools/src/hand-draw/hand-draw-tool.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { TLPointerEventHandler } from '@tldraw/core';
|
||||
// import { Draw } from '@toeverything/components/board-shapes';
|
||||
import { Vec } from '@tldraw/vec';
|
||||
|
||||
import { TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool } from '@toeverything/components/board-state';
|
||||
|
||||
enum Status {
|
||||
Idle = 'idle',
|
||||
Pointing = 'pointing',
|
||||
Draw = 'draw',
|
||||
}
|
||||
|
||||
export class HandDrawTool extends BaseTool {
|
||||
override type = TDShapeType.HandDraw as const;
|
||||
|
||||
override status: Status = Status.Idle;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.app.readOnly) return;
|
||||
if (this.status !== Status.Idle) return;
|
||||
|
||||
this.set_status(Status.Pointing);
|
||||
};
|
||||
|
||||
override onPointerMove: TLPointerEventHandler = (info, e) => {
|
||||
if (this.app.readOnly) return;
|
||||
const delta = Vec.div(info.delta, this.app.camera.zoom);
|
||||
const prev = this.app.camera.point;
|
||||
const next = Vec.sub(prev, delta);
|
||||
if (Vec.isEqual(next, prev)) return;
|
||||
|
||||
switch (this.status) {
|
||||
case Status.Pointing: {
|
||||
this.app.pan(Vec.neg(delta));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerUp: TLPointerEventHandler = () => {
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
|
||||
override onCancel = () => {
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/hand-draw/index.ts
Normal file
1
libs/components/board-tools/src/hand-draw/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './hand-draw-tool';
|
||||
49
libs/components/board-tools/src/hexagon-tool/hexagon-tool.ts
Normal file
49
libs/components/board-tools/src/hexagon-tool/hexagon-tool.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Utils, TLPointerEventHandler, TLBoundsCorner } from '@tldraw/core';
|
||||
import Vec from '@tldraw/vec';
|
||||
import { Hexagon } from '@toeverything/components/board-shapes';
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool, BaseToolStatus } from '@toeverything/components/board-state';
|
||||
|
||||
export class HexagonTool extends BaseTool {
|
||||
override type = TDShapeType.Triangle as const;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== BaseToolStatus.Idle) return;
|
||||
|
||||
const {
|
||||
currentPoint,
|
||||
currentGrid,
|
||||
settings: { showGrid },
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const newShape = Hexagon.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: showGrid
|
||||
? Vec.snap(currentPoint, currentGrid)
|
||||
: currentPoint,
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
|
||||
this.app.patchCreate([newShape]);
|
||||
|
||||
this.app.startSession(
|
||||
SessionType.TransformSingle,
|
||||
newShape.id,
|
||||
TLBoundsCorner.BottomRight,
|
||||
true
|
||||
);
|
||||
|
||||
this.set_status(BaseToolStatus.Creating);
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/hexagon-tool/index.ts
Normal file
1
libs/components/board-tools/src/hexagon-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './hexagon-tool';
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Utils, TLPointerEventHandler } from '@tldraw/core';
|
||||
import { Draw } from '@toeverything/components/board-shapes';
|
||||
import {
|
||||
SessionType,
|
||||
TDShapeType,
|
||||
DashStyle,
|
||||
StrokeWidth,
|
||||
} from '@toeverything/components/board-types';
|
||||
import { BaseTool } from '@toeverything/components/board-state';
|
||||
|
||||
enum Status {
|
||||
Idle = 'idle',
|
||||
Creating = 'creating',
|
||||
Extending = 'extending',
|
||||
Pinching = 'pinching',
|
||||
}
|
||||
|
||||
export class HighlightTool extends BaseTool {
|
||||
override type = TDShapeType.Highlight as const;
|
||||
|
||||
private last_shape_id?: string;
|
||||
|
||||
override onEnter = () => {
|
||||
this.last_shape_id = undefined;
|
||||
};
|
||||
|
||||
override onCancel = () => {
|
||||
switch (this.status) {
|
||||
case Status.Idle: {
|
||||
this.app.selectTool('select');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this.set_status(Status.Idle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.app.cancelSession();
|
||||
};
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = info => {
|
||||
if (this.status !== Status.Idle) return;
|
||||
const {
|
||||
currentPoint,
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
const previous =
|
||||
this.last_shape_id && this.app.getShape(this.last_shape_id);
|
||||
if (info.shiftKey && previous) {
|
||||
// Extend the previous shape
|
||||
this.app.startSession(SessionType.Draw, previous.id);
|
||||
this.set_status(Status.Extending);
|
||||
} else {
|
||||
// Create a new shape
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
|
||||
const newShape = Draw.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: currentPoint,
|
||||
style: {
|
||||
...currentStyle,
|
||||
strokeWidth: StrokeWidth.s4,
|
||||
dash: DashStyle.Solid,
|
||||
},
|
||||
workspace,
|
||||
});
|
||||
this.last_shape_id = id;
|
||||
this.app.patchCreate([newShape]);
|
||||
this.app.startSession(SessionType.Draw, id);
|
||||
this.set_status(Status.Creating);
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerMove: TLPointerEventHandler = () => {
|
||||
switch (this.status) {
|
||||
case Status.Extending:
|
||||
case Status.Creating: {
|
||||
this.app.updateSession();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerUp: TLPointerEventHandler = () => {
|
||||
this.app.completeSession();
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/highlight-tool/index.ts
Normal file
1
libs/components/board-tools/src/highlight-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './highlight-tool';
|
||||
64
libs/components/board-tools/src/index.ts
Normal file
64
libs/components/board-tools/src/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { TDShapeType, TDToolType } from '@toeverything/components/board-types';
|
||||
import { ArrowTool } from './arrow-tool';
|
||||
import { LineTool } from './line-tool';
|
||||
import { DrawTool } from './draw-tool';
|
||||
import { PencilTool } from './pencil-tool';
|
||||
import { HighlightTool } from './highlight-tool';
|
||||
import { EllipseTool } from './ellipse-tool';
|
||||
import { RectangleTool } from './rectangle-tool';
|
||||
import { TriangleTool } from './triangle-tool';
|
||||
import { SelectTool } from './select-tool';
|
||||
import { EraseTool } from './erase-tool';
|
||||
import { EditorTool } from './editor-tool';
|
||||
import { HexagonTool } from './hexagon-tool';
|
||||
import { PentagramTool } from './pentagram-tool';
|
||||
import { WhiteArrowTool } from './white-arrow-tool';
|
||||
import { LaserTool } from './laser-tool';
|
||||
import { HandDrawTool } from './hand-draw';
|
||||
import { FrameTool } from './frame-tool/frame-tool';
|
||||
|
||||
export interface ToolsMap {
|
||||
select: typeof SelectTool;
|
||||
erase: typeof EraseTool;
|
||||
[TDShapeType.Draw]: typeof DrawTool;
|
||||
[TDShapeType.Ellipse]: typeof EllipseTool;
|
||||
[TDShapeType.Rectangle]: typeof RectangleTool;
|
||||
[TDShapeType.Triangle]: typeof TriangleTool;
|
||||
[TDShapeType.Hexagon]: typeof HexagonTool;
|
||||
[TDShapeType.Pentagram]: typeof PentagramTool;
|
||||
[TDShapeType.Line]: typeof LineTool;
|
||||
[TDShapeType.Arrow]: typeof ArrowTool;
|
||||
[TDShapeType.Pencil]: typeof PencilTool;
|
||||
[TDShapeType.Highlight]: typeof HighlightTool;
|
||||
[TDShapeType.Editor]: typeof EditorTool;
|
||||
[TDShapeType.WhiteArrow]: typeof WhiteArrowTool;
|
||||
[TDShapeType.HandDraw]: typeof HandDrawTool;
|
||||
[TDShapeType.Laser]: typeof LaserTool;
|
||||
[TDShapeType.Frame]: typeof FrameTool;
|
||||
}
|
||||
|
||||
export type ToolOfType<K extends TDToolType> = ToolsMap[K];
|
||||
|
||||
export type ArgsOfType<K extends TDToolType> = ConstructorParameters<
|
||||
ToolOfType<K>
|
||||
>;
|
||||
|
||||
export const tools: { [K in TDToolType]: ToolsMap[K] } = {
|
||||
select: SelectTool,
|
||||
erase: EraseTool,
|
||||
[TDShapeType.Draw]: DrawTool,
|
||||
[TDShapeType.Pencil]: PencilTool,
|
||||
[TDShapeType.Highlight]: HighlightTool,
|
||||
[TDShapeType.Ellipse]: EllipseTool,
|
||||
[TDShapeType.Rectangle]: RectangleTool,
|
||||
[TDShapeType.Triangle]: TriangleTool,
|
||||
[TDShapeType.Pentagram]: PentagramTool,
|
||||
[TDShapeType.Line]: LineTool,
|
||||
[TDShapeType.Arrow]: ArrowTool,
|
||||
[TDShapeType.Editor]: EditorTool,
|
||||
[TDShapeType.Hexagon]: HexagonTool,
|
||||
[TDShapeType.WhiteArrow]: WhiteArrowTool,
|
||||
[TDShapeType.Laser]: LaserTool,
|
||||
[TDShapeType.HandDraw]: HandDrawTool,
|
||||
[TDShapeType.Frame]: FrameTool,
|
||||
};
|
||||
1
libs/components/board-tools/src/laser-tool/index.ts
Normal file
1
libs/components/board-tools/src/laser-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './laser-tool';
|
||||
69
libs/components/board-tools/src/laser-tool/laser-tool.ts
Normal file
69
libs/components/board-tools/src/laser-tool/laser-tool.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { TLPointerEventHandler } from '@tldraw/core';
|
||||
// import { Draw } from '@toeverything/components/board-shapes';
|
||||
import { Vec } from '@tldraw/vec';
|
||||
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool } from '@toeverything/components/board-state';
|
||||
|
||||
enum Status {
|
||||
Idle = 'idle',
|
||||
Pointing = 'pointing',
|
||||
Laser = 'laser',
|
||||
}
|
||||
|
||||
export class LaserTool extends BaseTool {
|
||||
override type = TDShapeType.Laser as const;
|
||||
|
||||
override status: Status = Status.Idle;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.app.readOnly) return;
|
||||
if (this.status !== Status.Idle) return;
|
||||
|
||||
this.set_status(Status.Pointing);
|
||||
};
|
||||
|
||||
override onPointerMove: TLPointerEventHandler = info => {
|
||||
if (this.app.readOnly) return;
|
||||
switch (this.status) {
|
||||
case Status.Pointing: {
|
||||
if (Vec.dist(info.origin, info.point) > 3) {
|
||||
this.app.startSession(SessionType.Laser);
|
||||
this.app.updateSession();
|
||||
this.set_status(Status.Laser);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Status.Laser: {
|
||||
this.app.updateSession();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerUp: TLPointerEventHandler = () => {
|
||||
if (this.app.readOnly) return;
|
||||
switch (this.status) {
|
||||
case Status.Laser: {
|
||||
this.app.completeSession();
|
||||
}
|
||||
}
|
||||
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
|
||||
override onCancel = () => {
|
||||
if (this.status === Status.Idle) {
|
||||
if (this.previous) {
|
||||
this.app.selectTool(this.previous);
|
||||
} else {
|
||||
this.app.selectTool('select');
|
||||
}
|
||||
} else {
|
||||
this.set_status(Status.Idle);
|
||||
}
|
||||
|
||||
this.app.cancelSession();
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/line-tool/index.ts
Normal file
1
libs/components/board-tools/src/line-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './line-tool';
|
||||
47
libs/components/board-tools/src/line-tool/line-tool.ts
Normal file
47
libs/components/board-tools/src/line-tool/line-tool.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Utils, TLPointerEventHandler } from '@tldraw/core';
|
||||
import Vec from '@tldraw/vec';
|
||||
import { Arrow } from '@toeverything/components/board-shapes';
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool, BaseToolStatus } from '@toeverything/components/board-state';
|
||||
|
||||
export class LineTool extends BaseTool {
|
||||
override type = TDShapeType.Line as const;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== BaseToolStatus.Idle) return;
|
||||
|
||||
const {
|
||||
currentPoint,
|
||||
currentGrid,
|
||||
settings: { showGrid },
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const newShape = Arrow.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: showGrid
|
||||
? Vec.snap(currentPoint, currentGrid)
|
||||
: currentPoint,
|
||||
decorations: {
|
||||
start: undefined,
|
||||
end: undefined,
|
||||
},
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
|
||||
this.app.patchCreate([newShape]);
|
||||
|
||||
this.app.startSession(SessionType.Arrow, newShape.id, 'end', true);
|
||||
|
||||
this.set_status(BaseToolStatus.Creating);
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/pencil-tool/index.ts
Normal file
1
libs/components/board-tools/src/pencil-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './pencil-tool';
|
||||
90
libs/components/board-tools/src/pencil-tool/pencil-tool.ts
Normal file
90
libs/components/board-tools/src/pencil-tool/pencil-tool.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Utils, TLPointerEventHandler } from '@tldraw/core';
|
||||
import { Draw } from '@toeverything/components/board-shapes';
|
||||
import {
|
||||
DashStyle,
|
||||
SessionType,
|
||||
TDShapeType,
|
||||
} from '@toeverything/components/board-types';
|
||||
import { BaseTool } from '@toeverything/components/board-state';
|
||||
|
||||
enum Status {
|
||||
Idle = 'idle',
|
||||
Creating = 'creating',
|
||||
Extending = 'extending',
|
||||
Pinching = 'pinching',
|
||||
}
|
||||
|
||||
export class PencilTool extends BaseTool {
|
||||
override type = TDShapeType.Pencil as const;
|
||||
|
||||
private last_shape_id?: string;
|
||||
|
||||
override onEnter = () => {
|
||||
this.last_shape_id = undefined;
|
||||
};
|
||||
|
||||
override onCancel = () => {
|
||||
switch (this.status) {
|
||||
case Status.Idle: {
|
||||
this.app.selectTool('select');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this.set_status(Status.Idle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.app.cancelSession();
|
||||
};
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = info => {
|
||||
if (this.status !== Status.Idle) return;
|
||||
const {
|
||||
currentPoint,
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
const previous =
|
||||
this.last_shape_id && this.app.getShape(this.last_shape_id);
|
||||
if (info.shiftKey && previous) {
|
||||
// Extend the previous shape
|
||||
this.app.startSession(SessionType.Draw, previous.id);
|
||||
this.set_status(Status.Extending);
|
||||
} else {
|
||||
// Create a new shape
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
|
||||
const newShape = Draw.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: currentPoint,
|
||||
style: { ...currentStyle, dash: DashStyle.Solid },
|
||||
workspace,
|
||||
});
|
||||
this.last_shape_id = id;
|
||||
this.app.patchCreate([newShape]);
|
||||
this.app.startSession(SessionType.Draw, id);
|
||||
this.set_status(Status.Creating);
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerMove: TLPointerEventHandler = () => {
|
||||
switch (this.status) {
|
||||
case Status.Extending:
|
||||
case Status.Creating: {
|
||||
this.app.updateSession();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerUp: TLPointerEventHandler = () => {
|
||||
this.app.completeSession();
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/pentagram-tool/index.ts
Normal file
1
libs/components/board-tools/src/pentagram-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './pentagram-tool';
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Utils, TLPointerEventHandler, TLBoundsCorner } from '@tldraw/core';
|
||||
import Vec from '@tldraw/vec';
|
||||
import { Pentagram } from '@toeverything/components/board-shapes';
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool, BaseToolStatus } from '@toeverything/components/board-state';
|
||||
|
||||
export class PentagramTool extends BaseTool {
|
||||
override type = TDShapeType.Pentagram as const;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== BaseToolStatus.Idle) return;
|
||||
|
||||
const {
|
||||
currentPoint,
|
||||
currentGrid,
|
||||
settings: { showGrid },
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const newShape = Pentagram.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: showGrid
|
||||
? Vec.snap(currentPoint, currentGrid)
|
||||
: currentPoint,
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
|
||||
this.app.patchCreate([newShape]);
|
||||
|
||||
this.app.startSession(
|
||||
SessionType.TransformSingle,
|
||||
newShape.id,
|
||||
TLBoundsCorner.BottomRight,
|
||||
true
|
||||
);
|
||||
|
||||
this.set_status(BaseToolStatus.Creating);
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/rectangle-tool/index.ts
Normal file
1
libs/components/board-tools/src/rectangle-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './rectangle-tool';
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Utils, TLPointerEventHandler, TLBoundsCorner } from '@tldraw/core';
|
||||
import Vec from '@tldraw/vec';
|
||||
import { Rectangle } from '@toeverything/components/board-shapes';
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool, BaseToolStatus } from '@toeverything/components/board-state';
|
||||
|
||||
export class RectangleTool extends BaseTool {
|
||||
override type = TDShapeType.Rectangle as const;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== BaseToolStatus.Idle) return;
|
||||
|
||||
const {
|
||||
currentPoint,
|
||||
currentGrid,
|
||||
settings: { showGrid },
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const newShape = Rectangle.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: showGrid
|
||||
? Vec.snap(currentPoint, currentGrid)
|
||||
: currentPoint,
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
|
||||
this.app.patchCreate([newShape]);
|
||||
|
||||
this.app.startSession(
|
||||
SessionType.TransformSingle,
|
||||
newShape.id,
|
||||
TLBoundsCorner.BottomRight,
|
||||
true
|
||||
);
|
||||
|
||||
this.set_status(BaseToolStatus.Creating);
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/select-tool/index.ts
Normal file
1
libs/components/board-tools/src/select-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './select-tool';
|
||||
768
libs/components/board-tools/src/select-tool/select-tool.ts
Normal file
768
libs/components/board-tools/src/select-tool/select-tool.ts
Normal file
@@ -0,0 +1,768 @@
|
||||
import {
|
||||
TLBoundsCorner,
|
||||
TLBoundsEdge,
|
||||
TLBoundsEventHandler,
|
||||
TLBoundsHandleEventHandler,
|
||||
TLCanvasEventHandler,
|
||||
TLPointerEventHandler,
|
||||
TLKeyboardEventHandler,
|
||||
TLShapeCloneHandler,
|
||||
Utils,
|
||||
} from '@tldraw/core';
|
||||
import {
|
||||
SessionType,
|
||||
TDShapeType,
|
||||
CLONING_DISTANCE,
|
||||
DEAD_ZONE,
|
||||
} from '@toeverything/components/board-types';
|
||||
import { BaseTool, TLDR } from '@toeverything/components/board-state';
|
||||
import Vec from '@tldraw/vec';
|
||||
|
||||
enum Status {
|
||||
Idle = 'idle',
|
||||
Creating = 'creating',
|
||||
Pinching = 'pinching',
|
||||
PointingCanvas = 'pointingCanvas',
|
||||
PointingHandle = 'pointingHandle',
|
||||
PointingBounds = 'pointingBounds',
|
||||
PointingClone = 'pointingClone',
|
||||
TranslatingClone = 'translatingClone',
|
||||
PointingBoundsHandle = 'pointingBoundsHandle',
|
||||
TranslatingHandle = 'translatingHandle',
|
||||
Translating = 'translating',
|
||||
Transforming = 'transforming',
|
||||
Rotating = 'rotating',
|
||||
Brushing = 'brushing',
|
||||
GridCloning = 'gridCloning',
|
||||
ClonePainting = 'clonePainting',
|
||||
}
|
||||
|
||||
export class SelectTool extends BaseTool<Status> {
|
||||
override type = 'select' as const;
|
||||
|
||||
pointedId?: string;
|
||||
|
||||
selectedGroupId?: string;
|
||||
|
||||
pointedHandleId?: 'start' | 'end' | 'bend';
|
||||
|
||||
pointedBoundsHandle?:
|
||||
| TLBoundsCorner
|
||||
| TLBoundsEdge
|
||||
| 'rotate'
|
||||
| 'center'
|
||||
| 'left'
|
||||
| 'right';
|
||||
|
||||
pointedLinkHandleId?: 'left' | 'center' | 'right';
|
||||
|
||||
/* --------------------- Methods -------------------- */
|
||||
|
||||
private deselect(id: string) {
|
||||
this.app.select(...this.app.selectedIds.filter(oid => oid !== id));
|
||||
}
|
||||
|
||||
private select(id: string) {
|
||||
this.app.select(id);
|
||||
}
|
||||
|
||||
private push_select(id: string) {
|
||||
const shape = this.app.getShape(id);
|
||||
this.app.select(
|
||||
...this.app.selectedIds.filter(oid => oid !== shape.parentId),
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
private select_none() {
|
||||
this.app.selectNone();
|
||||
}
|
||||
|
||||
override onEnter = () => {
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
|
||||
override onExit = () => {
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
|
||||
clonePaint = (point: number[]) => {
|
||||
if (this.app.selectedIds.length === 0) return;
|
||||
|
||||
const shapes = this.app.selectedIds.map(id => this.app.getShape(id));
|
||||
|
||||
const bounds = Utils.expandBounds(
|
||||
Utils.getCommonBounds(shapes.map(TLDR.get_bounds)),
|
||||
16
|
||||
);
|
||||
|
||||
const center = Utils.getBoundsCenter(bounds);
|
||||
|
||||
const size = [bounds.width, bounds.height];
|
||||
|
||||
const gridPoint = [
|
||||
center[0] +
|
||||
size[0] *
|
||||
Math.floor((point[0] + size[0] / 2 - center[0]) / size[0]),
|
||||
center[1] +
|
||||
size[1] *
|
||||
Math.floor((point[1] + size[1] / 2 - center[1]) / size[1]),
|
||||
];
|
||||
|
||||
const centeredBounds = Utils.centerBounds(bounds, gridPoint);
|
||||
|
||||
const hit = this.app.shapes.some(shape =>
|
||||
TLDR.get_shape_util(shape).hitTestBounds(shape, centeredBounds)
|
||||
);
|
||||
|
||||
if (!hit) {
|
||||
this.app.duplicate(this.app.selectedIds, gridPoint);
|
||||
}
|
||||
};
|
||||
|
||||
getShapeClone = (
|
||||
id: string,
|
||||
side:
|
||||
| 'top'
|
||||
| 'right'
|
||||
| 'bottom'
|
||||
| 'left'
|
||||
| 'topLeft'
|
||||
| 'topRight'
|
||||
| 'bottomRight'
|
||||
| 'bottomLeft'
|
||||
) => {
|
||||
const shape = this.app.getShape(id);
|
||||
const utils = TLDR.get_shape_util(shape);
|
||||
|
||||
if (utils.canClone) {
|
||||
const bounds = utils.getBounds(shape);
|
||||
|
||||
const center = utils.getCenter(shape);
|
||||
|
||||
let point = {
|
||||
top: [
|
||||
bounds.minX,
|
||||
bounds.minY - (bounds.height + CLONING_DISTANCE),
|
||||
],
|
||||
right: [bounds.maxX + CLONING_DISTANCE, bounds.minY],
|
||||
bottom: [bounds.minX, bounds.maxY + CLONING_DISTANCE],
|
||||
left: [
|
||||
bounds.minX - (bounds.width + CLONING_DISTANCE),
|
||||
bounds.minY,
|
||||
],
|
||||
topLeft: [
|
||||
bounds.minX - (bounds.width + CLONING_DISTANCE),
|
||||
bounds.minY - (bounds.height + CLONING_DISTANCE),
|
||||
],
|
||||
topRight: [
|
||||
bounds.maxX + CLONING_DISTANCE,
|
||||
bounds.minY - (bounds.height + CLONING_DISTANCE),
|
||||
],
|
||||
bottomLeft: [
|
||||
bounds.minX - (bounds.width + CLONING_DISTANCE),
|
||||
bounds.maxY + CLONING_DISTANCE,
|
||||
],
|
||||
bottomRight: [
|
||||
bounds.maxX + CLONING_DISTANCE,
|
||||
bounds.maxY + CLONING_DISTANCE,
|
||||
],
|
||||
}[side];
|
||||
|
||||
if (shape.rotation !== 0) {
|
||||
const newCenter = Vec.add(point, [
|
||||
bounds.width / 2,
|
||||
bounds.height / 2,
|
||||
]);
|
||||
|
||||
const rotatedCenter = Vec.rotWith(
|
||||
newCenter,
|
||||
center,
|
||||
shape.rotation || 0
|
||||
);
|
||||
|
||||
point = Vec.sub(rotatedCenter, [
|
||||
bounds.width / 2,
|
||||
bounds.height / 2,
|
||||
]);
|
||||
}
|
||||
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const clone = {
|
||||
...shape,
|
||||
id,
|
||||
point,
|
||||
};
|
||||
|
||||
// if (clone.type === TDShapeType.Sticky) {
|
||||
// clone.text = '';
|
||||
// }
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onCancel = () => {
|
||||
if (this.app.pageState.editingId) {
|
||||
this.app.setEditingId();
|
||||
} else {
|
||||
this.select_none();
|
||||
}
|
||||
this.app.cancelSession();
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
|
||||
override onKeyDown: TLKeyboardEventHandler = (key, info, e) => {
|
||||
switch (key) {
|
||||
case 'Escape': {
|
||||
this.onCancel();
|
||||
break;
|
||||
}
|
||||
case 'Tab': {
|
||||
if (
|
||||
!this.app.pageState.editingId &&
|
||||
this.status === Status.Idle &&
|
||||
this.app.selectedIds.length === 1
|
||||
) {
|
||||
const [selectedId] = this.app.selectedIds;
|
||||
const clonedShape = this.getShapeClone(selectedId, 'right');
|
||||
|
||||
if (clonedShape) {
|
||||
this.app.createShapes(clonedShape);
|
||||
this.set_status(Status.Idle);
|
||||
// if (clonedShape.type === TDShapeType.Sticky) {
|
||||
// this.app.select(clonedShape.id);
|
||||
// this.app.setEditingId(clonedShape.id);
|
||||
// }
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Meta':
|
||||
case 'Control':
|
||||
case 'Alt': {
|
||||
this.app.updateSession();
|
||||
break;
|
||||
}
|
||||
case 'Enter': {
|
||||
const { pageState } = this.app;
|
||||
if (
|
||||
pageState.selectedIds.length === 1 &&
|
||||
!pageState.editingId
|
||||
) {
|
||||
this.app.setEditingId(pageState.selectedIds[0]);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onKeyUp: TLKeyboardEventHandler = (key, info) => {
|
||||
if (
|
||||
this.status === Status.ClonePainting &&
|
||||
!(info.altKey && info.shiftKey)
|
||||
) {
|
||||
this.set_status(Status.Idle);
|
||||
return;
|
||||
}
|
||||
|
||||
/* noop */
|
||||
if (key === 'Meta' || key === 'Control' || key === 'Alt') {
|
||||
this.app.updateSession();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Keyup is handled on BaseTool
|
||||
|
||||
// Pointer Events (generic)
|
||||
|
||||
override onPointerMove: TLPointerEventHandler = (info, e) => {
|
||||
const { originPoint, currentPoint } = this.app;
|
||||
|
||||
switch (this.status) {
|
||||
case Status.PointingBoundsHandle: {
|
||||
if (!this.pointedBoundsHandle)
|
||||
throw Error('No pointed bounds handle');
|
||||
if (Vec.dist(originPoint, currentPoint) > DEAD_ZONE) {
|
||||
if (this.pointedBoundsHandle === 'rotate') {
|
||||
// Stat a rotate session
|
||||
this.set_status(Status.Rotating);
|
||||
this.app.startSession(SessionType.Rotate);
|
||||
} else if (
|
||||
this.pointedBoundsHandle === 'center' ||
|
||||
this.pointedBoundsHandle === 'left' ||
|
||||
this.pointedBoundsHandle === 'right'
|
||||
) {
|
||||
this.set_status(Status.Translating);
|
||||
this.app.startSession(
|
||||
SessionType.Translate,
|
||||
false,
|
||||
this.pointedBoundsHandle
|
||||
);
|
||||
} else {
|
||||
// Stat a transform session
|
||||
this.set_status(Status.Transforming);
|
||||
const idsToTransform = this.app.selectedIds.flatMap(
|
||||
id =>
|
||||
TLDR.get_document_branch(
|
||||
this.app.state,
|
||||
id,
|
||||
this.app.currentPageId
|
||||
)
|
||||
);
|
||||
if (idsToTransform.length === 1) {
|
||||
// if only one shape is selected, transform single
|
||||
this.app.startSession(
|
||||
SessionType.TransformSingle,
|
||||
idsToTransform[0],
|
||||
this.pointedBoundsHandle
|
||||
);
|
||||
} else {
|
||||
// otherwise, transform
|
||||
this.app.startSession(
|
||||
SessionType.Transform,
|
||||
this.pointedBoundsHandle
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Also update the session with the current point
|
||||
this.app.updateSession();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Status.PointingCanvas: {
|
||||
if (Vec.dist(originPoint, currentPoint) > DEAD_ZONE) {
|
||||
this.app.startSession(SessionType.Brush);
|
||||
this.set_status(Status.Brushing);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Status.PointingClone: {
|
||||
if (Vec.dist(originPoint, currentPoint) > DEAD_ZONE) {
|
||||
this.set_status(Status.TranslatingClone);
|
||||
this.app.startSession(SessionType.Translate);
|
||||
this.app.updateSession();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Status.PointingBounds: {
|
||||
if (Vec.dist(originPoint, currentPoint) > DEAD_ZONE) {
|
||||
this.set_status(Status.Translating);
|
||||
this.app.startSession(SessionType.Translate);
|
||||
this.app.updateSession();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Status.PointingHandle: {
|
||||
if (Vec.dist(originPoint, currentPoint) > DEAD_ZONE) {
|
||||
this.set_status(Status.TranslatingHandle);
|
||||
const selectedShape = this.app.getShape(
|
||||
this.app.selectedIds[0]
|
||||
);
|
||||
if (selectedShape) {
|
||||
if (this.pointedHandleId === 'bend') {
|
||||
this.app.startSession(
|
||||
SessionType.Handle,
|
||||
selectedShape.id,
|
||||
this.pointedHandleId
|
||||
);
|
||||
this.app.updateSession();
|
||||
} else {
|
||||
this.app.startSession(
|
||||
SessionType.Arrow,
|
||||
selectedShape.id,
|
||||
this.pointedHandleId,
|
||||
false
|
||||
);
|
||||
this.app.updateSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Status.ClonePainting: {
|
||||
this.clonePaint(currentPoint);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (this.app.session) {
|
||||
this.app.updateSession();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = (info, e) => {
|
||||
if (info.target === 'canvas' && this.status === Status.Idle) {
|
||||
const { currentPoint } = this.app;
|
||||
|
||||
if (info.spaceKey && e.buttons === 1) return;
|
||||
|
||||
if (this.status === Status.Idle && info.altKey && info.shiftKey) {
|
||||
this.set_status(Status.ClonePainting);
|
||||
this.clonePaint(currentPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unless the user is holding shift or meta, clear the current selection
|
||||
if (!info.shiftKey) {
|
||||
this.app.onShapeBlur();
|
||||
|
||||
if (info.altKey && this.app.selectedIds.length > 0) {
|
||||
this.app.duplicate(this.app.selectedIds, currentPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
this.select_none();
|
||||
}
|
||||
|
||||
this.set_status(Status.PointingCanvas);
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerUp: TLPointerEventHandler = info => {
|
||||
if (
|
||||
this.status === Status.TranslatingClone ||
|
||||
this.status === Status.PointingClone
|
||||
) {
|
||||
if (this.pointedId) {
|
||||
this.app.completeSession();
|
||||
this.app.setEditingId(this.pointedId);
|
||||
}
|
||||
this.set_status(Status.Idle);
|
||||
this.pointedId = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.status === Status.PointingBounds) {
|
||||
if (info.target === 'bounds') {
|
||||
// If we just clicked the selecting bounds's background,
|
||||
// clear the selection
|
||||
this.select_none();
|
||||
} else if (this.app.isSelected(info.target)) {
|
||||
// If we're holding shift...
|
||||
if (info.shiftKey) {
|
||||
// unless we just shift-selected the shape, remove it from
|
||||
// the selected shapes
|
||||
if (this.pointedId !== info.target) {
|
||||
this.deselect(info.target);
|
||||
}
|
||||
} else {
|
||||
// If we have other selected shapes, select this one instead
|
||||
if (
|
||||
this.pointedId !== info.target &&
|
||||
this.app.selectedIds.length > 1
|
||||
) {
|
||||
this.select(info.target);
|
||||
}
|
||||
}
|
||||
} else if (this.pointedId === info.target) {
|
||||
if (this.app.getShape(info.target).isLocked) return;
|
||||
// If the target is not selected and was just pointed
|
||||
// on pointer down...
|
||||
if (info.shiftKey) {
|
||||
this.push_select(info.target);
|
||||
} else {
|
||||
this.select(info.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the current session, if any; and reset the status
|
||||
this.app.completeSession();
|
||||
this.set_status(Status.Idle);
|
||||
this.pointedBoundsHandle = undefined;
|
||||
this.pointedHandleId = undefined;
|
||||
this.pointedId = undefined;
|
||||
};
|
||||
|
||||
// Canvas
|
||||
|
||||
override onDoubleClickCanvas: TLCanvasEventHandler = () => {
|
||||
// Needs debugging
|
||||
// const { currentPoint } = this.app
|
||||
// this.app.selectTool(TDShapeType.Text)
|
||||
// this.setStatus(Status.Idle)
|
||||
// this.app.createTextShapeAtPoint(currentPoint)
|
||||
};
|
||||
|
||||
// Shape
|
||||
override onPointShape: TLPointerEventHandler = (info, e) => {
|
||||
if (info.spaceKey && e.buttons === 1) return;
|
||||
|
||||
if (this.app.getShape(info.target).isLocked) return;
|
||||
|
||||
const { editingId, hoveredId } = this.app.pageState;
|
||||
|
||||
if (editingId && info.target !== editingId) {
|
||||
this.app.onShapeBlur();
|
||||
}
|
||||
|
||||
// While holding command and shift, select or deselect
|
||||
// the shape, ignoring any group that may contain it. Yikes!
|
||||
if (
|
||||
(this.status === Status.Idle ||
|
||||
this.status === Status.PointingBounds) &&
|
||||
info.metaKey &&
|
||||
info.shiftKey &&
|
||||
hoveredId
|
||||
) {
|
||||
this.pointedId = hoveredId;
|
||||
|
||||
if (this.app.isSelected(hoveredId)) {
|
||||
this.deselect(hoveredId);
|
||||
} else {
|
||||
this.push_select(hoveredId);
|
||||
this.set_status(Status.PointingBounds);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.status === Status.PointingBounds) {
|
||||
// The pointed id should be the shape's group, if it belongs
|
||||
// to a group, or else the shape itself, if it is on the page.
|
||||
const { parentId } = this.app.getShape(info.target);
|
||||
this.pointedId =
|
||||
parentId === this.app.currentPageId ? info.target : parentId;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.status === Status.Idle) {
|
||||
this.set_status(Status.PointingBounds);
|
||||
if (info.metaKey) {
|
||||
if (!info.shiftKey) {
|
||||
this.select_none();
|
||||
}
|
||||
|
||||
this.app.startSession(SessionType.Brush);
|
||||
|
||||
this.set_status(Status.Brushing);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we've clicked on a shape that is inside of a group,
|
||||
// then select the group rather than the shape.
|
||||
let shapeIdToSelect: string;
|
||||
const { parentId } = this.app.getShape(info.target);
|
||||
|
||||
// If the pointed shape is a child of the page, select the
|
||||
// target shape and clear the selected group id.
|
||||
if (parentId === this.app.currentPageId) {
|
||||
shapeIdToSelect = info.target;
|
||||
this.selectedGroupId = undefined;
|
||||
} else {
|
||||
// If the parent is some other group...
|
||||
if (parentId === this.selectedGroupId) {
|
||||
// If that group is the selected group, then select
|
||||
// the target shape.
|
||||
shapeIdToSelect = info.target;
|
||||
} else {
|
||||
// Otherwise, select the group and clear the selected
|
||||
// group id.
|
||||
shapeIdToSelect = parentId;
|
||||
|
||||
this.selectedGroupId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.app.isSelected(shapeIdToSelect)) {
|
||||
// Set the pointed ID to the shape that was clicked.
|
||||
this.pointedId = shapeIdToSelect;
|
||||
|
||||
// If the shape is not selected: then if the user is pressing shift,
|
||||
// add the shape to the current selection; otherwise, set the shape as
|
||||
// the only selected shape.
|
||||
if (info.shiftKey) {
|
||||
this.push_select(shapeIdToSelect);
|
||||
} else {
|
||||
this.select(shapeIdToSelect);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onDoubleClickShape: TLPointerEventHandler = info => {
|
||||
const shape = this.app.getShape(info.target);
|
||||
|
||||
if (shape.isLocked) {
|
||||
this.app.select(info.target);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we can edit the shape (and if we can select the shape) then
|
||||
// start editing
|
||||
if (
|
||||
TLDR.get_shape_util(shape.type).canEdit &&
|
||||
(shape.parentId === this.app.currentPageId ||
|
||||
shape.parentId === this.selectedGroupId)
|
||||
) {
|
||||
this.app.setEditingId(info.target);
|
||||
}
|
||||
|
||||
// If the shape is the child of a group, then drill into the group?
|
||||
if (shape.parentId !== this.app.currentPageId) {
|
||||
this.selectedGroupId = shape.parentId;
|
||||
}
|
||||
this.app.select(info.target);
|
||||
};
|
||||
|
||||
override onRightPointShape: TLPointerEventHandler = info => {
|
||||
if (!this.app.isSelected(info.target)) {
|
||||
this.app.select(info.target);
|
||||
}
|
||||
};
|
||||
|
||||
override onHoverShape: TLPointerEventHandler = info => {
|
||||
this.app.setHoveredId(info.target);
|
||||
};
|
||||
|
||||
override onUnhoverShape: TLPointerEventHandler = info => {
|
||||
const { currentPageId: oldCurrentPageId } = this.app;
|
||||
|
||||
// Wait a frame; and if we haven't changed the hovered id,
|
||||
// clear the current hovered id
|
||||
requestAnimationFrame(() => {
|
||||
if (
|
||||
oldCurrentPageId === this.app.currentPageId &&
|
||||
this.app.pageState.hoveredId === info.target
|
||||
) {
|
||||
this.app.setHoveredId(undefined);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* --------------------- Bounds --------------------- */
|
||||
|
||||
override onPointBounds: TLBoundsEventHandler = info => {
|
||||
if (info.metaKey) {
|
||||
if (!info.shiftKey) {
|
||||
this.select_none();
|
||||
}
|
||||
|
||||
this.app.startSession(SessionType.Brush);
|
||||
|
||||
this.set_status(Status.Brushing);
|
||||
return;
|
||||
}
|
||||
|
||||
this.set_status(Status.PointingBounds);
|
||||
};
|
||||
|
||||
override onRightPointBounds: TLPointerEventHandler = (info, e) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
override onReleaseBounds: TLBoundsEventHandler = () => {
|
||||
if (
|
||||
this.status === Status.Translating ||
|
||||
this.status === Status.Brushing
|
||||
) {
|
||||
this.app.completeSession();
|
||||
}
|
||||
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
|
||||
/* ----------------- Bounds Handles ----------------- */
|
||||
|
||||
override onPointBoundsHandle: TLBoundsHandleEventHandler = info => {
|
||||
this.pointedBoundsHandle = info.target;
|
||||
this.set_status(Status.PointingBoundsHandle);
|
||||
};
|
||||
|
||||
override onDoubleClickBoundsHandle: TLBoundsHandleEventHandler = info => {
|
||||
switch (info.target) {
|
||||
case 'center':
|
||||
case 'left':
|
||||
case 'right': {
|
||||
this.app.select(
|
||||
...TLDR.get_linked_shape_ids(
|
||||
this.app.state,
|
||||
this.app.currentPageId,
|
||||
info.target,
|
||||
info.shiftKey
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (this.app.selectedIds.length === 1) {
|
||||
this.app.resetBounds(this.app.selectedIds);
|
||||
const shape = this.app.getShape(this.app.selectedIds[0]);
|
||||
if ('label' in shape) {
|
||||
this.app.setEditingId(shape.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onReleaseBoundsHandle: TLBoundsHandleEventHandler = () => {
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
|
||||
/* --------------------- Handles -------------------- */
|
||||
|
||||
override onPointHandle: TLPointerEventHandler = info => {
|
||||
this.pointedHandleId = info.target as 'start' | 'end';
|
||||
this.set_status(Status.PointingHandle);
|
||||
};
|
||||
|
||||
override onDoubleClickHandle: TLPointerEventHandler = info => {
|
||||
if (info.target === 'bend') {
|
||||
const { selectedIds } = this.app;
|
||||
if (selectedIds.length !== 1) return;
|
||||
const shape = this.app.getShape(selectedIds[0]);
|
||||
if (
|
||||
TLDR.get_shape_util(shape.type).canEdit &&
|
||||
(shape.parentId === this.app.currentPageId ||
|
||||
shape.parentId === this.selectedGroupId)
|
||||
) {
|
||||
this.app.setEditingId(shape.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.app.toggleDecoration(info.target);
|
||||
};
|
||||
|
||||
override onReleaseHandle: TLPointerEventHandler = () => {
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
|
||||
/* ---------------------- Misc ---------------------- */
|
||||
|
||||
override onShapeClone: TLShapeCloneHandler = info => {
|
||||
const selectedShapeId = this.app.selectedIds[0];
|
||||
|
||||
const clonedShape = this.getShapeClone(selectedShapeId, info.target);
|
||||
|
||||
if (
|
||||
info.target === 'left' ||
|
||||
info.target === 'right' ||
|
||||
info.target === 'top' ||
|
||||
info.target === 'bottom'
|
||||
) {
|
||||
if (clonedShape) {
|
||||
this.app.createShapes(clonedShape);
|
||||
|
||||
// Now start pointing the bounds, so that a user can start
|
||||
// dragging to reposition if they wish.
|
||||
this.pointedId = clonedShape.id;
|
||||
this.set_status(Status.PointingClone);
|
||||
}
|
||||
} else {
|
||||
this.set_status(Status.GridCloning);
|
||||
this.app.startSession(SessionType.Grid, selectedShapeId);
|
||||
}
|
||||
};
|
||||
}
|
||||
1
libs/components/board-tools/src/triangle-tool/index.ts
Normal file
1
libs/components/board-tools/src/triangle-tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './triangle-tool';
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Utils, TLPointerEventHandler, TLBoundsCorner } from '@tldraw/core';
|
||||
import Vec from '@tldraw/vec';
|
||||
import { Triangle } from '@toeverything/components/board-shapes';
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool, BaseToolStatus } from '@toeverything/components/board-state';
|
||||
|
||||
export class TriangleTool extends BaseTool {
|
||||
override type = TDShapeType.Triangle as const;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== BaseToolStatus.Idle) return;
|
||||
|
||||
const {
|
||||
currentPoint,
|
||||
currentGrid,
|
||||
settings: { showGrid },
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const newShape = Triangle.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: showGrid
|
||||
? Vec.snap(currentPoint, currentGrid)
|
||||
: currentPoint,
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
|
||||
this.app.patchCreate([newShape]);
|
||||
|
||||
this.app.startSession(
|
||||
SessionType.TransformSingle,
|
||||
newShape.id,
|
||||
TLBoundsCorner.BottomRight,
|
||||
true
|
||||
);
|
||||
|
||||
this.set_status(BaseToolStatus.Creating);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './white-arrow-tool';
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Utils, TLPointerEventHandler, TLBoundsCorner } from '@tldraw/core';
|
||||
import Vec from '@tldraw/vec';
|
||||
import { WhiteArrow } from '@toeverything/components/board-shapes';
|
||||
import { SessionType, TDShapeType } from '@toeverything/components/board-types';
|
||||
import { BaseTool, BaseToolStatus } from '@toeverything/components/board-state';
|
||||
|
||||
export class WhiteArrowTool extends BaseTool {
|
||||
override type = TDShapeType.Triangle as const;
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.status !== BaseToolStatus.Idle) return;
|
||||
|
||||
const {
|
||||
currentPoint,
|
||||
currentGrid,
|
||||
settings: { showGrid },
|
||||
appState: { currentPageId, currentStyle },
|
||||
document: { id: workspace },
|
||||
} = this.app;
|
||||
|
||||
const childIndex = this.getNextChildIndex();
|
||||
|
||||
const id = Utils.uniqueId();
|
||||
|
||||
const newShape = WhiteArrow.create({
|
||||
id,
|
||||
parentId: currentPageId,
|
||||
childIndex,
|
||||
point: showGrid
|
||||
? Vec.snap(currentPoint, currentGrid)
|
||||
: currentPoint,
|
||||
style: { ...currentStyle },
|
||||
workspace,
|
||||
});
|
||||
|
||||
this.app.patchCreate([newShape]);
|
||||
|
||||
this.app.startSession(
|
||||
SessionType.TransformSingle,
|
||||
newShape.id,
|
||||
TLBoundsCorner.BottomRight,
|
||||
true
|
||||
);
|
||||
|
||||
this.set_status(BaseToolStatus.Creating);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user