kopia lustrzana https://github.com/Tldraw/Tldraw
feat: replace window confirm with a dialog (#898)
* feat: replace window confirm with react dialog * add dialog provider * export func * remove unused code * changes * Create file_open.tldr * Update TldrawApp.ts * clean, and add description * add a custom container for alert dialog * Fix fonts * Fix logic for open project * Style panel * Improve styling Co-authored-by: Steve Ruiz <steveruizok@gmail.com>pull/915/head
rodzic
6dccf26c43
commit
97b0b52a6e
|
@ -6,11 +6,14 @@ import { ContextMenu } from '~components/ContextMenu'
|
||||||
import { ErrorFallback } from '~components/ErrorFallback'
|
import { ErrorFallback } from '~components/ErrorFallback'
|
||||||
import { FocusButton } from '~components/FocusButton'
|
import { FocusButton } from '~components/FocusButton'
|
||||||
import { Loading } from '~components/Loading'
|
import { Loading } from '~components/Loading'
|
||||||
|
import { AlertDialog } from '~components/Primitives/AlertDialog'
|
||||||
import { ToolsPanel } from '~components/ToolsPanel'
|
import { ToolsPanel } from '~components/ToolsPanel'
|
||||||
import { TopPanel } from '~components/TopPanel'
|
import { TopPanel } from '~components/TopPanel'
|
||||||
import { GRID_SIZE } from '~constants'
|
import { GRID_SIZE } from '~constants'
|
||||||
import {
|
import {
|
||||||
|
AlertDialogContext,
|
||||||
ContainerContext,
|
ContainerContext,
|
||||||
|
DialogState,
|
||||||
TldrawContext,
|
TldrawContext,
|
||||||
useKeyboardShortcuts,
|
useKeyboardShortcuts,
|
||||||
useStylesheet,
|
useStylesheet,
|
||||||
|
@ -163,6 +166,21 @@ export function Tldraw({
|
||||||
return app
|
return app
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [onCancel, setOnCancel] = React.useState<(() => void) | null>(null)
|
||||||
|
const [onYes, setOnYes] = React.useState<(() => void) | null>(null)
|
||||||
|
const [onNo, setOnNo] = React.useState<(() => void) | null>(null)
|
||||||
|
const [dialogState, setDialogState] = React.useState<DialogState | null>(null)
|
||||||
|
|
||||||
|
const openDialog = React.useCallback(
|
||||||
|
(dialogState: DialogState, onYes: () => void, onNo: () => void, onCancel: () => void) => {
|
||||||
|
setDialogState(() => dialogState)
|
||||||
|
setOnCancel(() => onCancel)
|
||||||
|
setOnYes(() => onYes)
|
||||||
|
setOnNo(() => onNo)
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
// Create a new app if the `id` prop changes.
|
// Create a new app if the `id` prop changes.
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
if (id === sId) return
|
if (id === sId) return
|
||||||
|
@ -297,19 +315,23 @@ export function Tldraw({
|
||||||
// Use the `key` to ensure that new selector hooks are made when the id changes
|
// Use the `key` to ensure that new selector hooks are made when the id changes
|
||||||
return (
|
return (
|
||||||
<TldrawContext.Provider value={app}>
|
<TldrawContext.Provider value={app}>
|
||||||
<InnerTldraw
|
<AlertDialogContext.Provider
|
||||||
key={sId || 'Tldraw'}
|
value={{ onYes, onCancel, onNo, dialogState, setDialogState, openDialog }}
|
||||||
id={sId}
|
>
|
||||||
autofocus={autofocus}
|
<InnerTldraw
|
||||||
showPages={showPages}
|
key={sId || 'Tldraw'}
|
||||||
showMenu={showMenu}
|
id={sId}
|
||||||
showMultiplayerMenu={showMultiplayerMenu}
|
autofocus={autofocus}
|
||||||
showStyles={showStyles}
|
showPages={showPages}
|
||||||
showZoom={showZoom}
|
showMenu={showMenu}
|
||||||
showTools={showTools}
|
showMultiplayerMenu={showMultiplayerMenu}
|
||||||
showUI={showUI}
|
showStyles={showStyles}
|
||||||
readOnly={readOnly}
|
showZoom={showZoom}
|
||||||
/>
|
showTools={showTools}
|
||||||
|
showUI={showUI}
|
||||||
|
readOnly={readOnly}
|
||||||
|
/>
|
||||||
|
</AlertDialogContext.Provider>
|
||||||
</TldrawContext.Provider>
|
</TldrawContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -340,6 +362,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
showUI,
|
showUI,
|
||||||
}: InnerTldrawProps) {
|
}: InnerTldrawProps) {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
const [dialogContainer, setDialogContainer] = React.useState<any>(null)
|
||||||
|
|
||||||
const rWrapper = React.useRef<HTMLDivElement>(null)
|
const rWrapper = React.useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
@ -439,6 +462,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
return (
|
return (
|
||||||
<ContainerContext.Provider value={rWrapper}>
|
<ContainerContext.Provider value={rWrapper}>
|
||||||
<IntlProvider locale={translation.locale} messages={translation.messages}>
|
<IntlProvider locale={translation.locale} messages={translation.messages}>
|
||||||
|
<AlertDialog container={dialogContainer} />
|
||||||
<StyledLayout ref={rWrapper} tabIndex={-0}>
|
<StyledLayout ref={rWrapper} tabIndex={-0}>
|
||||||
<Loading />
|
<Loading />
|
||||||
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
||||||
|
@ -523,7 +547,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
{showUI && (
|
{showUI && (
|
||||||
<StyledUI>
|
<StyledUI ref={setDialogContainer}>
|
||||||
{settings.isFocusMode ? (
|
{settings.isFocusMode ? (
|
||||||
<FocusButton onSelect={app.toggleFocusMode} />
|
<FocusButton onSelect={app.toggleFocusMode} />
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
|
||||||
|
import * as React from 'react'
|
||||||
|
import { DialogState, useDialog } from '~hooks'
|
||||||
|
import { styled } from '~styles'
|
||||||
|
|
||||||
|
interface ContentProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
onClose?: () => void
|
||||||
|
container: any
|
||||||
|
}
|
||||||
|
|
||||||
|
function Content({ children, onClose, container }: ContentProps) {
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
onClose?.()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Portal container={container}>
|
||||||
|
<StyledOverlay />
|
||||||
|
<StyledContent onKeyDown={handleKeyDown}>{children}</StyledContent>
|
||||||
|
</AlertDialogPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledDescription = styled(AlertDialogPrimitive.Description, {
|
||||||
|
marginBottom: 20,
|
||||||
|
color: '$text',
|
||||||
|
fontSize: '$2',
|
||||||
|
lineHeight: 1.5,
|
||||||
|
textAlign: 'center',
|
||||||
|
maxWidth: '62%',
|
||||||
|
minWidth: 0,
|
||||||
|
alignSelf: 'center',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const AlertDialogRoot = AlertDialogPrimitive.Root
|
||||||
|
export const AlertDialogContent = Content
|
||||||
|
export const AlertDialogDescription = StyledDescription
|
||||||
|
export const AlertDialogAction = AlertDialogPrimitive.Action
|
||||||
|
export const AlertDialogCancel = AlertDialogPrimitive.Cancel
|
||||||
|
|
||||||
|
const descriptions: Record<DialogState, string> = {
|
||||||
|
saveFirstTime: 'Do you want to save your current project?',
|
||||||
|
saveAgain: 'Do you want to save changes to your current project?',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlertDialog = ({ container }: { container: any }) => {
|
||||||
|
const { setDialogState, dialogState, onCancel, onNo, onYes } = useDialog()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialogRoot open={dialogState !== null}>
|
||||||
|
<AlertDialogContent onClose={() => setDialogState(null)} container={container}>
|
||||||
|
{dialogState && (
|
||||||
|
<AlertDialogDescription>{descriptions[dialogState]}</AlertDialogDescription>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
gap: '$6',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{onCancel && (
|
||||||
|
<AlertDialogCancel asChild>
|
||||||
|
<Button
|
||||||
|
css={{ color: '$text' }}
|
||||||
|
onClick={() => {
|
||||||
|
onCancel()
|
||||||
|
setDialogState(null)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</AlertDialogCancel>
|
||||||
|
)}
|
||||||
|
<div style={{ flexShrink: 0 }}>
|
||||||
|
{onNo && (
|
||||||
|
<AlertDialogAction asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onNo()
|
||||||
|
setDialogState(null)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No
|
||||||
|
</Button>
|
||||||
|
</AlertDialogAction>
|
||||||
|
)}
|
||||||
|
{onYes && (
|
||||||
|
<AlertDialogAction asChild>
|
||||||
|
<Button
|
||||||
|
css={{ backgroundColor: '#2F80ED', color: 'White' }}
|
||||||
|
onClick={() => {
|
||||||
|
onYes()
|
||||||
|
setDialogState(null)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
</AlertDialogAction>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogRoot>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledOverlay = styled(AlertDialogPrimitive.Overlay, {
|
||||||
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, .15)',
|
||||||
|
pointerEvents: 'all',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const StyledDialogOverlay = styled(AlertDialogPrimitive.Overlay, {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, .15)',
|
||||||
|
position: 'absolute',
|
||||||
|
pointerEvents: 'all',
|
||||||
|
inset: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
const StyledContent = styled(AlertDialogPrimitive.Content, {
|
||||||
|
position: 'fixed',
|
||||||
|
font: '$ui',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 'max-content',
|
||||||
|
padding: '$3',
|
||||||
|
pointerEvents: 'all',
|
||||||
|
backgroundColor: '$panel',
|
||||||
|
borderRadius: '$3',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontFamily: '$ui',
|
||||||
|
border: '1px solid $panelContrast',
|
||||||
|
boxShadow: '$panel',
|
||||||
|
})
|
||||||
|
|
||||||
|
const Button = styled('button', {
|
||||||
|
all: 'unset',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
borderRadius: '$2',
|
||||||
|
padding: '0 15px',
|
||||||
|
fontSize: '$1',
|
||||||
|
lineHeight: 1,
|
||||||
|
fontWeight: 'normal',
|
||||||
|
height: 36,
|
||||||
|
color: '$text',
|
||||||
|
cursor: 'pointer',
|
||||||
|
minWidth: 48,
|
||||||
|
})
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './AlertDialog'
|
|
@ -29,7 +29,7 @@ export function DMContent({
|
||||||
const container = useContainer()
|
const container = useContainer()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Portal container={container.current} dir="ltr">
|
<DropdownMenu.Portal container={overflow ? undefined : container.current} dir="ltr">
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
align={align}
|
align={align}
|
||||||
alignOffset={alignOffset}
|
alignOffset={alignOffset}
|
||||||
|
|
Plik diff jest za duży
Load Diff
|
@ -5,3 +5,4 @@ export * from './useStylesheet'
|
||||||
export * from './useTheme'
|
export * from './useTheme'
|
||||||
export * from './useTldrawApp'
|
export * from './useTldrawApp'
|
||||||
export * from './useTranslation'
|
export * from './useTranslation'
|
||||||
|
export * from './useDialog'
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
export type DialogState = 'saveFirstTime' | 'saveAgain'
|
||||||
|
|
||||||
|
interface AlertDialogProps {
|
||||||
|
dialogState: DialogState | null
|
||||||
|
setDialogState: (dialogState: DialogState | null) => void
|
||||||
|
onYes: (() => void) | null
|
||||||
|
onNo: (() => void) | null
|
||||||
|
onCancel: (() => void) | null
|
||||||
|
openDialog: (
|
||||||
|
dialogState: DialogState,
|
||||||
|
onYes: () => void,
|
||||||
|
onNo: () => void,
|
||||||
|
onCancel: () => void
|
||||||
|
) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlertDialogContext = React.createContext<AlertDialogProps>({} as AlertDialogProps)
|
||||||
|
|
||||||
|
export const useDialog = () => {
|
||||||
|
const context = React.useContext(AlertDialogContext)
|
||||||
|
if (!context) throw new Error('useCtx must be inside a Provider with a value')
|
||||||
|
return context
|
||||||
|
}
|
|
@ -1,29 +1,72 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import type { TldrawApp } from '~state'
|
import type { TldrawApp } from '~state'
|
||||||
|
import { DialogState } from './useDialog'
|
||||||
|
|
||||||
export function useFileSystem() {
|
export function useFileSystem() {
|
||||||
const promptSaveBeforeChange = React.useCallback(async (app: TldrawApp) => {
|
|
||||||
if (app.isDirty) {
|
|
||||||
if (app.fileSystemHandle) {
|
|
||||||
if (window.confirm('Do you want to save changes to your current project?')) {
|
|
||||||
await app.saveProject()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (window.confirm('Do you want to save your current project?')) {
|
|
||||||
await app.saveProject()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onNewProject = React.useCallback(
|
const onNewProject = React.useCallback(
|
||||||
async (app: TldrawApp) => {
|
async (
|
||||||
if (window.confirm('Do you want to create a new project?')) {
|
app: TldrawApp,
|
||||||
await promptSaveBeforeChange(app)
|
openDialog: (
|
||||||
app.newProject()
|
dialogState: DialogState,
|
||||||
}
|
onYes: () => Promise<void>,
|
||||||
|
onNo: () => Promise<void>,
|
||||||
|
onCancel: () => Promise<void>
|
||||||
|
) => void
|
||||||
|
) => {
|
||||||
|
openDialog(
|
||||||
|
app.fileSystemHandle ? 'saveFirstTime' : 'saveAgain',
|
||||||
|
async () => {
|
||||||
|
// user pressed yes
|
||||||
|
try {
|
||||||
|
await app.saveProject()
|
||||||
|
app.newProject()
|
||||||
|
} catch (e) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
// user pressed no
|
||||||
|
app.newProject()
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
// user pressed cancel
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
[promptSaveBeforeChange]
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onOpenProject = React.useCallback(
|
||||||
|
async (
|
||||||
|
app: TldrawApp,
|
||||||
|
openDialog: (
|
||||||
|
dialogState: DialogState,
|
||||||
|
onYes: () => Promise<void>,
|
||||||
|
onNo: () => Promise<void>,
|
||||||
|
onCancel: () => Promise<void>
|
||||||
|
) => void
|
||||||
|
) => {
|
||||||
|
openDialog(
|
||||||
|
app.fileSystemHandle ? 'saveFirstTime' : 'saveAgain',
|
||||||
|
async () => {
|
||||||
|
// user pressed yes
|
||||||
|
try {
|
||||||
|
await app.saveProject()
|
||||||
|
await app.openProject()
|
||||||
|
} catch (e) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
// user pressed no
|
||||||
|
app.openProject()
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
// user pressed cancel
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onSaveProject = React.useCallback((app: TldrawApp) => {
|
const onSaveProject = React.useCallback((app: TldrawApp) => {
|
||||||
|
@ -34,14 +77,6 @@ export function useFileSystem() {
|
||||||
app.saveProjectAs()
|
app.saveProjectAs()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const onOpenProject = React.useCallback(
|
|
||||||
async (app: TldrawApp) => {
|
|
||||||
await promptSaveBeforeChange(app)
|
|
||||||
app.openProject()
|
|
||||||
},
|
|
||||||
[promptSaveBeforeChange]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onOpenMedia = React.useCallback(async (app: TldrawApp) => {
|
const onOpenMedia = React.useCallback(async (app: TldrawApp) => {
|
||||||
app.openAsset?.()
|
app.openAsset?.()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTldrawApp } from '~hooks'
|
import { useDialog, useTldrawApp } from '~hooks'
|
||||||
|
|
||||||
export function useFileSystemHandlers() {
|
export function useFileSystemHandlers() {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
|
||||||
|
const { openDialog } = useDialog()
|
||||||
|
|
||||||
const onNewProject = React.useCallback(
|
const onNewProject = React.useCallback(
|
||||||
async (e?: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => {
|
async (e?: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => {
|
||||||
if (e && app.callbacks.onOpenProject) e.preventDefault()
|
if (e && app.callbacks.onOpenProject) e.preventDefault()
|
||||||
app.callbacks.onNewProject?.(app)
|
app.callbacks.onNewProject?.(app, openDialog)
|
||||||
},
|
},
|
||||||
[app]
|
[app, openDialog]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onSaveProject = React.useCallback(
|
const onSaveProject = React.useCallback(
|
||||||
|
@ -31,9 +33,9 @@ export function useFileSystemHandlers() {
|
||||||
const onOpenProject = React.useCallback(
|
const onOpenProject = React.useCallback(
|
||||||
async (e?: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => {
|
async (e?: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => {
|
||||||
if (e && app.callbacks.onOpenProject) e.preventDefault()
|
if (e && app.callbacks.onOpenProject) e.preventDefault()
|
||||||
app.callbacks.onOpenProject?.(app)
|
app.callbacks.onOpenProject?.(app, openDialog)
|
||||||
},
|
},
|
||||||
[app]
|
[app, openDialog]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onOpenMedia = React.useCallback(
|
const onOpenMedia = React.useCallback(
|
||||||
|
|
|
@ -199,8 +199,8 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+n,⌘+n',
|
'ctrl+n,⌘+n',
|
||||||
(e) => {
|
(e) => {
|
||||||
|
e.preventDefault()
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
|
|
||||||
onNewProject(e)
|
onNewProject(e)
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
USER_COLORS,
|
USER_COLORS,
|
||||||
VIDEO_EXTENSIONS,
|
VIDEO_EXTENSIONS,
|
||||||
} from '~constants'
|
} from '~constants'
|
||||||
|
import { DialogState } from '~hooks'
|
||||||
import { shapeUtils } from '~state/shapes'
|
import { shapeUtils } from '~state/shapes'
|
||||||
import { defaultStyle } from '~state/shapes/shared'
|
import { defaultStyle } from '~state/shapes/shared'
|
||||||
import {
|
import {
|
||||||
|
@ -97,7 +98,16 @@ export interface TDCallbacks {
|
||||||
/**
|
/**
|
||||||
* (optional) A callback to run when the user creates a new project through the menu or through a keyboard shortcut.
|
* (optional) A callback to run when the user creates a new project through the menu or through a keyboard shortcut.
|
||||||
*/
|
*/
|
||||||
onNewProject?: (app: TldrawApp, e?: KeyboardEvent) => void
|
onNewProject?: (
|
||||||
|
app: TldrawApp,
|
||||||
|
openDialog: (
|
||||||
|
dialogState: DialogState,
|
||||||
|
onYes: () => void,
|
||||||
|
onNo: () => void,
|
||||||
|
onCancel: () => void
|
||||||
|
) => void,
|
||||||
|
e?: KeyboardEvent
|
||||||
|
) => void
|
||||||
/**
|
/**
|
||||||
* (optional) A callback to run when the user saves a project through the menu or through a keyboard shortcut.
|
* (optional) A callback to run when the user saves a project through the menu or through a keyboard shortcut.
|
||||||
*/
|
*/
|
||||||
|
@ -109,7 +119,16 @@ export interface TDCallbacks {
|
||||||
/**
|
/**
|
||||||
* (optional) A callback to run when the user opens new project through the menu or through a keyboard shortcut.
|
* (optional) A callback to run when the user opens new project through the menu or through a keyboard shortcut.
|
||||||
*/
|
*/
|
||||||
onOpenProject?: (app: TldrawApp, e?: KeyboardEvent) => void
|
onOpenProject?: (
|
||||||
|
app: TldrawApp,
|
||||||
|
openDialog: (
|
||||||
|
dialogState: DialogState,
|
||||||
|
onYes: () => void,
|
||||||
|
onNo: () => void,
|
||||||
|
onCancel: () => void
|
||||||
|
) => void,
|
||||||
|
e?: KeyboardEvent
|
||||||
|
) => void
|
||||||
/**
|
/**
|
||||||
* (optional) A callback to run when the opens a file to upload.
|
* (optional) A callback to run when the opens a file to upload.
|
||||||
*/
|
*/
|
||||||
|
@ -1384,18 +1403,13 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
*/
|
*/
|
||||||
saveProject = async () => {
|
saveProject = async () => {
|
||||||
if (this.readOnly) return
|
if (this.readOnly) return
|
||||||
try {
|
const fileHandle = await saveToFileSystem(
|
||||||
const fileHandle = await saveToFileSystem(
|
migrate(this.state, TldrawApp.version).document,
|
||||||
migrate(this.state, TldrawApp.version).document,
|
this.fileSystemHandle
|
||||||
this.fileSystemHandle
|
)
|
||||||
)
|
this.fileSystemHandle = fileHandle
|
||||||
this.fileSystemHandle = fileHandle
|
this.persist({})
|
||||||
this.persist({})
|
this.isDirty = false
|
||||||
this.isDirty = false
|
|
||||||
} catch (e: any) {
|
|
||||||
// Likely cancelled
|
|
||||||
console.error(e.message)
|
|
||||||
}
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2100,7 +2114,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
/**
|
/**
|
||||||
* Copy one or more shapes as SVG.
|
* Copy one or more shapes as SVG.
|
||||||
* @param ids The ids of the shapes to copy.
|
* @param ids The ids of the shapes to copy.
|
||||||
* @param pageId The page from which to copy the shapes.
|
|
||||||
* @returns A string containing the JSON.
|
* @returns A string containing the JSON.
|
||||||
*/
|
*/
|
||||||
copySvg = async (
|
copySvg = async (
|
||||||
|
@ -2220,7 +2233,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
/**
|
/**
|
||||||
* Copy one or more shapes as JSON.
|
* Copy one or more shapes as JSON.
|
||||||
* @param ids The ids of the shapes to copy.
|
* @param ids The ids of the shapes to copy.
|
||||||
* @param pageId The page from which to copy the shapes.
|
|
||||||
* @returns A string containing the JSON.
|
* @returns A string containing the JSON.
|
||||||
*/
|
*/
|
||||||
copyJson = (ids = this.selectedIds) => {
|
copyJson = (ids = this.selectedIds) => {
|
||||||
|
|
Ładowanie…
Reference in New Issue