Allow for resets when id changes
This commit is contained in:
parent
2aeb513342
commit
4e13b0e07b
15 changed files with 509 additions and 244 deletions
|
@ -26,7 +26,7 @@ interface CanvasProps<T extends TLShape> {
|
|||
meta?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const Canvas = React.memo(function Canvas<T extends TLShape>({
|
||||
export function Canvas<T extends TLShape>({
|
||||
page,
|
||||
pageState,
|
||||
meta,
|
||||
|
@ -66,4 +66,4 @@ export const Canvas = React.memo(function Canvas<T extends TLShape>({
|
|||
</svg>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,12 +18,7 @@ interface PageProps<T extends TLShape> {
|
|||
|
||||
/**
|
||||
* The Page component renders the current page.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
*```ts
|
||||
* example
|
||||
*``` */
|
||||
*/
|
||||
export function Page<T extends TLShape>({
|
||||
page,
|
||||
pageState,
|
||||
|
|
|
@ -10,10 +10,15 @@ import type {
|
|||
TLBinding,
|
||||
} from '../../types'
|
||||
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>>
|
||||
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.
|
||||
*/
|
||||
|
@ -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.
|
||||
*/
|
||||
meta?: M
|
||||
// Temp
|
||||
onTest?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,6 +70,7 @@ export interface RendererProps<T extends TLShape, M extends Record<string, unkno
|
|||
* @returns
|
||||
*/
|
||||
export function Renderer<T extends TLShape, M extends Record<string, unknown>>({
|
||||
id,
|
||||
shapeUtils,
|
||||
page,
|
||||
pageState,
|
||||
|
@ -82,13 +90,31 @@ export function Renderer<T extends TLShape, M extends Record<string, unknown>>({
|
|||
rPageState.current = pageState
|
||||
}, [pageState])
|
||||
|
||||
const [context] = React.useState(() => ({
|
||||
const rId = React.useRef(id)
|
||||
|
||||
const [context, setContext] = React.useState<TLContextType>(() => ({
|
||||
id,
|
||||
callbacks: rest,
|
||||
shapeUtils,
|
||||
rScreenBounds,
|
||||
rPageState,
|
||||
}))
|
||||
|
||||
React.useEffect(() => {
|
||||
if (id !== rId.current) {
|
||||
rest.onTest?.()
|
||||
setContext({
|
||||
id,
|
||||
callbacks: rest,
|
||||
shapeUtils,
|
||||
rScreenBounds,
|
||||
rPageState,
|
||||
})
|
||||
|
||||
rId.current = id
|
||||
}
|
||||
}, [id])
|
||||
|
||||
return (
|
||||
<TLContext.Provider value={context}>
|
||||
<Canvas
|
||||
|
|
|
@ -4,55 +4,53 @@ import type { IShapeTreeNode } from '+types'
|
|||
import { RenderedShape } from './rendered-shape'
|
||||
import { EditingTextShape } from './editing-text-shape'
|
||||
|
||||
export const Shape = React.memo(
|
||||
<M extends Record<string, unknown>>({
|
||||
shape,
|
||||
isEditing,
|
||||
isBinding,
|
||||
isHovered,
|
||||
isSelected,
|
||||
isCurrentParent,
|
||||
meta,
|
||||
}: IShapeTreeNode<M>) => {
|
||||
const { shapeUtils } = useTLContext()
|
||||
const events = useShapeEvents(shape.id, isCurrentParent)
|
||||
const utils = shapeUtils[shape.type]
|
||||
export const Shape = <M extends Record<string, unknown>>({
|
||||
shape,
|
||||
isEditing,
|
||||
isBinding,
|
||||
isHovered,
|
||||
isSelected,
|
||||
isCurrentParent,
|
||||
meta,
|
||||
}: IShapeTreeNode<M>) => {
|
||||
const { shapeUtils } = useTLContext()
|
||||
const events = useShapeEvents(shape.id, isCurrentParent)
|
||||
const utils = shapeUtils[shape.type]
|
||||
|
||||
const center = utils.getCenter(shape)
|
||||
const rotation = (shape.rotation || 0) * (180 / Math.PI)
|
||||
const transform = `rotate(${rotation}, ${center}) translate(${shape.point})`
|
||||
const center = utils.getCenter(shape)
|
||||
const rotation = (shape.rotation || 0) * (180 / Math.PI)
|
||||
const transform = `rotate(${rotation}, ${center}) translate(${shape.point})`
|
||||
|
||||
return (
|
||||
<g
|
||||
className={isCurrentParent ? 'tl-shape-group tl-current-parent' : 'tl-shape-group'}
|
||||
id={shape.id}
|
||||
transform={transform}
|
||||
{...events}
|
||||
>
|
||||
{isEditing && utils.isEditableText ? (
|
||||
<EditingTextShape
|
||||
shape={shape}
|
||||
isBinding={false}
|
||||
isCurrentParent={false}
|
||||
isEditing={true}
|
||||
isHovered={isHovered}
|
||||
isSelected={isSelected}
|
||||
utils={utils}
|
||||
meta={meta}
|
||||
/>
|
||||
) : (
|
||||
<RenderedShape
|
||||
shape={shape}
|
||||
utils={utils}
|
||||
isBinding={isBinding}
|
||||
isCurrentParent={isCurrentParent}
|
||||
isEditing={isEditing}
|
||||
isHovered={isHovered}
|
||||
isSelected={isSelected}
|
||||
meta={meta}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
)
|
||||
return (
|
||||
<g
|
||||
className={isCurrentParent ? 'tl-shape-group tl-current-parent' : 'tl-shape-group'}
|
||||
id={shape.id}
|
||||
transform={transform}
|
||||
{...events}
|
||||
>
|
||||
{isEditing && utils.isEditableText ? (
|
||||
<EditingTextShape
|
||||
shape={shape}
|
||||
isBinding={false}
|
||||
isCurrentParent={false}
|
||||
isEditing={true}
|
||||
isHovered={isHovered}
|
||||
isSelected={isSelected}
|
||||
utils={utils}
|
||||
meta={meta}
|
||||
/>
|
||||
) : (
|
||||
<RenderedShape
|
||||
shape={shape}
|
||||
utils={utils}
|
||||
isBinding={isBinding}
|
||||
isCurrentParent={isCurrentParent}
|
||||
isEditing={isEditing}
|
||||
isHovered={isHovered}
|
||||
isSelected={isSelected}
|
||||
meta={meta}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react'
|
||||
import { inputs } from '+inputs'
|
||||
import { useTLContext } from './useTLContext'
|
||||
import { Utils } from '+utils'
|
||||
import { TLContext } from '+hooks'
|
||||
|
||||
export function useShapeEvents(id: string, disable = false) {
|
||||
const { rPageState, rScreenBounds, callbacks } = useTLContext()
|
||||
const { rPageState, rScreenBounds, callbacks } = React.useContext(TLContext)
|
||||
|
||||
const onPointerDown = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as React from 'react'
|
|||
import type { TLCallbacks, TLShape, TLBounds, TLPageState, TLShapeUtils } from '+types'
|
||||
|
||||
export interface TLContextType {
|
||||
id?: string
|
||||
callbacks: Partial<TLCallbacks>
|
||||
shapeUtils: TLShapeUtils<TLShape>
|
||||
rPageState: React.MutableRefObject<TLPageState>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import * as React from 'react'
|
||||
import Controlled from './controlled'
|
||||
import Basic from './basic'
|
||||
import NewId from './newId'
|
||||
|
||||
export default function App(): JSX.Element {
|
||||
return <Controlled />
|
||||
return <NewId />
|
||||
}
|
||||
|
|
14
packages/dev/src/newId.tsx
Normal file
14
packages/dev/src/newId.tsx
Normal file
|
@ -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
|
||||
|
||||
console.log(sorted)
|
||||
|
||||
return (
|
||||
<ContextMenuRoot>
|
||||
<RadixContextMenu.TriggerItem as={RowButton} bp={breakpoints}>
|
||||
|
|
|
@ -51,29 +51,41 @@ export interface 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 [context, setContext] = React.useState(() => ({ tlstate, useSelector: tlstate.useStore }))
|
||||
|
||||
React.useEffect(() => {
|
||||
setTlstate(new TLDrawState(id, onChange, onMount))
|
||||
}, [id])
|
||||
if (id === sId) return
|
||||
// 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(() => {
|
||||
return { tlstate, useSelector: tlstate.useStore }
|
||||
})
|
||||
// Use the `key` to ensure that new selector hooks are made when the id changes
|
||||
|
||||
return (
|
||||
<TLDrawContext.Provider value={context}>
|
||||
<IdProvider>
|
||||
<InnerTldraw currentPageId={currentPageId} document={document} />
|
||||
<InnerTldraw
|
||||
key={sId || 'tldraw'}
|
||||
id={sId}
|
||||
currentPageId={currentPageId}
|
||||
document={document}
|
||||
/>
|
||||
</IdProvider>
|
||||
</TLDrawContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function InnerTldraw({
|
||||
id,
|
||||
currentPageId,
|
||||
document,
|
||||
}: {
|
||||
id?: string
|
||||
currentPageId?: string
|
||||
document?: TLDrawDocument
|
||||
}) {
|
||||
|
@ -138,10 +150,16 @@ function InnerTldraw({
|
|||
tlstate.changePage(currentPageId)
|
||||
}, [currentPageId, tlstate])
|
||||
|
||||
React.useEffect(() => {
|
||||
'Id Changed!'
|
||||
console.log(id, tlstate.id)
|
||||
}, [id])
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<ContextMenu>
|
||||
<Renderer
|
||||
id={id}
|
||||
page={page}
|
||||
pageState={pageState}
|
||||
shapeUtils={tldrawShapeUtils}
|
||||
|
|
|
@ -7,14 +7,14 @@ const statusSelector = (s: Data) => s.appState.status.current
|
|||
const activeToolSelector = (s: Data) => s.appState.activeTool
|
||||
|
||||
export function StatusBar(): JSX.Element | null {
|
||||
const { useSelector } = useTLDrawContext()
|
||||
const { tlstate, useSelector } = useTLDrawContext()
|
||||
const status = useSelector(statusSelector)
|
||||
const activeTool = useSelector(activeToolSelector)
|
||||
|
||||
return (
|
||||
<StatusBarContainer size={{ '@sm': 'small' }}>
|
||||
<Section>
|
||||
{activeTool} | {status}
|
||||
{tlstate.id} | {activeTool} | {status}
|
||||
</Section>
|
||||
</StatusBarContainer>
|
||||
)
|
||||
|
|
|
@ -32,11 +32,15 @@ export const ToolsPanel = React.memo((): JSX.Element => {
|
|||
|
||||
const isDebugMode = useSelector(isDebugModeSelector)
|
||||
|
||||
console.log(activeTool)
|
||||
|
||||
const selectSelectTool = React.useCallback(() => {
|
||||
console.log(tlstate.id)
|
||||
tlstate.selectTool('select')
|
||||
}, [tlstate])
|
||||
|
||||
const selectDrawTool = React.useCallback(() => {
|
||||
console.log(tlstate.id)
|
||||
tlstate.selectTool(TLDrawShapeType.Draw)
|
||||
}, [tlstate])
|
||||
|
||||
|
|
|
@ -29,195 +29,390 @@ export function useKeyboardShortcuts() {
|
|||
|
||||
/* ---------------------- Tools --------------------- */
|
||||
|
||||
useHotkeys('v,1', () => {
|
||||
tlstate.selectTool('select')
|
||||
})
|
||||
useHotkeys(
|
||||
'v,1',
|
||||
() => {
|
||||
tlstate.selectTool('select')
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('d,2', () => {
|
||||
tlstate.selectTool(TLDrawShapeType.Draw)
|
||||
})
|
||||
useHotkeys(
|
||||
'd,2',
|
||||
() => {
|
||||
tlstate.selectTool(TLDrawShapeType.Draw)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('r,3', () => {
|
||||
tlstate.selectTool(TLDrawShapeType.Rectangle)
|
||||
})
|
||||
useHotkeys(
|
||||
'r,3',
|
||||
() => {
|
||||
tlstate.selectTool(TLDrawShapeType.Rectangle)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('e,4', () => {
|
||||
tlstate.selectTool(TLDrawShapeType.Ellipse)
|
||||
})
|
||||
useHotkeys(
|
||||
'e,4',
|
||||
() => {
|
||||
tlstate.selectTool(TLDrawShapeType.Ellipse)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('a,5', () => {
|
||||
tlstate.selectTool(TLDrawShapeType.Arrow)
|
||||
})
|
||||
useHotkeys(
|
||||
'a,5',
|
||||
() => {
|
||||
tlstate.selectTool(TLDrawShapeType.Arrow)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('t,6', () => {
|
||||
tlstate.selectTool(TLDrawShapeType.Text)
|
||||
})
|
||||
useHotkeys(
|
||||
't,6',
|
||||
() => {
|
||||
tlstate.selectTool(TLDrawShapeType.Text)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
/* ---------------------- Misc ---------------------- */
|
||||
|
||||
// Save
|
||||
|
||||
useHotkeys('ctrl+s,command+s', () => {
|
||||
tlstate.saveProject()
|
||||
})
|
||||
useHotkeys(
|
||||
'ctrl+s,command+s',
|
||||
() => {
|
||||
tlstate.saveProject()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Undo Redo
|
||||
|
||||
useHotkeys('command+z,ctrl+z', () => {
|
||||
tlstate.undo()
|
||||
})
|
||||
useHotkeys(
|
||||
'command+z,ctrl+z',
|
||||
() => {
|
||||
tlstate.undo()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('ctrl+shift-z,command+shift+z', () => {
|
||||
tlstate.redo()
|
||||
})
|
||||
useHotkeys(
|
||||
'ctrl+shift-z,command+shift+z',
|
||||
() => {
|
||||
tlstate.redo()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Undo Redo
|
||||
|
||||
useHotkeys('command+u,ctrl+u', () => {
|
||||
tlstate.undoSelect()
|
||||
})
|
||||
useHotkeys(
|
||||
'command+u,ctrl+u',
|
||||
() => {
|
||||
tlstate.undoSelect()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('ctrl+shift-u,command+shift+u', () => {
|
||||
tlstate.redoSelect()
|
||||
})
|
||||
useHotkeys(
|
||||
'ctrl+shift-u,command+shift+u',
|
||||
() => {
|
||||
tlstate.redoSelect()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
/* -------------------- Commands -------------------- */
|
||||
|
||||
// Camera
|
||||
|
||||
useHotkeys('ctrl+=,command+=', (e) => {
|
||||
tlstate.zoomIn()
|
||||
e.preventDefault()
|
||||
})
|
||||
useHotkeys(
|
||||
'ctrl+=,command+=',
|
||||
(e) => {
|
||||
tlstate.zoomIn()
|
||||
e.preventDefault()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('ctrl+-,command+-', (e) => {
|
||||
tlstate.zoomOut()
|
||||
e.preventDefault()
|
||||
})
|
||||
useHotkeys(
|
||||
'ctrl+-,command+-',
|
||||
(e) => {
|
||||
tlstate.zoomOut()
|
||||
e.preventDefault()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('shift+1', () => {
|
||||
tlstate.zoomToFit()
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+1',
|
||||
() => {
|
||||
tlstate.zoomToFit()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('shift+2', () => {
|
||||
tlstate.zoomToSelection()
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+2',
|
||||
() => {
|
||||
tlstate.zoomToSelection()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('shift+0', () => {
|
||||
tlstate.zoomToActual()
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+0',
|
||||
() => {
|
||||
tlstate.zoomToActual()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Duplicate
|
||||
|
||||
useHotkeys('ctrl+d,command+d', (e) => {
|
||||
tlstate.duplicate()
|
||||
e.preventDefault()
|
||||
})
|
||||
useHotkeys(
|
||||
'ctrl+d,command+d',
|
||||
(e) => {
|
||||
tlstate.duplicate()
|
||||
e.preventDefault()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Flip
|
||||
|
||||
useHotkeys('shift+h', () => {
|
||||
tlstate.flipHorizontal()
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+h',
|
||||
() => {
|
||||
tlstate.flipHorizontal()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('shift+v', () => {
|
||||
tlstate.flipVertical()
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+v',
|
||||
() => {
|
||||
tlstate.flipVertical()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Cancel
|
||||
|
||||
useHotkeys('escape', () => {
|
||||
tlstate.cancel()
|
||||
})
|
||||
useHotkeys(
|
||||
'escape',
|
||||
() => {
|
||||
tlstate.cancel()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Delete
|
||||
|
||||
useHotkeys('backspace', () => {
|
||||
tlstate.delete()
|
||||
})
|
||||
useHotkeys(
|
||||
'backspace',
|
||||
() => {
|
||||
tlstate.delete()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Select All
|
||||
|
||||
useHotkeys('command+a,ctrl+a', () => {
|
||||
tlstate.selectAll()
|
||||
})
|
||||
useHotkeys(
|
||||
'command+a,ctrl+a',
|
||||
() => {
|
||||
tlstate.selectAll()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Nudge
|
||||
|
||||
useHotkeys('up', () => {
|
||||
tlstate.nudge([0, -1], false)
|
||||
})
|
||||
useHotkeys(
|
||||
'up',
|
||||
() => {
|
||||
tlstate.nudge([0, -1], false)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('right', () => {
|
||||
tlstate.nudge([1, 0], false)
|
||||
})
|
||||
useHotkeys(
|
||||
'right',
|
||||
() => {
|
||||
tlstate.nudge([1, 0], false)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('down', () => {
|
||||
tlstate.nudge([0, 1], false)
|
||||
})
|
||||
useHotkeys(
|
||||
'down',
|
||||
() => {
|
||||
tlstate.nudge([0, 1], false)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('left', () => {
|
||||
tlstate.nudge([-1, 0], false)
|
||||
})
|
||||
useHotkeys(
|
||||
'left',
|
||||
() => {
|
||||
tlstate.nudge([-1, 0], false)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('shift+up', () => {
|
||||
tlstate.nudge([0, -1], true)
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+up',
|
||||
() => {
|
||||
tlstate.nudge([0, -1], true)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('shift+right', () => {
|
||||
tlstate.nudge([1, 0], true)
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+right',
|
||||
() => {
|
||||
tlstate.nudge([1, 0], true)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('shift+down', () => {
|
||||
tlstate.nudge([0, 1], true)
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+down',
|
||||
() => {
|
||||
tlstate.nudge([0, 1], true)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('shift+left', () => {
|
||||
tlstate.nudge([-1, 0], true)
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+left',
|
||||
() => {
|
||||
tlstate.nudge([-1, 0], true)
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Copy & Paste
|
||||
|
||||
useHotkeys('command+c,ctrl+c', () => {
|
||||
tlstate.copy()
|
||||
})
|
||||
useHotkeys(
|
||||
'command+c,ctrl+c',
|
||||
() => {
|
||||
tlstate.copy()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('command+v,ctrl+v', () => {
|
||||
tlstate.paste()
|
||||
})
|
||||
useHotkeys(
|
||||
'command+v,ctrl+v',
|
||||
() => {
|
||||
tlstate.paste()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Group & Ungroup
|
||||
|
||||
useHotkeys('command+g,ctrl+g', (e) => {
|
||||
tlstate.group()
|
||||
e.preventDefault()
|
||||
})
|
||||
useHotkeys(
|
||||
'command+g,ctrl+g',
|
||||
(e) => {
|
||||
tlstate.group()
|
||||
e.preventDefault()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('command+shift+g,ctrl+shift+g', (e) => {
|
||||
tlstate.ungroup()
|
||||
e.preventDefault()
|
||||
})
|
||||
useHotkeys(
|
||||
'command+shift+g,ctrl+shift+g',
|
||||
(e) => {
|
||||
tlstate.ungroup()
|
||||
e.preventDefault()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
// Move
|
||||
|
||||
useHotkeys('[', () => {
|
||||
tlstate.moveBackward()
|
||||
})
|
||||
useHotkeys(
|
||||
'[',
|
||||
() => {
|
||||
tlstate.moveBackward()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys(']', () => {
|
||||
tlstate.moveForward()
|
||||
})
|
||||
useHotkeys(
|
||||
']',
|
||||
() => {
|
||||
tlstate.moveForward()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('shift+[', () => {
|
||||
tlstate.moveToBack()
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+[',
|
||||
() => {
|
||||
tlstate.moveToBack()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('shift+]', () => {
|
||||
tlstate.moveToFront()
|
||||
})
|
||||
useHotkeys(
|
||||
'shift+]',
|
||||
() => {
|
||||
tlstate.moveToFront()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
|
||||
useHotkeys('command+shift+backspace', (e) => {
|
||||
tlstate.resetDocument()
|
||||
e.preventDefault()
|
||||
})
|
||||
useHotkeys(
|
||||
'command+shift+backspace',
|
||||
(e) => {
|
||||
tlstate.resetDocument()
|
||||
e.preventDefault()
|
||||
},
|
||||
undefined,
|
||||
[tlstate]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -445,4 +445,27 @@ describe('TLDrawState', () => {
|
|||
|
||||
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> {
|
||||
get id() {
|
||||
return this._idbId
|
||||
}
|
||||
|
||||
private _onChange?: (tlstate: TLDrawState, data: Data, reason: string) => void
|
||||
private _onMount?: (tlstate: TLDrawState) => void
|
||||
|
||||
|
@ -482,62 +486,50 @@ export class TLDrawState extends StateManager<Data> {
|
|||
* @param document
|
||||
*/
|
||||
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]
|
||||
? this.currentPageId
|
||||
: Object.keys(document.pages)[0]
|
||||
if (!document.pages[this.currentPageId]) {
|
||||
nextState.appState = {
|
||||
...prevState.appState,
|
||||
currentPageId: Object.keys(document.pages)[0],
|
||||
}
|
||||
}
|
||||
|
||||
this.replaceState(
|
||||
{
|
||||
...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
|
||||
let i = 1
|
||||
|
||||
for (const key of keysToCheck) {
|
||||
if (!page.shapes[key]) {
|
||||
nextPageState[key] = undefined
|
||||
}
|
||||
}
|
||||
for (const nextPage of Object.values(document.pages)) {
|
||||
if (nextPage !== prevState.document.pages[nextPage.id]) {
|
||||
nextState.document.pages[nextPage.id] = nextPage
|
||||
|
||||
nextPageState.selectedIds = pageState.selectedIds.filter(
|
||||
(id) => !!document.pages[pageId].shapes[id]
|
||||
)
|
||||
if (!nextPage.name) {
|
||||
nextState.document.pages[nextPage.id].name = `Page ${i + 1}`
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [pageId, nextPageState]
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
`${reason}:${document.id}`
|
||||
)
|
||||
for (const nextPageState of Object.values(document.pageStates)) {
|
||||
if (nextPageState !== prevState.document.pageStates[nextPageState.id]) {
|
||||
nextState.document.pageStates[nextPageState.id] = nextPageState
|
||||
|
||||
console.log(
|
||||
'did page change?',
|
||||
this.state.document.pages['page1'] !== state.document.pages['page1']
|
||||
)
|
||||
const nextPage = document.pages[nextPageState.id]
|
||||
const keysToCheck = ['bindingId', 'editingId', 'hoveredId', 'pointedId'] as const
|
||||
|
||||
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}`)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue