[improvement] local copy and export for images (#669)

* local images

use assets for local copy

add menu options

* clean up packages

* cleanup unused content, move file handling into app.paste

* Add tldraw-assets.json to other files.

* add path to editor

* Update build.mjs

* add export to server example with link to gist

* Fix onAssetCreate and onAssetDelete APIs

* Update yarn.lock

* fix bugs on paste, adjust api for getting images, fix readonly on cut, copy, paste

* re-enable swc

* paste svg strings as svg images

* cleanup

* fix string case for tldraw json
This commit is contained in:
Steve Ruiz 2022-05-11 14:25:08 +01:00 committed by GitHub
parent 1a1ce48407
commit c54c800675
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1375 additions and 938 deletions

View file

@ -2,6 +2,7 @@
import fs from 'fs'
import esbuild from 'esbuild'
import { createRequire } from 'module'
import path from 'path'
const pkg = createRequire(import.meta.url)('../package.json')
@ -20,9 +21,11 @@ async function main() {
fs.mkdirSync('./dist')
}
fs.copyFile('./src/index.html', './dist/index.html', (err) => {
if (err) throw err
})
fs.readdirSync('./src/public').forEach((file) =>
fs.copyFile(path.join('./src/public', file), path.join('./dist', file), (err) => {
if (err) throw err
})
)
try {
esbuild.buildSync({

View file

@ -2,6 +2,7 @@
import fs from 'fs'
import esbuildServe from 'esbuild-serve'
import dotenv from 'dotenv'
import path from 'path'
dotenv.config()
@ -20,9 +21,11 @@ async function main() {
fs.mkdirSync('./dist')
}
fs.copyFile('./src/index.html', './dist/index.html', (err) => {
if (err) throw err
})
fs.readdirSync('./src/public').forEach((file) =>
fs.copyFile(path.join('./src/public', file), path.join('./dist', file), (err) => {
if (err) throw err
})
)
try {
await esbuildServe(

View file

@ -67,7 +67,6 @@ const App: React.FC = () => {
document={rInitialDocument.current}
onMount={handleMount}
onPersist={handlePersist}
onExport={exportToImage}
autofocus
/>
</div>

View file

@ -0,0 +1,20 @@
html,
* {
box-sizing: border-box;
}
body {
overscroll-behavior: none;
margin: 0px;
padding: 0px;
}
.tldraw {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
width: 100%;
height: 100%;
}

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,6 @@
import { Tldraw, TldrawApp, TldrawProps, useFileSystem } from '@tldraw/tldraw'
import { useAccountHandlers } from 'hooks/useAccountHandlers'
import React, { FC } from 'react'
import { exportToImage } from 'utils/export'
import * as gtag from 'utils/gtag'
declare const window: Window & { app: TldrawApp }
@ -46,7 +45,6 @@ const Editor: FC<EditorProps & Partial<TldrawProps>> = ({
showSponsorLink={!isSponsor}
onSignIn={isSponsor ? undefined : onSignIn}
onSignOut={isUser ? onSignOut : undefined}
onExport={exportToImage}
{...fileSystemEvents}
{...rest}
/>

View file

@ -6,7 +6,6 @@ import { useMultiplayerAssets } from 'hooks/useMultiplayerAssets'
import { useMultiplayerState } from 'hooks/useMultiplayerState'
import { FC } from 'react'
import { styled } from 'styles'
import { exportToImage } from 'utils/export'
const client = createClient({
publicApiKey: process.env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_API_KEY || '',
@ -56,7 +55,6 @@ function Editor({ roomId, isUser, isSponsor }: Props) {
showSponsorLink={!isSponsor}
onSignIn={isSponsor ? undefined : onSignIn}
onSignOut={isUser ? onSignOut : undefined}
onExport={exportToImage}
onAssetCreate={onAssetCreate}
onAssetDelete={onAssetDelete}
{...fileSystemEvents}

View file

@ -1,27 +1,38 @@
import { TldrawApp } from '@tldraw/tldraw'
import { useCallback } from 'react'
export function useMultiplayerAssets() {
const onAssetCreate = useCallback(async (file: File, id: string): Promise<string | false> => {
const filename = encodeURIComponent(file.name)
const fileType = encodeURIComponent(file.type)
const res = await fetch(`/api/upload?file=${filename}&fileType=${fileType}`)
const { url, fields } = await res.json()
const formData = new FormData()
Object.entries({ ...fields, file }).forEach(([key, value]) => {
formData.append(key, value as any)
})
const upload = await fetch(url, {
method: 'POST',
body: formData,
})
if (upload.ok) {
return url + '/' + filename
} else {
return false
}
}, [])
const onAssetCreate = useCallback(
// Send the asset to our upload enpoint, which in turn will send it to AWS and
// respond with the URL of the uploaded file.
async (app: TldrawApp, file: File, id: string): Promise<string | false> => {
const filename = encodeURIComponent(file.name)
const onAssetDelete = useCallback(async (id: string): Promise<boolean> => {
const fileType = encodeURIComponent(file.type)
const res = await fetch(`/api/upload?file=${filename}&fileType=${fileType}`)
const { url, fields } = await res.json()
const formData = new FormData()
Object.entries({ ...fields, file }).forEach(([key, value]) => {
formData.append(key, value as any)
})
const upload = await fetch(url, {
method: 'POST',
body: formData,
})
if (!upload.ok) return false
return url + '/' + filename
},
[]
)
const onAssetDelete = useCallback(async (app: TldrawApp, id: string): Promise<boolean> => {
// noop
return true
}, [])

View file

@ -34,7 +34,6 @@
"next-auth": "^4.0.5",
"next-pwa": "^5.4.4",
"next-themes": "^0.0.15",
"puppeteer-core": "9.0.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
@ -44,7 +43,6 @@
"@tldraw/tldraw": "*",
"@types/react": "^17.0.19",
"@types/react-dom": "^17.0.9",
"cors": "^2.8.5",
"eslint": "^8.8.0",
"eslint-config-next": "^12.0.10",
"typescript": "^4.5.2"

View file

@ -1,108 +0,0 @@
import { NextApiRequest, NextApiResponse } from 'next'
import chromium from 'chrome-aws-lambda'
import Cors from 'cors'
import { TDExport, TDExportTypes, TldrawApp } from '@tldraw/tldraw'
const cors = Cors({
methods: ['POST'],
})
function runMiddleware(
req: NextApiRequest,
res: NextApiResponse,
fn: (req: NextApiRequest, res: NextApiResponse, fn: (args: any) => any) => any
) {
return new Promise((resolve, reject) => {
fn(req, res, (result) => {
if (result instanceof Error) return reject(result)
return resolve(result)
})
})
}
const FRONTEND_URL =
process.env.NODE_ENV === 'development'
? 'http://localhost:3000/?exportMode'
: 'https://www.tldraw.com/?exportMode'
declare global {
interface Window {
app: TldrawApp
}
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await runMiddleware(req, res, cors)
const { body } = req
const {
size: [width, height],
type,
} = body
if (type === TDExportTypes.PDF) res.status(500).send('Not implemented yet.')
try {
const browser = await chromium.puppeteer.launch({
slowMo: 50,
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath,
ignoreHTTPSErrors: true,
headless: chromium.headless,
})
const page = await browser.newPage()
await page.setUserAgent(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36'
)
await page.goto(FRONTEND_URL, { timeout: 15 * 1000, waitUntil: 'networkidle0' })
await page.setViewport({ width: Math.floor(width), height: Math.floor(height) })
await page.evaluateHandle('document.fonts.ready')
let err: string
await page.evaluate(async (body: TDExport) => {
try {
let app = window.app
if (!app) app = await new Promise((resolve) => setTimeout(() => resolve(window.app), 250))
await app.ready
const { assets, shapes, currentPageId } = body
// If the hapes were a direct child of their current page,
// reparent them to the app's current page.
shapes.forEach((shape) => {
if (shape.parentId === currentPageId) {
shape.parentId = app.currentPageId
}
})
app.patchAssets(assets)
app.createShapes(...shapes)
app.selectAll()
app.zoomToSelection()
app.selectNone()
const tlContainer = document.getElementsByClassName('tl-container').item(0) as HTMLElement
if (tlContainer) {
tlContainer.style.background = 'transparent'
}
} catch (e) {
err = e.message
}
}, body)
if (err) {
throw err
}
const imageBuffer = await page.screenshot({
type,
omitBackground: true,
})
await browser.close()
res.status(200).send(imageBuffer)
} catch (err) {
console.error(err.message)
res.status(500).send(err)
}
}
// Allow the server to support requests with up to 5mb of data.
export const config = {
api: {
bodyParser: {
sizeLimit: '5mb',
},
},
}

File diff suppressed because one or more lines are too long

View file

@ -5,25 +5,25 @@ export const EXPORT_ENDPOINT =
? 'http://localhost:3000/api/export'
: 'https://www.tldraw.com/api/export'
export async function exportToImage(info: TDExport) {
if (info.serialized) {
const link = document.createElement('a')
link.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(info.serialized)
link.download = info.name + '.' + info.type
link.click()
// export async function exportToImage(info: TDExport) {
// if (info.serialized) {
// const link = document.createElement('a')
// link.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(info.serialized)
// link.download = info.name + '.' + info.type
// link.click()
return
}
// return
// }
const response = await fetch(EXPORT_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(info),
})
const blob = await response.blob()
const blobUrl = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = blobUrl
link.download = info.name + '.' + info.type
link.click()
}
// const response = await fetch(EXPORT_ENDPOINT, {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(info),
// })
// const blob = await response.blob()
// const blobUrl = URL.createObjectURL(blob)
// const link = document.createElement('a')
// link.href = blobUrl
// link.download = info.name + '.' + info.type
// link.click()
// }

View file

@ -37,7 +37,7 @@
"react-dom": "^17.0.2",
"react-feather": "^2.0.9",
"rimraf": "^3.0.2",
"typescript": "^4.5.5"
"typescript": "^4.6.4"
},
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
}

View file

@ -29,7 +29,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rimraf": "^3.0.2",
"typescript": "^4.5.5"
"typescript": "^4.6.4"
},
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
}

View file

@ -32,7 +32,7 @@
"react-router": "^6.2.1",
"react-router-dom": "^6.2.1",
"rimraf": "^3.0.2",
"typescript": "^4.5.5"
"typescript": "^4.6.4"
},
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
}

View file

@ -17,6 +17,7 @@ import UIOptions from './ui-options'
import { Multiplayer } from './multiplayer'
import { Multiplayer as MultiplayerWithImages } from './multiplayer-with-images'
import './styles.css'
import Export from '~export'
export default function App(): JSX.Element {
return (
@ -50,6 +51,8 @@ export default function App(): JSX.Element {
<Route path="/no-size-embedded" element={<NoSizeEmbedded />} />
<Route path="/export" element={<Export />} />
<Route path="/multiplayer" element={<Multiplayer />} />
<Route path="/multiplayer-with-images" element={MultiplayerWithImages} />
@ -102,6 +105,9 @@ export default function App(): JSX.Element {
<li>
<Link to="/no-size-embedded">Embedded (without explicit size)</Link>
</li>
<li>
<Link to="/export">Export</Link>
</li>
<li>
<Link to="/multiplayer">Multiplayer</Link>
</li>

View file

@ -0,0 +1,133 @@
import * as React from 'react'
import {
TldrawApp,
ImageShape,
TDExportType,
TDAssets,
TDAssetType,
TDShapeType,
TDShape,
Tldraw,
TLDR,
} from '@tldraw/tldraw'
import Vec from '@tldraw/vec'
import { Utils } from '@tldraw/core'
export default function Export(): JSX.Element {
const handleExport = React.useCallback(async (app: TldrawApp) => {
exportViaServer(app, TDExportType.PNG)
}, [])
return (
<div className="tldraw">
<Tldraw onExport={handleExport} />
</div>
)
}
async function exportViaServer(app: TldrawApp, type: TDExportType) {
app.setIsLoading(true)
const name = app.page.name ?? 'export'
// Export the selection, if any, or else the entire page.
const { currentPageId } = app
const shapeIds = app.selectedIds.length ? app.selectedIds : Object.keys(app.page.shapes)
try {
const assets: TDAssets = {}
// Collect shapes and assets (serializing assets if needed)
const shapes: TDShape[] = shapeIds.map((id) => {
const shape = { ...app.getShape(id) }
if (shape.assetId) {
const asset = { ...app.document.assets[shape.assetId] }
// If the asset is a GIF, then serialize an image
if (asset.src.toLowerCase().endsWith('gif')) {
asset.src = app.serializeImage(shape.id)
}
// If the asset is an image, then serialize an image
if (shape.type === TDShapeType.Video) {
asset.src = app.serializeVideo(shape.id)
asset.type = TDAssetType.Image
// Cast shape to image shapes to properly display snapshots
;(shape as unknown as ImageShape).type = TDShapeType.Image
}
// Patch asset table
assets[shape.assetId] = asset
}
return shape
})
const { width, height } = Utils.expandBounds(TLDR.getSelectedBounds(app.state), 64)
const size = [width, height]
// Create serialized data for JSON or SVGs
let serialized: string | undefined
switch (type) {
case TDExportType.SVG: {
const svg = await app.getSvg(shapeIds, {
transparentBackground: true,
includeFonts: true,
})
if (!svg) {
throw Error('Could not get an SVG.')
}
serialized = TLDR.getSvgString(svg, 1)
break
}
case TDExportType.JSON: {
serialized = JSON.stringify(shapes, null, 2)
break
}
}
if (serialized) {
// If we have serialized data, then just download this locally as a file
const link = document.createElement('a')
link.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(serialized)
link.download = name + '.' + type
link.click()
return
}
// Post the export info to a server that can create an image.
// See: https://gist.github.com/steveruizok/c30fc99b9b3d95a14c82c59bdcc69201
const endpoint = 'some_serverless_endpoint'
// The response should be a blob (e.g. an image or JSON file)
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name,
currentPageId,
shapes,
assets,
type,
size: type === 'png' ? Vec.mul(size, 2) : size,
}),
})
// Download the blob from the response
const blob = await response.blob()
const blobUrl = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = blobUrl
link.download = name + '.' + type
link.click()
} catch (error) {
console.error(error)
} finally {
app.setIsLoading(false)
}
}

View file

@ -1,33 +1,62 @@
import * as React from 'react'
import { TDExport, Tldraw } from '@tldraw/tldraw'
import { TldrawApp, TDExport, TDExportType, Tldraw } from '@tldraw/tldraw'
const ACTION = 'download' as 'download' | 'open'
export default function Export(): JSX.Element {
const handleExport = React.useCallback(async (info: TDExport) => {
if (info.serialized) {
const link = document.createElement('a')
link.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(info.serialized)
link.download = info.name + '.' + info.type
link.click()
const handleExport = React.useCallback(async (app: TldrawApp, info: TDExport) => {
// When a user exports, the default behavior is to download
// the exported data as a file. If the onExport callback is
// provided, it will be called instead.
return
switch (ACTION) {
case 'download': {
// Download the file
const blobUrl = URL.createObjectURL(info.blob)
const link = document.createElement('a')
link.href = blobUrl
link.download = info.name + '.' + info.type
link.click()
break
}
case 'open': {
// Open the file in a new tab
const blobUrl = URL.createObjectURL(info.blob)
const link = document.createElement('a')
link.href = blobUrl
link.target = '_blank'
link.click()
break
}
}
}, [])
const response = await fetch('some_serverless_endpoint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(info),
})
const blob = await response.blob()
const blobUrl = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = blobUrl
link.download = info.name + '.' + info.type
link.click()
const [app, setApp] = React.useState<TldrawApp>()
const handleExportSVG = React.useCallback(() => {
app?.exportImage(TDExportType.SVG, { scale: 1, quality: 1, transparentBackground: false })
}, [app])
const handleExportPNG = React.useCallback(() => {
app?.exportImage(TDExportType.PNG, { scale: 2, quality: 1, transparentBackground: true })
}, [app])
const handleExportJPG = React.useCallback(() => {
app?.exportImage(TDExportType.JPG, { scale: 2, quality: 1, transparentBackground: false })
}, [app])
const handleMount = React.useCallback((app: TldrawApp) => {
setApp(app)
}, [])
return (
<div className="tldraw">
<Tldraw onExport={handleExport} />
<Tldraw id="export_example" onMount={handleMount} onExport={handleExport} />
<div style={{ position: 'fixed', top: 128, left: 32, zIndex: 100 }}>
<button onClick={handleExportPNG}>Export as PNG</button>
<button onClick={handleExportSVG}>Export as SVG</button>
<button onClick={handleExportJPG}>Export as JPG</button>
</div>
</div>
)
}

File diff suppressed because one or more lines are too long

View file

@ -67,8 +67,8 @@
"source-map-loader": "^3.0.1",
"tslib": "^2.3.1",
"turbo": "^1.1.2",
"typedoc": "^0.22.11",
"typescript": "^4.5.5",
"typedoc": "^0.22.15",
"typescript": "^4.6.4",
"webpack": "^5.68.0"
},
"husky": {

View file

@ -78,7 +78,7 @@ const defaultTheme: TLTheme = {
grid: 'rgba(144, 144, 144, 1)',
}
const tlcss = css`
export const TLCSS = css`
@font-face {
font-family: 'Recursive';
font-style: normal;
@ -89,7 +89,6 @@ const tlcss = css`
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;
@ -100,7 +99,6 @@ const tlcss = css`
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;
@ -111,7 +109,6 @@ const tlcss = css`
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-zoom: 1;
--tl-scale: calc(1 / var(--tl-zoom));
@ -134,11 +131,9 @@ const tlcss = css`
overscroll-behavior: none;
background-color: var(--tl-background);
}
.tl-container * {
box-sizing: border-box;
}
.tl-overlay {
position: absolute;
width: 100%;
@ -146,7 +141,6 @@ const tlcss = css`
touch-action: none;
pointer-events: none;
}
.tl-grid {
position: absolute;
width: 100%;
@ -155,17 +149,14 @@ const tlcss = css`
pointer-events: none;
user-select: none;
}
.tl-snap-line {
stroke: var(--tl-accent);
stroke-width: calc(1px * var(--tl-scale));
}
.tl-snap-point {
stroke: var(--tl-accent);
stroke-width: calc(1px * var(--tl-scale));
}
.tl-canvas {
position: absolute;
width: 100%;
@ -174,7 +165,6 @@ const tlcss = css`
pointer-events: all;
overflow: clip;
}
.tl-layer {
position: absolute;
top: 0px;
@ -183,7 +173,6 @@ const tlcss = css`
width: 0px;
contain: layout style size;
}
.tl-absolute {
position: absolute;
top: 0px;
@ -191,7 +180,6 @@ const tlcss = css`
transform-origin: center center;
contain: layout style size;
}
.tl-positioned {
position: absolute;
top: 0px;
@ -204,14 +192,12 @@ const tlcss = css`
contain: layout style size;
will-change: var(--tl-performance-all);
}
.tl-positioned-svg {
width: 100%;
height: 100%;
overflow: hidden;
contain: layout style size;
}
.tl-positioned-div {
position: relative;
width: 100%;
@ -221,17 +207,14 @@ const tlcss = css`
overflow: hidden;
contain: layout style size;
}
.tl-positioned-selected {
will-change: var(--tl-performance-selected);
}
.tl-inner-div {
position: relative;
width: 100%;
height: 100%;
}
.tl-stroke-hitarea {
fill: none;
stroke: transparent;
@ -240,7 +223,6 @@ const tlcss = css`
stroke-linecap: round;
stroke-linejoin: round;
}
.tl-fill-hitarea {
fill: transparent;
stroke: transparent;
@ -249,56 +231,45 @@ const tlcss = css`
stroke-linecap: round;
stroke-linejoin: round;
}
.tl-counter-scaled {
transform: scale(var(--tl-scale));
}
.tl-dashed {
stroke-dasharray: calc(2px * var(--tl-scale)), calc(2px * var(--tl-scale));
}
.tl-transparent {
fill: transparent;
stroke: transparent;
}
.tl-cursor-ns {
cursor: ns-resize;
}
.tl-cursor-ew {
cursor: ew-resize;
}
.tl-cursor-nesw {
cursor: nesw-resize;
}
.tl-cursor-nwse {
cursor: nwse-resize;
}
.tl-corner-handle {
stroke: var(--tl-selectStroke);
fill: var(--tl-background);
stroke-width: calc(1.5px * var(--tl-scale));
}
.tl-rotate-handle {
stroke: var(--tl-selectStroke);
fill: var(--tl-background);
stroke-width: calc(1.5px * var(--tl-scale));
cursor: grab;
}
.tl-binding {
fill: var(--tl-selectFill);
stroke: var(--tl-selectStroke);
stroke-width: calc(1px * var(--tl-scale));
pointer-events: none;
}
.tl-user {
left: calc(-15px * var(--tl-scale));
top: calc(-15px * var(--tl-scale));
@ -308,51 +279,40 @@ const tlcss = css`
pointer-events: none;
will-change: transform;
}
.tl-indicator {
fill: transparent;
stroke-width: calc(1.5px * var(--tl-scale));
pointer-events: none;
}
.tl-user-indicator-bounds {
border-style: solid;
border-width: calc(1px * var(--tl-scale));
}
.tl-hovered {
stroke: var(--tl-selectStroke);
}
.tl-selected {
stroke: var(--tl-selectStroke);
}
.tl-editing {
stroke-width: calc(2.5px * min(5, var(--tl-scale)));
}
.tl-performance {
will-change: transform, contents;
}
.tl-clone-target {
pointer-events: all;
}
.tl-clone-target:hover .tl-clone-button {
opacity: 1;
}
.tl-clone-button-target {
cursor: pointer;
pointer-events: all;
}
.tl-clone-button-target:hover .tl-clone-button {
fill: var(--tl-selectStroke);
}
.tl-clone-button {
opacity: 0;
r: calc(8px * var(--tl-scale));
@ -360,25 +320,21 @@ const tlcss = css`
stroke: var(--tl-selectStroke);
fill: var(--tl-background);
}
.tl-bounds {
pointer-events: none;
contain: layout style size;
}
.tl-bounds-bg {
stroke: none;
fill: var(--tl-selectFill);
pointer-events: all;
contain: layout style size;
}
.tl-bounds-center {
fill: transparent;
stroke: var(--tl-selectStroke);
stroke-width: calc(1.5px * var(--tl-scale));
}
.tl-brush {
fill: var(--tl-brushFill);
stroke: var(--tl-brushStroke);
@ -386,78 +342,63 @@ const tlcss = css`
pointer-events: none;
contain: layout style size;
}
.tl-dashed-brush-line {
fill: none;
stroke: var(--tl-brushDashStroke);
stroke-width: calc(1px * var(--tl-scale));
pointer-events: none;
}
.tl-brush.dashed {
stroke: none;
}
.tl-dot {
fill: var(--tl-background);
stroke: var(--tl-foreground);
stroke-width: 2px;
}
.tl-handle {
pointer-events: all;
cursor: grab;
}
.tl-handle:hover .tl-handle-bg {
fill: var(--tl-selectFill);
}
.tl-handle:hover .tl-handle-bg > * {
stroke: var(--tl-selectFill);
}
.tl-handle:active .tl-handle-bg {
cursor: grabbing;
fill: var(--tl-selectFill);
}
.tl-handle:active .tl-handle-bg > * {
stroke: var(--tl-selectFill);
}
.tl-handle {
fill: var(--tl-background);
stroke: var(--tl-selectStroke);
stroke-width: 1.5px;
}
.tl-handle-bg {
fill: transparent;
stroke: none;
pointer-events: all;
r: calc(16px / max(1, var(--tl-zoom)));
}
.tl-binding-indicator {
fill: transparent;
stroke: var(--tl-binding);
}
.tl-centered-g {
transform: translate(var(--tl-padding), var(--tl-padding));
}
.tl-current-parent > *[data-shy='true'] {
opacity: 1;
}
.tl-binding {
fill: none;
stroke: var(--tl-selectStroke);
stroke-width: calc(2px * var(--tl-scale));
}
.tl-grid-dot {
fill: var(--tl-grid);
}
@ -474,5 +415,5 @@ export function useTLTheme(theme?: Partial<TLTheme>, selector?: string) {
useTheme('tl', tltheme, selector)
useStyle('tl-canvas', tlcss)
useStyle('tl-canvas', TLCSS)
}

View file

@ -41,18 +41,20 @@
"react-dom": "^17.0"
},
"dependencies": {
"@radix-ui/react-alert-dialog": "^0.1.5",
"@radix-ui/react-checkbox": "^0.1.4",
"@radix-ui/react-context-menu": "^0.1.4",
"@radix-ui/react-dropdown-menu": "^0.1.4",
"@radix-ui/react-icons": "^1.0.3",
"@radix-ui/react-radio-group": "^0.1.4",
"@radix-ui/react-tooltip": "^0.1.6",
"@stitches/react": "^1.2.6",
"@radix-ui/react-alert-dialog": "^0.1.7",
"@radix-ui/react-checkbox": "^0.1.5",
"@radix-ui/react-context-menu": "^0.1.6",
"@radix-ui/react-dropdown-menu": "^0.1.6",
"@radix-ui/react-icons": "^1.1.1",
"@radix-ui/react-radio-group": "^0.1.5",
"@radix-ui/react-tooltip": "^0.1.7",
"@stitches/react": "^1.2.8",
"@tldraw/core": "^1.9.1",
"@tldraw/intersect": "^1.7.1",
"@tldraw/vec": "^1.7.0",
"@types/lz-string": "^1.3.34",
"idb-keyval": "^6.1.0",
"lz-string": "^1.4.4",
"perfect-freehand": "^1.0.16",
"react-hotkey-hook": "^1.0.2",
"react-hotkeys-hook": "^3.4.4",
@ -60,12 +62,12 @@
"zustand": "^3.6.9"
},
"devDependencies": {
"@tldraw/core": "*",
"@tldraw/intersect": "*",
"@tldraw/vec": "*",
"@swc-node/jest": "^1.4.3",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.2",
"@tldraw/core": "*",
"@tldraw/intersect": "*",
"@tldraw/vec": "*",
"@types/node": "^17.0.14",
"@types/react": "^17.0.38",
"@typescript-eslint/eslint-plugin": "^5.10.2",
@ -74,7 +76,8 @@
"lask": "^0.0.29",
"mobx": "^6.3.8",
"react": ">=16.8",
"react-dom": "^16.8 || ^17.0"
"react-dom": "^16.8 || ^17.0",
"typescript": "^4.6.4"
},
"jest": {
"setupFilesAfterEnv": [

View file

@ -2,7 +2,7 @@ import * as React from 'react'
import { styled } from '~styles'
import * as RadixContextMenu from '@radix-ui/react-context-menu'
import { useTldrawApp } from '~hooks'
import { TDSnapshot, AlignType, DistributeType, StretchType, TDExportTypes } from '~types'
import { TDSnapshot, AlignType, DistributeType, StretchType, TDExportType } from '~types'
import {
AlignBottomIcon,
AlignCenterHorizontallyIcon,
@ -102,10 +102,6 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
app.delete()
}, [app])
const handleCopyJson = React.useCallback(() => {
app.copyJson()
}, [app])
const handleCut = React.useCallback(() => {
app.cut()
}, [app])
@ -118,8 +114,12 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
app.paste()
}, [app])
const handleCopySvg = React.useCallback(() => {
app.copySvg()
const handleCopySVG = React.useCallback(() => {
app.copyImage(TDExportType.SVG, { scale: 1, quality: 1, transparentBackground: false })
}, [app])
const handleCopyPNG = React.useCallback(() => {
app.copyImage(TDExportType.PNG, { scale: 2, quality: 1, transparentBackground: true })
}, [app])
const handleUndo = React.useCallback(() => {
@ -131,23 +131,27 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
}, [app])
const handleExportPNG = React.useCallback(async () => {
await app.exportSelectedShapesAs(TDExportTypes.PNG)
app.exportImage(TDExportType.PNG, { scale: 2, quality: 1, transparentBackground: true })
}, [app])
const handleExportJPG = React.useCallback(async () => {
await app.exportSelectedShapesAs(TDExportTypes.JPG)
app.exportImage(TDExportType.JPG, { scale: 2, quality: 1, transparentBackground: false })
}, [app])
const handleExportWEBP = React.useCallback(async () => {
await app.exportSelectedShapesAs(TDExportTypes.WEBP)
app.exportImage(TDExportType.WEBP, { scale: 2, quality: 1, transparentBackground: false })
}, [app])
const handleExportSVG = React.useCallback(async () => {
await app.exportSelectedShapesAs(TDExportTypes.SVG)
app.exportImage(TDExportType.SVG, { scale: 1, quality: 1, transparentBackground: false })
}, [app])
const handleCopyJSON = React.useCallback(async () => {
app.copyJson()
}, [app])
const handleExportJSON = React.useCallback(async () => {
await app.exportSelectedShapesAs(TDExportTypes.JSON)
app.exportJson()
}, [app])
const hasSelection = numberOfSelectedIds > 0
@ -212,57 +216,6 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
{hasTwoOrMore && (
<AlignDistributeSubMenu hasTwoOrMore={hasTwoOrMore} hasThreeOrMore={hasThreeOrMore} />
)}
{app.callbacks.onExport ? (
<>
<Divider />
<ContextMenuSubMenu label="Export" size="small" id="TD-ContextMenu-Export">
<CMRowButton onClick={handleExportPNG} id="TD-ContextMenu-Export-PNG">
PNG
</CMRowButton>
<CMRowButton onClick={handleExportJPG} id="TD-ContextMenu-Export-JPG">
JPG
</CMRowButton>
<CMRowButton onClick={handleExportWEBP} id="TD-ContextMenu-Export-WEBP">
WEBP
</CMRowButton>
<CMRowButton onClick={handleExportSVG} id="TD-ContextMenu-Export-SVG">
SVG
</CMRowButton>
<CMRowButton onClick={handleExportJSON} id="TD-ContextMenu-Export-JSON">
JSON
</CMRowButton>
<Divider />
<CMRowButton
onClick={handleCopySvg}
kbd="#⇧C"
id="TD-ContextMenu-Export-Copy_as_SVG"
>
Copy as SVG
</CMRowButton>
{isDebugMode && (
<CMRowButton onClick={handleCopyJson} id="TD-ContextMenu-Export-Copy_as_JSON">
Copy as JSON
</CMRowButton>
)}
</ContextMenuSubMenu>
</>
) : (
<>
<Divider />
<CMRowButton
onClick={handleCopySvg}
kbd="#⇧C"
id="TD-ContextMenu-Export-Copy_as_SVG"
>
Copy as SVG
</CMRowButton>
{isDebugMode && (
<CMRowButton onClick={handleCopyJson} id="TD-ContextMenu-Export-Copy_as_JSON">
Copy as JSON
</CMRowButton>
)}
</>
)}
<Divider />
<CMRowButton onClick={handleCut} kbd="#X" id="TD-ContextMenu-Cut">
Cut
@ -273,7 +226,39 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
Paste
</CMRowButton>
<Divider />
<ContextMenuSubMenu label="Copy as..." size="small" id="TD-ContextMenu-Copy-As">
<CMRowButton onClick={handleCopySVG} id="TD-ContextMenu-Copy-as-SVG">
SVG
</CMRowButton>
<CMRowButton onClick={handleCopyPNG} id="TD-ContextMenu-Copy-As-PNG">
PNG
</CMRowButton>
{isDebugMode && (
<CMRowButton onClick={handleCopyJSON} id="TD-ContextMenu-Copy_as_JSON">
JSON
</CMRowButton>
)}
</ContextMenuSubMenu>
<ContextMenuSubMenu label="Export as..." size="small" id="TD-ContextMenu-Export">
<CMRowButton onClick={handleExportSVG} id="TD-ContextMenu-Export-SVG">
SVG
</CMRowButton>
<CMRowButton onClick={handleExportPNG} id="TD-ContextMenu-Export-PNG">
PNG
</CMRowButton>
<CMRowButton onClick={handleExportJPG} id="TD-ContextMenu-Export-JPG">
JPG
</CMRowButton>
<CMRowButton onClick={handleExportWEBP} id="TD-ContextMenu-Export-WEBP">
WEBP
</CMRowButton>
{isDebugMode && (
<CMRowButton onClick={handleExportJSON} id="TD-ContextMenu-Export-JSON">
JSON
</CMRowButton>
)}
</ContextMenuSubMenu>
<Divider />
<CMRowButton onClick={handleDelete} kbd="⌫" id="TD-ContextMenu-Delete">
Delete

View file

@ -15,7 +15,7 @@ import { useFileSystemHandlers } from '~hooks'
import { HeartIcon } from '~components/Primitives/icons/HeartIcon'
import { preventEvent } from '~components/preventEvent'
import { DiscordIcon } from '~components/Primitives/icons'
import { TDExportTypes, TDSnapshot } from '~types'
import { TDExportType, TDSnapshot } from '~types'
import { Divider } from '~components/Primitives/Divider'
interface MenuProps {
@ -44,28 +44,40 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
const { onNewProject, onOpenProject, onSaveProject, onSaveProjectAs } = useFileSystemHandlers()
const handleDelete = React.useCallback(() => {
app.delete()
}, [app])
const handleCopySVG = React.useCallback(() => {
app.copyImage(TDExportType.SVG, { scale: 1, quality: 1, transparentBackground: false })
}, [app])
const handleCopyPNG = React.useCallback(() => {
app.copyImage(TDExportType.PNG, { scale: 2, quality: 1, transparentBackground: true })
}, [app])
const handleExportPNG = React.useCallback(async () => {
await app.exportAllShapesAs(TDExportTypes.PNG)
app.exportImage(TDExportType.PNG, { scale: 2, quality: 1, transparentBackground: true })
}, [app])
const handleExportJPG = React.useCallback(async () => {
await app.exportAllShapesAs(TDExportTypes.JPG)
app.exportImage(TDExportType.JPG, { scale: 2, quality: 1, transparentBackground: false })
}, [app])
const handleExportWEBP = React.useCallback(async () => {
await app.exportAllShapesAs(TDExportTypes.WEBP)
}, [app])
const handleExportPDF = React.useCallback(async () => {
await app.exportAllShapesAs(TDExportTypes.PDF)
app.exportImage(TDExportType.WEBP, { scale: 2, quality: 1, transparentBackground: false })
}, [app])
const handleExportSVG = React.useCallback(async () => {
await app.exportAllShapesAs(TDExportTypes.SVG)
app.exportImage(TDExportType.SVG, { scale: 2, quality: 1, transparentBackground: false })
}, [app])
const handleCopyJSON = React.useCallback(async () => {
app.copyJson()
}, [app])
const handleExportJSON = React.useCallback(async () => {
await app.exportAllShapesAs(TDExportTypes.JSON)
app.exportJson()
}, [app])
const handleSignIn = React.useCallback(() => {
@ -88,14 +100,6 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
app.paste()
}, [app])
const handleCopySvg = React.useCallback(() => {
app.copySvg()
}, [app])
const handleCopyJson = React.useCallback(() => {
app.copyJson()
}, [app])
const handleSelectAll = React.useCallback(() => {
app.selectAll()
}, [app])
@ -151,28 +155,6 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
Save As...
</DMItem>
)}
{app.callbacks.onExport && (
<>
<Divider />
<DMSubMenu label="Export" size="small" id="TD-MenuItem-File-Export">
<DMItem onClick={handleExportPNG} id="TD-MenuItem-File-Export-PNG">
PNG
</DMItem>
<DMItem onClick={handleExportJPG} id="TD-MenuItem-File-Export-JPG">
JPG
</DMItem>
<DMItem onClick={handleExportWEBP} id="TD-MenuItem-File-Export-WEBP">
WEBP
</DMItem>
<DMItem onClick={handleExportSVG} id="TD-MenuItem-File-Export-SVG">
SVG
</DMItem>
<DMItem onClick={handleExportJSON} id="TD-MenuItem-File-Export-JSON">
JSON
</DMItem>
</DMSubMenu>
</>
)}
{!disableAssets && (
<>
<Divider />
@ -183,89 +165,104 @@ export const Menu = React.memo(function Menu({ showSponsorLink, readOnly }: Menu
)}
</DMSubMenu>
)}
{!readOnly && (
<>
<DMSubMenu label="Edit..." id="TD-MenuItem-Edit">
<DMItem
onSelect={preventEvent}
onClick={app.undo}
kbd="#Z"
id="TD-MenuItem-Edit-Undo"
>
Undo
</DMItem>
<DMItem
onSelect={preventEvent}
onClick={app.redo}
kbd="#⇧Z"
id="TD-MenuItem-Edit-Redo"
>
Redo
</DMItem>
<DMDivider dir="ltr" />
<DMItem
onSelect={preventEvent}
disabled={!hasSelection}
onClick={handleCut}
kbd="#X"
id="TD-MenuItem-Edit-Cut"
>
Cut
</DMItem>
<DMItem
onSelect={preventEvent}
disabled={!hasSelection}
onClick={handleCopy}
kbd="#C"
id="TD-MenuItem-Edit-Copy"
>
Copy
</DMItem>
<DMItem
onSelect={preventEvent}
onClick={handlePaste}
kbd="#V"
id="TD-MenuItem-Edit-Paste"
>
Paste
</DMItem>
<DMDivider dir="ltr" />
<DMItem
onSelect={preventEvent}
disabled={!hasSelection}
onClick={handleCopySvg}
kbd="#⇧C"
id="TD-MenuItem-Edit-Copy_as_SVG"
>
Copy as SVG
</DMItem>
<DMItem
onSelect={preventEvent}
disabled={!hasSelection}
onClick={handleCopyJson}
id="TD-MenuItem-Edit-Copy_as_JSON"
>
Copy as JSON
</DMItem>
<DMDivider dir="ltr" />
<DMItem
onSelect={preventEvent}
onClick={handleSelectAll}
kbd="#A"
id="TD-MenuItem-Select_All"
>
Select All
</DMItem>
<DMItem
onSelect={preventEvent}
onClick={handleSelectNone}
id="TD-MenuItem-Select_None"
>
Select None
</DMItem>
</DMSubMenu>
</>
)}
<DMSubMenu label="Edit..." id="TD-MenuItem-Edit">
<DMItem
onSelect={preventEvent}
onClick={app.undo}
disabled={readOnly}
kbd="#Z"
id="TD-MenuItem-Edit-Undo"
>
Undo
</DMItem>
<DMItem
onSelect={preventEvent}
onClick={app.redo}
disabled={readOnly}
kbd="#⇧Z"
id="TD-MenuItem-Edit-Redo"
>
Redo
</DMItem>
<DMDivider dir="ltr" />
<DMItem
onSelect={preventEvent}
disabled={!hasSelection || readOnly}
onClick={handleCut}
kbd="#X"
id="TD-MenuItem-Edit-Cut"
>
Cut
</DMItem>
<DMItem
onSelect={preventEvent}
disabled={!hasSelection}
onClick={handleCopy}
kbd="#C"
id="TD-MenuItem-Edit-Copy"
>
Copy
</DMItem>
<DMItem
onSelect={preventEvent}
onClick={handlePaste}
kbd="#V"
id="TD-MenuItem-Edit-Paste"
>
Paste
</DMItem>
<DMDivider dir="ltr" />
<DMSubMenu label="Copy as..." size="small" id="TD-MenuItem-Copy-As">
<DMItem onClick={handleCopySVG} id="TD-MenuItem-Copy-as-SVG">
SVG
</DMItem>
<DMItem onClick={handleCopyPNG} id="TD-MenuItem-Copy-As-PNG">
PNG
</DMItem>
<DMItem onClick={handleCopyJSON} id="TD-MenuItem-Copy_as_JSON">
JSON
</DMItem>
</DMSubMenu>
<DMSubMenu label="Export as..." size="small" id="TD-MenuItem-Export">
<DMItem onClick={handleExportSVG} id="TD-MenuItem-Export-SVG">
SVG
</DMItem>
<DMItem onClick={handleExportPNG} id="TD-MenuItem-Export-PNG">
PNG
</DMItem>
<DMItem onClick={handleExportJPG} id="TD-MenuItem-Export-JPG">
JPG
</DMItem>
<DMItem onClick={handleExportWEBP} id="TD-MenuItem-Export-WEBP">
WEBP
</DMItem>
<DMItem onClick={handleExportJSON} id="TD-MenuItem-Export-JSON">
JSON
</DMItem>
</DMSubMenu>
<DMDivider dir="ltr" />
<DMItem
onSelect={preventEvent}
onClick={handleSelectAll}
kbd="#A"
id="TD-MenuItem-Select_All"
>
Select All
</DMItem>
<DMItem
onSelect={preventEvent}
disabled={!hasSelection}
onClick={handleSelectNone}
id="TD-MenuItem-Select_None"
>
Select None
</DMItem>
<DMDivider dir="ltr" />
<DMItem onSelect={handleDelete} disabled={!hasSelection} kbd="⌫" id="TD-MenuItem-Delete">
Delete
</DMItem>
</DMSubMenu>
<DMSubMenu label="View" id="TD-MenuItem-Edit">
<DMItem
onSelect={preventEvent}

View file

@ -17,21 +17,37 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
React.useEffect(() => {
if (!app) return
const handlePaste = (e: ClipboardEvent) => {
let items = e.clipboardData?.items ?? []
for (var index in items) {
var item = items[index]
if (item.kind === 'file') {
var file = item.getAsFile()
if (file) {
app.addMediaFromFile(file)
}
}
const handleCut = (e: ClipboardEvent) => {
if (!canHandleEvent(true)) return
if (app.readOnly) {
app.copy(undefined, undefined, e)
return
}
app.cut(undefined, undefined, e)
}
const handleCopy = (e: ClipboardEvent) => {
if (!canHandleEvent(true)) return
app.copy(undefined, undefined, e)
}
const handlePaste = (e: ClipboardEvent) => {
if (!canHandleEvent(true)) return
if (app.readOnly) return
app.paste(undefined, e)
}
document.addEventListener('cut', handleCut)
document.addEventListener('copy', handleCopy)
document.addEventListener('paste', handlePaste)
return () => {
document.removeEventListener('cut', handleCut)
document.removeEventListener('copy', handleCopy)
document.removeEventListener('paste', handlePaste)
}
}, [app])
@ -508,15 +524,15 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
// Copy, Cut & Paste
useHotkeys(
'⌘+c,ctrl+c',
() => {
if (!canHandleEvent()) return
app.copy()
},
undefined,
[app]
)
// useHotkeys(
// '⌘+c,ctrl+c',
// () => {
// if (!canHandleEvent()) return
// app.copy()
// },
// undefined,
// [app]
// )
useHotkeys(
'⌘+shift+c,ctrl+shift+c',
@ -530,26 +546,26 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
[app]
)
useHotkeys(
'⌘+x,ctrl+x',
() => {
if (!canHandleEvent()) return
app.cut()
},
undefined,
[app]
)
// useHotkeys(
// '⌘+x,ctrl+x',
// () => {
// if (!canHandleEvent()) return
// app.cut()
// },
// undefined,
// [app]
// )
useHotkeys(
'⌘+v,ctrl+v',
() => {
if (!canHandleEvent()) return
// useHotkeys(
// '⌘+v,ctrl+v',
// () => {
// if (!canHandleEvent()) return
app.paste()
},
undefined,
[app]
)
// app.paste()
// },
// undefined,
// [app]
// )
// Group & Ungroup

View file

@ -2,18 +2,16 @@ import * as React from 'react'
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 = `
@import url('');
`
const CSS = `@import url('${WEBFONT_URL}');`
export function useStylesheet() {
React.useLayoutEffect(() => {
if (styles.get(UID)) return
const style = document.createElement('style')
style.innerHTML = `@import url('${WEBFONT_URL}');`
style.innerHTML = CSS
style.setAttribute('id', UID)
document.head.appendChild(style)
styles.set(UID, style)
@ -24,5 +22,5 @@ export function useStylesheet() {
styles.delete(UID)
}
}
}, [UID, CSS])
}, [])
}

View file

@ -1,5 +1,6 @@
export * from './Tldraw'
export * from './types'
export * from './state/shapes'
export * from './state/TLDR'
export { TldrawApp } from './state'
export { useFileSystem } from './hooks'

View file

@ -11,6 +11,7 @@ import {
TDShapeType,
ArrowShape,
TDHandle,
TDExportType,
} from '~types'
import { Vec } from '@tldraw/vec'
import type { TDShapeUtil } from './shapes/TDShapeUtil'
@ -986,7 +987,13 @@ export class TLDR {
static copyStringToClipboard = (string: string) => {
try {
navigator.clipboard.writeText(string)
if (navigator.clipboard) {
navigator.clipboard.write([
new ClipboardItem({
'text/plain': new Blob([string], { type: 'text/plain' }),
}),
])
}
} catch (e) {
const textarea = document.createElement('textarea')
textarea.setAttribute('position', 'fixed')
@ -1082,4 +1089,78 @@ export class TLDR {
console.error(e)
}
}
/* -------------------------------------------------- */
/* Export */
/* -------------------------------------------------- */
static getSvgString(svg: SVGElement, scale = 1) {
const clone = svg.cloneNode(true) as SVGGraphicsElement
svg.setAttribute('width', +svg.getAttribute('width')! * scale + '')
svg.setAttribute('height', +svg.getAttribute('height')! * scale + '')
return new XMLSerializer()
.serializeToString(clone)
.replaceAll('&#10; ', '')
.replaceAll(/((\s|")[0-9]*\.[0-9]{2})([0-9]*)(\b|"|\))/g, '$1')
}
static getSvgAsDataUrl(svg: SVGElement, scale = 1) {
const svgString = TLDR.getSvgString(svg, scale)
const base64SVG = window.btoa(unescape(svgString))
return `data:image/svg+xml;base64,${base64SVG}`
}
static async getImageForSvg(
svg: SVGElement,
type: Exclude<TDExportType, TDExportType.JSON> = TDExportType.PNG,
opts = {} as Partial<{
scale: number
quality: number
}>
) {
const { scale = 2, quality = 1 } = opts
const svgString = TLDR.getSvgString(svg, scale)
const width = +svg.getAttribute('width')!
const height = +svg.getAttribute('height')!
if (!svgString) return
const canvas = await new Promise<HTMLCanvasElement>((resolve) => {
const image = new Image()
image.crossOrigin = 'anonymous'
const base64SVG = window.btoa(unescape(svgString))
const dataUrl = `data:image/svg+xml;base64,${base64SVG}`
image.onload = () => {
const canvas = document.createElement('canvas') as HTMLCanvasElement
const context = canvas.getContext('2d')!
canvas.width = width
canvas.height = height
context.drawImage(image, 0, 0, width, height)
URL.revokeObjectURL(dataUrl)
resolve(canvas)
}
image.src = dataUrl
})
const blob = await new Promise<Blob>((resolve) =>
canvas.toBlob((blob) => resolve(blob!), 'image/' + type, quality)
)
return blob
}
}

View file

@ -569,8 +569,8 @@ describe('TldrawTestApp', () => {
})
describe('When copying to SVG', () => {
it('Copies shapes.', () => {
const result = new TldrawTestApp()
it('Copies shapes.', async () => {
const result = await new TldrawTestApp()
.loadDocument(mockDocument)
.select('rect1')
.rotate(0.1)
@ -579,8 +579,8 @@ describe('TldrawTestApp', () => {
expect(result).toMatchSnapshot('copied svg')
})
it('Copies grouped shapes.', () => {
const result = new TldrawTestApp()
it('Copies grouped shapes.', async () => {
const result = await new TldrawTestApp()
.loadDocument(mockDocument)
.select('rect1', 'rect2')
.group()
@ -590,8 +590,8 @@ describe('TldrawTestApp', () => {
expect(result).toMatchSnapshot('copied svg with group')
})
it('Respects child index', () => {
const result = new TldrawTestApp()
it('Respects child index', async () => {
const result = await new TldrawTestApp()
.loadDocument(mockDocument)
.moveToBack(['rect2'])
.selectAll()
@ -600,10 +600,10 @@ describe('TldrawTestApp', () => {
expect(result).toMatchSnapshot('copied svg with reordered elements')
})
it('Copies Text shapes as <text> elements.', () => {
it('Copies Text shapes as <text> elements.', async () => {
const state2 = new TldrawTestApp()
const svgString = state2
const svgString = await state2
.createShapes({
id: 'text1',
type: TDShapeType.Text,

File diff suppressed because it is too large Load diff

View file

@ -227,8 +227,8 @@ Array [
]
`;
exports[`TldrawTestApp When copying to SVG Copies grouped shapes.: copied svg with group 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 232 232\\" width=\\"200\\" height=\\"200\\" fill=\\"transparent\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&amp;family=Source+Code+Pro&amp;family=Source+Sans+Pro&amp;family=Crimson+Pro&amp;display=block');</style></defs><g/></svg>"`;
exports[`TldrawTestApp When copying to SVG Copies grouped shapes.: copied svg with group 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 232 232\\" width=\\"200\\" height=\\"200\\" style=\\"background-color: rgb(255, 255, 255);\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&amp;family=Source+Code+Pro&amp;family=Source+Sans+Pro&amp;family=Crimson+Pro&amp;display=block');</style></defs><g/></svg>"`;
exports[`TldrawTestApp When copying to SVG Copies shapes.: copied svg 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 236.74 236.74\\" width=\\"204.74\\" height=\\"204.74\\" fill=\\"transparent\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&amp;family=Source+Code+Pro&amp;family=Source+Sans+Pro&amp;family=Crimson+Pro&amp;display=block');</style></defs></svg>"`;
exports[`TldrawTestApp When copying to SVG Copies shapes.: copied svg 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 236.74 236.74\\" width=\\"204.74\\" height=\\"204.74\\" style=\\"background-color: rgb(255, 255, 255);\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&amp;family=Source+Code+Pro&amp;family=Source+Sans+Pro&amp;family=Crimson+Pro&amp;display=block');</style></defs></svg>"`;
exports[`TldrawTestApp When copying to SVG Respects child index: copied svg with reordered elements 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 232 232\\" width=\\"200\\" height=\\"200\\" fill=\\"transparent\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&amp;family=Source+Code+Pro&amp;family=Source+Sans+Pro&amp;family=Crimson+Pro&amp;display=block');</style></defs></svg>"`;
exports[`TldrawTestApp When copying to SVG Respects child index: copied svg with reordered elements 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 232 232\\" width=\\"200\\" height=\\"200\\" style=\\"background-color: rgb(255, 255, 255);\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&amp;family=Source+Code+Pro&amp;family=Source+Sans+Pro&amp;family=Crimson+Pro&amp;display=block');</style></defs></svg>"`;

View file

@ -180,7 +180,7 @@ export abstract class TDShapeUtil<T extends TDShape, E extends Element = any> ex
getSvgElement = (shape: T): SVGElement | void => {
const elm = document.getElementById(shape.id + '_svg')?.cloneNode(true) as SVGElement
if (!elm) return // possibly in test mode
if ('label' in shape && (shape as any).label !== undefined) {
if ('label' in shape && (shape as any).label) {
const s = shape as TDShape & { label: string }
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
const bounds = this.getBounds(shape)
@ -191,8 +191,10 @@ export abstract class TDShapeUtil<T extends TDShape, E extends Element = any> ex
labelElm.setAttribute('transform-origin', 'top left')
labelElm.setAttribute(
'transform',
`translate(${(bounds.width - size[0]) / 2}, ${(bounds.height - size[1]) / 2})`
`translate(${bounds.width / 2}, ${(bounds.height - size[1]) / 2})`
)
g.setAttribute('text-align', 'center')
g.setAttribute('text-anchor', 'middle')
g.appendChild(elm)
g.appendChild(labelElm)
return g

View file

@ -7,21 +7,27 @@ import { LINE_HEIGHT } from '~constants'
export function getTextSvgElement(text: string, style: ShapeStyles, bounds: TLBounds) {
const fontSize = getFontSize(style.size, style.font)
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
const textLines = text.split('\n').map((line, i) => {
const textElm = document.createElementNS('http://www.w3.org/2000/svg', 'text')
textElm.textContent = line
textElm.setAttribute('y', LINE_HEIGHT * fontSize * (0.5 + i) + '')
textElm.setAttribute('letter-spacing', '-0.03px')
textElm.setAttribute('font-size', fontSize + 'px')
textElm.setAttribute('font-family', getFontFace(style.font).slice(1, -1))
textElm.setAttribute('text-align', getTextAlign(style.textAlign))
g.appendChild(textElm)
return textElm
})
g.setAttribute('font-size', fontSize + '')
g.setAttribute('font-family', getFontFace(style.font).slice(1, -1))
g.setAttribute('text-align', getTextAlign(style.textAlign))
switch (style.textAlign) {
case AlignStyle.Middle: {
g.setAttribute('text-align', 'center')
g.setAttribute('text-anchor', 'middle')
textLines.forEach((textElm) => textElm.setAttribute('x', bounds.width / 2 + ''))
textLines.forEach((textElm) => {
textElm.setAttribute('x', bounds.width / 2 + '')
})
break
}
case AlignStyle.End: {
@ -35,5 +41,6 @@ export function getTextSvgElement(text: string, style: ShapeStyles, bounds: TLBo
g.setAttribute('alignment-baseline', 'central')
}
}
return g
}

View file

@ -491,23 +491,18 @@ export type TDAssets = Record<string, TDAsset>
/* Export */
/* -------------------------------------------------- */
export enum TDExportTypes {
export enum TDExportType {
PNG = 'png',
JPG = 'jpeg',
WEBP = 'webp',
PDF = 'pdf',
SVG = 'svg',
JSON = 'json',
}
export interface TDExport {
currentPageId: string
name: string
shapes: TDShape[]
assets: TDAssets
type: TDExportTypes
size: number[]
serialized?: string
type: string
blob: Blob
}
/* -------------------------------------------------- */

File diff suppressed because one or more lines are too long

View file

@ -3,6 +3,7 @@
"exclude": ["node_modules", "dist", "docs"],
"include": ["src"],
"compilerOptions": {
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "src",
"baseUrl": ".",

398
yarn.lock
View file

@ -2090,52 +2090,52 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-alert-dialog@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-0.1.5.tgz#54968d9c5a7e65e2be77642ad3dead9e307f1d98"
integrity sha512-Lq9h3GSvw752e7dFll3UWvm4uWiTlYAXLFX6wr/VQPRoa7XaQO8/1NBu4ikLHAecGEd/uDGZLY3aP7ovGPQYtg==
"@radix-ui/react-alert-dialog@^0.1.7":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-0.1.7.tgz#2b9379d848323f5370a8b3928dd6e5ce95f8bd34"
integrity sha512-b0+TWr0VRWMWM7QcXvvcwbMGNzpTmvPBSBpYcoaD+QnVo3jdJt0k0bghwbYBuywzdyuRNUFf33xwah/57w09QA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-dialog" "0.1.5"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-dialog" "0.1.7"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-slot" "0.1.2"
"@radix-ui/react-arrow@0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.1.3.tgz#17f86eab216c48aff17b13b811569a9bbabaa44d"
integrity sha512-9x1gRYdlUD5OUwY7L+M+4FY/YltDSsrNSj8QXGPbxZxL5ghWXB/4lhyIGccCwk/e8ggfmQYv9SRNmn3LavPo3A==
"@radix-ui/react-arrow@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.1.4.tgz#a871448a418cd3507d83840fdd47558cb961672b"
integrity sha512-BB6XzAb7Ml7+wwpFdYVtZpK1BlMgqyafSQNGzhIpSZ4uXvXOHPlR5GP8M449JkeQzgQjv9Mp1AsJxFC0KuOtuA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-checkbox@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-0.1.4.tgz#170957725b0b64b9532621ec14e52c2d3e7a2bb6"
integrity sha512-UtiV0y4CNmcAdCqRaGGPxeET/asO44rfKxtBbMIxAD4BGPfSIe4+kkF0q624S5c7q07HXO0vhOYlSObR3Fj2bg==
"@radix-ui/react-checkbox@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-0.1.5.tgz#3a6bd54ba1720c8e5c03852acf460e35dfbe9da3"
integrity sha512-M8Y4dSXsKSbF+FryG5VvZKr/1MukMVG7swq9p5s7wYb8Rvn0UM0rQ5w8BWmSWSV4BL/gbJdhwVCznwXXlgZRZg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-label" "0.1.4"
"@radix-ui/react-presence" "0.1.1"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-label" "0.1.5"
"@radix-ui/react-presence" "0.1.2"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-use-previous" "0.1.0"
"@radix-ui/react-use-size" "0.1.0"
"@radix-ui/react-use-previous" "0.1.1"
"@radix-ui/react-use-size" "0.1.1"
"@radix-ui/react-collection@0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.1.3.tgz#7b584f5db40ce165883b87c383d3bd16c0000d69"
integrity sha512-tMBY65l87tj77fMX44EBjm5p8clR6swkcNFr0/dDVdEPC0Vf3fwkv62dezCnZyrRBpkOgZPDOp2kO73hYlCfXw==
"@radix-ui/react-collection@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.1.4.tgz#734061ffd5bb93e88889d49b87391a73a63824c9"
integrity sha512-3muGI15IdgaDFjOcO7xX8a35HQRBRF6LH9pS6UCeZeRmbslkVeHyJRQr2rzICBUoX7zgIA0kXyMDbpQnJGyJTA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-slot" "0.1.2"
"@radix-ui/react-compose-refs@0.1.0":
@ -2145,16 +2145,16 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-context-menu@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-0.1.4.tgz#d4706fe029f74b3330c6965601ed6a3f9165fb2c"
integrity sha512-GWwzAZDGT66b5HQAD4nfhSTih3pxhbEnNfVBerKYSL/74VWHZY6xpXf6peeb4m1zorVvYPMoT45KTO6hAJwTFg==
"@radix-ui/react-context-menu@^0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-0.1.6.tgz#0c75f2faffec6c8697247a4b685a432b3c4d07f0"
integrity sha512-0qa6ABaeqD+WYI+8iT0jH0QLLcV8Kv0xI+mZL4FFnG4ec9H0v+yngb5cfBBfs9e/KM8mDzFFpaeegqsQlLNqyQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-menu" "0.1.4"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-menu" "0.1.6"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-context@0.1.1":
@ -2164,52 +2164,52 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-dialog@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.1.5.tgz#4310659607f5ad0b8796623d5f7490dc47d3d295"
integrity sha512-WftvXcQSszUphCTLQkkpBIkrYYU0IYqgIvACLQady4BN4YHDgdNlrwdg2ti9QrXgq1PZ+0S/6BIaA1dmSuRQ2g==
"@radix-ui/react-dialog@0.1.7":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.1.7.tgz#285414cf66f5bbf42bc9935314e0381abe01e7d0"
integrity sha512-jXt8srGhHBRvEr9jhEAiwwJzWCWZoGRJ030aC9ja/gkRJbZdy0iD3FwXf+Ff4RtsZyLUMHW7VUwFOlz3Ixe1Vw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-dismissable-layer" "0.1.3"
"@radix-ui/react-dismissable-layer" "0.1.5"
"@radix-ui/react-focus-guards" "0.1.0"
"@radix-ui/react-focus-scope" "0.1.3"
"@radix-ui/react-id" "0.1.4"
"@radix-ui/react-portal" "0.1.3"
"@radix-ui/react-presence" "0.1.1"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-focus-scope" "0.1.4"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-portal" "0.1.4"
"@radix-ui/react-presence" "0.1.2"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-slot" "0.1.2"
"@radix-ui/react-use-controllable-state" "0.1.0"
aria-hidden "^1.1.1"
react-remove-scroll "^2.4.0"
"@radix-ui/react-dismissable-layer@0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.1.3.tgz#d427c7520c3799d2b957e40e7d67045d96120356"
integrity sha512-3veE7M8K13Qb+6+tC3DHWmWV9VMuuRoZvRLdrvz7biSraK/qkGBN4LbKZDaTdw2D2HS7RNpSd/sF8pFd3TaAgA==
"@radix-ui/react-dismissable-layer@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.1.5.tgz#9379032351e79028d472733a5cc8ba4a0ea43314"
integrity sha512-J+fYWijkX4M4QKwf9dtu1oC0U6e6CEl8WhBp3Ad23yz2Hia0XCo6Pk/mp5CAFy4QBtQedTSkhW05AdtSOEoajQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-use-body-pointer-events" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-body-pointer-events" "0.1.1"
"@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-escape-keydown" "0.1.0"
"@radix-ui/react-dropdown-menu@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-0.1.4.tgz#c5e7db72b4854ea8d8f9151f7053fa6873eb372f"
integrity sha512-KNFHOK+zKKqZ7x3OoxCXZ2TRESRmHpgxkXpY75i/GFt3i5N/RIH5rB9WSdwhdQXM7gkihYZIDwjdmhhSsgzHkw==
"@radix-ui/react-dropdown-menu@^0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-0.1.6.tgz#3203229788cd57e552c9f19dcc7008e2b545919c"
integrity sha512-RZhtzjWwJ4ZBN7D8ek4Zn+ilHzYuYta9yIxFnbC0pfqMnSi67IQNONo1tuuNqtFh9SRHacPKc65zo+kBBlxtdg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-id" "0.1.4"
"@radix-ui/react-menu" "0.1.4"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-menu" "0.1.6"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-focus-guards@0.1.0":
@ -2219,134 +2219,134 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-focus-scope@0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.1.3.tgz#b1cc825b6190001d731417ed90d192d13b41bce1"
integrity sha512-bKi+lw14SriQqYWMBe13b/wvxSqYMC+3FylMUEwOKA6JrBoldpkhX5XffGDdpDRTTpjbncdH3H7d1PL5Bs7Ikg==
"@radix-ui/react-focus-scope@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.1.4.tgz#c830724e212d42ffaaa81aee49533213d09b47df"
integrity sha512-fbA4ES3H4Wkxp+OeLhvN6SwL7mXNn/aBtUf7DRYxY9+Akrf7dRxl2ck4lgcpPsSg3zSDsEwLcY+h5cmj5yvlug==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-icons@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.0.3.tgz#4ef61f1234f44991f7a19e108f77ca37032b4be2"
integrity sha512-YbPAUZwTsvF/2H7IU35txaLUB+JNSV8GIhnswlqiFODP/P32t5op5keYUvQWsSj9TA0VLF367J24buUjIprn0w==
"@radix-ui/react-icons@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.1.1.tgz#38d2aa548035dd3b799c169bd17177b1cec3152b"
integrity sha512-xc3wQC59rsFylVbSusQCrrM+6695ppF730Q6yqzhRdqDcRNWIm2R6ngpzBoSOQMcwnq4p805F+Gr7xo4fmtN1A==
"@radix-ui/react-id@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.1.4.tgz#4cd6126e6ac8a43ebe6d52948a068b797cc9ad71"
integrity sha512-/hq5m/D0ZfJWOS7TLF+G0l08KDRs87LBE46JkAvgKkg1fW4jkucx9At9D9vauIPSbdNmww5kXEp566hMlA8eXA==
"@radix-ui/react-id@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.1.5.tgz#010d311bedd5a2884c1e9bb6aaaa4e6cc1d1d3b8"
integrity sha512-IPc4H/63bes0IZ1GJJozSEkSWcDyhNGtKFWUpJ+XtaLyQ1X3x7Mf6fWwWhDcpqlYEP+5WtAvfqcyEsyjP+ZhBQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-label@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.1.4.tgz#d62855ff6bb2950d6117462f8e00a0c5378651eb"
integrity sha512-I59IMdUhHixk6cG4D00UN+oFxTpur9cJQSOl+4EfSTJZs+x4PqDpM7p402/gb9sXJqylsUkDMHDdaPzLwPNf7g==
"@radix-ui/react-label@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.1.5.tgz#12cd965bfc983e0148121d4c99fb8e27a917c45c"
integrity sha512-Au9+n4/DhvjR0IHhvZ1LPdx/OW+3CGDie30ZyCkbSHIuLp4/CV4oPPGBwJ1vY99Jog3zyQhsGww9MXj8O9Aj/A==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-id" "0.1.4"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-menu@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-0.1.4.tgz#ed9aca2c2328038e2210e629228db6e3c67495c1"
integrity sha512-50HvBojjj2CrwIxcECRF9MdReoALRdpG7vtCAGbYs3eciIOLqtP6+Dx/sVz1YWe6Fsree/5vFQXZGImZYY/3TQ==
"@radix-ui/react-menu@0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-0.1.6.tgz#7f9521a10f6a9cd819b33b33d5ed9538d79b2e75"
integrity sha512-ho3+bhpr3oAFkOBJ8VkUb1BcGoiZBB3OmcWPqa6i5RTUKrzNX/d6rauochu2xDlWjiRtpVuiAcsTVOeIC4FbYQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-collection" "0.1.3"
"@radix-ui/react-collection" "0.1.4"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-dismissable-layer" "0.1.3"
"@radix-ui/react-dismissable-layer" "0.1.5"
"@radix-ui/react-focus-guards" "0.1.0"
"@radix-ui/react-focus-scope" "0.1.3"
"@radix-ui/react-id" "0.1.4"
"@radix-ui/react-popper" "0.1.3"
"@radix-ui/react-portal" "0.1.3"
"@radix-ui/react-presence" "0.1.1"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-roving-focus" "0.1.4"
"@radix-ui/react-focus-scope" "0.1.4"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-popper" "0.1.4"
"@radix-ui/react-portal" "0.1.4"
"@radix-ui/react-presence" "0.1.2"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-roving-focus" "0.1.5"
"@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-direction" "0.1.0"
aria-hidden "^1.1.1"
react-remove-scroll "^2.4.0"
"@radix-ui/react-popper@0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.1.3.tgz#a93bdd72845566007e5f3868caddd62318bb781e"
integrity sha512-2OV2YaJv7iTZexJY3HJ7B6Fs1A/3JXd3fRGU4JY0guACfGMD1C/jSgds505MKQOTiHE/quI6j3/q8yfzFjJR9g==
"@radix-ui/react-popper@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.1.4.tgz#dfc055dcd7dfae6a2eff7a70d333141d15a5d029"
integrity sha512-18gDYof97t8UQa7zwklG1Dr8jIdj3u+rVOQLzPi9f5i1YQak/pVGkaqw8aY+iDUknKKuZniTk/7jbAJUYlKyOw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/popper" "0.1.0"
"@radix-ui/react-arrow" "0.1.3"
"@radix-ui/react-arrow" "0.1.4"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-rect" "0.1.1"
"@radix-ui/react-use-size" "0.1.0"
"@radix-ui/react-use-size" "0.1.1"
"@radix-ui/rect" "0.1.1"
"@radix-ui/react-portal@0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.3.tgz#56826e789b3d4e37983f6d23666e3f1b1b9ee358"
integrity sha512-DrV+sPYLs0HhmX5/b7yRT6nLM9Nl6FtQe2KUG+46kiCOKQ+0XzNMO5hmeQtyq0mRf/qlC02rFu6OMsWpIqVsJg==
"@radix-ui/react-portal@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.4.tgz#17bdce3d7f1a9a0b35cb5e935ab8bc562441a7d2"
integrity sha512-MO0wRy2eYRTZ/CyOri9NANCAtAtq89DEtg90gicaTlkCfdqCLEBsLb+/q66BZQTr3xX/Vq01nnVfc/TkCqoqvw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-presence@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-0.1.1.tgz#2088dec6f4f8042f83dd2d6bf9e8ef09dadbbc15"
integrity sha512-LsL+NcWDpFUAYCmXeH02o4pgqcSLpwxP84UIjCtpIKrsPe2vLuhcp79KC/jZJeXz+of2lUpMAxpM+eCpxFZtlg==
"@radix-ui/react-presence@0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-0.1.2.tgz#9f11cce3df73cf65bc348e8b76d891f0d54c1fe3"
integrity sha512-3BRlFZraooIUfRlyN+b/Xs5hq1lanOOo/+3h6Pwu2GMFjkGKKa4Rd51fcqGqnVlbr3jYg+WLuGyAV4KlgqwrQw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-primitive@0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.1.3.tgz#585c35ef2ec06bab0ea9e0fc5c916e556661b881"
integrity sha512-fcyADaaAx2jdqEDLsTs6aX50S3L1c9K9CC6XMpJpuXFJCU4n9PGTFDZRtY2gAoXXoRCPIBsklCopSmGb6SsDjQ==
"@radix-ui/react-primitive@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.1.4.tgz#6c233cf08b0cb87fecd107e9efecb3f21861edc1"
integrity sha512-6gSl2IidySupIMJFjYnDIkIWRyQdbu/AHK7rbICPani+LW4b0XdxBXc46og/iZvuwW8pjCS8I2SadIerv84xYA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-slot" "0.1.2"
"@radix-ui/react-radio-group@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-0.1.4.tgz#a02eaa3c84ccba706b2b231f06c6dd5e1eb0ceae"
integrity sha512-ciFiJ/xX6Kn2+ylGlfbkfXUSBkV7FqX6Q3cHx/PH1h5FcCFdh7+FOIOem26AgI/i8NXhKIVJtlNaU6HUa6Sshg==
"@radix-ui/react-radio-group@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-0.1.5.tgz#ca8a676123a18b44804aff10af46129e2c2b37c3"
integrity sha512-ybgHsmh/V2crKvK6xZ56dpPul7b+vyxcq7obWqHbr5W6Ca11wdm0E7lS0i/Y6pgfIKYOWIARmZYDpRMEeRCPOw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-label" "0.1.4"
"@radix-ui/react-presence" "0.1.1"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-roving-focus" "0.1.4"
"@radix-ui/react-label" "0.1.5"
"@radix-ui/react-presence" "0.1.2"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-roving-focus" "0.1.5"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-use-previous" "0.1.0"
"@radix-ui/react-use-size" "0.1.0"
"@radix-ui/react-use-previous" "0.1.1"
"@radix-ui/react-use-size" "0.1.1"
"@radix-ui/react-roving-focus@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.1.4.tgz#693ca3eaab153feabe37f9d50987d6d2911cf819"
integrity sha512-zaixcAxRcWQliUSx6l9rdfJhvcbuY7Tb4Emb7H4DWCTx1kenXH8+n9mwa8gaSIJLLSSSMzBpQATlpFw9xv/bJQ==
"@radix-ui/react-roving-focus@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.1.5.tgz#cc48d17a36b56f253d54905b0fd60ee134cb97ee"
integrity sha512-ClwKPS5JZE+PaHCoW7eu1onvE61pDv4kO8W4t5Ra3qMFQiTJLZMdpBQUhksN//DaVygoLirz4Samdr5Y1x1FSA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-collection" "0.1.3"
"@radix-ui/react-collection" "0.1.4"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-id" "0.1.4"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-controllable-state" "0.1.0"
@ -2358,31 +2358,31 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-tooltip@^0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.1.6.tgz#46a3e385e004aaebd16ecaa1da7d1af70ba3bb45"
integrity sha512-0uaRpRmTCQo5yMUkDpv4LEDnaQDoeLXcNNhZonCZdbZBQ7ntvjURIWIigq1/pXZp0UX7oPpFzsXD9jUp8JT0WA==
"@radix-ui/react-tooltip@^0.1.7":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.1.7.tgz#6f8c00d6e489565d14abf209ce0fb8853c8c8ee3"
integrity sha512-eiBUsVOHenZ0JR16tl970bB0DafJBz6mFgSGfIGIVpflFj0LIsIDiLMsYyvYdx1KwwsIUDTEZtxcPm/sWjPzqA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-id" "0.1.4"
"@radix-ui/react-popper" "0.1.3"
"@radix-ui/react-portal" "0.1.3"
"@radix-ui/react-presence" "0.1.1"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-popper" "0.1.4"
"@radix-ui/react-portal" "0.1.4"
"@radix-ui/react-presence" "0.1.2"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-slot" "0.1.2"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-use-escape-keydown" "0.1.0"
"@radix-ui/react-use-previous" "0.1.0"
"@radix-ui/react-use-previous" "0.1.1"
"@radix-ui/react-use-rect" "0.1.1"
"@radix-ui/react-visually-hidden" "0.1.3"
"@radix-ui/react-visually-hidden" "0.1.4"
"@radix-ui/react-use-body-pointer-events@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.1.0.tgz#29b211464493f8ca5149ce34b96b95abbc97d741"
integrity sha512-svPyoHCcwOq/vpWNEvdH/yD91vN9p8BtiozNQbjVmJRxQ/vS12zqk70AxTGWe+2ZKHq2sggpEQNTv1JHyVFlnQ==
"@radix-ui/react-use-body-pointer-events@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.1.1.tgz#63e7fd81ca7ffd30841deb584cd2b7f460df2597"
integrity sha512-R8leV2AWmJokTmERM8cMXFHWSiv/fzOLhG/JLmRBhLTAzOj37EQizssq4oW0Z29VcZy2tODMi9Pk/htxwb+xpA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "0.1.0"
@ -2424,10 +2424,10 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-previous@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.1.0.tgz#fed880d41187d0fdd1e19c4588402765f342777e"
integrity sha512-0fxNc33rYnCzDMPSiSnfS8YklnxQo8WqbAQXPAgIaaA1jRu2qFB916PL4qCIW+avcAAqFD38vWhqDqcVmBharA==
"@radix-ui/react-use-previous@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.1.1.tgz#0226017f72267200f6e832a7103760e96a6db5d0"
integrity sha512-O/ZgrDBr11dR8rhO59ED8s5zIXBRFi8MiS+CmFGfi7MJYdLbfqVOmQU90Ghf87aifEgWe6380LA69KBneaShAg==
dependencies:
"@babel/runtime" "^7.13.10"
@ -2439,20 +2439,20 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/rect" "0.1.1"
"@radix-ui/react-use-size@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.1.0.tgz#dc49295d646f5d3f570943dbb88bd94fc7db7daf"
integrity sha512-TcZAsR+BYI46w/RbaSFCRACl+Jh6mDqhu6GS2r0iuJpIVrj8atff7qtTjmMmfGtEDNEjhl7DxN3pr1nTS/oruQ==
"@radix-ui/react-use-size@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.1.1.tgz#f6b75272a5d41c3089ca78c8a2e48e5f204ef90f"
integrity sha512-pTgWM5qKBu6C7kfKxrKPoBI2zZYZmp2cSXzpUiGM3qEBQlMLtYhaY2JXdXUCxz+XmD1YEjc8oRwvyfsD4AG4WA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-visually-hidden@0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.3.tgz#406a2f1e2f2cf27e5b85a29dc3aca718e695acaf"
integrity sha512-dPU6ZR2WQ/W9qv7E1Y8/I8ymqG+8sViU6dQQ6sfr2/8yGr0I4mmI7ywTnqXaE+YS9gHLEZHdQcEqTNESg6YfdQ==
"@radix-ui/react-visually-hidden@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.4.tgz#6c75eae34fb5d084b503506fbfc05587ced05f03"
integrity sha512-K/q6AEEzqeeEq/T0NPChvBqnwlp8Tl4NnQdrI/y8IOY7BRR+Ug0PEsVk6g48HJ7cA1//COugdxXXVVK/m0X1mA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "0.1.3"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/rect@0.1.1":
version "0.1.1"
@ -2678,6 +2678,11 @@
resolved "https://registry.yarnpkg.com/@stitches/react/-/react-1.2.6.tgz#61f2a3d1110334ecd33bcb7463650127d42470cb"
integrity sha512-gRVITYj8W4jJmoiVxWDv72yCvd12VvtUUAnTzs07EqmtvGCVgKZu3Dx0x5KVCcb0b6tfgvvNH2L84YrzdM4Mag==
"@stitches/react@^1.2.8":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@stitches/react/-/react-1.2.8.tgz#954f8008be8d9c65c4e58efa0937f32388ce3a38"
integrity sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==
"@surma/rollup-plugin-off-main-thread@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053"
@ -2993,6 +2998,11 @@
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
"@types/lz-string@^1.3.34":
version "1.3.34"
resolved "https://registry.yarnpkg.com/@types/lz-string/-/lz-string-1.3.34.tgz#69bfadde419314b4a374bf2c8e58659c035ed0a5"
integrity sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow==
"@types/minimatch@*", "@types/minimatch@^3.0.3":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
@ -4685,14 +4695,6 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
crc@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
@ -5147,11 +5149,6 @@ detective-typescript-70@^7.0.0:
node-source-walk "^4.2.0"
typescript "^3.9.7"
devtools-protocol@0.0.869402:
version "0.0.869402"
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.869402.tgz#03ade701761742e43ae4de5dc188bcd80f156d8d"
integrity sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==
diff-sequences@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
@ -6143,7 +6140,7 @@ external-editor@^3.1.0:
iconv-lite "^0.4.24"
tmp "^0.0.33"
extract-zip@2.0.1, extract-zip@^2.0.0:
extract-zip@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
@ -8556,10 +8553,10 @@ markdown-it@^12.3.2:
mdurl "^1.0.1"
uc.micro "^1.0.5"
marked@^4.0.10:
version "4.0.12"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.12.tgz#2262a4e6fd1afd2f13557726238b69a48b982f7d"
integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==
marked@^4.0.12:
version "4.0.15"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.15.tgz#0216b7c9d5fcf6ac5042343c41d81a8b1b5e1b4a"
integrity sha512-esX5lPdTfG4p8LDkv+obbRCyOKzB+820ZZyMOXJZygZBHrH9b3xXR64X4kT3sPe9Nx8qQXbmcz6kFSMt4Nfk6Q==
matcher@^3.0.0:
version "3.0.0"
@ -8972,7 +8969,7 @@ node-addon-api@^4.3.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7:
node-fetch@2.6.7, node-fetch@^2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
@ -9087,7 +9084,7 @@ oauth@^0.9.15:
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@ -9692,7 +9689,7 @@ process@^0.11.10:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
progress@2.0.3, progress@^2.0.1, progress@^2.0.3:
progress@2.0.3, progress@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
@ -9799,24 +9796,6 @@ pupa@^2.1.1:
dependencies:
escape-goat "^2.0.0"
puppeteer-core@9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-9.0.0.tgz#d99ad7b8d4b5b1b090977d95cb8eeafd71d12027"
integrity sha512-cgrnFLg5td0ciW3seGmLPlAf7e2jlIEjZlBOij/byS4iD3DRMHxItzVHv5OwEntkC1eamZqt8+WJJpAaGk6zPw==
dependencies:
debug "^4.1.0"
devtools-protocol "0.0.869402"
extract-zip "^2.0.0"
https-proxy-agent "^5.0.0"
node-fetch "^2.6.1"
pkg-dir "^4.2.0"
progress "^2.0.1"
proxy-from-env "^1.1.0"
rimraf "^3.0.2"
tar-fs "^2.0.0"
unbzip2-stream "^1.3.3"
ws "^7.2.3"
qs@^6.9.1:
version "6.10.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
@ -10542,10 +10521,10 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shiki@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.10.0.tgz#85f21ecfa95b377ff64db6c71442c22c220e9540"
integrity sha512-iczxaIYeBFHTFrQPb9DVy2SKgYxC4Wo7Iucm7C17cCh2Ge/refnvHscUOxM85u57MfLoNOtjoEFUWt9gBexblA==
shiki@^0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.10.1.tgz#6f9a16205a823b56c072d0f1a0bcd0f2646bef14"
integrity sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==
dependencies:
jsonc-parser "^3.0.0"
vscode-oniguruma "^1.6.1"
@ -11022,7 +11001,7 @@ tapable@^2.1.1, tapable@^2.2.0:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
tar-fs@^2.0.0, tar-fs@^2.1.1:
tar-fs@*, tar-fs@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
@ -11475,18 +11454,18 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typedoc@^0.22.11:
version "0.22.11"
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.22.11.tgz#a3d7f4577eef9fc82dd2e8f4e2915e69f884c250"
integrity sha512-pVr3hh6dkS3lPPaZz1fNpvcrqLdtEvXmXayN55czlamSgvEjh+57GUqfhAI1Xsuu/hNHUT1KNSx8LH2wBP/7SA==
typedoc@^0.22.15:
version "0.22.15"
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.22.15.tgz#c6ad7ed9d017dc2c3a06c9189cb392bd8e2d8c3f"
integrity sha512-CMd1lrqQbFvbx6S9G6fL4HKp3GoIuhujJReWqlIvSb2T26vGai+8Os3Mde7Pn832pXYemd9BMuuYWhFpL5st0Q==
dependencies:
glob "^7.2.0"
lunr "^2.3.9"
marked "^4.0.10"
minimatch "^3.0.4"
shiki "^0.10.0"
marked "^4.0.12"
minimatch "^5.0.1"
shiki "^0.10.1"
typescript@4.5.5, typescript@^4.5.2, typescript@^4.5.5:
typescript@4.5.5, typescript@^4.5.2:
version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
@ -11501,6 +11480,11 @@ typescript@^4.4.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==
typescript@^4.6.4:
version "4.6.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
typeson-registry@^1.0.0-alpha.20:
version "1.0.0-alpha.39"
resolved "https://registry.yarnpkg.com/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz#9e0f5aabd5eebfcffd65a796487541196f4b1211"
@ -11530,7 +11514,7 @@ unbox-primitive@^1.0.1:
has-symbols "^1.0.2"
which-boxed-primitive "^1.0.2"
unbzip2-stream@^1.0.9, unbzip2-stream@^1.3.3:
unbzip2-stream@^1.0.9:
version "1.4.3"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==
@ -11731,7 +11715,7 @@ validate-npm-package-name@^3.0.0:
dependencies:
builtins "^1.0.3"
vary@^1, vary@^1.1.2, vary@~1.1.2:
vary@^1.1.2, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
@ -12230,7 +12214,7 @@ ws@>=7.4.6:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
ws@^7.2.3, ws@^7.4.3, ws@^7.4.6:
ws@^7.4.3, ws@^7.4.6:
version "7.5.7"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67"
integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==