kopia lustrzana https://github.com/Tldraw/Tldraw
feat: add translation (#704)
* feat: add translation * modal, left menu translation * primary tools translation * render with intl provider for testing restore file * french translation done * context menu translation and test * added italian * Add menu to select language * translation for the word language * bump dev deps Bump react on www * Fix types * update dependencies * pre-release * Delete lask.config.json Co-authored-by: Enrico <franciscono.enry@gmail.com> Co-authored-by: Steve Ruiz <steveruizok@gmail.com>better-readonly
rodzic
7c08f2f5b6
commit
d919bd273e
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
'@tldraw/electron': minor
|
||||||
|
'@tldraw/vscode-editor': minor
|
||||||
|
'tldraw-vscode': minor
|
||||||
|
'@tldraw/www': minor
|
||||||
|
'@tldraw/core-example-simple': minor
|
||||||
|
'@tldraw/core-example-advanced': minor
|
||||||
|
'@tldraw/tldraw-example': minor
|
||||||
|
'@tldraw/core': minor
|
||||||
|
'@tldraw/tldraw': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Bump dependencies, add international support.
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"mode": "pre",
|
||||||
|
"tag": "next",
|
||||||
|
"initialVersions": {
|
||||||
|
"@tldraw/electron": "1.6.1",
|
||||||
|
"@tldraw/vscode-editor": "1.10.2",
|
||||||
|
"tldraw-vscode": "1.14.1",
|
||||||
|
"@tldraw/www": "1.6.7",
|
||||||
|
"@tldraw/core-example-simple": "1.7.0",
|
||||||
|
"@tldraw/core-example-advanced": "1.6.1",
|
||||||
|
"@tldraw/tldraw-example": "1.6.1",
|
||||||
|
"@tldraw/core": "1.13.1",
|
||||||
|
"@tldraw/curve": "1.7.0",
|
||||||
|
"@tldraw/intersect": "1.7.1",
|
||||||
|
"@tldraw/tldraw": "1.15.1",
|
||||||
|
"@tldraw/vec": "1.7.0"
|
||||||
|
},
|
||||||
|
"changesets": [
|
||||||
|
"clever-onions-flash"
|
||||||
|
]
|
||||||
|
}
|
2
.ignore
2
.ignore
|
@ -5,3 +5,5 @@ node_modules
|
||||||
*.d.ts
|
*.d.ts
|
||||||
*.js
|
*.js
|
||||||
*.md
|
*.md
|
||||||
|
*.lock
|
||||||
|
*.tsbuildinfo
|
|
@ -0,0 +1,6 @@
|
||||||
|
# @tldraw/electron
|
||||||
|
|
||||||
|
## 1.7.0-next.0
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Bump dependencies, add international support.
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@tldraw/electron",
|
"name": "@tldraw/electron",
|
||||||
"version": "1.6.1",
|
"version": "1.7.0-next.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "An electron app for tldraw.",
|
"description": "An electron app for tldraw.",
|
||||||
"author": "@steveruizok",
|
"author": "@steveruizok",
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
"package": "electron-builder"
|
"package": "electron-builder"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tldraw/tldraw": "^1.6.1",
|
"@tldraw/tldraw": "^1.16.0-next.0",
|
||||||
"@types/node": "^17.0.14",
|
"@types/node": "^17.0.14",
|
||||||
"@types/react": "^17.0.38",
|
"@types/react": "^17.0.38",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
"react": "^17.0",
|
"react": "^17.0",
|
||||||
"react-dom": "^17.0",
|
"react-dom": "^17.0",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"typescript": "4.5.5"
|
"typescript": "^4.7.3"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "io.comp.tldraw-electron",
|
"appId": "io.comp.tldraw-electron",
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
# @tldraw/vscode-editor
|
# @tldraw/vscode-editor
|
||||||
|
|
||||||
|
## 1.11.0-next.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Bump dependencies, add international support.
|
||||||
|
|
||||||
## 1.10.2
|
## 1.10.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@tldraw/vscode-editor",
|
"name": "@tldraw/vscode-editor",
|
||||||
"version": "1.10.2",
|
"version": "1.11.0-next.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "An an editor for the tldraw vscode extension.",
|
"description": "An an editor for the tldraw vscode extension.",
|
||||||
"author": "@steveruizok",
|
"author": "@steveruizok",
|
||||||
|
@ -18,18 +18,18 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tldraw/tldraw": "*",
|
"@tldraw/tldraw": "*",
|
||||||
"@types/node": "^17.0.14",
|
"@types/node": "^17.0.14",
|
||||||
"@types/react": "^17.0.38",
|
"@types/react": "^18.0.12",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^18.0.5",
|
||||||
"@types/react-router-dom": "^5.1.8",
|
"@types/react-router-dom": "^5.1.8",
|
||||||
"concurrently": "7.0.0",
|
"concurrently": "7.0.0",
|
||||||
"create-serve": "1.0.1",
|
"create-serve": "1.0.1",
|
||||||
"esbuild": "^0.14.38",
|
"esbuild": "^0.14.38",
|
||||||
"esbuild-serve": "^1.0.1",
|
"esbuild-serve": "^1.0.1",
|
||||||
"react": "^17.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^17.0",
|
"react-dom": "^18.1.0",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "4.5.5"
|
"typescript": "^4.7.3"
|
||||||
},
|
},
|
||||||
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
|
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
|
||||||
}
|
}
|
|
@ -1,5 +1,11 @@
|
||||||
## 1.2.4
|
## 1.2.4
|
||||||
|
|
||||||
|
## 1.15.0-next.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Bump dependencies, add international support.
|
||||||
|
|
||||||
## 1.14.1
|
## 1.14.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "tldraw-vscode",
|
"name": "tldraw-vscode",
|
||||||
"displayName": "tldraw",
|
"displayName": "tldraw",
|
||||||
"description": "The tldraw Extension for VS Code.",
|
"description": "The tldraw Extension for VS Code.",
|
||||||
"version": "1.14.1",
|
"version": "1.15.0-next.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"publisher": "tldraw-org",
|
"publisher": "tldraw-org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -128,8 +128,8 @@
|
||||||
"mocha": "^9.1.1",
|
"mocha": "^9.1.1",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"ts-loader": "^9.2.5",
|
"ts-loader": "^9.2.5",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "^4.4.3",
|
"typescript": "^4.7.3",
|
||||||
"vsce": "^2.2.0"
|
"vsce": "^2.2.0"
|
||||||
},
|
},
|
||||||
"gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296"
|
"gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296"
|
||||||
|
|
|
@ -1,5 +1,17 @@
|
||||||
# @tldraw/www
|
# @tldraw/www
|
||||||
|
|
||||||
|
## 1.7.0-next.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Bump dependencies, add international support.
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @tldraw/core@1.14.0-next.0
|
||||||
|
- @tldraw/tldraw@1.16.0-next.0
|
||||||
|
|
||||||
## 1.6.7
|
## 1.6.7
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Tldraw, TldrawApp, TldrawProps, useFileSystem } from '@tldraw/tldraw'
|
import { Tldraw, TldrawApp, TldrawProps, useFileSystem } from '@tldraw/tldraw'
|
||||||
import { useAccountHandlers } from 'hooks/useAccountHandlers'
|
import { useAccountHandlers } from 'hooks/useAccountHandlers'
|
||||||
import { useUploadAssets } from 'hooks/useUploadAssets'
|
import { useUploadAssets } from 'hooks/useUploadAssets'
|
||||||
import React, { FC } from 'react'
|
import * as React from 'react'
|
||||||
import * as gtag from 'utils/gtag'
|
import * as gtag from 'utils/gtag'
|
||||||
|
|
||||||
declare const window: Window & { app: TldrawApp }
|
declare const window: Window & { app: TldrawApp }
|
||||||
|
@ -12,7 +12,7 @@ interface EditorProps {
|
||||||
isSponsor?: boolean
|
isSponsor?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Editor: FC<EditorProps & Partial<TldrawProps>> = ({
|
const Editor: React.FC<EditorProps & Partial<TldrawProps>> = ({
|
||||||
id = 'home',
|
id = 'home',
|
||||||
isUser = false,
|
isUser = false,
|
||||||
isSponsor = false,
|
isSponsor = false,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createClient } from '@liveblocks/client'
|
import { createClient } from '@liveblocks/client'
|
||||||
import { LiveblocksProvider, RoomProvider } from '@liveblocks/react'
|
import { LiveblocksProvider, RoomProvider } from '@liveblocks/react'
|
||||||
import { Tldraw, TldrawApp, useFileSystem } from '@tldraw/tldraw'
|
import { Tldraw, useFileSystem } from '@tldraw/tldraw'
|
||||||
import { useAccountHandlers } from 'hooks/useAccountHandlers'
|
import { useAccountHandlers } from 'hooks/useAccountHandlers'
|
||||||
import { useMultiplayerAssets } from 'hooks/useMultiplayerAssets'
|
import { useMultiplayerAssets } from 'hooks/useMultiplayerAssets'
|
||||||
import { useMultiplayerState } from 'hooks/useMultiplayerState'
|
import { useMultiplayerState } from 'hooks/useMultiplayerState'
|
||||||
|
|
|
@ -3,10 +3,19 @@
|
||||||
import React, { useState, useRef, useCallback } from 'react'
|
import React, { useState, useRef, useCallback } from 'react'
|
||||||
import type { TldrawApp, TDUser, TDShape, TDBinding, TDDocument, TDAsset } from '@tldraw/tldraw'
|
import type { TldrawApp, TDUser, TDShape, TDBinding, TDDocument, TDAsset } from '@tldraw/tldraw'
|
||||||
import { useRedo, useUndo, useRoom, useUpdateMyPresence } from '@liveblocks/react'
|
import { useRedo, useUndo, useRoom, useUpdateMyPresence } from '@liveblocks/react'
|
||||||
import { LiveMap, LiveObject } from '@liveblocks/client'
|
import { LiveMap, LiveObject, Lson, LsonObject } from '@liveblocks/client'
|
||||||
|
|
||||||
declare const window: Window & { app: TldrawApp }
|
declare const window: Window & { app: TldrawApp }
|
||||||
|
|
||||||
|
type TDLsonShape = TDShape & Lson
|
||||||
|
type TDLsonBinding = TDBinding & Lson
|
||||||
|
type TDLsonAsset = TDAsset & Lson
|
||||||
|
type LsonDoc = {
|
||||||
|
uuid: string
|
||||||
|
document: TDDocument
|
||||||
|
migrated?: boolean
|
||||||
|
} & LsonObject
|
||||||
|
|
||||||
export function useMultiplayerState(roomId: string) {
|
export function useMultiplayerState(roomId: string) {
|
||||||
const [app, setApp] = useState<TldrawApp>()
|
const [app, setApp] = useState<TldrawApp>()
|
||||||
const [error, setError] = useState<Error>()
|
const [error, setError] = useState<Error>()
|
||||||
|
@ -17,9 +26,9 @@ export function useMultiplayerState(roomId: string) {
|
||||||
const onRedo = useRedo()
|
const onRedo = useRedo()
|
||||||
const updateMyPresence = useUpdateMyPresence()
|
const updateMyPresence = useUpdateMyPresence()
|
||||||
|
|
||||||
const rLiveShapes = useRef<LiveMap<string, TDShape>>()
|
const rLiveShapes = useRef<LiveMap<string, TDLsonShape>>()
|
||||||
const rLiveBindings = useRef<LiveMap<string, TDBinding>>()
|
const rLiveBindings = useRef<LiveMap<string, TDLsonBinding>>()
|
||||||
const rLiveAssets = useRef<LiveMap<string, TDAsset>>()
|
const rLiveAssets = useRef<LiveMap<string, TDLsonAsset>>()
|
||||||
|
|
||||||
// Callbacks --------------
|
// Callbacks --------------
|
||||||
|
|
||||||
|
@ -53,7 +62,7 @@ export function useMultiplayerState(roomId: string) {
|
||||||
if (!shape) {
|
if (!shape) {
|
||||||
lShapes.delete(id)
|
lShapes.delete(id)
|
||||||
} else {
|
} else {
|
||||||
lShapes.set(shape.id, shape)
|
lShapes.set(shape.id, shape as TDLsonShape)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -61,7 +70,7 @@ export function useMultiplayerState(roomId: string) {
|
||||||
if (!binding) {
|
if (!binding) {
|
||||||
lBindings.delete(id)
|
lBindings.delete(id)
|
||||||
} else {
|
} else {
|
||||||
lBindings.set(binding.id, binding)
|
lBindings.set(binding.id, binding as TDLsonBinding)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -69,7 +78,7 @@ export function useMultiplayerState(roomId: string) {
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
lAssets.delete(id)
|
lAssets.delete(id)
|
||||||
} else {
|
} else {
|
||||||
lAssets.set(asset.id, asset)
|
lAssets.set(asset.id, asset as TDLsonAsset)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -95,7 +104,7 @@ export function useMultiplayerState(roomId: string) {
|
||||||
|
|
||||||
// Handle changes to other users' presence
|
// Handle changes to other users' presence
|
||||||
unsubs.push(
|
unsubs.push(
|
||||||
room.subscribe('others', (others, event) => {
|
room.subscribe<{ id: string; user: TDUser }>('others', (others, event) => {
|
||||||
if (event.type === 'leave') {
|
if (event.type === 'leave') {
|
||||||
if (event.user.presence) {
|
if (event.user.presence) {
|
||||||
app?.removeUser(event.user.presence.id)
|
app?.removeUser(event.user.presence.id)
|
||||||
|
@ -123,23 +132,23 @@ export function useMultiplayerState(roomId: string) {
|
||||||
|
|
||||||
// Initialize (get or create) maps for shapes/bindings/assets
|
// Initialize (get or create) maps for shapes/bindings/assets
|
||||||
|
|
||||||
let lShapes: LiveMap<string, TDShape> = storage.root.get('shapes')
|
let lShapes: LiveMap<string, TDLsonShape> = storage.root.get('shapes')
|
||||||
if (!lShapes || !('_serialize' in lShapes)) {
|
if (!lShapes || !('_serialize' in lShapes)) {
|
||||||
storage.root.set('shapes', new LiveMap<string, TDShape>())
|
storage.root.set('shapes', new LiveMap<string, TDLsonShape>())
|
||||||
lShapes = storage.root.get('shapes')
|
lShapes = storage.root.get('shapes')
|
||||||
}
|
}
|
||||||
rLiveShapes.current = lShapes
|
rLiveShapes.current = lShapes
|
||||||
|
|
||||||
let lBindings: LiveMap<string, TDBinding> = storage.root.get('bindings')
|
let lBindings: LiveMap<string, TDLsonBinding> = storage.root.get('bindings')
|
||||||
if (!lBindings || !('_serialize' in lBindings)) {
|
if (!lBindings || !('_serialize' in lBindings)) {
|
||||||
storage.root.set('bindings', new LiveMap<string, TDBinding>())
|
storage.root.set('bindings', new LiveMap<string, TDLsonBinding>())
|
||||||
lBindings = storage.root.get('bindings')
|
lBindings = storage.root.get('bindings')
|
||||||
}
|
}
|
||||||
rLiveBindings.current = lBindings
|
rLiveBindings.current = lBindings
|
||||||
|
|
||||||
let lAssets: LiveMap<string, TDAsset> = storage.root.get('assets')
|
let lAssets: LiveMap<string, TDLsonAsset> = storage.root.get('assets')
|
||||||
if (!lAssets || !('_serialize' in lAssets)) {
|
if (!lAssets || !('_serialize' in lAssets)) {
|
||||||
storage.root.set('assets', new LiveMap<string, TDAsset>())
|
storage.root.set('assets', new LiveMap<string, TDLsonAsset>())
|
||||||
lAssets = storage.root.get('assets')
|
lAssets = storage.root.get('assets')
|
||||||
}
|
}
|
||||||
rLiveAssets.current = lAssets
|
rLiveAssets.current = lAssets
|
||||||
|
@ -150,11 +159,7 @@ export function useMultiplayerState(roomId: string) {
|
||||||
// document was a single LiveObject named 'doc'. If we find a doc,
|
// document was a single LiveObject named 'doc'. If we find a doc,
|
||||||
// then we need to move the shapes and bindings over to the new structures
|
// then we need to move the shapes and bindings over to the new structures
|
||||||
// and then mark the doc as migrated.
|
// and then mark the doc as migrated.
|
||||||
const doc = storage.root.get('doc') as LiveObject<{
|
const doc = storage.root.get('doc') as LiveObject<LsonDoc>
|
||||||
uuid: string
|
|
||||||
document: TDDocument
|
|
||||||
migrated?: boolean
|
|
||||||
}>
|
|
||||||
|
|
||||||
// No doc? No problem. This was likely a newer document
|
// No doc? No problem. This was likely a newer document
|
||||||
if (doc) {
|
if (doc) {
|
||||||
|
@ -167,9 +172,11 @@ export function useMultiplayerState(roomId: string) {
|
||||||
},
|
},
|
||||||
} = doc.toObject()
|
} = doc.toObject()
|
||||||
|
|
||||||
Object.values(shapes).forEach((shape) => lShapes.set(shape.id, shape))
|
Object.values(shapes).forEach((shape) => lShapes.set(shape.id, shape as TDLsonShape))
|
||||||
Object.values(bindings).forEach((binding) => lBindings.set(binding.id, binding))
|
Object.values(bindings).forEach((binding) =>
|
||||||
Object.values(assets).forEach((asset) => lAssets.set(asset.id, asset))
|
lBindings.set(binding.id, binding as TDLsonBinding)
|
||||||
|
)
|
||||||
|
Object.values(assets).forEach((asset) => lAssets.set(asset.id, asset as TDLsonAsset))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@tldraw/www",
|
"name": "@tldraw/www",
|
||||||
"version": "1.6.7",
|
"version": "1.7.0-next.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A tiny little drawing app (site).",
|
"description": "A tiny little drawing app (site).",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -18,33 +18,31 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@liveblocks/client": "^0.14.0",
|
"@sentry/webpack-plugin": "^1.17.1",
|
||||||
"@liveblocks/react": "^0.14.0",
|
"@types/next-auth": "^3.15.0",
|
||||||
|
"@types/react": "^18.0.12",
|
||||||
|
"@types/react-dom": "^18.0.5",
|
||||||
|
"eslint": "^8.8.0",
|
||||||
|
"eslint-config-next": "^12.0.10",
|
||||||
|
"typescript": "^4.7.3",
|
||||||
|
"@liveblocks/client": "^0.16.17",
|
||||||
|
"@liveblocks/react": "^0.16.17",
|
||||||
"@sentry/integrations": "^6.13.2",
|
"@sentry/integrations": "^6.13.2",
|
||||||
"@sentry/node": "^6.13.2",
|
"@sentry/node": "^6.13.2",
|
||||||
"@sentry/react": "^6.13.2",
|
"@sentry/react": "^6.13.2",
|
||||||
"@sentry/tracing": "^6.13.2",
|
"@sentry/tracing": "^6.13.2",
|
||||||
"@stitches/react": "^1.2.5",
|
"@stitches/react": "^1.2.8",
|
||||||
"@tldraw/core": "*",
|
"@tldraw/core": "*",
|
||||||
"@tldraw/tldraw": "*",
|
"@tldraw/tldraw": "*",
|
||||||
"aws-sdk": "^2.1053.0",
|
"aws-sdk": "^2.1053.0",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
"next": "^12.0.7",
|
"next": "^12.1.6",
|
||||||
"next-auth": "^4.0.5",
|
"next-auth": "^4.0.5",
|
||||||
"next-pwa": "^5.4.4",
|
"next-pwa": "^5.5.4",
|
||||||
"next-themes": "^0.0.15",
|
"next-themes": "^0.0.15",
|
||||||
"react": "^17.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^17.0"
|
"react-dom": "^18.1.0"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/next-auth": "^3.15.0",
|
|
||||||
"@sentry/webpack-plugin": "^1.17.1",
|
|
||||||
"@types/react": "^17.0.19",
|
|
||||||
"@types/react-dom": "^17.0.9",
|
|
||||||
"eslint": "^8.8.0",
|
|
||||||
"eslint-config-next": "^12.0.10",
|
|
||||||
"typescript": "^4.5.2"
|
|
||||||
},
|
},
|
||||||
"gitHead": "838fabdbff1a66d4d7ee8aa5c5d117bc55acbff2"
|
"gitHead": "838fabdbff1a66d4d7ee8aa5c5d117bc55acbff2"
|
||||||
}
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
# @tldraw/core-example-advanced
|
||||||
|
|
||||||
|
## 1.7.0-next.0
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Bump dependencies, add international support.
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.6.1",
|
"version": "1.7.0-next.0",
|
||||||
"name": "@tldraw/core-example-advanced",
|
"name": "@tldraw/core-example-advanced",
|
||||||
"description": "An advanced example project for @tldraw/core.",
|
"description": "An advanced example project for @tldraw/core.",
|
||||||
"author": "@steveruizok",
|
"author": "@steveruizok",
|
||||||
|
@ -22,8 +22,6 @@
|
||||||
"@tldraw/intersect": "*",
|
"@tldraw/intersect": "*",
|
||||||
"@tldraw/vec": "*",
|
"@tldraw/vec": "*",
|
||||||
"@types/node": "^17.0.14",
|
"@types/node": "^17.0.14",
|
||||||
"@types/react": "^17.0.38",
|
|
||||||
"@types/react-dom": "^17.0.11",
|
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"concurrently": "^7.0.0",
|
"concurrently": "^7.0.0",
|
||||||
"create-serve": "^1.0.1",
|
"create-serve": "^1.0.1",
|
||||||
|
@ -33,11 +31,13 @@
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"nanoid": "^3.1.31",
|
"nanoid": "^3.1.31",
|
||||||
"perfect-freehand": "^1.1.0",
|
"perfect-freehand": "^1.1.0",
|
||||||
"react": "^17.0.2",
|
|
||||||
"react-dom": "^17.0.2",
|
|
||||||
"react-feather": "^2.0.9",
|
"react-feather": "^2.0.9",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "^4.7.3",
|
||||||
|
"@types/react": "^18.0.12",
|
||||||
|
"@types/react-dom": "^18.0.5",
|
||||||
|
"react": "^18.1.0",
|
||||||
|
"react-dom": "^18.1.0"
|
||||||
},
|
},
|
||||||
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
|
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
|
||||||
}
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import { createRoot } from 'react-dom/client'
|
||||||
import App from './app'
|
import App from './app'
|
||||||
|
import './styles.css'
|
||||||
|
|
||||||
ReactDOM.render(
|
const container = document.getElementById('root')!
|
||||||
|
const root = createRoot(container)
|
||||||
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</React.StrictMode>,
|
</React.StrictMode>
|
||||||
document.getElementById('root')
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
# @tldraw/core-example-simple
|
# @tldraw/core-example-simple
|
||||||
|
|
||||||
|
## 1.8.0-next.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Bump dependencies, add international support.
|
||||||
|
|
||||||
## 1.7.0
|
## 1.7.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|
||||||
- Fix build error in extension.
|
- Fix build error in extension.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.7.0",
|
"version": "1.8.0-next.0",
|
||||||
"name": "@tldraw/core-example-simple",
|
"name": "@tldraw/core-example-simple",
|
||||||
"description": "A simple example project for @tldraw/core.",
|
"description": "A simple example project for @tldraw/core.",
|
||||||
"author": "@steveruizok",
|
"author": "@steveruizok",
|
||||||
|
@ -18,18 +18,18 @@
|
||||||
"@tldraw/core": "*",
|
"@tldraw/core": "*",
|
||||||
"@tldraw/vec": "*",
|
"@tldraw/vec": "*",
|
||||||
"@types/node": "^17.0.14",
|
"@types/node": "^17.0.14",
|
||||||
"@types/react": "^17.0.38",
|
|
||||||
"@types/react-dom": "^17.0.11",
|
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"concurrently": "^7.0.0",
|
"concurrently": "^7.0.0",
|
||||||
"esbuild": "^0.14.18",
|
"esbuild": "^0.14.18",
|
||||||
"esbuild-serve": "^1.0.1",
|
"esbuild-serve": "^1.0.1",
|
||||||
"mobx": "^6.3.13",
|
"mobx": "^6.3.13",
|
||||||
"mobx-react-lite": "^3.2.3",
|
"mobx-react-lite": "^3.2.3",
|
||||||
"react": "^17.0.2",
|
|
||||||
"react-dom": "^17.0.2",
|
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "^4.7.3",
|
||||||
|
"@types/react": "^18.0.12",
|
||||||
|
"@types/react-dom": "^18.0.5",
|
||||||
|
"react": "^18.1.0",
|
||||||
|
"react-dom": "^18.1.0"
|
||||||
},
|
},
|
||||||
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
|
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
|
||||||
}
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import { createRoot } from 'react-dom/client'
|
||||||
import App from './app'
|
import App from './app'
|
||||||
import './styles.css'
|
import './styles.css'
|
||||||
|
|
||||||
ReactDOM.render(
|
const container = document.getElementById('root')!
|
||||||
|
const root = createRoot(container)
|
||||||
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</React.StrictMode>,
|
</React.StrictMode>
|
||||||
document.getElementById('root')
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
# @tldraw/tldraw-example
|
||||||
|
|
||||||
|
## 1.7.0-next.0
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Bump dependencies, add international support.
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@tldraw/tldraw-example",
|
"name": "@tldraw/tldraw-example",
|
||||||
"version": "1.6.1",
|
"version": "1.7.0-next.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "An example project for @tldraw/tldraw.",
|
"description": "An example project for @tldraw/tldraw.",
|
||||||
"author": "@steveruizok",
|
"author": "@steveruizok",
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
"@liveblocks/client": "^0.14.0",
|
"@liveblocks/client": "^0.14.0",
|
||||||
"@liveblocks/react": "^0.14.0",
|
"@liveblocks/react": "^0.14.0",
|
||||||
"@types/node": "^17.0.14",
|
"@types/node": "^17.0.14",
|
||||||
"@types/react": "^17.0.38",
|
|
||||||
"@types/react-dom": "^17.0.11",
|
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"concurrently": "^7.0.0",
|
"concurrently": "^7.0.0",
|
||||||
"create-serve": "^1.0.1",
|
"create-serve": "^1.0.1",
|
||||||
|
@ -27,12 +25,14 @@
|
||||||
"esbuild-envfile-plugin": "^1.0.2",
|
"esbuild-envfile-plugin": "^1.0.2",
|
||||||
"esbuild-serve": "^1.0.1",
|
"esbuild-serve": "^1.0.1",
|
||||||
"firebase": "^9.6.5",
|
"firebase": "^9.6.5",
|
||||||
"react": "^17.0.2",
|
|
||||||
"react-dom": "^17.0.2",
|
|
||||||
"react-router": "^6.2.1",
|
"react-router": "^6.2.1",
|
||||||
"react-router-dom": "^6.2.1",
|
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "^4.7.3",
|
||||||
|
"@types/react": "^18.0.12",
|
||||||
|
"@types/react-dom": "^18.0.5",
|
||||||
|
"react": "^18.1.0",
|
||||||
|
"react-dom": "^18.1.0",
|
||||||
|
"react-router-dom": "^6.3.0"
|
||||||
},
|
},
|
||||||
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
|
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
|
||||||
}
|
}
|
|
@ -55,7 +55,9 @@ export default function App() {
|
||||||
<main>
|
<main>
|
||||||
<Routes>
|
<Routes>
|
||||||
{pages.map((page) =>
|
{pages.map((page) =>
|
||||||
page === '---' ? null : <Route path={page.path} element={<page.component />} />
|
page === '---' ? null : (
|
||||||
|
<Route key={page.path} path={page.path} element={<page.component />} />
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import { createRoot } from 'react-dom/client'
|
||||||
import App from './app'
|
import App from './app'
|
||||||
import { HashRouter } from 'react-router-dom'
|
import { HashRouter } from 'react-router-dom'
|
||||||
|
|
||||||
ReactDOM.render(
|
const container = document.getElementById('root')!
|
||||||
|
const root = createRoot(container)
|
||||||
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<App />
|
<App />
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>
|
||||||
document.getElementById('root')
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -61,14 +61,12 @@
|
||||||
"mobx": "^6.3.8",
|
"mobx": "^6.3.8",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"pretty-quick": "^3.1.3",
|
"pretty-quick": "^3.1.3",
|
||||||
"react": "^17.0",
|
|
||||||
"react-dom": "^17.0",
|
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"source-map-loader": "^3.0.1",
|
"source-map-loader": "^3.0.1",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.4.0",
|
||||||
"turbo": "^1.1.2",
|
"turbo": "^1.1.2",
|
||||||
"typedoc": "^0.22.15",
|
"typedoc": "^0.22.15",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.7.3",
|
||||||
"webpack": "^5.68.0"
|
"webpack": "^5.68.0"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.14.0-next.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Bump dependencies, add international support.
|
||||||
|
|
||||||
## 1.13.1
|
## 1.13.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.13.1",
|
"version": "1.14.0-next.0",
|
||||||
"name": "@tldraw/core",
|
"name": "@tldraw/core",
|
||||||
"description": "The tldraw core renderer and utilities.",
|
"description": "The tldraw core renderer and utilities.",
|
||||||
"author": "@steveruizok",
|
"author": "@steveruizok",
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=16.8",
|
"react": ">=16.8",
|
||||||
"react-dom": "^16.8 || ^17.0"
|
"react-dom": ">=16.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc-node/jest": "^1.4.3",
|
"@swc-node/jest": "^1.4.3",
|
||||||
|
@ -55,14 +55,15 @@
|
||||||
"@tldraw/intersect": "*",
|
"@tldraw/intersect": "*",
|
||||||
"@tldraw/vec": "*",
|
"@tldraw/vec": "*",
|
||||||
"@types/node": "^17.0.14",
|
"@types/node": "^17.0.14",
|
||||||
"@types/react": "^17.0.38",
|
"@types/react": "^18.0.12",
|
||||||
|
"@types/react-dom": "^18.0.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||||
"@typescript-eslint/parser": "^5.10.2",
|
"@typescript-eslint/parser": "^5.10.2",
|
||||||
"eslint": "^8.8.0",
|
"eslint": "^8.8.0",
|
||||||
"lask": "^0.0.29",
|
"lask": "^0.0.29",
|
||||||
"mobx": "^6.3.8",
|
"mobx": "^6.3.8",
|
||||||
"react": ">=16.8",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^16.8 || ^17.0"
|
"react-dom": "^18.1.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"setupFilesAfterEnv": [
|
"setupFilesAfterEnv": [
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.16.0-next.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Bump dependencies, add international support.
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @tldraw/core@1.14.0-next.0
|
||||||
|
|
||||||
## 1.15.1
|
## 1.15.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@tldraw/tldraw",
|
"name": "@tldraw/tldraw",
|
||||||
"version": "1.15.1",
|
"version": "1.16.0-next.0",
|
||||||
"description": "A tiny little drawing app (editor)",
|
"description": "A tiny little drawing app (editor)",
|
||||||
"author": "@steveruizok",
|
"author": "@steveruizok",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -37,29 +37,26 @@
|
||||||
"docs": "typedoc"
|
"docs": "typedoc"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^17.0",
|
"react": ">=16.8",
|
||||||
"react-dom": "^17.0"
|
"react-dom": ">=16.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-alert-dialog": "^0.1.7",
|
"@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-context-menu": "^0.1.6",
|
||||||
"@radix-ui/react-dropdown-menu": "^0.1.6",
|
"@radix-ui/react-dropdown-menu": "^0.1.6",
|
||||||
"@radix-ui/react-icons": "^1.1.1",
|
"@radix-ui/react-icons": "^1.1.1",
|
||||||
"@radix-ui/react-radio-group": "^0.1.5",
|
|
||||||
"@radix-ui/react-tooltip": "^0.1.7",
|
"@radix-ui/react-tooltip": "^0.1.7",
|
||||||
"@stitches/react": "^1.2.8",
|
"@stitches/react": "^1.2.8",
|
||||||
"@tldraw/core": "^1.13.1",
|
"@tldraw/core": "^1.14.0-next.0",
|
||||||
"@tldraw/intersect": "^1.7.1",
|
"@tldraw/intersect": "^1.7.1",
|
||||||
"@tldraw/vec": "^1.7.0",
|
"@tldraw/vec": "^1.7.0",
|
||||||
"@types/lz-string": "^1.3.34",
|
|
||||||
"idb-keyval": "^6.1.0",
|
"idb-keyval": "^6.1.0",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"perfect-freehand": "^1.1.0",
|
"perfect-freehand": "^1.1.0",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-hotkey-hook": "^1.0.2",
|
|
||||||
"react-hotkeys-hook": "^3.4.4",
|
"react-hotkeys-hook": "^3.4.4",
|
||||||
"tslib": "^2.3.1",
|
"react-intl": "^6.0.3",
|
||||||
|
"tslib": "^2.4.0",
|
||||||
"zustand": "^3.6.9"
|
"zustand": "^3.6.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -69,16 +66,18 @@
|
||||||
"@tldraw/core": "*",
|
"@tldraw/core": "*",
|
||||||
"@tldraw/intersect": "*",
|
"@tldraw/intersect": "*",
|
||||||
"@tldraw/vec": "*",
|
"@tldraw/vec": "*",
|
||||||
|
"@types/lz-string": "^1.3.34",
|
||||||
"@types/node": "^17.0.14",
|
"@types/node": "^17.0.14",
|
||||||
"@types/react": "^17.0.38",
|
"@types/react": "^18.0.12",
|
||||||
|
"@types/react-dom": "^18.0.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||||
"@typescript-eslint/parser": "^5.10.2",
|
"@typescript-eslint/parser": "^5.10.2",
|
||||||
"eslint": "^8.8.0",
|
"eslint": "^8.8.0",
|
||||||
"lask": "^0.0.29",
|
"lask": "^0.0.29",
|
||||||
"mobx": "^6.3.8",
|
"mobx": "^6.3.8",
|
||||||
"react": "^17.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^17.0",
|
"react-dom": "^18.1.0",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "^4.7.3"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"setupFilesAfterEnv": [
|
"setupFilesAfterEnv": [
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { Renderer } from '@tldraw/core'
|
import { Renderer } from '@tldraw/core'
|
||||||
|
import { IntlConfig, IntlProvider } from 'react-intl'
|
||||||
import { styled, dark } from '~styles'
|
import { styled, dark } from '~styles'
|
||||||
import { TDDocument, TDStatus } from '~types'
|
import { TDDocument, TDStatus } from '~types'
|
||||||
import { TldrawApp, TDCallbacks } from '~state'
|
import { TldrawApp, TDCallbacks } from '~state'
|
||||||
|
@ -12,9 +13,15 @@ import { FocusButton } from '~components/FocusButton'
|
||||||
import { TLDR } from '~state/TLDR'
|
import { TLDR } from '~state/TLDR'
|
||||||
import { GRID_SIZE } from '~constants'
|
import { GRID_SIZE } from '~constants'
|
||||||
import { Loading } from '~components/Loading'
|
import { Loading } from '~components/Loading'
|
||||||
import { ErrorBoundary } from 'react-error-boundary'
|
import { ErrorBoundary as _Errorboundary } from 'react-error-boundary'
|
||||||
import { ErrorFallback } from '~components/ErrorFallback'
|
import { ErrorFallback } from '~components/ErrorFallback'
|
||||||
|
|
||||||
|
import messages_en from './translations/en.json'
|
||||||
|
import messages_fr from './translations/fr.json'
|
||||||
|
import messages_it from './translations/it.json'
|
||||||
|
|
||||||
|
const ErrorBoundary = _Errorboundary as any
|
||||||
|
|
||||||
export interface TldrawProps extends TDCallbacks {
|
export interface TldrawProps extends TDCallbacks {
|
||||||
/**
|
/**
|
||||||
* (optional) If provided, the component will load / persist state under this key.
|
* (optional) If provided, the component will load / persist state under this key.
|
||||||
|
@ -417,7 +424,19 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
const hideCloneHandles =
|
const hideCloneHandles =
|
||||||
isInSession || !isSelecting || !settings.showCloneHandles || pageState.camera.zoom < 0.2
|
isInSession || !isSelecting || !settings.showCloneHandles || pageState.camera.zoom < 0.2
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
en: messages_en,
|
||||||
|
fr: messages_fr,
|
||||||
|
it: messages_it,
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultLanguage = settings.language ?? navigator.language.split(/[-_]/)[0]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<IntlProvider
|
||||||
|
locale={defaultLanguage}
|
||||||
|
messages={messages[defaultLanguage] as IntlConfig['messages']}
|
||||||
|
>
|
||||||
<StyledLayout ref={rWrapper} tabIndex={-0} className={settings.isDarkMode ? dark : ''}>
|
<StyledLayout ref={rWrapper} tabIndex={-0} className={settings.isDarkMode ? dark : ''}>
|
||||||
<Loading />
|
<Loading />
|
||||||
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
||||||
|
@ -523,6 +542,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
</StyledUI>
|
</StyledUI>
|
||||||
)}
|
)}
|
||||||
</StyledLayout>
|
</StyledLayout>
|
||||||
|
</IntlProvider>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { ContextMenu } from './ContextMenu'
|
import { ContextMenu } from './ContextMenu'
|
||||||
import { renderWithContext } from '~test'
|
import { renderWithContext, renderWithIntlProvider } from '~test'
|
||||||
|
|
||||||
describe('context menu', () => {
|
describe('context menu', () => {
|
||||||
test('mounts component without crashing', () => {
|
test('mounts component without crashing', () => {
|
||||||
renderWithContext(
|
renderWithContext(
|
||||||
|
renderWithIntlProvider(
|
||||||
<ContextMenu onBlur={jest.fn()}>
|
<ContextMenu onBlur={jest.fn()}>
|
||||||
<div>Hello</div>
|
<div>Hello</div>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
)
|
)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { Divider } from '~components/Primitives/Divider'
|
||||||
import { MenuContent } from '~components/Primitives/MenuContent'
|
import { MenuContent } from '~components/Primitives/MenuContent'
|
||||||
import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
|
import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
|
||||||
import { ToolButton, ToolButtonProps } from '~components/Primitives/ToolButton'
|
import { ToolButton, ToolButtonProps } from '~components/Primitives/ToolButton'
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl'
|
||||||
|
|
||||||
const numberOfSelectedIdsSelector = (s: TDSnapshot) => {
|
const numberOfSelectedIdsSelector = (s: TDSnapshot) => {
|
||||||
return s.document.pageStates[s.appState.currentPageId].selectedIds.length
|
return s.document.pageStates[s.appState.currentPageId].selectedIds.length
|
||||||
|
@ -56,6 +57,7 @@ interface InnerContextMenuProps {
|
||||||
|
|
||||||
const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProps) {
|
const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProps) {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
const intl = useIntl()
|
||||||
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
|
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
|
||||||
const isDebugMode = app.useStore(isDebugModeSelector)
|
const isDebugMode = app.useStore(isDebugModeSelector)
|
||||||
const hasGroupSelected = app.useStore(hasGroupSelectedSelector)
|
const hasGroupSelected = app.useStore(hasGroupSelectedSelector)
|
||||||
|
@ -171,45 +173,46 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
|
||||||
{hasSelection ? (
|
{hasSelection ? (
|
||||||
<>
|
<>
|
||||||
<CMRowButton onClick={handleDuplicate} kbd="#D" id="TD-ContextMenu-Duplicate">
|
<CMRowButton onClick={handleDuplicate} kbd="#D" id="TD-ContextMenu-Duplicate">
|
||||||
Duplicate
|
<FormattedMessage id="duplicate" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<CMRowButton
|
<CMRowButton
|
||||||
onClick={handleFlipHorizontal}
|
onClick={handleFlipHorizontal}
|
||||||
kbd="⇧H"
|
kbd="⇧H"
|
||||||
id="TD-ContextMenu-Flip_Horizontal"
|
id="TD-ContextMenu-Flip_Horizontal"
|
||||||
>
|
>
|
||||||
Flip Horizontal
|
<FormattedMessage id="flip.horizontal" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<CMRowButton onClick={handleFlipVertical} kbd="⇧V" id="TD-ContextMenu-Flip_Vertical">
|
<CMRowButton onClick={handleFlipVertical} kbd="⇧V" id="TD-ContextMenu-Flip_Vertical">
|
||||||
Flip Vertical
|
<FormattedMessage id="flip.vertical" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<CMRowButton onClick={handleLock} kbd="#⇧L" id="TD-ContextMenu- Lock_Unlock">
|
<CMRowButton onClick={handleLock} kbd="#⇧L" id="TD-ContextMenu- Lock_Unlock">
|
||||||
Lock / Unlock
|
<FormattedMessage id="lock" /> / <FormattedMessage id="unlock" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
{(hasTwoOrMore || hasGroupSelected) && <Divider />}
|
{(hasTwoOrMore || hasGroupSelected) && <Divider />}
|
||||||
{hasTwoOrMore && (
|
{hasTwoOrMore && (
|
||||||
<CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Group">
|
<CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Group">
|
||||||
Group
|
<FormattedMessage id="group" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
)}
|
)}
|
||||||
{hasGroupSelected && (
|
{hasGroupSelected && (
|
||||||
<CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Ungroup">
|
<CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Ungroup">
|
||||||
Ungroup
|
<FormattedMessage id="ungroup" />
|
||||||
|
<FormattedMessage id="ungroup" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
)}
|
)}
|
||||||
<Divider />
|
<Divider />
|
||||||
<ContextMenuSubMenu label="Move" id="TD-ContextMenu-Move">
|
<ContextMenuSubMenu label={intl.formatMessage({ id: 'move' })} id="TD-ContextMenu-Move">
|
||||||
<CMRowButton onClick={handleMoveToFront} kbd="⇧]" id="TD-ContextMenu-Move-To_Front">
|
<CMRowButton onClick={handleMoveToFront} kbd="⇧]" id="TD-ContextMenu-Move-To_Front">
|
||||||
To Front
|
<FormattedMessage id="to.front" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<CMRowButton onClick={handleMoveForward} kbd="]" id="TD-ContextMenu-Move-Forward">
|
<CMRowButton onClick={handleMoveForward} kbd="]" id="TD-ContextMenu-Move-Forward">
|
||||||
Forward
|
<FormattedMessage id="forward" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<CMRowButton onClick={handleMoveBackward} kbd="[" id="TD-ContextMenu-Move-Backward">
|
<CMRowButton onClick={handleMoveBackward} kbd="[" id="TD-ContextMenu-Move-Backward">
|
||||||
Backward
|
<FormattedMessage id="backward" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<CMRowButton onClick={handleMoveToBack} kbd="⇧[" id="TD-ContextMenu-Move-To_Back">
|
<CMRowButton onClick={handleMoveToBack} kbd="⇧[" id="TD-ContextMenu-Move-To_Back">
|
||||||
To Back
|
<FormattedMessage id="back" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
</ContextMenuSubMenu>
|
</ContextMenuSubMenu>
|
||||||
<MoveToPageMenu />
|
<MoveToPageMenu />
|
||||||
|
@ -218,16 +221,20 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
|
||||||
)}
|
)}
|
||||||
<Divider />
|
<Divider />
|
||||||
<CMRowButton onClick={handleCut} kbd="#X" id="TD-ContextMenu-Cut">
|
<CMRowButton onClick={handleCut} kbd="#X" id="TD-ContextMenu-Cut">
|
||||||
Cut
|
<FormattedMessage id="cut" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<CMRowButton onClick={handleCopy} kbd="#C" id="TD-ContextMenu-Copy">
|
<CMRowButton onClick={handleCopy} kbd="#C" id="TD-ContextMenu-Copy">
|
||||||
Copy
|
<FormattedMessage id="copy" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
|
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
|
||||||
Paste
|
<FormattedMessage id="paste" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ContextMenuSubMenu label="Copy as..." size="small" id="TD-ContextMenu-Copy-As">
|
<ContextMenuSubMenu
|
||||||
|
label={`${intl.formatMessage({ id: 'copy.as' })}...`}
|
||||||
|
size="small"
|
||||||
|
id="TD-ContextMenu-Copy-As"
|
||||||
|
>
|
||||||
<CMRowButton onClick={handleCopySVG} id="TD-ContextMenu-Copy-as-SVG">
|
<CMRowButton onClick={handleCopySVG} id="TD-ContextMenu-Copy-as-SVG">
|
||||||
SVG
|
SVG
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
|
@ -240,7 +247,11 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
)}
|
)}
|
||||||
</ContextMenuSubMenu>
|
</ContextMenuSubMenu>
|
||||||
<ContextMenuSubMenu label="Export as..." size="small" id="TD-ContextMenu-Export">
|
<ContextMenuSubMenu
|
||||||
|
label={`${intl.formatMessage({ id: 'export.as' })}...`}
|
||||||
|
size="small"
|
||||||
|
id="TD-ContextMenu-Export"
|
||||||
|
>
|
||||||
<CMRowButton onClick={handleExportSVG} id="TD-ContextMenu-Export-SVG">
|
<CMRowButton onClick={handleExportSVG} id="TD-ContextMenu-Export-SVG">
|
||||||
SVG
|
SVG
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
|
@ -261,19 +272,19 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
|
||||||
</ContextMenuSubMenu>
|
</ContextMenuSubMenu>
|
||||||
<Divider />
|
<Divider />
|
||||||
<CMRowButton onClick={handleDelete} kbd="⌫" id="TD-ContextMenu-Delete">
|
<CMRowButton onClick={handleDelete} kbd="⌫" id="TD-ContextMenu-Delete">
|
||||||
Delete
|
<FormattedMessage id="delete" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
|
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
|
||||||
Paste
|
<FormattedMessage id="paste" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<CMRowButton onClick={handleUndo} kbd="#Z" id="TD-ContextMenu-Undo">
|
<CMRowButton onClick={handleUndo} kbd="#Z" id="TD-ContextMenu-Undo">
|
||||||
Undo
|
<FormattedMessage id="undo" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
<CMRowButton onClick={handleRedo} kbd="#⇧Z" id="TD-ContextMenu-Redo">
|
<CMRowButton onClick={handleRedo} kbd="#⇧Z" id="TD-ContextMenu-Redo">
|
||||||
Redo
|
<FormattedMessage id="redo" />
|
||||||
</CMRowButton>
|
</CMRowButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -430,7 +441,9 @@ function MoveToPageMenu() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadixContextMenu.Root dir="ltr">
|
<RadixContextMenu.Root dir="ltr">
|
||||||
<CMTriggerButton isSubmenu>Move To Page</CMTriggerButton>
|
<CMTriggerButton isSubmenu>
|
||||||
|
<FormattedMessage id="move.to.page" />
|
||||||
|
</CMTriggerButton>
|
||||||
<RadixContextMenu.Content dir="ltr" sideOffset={2} alignOffset={-2} asChild>
|
<RadixContextMenu.Content dir="ltr" sideOffset={2} alignOffset={-2} asChild>
|
||||||
<MenuContent>
|
<MenuContent>
|
||||||
{sorted.map(({ id, name }, i) => (
|
{sorted.map(({ id, name }, i) => (
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { RowButton } from '~components/Primitives/RowButton'
|
||||||
import { useTldrawApp } from '~hooks'
|
import { useTldrawApp } from '~hooks'
|
||||||
import { styled } from '~styles'
|
import { styled } from '~styles'
|
||||||
|
|
||||||
export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps): any {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
|
||||||
const refreshPage = () => {
|
const refreshPage = () => {
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { Arrow } from '@radix-ui/react-dropdown-menu'
|
|
||||||
import { breakpoints } from '~components/breakpoints'
|
|
||||||
import { styled } from '~styles/stitches.config'
|
|
||||||
|
|
||||||
export const DMArrow = styled(Arrow, { fill: '$panel', bp: breakpoints })
|
|
|
@ -1,4 +1,3 @@
|
||||||
export * from './DMArrow'
|
|
||||||
export * from './DMItem'
|
export * from './DMItem'
|
||||||
export * from './DMCheckboxItem'
|
export * from './DMCheckboxItem'
|
||||||
export * from './DMContent'
|
export * from './DMContent'
|
||||||
|
|
|
@ -32,6 +32,7 @@ import {
|
||||||
import { DMContent } from '~components/Primitives/DropdownMenu'
|
import { DMContent } from '~components/Primitives/DropdownMenu'
|
||||||
import { Divider } from '~components/Primitives/Divider'
|
import { Divider } from '~components/Primitives/Divider'
|
||||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||||
|
import { useIntl } from 'react-intl'
|
||||||
|
|
||||||
const selectedShapesCountSelector = (s: TDSnapshot) =>
|
const selectedShapesCountSelector = (s: TDSnapshot) =>
|
||||||
s.document.pageStates[s.appState.currentPageId].selectedIds.length
|
s.document.pageStates[s.appState.currentPageId].selectedIds.length
|
||||||
|
@ -74,6 +75,9 @@ const hasMultipleSelectionSelector = (s: TDSnapshot) => {
|
||||||
|
|
||||||
export function ActionButton() {
|
export function ActionButton() {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const isFrenchLang = navigator.language === 'fr'
|
||||||
|
|
||||||
const isAllLocked = app.useStore(isAllLockedSelector)
|
const isAllLocked = app.useStore(isAllLockedSelector)
|
||||||
|
|
||||||
|
@ -189,22 +193,35 @@ export function ActionButton() {
|
||||||
<>
|
<>
|
||||||
<ButtonsRow>
|
<ButtonsRow>
|
||||||
<ToolButton variant="icon" disabled={!hasSelection} onClick={handleDuplicate}>
|
<ToolButton variant="icon" disabled={!hasSelection} onClick={handleDuplicate}>
|
||||||
<Tooltip label="Duplicate" kbd={`#D`} id="TD-Tools-Copy">
|
<Tooltip
|
||||||
|
label={intl.formatMessage({ id: 'duplicate' })}
|
||||||
|
kbd={`#D`}
|
||||||
|
id="TD-Tools-Copy"
|
||||||
|
>
|
||||||
<CopyIcon />
|
<CopyIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
<ToolButton disabled={!hasSelection} onClick={handleRotate}>
|
<ToolButton disabled={!hasSelection} onClick={handleRotate}>
|
||||||
<Tooltip label="Rotate" id="TD-Tools-Rotate">
|
<Tooltip label={intl.formatMessage({ id: 'rotate' })} id="TD-Tools-Rotate">
|
||||||
<RotateCounterClockwiseIcon />
|
<RotateCounterClockwiseIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
<ToolButton disabled={!hasSelection} onClick={handleToggleLocked}>
|
<ToolButton disabled={!hasSelection} onClick={handleToggleLocked}>
|
||||||
<Tooltip label="Toggle Locked" kbd={`#L`} id="TD-Tools-Lock">
|
<Tooltip
|
||||||
|
label={intl.formatMessage({ id: isAllLocked ? 'unlock' : 'lock' })}
|
||||||
|
kbd={`#L`}
|
||||||
|
id="TD-Tools-Lock"
|
||||||
|
>
|
||||||
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
<ToolButton disabled={!hasSelection} onClick={handleToggleAspectRatio}>
|
<ToolButton disabled={!hasSelection} onClick={handleToggleAspectRatio}>
|
||||||
<Tooltip label="Toggle Aspect Ratio Lock" id="TD-Tools-AspectRatio">
|
<Tooltip
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: isAllAspectLocked ? 'unlock.aspect.ratio' : 'lock.aspect.ratio',
|
||||||
|
})}
|
||||||
|
id="TD-Tools-AspectRatio"
|
||||||
|
>
|
||||||
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
|
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
|
@ -212,34 +229,50 @@ export function ActionButton() {
|
||||||
disabled={!hasSelection || (!isAllGrouped && !hasMultipleSelection)}
|
disabled={!hasSelection || (!isAllGrouped && !hasMultipleSelection)}
|
||||||
onClick={handleGroup}
|
onClick={handleGroup}
|
||||||
>
|
>
|
||||||
<Tooltip label="Group" kbd={`#G`} id="TD-Tools-Group">
|
<Tooltip label={intl.formatMessage({ id: 'group' })} kbd={`#G`} id="TD-Tools-Group">
|
||||||
<GroupIcon />
|
<GroupIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
</ButtonsRow>
|
</ButtonsRow>
|
||||||
<ButtonsRow>
|
<ButtonsRow>
|
||||||
<ToolButton disabled={!hasSelection} onClick={handleMoveToBack}>
|
<ToolButton disabled={!hasSelection} onClick={handleMoveToBack}>
|
||||||
<Tooltip label="Move to Back" kbd={`#⇧[`} id="TD-Tools-PinBottom">
|
<Tooltip
|
||||||
|
label={intl.formatMessage({ id: 'move.to.back' })}
|
||||||
|
kbd={`#⇧[`}
|
||||||
|
id="TD-Tools-PinBottom"
|
||||||
|
>
|
||||||
<PinBottomIcon />
|
<PinBottomIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
<ToolButton disabled={!hasSelection} onClick={handleMoveBackward}>
|
<ToolButton disabled={!hasSelection} onClick={handleMoveBackward}>
|
||||||
<Tooltip label="Move Backward" kbd={`#[`} id="TD-Tools-ArrowDown">
|
<Tooltip
|
||||||
|
label={intl.formatMessage({ id: 'move.backward' })}
|
||||||
|
kbd={`#[`}
|
||||||
|
id="TD-Tools-ArrowDown"
|
||||||
|
>
|
||||||
<ArrowDownIcon />
|
<ArrowDownIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
<ToolButton disabled={!hasSelection} onClick={handleMoveForward}>
|
<ToolButton disabled={!hasSelection} onClick={handleMoveForward}>
|
||||||
<Tooltip label="Move Forward" kbd={`#]`} id="TD-Tools-ArrowUp">
|
<Tooltip
|
||||||
|
label={intl.formatMessage({ id: 'move.forward' })}
|
||||||
|
kbd={`#]`}
|
||||||
|
id="TD-Tools-ArrowUp"
|
||||||
|
>
|
||||||
<ArrowUpIcon />
|
<ArrowUpIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
<ToolButton disabled={!hasSelection} onClick={handleMoveToFront}>
|
<ToolButton disabled={!hasSelection} onClick={handleMoveToFront}>
|
||||||
<Tooltip label="Move to Front" kbd={`#⇧]`} id="TD-Tools-PinTop">
|
<Tooltip
|
||||||
|
label={intl.formatMessage({ id: 'move.to.front' })}
|
||||||
|
kbd={`#⇧]`}
|
||||||
|
id="TD-Tools-PinTop"
|
||||||
|
>
|
||||||
<PinTopIcon />
|
<PinTopIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
<ToolButton disabled={!hasSelection} onClick={handleResetAngle}>
|
<ToolButton disabled={!hasSelection} onClick={handleResetAngle}>
|
||||||
<Tooltip label="Reset Angle" id="TD-Tools-ResetAngle">
|
<Tooltip label={intl.formatMessage({ id: 'reset.angle' })} id="TD-Tools-ResetAngle">
|
||||||
<AngleIcon />
|
<AngleIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
|
|
|
@ -3,9 +3,11 @@ import { Tooltip } from '~components/Primitives/Tooltip'
|
||||||
import { useTldrawApp } from '~hooks'
|
import { useTldrawApp } from '~hooks'
|
||||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||||
import { TrashIcon } from '~components/Primitives/icons'
|
import { TrashIcon } from '~components/Primitives/icons'
|
||||||
|
import { useIntl } from 'react-intl'
|
||||||
|
|
||||||
export function DeleteButton() {
|
export function DeleteButton() {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const handleDelete = React.useCallback(() => {
|
const handleDelete = React.useCallback(() => {
|
||||||
app.delete()
|
app.delete()
|
||||||
|
@ -18,7 +20,7 @@ export function DeleteButton() {
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label="Delete" kbd="⌫" id="TD-Delete">
|
<Tooltip label={intl.formatMessage({ id: 'delete' })} kbd="⌫" id="TD-Delete">
|
||||||
<ToolButton variant="circle" disabled={!hasSelection} onSelect={handleDelete}>
|
<ToolButton variant="circle" disabled={!hasSelection} onSelect={handleDelete}>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import { useIntl } from 'react-intl'
|
||||||
import {
|
import {
|
||||||
ArrowTopRightIcon,
|
ArrowTopRightIcon,
|
||||||
CursorArrowIcon,
|
CursorArrowIcon,
|
||||||
|
@ -18,6 +19,7 @@ const toolLockedSelector = (s: TDSnapshot) => s.appState.isToolLocked
|
||||||
|
|
||||||
export const PrimaryTools = React.memo(function PrimaryTools() {
|
export const PrimaryTools = React.memo(function PrimaryTools() {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const activeTool = app.useStore(activeToolSelector)
|
const activeTool = app.useStore(activeToolSelector)
|
||||||
|
|
||||||
|
@ -51,7 +53,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
||||||
<Panel side="center" id="TD-PrimaryTools">
|
<Panel side="center" id="TD-PrimaryTools">
|
||||||
<ToolButtonWithTooltip
|
<ToolButtonWithTooltip
|
||||||
kbd={'1'}
|
kbd={'1'}
|
||||||
label={'select'}
|
label={intl.formatMessage({ id: 'select' })}
|
||||||
onClick={selectSelectTool}
|
onClick={selectSelectTool}
|
||||||
isActive={activeTool === 'select'}
|
isActive={activeTool === 'select'}
|
||||||
id="TD-PrimaryTools-CursorArrow"
|
id="TD-PrimaryTools-CursorArrow"
|
||||||
|
@ -60,7 +62,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
||||||
</ToolButtonWithTooltip>
|
</ToolButtonWithTooltip>
|
||||||
<ToolButtonWithTooltip
|
<ToolButtonWithTooltip
|
||||||
kbd={'2'}
|
kbd={'2'}
|
||||||
label={TDShapeType.Draw}
|
label={intl.formatMessage({ id: 'draw' })}
|
||||||
onClick={selectDrawTool}
|
onClick={selectDrawTool}
|
||||||
isActive={activeTool === TDShapeType.Draw}
|
isActive={activeTool === TDShapeType.Draw}
|
||||||
id="TD-PrimaryTools-Pencil"
|
id="TD-PrimaryTools-Pencil"
|
||||||
|
@ -69,7 +71,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
||||||
</ToolButtonWithTooltip>
|
</ToolButtonWithTooltip>
|
||||||
<ToolButtonWithTooltip
|
<ToolButtonWithTooltip
|
||||||
kbd={'3'}
|
kbd={'3'}
|
||||||
label={'eraser'}
|
label={intl.formatMessage({ id: 'eraser' })}
|
||||||
onClick={selectEraseTool}
|
onClick={selectEraseTool}
|
||||||
isActive={activeTool === 'erase'}
|
isActive={activeTool === 'erase'}
|
||||||
id="TD-PrimaryTools-Eraser"
|
id="TD-PrimaryTools-Eraser"
|
||||||
|
@ -79,7 +81,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
||||||
<ShapesMenu activeTool={activeTool} isToolLocked={isToolLocked} />
|
<ShapesMenu activeTool={activeTool} isToolLocked={isToolLocked} />
|
||||||
<ToolButtonWithTooltip
|
<ToolButtonWithTooltip
|
||||||
kbd={'8'}
|
kbd={'8'}
|
||||||
label={TDShapeType.Arrow}
|
label={intl.formatMessage({ id: 'arrow' })}
|
||||||
onClick={selectArrowTool}
|
onClick={selectArrowTool}
|
||||||
isLocked={isToolLocked}
|
isLocked={isToolLocked}
|
||||||
isActive={activeTool === TDShapeType.Arrow}
|
isActive={activeTool === TDShapeType.Arrow}
|
||||||
|
@ -89,7 +91,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
||||||
</ToolButtonWithTooltip>
|
</ToolButtonWithTooltip>
|
||||||
<ToolButtonWithTooltip
|
<ToolButtonWithTooltip
|
||||||
kbd={'9'}
|
kbd={'9'}
|
||||||
label={TDShapeType.Text}
|
label={intl.formatMessage({ id: 'text' })}
|
||||||
onClick={selectTextTool}
|
onClick={selectTextTool}
|
||||||
isLocked={isToolLocked}
|
isLocked={isToolLocked}
|
||||||
isActive={activeTool === TDShapeType.Text}
|
isActive={activeTool === TDShapeType.Text}
|
||||||
|
@ -99,7 +101,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
||||||
</ToolButtonWithTooltip>
|
</ToolButtonWithTooltip>
|
||||||
<ToolButtonWithTooltip
|
<ToolButtonWithTooltip
|
||||||
kbd={'0'}
|
kbd={'0'}
|
||||||
label={TDShapeType.Sticky}
|
label={intl.formatMessage({ id: 'sticky' })}
|
||||||
onClick={selectStickyTool}
|
onClick={selectStickyTool}
|
||||||
isActive={activeTool === TDShapeType.Sticky}
|
isActive={activeTool === TDShapeType.Sticky}
|
||||||
id="TD-PrimaryTools-Pencil2"
|
id="TD-PrimaryTools-Pencil2"
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { useTldrawApp } from '~hooks'
|
||||||
import { SquareIcon, CircleIcon, VercelLogoIcon } from '@radix-ui/react-icons'
|
import { SquareIcon, CircleIcon, VercelLogoIcon } from '@radix-ui/react-icons'
|
||||||
import { Tooltip } from '~components/Primitives/Tooltip'
|
import { Tooltip } from '~components/Primitives/Tooltip'
|
||||||
import { LineIcon } from '~components/Primitives/icons'
|
import { LineIcon } from '~components/Primitives/icons'
|
||||||
|
import { useIntl } from 'react-intl'
|
||||||
|
|
||||||
interface ShapesMenuProps {
|
interface ShapesMenuProps {
|
||||||
activeTool: TDToolType
|
activeTool: TDToolType
|
||||||
|
@ -44,6 +45,7 @@ export const ShapesMenu = React.memo(function ShapesMenu({
|
||||||
isToolLocked,
|
isToolLocked,
|
||||||
}: ShapesMenuProps) {
|
}: ShapesMenuProps) {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const status = app.useStore(statusSelector)
|
const status = app.useStore(statusSelector)
|
||||||
|
|
||||||
|
@ -92,7 +94,7 @@ export const ShapesMenu = React.memo(function ShapesMenu({
|
||||||
{shapeShapes.map((shape, i) => (
|
{shapeShapes.map((shape, i) => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key={shape}
|
key={shape}
|
||||||
label={shape[0].toUpperCase() + shape.slice(1)}
|
label={intl.formatMessage({ id: shape[0].toUpperCase() + shape.slice(1) })}
|
||||||
kbd={(4 + i).toString()}
|
kbd={(4 + i).toString()}
|
||||||
id={`TD-PrimaryTools-Shapes-${shape}`}
|
id={`TD-PrimaryTools-Shapes-${shape}`}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { ToolsPanel } from './ToolsPanel'
|
import { ToolsPanel } from './ToolsPanel'
|
||||||
import { renderWithContext } from '~test'
|
import { renderWithContext, renderWithIntlProvider } from '~test'
|
||||||
|
|
||||||
describe('tools panel', () => {
|
describe('tools panel', () => {
|
||||||
test('mounts component without crashing', () => {
|
test('mounts component without crashing', () => {
|
||||||
renderWithContext(<ToolsPanel onBlur={() => void null} />)
|
renderWithContext(renderWithIntlProvider(<ToolsPanel onBlur={() => void null} />))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import * as React from 'react'
|
||||||
|
import { useIntl } from 'react-intl'
|
||||||
|
import { DMCheckboxItem, DMSubMenu } from '~components/Primitives/DropdownMenu'
|
||||||
|
import { useTldrawApp } from '~hooks'
|
||||||
|
import { TDLanguage, TDSnapshot } from '~types'
|
||||||
|
|
||||||
|
const settingsSelector = (s: TDSnapshot) => s.settings
|
||||||
|
|
||||||
|
type ILang = {
|
||||||
|
label: string
|
||||||
|
code: TDLanguage
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LanguageMenu() {
|
||||||
|
const app = useTldrawApp()
|
||||||
|
const setting = app.useStore(settingsSelector)
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const languages: ILang[] = [
|
||||||
|
{ label: 'English', code: 'en' },
|
||||||
|
{ label: 'Français', code: 'fr' },
|
||||||
|
{ label: 'Italiano', code: 'it' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleChangeLanguage = React.useCallback(
|
||||||
|
(code: TDLanguage) => {
|
||||||
|
app.setSetting('language', code)
|
||||||
|
},
|
||||||
|
[app]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DMSubMenu label={intl.formatMessage({ id: 'language' })}>
|
||||||
|
{languages.map((language) => (
|
||||||
|
<DMCheckboxItem
|
||||||
|
checked={setting.language === language.code}
|
||||||
|
onCheckedChange={() => handleChangeLanguage(language.code)}
|
||||||
|
id={`TD-MenuItem-Language-${language}`}
|
||||||
|
>
|
||||||
|
{language.label}
|
||||||
|
</DMCheckboxItem>
|
||||||
|
))}
|
||||||
|
</DMSubMenu>
|
||||||
|
)
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ import { preventEvent } from '~components/preventEvent'
|
||||||
import { DiscordIcon } from '~components/Primitives/icons'
|
import { DiscordIcon } from '~components/Primitives/icons'
|
||||||
import { TDExportType, TDSnapshot } from '~types'
|
import { TDExportType, TDSnapshot } from '~types'
|
||||||
import { Divider } from '~components/Primitives/Divider'
|
import { Divider } from '~components/Primitives/Divider'
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl'
|
||||||
|
import { LanguageMenu } from '../LanguageMenu/LanguageMenu'
|
||||||
|
|
||||||
interface MenuProps {
|
interface MenuProps {
|
||||||
sponsor: boolean | undefined
|
sponsor: boolean | undefined
|
||||||
|
@ -39,6 +41,7 @@ const disableAssetsSelector = (s: TDSnapshot) => {
|
||||||
|
|
||||||
export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
|
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
|
||||||
|
|
||||||
|
@ -140,38 +143,40 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
</DMTriggerIcon>
|
</DMTriggerIcon>
|
||||||
<DMContent variant="menu" id="TD-Menu">
|
<DMContent variant="menu" id="TD-Menu">
|
||||||
{showFileMenu && (
|
{showFileMenu && (
|
||||||
<DMSubMenu label="File..." id="TD-MenuItem-File">
|
<DMSubMenu label={`${intl.formatMessage({ id: 'menu.file' })}...`} id="TD-MenuItem-File">
|
||||||
{app.callbacks.onNewProject && (
|
{app.callbacks.onNewProject && (
|
||||||
<DMItem onClick={onNewProject} kbd="#N" id="TD-MenuItem-File-New_Project">
|
<DMItem onClick={onNewProject} kbd="#N" id="TD-MenuItem-File-New_Project">
|
||||||
New Project
|
<FormattedMessage id="new.project" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
)}
|
)}
|
||||||
{app.callbacks.onOpenProject && (
|
{app.callbacks.onOpenProject && (
|
||||||
<DMItem onClick={onOpenProject} kbd="#O" id="TD-MenuItem-File-Open">
|
<DMItem onClick={onOpenProject} kbd="#O" id="TD-MenuItem-File-Open">
|
||||||
Open...
|
<FormattedMessage id="open" />
|
||||||
|
...
|
||||||
</DMItem>
|
</DMItem>
|
||||||
)}
|
)}
|
||||||
{app.callbacks.onSaveProject && (
|
{app.callbacks.onSaveProject && (
|
||||||
<DMItem onClick={onSaveProject} kbd="#S" id="TD-MenuItem-File-Save">
|
<DMItem onClick={onSaveProject} kbd="#S" id="TD-MenuItem-File-Save">
|
||||||
Save
|
<FormattedMessage id="save" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
)}
|
)}
|
||||||
{app.callbacks.onSaveProjectAs && (
|
{app.callbacks.onSaveProjectAs && (
|
||||||
<DMItem onClick={onSaveProjectAs} kbd="#⇧S" id="TD-MenuItem-File-Save_As">
|
<DMItem onClick={onSaveProjectAs} kbd="#⇧S" id="TD-MenuItem-File-Save_As">
|
||||||
Save As...
|
<FormattedMessage id="save.as" />
|
||||||
|
...
|
||||||
</DMItem>
|
</DMItem>
|
||||||
)}
|
)}
|
||||||
{!disableAssets && (
|
{!disableAssets && (
|
||||||
<>
|
<>
|
||||||
<Divider />
|
<Divider />
|
||||||
<DMItem onClick={handleUploadMedia} kbd="#U" id="TD-MenuItem-File-Upload_Media">
|
<DMItem onClick={handleUploadMedia} kbd="#U" id="TD-MenuItem-File-Upload_Media">
|
||||||
Upload Media
|
<FormattedMessage id="upload.media" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DMSubMenu>
|
</DMSubMenu>
|
||||||
)}
|
)}
|
||||||
<DMSubMenu label="Edit..." id="TD-MenuItem-Edit">
|
<DMSubMenu label={`${intl.formatMessage({ id: 'menu.edit' })}...`} id="TD-MenuItem-Edit">
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
onClick={app.undo}
|
onClick={app.undo}
|
||||||
|
@ -179,7 +184,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
kbd="#Z"
|
kbd="#Z"
|
||||||
id="TD-MenuItem-Edit-Undo"
|
id="TD-MenuItem-Edit-Undo"
|
||||||
>
|
>
|
||||||
Undo
|
<FormattedMessage id="undo" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
|
@ -188,7 +193,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
kbd="#⇧Z"
|
kbd="#⇧Z"
|
||||||
id="TD-MenuItem-Edit-Redo"
|
id="TD-MenuItem-Edit-Redo"
|
||||||
>
|
>
|
||||||
Redo
|
<FormattedMessage id="redo" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMDivider dir="ltr" />
|
<DMDivider dir="ltr" />
|
||||||
<DMItem
|
<DMItem
|
||||||
|
@ -198,7 +203,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
kbd="#X"
|
kbd="#X"
|
||||||
id="TD-MenuItem-Edit-Cut"
|
id="TD-MenuItem-Edit-Cut"
|
||||||
>
|
>
|
||||||
Cut
|
<FormattedMessage id="cut" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
|
@ -207,7 +212,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
kbd="#C"
|
kbd="#C"
|
||||||
id="TD-MenuItem-Edit-Copy"
|
id="TD-MenuItem-Edit-Copy"
|
||||||
>
|
>
|
||||||
Copy
|
<FormattedMessage id="copy" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
|
@ -215,10 +220,14 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
kbd="#V"
|
kbd="#V"
|
||||||
id="TD-MenuItem-Edit-Paste"
|
id="TD-MenuItem-Edit-Paste"
|
||||||
>
|
>
|
||||||
Paste
|
<FormattedMessage id="paste" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMDivider dir="ltr" />
|
<DMDivider dir="ltr" />
|
||||||
<DMSubMenu label="Copy as..." size="small" id="TD-MenuItem-Copy-As">
|
<DMSubMenu
|
||||||
|
label={`${intl.formatMessage({ id: 'copy.as' })}...`}
|
||||||
|
size="small"
|
||||||
|
id="TD-MenuItem-Copy-As"
|
||||||
|
>
|
||||||
<DMItem onClick={handleCopySVG} id="TD-MenuItem-Copy-as-SVG">
|
<DMItem onClick={handleCopySVG} id="TD-MenuItem-Copy-as-SVG">
|
||||||
SVG
|
SVG
|
||||||
</DMItem>
|
</DMItem>
|
||||||
|
@ -229,7 +238,11 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
JSON
|
JSON
|
||||||
</DMItem>
|
</DMItem>
|
||||||
</DMSubMenu>
|
</DMSubMenu>
|
||||||
<DMSubMenu label="Export as..." size="small" id="TD-MenuItem-Export">
|
<DMSubMenu
|
||||||
|
label={`${intl.formatMessage({ id: 'export.as' })}...`}
|
||||||
|
size="small"
|
||||||
|
id="TD-MenuItem-Export"
|
||||||
|
>
|
||||||
<DMItem onClick={handleExportSVG} id="TD-MenuItem-Export-SVG">
|
<DMItem onClick={handleExportSVG} id="TD-MenuItem-Export-SVG">
|
||||||
SVG
|
SVG
|
||||||
</DMItem>
|
</DMItem>
|
||||||
|
@ -254,7 +267,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
kbd="#A"
|
kbd="#A"
|
||||||
id="TD-MenuItem-Select_All"
|
id="TD-MenuItem-Select_All"
|
||||||
>
|
>
|
||||||
Select All
|
<FormattedMessage id="select.all" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
|
@ -262,21 +275,21 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
onClick={handleSelectNone}
|
onClick={handleSelectNone}
|
||||||
id="TD-MenuItem-Select_None"
|
id="TD-MenuItem-Select_None"
|
||||||
>
|
>
|
||||||
Select None
|
<FormattedMessage id="select.none" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMDivider dir="ltr" />
|
<DMDivider dir="ltr" />
|
||||||
<DMItem onSelect={handleDelete} disabled={!hasSelection} kbd="⌫" id="TD-MenuItem-Delete">
|
<DMItem onSelect={handleDelete} disabled={!hasSelection} kbd="⌫" id="TD-MenuItem-Delete">
|
||||||
Delete
|
<FormattedMessage id="delete" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
</DMSubMenu>
|
</DMSubMenu>
|
||||||
<DMSubMenu label="View" id="TD-MenuItem-Edit">
|
<DMSubMenu label={intl.formatMessage({ id: 'menu.view' })} id="TD-MenuItem-Edit">
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
onClick={app.zoomIn}
|
onClick={app.zoomIn}
|
||||||
kbd="#+"
|
kbd="#+"
|
||||||
id="TD-MenuItem-View-ZoomIn"
|
id="TD-MenuItem-View-ZoomIn"
|
||||||
>
|
>
|
||||||
Zoom In
|
<FormattedMessage id="zoom.in" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
|
@ -284,7 +297,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
kbd="#-"
|
kbd="#-"
|
||||||
id="TD-MenuItem-View-ZoomOut"
|
id="TD-MenuItem-View-ZoomOut"
|
||||||
>
|
>
|
||||||
Zoom Out
|
<FormattedMessage id="zoom.out" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
|
@ -292,7 +305,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
kbd="⇧+0"
|
kbd="⇧+0"
|
||||||
id="TD-MenuItem-View-ZoomTo100"
|
id="TD-MenuItem-View-ZoomTo100"
|
||||||
>
|
>
|
||||||
Zoom to 100%
|
<FormattedMessage id="zoom.to" /> 100%
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
|
@ -300,7 +313,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
kbd="⇧+1"
|
kbd="⇧+1"
|
||||||
id="TD-MenuItem-View-ZoomToFit"
|
id="TD-MenuItem-View-ZoomToFit"
|
||||||
>
|
>
|
||||||
Zoom to Fit
|
<FormattedMessage id="zoom.to.fit" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
|
@ -308,12 +321,14 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
kbd="⇧+2"
|
kbd="⇧+2"
|
||||||
id="TD-MenuItem-View-ZoomToSelection"
|
id="TD-MenuItem-View-ZoomToSelection"
|
||||||
>
|
>
|
||||||
Zoom to Selection
|
<FormattedMessage id="zoom.to.selection" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
</DMSubMenu>
|
</DMSubMenu>
|
||||||
<DMDivider dir="ltr" />
|
<DMDivider dir="ltr" />
|
||||||
<PreferencesMenu />
|
<PreferencesMenu />
|
||||||
<DMDivider dir="ltr" />
|
<DMDivider dir="ltr" />
|
||||||
|
<LanguageMenu />
|
||||||
|
<DMDivider dir="ltr" />
|
||||||
<a href="https://github.com/Tldraw/Tldraw" target="_blank" rel="nofollow">
|
<a href="https://github.com/Tldraw/Tldraw" target="_blank" rel="nofollow">
|
||||||
<DMItem id="TD-MenuItem-Github">
|
<DMItem id="TD-MenuItem-Github">
|
||||||
GitHub
|
GitHub
|
||||||
|
@ -341,7 +356,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
{sponsor === false && (
|
{sponsor === false && (
|
||||||
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
|
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
|
||||||
<DMItem isSponsor id="TD-MenuItem-Become_a_Sponsor">
|
<DMItem isSponsor id="TD-MenuItem-Become_a_Sponsor">
|
||||||
Become a Sponsor{' '}
|
<FormattedMessage id="become.a.sponsor" />{' '}
|
||||||
<SmallIcon>
|
<SmallIcon>
|
||||||
<HeartIcon />
|
<HeartIcon />
|
||||||
</SmallIcon>
|
</SmallIcon>
|
||||||
|
@ -351,7 +366,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
{sponsor === true && (
|
{sponsor === true && (
|
||||||
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
|
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
|
||||||
<DMItem id="TD-MenuItem-is_a_Sponsor">
|
<DMItem id="TD-MenuItem-is_a_Sponsor">
|
||||||
Sponsored!
|
<FormattedMessage id="sponsored" />!
|
||||||
<SmallIcon>
|
<SmallIcon>
|
||||||
<HeartFilledIcon />
|
<HeartFilledIcon />
|
||||||
</SmallIcon>
|
</SmallIcon>
|
||||||
|
@ -363,12 +378,12 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
<DMDivider dir="ltr" />{' '}
|
<DMDivider dir="ltr" />{' '}
|
||||||
{app.callbacks.onSignIn && (
|
{app.callbacks.onSignIn && (
|
||||||
<DMItem onSelect={handleSignIn} id="TD-MenuItem-Sign_in">
|
<DMItem onSelect={handleSignIn} id="TD-MenuItem-Sign_in">
|
||||||
Sign In
|
<FormattedMessage id="menu.sign.in" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
)}
|
)}
|
||||||
{app.callbacks.onSignOut && (
|
{app.callbacks.onSignOut && (
|
||||||
<DMItem onSelect={handleSignOut} id="TD-MenuItem-Sign_out">
|
<DMItem onSelect={handleSignOut} id="TD-MenuItem-Sign_out">
|
||||||
Sign Out
|
<FormattedMessage id="menu.sign.out" />
|
||||||
<SmallIcon>
|
<SmallIcon>
|
||||||
<ExitIcon />
|
<ExitIcon />
|
||||||
</SmallIcon>
|
</SmallIcon>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { MultiplayerIcon } from '~components/Primitives/icons'
|
||||||
import { TDAssetType, TDSnapshot } from '~types'
|
import { TDAssetType, TDSnapshot } from '~types'
|
||||||
import { TLDR } from '~state/TLDR'
|
import { TLDR } from '~state/TLDR'
|
||||||
import { Utils } from '@tldraw/core'
|
import { Utils } from '@tldraw/core'
|
||||||
|
import { FormattedMessage } from 'react-intl'
|
||||||
|
|
||||||
const roomSelector = (state: TDSnapshot) => state.room
|
const roomSelector = (state: TDSnapshot) => state.room
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
|
||||||
pageId: app.currentPageId,
|
pageId: app.currentPageId,
|
||||||
document: nextDocument,
|
document: nextDocument,
|
||||||
}),
|
}),
|
||||||
}).then(d => d.json())
|
}).then((d) => d.json())
|
||||||
|
|
||||||
if (result?.url) {
|
if (result?.url) {
|
||||||
window.location.href = result.url
|
window.location.href = result.url
|
||||||
|
@ -99,20 +100,23 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
|
||||||
</DMTriggerIcon>
|
</DMTriggerIcon>
|
||||||
<DMContent variant="menu" align="start" id="TD-MultiplayerMenu">
|
<DMContent variant="menu" align="start" id="TD-MultiplayerMenu">
|
||||||
<DMItem id="TD-Multiplayer-CopyInviteLink" onClick={handleCopySelect} disabled={!room}>
|
<DMItem id="TD-Multiplayer-CopyInviteLink" onClick={handleCopySelect} disabled={!room}>
|
||||||
Copy Invite Link<SmallIcon>{copied ? <CheckIcon /> : <ClipboardIcon />}</SmallIcon>
|
<FormattedMessage id="copy.invite.link" />
|
||||||
|
<SmallIcon>{copied ? <CheckIcon /> : <ClipboardIcon />}</SmallIcon>
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMDivider id="TD-Multiplayer-CopyInviteLinkDivider" />
|
<DMDivider id="TD-Multiplayer-CopyInviteLinkDivider" />
|
||||||
<DMItem
|
<DMItem
|
||||||
id="TD-Multiplayer-CreateMultiplayerProject"
|
id="TD-Multiplayer-CreateMultiplayerProject"
|
||||||
onClick={handleCreateMultiplayerProject}
|
onClick={handleCreateMultiplayerProject}
|
||||||
>
|
>
|
||||||
<a href="https://tldraw.com/r">Create a Multiplayer Project</a>
|
<a href="https://tldraw.com/r">
|
||||||
|
<FormattedMessage id="create.multiplayer.project" />
|
||||||
|
</a>
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
id="TD-Multiplayer-CopyToMultiplayerProject"
|
id="TD-Multiplayer-CopyToMultiplayerProject"
|
||||||
onClick={handleCopyToMultiplayerProject}
|
onClick={handleCopyToMultiplayerProject}
|
||||||
>
|
>
|
||||||
Copy to Multiplayer Project
|
<FormattedMessage id="copy.multiplayer.project" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
</DMContent>
|
</DMContent>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { DMContent, DMDivider } from '~components/Primitives/DropdownMenu'
|
||||||
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||||
import { RowButton } from '~components/Primitives/RowButton'
|
import { RowButton } from '~components/Primitives/RowButton'
|
||||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||||
|
import { FormattedMessage } from 'react-intl'
|
||||||
|
|
||||||
const sortedSelector = (s: TDSnapshot) =>
|
const sortedSelector = (s: TDSnapshot) =>
|
||||||
Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))
|
Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))
|
||||||
|
@ -102,7 +103,9 @@ function PageMenuContent({ onClose }: { onClose: () => void }) {
|
||||||
<DMDivider />
|
<DMDivider />
|
||||||
<DropdownMenu.Item onSelect={handleCreatePage} asChild>
|
<DropdownMenu.Item onSelect={handleCreatePage} asChild>
|
||||||
<RowButton>
|
<RowButton>
|
||||||
<span>Create Page</span>
|
<span>
|
||||||
|
<FormattedMessage id="create.page" />
|
||||||
|
</span>
|
||||||
<SmallIcon>
|
<SmallIcon>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</SmallIcon>
|
</SmallIcon>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { IconButton } from '~components/Primitives/IconButton/IconButton'
|
||||||
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||||
import { breakpoints } from '~components/breakpoints'
|
import { breakpoints } from '~components/breakpoints'
|
||||||
import { TextField } from '~components/Primitives/TextField'
|
import { TextField } from '~components/Primitives/TextField'
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl'
|
||||||
|
|
||||||
const canDeleteSelector = (s: TDSnapshot) => {
|
const canDeleteSelector = (s: TDSnapshot) => {
|
||||||
return Object.keys(s.document.pages).length > 1
|
return Object.keys(s.document.pages).length > 1
|
||||||
|
@ -23,6 +24,7 @@ interface PageOptionsDialogProps {
|
||||||
|
|
||||||
export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogProps) {
|
export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogProps) {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = React.useState(false)
|
const [isOpen, setIsOpen] = React.useState(false)
|
||||||
const [pageName, setPageName] = React.useState(page.name || 'Page')
|
const [pageName, setPageName] = React.useState(page.name || 'Page')
|
||||||
|
@ -94,19 +96,23 @@ export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogPr
|
||||||
<StyledDialogOverlay onPointerDown={close} />
|
<StyledDialogOverlay onPointerDown={close} />
|
||||||
<StyledDialogContent dir="ltr" onKeyDown={stopPropagation} onKeyUp={stopPropagation}>
|
<StyledDialogContent dir="ltr" onKeyDown={stopPropagation} onKeyUp={stopPropagation}>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="Page name"
|
placeholder={intl.formatMessage({ id: 'page.name' })}
|
||||||
value={pageName}
|
value={pageName}
|
||||||
onChange={handleRename}
|
onChange={handleRename}
|
||||||
icon={<Pencil1Icon />}
|
icon={<Pencil1Icon />}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<DialogAction onSelect={handleDuplicate}>Duplicate</DialogAction>
|
<DialogAction onSelect={handleDuplicate}>
|
||||||
|
<FormattedMessage id="duplicate" />
|
||||||
|
</DialogAction>
|
||||||
<DialogAction disabled={!canDelete} onSelect={handleDelete}>
|
<DialogAction disabled={!canDelete} onSelect={handleDelete}>
|
||||||
Delete
|
<FormattedMessage id="delete" />
|
||||||
</DialogAction>
|
</DialogAction>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Dialog.Cancel asChild>
|
<Dialog.Cancel asChild>
|
||||||
<RowButton>Cancel</RowButton>
|
<RowButton>
|
||||||
|
<FormattedMessage id="cancel" />
|
||||||
|
</RowButton>
|
||||||
</Dialog.Cancel>
|
</Dialog.Cancel>
|
||||||
</StyledDialogContent>
|
</StyledDialogContent>
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl'
|
||||||
import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/Primitives/DropdownMenu'
|
import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/Primitives/DropdownMenu'
|
||||||
import { useTldrawApp } from '~hooks'
|
import { useTldrawApp } from '~hooks'
|
||||||
import { TDSnapshot } from '~types'
|
import { TDSnapshot } from '~types'
|
||||||
|
@ -7,58 +8,59 @@ const settingsSelector = (s: TDSnapshot) => s.settings
|
||||||
|
|
||||||
export function PreferencesMenu() {
|
export function PreferencesMenu() {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const settings = app.useStore(settingsSelector)
|
const settings = app.useStore(settingsSelector)
|
||||||
|
|
||||||
const toggleDebugMode = React.useCallback(() => {
|
const toggleDebugMode = React.useCallback(() => {
|
||||||
app.setSetting('isDebugMode', v => !v)
|
app.setSetting('isDebugMode', (v) => !v)
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
const toggleDarkMode = React.useCallback(() => {
|
const toggleDarkMode = React.useCallback(() => {
|
||||||
app.setSetting('isDarkMode', v => !v)
|
app.setSetting('isDarkMode', (v) => !v)
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
const toggleFocusMode = React.useCallback(() => {
|
const toggleFocusMode = React.useCallback(() => {
|
||||||
app.setSetting('isFocusMode', v => !v)
|
app.setSetting('isFocusMode', (v) => !v)
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
const toggleRotateHandle = React.useCallback(() => {
|
const toggleRotateHandle = React.useCallback(() => {
|
||||||
app.setSetting('showRotateHandles', v => !v)
|
app.setSetting('showRotateHandles', (v) => !v)
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
const toggleGrid = React.useCallback(() => {
|
const toggleGrid = React.useCallback(() => {
|
||||||
app.setSetting('showGrid', v => !v)
|
app.setSetting('showGrid', (v) => !v)
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
const toggleBoundShapesHandle = React.useCallback(() => {
|
const toggleBoundShapesHandle = React.useCallback(() => {
|
||||||
app.setSetting('showBindingHandles', v => !v)
|
app.setSetting('showBindingHandles', (v) => !v)
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
const toggleisSnapping = React.useCallback(() => {
|
const toggleisSnapping = React.useCallback(() => {
|
||||||
app.setSetting('isSnapping', v => !v)
|
app.setSetting('isSnapping', (v) => !v)
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
const toggleKeepStyleMenuOpen = React.useCallback(() => {
|
const toggleKeepStyleMenuOpen = React.useCallback(() => {
|
||||||
app.setSetting('keepStyleMenuOpen', v => !v)
|
app.setSetting('keepStyleMenuOpen', (v) => !v)
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
const toggleCloneControls = React.useCallback(() => {
|
const toggleCloneControls = React.useCallback(() => {
|
||||||
app.setSetting('showCloneHandles', v => !v)
|
app.setSetting('showCloneHandles', (v) => !v)
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
const toggleCadSelectMode = React.useCallback(() => {
|
const toggleCadSelectMode = React.useCallback(() => {
|
||||||
app.setSetting('isCadSelectMode', v => !v)
|
app.setSetting('isCadSelectMode', (v) => !v)
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DMSubMenu label="Preferences" id="TD-MenuItem-Preferences">
|
<DMSubMenu label={intl.formatMessage({ id: 'menu.preferences' })} id="TD-MenuItem-Preferences">
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
checked={settings.isDarkMode}
|
checked={settings.isDarkMode}
|
||||||
onCheckedChange={toggleDarkMode}
|
onCheckedChange={toggleDarkMode}
|
||||||
kbd="#⇧D"
|
kbd="#⇧D"
|
||||||
id="TD-MenuItem-Preferences-Dark_Mode"
|
id="TD-MenuItem-Preferences-Dark_Mode"
|
||||||
>
|
>
|
||||||
Dark Mode
|
<FormattedMessage id="preferences.dark.mode" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
checked={settings.isFocusMode}
|
checked={settings.isFocusMode}
|
||||||
|
@ -66,14 +68,14 @@ export function PreferencesMenu() {
|
||||||
kbd="#."
|
kbd="#."
|
||||||
id="TD-MenuItem-Preferences-Focus_Mode"
|
id="TD-MenuItem-Preferences-Focus_Mode"
|
||||||
>
|
>
|
||||||
Focus Mode
|
<FormattedMessage id="preferences.focus.mode" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
checked={settings.isDebugMode}
|
checked={settings.isDebugMode}
|
||||||
onCheckedChange={toggleDebugMode}
|
onCheckedChange={toggleDebugMode}
|
||||||
id="TD-MenuItem-Preferences-Debug_Mode"
|
id="TD-MenuItem-Preferences-Debug_Mode"
|
||||||
>
|
>
|
||||||
Debug Mode
|
<FormattedMessage id="preferences.debug.mode" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMDivider />
|
<DMDivider />
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
|
@ -82,49 +84,49 @@ export function PreferencesMenu() {
|
||||||
kbd="#⇧G"
|
kbd="#⇧G"
|
||||||
id="TD-MenuItem-Preferences-Grid"
|
id="TD-MenuItem-Preferences-Grid"
|
||||||
>
|
>
|
||||||
Show Grid
|
<FormattedMessage id="preferences.show.grid" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
checked={settings.isCadSelectMode}
|
checked={settings.isCadSelectMode}
|
||||||
onCheckedChange={toggleCadSelectMode}
|
onCheckedChange={toggleCadSelectMode}
|
||||||
id="TD-MenuItem-Preferences-Cad_Selection"
|
id="TD-MenuItem-Preferences-Cad_Selection"
|
||||||
>
|
>
|
||||||
Use CAD Selection
|
<FormattedMessage id="preferences.use.cad.selection" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
checked={settings.keepStyleMenuOpen}
|
checked={settings.keepStyleMenuOpen}
|
||||||
onCheckedChange={toggleKeepStyleMenuOpen}
|
onCheckedChange={toggleKeepStyleMenuOpen}
|
||||||
id="TD-MenuItem-Preferences-Style_menu"
|
id="TD-MenuItem-Preferences-Style_menu"
|
||||||
>
|
>
|
||||||
Keep Style Menu Open
|
<FormattedMessage id="preferences.keep.stylemenu.open" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
checked={settings.isSnapping}
|
checked={settings.isSnapping}
|
||||||
onCheckedChange={toggleisSnapping}
|
onCheckedChange={toggleisSnapping}
|
||||||
id="TD-MenuItem-Preferences-Always_Show_Snaps"
|
id="TD-MenuItem-Preferences-Always_Show_Snaps"
|
||||||
>
|
>
|
||||||
Always Show Snaps
|
<FormattedMessage id="preferences.always.show.snaps" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
checked={settings.showRotateHandles}
|
checked={settings.showRotateHandles}
|
||||||
onCheckedChange={toggleRotateHandle}
|
onCheckedChange={toggleRotateHandle}
|
||||||
id="TD-MenuItem-Preferences-Rotate_Handles"
|
id="TD-MenuItem-Preferences-Rotate_Handles"
|
||||||
>
|
>
|
||||||
Rotate Handles
|
<FormattedMessage id="preferences.rotate.handles" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
checked={settings.showBindingHandles}
|
checked={settings.showBindingHandles}
|
||||||
onCheckedChange={toggleBoundShapesHandle}
|
onCheckedChange={toggleBoundShapesHandle}
|
||||||
id="TD-MenuItem-Preferences-Binding_Handles"
|
id="TD-MenuItem-Preferences-Binding_Handles"
|
||||||
>
|
>
|
||||||
Binding Handles
|
<FormattedMessage id="preferences.binding.handles" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
checked={settings.showCloneHandles}
|
checked={settings.showCloneHandles}
|
||||||
onCheckedChange={toggleCloneControls}
|
onCheckedChange={toggleCloneControls}
|
||||||
id="TD-MenuItem-Preferences-Clone_Handles"
|
id="TD-MenuItem-Preferences-Clone_Handles"
|
||||||
>
|
>
|
||||||
Clone Handles
|
<FormattedMessage id="preferences.clone.handles" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
</DMSubMenu>
|
</DMSubMenu>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { strokes, fills, defaultTextStyle } from '~state/shapes/shared/shape-styles'
|
import { strokes, fills, defaultTextStyle } from '~state/shapes/shared/shape-styles'
|
||||||
|
import { FormattedMessage } from 'react-intl'
|
||||||
import { useTldrawApp } from '~hooks'
|
import { useTldrawApp } from '~hooks'
|
||||||
import {
|
import {
|
||||||
DMCheckboxItem,
|
DMCheckboxItem,
|
||||||
|
@ -135,9 +136,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
||||||
} else {
|
} else {
|
||||||
const overrides = new Set<string>([])
|
const overrides = new Set<string>([])
|
||||||
app.selectedIds
|
app.selectedIds
|
||||||
.map(id => page.shapes[id])
|
.map((id) => page.shapes[id])
|
||||||
.forEach(shape => {
|
.forEach((shape) => {
|
||||||
STYLE_KEYS.forEach(key => {
|
STYLE_KEYS.forEach((key) => {
|
||||||
if (overrides.has(key)) return
|
if (overrides.has(key)) return
|
||||||
if (commonStyle[key] === undefined) {
|
if (commonStyle[key] === undefined) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
@ -201,7 +202,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
||||||
>
|
>
|
||||||
<DropdownMenu.Trigger asChild id="TD-Styles">
|
<DropdownMenu.Trigger asChild id="TD-Styles">
|
||||||
<ToolButton variant="text">
|
<ToolButton variant="text">
|
||||||
Styles
|
<FormattedMessage id="styles" />
|
||||||
<OverlapIcons
|
<OverlapIcons
|
||||||
style={{
|
style={{
|
||||||
color: strokes[theme][displayedStyle.color as ColorStyle],
|
color: strokes[theme][displayedStyle.color as ColorStyle],
|
||||||
|
@ -220,7 +221,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DMContent>
|
<DMContent>
|
||||||
<StyledRow variant="tall" id="TD-Styles-Color-Container">
|
<StyledRow variant="tall" id="TD-Styles-Color-Container">
|
||||||
<span>Color</span>
|
<span>
|
||||||
|
<FormattedMessage id="style.menu.color" />
|
||||||
|
</span>
|
||||||
<ColorGrid>
|
<ColorGrid>
|
||||||
{Object.keys(strokes.light).map((style: string) => (
|
{Object.keys(strokes.light).map((style: string) => (
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
|
@ -253,12 +256,12 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
||||||
onCheckedChange={handleToggleFilled}
|
onCheckedChange={handleToggleFilled}
|
||||||
id="TD-Styles-Fill"
|
id="TD-Styles-Fill"
|
||||||
>
|
>
|
||||||
Fill
|
<FormattedMessage id="style.menu.fill" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<StyledRow id="TD-Styles-Dash-Container">
|
<StyledRow id="TD-Styles-Dash-Container">
|
||||||
Dash
|
<FormattedMessage id="style.menu.dash" />
|
||||||
<StyledGroup dir="ltr" value={displayedStyle.dash} onValueChange={handleDashChange}>
|
<StyledGroup dir="ltr" value={displayedStyle.dash} onValueChange={handleDashChange}>
|
||||||
{Object.values(DashStyle).map(style => (
|
{Object.values(DashStyle).map((style) => (
|
||||||
<DMRadioItem
|
<DMRadioItem
|
||||||
key={style}
|
key={style}
|
||||||
isActive={style === displayedStyle.dash}
|
isActive={style === displayedStyle.dash}
|
||||||
|
@ -273,9 +276,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
||||||
</StyledGroup>
|
</StyledGroup>
|
||||||
</StyledRow>
|
</StyledRow>
|
||||||
<StyledRow id="TD-Styles-Size-Container">
|
<StyledRow id="TD-Styles-Size-Container">
|
||||||
Size
|
<FormattedMessage id="style.menu.size" />
|
||||||
<StyledGroup dir="ltr" value={displayedStyle.size} onValueChange={handleSizeChange}>
|
<StyledGroup dir="ltr" value={displayedStyle.size} onValueChange={handleSizeChange}>
|
||||||
{Object.values(SizeStyle).map(sizeStyle => (
|
{Object.values(SizeStyle).map((sizeStyle) => (
|
||||||
<DMRadioItem
|
<DMRadioItem
|
||||||
key={sizeStyle}
|
key={sizeStyle}
|
||||||
isActive={sizeStyle === displayedStyle.size}
|
isActive={sizeStyle === displayedStyle.size}
|
||||||
|
@ -293,9 +296,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
||||||
<>
|
<>
|
||||||
<Divider />
|
<Divider />
|
||||||
<StyledRow id="TD-Styles-Font-Container">
|
<StyledRow id="TD-Styles-Font-Container">
|
||||||
Font
|
<FormattedMessage id="style.menu.font" />
|
||||||
<StyledGroup dir="ltr" value={displayedStyle.font} onValueChange={handleFontChange}>
|
<StyledGroup dir="ltr" value={displayedStyle.font} onValueChange={handleFontChange}>
|
||||||
{Object.values(FontStyle).map(fontStyle => (
|
{Object.values(FontStyle).map((fontStyle) => (
|
||||||
<DMRadioItem
|
<DMRadioItem
|
||||||
key={fontStyle}
|
key={fontStyle}
|
||||||
isActive={fontStyle === displayedStyle.font}
|
isActive={fontStyle === displayedStyle.font}
|
||||||
|
@ -311,13 +314,13 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
||||||
</StyledRow>
|
</StyledRow>
|
||||||
{options === 'text' && (
|
{options === 'text' && (
|
||||||
<StyledRow id="TD-Styles-Align-Container">
|
<StyledRow id="TD-Styles-Align-Container">
|
||||||
Align
|
<FormattedMessage id="style.menu.align" />
|
||||||
<StyledGroup
|
<StyledGroup
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
value={displayedStyle.textAlign}
|
value={displayedStyle.textAlign}
|
||||||
onValueChange={handleTextAlignChange}
|
onValueChange={handleTextAlignChange}
|
||||||
>
|
>
|
||||||
{Object.values(AlignStyle).map(style => (
|
{Object.values(AlignStyle).map((style) => (
|
||||||
<DMRadioItem
|
<DMRadioItem
|
||||||
key={style}
|
key={style}
|
||||||
isActive={style === displayedStyle.textAlign}
|
isActive={style === displayedStyle.textAlign}
|
||||||
|
@ -341,7 +344,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
||||||
onCheckedChange={handleToggleKeepOpen}
|
onCheckedChange={handleToggleKeepOpen}
|
||||||
id="TD-Styles-Keep-Open"
|
id="TD-Styles-Keep-Open"
|
||||||
>
|
>
|
||||||
Keep Open
|
<FormattedMessage id="style.menu.keep.open" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
</DMContent>
|
</DMContent>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { DMItem, DMContent } from '~components/Primitives/DropdownMenu'
|
import { DMItem, DMContent } from '~components/Primitives/DropdownMenu'
|
||||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||||
import { preventEvent } from '~components/preventEvent'
|
import { preventEvent } from '~components/preventEvent'
|
||||||
|
import { FormattedMessage } from 'react-intl'
|
||||||
|
|
||||||
const zoomSelector = (s: TDSnapshot) => s.document.pageStates[s.appState.currentPageId].camera.zoom
|
const zoomSelector = (s: TDSnapshot) => s.document.pageStates[s.appState.currentPageId].camera.zoom
|
||||||
|
|
||||||
|
@ -23,16 +24,16 @@ export const ZoomMenu = React.memo(function ZoomMenu() {
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DMContent align="end">
|
<DMContent align="end">
|
||||||
<DMItem onSelect={preventEvent} onClick={app.zoomIn} kbd="#+" id="TD-Zoom-Zoom_In">
|
<DMItem onSelect={preventEvent} onClick={app.zoomIn} kbd="#+" id="TD-Zoom-Zoom_In">
|
||||||
Zoom In
|
<FormattedMessage id="zoom.in" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem onSelect={preventEvent} onClick={app.zoomOut} kbd="#−" id="TD-Zoom-Zoom_Out">
|
<DMItem onSelect={preventEvent} onClick={app.zoomOut} kbd="#−" id="TD-Zoom-Zoom_Out">
|
||||||
Zoom Out
|
<FormattedMessage id="zoom.out" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem onSelect={preventEvent} onClick={app.resetZoom} kbd="⇧0" id="TD-Zoom-Zoom_To_100%">
|
<DMItem onSelect={preventEvent} onClick={app.resetZoom} kbd="⇧0" id="TD-Zoom-Zoom_To_100%">
|
||||||
To 100%
|
<FormattedMessage id="to" /> 100%
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem onSelect={preventEvent} onClick={app.zoomToFit} kbd="⇧1" id="TD-Zoom-To_Fit">
|
<DMItem onSelect={preventEvent} onClick={app.zoomToFit} kbd="⇧1" id="TD-Zoom-To_Fit">
|
||||||
To Fit
|
<FormattedMessage id="to.fit" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
|
@ -40,7 +41,7 @@ export const ZoomMenu = React.memo(function ZoomMenu() {
|
||||||
kbd="⇧2"
|
kbd="⇧2"
|
||||||
id="TD-Zoom-To_Selection"
|
id="TD-Zoom-To_Selection"
|
||||||
>
|
>
|
||||||
To Selection
|
<FormattedMessage id="to.selection" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
</DMContent>
|
</DMContent>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
|
|
|
@ -22,17 +22,17 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
if (!canHandleEvent(true)) return
|
if (!canHandleEvent(true)) return
|
||||||
|
|
||||||
if (app.readOnly) {
|
if (app.readOnly) {
|
||||||
app.copy(undefined, undefined, e)
|
app.copy(undefined, e)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
app.cut(undefined, undefined, e)
|
app.cut(undefined, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCopy = (e: ClipboardEvent) => {
|
const handleCopy = (e: ClipboardEvent) => {
|
||||||
if (!canHandleEvent(true)) return
|
if (!canHandleEvent(true)) return
|
||||||
|
|
||||||
app.copy(undefined, undefined, e)
|
app.copy(undefined, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePaste = (e: ClipboardEvent) => {
|
const handlePaste = (e: ClipboardEvent) => {
|
||||||
|
@ -159,7 +159,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+shift+d,⌘+shift+d',
|
'ctrl+shift+d,⌘+shift+d',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent(true)) return
|
if (!canHandleEvent(true)) return
|
||||||
app.toggleDarkMode()
|
app.toggleDarkMode()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -192,17 +192,12 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
// File System
|
// File System
|
||||||
|
|
||||||
const {
|
const { onNewProject, onOpenProject, onSaveProject, onSaveProjectAs, onOpenMedia } =
|
||||||
onNewProject,
|
useFileSystemHandlers()
|
||||||
onOpenProject,
|
|
||||||
onSaveProject,
|
|
||||||
onSaveProjectAs,
|
|
||||||
onOpenMedia,
|
|
||||||
} = useFileSystemHandlers()
|
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+n,⌘+n',
|
'ctrl+n,⌘+n',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
|
|
||||||
onNewProject(e)
|
onNewProject(e)
|
||||||
|
@ -212,7 +207,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
)
|
)
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+s,⌘+s',
|
'ctrl+s,⌘+s',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
|
|
||||||
onSaveProject(e)
|
onSaveProject(e)
|
||||||
|
@ -223,7 +218,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+shift+s,⌘+shift+s',
|
'ctrl+shift+s,⌘+shift+s',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
|
|
||||||
onSaveProjectAs(e)
|
onSaveProjectAs(e)
|
||||||
|
@ -233,7 +228,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
)
|
)
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+o,⌘+o',
|
'ctrl+o,⌘+o',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
|
|
||||||
onOpenProject(e)
|
onOpenProject(e)
|
||||||
|
@ -243,7 +238,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
)
|
)
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+u,⌘+u',
|
'ctrl+u,⌘+u',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
onOpenMedia(e)
|
onOpenMedia(e)
|
||||||
},
|
},
|
||||||
|
@ -311,7 +306,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+=,⌘+=,ctrl+num_subtract,⌘+num_subtract',
|
'ctrl+=,⌘+=,ctrl+num_subtract,⌘+num_subtract',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent(true)) return
|
if (!canHandleEvent(true)) return
|
||||||
app.zoomIn()
|
app.zoomIn()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -322,7 +317,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+-,⌘+-,ctrl+num_add,⌘+num_add',
|
'ctrl+-,⌘+-,ctrl+num_add,⌘+num_add',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent(true)) return
|
if (!canHandleEvent(true)) return
|
||||||
|
|
||||||
app.zoomOut()
|
app.zoomOut()
|
||||||
|
@ -366,7 +361,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+d,⌘+d',
|
'ctrl+d,⌘+d',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
|
|
||||||
app.duplicate()
|
app.duplicate()
|
||||||
|
@ -541,7 +536,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'⌘+shift+c,ctrl+shift+c',
|
'⌘+shift+c,ctrl+shift+c',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
|
|
||||||
app.copySvg()
|
app.copySvg()
|
||||||
|
@ -576,7 +571,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'⌘+g,ctrl+g',
|
'⌘+g,ctrl+g',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
|
|
||||||
app.group()
|
app.group()
|
||||||
|
@ -588,7 +583,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'⌘+shift+g,ctrl+shift+g',
|
'⌘+shift+g,ctrl+shift+g',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
|
|
||||||
app.ungroup()
|
app.ungroup()
|
||||||
|
@ -642,7 +637,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'ctrl+shift+backspace,⌘+shift+backspace',
|
'ctrl+shift+backspace,⌘+shift+backspace',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent()) return
|
if (!canHandleEvent()) return
|
||||||
if (app.settings.isDebugMode) {
|
if (app.settings.isDebugMode) {
|
||||||
app.resetDocument()
|
app.resetDocument()
|
||||||
|
@ -657,7 +652,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'alt+command+l,alt+ctrl+l',
|
'alt+command+l,alt+ctrl+l',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent(true)) return
|
if (!canHandleEvent(true)) return
|
||||||
app.style({ textAlign: AlignStyle.Start })
|
app.style({ textAlign: AlignStyle.Start })
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -668,7 +663,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'alt+command+t,alt+ctrl+t',
|
'alt+command+t,alt+ctrl+t',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent(true)) return
|
if (!canHandleEvent(true)) return
|
||||||
app.style({ textAlign: AlignStyle.Middle })
|
app.style({ textAlign: AlignStyle.Middle })
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -679,7 +674,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'alt+command+r,alt+ctrl+r',
|
'alt+command+r,alt+ctrl+r',
|
||||||
e => {
|
(e) => {
|
||||||
if (!canHandleEvent(true)) return
|
if (!canHandleEvent(true)) return
|
||||||
app.style({ textAlign: AlignStyle.End })
|
app.style({ textAlign: AlignStyle.End })
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
|
@ -147,7 +147,7 @@ export class StateManager<T extends Record<string, any>> {
|
||||||
*/
|
*/
|
||||||
private applyPatch = (patch: Patch<T>, id?: string) => {
|
private applyPatch = (patch: Patch<T>, id?: string) => {
|
||||||
const prev = this._state
|
const prev = this._state
|
||||||
const next = Utils.deepMerge(this._state, patch)
|
const next = Utils.deepMerge(this._state, patch as any)
|
||||||
const final = this.cleanup(next, prev, patch, id)
|
const final = this.cleanup(next, prev, patch, id)
|
||||||
if (this.onStateWillChange) {
|
if (this.onStateWillChange) {
|
||||||
this.onStateWillChange(final, id)
|
this.onStateWillChange(final, id)
|
||||||
|
@ -175,7 +175,7 @@ export class StateManager<T extends Record<string, any>> {
|
||||||
* @param id (optional) An id for the just-applied patch.
|
* @param id (optional) An id for the just-applied patch.
|
||||||
* @returns The final new state to apply.
|
* @returns The final new state to apply.
|
||||||
*/
|
*/
|
||||||
protected cleanup = (nextState: T, prevState: T, patch: Patch<T>, id?: string): T => nextState
|
protected cleanup = (nextState: T, _prevState: T, _patch: Patch<T>, _id?: string): T => nextState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A life-cycle method called when the state is about to change.
|
* A life-cycle method called when the state is about to change.
|
||||||
|
|
|
@ -1713,10 +1713,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
/* Clipboard */
|
/* Clipboard */
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
private getClipboard(
|
private getClipboard(ids = this.selectedIds):
|
||||||
ids = this.selectedIds,
|
|
||||||
pageId = this.currentPageId
|
|
||||||
):
|
|
||||||
| {
|
| {
|
||||||
shapes: TDShape[]
|
shapes: TDShape[]
|
||||||
bindings: TDBinding[]
|
bindings: TDBinding[]
|
||||||
|
@ -1757,10 +1754,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
* Cut (copy and delete) one or more shapes to the clipboard.
|
* Cut (copy and delete) one or more shapes to the clipboard.
|
||||||
* @param ids The ids of the shapes to cut.
|
* @param ids The ids of the shapes to cut.
|
||||||
*/
|
*/
|
||||||
cut = (ids = this.selectedIds, pageId = this.currentPageId, e?: ClipboardEvent): this => {
|
cut = (ids = this.selectedIds, e?: ClipboardEvent): this => {
|
||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
|
|
||||||
this.copy(ids, pageId, e)
|
this.copy(ids, e)
|
||||||
if (!this.readOnly) {
|
if (!this.readOnly) {
|
||||||
this.delete(ids)
|
this.delete(ids)
|
||||||
}
|
}
|
||||||
|
@ -1771,10 +1768,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
* Copy one or more shapes to the clipboard.
|
* Copy one or more shapes to the clipboard.
|
||||||
* @param ids The ids of the shapes to copy.
|
* @param ids The ids of the shapes to copy.
|
||||||
*/
|
*/
|
||||||
copy = (ids = this.selectedIds, pageId = this.currentPageId, e?: ClipboardEvent): this => {
|
copy = (ids = this.selectedIds, e?: ClipboardEvent): this => {
|
||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
|
|
||||||
this.clipboard = this.getClipboard(ids, pageId)
|
this.clipboard = this.getClipboard(ids)
|
||||||
|
|
||||||
const jsonString = JSON.stringify({
|
const jsonString = JSON.stringify({
|
||||||
type: 'tldr/clipboard',
|
type: 'tldr/clipboard',
|
||||||
|
@ -1962,9 +1959,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e !== undefined) {
|
if (e !== undefined) {
|
||||||
let items = e.clipboardData?.items ?? []
|
const items = e.clipboardData?.items ?? []
|
||||||
for (var index in items) {
|
for (const index in items) {
|
||||||
var item = items[index]
|
const item = items[index]
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// We could eventually support pasting multiple files / images,
|
// We could eventually support pasting multiple files / images,
|
||||||
|
@ -1989,7 +1986,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case 'file': {
|
case 'file': {
|
||||||
var file = item.getAsFile()
|
const file = item.getAsFile()
|
||||||
if (file) {
|
if (file) {
|
||||||
this.addMediaFromFile(file)
|
this.addMediaFromFile(file)
|
||||||
return
|
return
|
||||||
|
@ -2386,7 +2383,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
transparentBackground: boolean
|
transparentBackground: boolean
|
||||||
}>
|
}>
|
||||||
) => {
|
) => {
|
||||||
const { scale = 2, quality = 1, ids = this.selectedIds, pageId = this.currentPageId } = opts
|
const { pageId = this.currentPageId } = opts
|
||||||
|
|
||||||
const blob = await this.getImage(format, opts)
|
const blob = await this.getImage(format, opts)
|
||||||
|
|
||||||
|
@ -2977,7 +2974,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
// Ensure that the pasted shape fits inside of the current viewport
|
// Ensure that the pasted shape fits inside of the current viewport
|
||||||
|
|
||||||
if (size[0] > this.viewport.width) {
|
if (size[0] > this.viewport.width) {
|
||||||
let r = size[1] / size[0]
|
const r = size[1] / size[0]
|
||||||
size[0] = this.viewport.width - (FIT_TO_SCREEN_PADDING / this.camera.zoom) * 2
|
size[0] = this.viewport.width - (FIT_TO_SCREEN_PADDING / this.camera.zoom) * 2
|
||||||
size[1] = size[0] * r
|
size[1] = size[0] * r
|
||||||
if (size[1] < 32 || size[1] < 32) {
|
if (size[1] < 32 || size[1] < 32) {
|
||||||
|
@ -2985,7 +2982,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
size[0] = size[1] / r
|
size[0] = size[1] / r
|
||||||
}
|
}
|
||||||
} else if (size[1] > this.viewport.height) {
|
} else if (size[1] > this.viewport.height) {
|
||||||
let r = size[0] / size[1]
|
const r = size[0] / size[1]
|
||||||
size[1] = this.viewport.height - (FIT_TO_SCREEN_PADDING / this.camera.zoom) * 2
|
size[1] = this.viewport.height - (FIT_TO_SCREEN_PADDING / this.camera.zoom) * 2
|
||||||
size[0] = size[1] * r
|
size[0] = size[1] * r
|
||||||
if (size[1] < 32 || size[1] < 32) {
|
if (size[1] < 32 || size[1] < 32) {
|
||||||
|
@ -4093,6 +4090,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
showBindingHandles: true,
|
showBindingHandles: true,
|
||||||
showCloneHandles: false,
|
showCloneHandles: false,
|
||||||
showGrid: false,
|
showGrid: false,
|
||||||
|
language: 'en',
|
||||||
},
|
},
|
||||||
appState: {
|
appState: {
|
||||||
status: TDStatus.Idle,
|
status: TDStatus.Idle,
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
import {
|
import { TLPageState, Utils, TLBoundsWithCenter, TLSnapLine, TLBounds } from '@tldraw/core'
|
||||||
TLPageState,
|
|
||||||
Utils,
|
|
||||||
TLBoundsWithCenter,
|
|
||||||
TLSnapLine,
|
|
||||||
TLBounds,
|
|
||||||
TLPerformanceMode,
|
|
||||||
} from '@tldraw/core'
|
|
||||||
import { Vec } from '@tldraw/vec'
|
import { Vec } from '@tldraw/vec'
|
||||||
import {
|
import {
|
||||||
TDShape,
|
TDShape,
|
||||||
|
|
|
@ -277,7 +277,7 @@ export class DrawUtil extends TDShapeUtil<T, E> {
|
||||||
const bounds = this.getBounds(shape)
|
const bounds = this.getBounds(shape)
|
||||||
|
|
||||||
if (bounds.width < 8 && bounds.height < 8) {
|
if (bounds.width < 8 && bounds.height < 8) {
|
||||||
return Vec.distanceToLineSegment(A, B, Utils.getBoundsCenter(bounds)) < 5
|
return Vec.distanceToLineSegment(A, B, Utils.getBoundsCenter(bounds)) < 5 // divide by zoom
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intersectLineSegmentBounds(ptA, ptB, bounds)) {
|
if (intersectLineSegmentBounds(ptA, ptB, bounds)) {
|
||||||
|
|
|
@ -146,7 +146,7 @@ export class TextUtil extends TDShapeUtil<T, E> {
|
||||||
)
|
)
|
||||||
|
|
||||||
const handlePointerDown = React.useCallback(
|
const handlePointerDown = React.useCallback(
|
||||||
(e) => {
|
(e: React.PointerEvent<HTMLDivElement | HTMLTextAreaElement>) => {
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ export const TextLabel = React.memo(function TextLabel({
|
||||||
)
|
)
|
||||||
|
|
||||||
const handlePointerDown = React.useCallback(
|
const handlePointerDown = React.useCallback(
|
||||||
(e) => {
|
(e: React.PointerEvent<HTMLTextAreaElement | HTMLDivElement>) => {
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import {
|
||||||
TLKeyboardEventHandler,
|
TLKeyboardEventHandler,
|
||||||
TLPinchEventHandler,
|
TLPinchEventHandler,
|
||||||
TLPointerEventHandler,
|
TLPointerEventHandler,
|
||||||
TLWheelEventHandler,
|
|
||||||
Utils,
|
Utils,
|
||||||
} from '@tldraw/core'
|
} from '@tldraw/core'
|
||||||
import type { TldrawApp } from '../internal'
|
import type { TldrawApp } from '../internal'
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './mockDocument'
|
export * from './mockDocument'
|
||||||
export * from './renderWithContext'
|
export * from './renderWithContext'
|
||||||
export * from './TldrawTestApp'
|
export * from './TldrawTestApp'
|
||||||
|
export * from './renderWithIntlProvider'
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import * as React from 'react'
|
||||||
|
import { IntlProvider } from 'react-intl'
|
||||||
|
import messages_en from '~translations/en.json'
|
||||||
|
import messages_fr from '~translations/fr.json'
|
||||||
|
|
||||||
|
export const renderWithIntlProvider = (children: React.ReactNode) => {
|
||||||
|
const messages = {
|
||||||
|
en: messages_en,
|
||||||
|
fr: messages_fr,
|
||||||
|
}
|
||||||
|
const language = navigator.language.split(/[-_]/)[0]
|
||||||
|
return (
|
||||||
|
// @ts-ignore
|
||||||
|
<IntlProvider locale={language} messages={messages[language]}>
|
||||||
|
<>{children}</>
|
||||||
|
</IntlProvider>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
{
|
||||||
|
"style.menu.color": "Color",
|
||||||
|
"style.menu.fill": "Fill",
|
||||||
|
"style.menu.dash": "Dash",
|
||||||
|
"style.menu.size": "Size",
|
||||||
|
"style.menu.keep.open": "Keep open",
|
||||||
|
"style.menu.font": "Font",
|
||||||
|
"style.menu.align": "Align",
|
||||||
|
"styles": "Styles",
|
||||||
|
|
||||||
|
"zoom.in": "Zoom in",
|
||||||
|
"zoom.out": "Zoom out",
|
||||||
|
"to": "to",
|
||||||
|
"to.selection": "To selection",
|
||||||
|
"to.fit": "To fit",
|
||||||
|
|
||||||
|
"menu.file": "File",
|
||||||
|
"menu.edit": "Edit",
|
||||||
|
"menu.view": "View",
|
||||||
|
"menu.preferences": "Preferences",
|
||||||
|
"menu.sign.in": "Sign In",
|
||||||
|
"menu.sign.out": "Sign Out",
|
||||||
|
"sponsored": "Sponsored",
|
||||||
|
"become.a.sponsor": "Become a sponsor",
|
||||||
|
"zoom.to.selection": "Zoom to Selection",
|
||||||
|
"zoom.to.fit": "Zoom to Fit",
|
||||||
|
"zoom.to": "Zoom to",
|
||||||
|
|
||||||
|
"preferences.dark.mode": "Dark Mode",
|
||||||
|
"preferences.focus.mode": "Focus Mode",
|
||||||
|
"preferences.debug.mode": "Debug Mode",
|
||||||
|
"preferences.show.grid": "Show Grid",
|
||||||
|
"preferences.use.cad.selection": "Use CAD Selection",
|
||||||
|
"preferences.keep.stylemenu.open": "Keep Style Menu Open",
|
||||||
|
"preferences.always.show.snaps": "Always Show Snaps",
|
||||||
|
"preferences.rotate.handles": "Rotate Handles",
|
||||||
|
"preferences.binding.handles": "Binding Handles",
|
||||||
|
"preferences.clone.handles": "Clone Handles",
|
||||||
|
|
||||||
|
"undo": "Undo",
|
||||||
|
"redo": "Redo",
|
||||||
|
"cut": "Cut",
|
||||||
|
"copy": "Copy",
|
||||||
|
"paste": "Paste",
|
||||||
|
"copy.as": "Copy as",
|
||||||
|
"export.as": "Export as",
|
||||||
|
"select.all": "Select all",
|
||||||
|
"select.none": "Select none",
|
||||||
|
"delete": "Delete",
|
||||||
|
|
||||||
|
"new.project": "New Project",
|
||||||
|
"open": "Open",
|
||||||
|
"save": "Save",
|
||||||
|
"save.as": "Save As",
|
||||||
|
"upload.media": "Upload Media",
|
||||||
|
|
||||||
|
"create.page": "Create Page",
|
||||||
|
"new.page": "New Page",
|
||||||
|
"page.name": "Page Name",
|
||||||
|
"duplicate": "Duplicate",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"copy.invite.link": "Copy Invite Link",
|
||||||
|
"create.multiplayer.project": "Create a Multiplayer Project",
|
||||||
|
"copy.multiplayer.project": "Copy to Multiplayer Project",
|
||||||
|
|
||||||
|
"select": "Select",
|
||||||
|
"eraser": "Eraser",
|
||||||
|
"draw": "Draw",
|
||||||
|
"arrow": "Arrow",
|
||||||
|
"text": "Text",
|
||||||
|
"sticky": "Sticky",
|
||||||
|
"Rectangle": "Rectangle",
|
||||||
|
"Ellipse": "Ellipse",
|
||||||
|
"Triangle": "Triangle",
|
||||||
|
"Line": "Line",
|
||||||
|
|
||||||
|
"rotate": "Rotate",
|
||||||
|
"lock.aspect.ratio": "Lock Aspect Ratio",
|
||||||
|
"unlock.aspect.ratio": "Unlock Aspect Ratio",
|
||||||
|
"group": "Group",
|
||||||
|
"ungroup": "Ungroup",
|
||||||
|
"move.to.back": "Move to Back",
|
||||||
|
"move.backward": "Move Backward",
|
||||||
|
"move.forward": "Move Forward",
|
||||||
|
"move.to.front": "Move to Front",
|
||||||
|
"reset.angle": "Reset Angle",
|
||||||
|
"lock": "Lock",
|
||||||
|
"unlock": "Unlock",
|
||||||
|
|
||||||
|
"move.to.page": "Move to Page",
|
||||||
|
"flip.horizontal": "Flip Horizontal",
|
||||||
|
"flip.vertical": "Flip Vertical",
|
||||||
|
"move": "Move",
|
||||||
|
"to.front": "To Front",
|
||||||
|
"forward": "Forward",
|
||||||
|
"backward": "Backward",
|
||||||
|
"back": "Back",
|
||||||
|
"language": "Language"
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
{
|
||||||
|
"style.menu.color": "Couleur",
|
||||||
|
"style.menu.fill": "Remplir",
|
||||||
|
"style.menu.dash": "Bordure",
|
||||||
|
"style.menu.size": "Taille",
|
||||||
|
"style.menu.keep.open": "Garder ouvert",
|
||||||
|
"style.menu.font": "Font",
|
||||||
|
"style.menu.align": "Alignement",
|
||||||
|
"styles": "Styles",
|
||||||
|
|
||||||
|
"zoom.in": "Zoomer",
|
||||||
|
"zoom.out": "Dézoomer",
|
||||||
|
"to": "À",
|
||||||
|
"to.selection": "Sélection",
|
||||||
|
"to.fit": "Adapter",
|
||||||
|
|
||||||
|
"menu.file": "Fichier",
|
||||||
|
"menu.edit": "Modifier",
|
||||||
|
"menu.view": "Vue",
|
||||||
|
"menu.preferences": "Préférences",
|
||||||
|
"menu.sign.in": "S'authentifier",
|
||||||
|
"menu.sign.out": "Se déconnecter",
|
||||||
|
"sponsored": "Sponsorisé",
|
||||||
|
"become.a.sponsor": "Devenir un sponsor",
|
||||||
|
"zoom.to.selection": "Zoomer sur la sélection",
|
||||||
|
"zoom.to.fit": "Zoomer pour adapter",
|
||||||
|
"zoom.to": "Réinitialiser le zoom à",
|
||||||
|
|
||||||
|
"preferences.dark.mode": "Mode Sombre",
|
||||||
|
"preferences.focus.mode": "Mode Focus",
|
||||||
|
"preferences.debug.mode": "Débogage Mode",
|
||||||
|
"preferences.show.grid": "Montrer La Grille",
|
||||||
|
"preferences.use.cad.selection": "Utiliser La Sélection CAD",
|
||||||
|
"preferences.keep.stylemenu.open": "Garder Le Menu Style Ouvert",
|
||||||
|
"preferences.always.show.snaps": "Garder Les Snaps Visible",
|
||||||
|
"preferences.rotate.handles": "Manipuler La Rotation",
|
||||||
|
"preferences.binding.handles": "Manipuler La Liaison",
|
||||||
|
"preferences.clone.handles": "Manipuler Le Clonage",
|
||||||
|
|
||||||
|
"undo": "Annuler",
|
||||||
|
"redo": "Refaire",
|
||||||
|
"cut": "Couper",
|
||||||
|
"copy": "Copier",
|
||||||
|
"paste": "Coller",
|
||||||
|
"copy.as": "Copier en tant que",
|
||||||
|
"export.as": "Exporter en tant que",
|
||||||
|
"select.all": "Sélectionner tout",
|
||||||
|
"select.none": "Sélectionner aucun",
|
||||||
|
"delete": "Supprimer",
|
||||||
|
|
||||||
|
"new.project": "Nouveau Project",
|
||||||
|
"open": "Ouvrir",
|
||||||
|
"save": "Enregistrer",
|
||||||
|
"save.as": "Enregistrer en tant que",
|
||||||
|
"upload.media": "Uploader Un Média",
|
||||||
|
|
||||||
|
"create.page": "Créer une Page",
|
||||||
|
"new.page": "Nouvelle Page",
|
||||||
|
"page.name": "Nom de la Page",
|
||||||
|
"duplicate": "Dupliquer",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"copy.invite.link": "Copier le Lien d'Invitation",
|
||||||
|
"create.multiplayer.project": "Créer un Project Multi-joueurs",
|
||||||
|
"copy.multiplayer.project": "Copier dans un Projet Multi-joueurs",
|
||||||
|
|
||||||
|
"select": "Selection",
|
||||||
|
"eraser": "Gomme",
|
||||||
|
"draw": "Crayon",
|
||||||
|
"arrow": "Flèche",
|
||||||
|
"text": "Text",
|
||||||
|
"sticky": "Papier collant",
|
||||||
|
"Rectangle": "Rectangle",
|
||||||
|
"Ellipse": "Cercle",
|
||||||
|
"Triangle": "Triangle",
|
||||||
|
"Line": "Ligne",
|
||||||
|
|
||||||
|
"rotate": "Retourner",
|
||||||
|
"lock.aspect.ratio": "Verouiller l'Aspect Ratio",
|
||||||
|
"unlock.aspect.ratio": "Déverouiller l'Aspect Ratio",
|
||||||
|
"group": "Grouper",
|
||||||
|
"ungroup": "Dégrouper",
|
||||||
|
"move.to.back": "Envoyer vers l'arrière",
|
||||||
|
"move.backward": "Mettre en arrière-plan",
|
||||||
|
"move.forward": "Mettre au premier plan",
|
||||||
|
"move.to.front": "Envoyer vers l'avant",
|
||||||
|
"reset.angle": "Réinitialiser l'Angle",
|
||||||
|
"lock": "Verouiller",
|
||||||
|
"unlock": "Déverouiller",
|
||||||
|
"move.to.page": "Déplacer vers la page",
|
||||||
|
"flip.horizontal": "Retourner Horizontalement",
|
||||||
|
"flip.vertical": "Retourner Verticalement",
|
||||||
|
"move": "Mettre",
|
||||||
|
"to.front": "À l'avant",
|
||||||
|
"forward": "Au premier plan",
|
||||||
|
"backward": "En arrière plan",
|
||||||
|
"back": "À l'arrière",
|
||||||
|
"language": "Langage"
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
{
|
||||||
|
"style.menu.color": "Colore",
|
||||||
|
"style.menu.fill": "Riempi",
|
||||||
|
"style.menu.dash": "Tratteggo",
|
||||||
|
"style.menu.size": "Dimensione",
|
||||||
|
"style.menu.keep.open": "Mantieni aperto",
|
||||||
|
"style.menu.font": "Font",
|
||||||
|
"style.menu.align": "Allineamento",
|
||||||
|
"styles": "Stile",
|
||||||
|
|
||||||
|
"zoom.in": "Ingrandisci",
|
||||||
|
"zoom.out": "Rimpicciolisci",
|
||||||
|
"to": "Imposta",
|
||||||
|
"to.selection": "Adatta alla selezione",
|
||||||
|
"to.fit": "Adatta",
|
||||||
|
|
||||||
|
"menu.file": "File",
|
||||||
|
"menu.edit": "Modifica",
|
||||||
|
"menu.view": "Visualizzazione",
|
||||||
|
"menu.preferences": "Preferenze",
|
||||||
|
"menu.sign.in": "Accedi",
|
||||||
|
"menu.sign.out": "Esci",
|
||||||
|
"sponsored": "Sponsorizza",
|
||||||
|
"become.a.sponsor": "Sponsorizza",
|
||||||
|
"zoom.to.selection": "Adatta alla selezione",
|
||||||
|
"zoom.to.fit": "Adatta",
|
||||||
|
"zoom.to": "Ingrandisci",
|
||||||
|
|
||||||
|
"preferences.dark.mode": "Modalità scura",
|
||||||
|
"preferences.focus.mode": "Modalità zen",
|
||||||
|
"preferences.debug.mode": "Modalità sviluppatore",
|
||||||
|
"preferences.show.grid": "Mostra griglia",
|
||||||
|
"preferences.use.cad.selection": "Selezione CAD",
|
||||||
|
"preferences.keep.stylemenu.open": "Mantieni menu stile aperto",
|
||||||
|
"preferences.always.show.snaps": "Mostra sempre le guide",
|
||||||
|
"preferences.rotate.handles": "Controlli d'inclinazione",
|
||||||
|
"preferences.binding.handles": "Controlli d'associazione",
|
||||||
|
"preferences.clone.handles": "Controlli di clonazione",
|
||||||
|
|
||||||
|
"undo": "Annulla",
|
||||||
|
"redo": "Ripristina",
|
||||||
|
"cut": "Taglia",
|
||||||
|
"copy": "Copia",
|
||||||
|
"paste": "Incolla",
|
||||||
|
"copy.as": "Copia come",
|
||||||
|
"export.as": "Esporta come",
|
||||||
|
"select.all": "Seleziona tutto",
|
||||||
|
"select.none": "Deseleziona tutto",
|
||||||
|
"delete": "Elimina",
|
||||||
|
|
||||||
|
"new.project": "Nuovo progetto",
|
||||||
|
"open": "Apri",
|
||||||
|
"save": "Salva",
|
||||||
|
"save.as": "Salva come",
|
||||||
|
"upload.media": "Carica contenuti multimediali",
|
||||||
|
|
||||||
|
"create.page": "Crea nuova pagina",
|
||||||
|
"new.page": "Nuova pagina",
|
||||||
|
"page.name": "Nome pagina",
|
||||||
|
"duplicate": "Duplica",
|
||||||
|
"cancel": "Chiudi",
|
||||||
|
"copy.invite.link": "Copia link invito",
|
||||||
|
"create.multiplayer.project": "Crea progetto multiplayer",
|
||||||
|
"copy.multiplayer.project": "Trasforma in progetto multiplayer",
|
||||||
|
|
||||||
|
"select": "Seleziona",
|
||||||
|
"eraser": "Gomma",
|
||||||
|
"draw": "Matita",
|
||||||
|
"arrow": "Freccia",
|
||||||
|
"text": "Casella di testo",
|
||||||
|
"sticky": "Post-it",
|
||||||
|
"Rectangle": "Rettangolo",
|
||||||
|
"Ellipse": "Ellisse",
|
||||||
|
"Triangle": "Triangolo",
|
||||||
|
"Line": "Linea",
|
||||||
|
|
||||||
|
"rotate": "Ruota",
|
||||||
|
"lock.aspect.ratio": "Blocca rapporto lati",
|
||||||
|
"unlock.aspect.ratio": "Sblocca rapporto lati",
|
||||||
|
"group": "Raggruppa",
|
||||||
|
"move.to.back": "Muovi in fondo",
|
||||||
|
"move.backward": "Sposta indietro",
|
||||||
|
"move.forward": "Sposta avanti",
|
||||||
|
"move.to.front": "Muovi in fronte",
|
||||||
|
"reset.angle": "Reimposta angolo",
|
||||||
|
"lock": "Blocca",
|
||||||
|
"unlock": "Sblocca",
|
||||||
|
|
||||||
|
"move.to.page": "Trasferisci a pagina",
|
||||||
|
"flip.horizontal": "Ribalta orizzontalmente",
|
||||||
|
"flip.vertical": "Ribalta verticalmente",
|
||||||
|
"move": "Sposta",
|
||||||
|
"to.front": "In primo piano",
|
||||||
|
"forward": "Sposta avanti",
|
||||||
|
"backward": "Sposta indietro",
|
||||||
|
"back": "In fondo",
|
||||||
|
"language": "Lingua"
|
||||||
|
}
|
|
@ -76,6 +76,8 @@ export class TDEventHandler {
|
||||||
onShapeClone?: TLShapeCloneHandler
|
onShapeClone?: TLShapeCloneHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TDLanguage = 'en' | 'fr' | 'it'
|
||||||
|
|
||||||
// The shape of the TldrawApp's React (zustand) store
|
// The shape of the TldrawApp's React (zustand) store
|
||||||
export interface TDSnapshot {
|
export interface TDSnapshot {
|
||||||
settings: {
|
settings: {
|
||||||
|
@ -94,6 +96,7 @@ export interface TDSnapshot {
|
||||||
showBindingHandles: boolean
|
showBindingHandles: boolean
|
||||||
showCloneHandles: boolean
|
showCloneHandles: boolean
|
||||||
showGrid: boolean
|
showGrid: boolean
|
||||||
|
language: TDLanguage
|
||||||
}
|
}
|
||||||
appState: {
|
appState: {
|
||||||
currentStyle: ShapeStyles
|
currentStyle: ShapeStyles
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"exclude": ["node_modules", "dist", "docs"],
|
"exclude": ["node_modules", "dist", "docs"],
|
||||||
"include": ["src"],
|
"include": ["src", "./src/translations/*.json"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
|
|
Ładowanie…
Reference in New Issue