kopia lustrzana https://github.com/Tldraw/Tldraw
tool-with-ui
rodzic
cf31e64bd4
commit
9df11cd011
|
@ -15,9 +15,10 @@ import {
|
|||
} from 'tldraw'
|
||||
import 'tldraw/tldraw.css'
|
||||
import { SimpleEraserToolUtil } from './SimpleEraserTool/SimpleEraserTool'
|
||||
import { SimpleSelectToolUtil } from './SimpleSelectTool'
|
||||
import { SimpleSelectToolUtil } from './SimpleSelectTool/SimpleSelectTool'
|
||||
|
||||
const tools: TLToolUtilConstructor<any, any>[] = [SimpleSelectToolUtil, SimpleEraserToolUtil]
|
||||
|
||||
const components: TLComponents = {
|
||||
Scribble: TldrawScribble,
|
||||
}
|
||||
|
@ -46,6 +47,8 @@ export default function NewToolExample() {
|
|||
{ type: 'geo', x: 200, y: 200 },
|
||||
{ type: 'geo', x: 400, y: 400 },
|
||||
{ type: 'text', x: 200, y: 400, props: { text: 'hello' } },
|
||||
{ type: 'frame', x: 100, y: 600 },
|
||||
{ type: 'geo', x: 150, y: 625 },
|
||||
])
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -4,13 +4,12 @@ import {
|
|||
TLFrameShape,
|
||||
TLGroupShape,
|
||||
TLShapeId,
|
||||
TLToolContext,
|
||||
ToolUtil,
|
||||
Vec,
|
||||
pointInPolygon,
|
||||
} from 'tldraw'
|
||||
|
||||
interface SimpleEraserContext extends TLToolContext {
|
||||
readonly type: '@simple/eraser'
|
||||
type SimpleEraserContext = {
|
||||
state:
|
||||
| {
|
||||
name: 'idle'
|
||||
|
@ -24,12 +23,21 @@ interface SimpleEraserContext extends TLToolContext {
|
|||
}
|
||||
}
|
||||
|
||||
export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext> {
|
||||
type SimpleEraserToolConfig = {
|
||||
scribbleSize: number
|
||||
}
|
||||
|
||||
export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext, SimpleEraserToolConfig> {
|
||||
static override type = '@simple/eraser' as const
|
||||
|
||||
getDefaultConfig(): SimpleEraserToolConfig {
|
||||
return {
|
||||
scribbleSize: 12,
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultContext(): SimpleEraserContext {
|
||||
return {
|
||||
type: '@simple/eraser',
|
||||
state: { name: 'idle' },
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +80,7 @@ export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext> {
|
|||
|
||||
switch (context.state.name) {
|
||||
case 'idle': {
|
||||
if (editor.inputs.isPointing) {
|
||||
if (event.name === 'pointer_down') {
|
||||
// started pointing
|
||||
this.setContext({
|
||||
state: { name: 'pointing' },
|
||||
|
@ -90,13 +98,7 @@ export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext> {
|
|||
|
||||
if (editor.inputs.isDragging || event.name === 'long_press') {
|
||||
// started dragging
|
||||
const scribble = editor.scribbles.addScribble({
|
||||
color: 'muted-1',
|
||||
size: 12,
|
||||
})
|
||||
this.setContext({
|
||||
state: { name: 'erasing', scribbleId: scribble.id },
|
||||
})
|
||||
this.startErasingAfterDragging()
|
||||
this.updateErasingShapes()
|
||||
return
|
||||
}
|
||||
|
@ -125,10 +127,11 @@ export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext> {
|
|||
scribbleId: null as string | null,
|
||||
excludedShapeIds: new Set<TLShapeId>(),
|
||||
erasingShapeIds: new Set<TLShapeId>(),
|
||||
prevPoint: new Vec(),
|
||||
}
|
||||
|
||||
private cancel() {
|
||||
const { editor } = this
|
||||
const { memo, editor } = this
|
||||
|
||||
// Reset the erasing shapes
|
||||
editor.setErasingShapes([])
|
||||
|
@ -137,14 +140,18 @@ export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext> {
|
|||
// Stop the scribble
|
||||
const context = this.getContext()
|
||||
if (context.state.name === 'erasing') {
|
||||
this.editor.scribbles.stop(context.state.scribbleId)
|
||||
editor.scribbles.stop(context.state.scribbleId)
|
||||
}
|
||||
|
||||
memo.erasingShapeIds.clear()
|
||||
memo.excludedShapeIds.clear()
|
||||
memo.scribbleId = null
|
||||
|
||||
this.setContext({ state: { name: 'idle' } })
|
||||
}
|
||||
|
||||
private complete() {
|
||||
const { editor } = this
|
||||
const { memo, editor } = this
|
||||
|
||||
// Delete any shapes that were marked as erasing
|
||||
const erasingShapeIds = editor.getErasingShapeIds()
|
||||
|
@ -156,30 +163,80 @@ export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext> {
|
|||
// Stop the scribble
|
||||
const context = this.getContext()
|
||||
if (context.state.name === 'erasing') {
|
||||
this.editor.scribbles.stop(context.state.scribbleId)
|
||||
editor.scribbles.stop(context.state.scribbleId)
|
||||
}
|
||||
|
||||
memo.erasingShapeIds.clear()
|
||||
memo.excludedShapeIds.clear()
|
||||
memo.scribbleId = null
|
||||
|
||||
this.setContext({ state: { name: 'idle' } })
|
||||
}
|
||||
|
||||
private startErasingPointedShapes() {
|
||||
const { editor, memo } = this
|
||||
const { originPagePoint } = editor.inputs
|
||||
const { erasingShapeIds, prevPoint } = memo
|
||||
|
||||
editor.mark('erasing')
|
||||
|
||||
const zoomLevel = this.editor.getZoomLevel()
|
||||
const minDist = HIT_TEST_MARGIN / zoomLevel
|
||||
prevPoint.setTo(originPagePoint)
|
||||
|
||||
const minDist = HIT_TEST_MARGIN / editor.getZoomLevel()
|
||||
|
||||
// Populate the erasing shape ids working front to back...
|
||||
const shapes = editor.getCurrentPageShapesSorted()
|
||||
for (let i = shapes.length - 1; i > -1; i--) {
|
||||
const shape = shapes[i]
|
||||
|
||||
// Look for hit shapes
|
||||
if (
|
||||
editor.isPointInShape(shape, originPagePoint, {
|
||||
hitInside: false,
|
||||
margin: minDist,
|
||||
})
|
||||
) {
|
||||
const hitShape = editor.getOutermostSelectableShape(shape)
|
||||
// If we've hit a frame after hitting any other shape, stop here
|
||||
if (editor.isShapeOfType<TLFrameShape>(hitShape, 'frame') && erasingShapeIds.size > 0) {
|
||||
break
|
||||
}
|
||||
|
||||
erasingShapeIds.add(hitShape.id)
|
||||
}
|
||||
}
|
||||
|
||||
editor.setErasingShapes(Array.from(erasingShapeIds))
|
||||
}
|
||||
|
||||
private startErasingAfterDragging() {
|
||||
const {
|
||||
editor,
|
||||
memo: { erasingShapeIds, excludedShapeIds },
|
||||
} = this
|
||||
const { originPagePoint } = editor.inputs
|
||||
|
||||
const scribble = editor.scribbles.addScribble({
|
||||
color: 'muted-1',
|
||||
size: 12,
|
||||
})
|
||||
this.setContext({
|
||||
state: { name: 'erasing', scribbleId: scribble.id },
|
||||
})
|
||||
|
||||
// Clear any erasing shapes from the pointing state
|
||||
erasingShapeIds.clear()
|
||||
|
||||
// Populate the excluded shape ids and the erasing shape ids, working front to back...
|
||||
excludedShapeIds.clear()
|
||||
|
||||
// Populate the excluded shape ids and the erasing shape ids
|
||||
// working front to back...
|
||||
const shapes = editor.getCurrentPageShapesSorted()
|
||||
for (let i = shapes.length - 1; i > -1; i--) {
|
||||
const shape = shapes[i]
|
||||
|
||||
// If the shape is locked, exclude it
|
||||
if (editor.isShapeOrAncestorLocked(shape)) {
|
||||
memo.excludedShapeIds.add(shape.id)
|
||||
excludedShapeIds.add(shape.id)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -188,35 +245,17 @@ export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext> {
|
|||
editor.isShapeOfType<TLGroupShape>(shape, 'group') ||
|
||||
editor.isShapeOfType<TLFrameShape>(shape, 'frame')
|
||||
) {
|
||||
this.editor.isPointInShape(shape, originPagePoint, {
|
||||
hitInside: true,
|
||||
margin: 0,
|
||||
})
|
||||
memo.excludedShapeIds.add(shape.id)
|
||||
if (
|
||||
editor.isPointInShape(shape, originPagePoint, {
|
||||
hitInside: true,
|
||||
margin: 0,
|
||||
})
|
||||
) {
|
||||
excludedShapeIds.add(shape.id)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Look for hit shapes
|
||||
if (
|
||||
this.editor.isPointInShape(shape, originPagePoint, {
|
||||
hitInside: false,
|
||||
margin: minDist,
|
||||
})
|
||||
) {
|
||||
const hitShape = this.editor.getOutermostSelectableShape(shape)
|
||||
// If we've hit a frame after hitting any other shape, stop here
|
||||
if (
|
||||
this.editor.isShapeOfType<TLFrameShape>(hitShape, 'frame') &&
|
||||
memo.erasingShapeIds.size > 0
|
||||
) {
|
||||
break
|
||||
}
|
||||
|
||||
memo.erasingShapeIds.add(hitShape.id)
|
||||
}
|
||||
}
|
||||
|
||||
this.editor.setErasingShapes(Array.from(memo.erasingShapeIds))
|
||||
}
|
||||
|
||||
private updateErasingShapes() {
|
||||
|
@ -225,25 +264,22 @@ export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext> {
|
|||
const context = this.getContext()
|
||||
if (context.state.name !== 'erasing') return
|
||||
|
||||
// Erase
|
||||
const { excludedShapeIds } = memo
|
||||
const { excludedShapeIds, erasingShapeIds, prevPoint } = memo
|
||||
const { scribbleId } = context.state
|
||||
|
||||
const zoomLevel = editor.getZoomLevel()
|
||||
const erasingShapeIds = editor.getErasingShapeIds()
|
||||
|
||||
const {
|
||||
inputs: { currentPagePoint, previousPagePoint },
|
||||
inputs: { currentPagePoint },
|
||||
} = editor
|
||||
|
||||
// Update scribble
|
||||
const { x, y } = currentPagePoint
|
||||
editor.scribbles.addPoint(scribbleId, x, y)
|
||||
|
||||
const erasing = new Set<TLShapeId>(erasingShapeIds)
|
||||
const minDist = HIT_TEST_MARGIN / zoomLevel
|
||||
const minDist = HIT_TEST_MARGIN / editor.getZoomLevel()
|
||||
|
||||
const currentPageShapes = editor.getCurrentPageShapes()
|
||||
for (const shape of currentPageShapes) {
|
||||
// Skip groups
|
||||
if (editor.isShapeOfType<TLGroupShape>(shape, 'group')) continue
|
||||
|
||||
// Avoid testing masked shapes, unless the pointer is inside the mask
|
||||
|
@ -257,7 +293,7 @@ export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext> {
|
|||
const pageTransform = editor.getShapePageTransform(shape)
|
||||
if (!geometry || !pageTransform) continue
|
||||
const pt = pageTransform.clone().invert()
|
||||
const A = pt.applyToPoint(previousPagePoint)
|
||||
const A = pt.applyToPoint(prevPoint)
|
||||
const B = pt.applyToPoint(currentPagePoint)
|
||||
|
||||
// If the line segment is entirely above / below / left / right of the shape's bounding box, skip the hit test
|
||||
|
@ -272,13 +308,16 @@ export class SimpleEraserToolUtil extends ToolUtil<SimpleEraserContext> {
|
|||
}
|
||||
|
||||
if (geometry.hitTestLineSegment(A, B, minDist)) {
|
||||
erasing.add(editor.getOutermostSelectableShape(shape).id)
|
||||
const shapeToErase = editor.getOutermostSelectableShape(shape)
|
||||
if (excludedShapeIds.has(shapeToErase.id)) continue
|
||||
erasingShapeIds.add(shapeToErase.id)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.setErasingShapes(Array.from(erasing).filter((id) => !excludedShapeIds.has(id)))
|
||||
// Update the prev page point for next segment
|
||||
prevPoint.setTo(currentPagePoint)
|
||||
|
||||
// Remove the hit shapes
|
||||
editor.setErasingShapes(Array.from(erasingShapeIds))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
SharedStyleMap,
|
||||
TLEventInfo,
|
||||
TLShapeId,
|
||||
TLToolContext,
|
||||
ToolUtil,
|
||||
dedupe,
|
||||
getOwnProperty,
|
||||
|
@ -19,8 +18,7 @@ import {
|
|||
useValue,
|
||||
} from 'tldraw'
|
||||
|
||||
interface SimpleSelectContext extends TLToolContext {
|
||||
readonly type: '@simple/select'
|
||||
type SimpleSelectContext = {
|
||||
state:
|
||||
| {
|
||||
name: 'idle'
|
||||
|
@ -45,24 +43,26 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectContext> {
|
|||
|
||||
getDefaultContext(): SimpleSelectContext {
|
||||
return {
|
||||
type: '@simple/select',
|
||||
state: { name: 'idle' },
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultConfig() {
|
||||
return {}
|
||||
}
|
||||
|
||||
underlay() {
|
||||
return null
|
||||
}
|
||||
|
||||
overlay() {
|
||||
const { state } = this.getContext()
|
||||
if (state.name !== 'brushing') return
|
||||
|
||||
return (
|
||||
<>
|
||||
<ShapeIndicators />
|
||||
<HintedShapeIndicator />
|
||||
<SelectionBrush brush={state.brush} />
|
||||
{state.name === 'brushing' && <SelectionBrush brush={state.brush} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -132,59 +132,62 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectContext> {
|
|||
break
|
||||
}
|
||||
case 'brushing': {
|
||||
if (editor.inputs.isDragging) {
|
||||
if (
|
||||
event.name === 'pointer_move' ||
|
||||
// for modifiers
|
||||
event.name === 'key_down' ||
|
||||
event.name === 'key_up'
|
||||
) {
|
||||
const { originPagePoint, currentPagePoint } = editor.inputs
|
||||
const box = Box.FromPoints([originPagePoint, currentPagePoint])
|
||||
|
||||
// update the box in the context
|
||||
this.setContext({
|
||||
state: {
|
||||
name: 'brushing',
|
||||
brush: box.toJson(),
|
||||
},
|
||||
})
|
||||
|
||||
const hitIds = new Set<TLShapeId>()
|
||||
|
||||
// If we're holding shift, add the initial selected ids to the hitIds set
|
||||
if (editor.inputs.shiftKey) {
|
||||
for (const id of memo.initialSelectedIds) {
|
||||
hitIds.add(id)
|
||||
}
|
||||
}
|
||||
|
||||
// Test the rest of the shapes on the page (broad phase only for simplifity)
|
||||
for (const shape of editor.getCurrentPageShapes()) {
|
||||
if (hitIds.has(shape.id)) continue
|
||||
const pageBounds = editor.getShapePageBounds(shape.id)
|
||||
if (!pageBounds) continue
|
||||
if (box.collides(pageBounds)) {
|
||||
hitIds.add(shape.id)
|
||||
}
|
||||
}
|
||||
|
||||
// If the selected ids have changed, update the selection
|
||||
const currentSelectedIds = editor.getSelectedShapeIds()
|
||||
if (
|
||||
currentSelectedIds.length !== hitIds.size ||
|
||||
currentSelectedIds.some((id) => !hitIds.has(id))
|
||||
) {
|
||||
editor.setSelectedShapes(Array.from(hitIds))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!editor.inputs.isPointing) {
|
||||
// Stopped pointing
|
||||
this.setContext({
|
||||
state: {
|
||||
name: 'idle',
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
event.name === 'pointer_move' ||
|
||||
// for modifiers
|
||||
event.name === 'key_down' ||
|
||||
event.name === 'key_up'
|
||||
) {
|
||||
const { originPagePoint, currentPagePoint } = editor.inputs
|
||||
const box = Box.FromPoints([originPagePoint, currentPagePoint])
|
||||
|
||||
// update the box in the context
|
||||
this.setContext({
|
||||
state: {
|
||||
name: 'brushing',
|
||||
brush: box.toJson(),
|
||||
},
|
||||
})
|
||||
|
||||
const hitIds = new Set<TLShapeId>()
|
||||
|
||||
// If we're holding shift, add the initial selected ids to the hitIds set
|
||||
if (editor.inputs.shiftKey) {
|
||||
for (const id of memo.initialSelectedIds) {
|
||||
hitIds.add(id)
|
||||
}
|
||||
}
|
||||
|
||||
// Test the rest of the shapes on the page (broad phase only for simplifity)
|
||||
for (const shape of editor.getCurrentPageShapes()) {
|
||||
if (hitIds.has(shape.id)) continue
|
||||
const pageBounds = editor.getShapePageBounds(shape.id)
|
||||
if (!pageBounds) continue
|
||||
if (box.collides(pageBounds)) {
|
||||
hitIds.add(shape.id)
|
||||
}
|
||||
}
|
||||
|
||||
// If the selected ids have changed, update the selection
|
||||
const currentSelectedIds = editor.getSelectedShapeIds()
|
||||
if (
|
||||
currentSelectedIds.length !== hitIds.size ||
|
||||
currentSelectedIds.some((id) => !hitIds.has(id))
|
||||
) {
|
||||
editor.setSelectedShapes(Array.from(hitIds))
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -235,17 +238,16 @@ function ShapeIndicators() {
|
|||
// todo: move to tldraw selected ids wrappe
|
||||
const prev = rPreviousSelectedShapeIds.current
|
||||
const next = new Set<TLShapeId>()
|
||||
if (!editor.getInstanceState().isChangingStyle) {
|
||||
const instanceState = editor.getInstanceState()
|
||||
if (!instanceState.isChangingStyle) {
|
||||
const selected = editor.getSelectedShapeIds()
|
||||
for (const id of selected) {
|
||||
next.add(id)
|
||||
}
|
||||
if (editor.isInAny('select.idle', 'select.editing_shape')) {
|
||||
const instanceState = editor.getInstanceState()
|
||||
if (instanceState.isHoveringCanvas && !instanceState.isCoarsePointer) {
|
||||
const hovered = editor.getHoveredShapeId()
|
||||
if (hovered) next.add(hovered)
|
||||
}
|
||||
|
||||
if (instanceState.isHoveringCanvas && !instanceState.isCoarsePointer) {
|
||||
const hovered = editor.getHoveredShapeId()
|
||||
if (hovered) next.add(hovered)
|
||||
}
|
||||
}
|
||||
|
|
@ -188,8 +188,10 @@ export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxSha
|
|||
export { StateNode, type TLStateNodeConstructor } from './lib/editor/tools/StateNode'
|
||||
export {
|
||||
ToolUtil,
|
||||
type TLToolConfig,
|
||||
type TLToolContext,
|
||||
type TLToolUtilConstructor,
|
||||
type TLToolUtilConstructorWithConfig,
|
||||
} from './lib/editor/tools/ToolUtil'
|
||||
export {
|
||||
useSvgExportContext,
|
||||
|
|
|
@ -529,20 +529,20 @@ function DebugSvgCopy({ id }: { id: TLShapeId }) {
|
|||
)
|
||||
}
|
||||
|
||||
function SelectionForegroundWrapper() {
|
||||
const editor = useEditor()
|
||||
const selectionRotation = useValue('selection rotation', () => editor.getSelectionRotation(), [
|
||||
editor,
|
||||
])
|
||||
const selectionBounds = useValue(
|
||||
'selection bounds',
|
||||
() => editor.getSelectionRotatedPageBounds(),
|
||||
[editor]
|
||||
)
|
||||
const { SelectionForeground } = useEditorComponents()
|
||||
if (!selectionBounds || !SelectionForeground) return null
|
||||
return <SelectionForeground bounds={selectionBounds} rotation={selectionRotation} />
|
||||
}
|
||||
// function SelectionForegroundWrapper() {
|
||||
// const editor = useEditor()
|
||||
// const selectionRotation = useValue('selection rotation', () => editor.getSelectionRotation(), [
|
||||
// editor,
|
||||
// ])
|
||||
// const selectionBounds = useValue(
|
||||
// 'selection bounds',
|
||||
// () => editor.getSelectionRotatedPageBounds(),
|
||||
// [editor]
|
||||
// )
|
||||
// const { SelectionForeground } = useEditorComponents()
|
||||
// if (!selectionBounds || !SelectionForeground) return null
|
||||
// return <SelectionForeground bounds={selectionBounds} rotation={selectionRotation} />
|
||||
// }
|
||||
|
||||
function SelectionBackgroundWrapper() {
|
||||
const editor = useEditor()
|
||||
|
|
|
@ -132,7 +132,7 @@ import { TextManager } from './managers/TextManager'
|
|||
import { TickManager } from './managers/TickManager'
|
||||
import { UserPreferencesManager } from './managers/UserPreferencesManager'
|
||||
import { ShapeUtil, TLResizeMode, TLShapeUtilConstructor } from './shapes/ShapeUtil'
|
||||
import { TLToolUtilConstructor, ToolUtil } from './tools/ToolUtil'
|
||||
import { TLToolUtilConstructor, TLToolUtilConstructorWithConfig, ToolUtil } from './tools/ToolUtil'
|
||||
import { TLContent } from './types/clipboard-types'
|
||||
import { TLEventMap } from './types/emit-types'
|
||||
import {
|
||||
|
@ -178,7 +178,7 @@ export interface TLEditorOptions {
|
|||
/**
|
||||
* An array of tools to use in the editor. These will be used to handle events and manage user interactions in the editor.
|
||||
*/
|
||||
tools: readonly TLToolUtilConstructor<any>[]
|
||||
tools: readonly (TLToolUtilConstructor<any, any> | TLToolUtilConstructorWithConfig<any, any>)[]
|
||||
/**
|
||||
* An array of bindings to use in the editor. These will be used to create and manage bindings in the editor.
|
||||
*/
|
||||
|
@ -279,9 +279,20 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const toolMap: Record<string, ToolUtil<any>> = {}
|
||||
|
||||
for (const Tool of [...tools]) {
|
||||
const tool = new Tool(this)
|
||||
toolMap[Tool.type] = tool
|
||||
for (const _tool of [...tools]) {
|
||||
let ToolConstructor: TLToolUtilConstructor<any, any>
|
||||
let config: object
|
||||
if (Array.isArray(_tool)) {
|
||||
ToolConstructor = _tool[0]
|
||||
config = _tool[1]
|
||||
} else {
|
||||
ToolConstructor = _tool
|
||||
config = {}
|
||||
}
|
||||
|
||||
const tool = new ToolConstructor(this, config)
|
||||
toolMap[ToolConstructor.type] = tool
|
||||
|
||||
tool.setContext(tool.getDefaultContext())
|
||||
}
|
||||
|
||||
|
@ -1020,7 +1031,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
isIn(path: string): boolean {
|
||||
isIn(_path: string): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -1039,52 +1050,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return paths.some((path) => this.isIn(path))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selected tool.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.setCurrentTool('hand')
|
||||
* editor.setCurrentTool('hand', { date: Date.now() })
|
||||
* ```
|
||||
*
|
||||
* @param id - The id of the tool to select.
|
||||
* @param info - Arbitrary data to pass along into the transition.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
setCurrentTool(id: string, info = {}): this {
|
||||
const current = this.getCurrentTool()
|
||||
const next = this.getTool(id)
|
||||
if (current !== next) {
|
||||
current.onExit(info)
|
||||
console.log('setting', id)
|
||||
this._currentToolId.set(id)
|
||||
current.onEnter(info)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
// /**
|
||||
// * The current selected tool.
|
||||
// *
|
||||
// * @public
|
||||
// */
|
||||
// @computed getCurrentTool(): StateNode {
|
||||
// return this.root.getCurrent()!
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * The id of the current selected tool.
|
||||
// *
|
||||
// * @public
|
||||
// */
|
||||
// @computed getCurrentToolId(): string {
|
||||
// const currentTool = this.getCurrentTool()
|
||||
// if (!currentTool) return ''
|
||||
// return currentTool.getCurrentToolIdMask() ?? currentTool.id
|
||||
// }
|
||||
|
||||
/* ---------------- Document Settings --------------- */
|
||||
|
||||
/**
|
||||
|
@ -1928,6 +1893,31 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return this._tools.get()[id] as T
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selected tool.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.setCurrentTool('hand')
|
||||
* editor.setCurrentTool('hand', { date: Date.now() })
|
||||
* ```
|
||||
*
|
||||
* @param id - The id of the tool to select.
|
||||
* @param info - Arbitrary data to pass along into the transition.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
setCurrentTool(id: string, info = {}): this {
|
||||
const current = this.getCurrentTool()
|
||||
const next = this.getTool(id)
|
||||
if (current !== next) {
|
||||
current.onExit(info)
|
||||
this._currentToolId.set(id)
|
||||
current.onEnter(info)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/* --------------------- Camera --------------------- */
|
||||
|
||||
/** @internal */
|
||||
|
|
|
@ -4,21 +4,29 @@ import { ReadonlySharedStyleMap } from '../../utils/SharedStylesMap'
|
|||
import { Editor } from '../Editor'
|
||||
import { TLEventInfo } from '../types/event-types'
|
||||
|
||||
export interface TLToolContext {
|
||||
type: string
|
||||
}
|
||||
export abstract class ToolUtil<Context extends object, Config extends object = object> {
|
||||
constructor(
|
||||
public editor: Editor,
|
||||
config: Partial<Config> = {}
|
||||
) {
|
||||
this.config = { ...this.getDefaultConfig?.(), ...config }
|
||||
}
|
||||
|
||||
export abstract class ToolUtil<T extends TLToolContext> {
|
||||
constructor(public editor: Editor) {}
|
||||
static type: string
|
||||
|
||||
static type: TLToolContext['type']
|
||||
public readonly config: Partial<Config>
|
||||
|
||||
/**
|
||||
* The tool's default context, set when the tool is first registered in the Editor.
|
||||
*/
|
||||
abstract getDefaultContext(): T
|
||||
abstract getDefaultConfig?(): Config
|
||||
|
||||
private _context = atom<T>('tool context', {} as T)
|
||||
/**
|
||||
* The tool's default context, set when the tool is first registered in the Editor.
|
||||
*/
|
||||
abstract getDefaultContext(): Context
|
||||
|
||||
private _context = atom<Context>('tool context', {} as Context)
|
||||
|
||||
/**
|
||||
* Get the tool's context.
|
||||
|
@ -32,7 +40,7 @@ export abstract class ToolUtil<T extends TLToolContext> {
|
|||
*
|
||||
* @param context - A partial of the tool's context.
|
||||
*/
|
||||
setContext(context: Partial<T>) {
|
||||
setContext(context: Partial<Context>) {
|
||||
this._context.set({ ...this._context.__unsafe__getWithoutCapture(), ...context })
|
||||
}
|
||||
|
||||
|
@ -74,10 +82,12 @@ export abstract class ToolUtil<T extends TLToolContext> {
|
|||
}
|
||||
|
||||
/** @public */
|
||||
export interface TLToolUtilConstructor<
|
||||
T extends TLToolContext,
|
||||
U extends ToolUtil<T> = ToolUtil<T>,
|
||||
> {
|
||||
new (editor: Editor): U
|
||||
type: T['type']
|
||||
export interface TLToolUtilConstructor<T extends object, Q extends object> {
|
||||
new (editor: Editor, config: Q): ToolUtil<T, Q>
|
||||
type: string
|
||||
}
|
||||
|
||||
export type TLToolUtilConstructorWithConfig<T extends object, Q extends object> = [
|
||||
TLToolUtilConstructor<T, Q>,
|
||||
Q,
|
||||
]
|
||||
|
|
|
@ -60,13 +60,14 @@ export class FrameShapeUtil extends BaseBoxShapeUtil<TLFrameShape> {
|
|||
const isCreating = useValue(
|
||||
'is creating this shape',
|
||||
() => {
|
||||
const resizingState = this.editor.getStateDescendant('select.resizing')
|
||||
if (!resizingState) return false
|
||||
if (!resizingState.getIsActive()) return false
|
||||
const info = (resizingState as typeof resizingState & { info: { isCreating: boolean } })
|
||||
?.info
|
||||
if (!info) return false
|
||||
return info.isCreating && this.editor.getOnlySelectedShapeId() === shape.id
|
||||
return false
|
||||
// const resizingState = this.editor.getStateDescendant('select.resizing')
|
||||
// if (!resizingState) return false
|
||||
// if (!resizingState.getIsActive()) return false
|
||||
// const info = (resizingState as typeof resizingState & { info: { isCreating: boolean } })
|
||||
// ?.info
|
||||
// if (!info) return false
|
||||
// return info.isCreating && this.editor.getOnlySelectedShapeId() === shape.id
|
||||
},
|
||||
[shape.id]
|
||||
)
|
||||
|
|
Ładowanie…
Reference in New Issue