kopia lustrzana https://github.com/Tldraw/Tldraw
Allow for resets when id changes
rodzic
2aeb513342
commit
4e13b0e07b
|
@ -26,7 +26,7 @@ interface CanvasProps<T extends TLShape> {
|
||||||
meta?: Record<string, unknown>
|
meta?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Canvas = React.memo(function Canvas<T extends TLShape>({
|
export function Canvas<T extends TLShape>({
|
||||||
page,
|
page,
|
||||||
pageState,
|
pageState,
|
||||||
meta,
|
meta,
|
||||||
|
@ -66,4 +66,4 @@ export const Canvas = React.memo(function Canvas<T extends TLShape>({
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
|
@ -18,12 +18,7 @@ interface PageProps<T extends TLShape> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Page component renders the current page.
|
* The Page component renders the current page.
|
||||||
*
|
*/
|
||||||
* ### Example
|
|
||||||
*
|
|
||||||
*```ts
|
|
||||||
* example
|
|
||||||
*``` */
|
|
||||||
export function Page<T extends TLShape>({
|
export function Page<T extends TLShape>({
|
||||||
page,
|
page,
|
||||||
pageState,
|
pageState,
|
||||||
|
|
|
@ -10,10 +10,15 @@ import type {
|
||||||
TLBinding,
|
TLBinding,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
import { Canvas } from '../canvas'
|
import { Canvas } from '../canvas'
|
||||||
import { useTLTheme, TLContext } from '../../hooks'
|
import { useTLTheme, TLContext, TLContextType } from '../../hooks'
|
||||||
|
|
||||||
export interface RendererProps<T extends TLShape, M extends Record<string, unknown>>
|
export interface RendererProps<T extends TLShape, M extends Record<string, unknown>>
|
||||||
extends Partial<TLCallbacks> {
|
extends Partial<TLCallbacks> {
|
||||||
|
/**
|
||||||
|
* An id representing the current document. Changing the id will
|
||||||
|
* update the context and trigger a re-render.
|
||||||
|
*/
|
||||||
|
id?: string
|
||||||
/**
|
/**
|
||||||
* An object containing instances of your shape classes.
|
* An object containing instances of your shape classes.
|
||||||
*/
|
*/
|
||||||
|
@ -52,6 +57,8 @@ export interface RendererProps<T extends TLShape, M extends Record<string, unkno
|
||||||
* An object of custom options that should be passed to rendered shapes.
|
* An object of custom options that should be passed to rendered shapes.
|
||||||
*/
|
*/
|
||||||
meta?: M
|
meta?: M
|
||||||
|
// Temp
|
||||||
|
onTest?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,6 +70,7 @@ export interface RendererProps<T extends TLShape, M extends Record<string, unkno
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function Renderer<T extends TLShape, M extends Record<string, unknown>>({
|
export function Renderer<T extends TLShape, M extends Record<string, unknown>>({
|
||||||
|
id,
|
||||||
shapeUtils,
|
shapeUtils,
|
||||||
page,
|
page,
|
||||||
pageState,
|
pageState,
|
||||||
|
@ -82,13 +90,31 @@ export function Renderer<T extends TLShape, M extends Record<string, unknown>>({
|
||||||
rPageState.current = pageState
|
rPageState.current = pageState
|
||||||
}, [pageState])
|
}, [pageState])
|
||||||
|
|
||||||
const [context] = React.useState(() => ({
|
const rId = React.useRef(id)
|
||||||
|
|
||||||
|
const [context, setContext] = React.useState<TLContextType>(() => ({
|
||||||
|
id,
|
||||||
callbacks: rest,
|
callbacks: rest,
|
||||||
shapeUtils,
|
shapeUtils,
|
||||||
rScreenBounds,
|
rScreenBounds,
|
||||||
rPageState,
|
rPageState,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (id !== rId.current) {
|
||||||
|
rest.onTest?.()
|
||||||
|
setContext({
|
||||||
|
id,
|
||||||
|
callbacks: rest,
|
||||||
|
shapeUtils,
|
||||||
|
rScreenBounds,
|
||||||
|
rPageState,
|
||||||
|
})
|
||||||
|
|
||||||
|
rId.current = id
|
||||||
|
}
|
||||||
|
}, [id])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TLContext.Provider value={context}>
|
<TLContext.Provider value={context}>
|
||||||
<Canvas
|
<Canvas
|
||||||
|
|
|
@ -4,55 +4,53 @@ import type { IShapeTreeNode } from '+types'
|
||||||
import { RenderedShape } from './rendered-shape'
|
import { RenderedShape } from './rendered-shape'
|
||||||
import { EditingTextShape } from './editing-text-shape'
|
import { EditingTextShape } from './editing-text-shape'
|
||||||
|
|
||||||
export const Shape = React.memo(
|
export const Shape = <M extends Record<string, unknown>>({
|
||||||
<M extends Record<string, unknown>>({
|
shape,
|
||||||
shape,
|
isEditing,
|
||||||
isEditing,
|
isBinding,
|
||||||
isBinding,
|
isHovered,
|
||||||
isHovered,
|
isSelected,
|
||||||
isSelected,
|
isCurrentParent,
|
||||||
isCurrentParent,
|
meta,
|
||||||
meta,
|
}: IShapeTreeNode<M>) => {
|
||||||
}: IShapeTreeNode<M>) => {
|
const { shapeUtils } = useTLContext()
|
||||||
const { shapeUtils } = useTLContext()
|
const events = useShapeEvents(shape.id, isCurrentParent)
|
||||||
const events = useShapeEvents(shape.id, isCurrentParent)
|
const utils = shapeUtils[shape.type]
|
||||||
const utils = shapeUtils[shape.type]
|
|
||||||
|
|
||||||
const center = utils.getCenter(shape)
|
const center = utils.getCenter(shape)
|
||||||
const rotation = (shape.rotation || 0) * (180 / Math.PI)
|
const rotation = (shape.rotation || 0) * (180 / Math.PI)
|
||||||
const transform = `rotate(${rotation}, ${center}) translate(${shape.point})`
|
const transform = `rotate(${rotation}, ${center}) translate(${shape.point})`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g
|
<g
|
||||||
className={isCurrentParent ? 'tl-shape-group tl-current-parent' : 'tl-shape-group'}
|
className={isCurrentParent ? 'tl-shape-group tl-current-parent' : 'tl-shape-group'}
|
||||||
id={shape.id}
|
id={shape.id}
|
||||||
transform={transform}
|
transform={transform}
|
||||||
{...events}
|
{...events}
|
||||||
>
|
>
|
||||||
{isEditing && utils.isEditableText ? (
|
{isEditing && utils.isEditableText ? (
|
||||||
<EditingTextShape
|
<EditingTextShape
|
||||||
shape={shape}
|
shape={shape}
|
||||||
isBinding={false}
|
isBinding={false}
|
||||||
isCurrentParent={false}
|
isCurrentParent={false}
|
||||||
isEditing={true}
|
isEditing={true}
|
||||||
isHovered={isHovered}
|
isHovered={isHovered}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
utils={utils}
|
utils={utils}
|
||||||
meta={meta}
|
meta={meta}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<RenderedShape
|
<RenderedShape
|
||||||
shape={shape}
|
shape={shape}
|
||||||
utils={utils}
|
utils={utils}
|
||||||
isBinding={isBinding}
|
isBinding={isBinding}
|
||||||
isCurrentParent={isCurrentParent}
|
isCurrentParent={isCurrentParent}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
isHovered={isHovered}
|
isHovered={isHovered}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
meta={meta}
|
meta={meta}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</g>
|
</g>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { inputs } from '+inputs'
|
import { inputs } from '+inputs'
|
||||||
import { useTLContext } from './useTLContext'
|
|
||||||
import { Utils } from '+utils'
|
import { Utils } from '+utils'
|
||||||
|
import { TLContext } from '+hooks'
|
||||||
|
|
||||||
export function useShapeEvents(id: string, disable = false) {
|
export function useShapeEvents(id: string, disable = false) {
|
||||||
const { rPageState, rScreenBounds, callbacks } = useTLContext()
|
const { rPageState, rScreenBounds, callbacks } = React.useContext(TLContext)
|
||||||
|
|
||||||
const onPointerDown = React.useCallback(
|
const onPointerDown = React.useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as React from 'react'
|
||||||
import type { TLCallbacks, TLShape, TLBounds, TLPageState, TLShapeUtils } from '+types'
|
import type { TLCallbacks, TLShape, TLBounds, TLPageState, TLShapeUtils } from '+types'
|
||||||
|
|
||||||
export interface TLContextType {
|
export interface TLContextType {
|
||||||
|
id?: string
|
||||||
callbacks: Partial<TLCallbacks>
|
callbacks: Partial<TLCallbacks>
|
||||||
shapeUtils: TLShapeUtils<TLShape>
|
shapeUtils: TLShapeUtils<TLShape>
|
||||||
rPageState: React.MutableRefObject<TLPageState>
|
rPageState: React.MutableRefObject<TLPageState>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import Controlled from './controlled'
|
import Controlled from './controlled'
|
||||||
import Basic from './basic'
|
import Basic from './basic'
|
||||||
|
import NewId from './newId'
|
||||||
|
|
||||||
export default function App(): JSX.Element {
|
export default function App(): JSX.Element {
|
||||||
return <Controlled />
|
return <NewId />
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import * as React from 'react'
|
||||||
|
import { TLDraw } from '@tldraw/tldraw'
|
||||||
|
|
||||||
|
export default function NewId() {
|
||||||
|
const [id, setId] = React.useState('example')
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => setId('example2'), 2000)
|
||||||
|
|
||||||
|
return () => clearTimeout(timeout)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <TLDraw id={id} />
|
||||||
|
}
|
|
@ -359,8 +359,6 @@ function MoveToPageMenu(): JSX.Element | null {
|
||||||
|
|
||||||
if (sorted.length === 0) return null
|
if (sorted.length === 0) return null
|
||||||
|
|
||||||
console.log(sorted)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenuRoot>
|
<ContextMenuRoot>
|
||||||
<RadixContextMenu.TriggerItem as={RowButton} bp={breakpoints}>
|
<RadixContextMenu.TriggerItem as={RowButton} bp={breakpoints}>
|
||||||
|
|
|
@ -51,29 +51,41 @@ export interface TLDrawProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TLDraw({ id, document, currentPageId, onMount, onChange }: TLDrawProps) {
|
export function TLDraw({ id, document, currentPageId, onMount, onChange }: TLDrawProps) {
|
||||||
|
const [sId, setSId] = React.useState(id)
|
||||||
const [tlstate, setTlstate] = React.useState(() => new TLDrawState(id))
|
const [tlstate, setTlstate] = React.useState(() => new TLDrawState(id))
|
||||||
|
const [context, setContext] = React.useState(() => ({ tlstate, useSelector: tlstate.useStore }))
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setTlstate(new TLDrawState(id, onChange, onMount))
|
if (id === sId) return
|
||||||
}, [id])
|
// If a new id is loaded, replace the entire state
|
||||||
|
const newState = new TLDrawState(id, onChange, onMount)
|
||||||
|
setTlstate(newState)
|
||||||
|
setContext({ tlstate: newState, useSelector: newState.useStore })
|
||||||
|
setSId(id)
|
||||||
|
}, [sId, id])
|
||||||
|
|
||||||
const [context] = React.useState(() => {
|
// Use the `key` to ensure that new selector hooks are made when the id changes
|
||||||
return { tlstate, useSelector: tlstate.useStore }
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TLDrawContext.Provider value={context}>
|
<TLDrawContext.Provider value={context}>
|
||||||
<IdProvider>
|
<IdProvider>
|
||||||
<InnerTldraw currentPageId={currentPageId} document={document} />
|
<InnerTldraw
|
||||||
|
key={sId || 'tldraw'}
|
||||||
|
id={sId}
|
||||||
|
currentPageId={currentPageId}
|
||||||
|
document={document}
|
||||||
|
/>
|
||||||
</IdProvider>
|
</IdProvider>
|
||||||
</TLDrawContext.Provider>
|
</TLDrawContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function InnerTldraw({
|
function InnerTldraw({
|
||||||
|
id,
|
||||||
currentPageId,
|
currentPageId,
|
||||||
document,
|
document,
|
||||||
}: {
|
}: {
|
||||||
|
id?: string
|
||||||
currentPageId?: string
|
currentPageId?: string
|
||||||
document?: TLDrawDocument
|
document?: TLDrawDocument
|
||||||
}) {
|
}) {
|
||||||
|
@ -138,10 +150,16 @@ function InnerTldraw({
|
||||||
tlstate.changePage(currentPageId)
|
tlstate.changePage(currentPageId)
|
||||||
}, [currentPageId, tlstate])
|
}, [currentPageId, tlstate])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
'Id Changed!'
|
||||||
|
console.log(id, tlstate.id)
|
||||||
|
}, [id])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<Renderer
|
<Renderer
|
||||||
|
id={id}
|
||||||
page={page}
|
page={page}
|
||||||
pageState={pageState}
|
pageState={pageState}
|
||||||
shapeUtils={tldrawShapeUtils}
|
shapeUtils={tldrawShapeUtils}
|
||||||
|
|
|
@ -7,14 +7,14 @@ const statusSelector = (s: Data) => s.appState.status.current
|
||||||
const activeToolSelector = (s: Data) => s.appState.activeTool
|
const activeToolSelector = (s: Data) => s.appState.activeTool
|
||||||
|
|
||||||
export function StatusBar(): JSX.Element | null {
|
export function StatusBar(): JSX.Element | null {
|
||||||
const { useSelector } = useTLDrawContext()
|
const { tlstate, useSelector } = useTLDrawContext()
|
||||||
const status = useSelector(statusSelector)
|
const status = useSelector(statusSelector)
|
||||||
const activeTool = useSelector(activeToolSelector)
|
const activeTool = useSelector(activeToolSelector)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusBarContainer size={{ '@sm': 'small' }}>
|
<StatusBarContainer size={{ '@sm': 'small' }}>
|
||||||
<Section>
|
<Section>
|
||||||
{activeTool} | {status}
|
{tlstate.id} | {activeTool} | {status}
|
||||||
</Section>
|
</Section>
|
||||||
</StatusBarContainer>
|
</StatusBarContainer>
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,11 +32,15 @@ export const ToolsPanel = React.memo((): JSX.Element => {
|
||||||
|
|
||||||
const isDebugMode = useSelector(isDebugModeSelector)
|
const isDebugMode = useSelector(isDebugModeSelector)
|
||||||
|
|
||||||
|
console.log(activeTool)
|
||||||
|
|
||||||
const selectSelectTool = React.useCallback(() => {
|
const selectSelectTool = React.useCallback(() => {
|
||||||
|
console.log(tlstate.id)
|
||||||
tlstate.selectTool('select')
|
tlstate.selectTool('select')
|
||||||
}, [tlstate])
|
}, [tlstate])
|
||||||
|
|
||||||
const selectDrawTool = React.useCallback(() => {
|
const selectDrawTool = React.useCallback(() => {
|
||||||
|
console.log(tlstate.id)
|
||||||
tlstate.selectTool(TLDrawShapeType.Draw)
|
tlstate.selectTool(TLDrawShapeType.Draw)
|
||||||
}, [tlstate])
|
}, [tlstate])
|
||||||
|
|
||||||
|
|
|
@ -29,195 +29,390 @@ export function useKeyboardShortcuts() {
|
||||||
|
|
||||||
/* ---------------------- Tools --------------------- */
|
/* ---------------------- Tools --------------------- */
|
||||||
|
|
||||||
useHotkeys('v,1', () => {
|
useHotkeys(
|
||||||
tlstate.selectTool('select')
|
'v,1',
|
||||||
})
|
() => {
|
||||||
|
tlstate.selectTool('select')
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('d,2', () => {
|
useHotkeys(
|
||||||
tlstate.selectTool(TLDrawShapeType.Draw)
|
'd,2',
|
||||||
})
|
() => {
|
||||||
|
tlstate.selectTool(TLDrawShapeType.Draw)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('r,3', () => {
|
useHotkeys(
|
||||||
tlstate.selectTool(TLDrawShapeType.Rectangle)
|
'r,3',
|
||||||
})
|
() => {
|
||||||
|
tlstate.selectTool(TLDrawShapeType.Rectangle)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('e,4', () => {
|
useHotkeys(
|
||||||
tlstate.selectTool(TLDrawShapeType.Ellipse)
|
'e,4',
|
||||||
})
|
() => {
|
||||||
|
tlstate.selectTool(TLDrawShapeType.Ellipse)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('a,5', () => {
|
useHotkeys(
|
||||||
tlstate.selectTool(TLDrawShapeType.Arrow)
|
'a,5',
|
||||||
})
|
() => {
|
||||||
|
tlstate.selectTool(TLDrawShapeType.Arrow)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('t,6', () => {
|
useHotkeys(
|
||||||
tlstate.selectTool(TLDrawShapeType.Text)
|
't,6',
|
||||||
})
|
() => {
|
||||||
|
tlstate.selectTool(TLDrawShapeType.Text)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
/* ---------------------- Misc ---------------------- */
|
/* ---------------------- Misc ---------------------- */
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
|
|
||||||
useHotkeys('ctrl+s,command+s', () => {
|
useHotkeys(
|
||||||
tlstate.saveProject()
|
'ctrl+s,command+s',
|
||||||
})
|
() => {
|
||||||
|
tlstate.saveProject()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Undo Redo
|
// Undo Redo
|
||||||
|
|
||||||
useHotkeys('command+z,ctrl+z', () => {
|
useHotkeys(
|
||||||
tlstate.undo()
|
'command+z,ctrl+z',
|
||||||
})
|
() => {
|
||||||
|
tlstate.undo()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('ctrl+shift-z,command+shift+z', () => {
|
useHotkeys(
|
||||||
tlstate.redo()
|
'ctrl+shift-z,command+shift+z',
|
||||||
})
|
() => {
|
||||||
|
tlstate.redo()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Undo Redo
|
// Undo Redo
|
||||||
|
|
||||||
useHotkeys('command+u,ctrl+u', () => {
|
useHotkeys(
|
||||||
tlstate.undoSelect()
|
'command+u,ctrl+u',
|
||||||
})
|
() => {
|
||||||
|
tlstate.undoSelect()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('ctrl+shift-u,command+shift+u', () => {
|
useHotkeys(
|
||||||
tlstate.redoSelect()
|
'ctrl+shift-u,command+shift+u',
|
||||||
})
|
() => {
|
||||||
|
tlstate.redoSelect()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
/* -------------------- Commands -------------------- */
|
/* -------------------- Commands -------------------- */
|
||||||
|
|
||||||
// Camera
|
// Camera
|
||||||
|
|
||||||
useHotkeys('ctrl+=,command+=', (e) => {
|
useHotkeys(
|
||||||
tlstate.zoomIn()
|
'ctrl+=,command+=',
|
||||||
e.preventDefault()
|
(e) => {
|
||||||
})
|
tlstate.zoomIn()
|
||||||
|
e.preventDefault()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('ctrl+-,command+-', (e) => {
|
useHotkeys(
|
||||||
tlstate.zoomOut()
|
'ctrl+-,command+-',
|
||||||
e.preventDefault()
|
(e) => {
|
||||||
})
|
tlstate.zoomOut()
|
||||||
|
e.preventDefault()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('shift+1', () => {
|
useHotkeys(
|
||||||
tlstate.zoomToFit()
|
'shift+1',
|
||||||
})
|
() => {
|
||||||
|
tlstate.zoomToFit()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('shift+2', () => {
|
useHotkeys(
|
||||||
tlstate.zoomToSelection()
|
'shift+2',
|
||||||
})
|
() => {
|
||||||
|
tlstate.zoomToSelection()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('shift+0', () => {
|
useHotkeys(
|
||||||
tlstate.zoomToActual()
|
'shift+0',
|
||||||
})
|
() => {
|
||||||
|
tlstate.zoomToActual()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Duplicate
|
// Duplicate
|
||||||
|
|
||||||
useHotkeys('ctrl+d,command+d', (e) => {
|
useHotkeys(
|
||||||
tlstate.duplicate()
|
'ctrl+d,command+d',
|
||||||
e.preventDefault()
|
(e) => {
|
||||||
})
|
tlstate.duplicate()
|
||||||
|
e.preventDefault()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Flip
|
// Flip
|
||||||
|
|
||||||
useHotkeys('shift+h', () => {
|
useHotkeys(
|
||||||
tlstate.flipHorizontal()
|
'shift+h',
|
||||||
})
|
() => {
|
||||||
|
tlstate.flipHorizontal()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('shift+v', () => {
|
useHotkeys(
|
||||||
tlstate.flipVertical()
|
'shift+v',
|
||||||
})
|
() => {
|
||||||
|
tlstate.flipVertical()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Cancel
|
// Cancel
|
||||||
|
|
||||||
useHotkeys('escape', () => {
|
useHotkeys(
|
||||||
tlstate.cancel()
|
'escape',
|
||||||
})
|
() => {
|
||||||
|
tlstate.cancel()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Delete
|
// Delete
|
||||||
|
|
||||||
useHotkeys('backspace', () => {
|
useHotkeys(
|
||||||
tlstate.delete()
|
'backspace',
|
||||||
})
|
() => {
|
||||||
|
tlstate.delete()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Select All
|
// Select All
|
||||||
|
|
||||||
useHotkeys('command+a,ctrl+a', () => {
|
useHotkeys(
|
||||||
tlstate.selectAll()
|
'command+a,ctrl+a',
|
||||||
})
|
() => {
|
||||||
|
tlstate.selectAll()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Nudge
|
// Nudge
|
||||||
|
|
||||||
useHotkeys('up', () => {
|
useHotkeys(
|
||||||
tlstate.nudge([0, -1], false)
|
'up',
|
||||||
})
|
() => {
|
||||||
|
tlstate.nudge([0, -1], false)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('right', () => {
|
useHotkeys(
|
||||||
tlstate.nudge([1, 0], false)
|
'right',
|
||||||
})
|
() => {
|
||||||
|
tlstate.nudge([1, 0], false)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('down', () => {
|
useHotkeys(
|
||||||
tlstate.nudge([0, 1], false)
|
'down',
|
||||||
})
|
() => {
|
||||||
|
tlstate.nudge([0, 1], false)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('left', () => {
|
useHotkeys(
|
||||||
tlstate.nudge([-1, 0], false)
|
'left',
|
||||||
})
|
() => {
|
||||||
|
tlstate.nudge([-1, 0], false)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('shift+up', () => {
|
useHotkeys(
|
||||||
tlstate.nudge([0, -1], true)
|
'shift+up',
|
||||||
})
|
() => {
|
||||||
|
tlstate.nudge([0, -1], true)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('shift+right', () => {
|
useHotkeys(
|
||||||
tlstate.nudge([1, 0], true)
|
'shift+right',
|
||||||
})
|
() => {
|
||||||
|
tlstate.nudge([1, 0], true)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('shift+down', () => {
|
useHotkeys(
|
||||||
tlstate.nudge([0, 1], true)
|
'shift+down',
|
||||||
})
|
() => {
|
||||||
|
tlstate.nudge([0, 1], true)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('shift+left', () => {
|
useHotkeys(
|
||||||
tlstate.nudge([-1, 0], true)
|
'shift+left',
|
||||||
})
|
() => {
|
||||||
|
tlstate.nudge([-1, 0], true)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Copy & Paste
|
// Copy & Paste
|
||||||
|
|
||||||
useHotkeys('command+c,ctrl+c', () => {
|
useHotkeys(
|
||||||
tlstate.copy()
|
'command+c,ctrl+c',
|
||||||
})
|
() => {
|
||||||
|
tlstate.copy()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('command+v,ctrl+v', () => {
|
useHotkeys(
|
||||||
tlstate.paste()
|
'command+v,ctrl+v',
|
||||||
})
|
() => {
|
||||||
|
tlstate.paste()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Group & Ungroup
|
// Group & Ungroup
|
||||||
|
|
||||||
useHotkeys('command+g,ctrl+g', (e) => {
|
useHotkeys(
|
||||||
tlstate.group()
|
'command+g,ctrl+g',
|
||||||
e.preventDefault()
|
(e) => {
|
||||||
})
|
tlstate.group()
|
||||||
|
e.preventDefault()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('command+shift+g,ctrl+shift+g', (e) => {
|
useHotkeys(
|
||||||
tlstate.ungroup()
|
'command+shift+g,ctrl+shift+g',
|
||||||
e.preventDefault()
|
(e) => {
|
||||||
})
|
tlstate.ungroup()
|
||||||
|
e.preventDefault()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
// Move
|
// Move
|
||||||
|
|
||||||
useHotkeys('[', () => {
|
useHotkeys(
|
||||||
tlstate.moveBackward()
|
'[',
|
||||||
})
|
() => {
|
||||||
|
tlstate.moveBackward()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys(']', () => {
|
useHotkeys(
|
||||||
tlstate.moveForward()
|
']',
|
||||||
})
|
() => {
|
||||||
|
tlstate.moveForward()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('shift+[', () => {
|
useHotkeys(
|
||||||
tlstate.moveToBack()
|
'shift+[',
|
||||||
})
|
() => {
|
||||||
|
tlstate.moveToBack()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('shift+]', () => {
|
useHotkeys(
|
||||||
tlstate.moveToFront()
|
'shift+]',
|
||||||
})
|
() => {
|
||||||
|
tlstate.moveToFront()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
|
|
||||||
useHotkeys('command+shift+backspace', (e) => {
|
useHotkeys(
|
||||||
tlstate.resetDocument()
|
'command+shift+backspace',
|
||||||
e.preventDefault()
|
(e) => {
|
||||||
})
|
tlstate.resetDocument()
|
||||||
|
e.preventDefault()
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
[tlstate]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -445,4 +445,27 @@ describe('TLDrawState', () => {
|
||||||
|
|
||||||
expect(tlstate2.shapes.length).toBe(1)
|
expect(tlstate2.shapes.length).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('when the document prop changes', () => {
|
||||||
|
it.todo('updates the document if the new id is the same as the old one')
|
||||||
|
|
||||||
|
it.todo('replaces the document if the ids are different')
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
We want to be able to use the `document` property to update the
|
||||||
|
document without blowing out the current state. For example, we
|
||||||
|
may want to patch in changes that occurred from another user.
|
||||||
|
|
||||||
|
When the `document` prop changes in the TLDraw component, we want
|
||||||
|
to update the document in a way that preserves the identity of as
|
||||||
|
much as possible, while still protecting against invalid states.
|
||||||
|
|
||||||
|
If this isn't possible, then we should guide the developer to
|
||||||
|
instead use a helper like `patchDocument` to update the document.
|
||||||
|
|
||||||
|
If the `id` property of the new document is the same as the
|
||||||
|
previous document, then we call `updateDocument`. Otherwise, we
|
||||||
|
call `replaceDocument`, which does a harder reset of the state's
|
||||||
|
internal state.
|
||||||
|
*/
|
||||||
})
|
})
|
||||||
|
|
|
@ -97,6 +97,10 @@ const defaultState: Data = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TLDrawState extends StateManager<Data> {
|
export class TLDrawState extends StateManager<Data> {
|
||||||
|
get id() {
|
||||||
|
return this._idbId
|
||||||
|
}
|
||||||
|
|
||||||
private _onChange?: (tlstate: TLDrawState, data: Data, reason: string) => void
|
private _onChange?: (tlstate: TLDrawState, data: Data, reason: string) => void
|
||||||
private _onMount?: (tlstate: TLDrawState) => void
|
private _onMount?: (tlstate: TLDrawState) => void
|
||||||
|
|
||||||
|
@ -482,62 +486,50 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
* @param document
|
* @param document
|
||||||
*/
|
*/
|
||||||
updateDocument = (document: TLDrawDocument, reason = 'updated_document'): this => {
|
updateDocument = (document: TLDrawDocument, reason = 'updated_document'): this => {
|
||||||
console.log(reason)
|
const prevState = this.state
|
||||||
|
|
||||||
const state = this.state
|
const nextState = { ...prevState, document: { ...prevState.document } }
|
||||||
|
|
||||||
const currentPageId = document.pages[this.currentPageId]
|
if (!document.pages[this.currentPageId]) {
|
||||||
? this.currentPageId
|
nextState.appState = {
|
||||||
: Object.keys(document.pages)[0]
|
...prevState.appState,
|
||||||
|
currentPageId: Object.keys(document.pages)[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.replaceState(
|
let i = 1
|
||||||
{
|
|
||||||
...this.state,
|
|
||||||
appState: {
|
|
||||||
...this.appState,
|
|
||||||
currentPageId,
|
|
||||||
},
|
|
||||||
document: {
|
|
||||||
...document,
|
|
||||||
pages: Object.fromEntries(
|
|
||||||
Object.entries(document.pages)
|
|
||||||
.sort((a, b) => (a[1].childIndex || 0) - (b[1].childIndex || 0))
|
|
||||||
.map(([pageId, page], i) => {
|
|
||||||
const nextPage = { ...page }
|
|
||||||
if (!nextPage.name) nextPage.name = `Page ${i + 1}`
|
|
||||||
return [pageId, nextPage]
|
|
||||||
})
|
|
||||||
),
|
|
||||||
pageStates: Object.fromEntries(
|
|
||||||
Object.entries(document.pageStates).map(([pageId, pageState]) => {
|
|
||||||
const page = document.pages[pageId]
|
|
||||||
const nextPageState = { ...pageState }
|
|
||||||
const keysToCheck = ['bindingId', 'editingId', 'hoveredId', 'pointedId'] as const
|
|
||||||
|
|
||||||
for (const key of keysToCheck) {
|
for (const nextPage of Object.values(document.pages)) {
|
||||||
if (!page.shapes[key]) {
|
if (nextPage !== prevState.document.pages[nextPage.id]) {
|
||||||
nextPageState[key] = undefined
|
nextState.document.pages[nextPage.id] = nextPage
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nextPageState.selectedIds = pageState.selectedIds.filter(
|
if (!nextPage.name) {
|
||||||
(id) => !!document.pages[pageId].shapes[id]
|
nextState.document.pages[nextPage.id].name = `Page ${i + 1}`
|
||||||
)
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [pageId, nextPageState]
|
for (const nextPageState of Object.values(document.pageStates)) {
|
||||||
})
|
if (nextPageState !== prevState.document.pageStates[nextPageState.id]) {
|
||||||
),
|
nextState.document.pageStates[nextPageState.id] = nextPageState
|
||||||
},
|
|
||||||
},
|
|
||||||
`${reason}:${document.id}`
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(
|
const nextPage = document.pages[nextPageState.id]
|
||||||
'did page change?',
|
const keysToCheck = ['bindingId', 'editingId', 'hoveredId', 'pointedId'] as const
|
||||||
this.state.document.pages['page1'] !== state.document.pages['page1']
|
|
||||||
)
|
|
||||||
|
|
||||||
return this
|
for (const key of keysToCheck) {
|
||||||
|
if (!nextPage.shapes[key]) {
|
||||||
|
nextPageState[key] = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPageState.selectedIds = nextPageState.selectedIds.filter(
|
||||||
|
(id) => !!document.pages[nextPage.id].shapes[id]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.replaceState(nextState, `${reason}:${document.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Ładowanie…
Reference in New Issue