[improvement] Adds error boundary (#690)
* Add error boundary * Update useStyle.tsx * Update ErrorFallback.tsx
This commit is contained in:
parent
e33edb9cab
commit
e2a6badaef
12 changed files with 280 additions and 124 deletions
|
@ -48,11 +48,11 @@
|
||||||
"react-dom": "^16.8 || ^17.0"
|
"react-dom": "^16.8 || ^17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tldraw/intersect": "*",
|
|
||||||
"@tldraw/vec": "*",
|
|
||||||
"@swc-node/jest": "^1.4.3",
|
"@swc-node/jest": "^1.4.3",
|
||||||
"@testing-library/jest-dom": "^5.16.2",
|
"@testing-library/jest-dom": "^5.16.2",
|
||||||
"@testing-library/react": "^12.1.2",
|
"@testing-library/react": "^12.1.2",
|
||||||
|
"@tldraw/intersect": "*",
|
||||||
|
"@tldraw/vec": "*",
|
||||||
"@types/node": "^17.0.14",
|
"@types/node": "^17.0.14",
|
||||||
"@types/react": "^17.0.38",
|
"@types/react": "^17.0.38",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { observer } from 'mobx-react-lite'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
import {
|
import {
|
||||||
usePreventNavigationCss,
|
usePreventNavigationCss,
|
||||||
useZoomEvents,
|
useZoomEvents,
|
||||||
|
@ -136,5 +136,3 @@ export const Canvas = observer(function _Canvas<
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const stopPropagation: React.ClipboardEventHandler<HTMLDivElement> = (e) => e.stopPropagation()
|
|
||||||
|
|
|
@ -29,14 +29,24 @@ export function useSelection<T extends TLShape>(
|
||||||
if (selectedIds.length === 1) {
|
if (selectedIds.length === 1) {
|
||||||
const id = selectedIds[0]
|
const id = selectedIds[0]
|
||||||
const shape = page.shapes[id]
|
const shape = page.shapes[id]
|
||||||
|
|
||||||
|
if (!shape) {
|
||||||
|
throw Error(`selectedIds is set to the id of a shape that doesn't exist: ${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
rotation = shape.rotation || 0
|
rotation = shape.rotation || 0
|
||||||
isLocked = shape.isLocked || false
|
isLocked = shape.isLocked || false
|
||||||
|
|
||||||
const utils = getShapeUtils(shapeUtils, shape)
|
const utils = getShapeUtils(shapeUtils, shape)
|
||||||
|
|
||||||
bounds = utils.hideBounds ? undefined : utils.getBounds(shape)
|
bounds = utils.hideBounds ? undefined : utils.getBounds(shape)
|
||||||
} else if (selectedIds.length > 1) {
|
} else if (selectedIds.length > 1) {
|
||||||
const selectedShapes = selectedIds.map((id) => page.shapes[id])
|
const selectedShapes = selectedIds.map((id) => page.shapes[id])
|
||||||
|
|
||||||
rotation = 0
|
rotation = 0
|
||||||
|
|
||||||
isLocked = selectedShapes.every((shape) => shape.isLocked)
|
isLocked = selectedShapes.every((shape) => shape.isLocked)
|
||||||
|
|
||||||
bounds = selectedShapes.reduce((acc, shape, i) => {
|
bounds = selectedShapes.reduce((acc, shape, i) => {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
return getShapeUtils(shapeUtils, shape).getRotatedBounds(shape)
|
return getShapeUtils(shapeUtils, shape).getRotatedBounds(shape)
|
||||||
|
@ -48,9 +58,11 @@ export function useSelection<T extends TLShape>(
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
const [minX, minY] = canvasToScreen([bounds.minX, bounds.minY], pageState.camera)
|
const [minX, minY] = canvasToScreen([bounds.minX, bounds.minY], pageState.camera)
|
||||||
const [maxX, maxY] = canvasToScreen([bounds.maxX, bounds.maxY], pageState.camera)
|
const [maxX, maxY] = canvasToScreen([bounds.maxX, bounds.maxY], pageState.camera)
|
||||||
|
|
||||||
isLinked = !!Object.values(page.bindings).find(
|
isLinked = !!Object.values(page.bindings).find(
|
||||||
(binding) => selectedIds.includes(binding.toId) || selectedIds.includes(binding.fromId)
|
(binding) => selectedIds.includes(binding.toId) || selectedIds.includes(binding.fromId)
|
||||||
)
|
)
|
||||||
|
|
||||||
rSelectionBounds.current = {
|
rSelectionBounds.current = {
|
||||||
minX,
|
minX,
|
||||||
minY,
|
minY,
|
||||||
|
|
|
@ -138,8 +138,14 @@ export function useShapeTree<T extends TLShape, M extends Record<string, unknown
|
||||||
// If the shape's parent is a different shape (e.g. a group),
|
// If the shape's parent is a different shape (e.g. a group),
|
||||||
// add the parent to the sets of shapes to render. The parent's
|
// add the parent to the sets of shapes to render. The parent's
|
||||||
// children will all be rendered.
|
// children will all be rendered.
|
||||||
shapesIdsToRender.add(shape.parentId)
|
const parent = page.shapes[shape.parentId]
|
||||||
shapesToRender.add(page.shapes[shape.parentId])
|
|
||||||
|
if (parent === undefined) {
|
||||||
|
throw Error(`A shape (${shape.id}) has a parent (${shape.parentId}) that does not exist!`)
|
||||||
|
} else {
|
||||||
|
shapesIdsToRender.add(parent.id)
|
||||||
|
shapesToRender.add(parent)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Call onRenderCountChange callback when number of rendering shapes changes
|
// Call onRenderCountChange callback when number of rendering shapes changes
|
||||||
|
@ -163,7 +169,11 @@ export function useShapeTree<T extends TLShape, M extends Record<string, unknown
|
||||||
|
|
||||||
const tree: IShapeTreeNode<T, M>[] = []
|
const tree: IShapeTreeNode<T, M>[] = []
|
||||||
|
|
||||||
shapesToRender.forEach((shape) =>
|
shapesToRender.forEach((shape) => {
|
||||||
|
if (shape === undefined) {
|
||||||
|
throw Error('Rendered shapes included a missing shape')
|
||||||
|
}
|
||||||
|
|
||||||
addToShapeTree(
|
addToShapeTree(
|
||||||
shape,
|
shape,
|
||||||
tree,
|
tree,
|
||||||
|
@ -174,7 +184,7 @@ export function useShapeTree<T extends TLShape, M extends Record<string, unknown
|
||||||
false,
|
false,
|
||||||
meta
|
meta
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
|
|
||||||
tree.sort((a, b) => a.shape.childIndex - b.shape.childIndex)
|
tree.sort((a, b) => a.shape.childIndex - b.shape.childIndex)
|
||||||
|
|
||||||
|
|
|
@ -79,36 +79,6 @@ const defaultTheme: TLTheme = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TLCSS = css`
|
export const TLCSS = css`
|
||||||
@font-face {
|
|
||||||
font-family: 'Recursive';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImKsvxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
|
|
||||||
format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
|
|
||||||
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Recursive';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImKsvxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
|
|
||||||
format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
|
|
||||||
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Recursive Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 420;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImqvTxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
|
|
||||||
format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
|
|
||||||
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
.tl-container {
|
.tl-container {
|
||||||
--tl-zoom: 1;
|
--tl-zoom: 1;
|
||||||
--tl-scale: calc(1 / var(--tl-zoom));
|
--tl-scale: calc(1 / var(--tl-zoom));
|
||||||
|
@ -351,11 +321,6 @@ export const TLCSS = css`
|
||||||
.tl-brush.dashed {
|
.tl-brush.dashed {
|
||||||
stroke: none;
|
stroke: none;
|
||||||
}
|
}
|
||||||
.tl-dot {
|
|
||||||
fill: var(--tl-background);
|
|
||||||
stroke: var(--tl-foreground);
|
|
||||||
stroke-width: 2px;
|
|
||||||
}
|
|
||||||
.tl-handle {
|
.tl-handle {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
"idb-keyval": "^6.1.0",
|
"idb-keyval": "^6.1.0",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"perfect-freehand": "^1.0.16",
|
"perfect-freehand": "^1.0.16",
|
||||||
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-hotkey-hook": "^1.0.2",
|
"react-hotkey-hook": "^1.0.2",
|
||||||
"react-hotkeys-hook": "^3.4.4",
|
"react-hotkeys-hook": "^3.4.4",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
|
|
|
@ -12,6 +12,8 @@ import { FocusButton } from '~components/FocusButton'
|
||||||
import { TLDR } from '~state/TLDR'
|
import { TLDR } from '~state/TLDR'
|
||||||
import { GRID_SIZE } from '~constants'
|
import { GRID_SIZE } from '~constants'
|
||||||
import { Loading } from '~components/Loading'
|
import { Loading } from '~components/Loading'
|
||||||
|
import { ErrorBoundary } from 'react-error-boundary'
|
||||||
|
import { ErrorFallback } from '~components/ErrorFallback'
|
||||||
|
|
||||||
export interface TldrawProps extends TDCallbacks {
|
export interface TldrawProps extends TDCallbacks {
|
||||||
/**
|
/**
|
||||||
|
@ -415,81 +417,83 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
<Loading />
|
<Loading />
|
||||||
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<Renderer
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
id={id}
|
<Renderer
|
||||||
containerRef={rWrapper}
|
id={id}
|
||||||
shapeUtils={shapeUtils}
|
containerRef={rWrapper}
|
||||||
page={page}
|
shapeUtils={shapeUtils}
|
||||||
pageState={pageState}
|
page={page}
|
||||||
assets={assets}
|
pageState={pageState}
|
||||||
snapLines={appState.snapLines}
|
assets={assets}
|
||||||
grid={GRID_SIZE}
|
snapLines={appState.snapLines}
|
||||||
users={room?.users}
|
grid={GRID_SIZE}
|
||||||
userId={room?.userId}
|
users={room?.users}
|
||||||
theme={theme}
|
userId={room?.userId}
|
||||||
meta={meta}
|
theme={theme}
|
||||||
hideBounds={hideBounds}
|
meta={meta}
|
||||||
hideHandles={hideHandles}
|
hideBounds={hideBounds}
|
||||||
hideResizeHandles={isHideResizeHandlesShape}
|
hideHandles={hideHandles}
|
||||||
hideIndicators={hideIndicators}
|
hideResizeHandles={isHideResizeHandlesShape}
|
||||||
hideBindingHandles={!settings.showBindingHandles}
|
hideIndicators={hideIndicators}
|
||||||
hideCloneHandles={hideCloneHandles}
|
hideBindingHandles={!settings.showBindingHandles}
|
||||||
hideRotateHandles={!settings.showRotateHandles}
|
hideCloneHandles={hideCloneHandles}
|
||||||
hideGrid={!settings.showGrid}
|
hideRotateHandles={!settings.showRotateHandles}
|
||||||
showDashedBrush={showDashedBrush}
|
hideGrid={!settings.showGrid}
|
||||||
performanceMode={app.session?.performanceMode}
|
showDashedBrush={showDashedBrush}
|
||||||
onPinchStart={app.onPinchStart}
|
performanceMode={app.session?.performanceMode}
|
||||||
onPinchEnd={app.onPinchEnd}
|
onPinchStart={app.onPinchStart}
|
||||||
onPinch={app.onPinch}
|
onPinchEnd={app.onPinchEnd}
|
||||||
onPan={app.onPan}
|
onPinch={app.onPinch}
|
||||||
onZoom={app.onZoom}
|
onPan={app.onPan}
|
||||||
onPointerDown={app.onPointerDown}
|
onZoom={app.onZoom}
|
||||||
onPointerMove={app.onPointerMove}
|
onPointerDown={app.onPointerDown}
|
||||||
onPointerUp={app.onPointerUp}
|
onPointerMove={app.onPointerMove}
|
||||||
onPointCanvas={app.onPointCanvas}
|
onPointerUp={app.onPointerUp}
|
||||||
onDoubleClickCanvas={app.onDoubleClickCanvas}
|
onPointCanvas={app.onPointCanvas}
|
||||||
onRightPointCanvas={app.onRightPointCanvas}
|
onDoubleClickCanvas={app.onDoubleClickCanvas}
|
||||||
onDragCanvas={app.onDragCanvas}
|
onRightPointCanvas={app.onRightPointCanvas}
|
||||||
onReleaseCanvas={app.onReleaseCanvas}
|
onDragCanvas={app.onDragCanvas}
|
||||||
onPointShape={app.onPointShape}
|
onReleaseCanvas={app.onReleaseCanvas}
|
||||||
onDoubleClickShape={app.onDoubleClickShape}
|
onPointShape={app.onPointShape}
|
||||||
onRightPointShape={app.onRightPointShape}
|
onDoubleClickShape={app.onDoubleClickShape}
|
||||||
onDragShape={app.onDragShape}
|
onRightPointShape={app.onRightPointShape}
|
||||||
onHoverShape={app.onHoverShape}
|
onDragShape={app.onDragShape}
|
||||||
onUnhoverShape={app.onUnhoverShape}
|
onHoverShape={app.onHoverShape}
|
||||||
onReleaseShape={app.onReleaseShape}
|
onUnhoverShape={app.onUnhoverShape}
|
||||||
onPointBounds={app.onPointBounds}
|
onReleaseShape={app.onReleaseShape}
|
||||||
onDoubleClickBounds={app.onDoubleClickBounds}
|
onPointBounds={app.onPointBounds}
|
||||||
onRightPointBounds={app.onRightPointBounds}
|
onDoubleClickBounds={app.onDoubleClickBounds}
|
||||||
onDragBounds={app.onDragBounds}
|
onRightPointBounds={app.onRightPointBounds}
|
||||||
onHoverBounds={app.onHoverBounds}
|
onDragBounds={app.onDragBounds}
|
||||||
onUnhoverBounds={app.onUnhoverBounds}
|
onHoverBounds={app.onHoverBounds}
|
||||||
onReleaseBounds={app.onReleaseBounds}
|
onUnhoverBounds={app.onUnhoverBounds}
|
||||||
onPointBoundsHandle={app.onPointBoundsHandle}
|
onReleaseBounds={app.onReleaseBounds}
|
||||||
onDoubleClickBoundsHandle={app.onDoubleClickBoundsHandle}
|
onPointBoundsHandle={app.onPointBoundsHandle}
|
||||||
onRightPointBoundsHandle={app.onRightPointBoundsHandle}
|
onDoubleClickBoundsHandle={app.onDoubleClickBoundsHandle}
|
||||||
onDragBoundsHandle={app.onDragBoundsHandle}
|
onRightPointBoundsHandle={app.onRightPointBoundsHandle}
|
||||||
onHoverBoundsHandle={app.onHoverBoundsHandle}
|
onDragBoundsHandle={app.onDragBoundsHandle}
|
||||||
onUnhoverBoundsHandle={app.onUnhoverBoundsHandle}
|
onHoverBoundsHandle={app.onHoverBoundsHandle}
|
||||||
onReleaseBoundsHandle={app.onReleaseBoundsHandle}
|
onUnhoverBoundsHandle={app.onUnhoverBoundsHandle}
|
||||||
onPointHandle={app.onPointHandle}
|
onReleaseBoundsHandle={app.onReleaseBoundsHandle}
|
||||||
onDoubleClickHandle={app.onDoubleClickHandle}
|
onPointHandle={app.onPointHandle}
|
||||||
onRightPointHandle={app.onRightPointHandle}
|
onDoubleClickHandle={app.onDoubleClickHandle}
|
||||||
onDragHandle={app.onDragHandle}
|
onRightPointHandle={app.onRightPointHandle}
|
||||||
onHoverHandle={app.onHoverHandle}
|
onDragHandle={app.onDragHandle}
|
||||||
onUnhoverHandle={app.onUnhoverHandle}
|
onHoverHandle={app.onHoverHandle}
|
||||||
onReleaseHandle={app.onReleaseHandle}
|
onUnhoverHandle={app.onUnhoverHandle}
|
||||||
onError={app.onError}
|
onReleaseHandle={app.onReleaseHandle}
|
||||||
onRenderCountChange={app.onRenderCountChange}
|
onError={app.onError}
|
||||||
onShapeChange={app.onShapeChange}
|
onRenderCountChange={app.onRenderCountChange}
|
||||||
onShapeBlur={app.onShapeBlur}
|
onShapeChange={app.onShapeChange}
|
||||||
onShapeClone={app.onShapeClone}
|
onShapeBlur={app.onShapeBlur}
|
||||||
onBoundsChange={app.updateBounds}
|
onShapeClone={app.onShapeClone}
|
||||||
onKeyDown={app.onKeyDown}
|
onBoundsChange={app.updateBounds}
|
||||||
onKeyUp={app.onKeyUp}
|
onKeyDown={app.onKeyDown}
|
||||||
onDragOver={app.onDragOver}
|
onKeyUp={app.onKeyUp}
|
||||||
onDrop={app.onDrop}
|
onDragOver={app.onDragOver}
|
||||||
/>
|
onDrop={app.onDrop}
|
||||||
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
{showUI && (
|
{showUI && (
|
||||||
<StyledUI>
|
<StyledUI>
|
||||||
|
|
122
packages/tldraw/src/components/ErrorFallback/ErrorFallback.tsx
Normal file
122
packages/tldraw/src/components/ErrorFallback/ErrorFallback.tsx
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import * as React from 'react'
|
||||||
|
import { FallbackProps } from 'react-error-boundary'
|
||||||
|
import { Divider } from '~components/Primitives/Divider'
|
||||||
|
import { RowButton } from '~components/Primitives/RowButton'
|
||||||
|
import { useTldrawApp } from '~hooks'
|
||||||
|
import { styled } from '~styles'
|
||||||
|
|
||||||
|
export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||||
|
const app = useTldrawApp()
|
||||||
|
|
||||||
|
const refreshPage = () => {
|
||||||
|
window.location.reload()
|
||||||
|
resetErrorBoundary()
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyError = () => {
|
||||||
|
const textarea = document.createElement('textarea')
|
||||||
|
textarea.value = error.message
|
||||||
|
document.body.appendChild(textarea)
|
||||||
|
textarea.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
textarea.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadBackup = () => {
|
||||||
|
app.saveProjectAs()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetDocument = () => {
|
||||||
|
app.resetDocument()
|
||||||
|
resetErrorBoundary()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<InnerContainer>
|
||||||
|
<div>We've encountered an error!</div>
|
||||||
|
<pre>
|
||||||
|
<code>{error.message}</code>
|
||||||
|
</pre>
|
||||||
|
<Buttons>
|
||||||
|
<RowButton onClick={copyError}>Copy Error</RowButton>
|
||||||
|
<RowButton onClick={refreshPage}>Refresh Page</RowButton>
|
||||||
|
</Buttons>
|
||||||
|
<Divider />
|
||||||
|
<p>
|
||||||
|
Keep getting this error?{' '}
|
||||||
|
<a onClick={downloadBackup} title="Download your project">
|
||||||
|
Download your project
|
||||||
|
</a>{' '}
|
||||||
|
as a backup and then{' '}
|
||||||
|
<a onClick={resetDocument} title="Reset the document">
|
||||||
|
reset the document
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</InnerContainer>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled('div', {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '$canvas',
|
||||||
|
})
|
||||||
|
|
||||||
|
const InnerContainer = styled('div', {
|
||||||
|
backgroundColor: '$panel',
|
||||||
|
border: '1px solid $panelContrast',
|
||||||
|
padding: '$5',
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow: '$panel',
|
||||||
|
maxWidth: 320,
|
||||||
|
color: '$text',
|
||||||
|
fontFamily: '$ui',
|
||||||
|
fontSize: '$2',
|
||||||
|
textAlign: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '$3',
|
||||||
|
'& > pre': {
|
||||||
|
marginTop: '$3',
|
||||||
|
marginBottom: '$3',
|
||||||
|
textAlign: 'left',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
backgroundColor: '$hover',
|
||||||
|
padding: '$4',
|
||||||
|
borderRadius: '$2',
|
||||||
|
fontFamily: '"Menlo", "Monaco", monospace',
|
||||||
|
fontWeight: 500,
|
||||||
|
},
|
||||||
|
'& p': {
|
||||||
|
fontFamily: '$body',
|
||||||
|
lineHeight: 1.7,
|
||||||
|
padding: '$5',
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
'& a': {
|
||||||
|
color: '$text',
|
||||||
|
cursor: 'pointer',
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
|
'& hr': {
|
||||||
|
marginLeft: '-$5',
|
||||||
|
marginRight: '-$5',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Buttons = styled('div', {
|
||||||
|
display: 'flex',
|
||||||
|
'& > button > div': {
|
||||||
|
justifyContent: 'center',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
})
|
1
packages/tldraw/src/components/ErrorFallback/index.ts
Normal file
1
packages/tldraw/src/components/ErrorFallback/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './ErrorFallback'
|
|
@ -3,9 +3,43 @@ import * as React from 'react'
|
||||||
const styles = new Map<string, HTMLStyleElement>()
|
const styles = new Map<string, HTMLStyleElement>()
|
||||||
|
|
||||||
const UID = `tldraw-fonts`
|
const UID = `tldraw-fonts`
|
||||||
const WEBFONT_URL =
|
|
||||||
'https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block'
|
const CSS = `
|
||||||
const CSS = `@import url('${WEBFONT_URL}');`
|
@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block');
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Recursive';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImKsvxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
|
||||||
|
format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
|
||||||
|
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Recursive';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImKsvxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
|
||||||
|
format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
|
||||||
|
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Recursive Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 420;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImqvTxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
|
||||||
|
format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
|
||||||
|
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export function useStylesheet() {
|
export function useStylesheet() {
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
|
|
|
@ -21,6 +21,7 @@ const { styled, createTheme } = createStitches({
|
||||||
tooltip: '#1d1d1d',
|
tooltip: '#1d1d1d',
|
||||||
tooltipContrast: '#ffffff',
|
tooltipContrast: '#ffffff',
|
||||||
warn: 'rgba(255, 100, 100, 1)',
|
warn: 'rgba(255, 100, 100, 1)',
|
||||||
|
canvas: 'rgb(248, 249, 250)',
|
||||||
},
|
},
|
||||||
shadows: {
|
shadows: {
|
||||||
2: '0px 1px 1px rgba(0, 0, 0, 0.14)',
|
2: '0px 1px 1px rgba(0, 0, 0, 0.14)',
|
||||||
|
@ -110,6 +111,7 @@ export const dark = createTheme({
|
||||||
text: '#f8f9fa',
|
text: '#f8f9fa',
|
||||||
tooltip: '#1d1d1d',
|
tooltip: '#1d1d1d',
|
||||||
tooltipContrast: '#ffffff',
|
tooltipContrast: '#ffffff',
|
||||||
|
canvas: '#212529',
|
||||||
},
|
},
|
||||||
shadows: {
|
shadows: {
|
||||||
2: '0px 1px 1px rgba(0, 0, 0, 0.24)',
|
2: '0px 1px 1px rgba(0, 0, 0, 0.24)',
|
||||||
|
|
|
@ -9385,6 +9385,13 @@ rc@^1.2.7, rc@^1.2.8:
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
scheduler "^0.20.2"
|
scheduler "^0.20.2"
|
||||||
|
|
||||||
|
react-error-boundary@^3.1.4:
|
||||||
|
version "3.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0"
|
||||||
|
integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.5"
|
||||||
|
|
||||||
react-feather@^2.0.9:
|
react-feather@^2.0.9:
|
||||||
version "2.0.9"
|
version "2.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.9.tgz#6e42072130d2fa9a09d4476b0e61b0ed17814480"
|
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.9.tgz#6e42072130d2fa9a09d4476b0e61b0ed17814480"
|
||||||
|
|
Loading…
Reference in a new issue