[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:
parent
1a1ce48407
commit
c54c800675
39 changed files with 1375 additions and 938 deletions
|
@ -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({
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -67,7 +67,6 @@ const App: React.FC = () => {
|
|||
document={rInitialDocument.current}
|
||||
onMount={handleMount}
|
||||
onPersist={handlePersist}
|
||||
onExport={exportToImage}
|
||||
autofocus
|
||||
/>
|
||||
</div>
|
||||
|
|
20
apps/vscode/editor/src/public/styles.css
Normal file
20
apps/vscode/editor/src/public/styles.css
Normal 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%;
|
||||
}
|
8
apps/vscode/editor/src/public/tldraw-assets.json
Normal file
8
apps/vscode/editor/src/public/tldraw-assets.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
}, [])
|
||||
|
|
|
@ -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,10 +43,9 @@
|
|||
"@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"
|
||||
},
|
||||
"gitHead": "838fabdbff1a66d4d7ee8aa5c5d117bc55acbff2"
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
}
|
8
apps/www/public/tldraw-assets.json
Normal file
8
apps/www/public/tldraw-assets.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -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()
|
||||
// }
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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>
|
||||
|
|
133
examples/tldraw-example/src/export-to-server.tsx
Normal file
133
examples/tldraw-example/src/export-to-server.tsx
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
8
examples/tldraw-example/src/public/tldraw-assets.json
Normal file
8
examples/tldraw-example/src/public/tldraw-assets.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -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": {
|
||||
|
@ -81,4 +81,4 @@
|
|||
"dependencies": {
|
||||
"@changesets/cli": "^2.20.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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])
|
||||
}, [])
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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(' ', '')
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&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&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&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&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&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&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&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&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&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&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block');</style></defs></svg>"`;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
|
|
8
packages/tldraw/tldraw-assets.json
Normal file
8
packages/tldraw/tldraw-assets.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -3,6 +3,7 @@
|
|||
"exclude": ["node_modules", "dist", "docs"],
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "src",
|
||||
"baseUrl": ".",
|
||||
|
|
398
yarn.lock
398
yarn.lock
|
@ -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==
|
||||
|
|
Loading…
Reference in a new issue