2021-08-10 16:12:55 +00:00
|
|
|
import * as React from 'react'
|
|
|
|
import { Renderer } from '@tldraw/core'
|
2021-11-06 11:16:30 +00:00
|
|
|
import { styled, dark } from '~styles'
|
2021-11-22 14:00:24 +00:00
|
|
|
import { TDDocument, TDShape, TDBinding, TDStatus, TDUser } from '~types'
|
2021-11-16 16:01:29 +00:00
|
|
|
import { TldrawApp, TDCallbacks } from '~state'
|
|
|
|
import { TldrawContext, useStylesheet, useKeyboardShortcuts, useTldrawApp } from '~hooks'
|
2021-11-06 11:16:30 +00:00
|
|
|
import { shapeUtils } from '~state/shapes'
|
2021-11-03 16:46:33 +00:00
|
|
|
import { ToolsPanel } from '~components/ToolsPanel'
|
|
|
|
import { TopPanel } from '~components/TopPanel'
|
|
|
|
import { ContextMenu } from '~components/ContextMenu'
|
2021-11-26 15:14:10 +00:00
|
|
|
import { FocusButton } from '~components/FocusButton'
|
|
|
|
import { TLDR } from '~state/TLDR'
|
|
|
|
import { GRID_SIZE } from '~constants'
|
2021-08-10 16:12:55 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
export interface TldrawProps extends TDCallbacks {
|
2021-08-30 13:04:12 +00:00
|
|
|
/**
|
|
|
|
* (optional) If provided, the component will load / persist state under this key.
|
|
|
|
*/
|
|
|
|
id?: string
|
2021-10-09 13:57:44 +00:00
|
|
|
|
2021-08-30 13:04:12 +00:00
|
|
|
/**
|
|
|
|
* (optional) The document to load or update from.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
document?: TDDocument
|
2021-10-09 13:57:44 +00:00
|
|
|
|
2021-08-30 13:04:12 +00:00
|
|
|
/**
|
|
|
|
* (optional) The current page id.
|
|
|
|
*/
|
|
|
|
currentPageId?: string
|
2021-09-21 15:47:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* (optional) Whether the editor should immediately receive focus. Defaults to true.
|
|
|
|
*/
|
|
|
|
autofocus?: boolean
|
2021-10-09 13:57:44 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* (optional) Whether to show the menu UI.
|
|
|
|
*/
|
|
|
|
showMenu?: boolean
|
|
|
|
|
|
|
|
/**
|
|
|
|
* (optional) Whether to show the pages UI.
|
|
|
|
*/
|
|
|
|
showPages?: boolean
|
|
|
|
|
2021-11-03 16:46:33 +00:00
|
|
|
/**
|
|
|
|
* (optional) Whether to show the styles UI.
|
|
|
|
*/
|
|
|
|
showStyles?: boolean
|
|
|
|
|
|
|
|
/**
|
|
|
|
* (optional) Whether to show the zoom UI.
|
|
|
|
*/
|
|
|
|
showZoom?: boolean
|
|
|
|
|
|
|
|
/**
|
|
|
|
* (optional) Whether to show the tools UI.
|
|
|
|
*/
|
|
|
|
showTools?: boolean
|
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
/**
|
|
|
|
* (optional) Whether to show a sponsor link for Tldraw.
|
|
|
|
*/
|
|
|
|
showSponsorLink?: boolean
|
|
|
|
|
2021-11-03 16:46:33 +00:00
|
|
|
/**
|
|
|
|
* (optional) Whether to show the UI.
|
|
|
|
*/
|
|
|
|
showUI?: boolean
|
|
|
|
|
2021-11-05 14:13:14 +00:00
|
|
|
/**
|
|
|
|
* (optional) Whether to the document should be read only.
|
|
|
|
*/
|
|
|
|
readOnly?: boolean
|
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
/**
|
|
|
|
* (optional) Whether to to show the app's dark mode UI.
|
|
|
|
*/
|
|
|
|
darkMode?: boolean
|
|
|
|
|
2021-08-30 13:04:12 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the component mounts.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onMount?: (state: TldrawApp) => void
|
|
|
|
|
2021-11-05 14:13:14 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the user creates a new project through the menu or through a keyboard shortcut.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onNewProject?: (state: TldrawApp, e?: KeyboardEvent) => void
|
|
|
|
|
2021-11-05 14:13:14 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the user saves a project through the menu or through a keyboard shortcut.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onSaveProject?: (state: TldrawApp, e?: KeyboardEvent) => void
|
|
|
|
|
2021-11-05 14:13:14 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the user saves a project as a new project through the menu or through a keyboard shortcut.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onSaveProjectAs?: (state: TldrawApp, e?: KeyboardEvent) => void
|
|
|
|
|
2021-11-05 14:13:14 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the user opens new project through the menu or through a keyboard shortcut.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onOpenProject?: (state: TldrawApp, e?: KeyboardEvent) => void
|
|
|
|
|
2021-11-05 14:13:14 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the user signs in via the menu.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onSignIn?: (state: TldrawApp) => void
|
|
|
|
|
2021-11-05 14:13:14 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the user signs out via the menu.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onSignOut?: (state: TldrawApp) => void
|
2021-11-08 14:21:37 +00:00
|
|
|
|
2021-11-05 14:13:14 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the user creates a new project.
|
|
|
|
*/
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePresence?: (state: TldrawApp, user: TDUser) => void
|
2021-11-08 14:21:37 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the component's state changes.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onChange?: (state: TldrawApp, reason?: string) => void
|
2021-11-08 14:21:37 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the state is patched.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onPatch?: (state: TldrawApp, reason?: string) => void
|
2021-11-08 14:21:37 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the state is changed with a command.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onCommand?: (state: TldrawApp, reason?: string) => void
|
2021-11-08 14:21:37 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the state is persisted.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onPersist?: (state: TldrawApp) => void
|
2021-11-08 14:21:37 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the user undos.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onUndo?: (state: TldrawApp) => void
|
2021-11-08 14:21:37 +00:00
|
|
|
/**
|
|
|
|
* (optional) A callback to run when the user redos.
|
|
|
|
*/
|
2021-11-16 16:01:29 +00:00
|
|
|
onRedo?: (state: TldrawApp) => void
|
2021-11-22 14:00:24 +00:00
|
|
|
|
|
|
|
onChangePage?: (
|
|
|
|
app: TldrawApp,
|
|
|
|
shapes: Record<string, TDShape | undefined>,
|
|
|
|
bindings: Record<string, TDBinding | undefined>
|
|
|
|
) => void
|
2021-08-30 13:04:12 +00:00
|
|
|
}
|
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
export function Tldraw({
|
2021-09-21 15:47:04 +00:00
|
|
|
id,
|
|
|
|
document,
|
|
|
|
currentPageId,
|
2021-11-16 16:01:29 +00:00
|
|
|
darkMode = false,
|
2021-09-21 15:47:04 +00:00
|
|
|
autofocus = true,
|
2021-10-09 13:57:44 +00:00
|
|
|
showMenu = true,
|
|
|
|
showPages = true,
|
2021-11-03 16:46:33 +00:00
|
|
|
showTools = true,
|
|
|
|
showZoom = true,
|
|
|
|
showStyles = true,
|
|
|
|
showUI = true,
|
2021-11-05 14:13:14 +00:00
|
|
|
readOnly = false,
|
2021-11-16 16:01:29 +00:00
|
|
|
showSponsorLink = false,
|
2021-09-21 15:47:04 +00:00
|
|
|
onMount,
|
|
|
|
onChange,
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePresence,
|
2021-11-05 14:13:14 +00:00
|
|
|
onNewProject,
|
|
|
|
onSaveProject,
|
|
|
|
onSaveProjectAs,
|
|
|
|
onOpenProject,
|
|
|
|
onSignOut,
|
|
|
|
onSignIn,
|
2021-11-08 14:21:37 +00:00
|
|
|
onUndo,
|
|
|
|
onRedo,
|
|
|
|
onPersist,
|
|
|
|
onPatch,
|
|
|
|
onCommand,
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePage,
|
2021-11-16 16:01:29 +00:00
|
|
|
}: TldrawProps) {
|
2021-09-08 10:16:10 +00:00
|
|
|
const [sId, setSId] = React.useState(id)
|
2021-09-08 11:53:52 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
// Create a new app when the component mounts.
|
|
|
|
const [app, setApp] = React.useState(
|
2021-11-09 10:55:01 +00:00
|
|
|
() =>
|
2021-11-16 16:01:29 +00:00
|
|
|
new TldrawApp(id, {
|
2021-11-09 10:55:01 +00:00
|
|
|
onMount,
|
|
|
|
onChange,
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePresence,
|
2021-11-09 10:55:01 +00:00
|
|
|
onNewProject,
|
|
|
|
onSaveProject,
|
|
|
|
onSaveProjectAs,
|
|
|
|
onOpenProject,
|
|
|
|
onSignOut,
|
|
|
|
onSignIn,
|
|
|
|
onUndo,
|
|
|
|
onRedo,
|
2021-11-22 14:00:24 +00:00
|
|
|
onPersist,
|
2021-11-09 10:55:01 +00:00
|
|
|
onPatch,
|
|
|
|
onCommand,
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePage,
|
2021-11-09 10:55:01 +00:00
|
|
|
})
|
|
|
|
)
|
2021-11-08 14:21:37 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
// Create a new app if the `id` prop changes.
|
2021-08-30 13:04:12 +00:00
|
|
|
React.useEffect(() => {
|
2021-09-08 10:16:10 +00:00
|
|
|
if (id === sId) return
|
2021-11-05 14:13:14 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const newApp = new TldrawApp(id, {
|
2021-11-09 10:55:01 +00:00
|
|
|
onMount,
|
|
|
|
onChange,
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePresence,
|
2021-11-09 10:55:01 +00:00
|
|
|
onNewProject,
|
|
|
|
onSaveProject,
|
|
|
|
onSaveProjectAs,
|
|
|
|
onOpenProject,
|
|
|
|
onSignOut,
|
|
|
|
onSignIn,
|
|
|
|
onUndo,
|
|
|
|
onRedo,
|
2021-11-22 14:00:24 +00:00
|
|
|
onPersist,
|
2021-11-09 10:55:01 +00:00
|
|
|
onPatch,
|
|
|
|
onCommand,
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePage,
|
2021-11-09 10:55:01 +00:00
|
|
|
})
|
2021-11-08 14:21:37 +00:00
|
|
|
|
2021-11-05 14:13:14 +00:00
|
|
|
setSId(id)
|
2021-11-08 14:21:37 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
setApp(newApp)
|
2021-09-08 10:16:10 +00:00
|
|
|
}, [sId, id])
|
2021-08-30 13:04:12 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
// Update the document if the `document` prop changes but the ids,
|
|
|
|
// are the same, or else load a new document if the ids are different.
|
2021-11-05 14:13:14 +00:00
|
|
|
React.useEffect(() => {
|
2021-11-08 14:21:37 +00:00
|
|
|
if (!document) return
|
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
if (document.id === app.document.id) {
|
|
|
|
app.updateDocument(document)
|
2021-11-08 14:21:37 +00:00
|
|
|
} else {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.loadDocument(document)
|
|
|
|
}
|
|
|
|
}, [document, app])
|
|
|
|
|
|
|
|
// Change the page when the `currentPageId` prop changes
|
|
|
|
React.useEffect(() => {
|
|
|
|
if (!currentPageId) return
|
|
|
|
app.changePage(currentPageId)
|
|
|
|
}, [currentPageId, app])
|
|
|
|
|
|
|
|
// Toggle the app's readOnly mode when the `readOnly` prop changes
|
|
|
|
React.useEffect(() => {
|
|
|
|
app.readOnly = readOnly
|
|
|
|
}, [app, readOnly])
|
|
|
|
|
|
|
|
// Toggle the app's readOnly mode when the `readOnly` prop changes
|
|
|
|
React.useEffect(() => {
|
|
|
|
if (darkMode && !app.settings.isDarkMode) {
|
|
|
|
// app.toggleDarkMode()
|
2021-11-08 14:21:37 +00:00
|
|
|
}
|
2021-11-16 16:01:29 +00:00
|
|
|
}, [app, darkMode])
|
2021-11-08 14:21:37 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
// Update the app's callbacks when any callback changes.
|
2021-11-08 14:21:37 +00:00
|
|
|
React.useEffect(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.callbacks = {
|
2021-11-08 14:21:37 +00:00
|
|
|
onMount,
|
|
|
|
onChange,
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePresence,
|
2021-11-08 14:21:37 +00:00
|
|
|
onNewProject,
|
|
|
|
onSaveProject,
|
|
|
|
onSaveProjectAs,
|
|
|
|
onOpenProject,
|
|
|
|
onSignOut,
|
|
|
|
onSignIn,
|
|
|
|
onUndo,
|
|
|
|
onRedo,
|
2021-11-22 14:00:24 +00:00
|
|
|
onPersist,
|
2021-11-08 14:21:37 +00:00
|
|
|
onPatch,
|
|
|
|
onCommand,
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePage,
|
2021-11-08 14:21:37 +00:00
|
|
|
}
|
|
|
|
}, [
|
|
|
|
onMount,
|
|
|
|
onChange,
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePresence,
|
2021-11-08 14:21:37 +00:00
|
|
|
onNewProject,
|
|
|
|
onSaveProject,
|
|
|
|
onSaveProjectAs,
|
|
|
|
onOpenProject,
|
|
|
|
onSignOut,
|
|
|
|
onSignIn,
|
|
|
|
onUndo,
|
|
|
|
onRedo,
|
2021-11-22 14:00:24 +00:00
|
|
|
onPersist,
|
2021-11-08 14:21:37 +00:00
|
|
|
onPatch,
|
|
|
|
onCommand,
|
2021-11-22 14:00:24 +00:00
|
|
|
onChangePage,
|
2021-11-08 14:21:37 +00:00
|
|
|
])
|
2021-11-05 14:13:14 +00:00
|
|
|
|
2021-09-08 10:16:10 +00:00
|
|
|
// Use the `key` to ensure that new selector hooks are made when the id changes
|
2021-09-02 20:13:54 +00:00
|
|
|
return (
|
2021-11-16 16:01:29 +00:00
|
|
|
<TldrawContext.Provider value={app}>
|
2021-12-12 21:44:44 +00:00
|
|
|
<InnerTldraw
|
|
|
|
key={sId || 'Tldraw'}
|
|
|
|
id={sId}
|
|
|
|
autofocus={autofocus}
|
|
|
|
showPages={showPages}
|
|
|
|
showMenu={showMenu}
|
|
|
|
showStyles={showStyles}
|
|
|
|
showZoom={showZoom}
|
|
|
|
showTools={showTools}
|
|
|
|
showUI={showUI}
|
|
|
|
showSponsorLink={showSponsorLink}
|
|
|
|
readOnly={readOnly}
|
|
|
|
/>
|
2021-11-16 16:01:29 +00:00
|
|
|
</TldrawContext.Provider>
|
2021-09-02 20:13:54 +00:00
|
|
|
)
|
|
|
|
}
|
2021-08-10 16:12:55 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
interface InnerTldrawProps {
|
2021-11-03 16:46:33 +00:00
|
|
|
id?: string
|
|
|
|
autofocus: boolean
|
|
|
|
showPages: boolean
|
|
|
|
showMenu: boolean
|
|
|
|
showZoom: boolean
|
|
|
|
showStyles: boolean
|
|
|
|
showUI: boolean
|
|
|
|
showTools: boolean
|
2021-11-16 16:01:29 +00:00
|
|
|
showSponsorLink: boolean
|
2021-11-05 14:13:14 +00:00
|
|
|
readOnly: boolean
|
2021-11-03 16:46:33 +00:00
|
|
|
}
|
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const InnerTldraw = React.memo(function InnerTldraw({
|
2021-09-22 11:28:55 +00:00
|
|
|
id,
|
2021-09-21 15:47:04 +00:00
|
|
|
autofocus,
|
2021-10-09 13:57:44 +00:00
|
|
|
showPages,
|
|
|
|
showMenu,
|
2021-11-03 16:46:33 +00:00
|
|
|
showZoom,
|
|
|
|
showStyles,
|
|
|
|
showTools,
|
2021-11-16 16:01:29 +00:00
|
|
|
showSponsorLink,
|
2021-11-05 14:13:14 +00:00
|
|
|
readOnly,
|
2021-11-03 16:46:33 +00:00
|
|
|
showUI,
|
2021-11-16 16:01:29 +00:00
|
|
|
}: InnerTldrawProps) {
|
|
|
|
const app = useTldrawApp()
|
2021-08-30 13:04:12 +00:00
|
|
|
|
2021-09-21 15:47:04 +00:00
|
|
|
const rWrapper = React.useRef<HTMLDivElement>(null)
|
2021-08-30 13:04:12 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const state = app.useStore()
|
2021-08-30 13:04:12 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const { document, settings, appState, room } = state
|
2021-08-30 13:04:12 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const isSelecting = state.appState.activeTool === 'select'
|
2021-10-18 13:30:42 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const page = document.pages[appState.currentPageId]
|
|
|
|
const pageState = document.pageStates[page.id]
|
2021-12-01 22:31:19 +00:00
|
|
|
const { selectedIds } = pageState
|
2021-10-09 13:57:44 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const isHideBoundsShape =
|
2021-12-01 22:31:19 +00:00
|
|
|
selectedIds.length === 1 &&
|
|
|
|
page.shapes[selectedIds[0]] &&
|
2021-11-16 16:01:29 +00:00
|
|
|
TLDR.getShapeUtil(page.shapes[selectedIds[0]].type).hideBounds
|
2021-09-22 11:28:55 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const isHideResizeHandlesShape =
|
|
|
|
selectedIds.length === 1 &&
|
2021-12-01 22:31:19 +00:00
|
|
|
page.shapes[selectedIds[0]] &&
|
2021-11-16 16:01:29 +00:00
|
|
|
TLDR.getShapeUtil(page.shapes[selectedIds[0]].type).hideResizeHandles
|
2021-08-30 13:04:12 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const isInSession = app.session !== undefined
|
2021-08-12 13:39:41 +00:00
|
|
|
|
|
|
|
// Hide bounds when not using the select tool, or when the only selected shape has handles
|
2021-09-03 11:07:34 +00:00
|
|
|
const hideBounds =
|
2021-11-16 16:01:29 +00:00
|
|
|
(isInSession && app.session?.constructor.name !== 'BrushSession') ||
|
2021-10-13 13:55:31 +00:00
|
|
|
!isSelecting ||
|
2021-10-13 16:03:33 +00:00
|
|
|
isHideBoundsShape ||
|
2021-10-13 13:55:31 +00:00
|
|
|
!!pageState.editingId
|
2021-08-12 13:39:41 +00:00
|
|
|
|
|
|
|
// Hide bounds when not using the select tool, or when in session
|
2021-09-03 10:52:40 +00:00
|
|
|
const hideHandles = isInSession || !isSelecting
|
2021-08-12 13:39:41 +00:00
|
|
|
|
|
|
|
// Hide indicators when not using the select tool, or when in session
|
2021-09-11 17:07:53 +00:00
|
|
|
const hideIndicators =
|
2021-11-16 16:01:29 +00:00
|
|
|
(isInSession && state.appState.status !== TDStatus.Brushing) || !isSelecting
|
2021-08-10 16:12:55 +00:00
|
|
|
|
2021-08-30 10:49:49 +00:00
|
|
|
// Custom rendering meta, with dark mode for shapes
|
2021-11-16 16:01:29 +00:00
|
|
|
const meta = React.useMemo(() => {
|
|
|
|
return { isDarkMode: settings.isDarkMode }
|
|
|
|
}, [settings.isDarkMode])
|
2021-08-30 10:44:42 +00:00
|
|
|
|
2021-09-02 20:13:54 +00:00
|
|
|
// Custom theme, based on darkmode
|
2021-08-23 16:13:10 +00:00
|
|
|
const theme = React.useMemo(() => {
|
2021-10-19 13:29:55 +00:00
|
|
|
if (settings.isDarkMode) {
|
2021-08-23 16:13:10 +00:00
|
|
|
return {
|
|
|
|
brushFill: 'rgba(180, 180, 180, .05)',
|
|
|
|
brushStroke: 'rgba(180, 180, 180, .25)',
|
|
|
|
selected: 'rgba(38, 150, 255, 1.000)',
|
|
|
|
selectFill: 'rgba(38, 150, 255, 0.05)',
|
2021-11-16 16:01:29 +00:00
|
|
|
background: '#212529',
|
2021-08-23 16:13:10 +00:00
|
|
|
foreground: '#49555f',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {}
|
2021-10-19 13:29:55 +00:00
|
|
|
}, [settings.isDarkMode])
|
2021-08-23 16:13:10 +00:00
|
|
|
|
2021-11-11 09:54:58 +00:00
|
|
|
// When the context menu is blurred, close the menu by sending pointer events
|
|
|
|
// to the context menu's ref. This is a hack around the fact that certain shapes
|
|
|
|
// stop event propagation, which causes the menu to stay open even when blurred.
|
2021-11-18 18:18:30 +00:00
|
|
|
const handleMenuBlur = React.useCallback<React.FocusEventHandler>((e) => {
|
2021-11-11 09:54:58 +00:00
|
|
|
const elm = rWrapper.current
|
|
|
|
if (!elm) return
|
|
|
|
if (!elm.contains(e.relatedTarget)) return
|
|
|
|
elm.dispatchEvent(new Event('pointerdown', { bubbles: true }))
|
|
|
|
elm.dispatchEvent(new Event('pointerup', { bubbles: true }))
|
|
|
|
}, [])
|
|
|
|
|
2021-08-10 16:12:55 +00:00
|
|
|
return (
|
2021-11-18 18:18:30 +00:00
|
|
|
<StyledLayout ref={rWrapper} tabIndex={-0} className={settings.isDarkMode ? dark : ''}>
|
2021-10-15 12:49:48 +00:00
|
|
|
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
2021-11-18 18:18:30 +00:00
|
|
|
<ContextMenu onBlur={handleMenuBlur}>
|
2021-10-15 12:24:34 +00:00
|
|
|
<Renderer
|
|
|
|
id={id}
|
|
|
|
containerRef={rWrapper}
|
2021-11-05 14:13:14 +00:00
|
|
|
shapeUtils={shapeUtils}
|
2021-10-15 12:24:34 +00:00
|
|
|
page={page}
|
|
|
|
pageState={pageState}
|
2021-11-16 16:01:29 +00:00
|
|
|
snapLines={appState.snapLines}
|
2021-11-26 15:14:10 +00:00
|
|
|
grid={GRID_SIZE}
|
2021-11-16 16:01:29 +00:00
|
|
|
users={room?.users}
|
|
|
|
userId={room?.userId}
|
2021-10-15 12:24:34 +00:00
|
|
|
theme={theme}
|
|
|
|
meta={meta}
|
|
|
|
hideBounds={hideBounds}
|
|
|
|
hideHandles={hideHandles}
|
2021-11-16 16:01:29 +00:00
|
|
|
hideResizeHandles={isHideResizeHandlesShape}
|
2021-10-15 12:24:34 +00:00
|
|
|
hideIndicators={hideIndicators}
|
2021-10-19 13:29:55 +00:00
|
|
|
hideBindingHandles={!settings.showBindingHandles}
|
|
|
|
hideCloneHandles={!settings.showCloneHandles}
|
|
|
|
hideRotateHandles={!settings.showRotateHandles}
|
2021-11-26 15:14:10 +00:00
|
|
|
hideGrid={!settings.showGrid}
|
2021-11-16 16:01:29 +00:00
|
|
|
onPinchStart={app.onPinchStart}
|
|
|
|
onPinchEnd={app.onPinchEnd}
|
|
|
|
onPinch={app.onPinch}
|
|
|
|
onPan={app.onPan}
|
|
|
|
onZoom={app.onZoom}
|
|
|
|
onPointerDown={app.onPointerDown}
|
|
|
|
onPointerMove={app.onPointerMove}
|
|
|
|
onPointerUp={app.onPointerUp}
|
|
|
|
onPointCanvas={app.onPointCanvas}
|
|
|
|
onDoubleClickCanvas={app.onDoubleClickCanvas}
|
|
|
|
onRightPointCanvas={app.onRightPointCanvas}
|
|
|
|
onDragCanvas={app.onDragCanvas}
|
|
|
|
onReleaseCanvas={app.onReleaseCanvas}
|
|
|
|
onPointShape={app.onPointShape}
|
|
|
|
onDoubleClickShape={app.onDoubleClickShape}
|
|
|
|
onRightPointShape={app.onRightPointShape}
|
|
|
|
onDragShape={app.onDragShape}
|
|
|
|
onHoverShape={app.onHoverShape}
|
|
|
|
onUnhoverShape={app.onUnhoverShape}
|
|
|
|
onReleaseShape={app.onReleaseShape}
|
|
|
|
onPointBounds={app.onPointBounds}
|
|
|
|
onDoubleClickBounds={app.onDoubleClickBounds}
|
|
|
|
onRightPointBounds={app.onRightPointBounds}
|
|
|
|
onDragBounds={app.onDragBounds}
|
|
|
|
onHoverBounds={app.onHoverBounds}
|
|
|
|
onUnhoverBounds={app.onUnhoverBounds}
|
|
|
|
onReleaseBounds={app.onReleaseBounds}
|
|
|
|
onPointBoundsHandle={app.onPointBoundsHandle}
|
|
|
|
onDoubleClickBoundsHandle={app.onDoubleClickBoundsHandle}
|
|
|
|
onRightPointBoundsHandle={app.onRightPointBoundsHandle}
|
|
|
|
onDragBoundsHandle={app.onDragBoundsHandle}
|
|
|
|
onHoverBoundsHandle={app.onHoverBoundsHandle}
|
|
|
|
onUnhoverBoundsHandle={app.onUnhoverBoundsHandle}
|
|
|
|
onReleaseBoundsHandle={app.onReleaseBoundsHandle}
|
|
|
|
onPointHandle={app.onPointHandle}
|
|
|
|
onDoubleClickHandle={app.onDoubleClickHandle}
|
|
|
|
onRightPointHandle={app.onRightPointHandle}
|
|
|
|
onDragHandle={app.onDragHandle}
|
|
|
|
onHoverHandle={app.onHoverHandle}
|
|
|
|
onUnhoverHandle={app.onUnhoverHandle}
|
|
|
|
onReleaseHandle={app.onReleaseHandle}
|
|
|
|
onError={app.onError}
|
|
|
|
onRenderCountChange={app.onRenderCountChange}
|
|
|
|
onShapeChange={app.onShapeChange}
|
|
|
|
onShapeBlur={app.onShapeBlur}
|
|
|
|
onShapeClone={app.onShapeClone}
|
|
|
|
onBoundsChange={app.updateBounds}
|
|
|
|
onKeyDown={app.onKeyDown}
|
|
|
|
onKeyUp={app.onKeyUp}
|
2021-10-15 12:24:34 +00:00
|
|
|
/>
|
2021-10-15 12:49:48 +00:00
|
|
|
</ContextMenu>
|
2021-11-03 16:46:33 +00:00
|
|
|
{showUI && (
|
|
|
|
<StyledUI>
|
|
|
|
{settings.isFocusMode ? (
|
2021-11-16 16:01:29 +00:00
|
|
|
<FocusButton onSelect={app.toggleFocusMode} />
|
2021-11-03 16:46:33 +00:00
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<TopPanel
|
2021-11-05 14:13:14 +00:00
|
|
|
readOnly={readOnly}
|
2021-11-03 16:46:33 +00:00
|
|
|
showPages={showPages}
|
|
|
|
showMenu={showMenu}
|
|
|
|
showStyles={showStyles}
|
2021-11-05 14:13:14 +00:00
|
|
|
showZoom={showZoom}
|
2021-11-16 16:01:29 +00:00
|
|
|
showSponsorLink={showSponsorLink}
|
2021-11-03 16:46:33 +00:00
|
|
|
/>
|
|
|
|
<StyledSpacer />
|
2021-11-18 18:18:30 +00:00
|
|
|
{showTools && !readOnly && <ToolsPanel onBlur={handleMenuBlur} />}
|
2021-11-03 16:46:33 +00:00
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</StyledUI>
|
|
|
|
)}
|
|
|
|
</StyledLayout>
|
2021-08-10 16:12:55 +00:00
|
|
|
)
|
2021-11-08 14:21:37 +00:00
|
|
|
})
|
2021-08-10 16:12:55 +00:00
|
|
|
|
2021-11-06 11:19:15 +00:00
|
|
|
const OneOff = React.memo(function OneOff({
|
|
|
|
focusableRef,
|
|
|
|
autofocus,
|
|
|
|
}: {
|
|
|
|
autofocus?: boolean
|
|
|
|
focusableRef: React.RefObject<HTMLDivElement>
|
|
|
|
}) {
|
|
|
|
useKeyboardShortcuts(focusableRef)
|
|
|
|
useStylesheet()
|
2021-09-21 15:47:04 +00:00
|
|
|
|
2021-11-06 11:19:15 +00:00
|
|
|
React.useEffect(() => {
|
|
|
|
if (autofocus) {
|
|
|
|
focusableRef.current?.focus()
|
|
|
|
}
|
|
|
|
}, [autofocus])
|
|
|
|
|
|
|
|
return null
|
|
|
|
})
|
2021-09-21 15:47:04 +00:00
|
|
|
|
2021-11-03 16:46:33 +00:00
|
|
|
const StyledLayout = styled('div', {
|
2021-09-08 16:18:43 +00:00
|
|
|
position: 'absolute',
|
2021-08-10 16:12:55 +00:00
|
|
|
height: '100%',
|
|
|
|
width: '100%',
|
2021-09-11 22:17:54 +00:00
|
|
|
minHeight: 0,
|
|
|
|
minWidth: 0,
|
|
|
|
maxHeight: '100%',
|
|
|
|
maxWidth: '100%',
|
|
|
|
overflow: 'hidden',
|
2021-08-10 16:12:55 +00:00
|
|
|
boxSizing: 'border-box',
|
2021-09-21 15:47:04 +00:00
|
|
|
outline: 'none',
|
2021-08-18 07:19:13 +00:00
|
|
|
|
2021-08-10 16:12:55 +00:00
|
|
|
'& .tl-container': {
|
|
|
|
position: 'absolute',
|
|
|
|
top: 0,
|
|
|
|
left: 0,
|
2021-10-14 12:33:39 +00:00
|
|
|
height: '100%',
|
|
|
|
width: '100%',
|
|
|
|
zIndex: 1,
|
|
|
|
},
|
2021-11-16 16:01:29 +00:00
|
|
|
|
|
|
|
'& input, textarea, button, select, label, button': {
|
|
|
|
webkitTouchCallout: 'none',
|
|
|
|
webkitUserSelect: 'none',
|
|
|
|
'-webkit-tap-highlight-color': 'transparent',
|
|
|
|
'tap-highlight-color': 'transparent',
|
|
|
|
},
|
2021-10-14 12:33:39 +00:00
|
|
|
})
|
|
|
|
|
2021-11-03 16:46:33 +00:00
|
|
|
const StyledUI = styled('div', {
|
2021-10-14 12:33:39 +00:00
|
|
|
position: 'absolute',
|
|
|
|
top: 0,
|
|
|
|
left: 0,
|
|
|
|
height: '100%',
|
|
|
|
width: '100%',
|
|
|
|
padding: '8px 8px 0 8px',
|
|
|
|
display: 'flex',
|
|
|
|
alignItems: 'flex-start',
|
|
|
|
justifyContent: 'flex-start',
|
|
|
|
pointerEvents: 'none',
|
|
|
|
zIndex: 2,
|
|
|
|
'& > *': {
|
|
|
|
pointerEvents: 'all',
|
2021-08-10 16:12:55 +00:00
|
|
|
},
|
|
|
|
})
|
2021-09-09 13:06:45 +00:00
|
|
|
|
2021-11-03 16:46:33 +00:00
|
|
|
const StyledSpacer = styled('div', {
|
2021-09-09 13:06:45 +00:00
|
|
|
flexGrow: 2,
|
|
|
|
})
|