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>
This commit is contained in:
parent
7c08f2f5b6
commit
d919bd273e
64 changed files with 1387 additions and 694 deletions
13
.changeset/clever-onions-flash.md
Normal file
13
.changeset/clever-onions-flash.md
Normal file
|
@ -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.
|
21
.changeset/pre.json
Normal file
21
.changeset/pre.json
Normal file
|
@ -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
|
||||
*.js
|
||||
*.md
|
||||
*.lock
|
||||
*.tsbuildinfo
|
6
apps/electron/CHANGELOG.md
Normal file
6
apps/electron/CHANGELOG.md
Normal file
|
@ -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",
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-next.0",
|
||||
"private": true,
|
||||
"description": "An electron app for tldraw.",
|
||||
"author": "@steveruizok",
|
||||
|
@ -18,7 +18,7 @@
|
|||
"package": "electron-builder"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tldraw/tldraw": "^1.6.1",
|
||||
"@tldraw/tldraw": "^1.16.0-next.0",
|
||||
"@types/node": "^17.0.14",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
|
@ -32,7 +32,7 @@
|
|||
"react": "^17.0",
|
||||
"react-dom": "^17.0",
|
||||
"rimraf": "3.0.2",
|
||||
"typescript": "4.5.5"
|
||||
"typescript": "^4.7.3"
|
||||
},
|
||||
"build": {
|
||||
"appId": "io.comp.tldraw-electron",
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# @tldraw/vscode-editor
|
||||
|
||||
## 1.11.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Bump dependencies, add international support.
|
||||
|
||||
## 1.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@tldraw/vscode-editor",
|
||||
"version": "1.10.2",
|
||||
"version": "1.11.0-next.0",
|
||||
"private": true,
|
||||
"description": "An an editor for the tldraw vscode extension.",
|
||||
"author": "@steveruizok",
|
||||
|
@ -18,18 +18,18 @@
|
|||
"devDependencies": {
|
||||
"@tldraw/tldraw": "*",
|
||||
"@types/node": "^17.0.14",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react": "^18.0.12",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"concurrently": "7.0.0",
|
||||
"create-serve": "1.0.1",
|
||||
"esbuild": "^0.14.38",
|
||||
"esbuild-serve": "^1.0.1",
|
||||
"react": "^17.0",
|
||||
"react-dom": "^17.0",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"rimraf": "3.0.2",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "4.5.5"
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.3"
|
||||
},
|
||||
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
|
||||
}
|
|
@ -1,5 +1,11 @@
|
|||
## 1.2.4
|
||||
|
||||
## 1.15.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Bump dependencies, add international support.
|
||||
|
||||
## 1.14.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "tldraw-vscode",
|
||||
"displayName": "tldraw",
|
||||
"description": "The tldraw Extension for VS Code.",
|
||||
"version": "1.14.1",
|
||||
"version": "1.15.0-next.0",
|
||||
"license": "MIT",
|
||||
"publisher": "tldraw-org",
|
||||
"repository": {
|
||||
|
@ -128,8 +128,8 @@
|
|||
"mocha": "^9.1.1",
|
||||
"process": "^0.11.10",
|
||||
"ts-loader": "^9.2.5",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.4.3",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.3",
|
||||
"vsce": "^2.2.0"
|
||||
},
|
||||
"gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296"
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
# @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
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Tldraw, TldrawApp, TldrawProps, useFileSystem } from '@tldraw/tldraw'
|
||||
import { useAccountHandlers } from 'hooks/useAccountHandlers'
|
||||
import { useUploadAssets } from 'hooks/useUploadAssets'
|
||||
import React, { FC } from 'react'
|
||||
import * as React from 'react'
|
||||
import * as gtag from 'utils/gtag'
|
||||
|
||||
declare const window: Window & { app: TldrawApp }
|
||||
|
@ -12,7 +12,7 @@ interface EditorProps {
|
|||
isSponsor?: boolean
|
||||
}
|
||||
|
||||
const Editor: FC<EditorProps & Partial<TldrawProps>> = ({
|
||||
const Editor: React.FC<EditorProps & Partial<TldrawProps>> = ({
|
||||
id = 'home',
|
||||
isUser = false,
|
||||
isSponsor = false,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createClient } from '@liveblocks/client'
|
||||
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 { useMultiplayerAssets } from 'hooks/useMultiplayerAssets'
|
||||
import { useMultiplayerState } from 'hooks/useMultiplayerState'
|
||||
|
|
|
@ -3,10 +3,19 @@
|
|||
import React, { useState, useRef, useCallback } from 'react'
|
||||
import type { TldrawApp, TDUser, TDShape, TDBinding, TDDocument, TDAsset } from '@tldraw/tldraw'
|
||||
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 }
|
||||
|
||||
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) {
|
||||
const [app, setApp] = useState<TldrawApp>()
|
||||
const [error, setError] = useState<Error>()
|
||||
|
@ -17,9 +26,9 @@ export function useMultiplayerState(roomId: string) {
|
|||
const onRedo = useRedo()
|
||||
const updateMyPresence = useUpdateMyPresence()
|
||||
|
||||
const rLiveShapes = useRef<LiveMap<string, TDShape>>()
|
||||
const rLiveBindings = useRef<LiveMap<string, TDBinding>>()
|
||||
const rLiveAssets = useRef<LiveMap<string, TDAsset>>()
|
||||
const rLiveShapes = useRef<LiveMap<string, TDLsonShape>>()
|
||||
const rLiveBindings = useRef<LiveMap<string, TDLsonBinding>>()
|
||||
const rLiveAssets = useRef<LiveMap<string, TDLsonAsset>>()
|
||||
|
||||
// Callbacks --------------
|
||||
|
||||
|
@ -53,7 +62,7 @@ export function useMultiplayerState(roomId: string) {
|
|||
if (!shape) {
|
||||
lShapes.delete(id)
|
||||
} else {
|
||||
lShapes.set(shape.id, shape)
|
||||
lShapes.set(shape.id, shape as TDLsonShape)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -61,7 +70,7 @@ export function useMultiplayerState(roomId: string) {
|
|||
if (!binding) {
|
||||
lBindings.delete(id)
|
||||
} else {
|
||||
lBindings.set(binding.id, binding)
|
||||
lBindings.set(binding.id, binding as TDLsonBinding)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -69,7 +78,7 @@ export function useMultiplayerState(roomId: string) {
|
|||
if (!asset) {
|
||||
lAssets.delete(id)
|
||||
} 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
|
||||
unsubs.push(
|
||||
room.subscribe('others', (others, event) => {
|
||||
room.subscribe<{ id: string; user: TDUser }>('others', (others, event) => {
|
||||
if (event.type === 'leave') {
|
||||
if (event.user.presence) {
|
||||
app?.removeUser(event.user.presence.id)
|
||||
|
@ -123,23 +132,23 @@ export function useMultiplayerState(roomId: string) {
|
|||
|
||||
// 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)) {
|
||||
storage.root.set('shapes', new LiveMap<string, TDShape>())
|
||||
storage.root.set('shapes', new LiveMap<string, TDLsonShape>())
|
||||
lShapes = storage.root.get('shapes')
|
||||
}
|
||||
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)) {
|
||||
storage.root.set('bindings', new LiveMap<string, TDBinding>())
|
||||
storage.root.set('bindings', new LiveMap<string, TDLsonBinding>())
|
||||
lBindings = storage.root.get('bindings')
|
||||
}
|
||||
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)) {
|
||||
storage.root.set('assets', new LiveMap<string, TDAsset>())
|
||||
storage.root.set('assets', new LiveMap<string, TDLsonAsset>())
|
||||
lAssets = storage.root.get('assets')
|
||||
}
|
||||
rLiveAssets.current = lAssets
|
||||
|
@ -150,11 +159,7 @@ export function useMultiplayerState(roomId: string) {
|
|||
// 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
|
||||
// and then mark the doc as migrated.
|
||||
const doc = storage.root.get('doc') as LiveObject<{
|
||||
uuid: string
|
||||
document: TDDocument
|
||||
migrated?: boolean
|
||||
}>
|
||||
const doc = storage.root.get('doc') as LiveObject<LsonDoc>
|
||||
|
||||
// No doc? No problem. This was likely a newer document
|
||||
if (doc) {
|
||||
|
@ -167,9 +172,11 @@ export function useMultiplayerState(roomId: string) {
|
|||
},
|
||||
} = doc.toObject()
|
||||
|
||||
Object.values(shapes).forEach((shape) => lShapes.set(shape.id, shape))
|
||||
Object.values(bindings).forEach((binding) => lBindings.set(binding.id, binding))
|
||||
Object.values(assets).forEach((asset) => lAssets.set(asset.id, asset))
|
||||
Object.values(shapes).forEach((shape) => lShapes.set(shape.id, shape as TDLsonShape))
|
||||
Object.values(bindings).forEach((binding) =>
|
||||
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",
|
||||
"version": "1.6.7",
|
||||
"version": "1.7.0-next.0",
|
||||
"private": true,
|
||||
"description": "A tiny little drawing app (site).",
|
||||
"repository": {
|
||||
|
@ -18,33 +18,31 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@liveblocks/client": "^0.14.0",
|
||||
"@liveblocks/react": "^0.14.0",
|
||||
"@sentry/webpack-plugin": "^1.17.1",
|
||||
"@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/node": "^6.13.2",
|
||||
"@sentry/react": "^6.13.2",
|
||||
"@sentry/tracing": "^6.13.2",
|
||||
"@stitches/react": "^1.2.5",
|
||||
"@stitches/react": "^1.2.8",
|
||||
"@tldraw/core": "*",
|
||||
"@tldraw/tldraw": "*",
|
||||
"aws-sdk": "^2.1053.0",
|
||||
"lz-string": "^1.4.4",
|
||||
"nanoid": "^3.3.4",
|
||||
"next": "^12.0.7",
|
||||
"next": "^12.1.6",
|
||||
"next-auth": "^4.0.5",
|
||||
"next-pwa": "^5.4.4",
|
||||
"next-pwa": "^5.5.4",
|
||||
"next-themes": "^0.0.15",
|
||||
"react": "^17.0",
|
||||
"react-dom": "^17.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"
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0"
|
||||
},
|
||||
"gitHead": "838fabdbff1a66d4d7ee8aa5c5d117bc55acbff2"
|
||||
}
|
6
examples/core-example-advanced/CHANGELOG.md
Normal file
6
examples/core-example-advanced/CHANGELOG.md
Normal file
|
@ -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",
|
||||
"description": "An advanced example project for @tldraw/core.",
|
||||
"author": "@steveruizok",
|
||||
|
@ -22,8 +22,6 @@
|
|||
"@tldraw/intersect": "*",
|
||||
"@tldraw/vec": "*",
|
||||
"@types/node": "^17.0.14",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"concurrently": "^7.0.0",
|
||||
"create-serve": "^1.0.1",
|
||||
|
@ -33,11 +31,13 @@
|
|||
"lodash": "^4.17.21",
|
||||
"nanoid": "^3.1.31",
|
||||
"perfect-freehand": "^1.1.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-feather": "^2.0.9",
|
||||
"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"
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './app'
|
||||
import './styles.css'
|
||||
|
||||
ReactDOM.render(
|
||||
const container = document.getElementById('root')!
|
||||
const root = createRoot(container)
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
</React.StrictMode>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
# @tldraw/core-example-simple
|
||||
|
||||
## 1.8.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Bump dependencies, add international support.
|
||||
|
||||
## 1.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Fix build error in extension.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0-next.0",
|
||||
"name": "@tldraw/core-example-simple",
|
||||
"description": "A simple example project for @tldraw/core.",
|
||||
"author": "@steveruizok",
|
||||
|
@ -18,18 +18,18 @@
|
|||
"@tldraw/core": "*",
|
||||
"@tldraw/vec": "*",
|
||||
"@types/node": "^17.0.14",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"concurrently": "^7.0.0",
|
||||
"esbuild": "^0.14.18",
|
||||
"esbuild-serve": "^1.0.1",
|
||||
"mobx": "^6.3.13",
|
||||
"mobx-react-lite": "^3.2.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.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"
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './app'
|
||||
import './styles.css'
|
||||
|
||||
ReactDOM.render(
|
||||
const container = document.getElementById('root')!
|
||||
const root = createRoot(container)
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
</React.StrictMode>
|
||||
)
|
||||
|
|
6
examples/tldraw-example/CHANGELOG.md
Normal file
6
examples/tldraw-example/CHANGELOG.md
Normal file
|
@ -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",
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-next.0",
|
||||
"private": true,
|
||||
"description": "An example project for @tldraw/tldraw.",
|
||||
"author": "@steveruizok",
|
||||
|
@ -18,8 +18,6 @@
|
|||
"@liveblocks/client": "^0.14.0",
|
||||
"@liveblocks/react": "^0.14.0",
|
||||
"@types/node": "^17.0.14",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"concurrently": "^7.0.0",
|
||||
"create-serve": "^1.0.1",
|
||||
|
@ -27,12 +25,14 @@
|
|||
"esbuild-envfile-plugin": "^1.0.2",
|
||||
"esbuild-serve": "^1.0.1",
|
||||
"firebase": "^9.6.5",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router": "^6.2.1",
|
||||
"react-router-dom": "^6.2.1",
|
||||
"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"
|
||||
}
|
|
@ -55,7 +55,9 @@ export default function App() {
|
|||
<main>
|
||||
<Routes>
|
||||
{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
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './app'
|
||||
import { HashRouter } from 'react-router-dom'
|
||||
|
||||
ReactDOM.render(
|
||||
const container = document.getElementById('root')!
|
||||
const root = createRoot(container)
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<HashRouter>
|
||||
<App />
|
||||
</HashRouter>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
</React.StrictMode>
|
||||
)
|
||||
|
|
|
@ -61,14 +61,12 @@
|
|||
"mobx": "^6.3.8",
|
||||
"prettier": "^2.5.1",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"react": "^17.0",
|
||||
"react-dom": "^17.0",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"source-map-loader": "^3.0.1",
|
||||
"tslib": "^2.3.1",
|
||||
"tslib": "^2.4.0",
|
||||
"turbo": "^1.1.2",
|
||||
"typedoc": "^0.22.15",
|
||||
"typescript": "^4.6.4",
|
||||
"typescript": "^4.7.3",
|
||||
"webpack": "^5.68.0"
|
||||
},
|
||||
"husky": {
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# Changelog
|
||||
|
||||
## 1.14.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Bump dependencies, add international support.
|
||||
|
||||
## 1.13.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.13.1",
|
||||
"version": "1.14.0-next.0",
|
||||
"name": "@tldraw/core",
|
||||
"description": "The tldraw core renderer and utilities.",
|
||||
"author": "@steveruizok",
|
||||
|
@ -46,7 +46,7 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8",
|
||||
"react-dom": "^16.8 || ^17.0"
|
||||
"react-dom": ">=16.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc-node/jest": "^1.4.3",
|
||||
|
@ -55,14 +55,15 @@
|
|||
"@tldraw/intersect": "*",
|
||||
"@tldraw/vec": "*",
|
||||
"@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/parser": "^5.10.2",
|
||||
"eslint": "^8.8.0",
|
||||
"lask": "^0.0.29",
|
||||
"mobx": "^6.3.8",
|
||||
"react": ">=16.8",
|
||||
"react-dom": "^16.8 || ^17.0"
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0"
|
||||
},
|
||||
"jest": {
|
||||
"setupFilesAfterEnv": [
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
# 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
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@tldraw/tldraw",
|
||||
"version": "1.15.1",
|
||||
"version": "1.16.0-next.0",
|
||||
"description": "A tiny little drawing app (editor)",
|
||||
"author": "@steveruizok",
|
||||
"repository": {
|
||||
|
@ -37,29 +37,26 @@
|
|||
"docs": "typedoc"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0",
|
||||
"react-dom": "^17.0"
|
||||
"react": ">=16.8",
|
||||
"react-dom": ">=16.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-alert-dialog": "^0.1.7",
|
||||
"@radix-ui/react-checkbox": "^0.1.5",
|
||||
"@radix-ui/react-context-menu": "^0.1.6",
|
||||
"@radix-ui/react-dropdown-menu": "^0.1.6",
|
||||
"@radix-ui/react-icons": "^1.1.1",
|
||||
"@radix-ui/react-radio-group": "^0.1.5",
|
||||
"@radix-ui/react-tooltip": "^0.1.7",
|
||||
"@stitches/react": "^1.2.8",
|
||||
"@tldraw/core": "^1.13.1",
|
||||
"@tldraw/core": "^1.14.0-next.0",
|
||||
"@tldraw/intersect": "^1.7.1",
|
||||
"@tldraw/vec": "^1.7.0",
|
||||
"@types/lz-string": "^1.3.34",
|
||||
"idb-keyval": "^6.1.0",
|
||||
"lz-string": "^1.4.4",
|
||||
"perfect-freehand": "^1.1.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-hotkey-hook": "^1.0.2",
|
||||
"react-hotkeys-hook": "^3.4.4",
|
||||
"tslib": "^2.3.1",
|
||||
"react-intl": "^6.0.3",
|
||||
"tslib": "^2.4.0",
|
||||
"zustand": "^3.6.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -69,16 +66,18 @@
|
|||
"@tldraw/core": "*",
|
||||
"@tldraw/intersect": "*",
|
||||
"@tldraw/vec": "*",
|
||||
"@types/lz-string": "^1.3.34",
|
||||
"@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/parser": "^5.10.2",
|
||||
"eslint": "^8.8.0",
|
||||
"lask": "^0.0.29",
|
||||
"mobx": "^6.3.8",
|
||||
"react": "^17.0",
|
||||
"react-dom": "^17.0",
|
||||
"typescript": "^4.6.4"
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"typescript": "^4.7.3"
|
||||
},
|
||||
"jest": {
|
||||
"setupFilesAfterEnv": [
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from 'react'
|
||||
import { Renderer } from '@tldraw/core'
|
||||
import { IntlConfig, IntlProvider } from 'react-intl'
|
||||
import { styled, dark } from '~styles'
|
||||
import { TDDocument, TDStatus } from '~types'
|
||||
import { TldrawApp, TDCallbacks } from '~state'
|
||||
|
@ -12,9 +13,15 @@ import { FocusButton } from '~components/FocusButton'
|
|||
import { TLDR } from '~state/TLDR'
|
||||
import { GRID_SIZE } from '~constants'
|
||||
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 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 {
|
||||
/**
|
||||
* (optional) If provided, the component will load / persist state under this key.
|
||||
|
@ -417,112 +424,125 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||
const hideCloneHandles =
|
||||
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 (
|
||||
<StyledLayout ref={rWrapper} tabIndex={-0} className={settings.isDarkMode ? dark : ''}>
|
||||
<Loading />
|
||||
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
||||
<ContextMenu>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<Renderer
|
||||
id={id}
|
||||
containerRef={rWrapper}
|
||||
shapeUtils={shapeUtils}
|
||||
page={page}
|
||||
pageState={pageState}
|
||||
assets={assets}
|
||||
snapLines={appState.snapLines}
|
||||
eraseLine={appState.eraseLine}
|
||||
grid={GRID_SIZE}
|
||||
users={room?.users}
|
||||
userId={room?.userId}
|
||||
theme={theme}
|
||||
meta={meta}
|
||||
hideBounds={hideBounds}
|
||||
hideHandles={hideHandles}
|
||||
hideResizeHandles={isHideResizeHandlesShape}
|
||||
hideIndicators={hideIndicators}
|
||||
hideBindingHandles={!settings.showBindingHandles}
|
||||
hideCloneHandles={hideCloneHandles}
|
||||
hideRotateHandles={!settings.showRotateHandles}
|
||||
hideGrid={!settings.showGrid}
|
||||
showDashedBrush={showDashedBrush}
|
||||
performanceMode={app.session?.performanceMode}
|
||||
onPinchStart={app.onPinchStart}
|
||||
onPinchEnd={app.onPinchEnd}
|
||||
onPinch={app.onPinch}
|
||||
onPan={app.onPan}
|
||||
onZoom={app.onZoom}
|
||||
onPointerDown={app.onPointerDown}
|
||||
onPointerMove={app.onPointerMove}
|
||||
onPointerUp={app.onPointerUp}
|
||||
onPointCanvas={app.onPointCanvas}
|
||||
onDoubleClickCanvas={app.onDoubleClickCanvas}
|
||||
onRightPointCanvas={app.onRightPointCanvas}
|
||||
onDragCanvas={app.onDragCanvas}
|
||||
onReleaseCanvas={app.onReleaseCanvas}
|
||||
onPointShape={app.onPointShape}
|
||||
onDoubleClickShape={app.onDoubleClickShape}
|
||||
onRightPointShape={app.onRightPointShape}
|
||||
onDragShape={app.onDragShape}
|
||||
onHoverShape={app.onHoverShape}
|
||||
onUnhoverShape={app.onUnhoverShape}
|
||||
onReleaseShape={app.onReleaseShape}
|
||||
onPointBounds={app.onPointBounds}
|
||||
onDoubleClickBounds={app.onDoubleClickBounds}
|
||||
onRightPointBounds={app.onRightPointBounds}
|
||||
onDragBounds={app.onDragBounds}
|
||||
onHoverBounds={app.onHoverBounds}
|
||||
onUnhoverBounds={app.onUnhoverBounds}
|
||||
onReleaseBounds={app.onReleaseBounds}
|
||||
onPointBoundsHandle={app.onPointBoundsHandle}
|
||||
onDoubleClickBoundsHandle={app.onDoubleClickBoundsHandle}
|
||||
onRightPointBoundsHandle={app.onRightPointBoundsHandle}
|
||||
onDragBoundsHandle={app.onDragBoundsHandle}
|
||||
onHoverBoundsHandle={app.onHoverBoundsHandle}
|
||||
onUnhoverBoundsHandle={app.onUnhoverBoundsHandle}
|
||||
onReleaseBoundsHandle={app.onReleaseBoundsHandle}
|
||||
onPointHandle={app.onPointHandle}
|
||||
onDoubleClickHandle={app.onDoubleClickHandle}
|
||||
onRightPointHandle={app.onRightPointHandle}
|
||||
onDragHandle={app.onDragHandle}
|
||||
onHoverHandle={app.onHoverHandle}
|
||||
onUnhoverHandle={app.onUnhoverHandle}
|
||||
onReleaseHandle={app.onReleaseHandle}
|
||||
onError={app.onError}
|
||||
onRenderCountChange={app.onRenderCountChange}
|
||||
onShapeChange={app.onShapeChange}
|
||||
onShapeBlur={app.onShapeBlur}
|
||||
onShapeClone={app.onShapeClone}
|
||||
onBoundsChange={app.updateBounds}
|
||||
onKeyDown={app.onKeyDown}
|
||||
onKeyUp={app.onKeyUp}
|
||||
onDragOver={app.onDragOver}
|
||||
onDrop={app.onDrop}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</ContextMenu>
|
||||
{showUI && (
|
||||
<StyledUI>
|
||||
{settings.isFocusMode ? (
|
||||
<FocusButton onSelect={app.toggleFocusMode} />
|
||||
) : (
|
||||
<>
|
||||
<TopPanel
|
||||
readOnly={readOnly}
|
||||
showPages={showPages}
|
||||
showMenu={showMenu}
|
||||
showMultiplayerMenu={showMultiplayerMenu}
|
||||
showStyles={showStyles}
|
||||
showZoom={showZoom}
|
||||
sponsor={showSponsorLink}
|
||||
/>
|
||||
<StyledSpacer />
|
||||
{showTools && !readOnly && <ToolsPanel />}
|
||||
</>
|
||||
)}
|
||||
</StyledUI>
|
||||
)}
|
||||
</StyledLayout>
|
||||
<IntlProvider
|
||||
locale={defaultLanguage}
|
||||
messages={messages[defaultLanguage] as IntlConfig['messages']}
|
||||
>
|
||||
<StyledLayout ref={rWrapper} tabIndex={-0} className={settings.isDarkMode ? dark : ''}>
|
||||
<Loading />
|
||||
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
||||
<ContextMenu>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<Renderer
|
||||
id={id}
|
||||
containerRef={rWrapper}
|
||||
shapeUtils={shapeUtils}
|
||||
page={page}
|
||||
pageState={pageState}
|
||||
assets={assets}
|
||||
snapLines={appState.snapLines}
|
||||
eraseLine={appState.eraseLine}
|
||||
grid={GRID_SIZE}
|
||||
users={room?.users}
|
||||
userId={room?.userId}
|
||||
theme={theme}
|
||||
meta={meta}
|
||||
hideBounds={hideBounds}
|
||||
hideHandles={hideHandles}
|
||||
hideResizeHandles={isHideResizeHandlesShape}
|
||||
hideIndicators={hideIndicators}
|
||||
hideBindingHandles={!settings.showBindingHandles}
|
||||
hideCloneHandles={hideCloneHandles}
|
||||
hideRotateHandles={!settings.showRotateHandles}
|
||||
hideGrid={!settings.showGrid}
|
||||
showDashedBrush={showDashedBrush}
|
||||
performanceMode={app.session?.performanceMode}
|
||||
onPinchStart={app.onPinchStart}
|
||||
onPinchEnd={app.onPinchEnd}
|
||||
onPinch={app.onPinch}
|
||||
onPan={app.onPan}
|
||||
onZoom={app.onZoom}
|
||||
onPointerDown={app.onPointerDown}
|
||||
onPointerMove={app.onPointerMove}
|
||||
onPointerUp={app.onPointerUp}
|
||||
onPointCanvas={app.onPointCanvas}
|
||||
onDoubleClickCanvas={app.onDoubleClickCanvas}
|
||||
onRightPointCanvas={app.onRightPointCanvas}
|
||||
onDragCanvas={app.onDragCanvas}
|
||||
onReleaseCanvas={app.onReleaseCanvas}
|
||||
onPointShape={app.onPointShape}
|
||||
onDoubleClickShape={app.onDoubleClickShape}
|
||||
onRightPointShape={app.onRightPointShape}
|
||||
onDragShape={app.onDragShape}
|
||||
onHoverShape={app.onHoverShape}
|
||||
onUnhoverShape={app.onUnhoverShape}
|
||||
onReleaseShape={app.onReleaseShape}
|
||||
onPointBounds={app.onPointBounds}
|
||||
onDoubleClickBounds={app.onDoubleClickBounds}
|
||||
onRightPointBounds={app.onRightPointBounds}
|
||||
onDragBounds={app.onDragBounds}
|
||||
onHoverBounds={app.onHoverBounds}
|
||||
onUnhoverBounds={app.onUnhoverBounds}
|
||||
onReleaseBounds={app.onReleaseBounds}
|
||||
onPointBoundsHandle={app.onPointBoundsHandle}
|
||||
onDoubleClickBoundsHandle={app.onDoubleClickBoundsHandle}
|
||||
onRightPointBoundsHandle={app.onRightPointBoundsHandle}
|
||||
onDragBoundsHandle={app.onDragBoundsHandle}
|
||||
onHoverBoundsHandle={app.onHoverBoundsHandle}
|
||||
onUnhoverBoundsHandle={app.onUnhoverBoundsHandle}
|
||||
onReleaseBoundsHandle={app.onReleaseBoundsHandle}
|
||||
onPointHandle={app.onPointHandle}
|
||||
onDoubleClickHandle={app.onDoubleClickHandle}
|
||||
onRightPointHandle={app.onRightPointHandle}
|
||||
onDragHandle={app.onDragHandle}
|
||||
onHoverHandle={app.onHoverHandle}
|
||||
onUnhoverHandle={app.onUnhoverHandle}
|
||||
onReleaseHandle={app.onReleaseHandle}
|
||||
onError={app.onError}
|
||||
onRenderCountChange={app.onRenderCountChange}
|
||||
onShapeChange={app.onShapeChange}
|
||||
onShapeBlur={app.onShapeBlur}
|
||||
onShapeClone={app.onShapeClone}
|
||||
onBoundsChange={app.updateBounds}
|
||||
onKeyDown={app.onKeyDown}
|
||||
onKeyUp={app.onKeyUp}
|
||||
onDragOver={app.onDragOver}
|
||||
onDrop={app.onDrop}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</ContextMenu>
|
||||
{showUI && (
|
||||
<StyledUI>
|
||||
{settings.isFocusMode ? (
|
||||
<FocusButton onSelect={app.toggleFocusMode} />
|
||||
) : (
|
||||
<>
|
||||
<TopPanel
|
||||
readOnly={readOnly}
|
||||
showPages={showPages}
|
||||
showMenu={showMenu}
|
||||
showMultiplayerMenu={showMultiplayerMenu}
|
||||
showStyles={showStyles}
|
||||
showZoom={showZoom}
|
||||
sponsor={showSponsorLink}
|
||||
/>
|
||||
<StyledSpacer />
|
||||
{showTools && !readOnly && <ToolsPanel />}
|
||||
</>
|
||||
)}
|
||||
</StyledUI>
|
||||
)}
|
||||
</StyledLayout>
|
||||
</IntlProvider>
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import * as React from 'react'
|
||||
import { ContextMenu } from './ContextMenu'
|
||||
import { renderWithContext } from '~test'
|
||||
import { renderWithContext, renderWithIntlProvider } from '~test'
|
||||
|
||||
describe('context menu', () => {
|
||||
test('mounts component without crashing', () => {
|
||||
renderWithContext(
|
||||
<ContextMenu onBlur={jest.fn()}>
|
||||
<div>Hello</div>
|
||||
</ContextMenu>
|
||||
renderWithIntlProvider(
|
||||
<ContextMenu onBlur={jest.fn()}>
|
||||
<div>Hello</div>
|
||||
</ContextMenu>
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -19,6 +19,7 @@ import { Divider } from '~components/Primitives/Divider'
|
|||
import { MenuContent } from '~components/Primitives/MenuContent'
|
||||
import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
|
||||
import { ToolButton, ToolButtonProps } from '~components/Primitives/ToolButton'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
|
||||
const numberOfSelectedIdsSelector = (s: TDSnapshot) => {
|
||||
return s.document.pageStates[s.appState.currentPageId].selectedIds.length
|
||||
|
@ -56,6 +57,7 @@ interface InnerContextMenuProps {
|
|||
|
||||
const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProps) {
|
||||
const app = useTldrawApp()
|
||||
const intl = useIntl()
|
||||
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
|
||||
const isDebugMode = app.useStore(isDebugModeSelector)
|
||||
const hasGroupSelected = app.useStore(hasGroupSelectedSelector)
|
||||
|
@ -171,45 +173,46 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
|
|||
{hasSelection ? (
|
||||
<>
|
||||
<CMRowButton onClick={handleDuplicate} kbd="#D" id="TD-ContextMenu-Duplicate">
|
||||
Duplicate
|
||||
<FormattedMessage id="duplicate" />
|
||||
</CMRowButton>
|
||||
<CMRowButton
|
||||
onClick={handleFlipHorizontal}
|
||||
kbd="⇧H"
|
||||
id="TD-ContextMenu-Flip_Horizontal"
|
||||
>
|
||||
Flip Horizontal
|
||||
<FormattedMessage id="flip.horizontal" />
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleFlipVertical} kbd="⇧V" id="TD-ContextMenu-Flip_Vertical">
|
||||
Flip Vertical
|
||||
<FormattedMessage id="flip.vertical" />
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleLock} kbd="#⇧L" id="TD-ContextMenu- Lock_Unlock">
|
||||
Lock / Unlock
|
||||
<FormattedMessage id="lock" /> / <FormattedMessage id="unlock" />
|
||||
</CMRowButton>
|
||||
{(hasTwoOrMore || hasGroupSelected) && <Divider />}
|
||||
{hasTwoOrMore && (
|
||||
<CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Group">
|
||||
Group
|
||||
<FormattedMessage id="group" />
|
||||
</CMRowButton>
|
||||
)}
|
||||
{hasGroupSelected && (
|
||||
<CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Ungroup">
|
||||
Ungroup
|
||||
<FormattedMessage id="ungroup" />
|
||||
<FormattedMessage id="ungroup" />
|
||||
</CMRowButton>
|
||||
)}
|
||||
<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">
|
||||
To Front
|
||||
<FormattedMessage id="to.front" />
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleMoveForward} kbd="]" id="TD-ContextMenu-Move-Forward">
|
||||
Forward
|
||||
<FormattedMessage id="forward" />
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleMoveBackward} kbd="[" id="TD-ContextMenu-Move-Backward">
|
||||
Backward
|
||||
<FormattedMessage id="backward" />
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleMoveToBack} kbd="⇧[" id="TD-ContextMenu-Move-To_Back">
|
||||
To Back
|
||||
<FormattedMessage id="back" />
|
||||
</CMRowButton>
|
||||
</ContextMenuSubMenu>
|
||||
<MoveToPageMenu />
|
||||
|
@ -218,16 +221,20 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
|
|||
)}
|
||||
<Divider />
|
||||
<CMRowButton onClick={handleCut} kbd="#X" id="TD-ContextMenu-Cut">
|
||||
Cut
|
||||
<FormattedMessage id="cut" />
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleCopy} kbd="#C" id="TD-ContextMenu-Copy">
|
||||
Copy
|
||||
<FormattedMessage id="copy" />
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
|
||||
Paste
|
||||
<FormattedMessage id="paste" />
|
||||
</CMRowButton>
|
||||
<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">
|
||||
SVG
|
||||
</CMRowButton>
|
||||
|
@ -240,7 +247,11 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
|
|||
</CMRowButton>
|
||||
)}
|
||||
</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">
|
||||
SVG
|
||||
</CMRowButton>
|
||||
|
@ -261,19 +272,19 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
|
|||
</ContextMenuSubMenu>
|
||||
<Divider />
|
||||
<CMRowButton onClick={handleDelete} kbd="⌫" id="TD-ContextMenu-Delete">
|
||||
Delete
|
||||
<FormattedMessage id="delete" />
|
||||
</CMRowButton>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
|
||||
Paste
|
||||
<FormattedMessage id="paste" />
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleUndo} kbd="#Z" id="TD-ContextMenu-Undo">
|
||||
Undo
|
||||
<FormattedMessage id="undo" />
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleRedo} kbd="#⇧Z" id="TD-ContextMenu-Redo">
|
||||
Redo
|
||||
<FormattedMessage id="redo" />
|
||||
</CMRowButton>
|
||||
</>
|
||||
)}
|
||||
|
@ -430,7 +441,9 @@ function MoveToPageMenu() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<MenuContent>
|
||||
{sorted.map(({ id, name }, i) => (
|
||||
|
|
|
@ -5,7 +5,7 @@ import { RowButton } from '~components/Primitives/RowButton'
|
|||
import { useTldrawApp } from '~hooks'
|
||||
import { styled } from '~styles'
|
||||
|
||||
export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||
export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps): any {
|
||||
const app = useTldrawApp()
|
||||
|
||||
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 './DMCheckboxItem'
|
||||
export * from './DMContent'
|
||||
|
|
|
@ -32,6 +32,7 @@ import {
|
|||
import { DMContent } from '~components/Primitives/DropdownMenu'
|
||||
import { Divider } from '~components/Primitives/Divider'
|
||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||
import { useIntl } from 'react-intl'
|
||||
|
||||
const selectedShapesCountSelector = (s: TDSnapshot) =>
|
||||
s.document.pageStates[s.appState.currentPageId].selectedIds.length
|
||||
|
@ -74,6 +75,9 @@ const hasMultipleSelectionSelector = (s: TDSnapshot) => {
|
|||
|
||||
export function ActionButton() {
|
||||
const app = useTldrawApp()
|
||||
const intl = useIntl()
|
||||
|
||||
const isFrenchLang = navigator.language === 'fr'
|
||||
|
||||
const isAllLocked = app.useStore(isAllLockedSelector)
|
||||
|
||||
|
@ -189,22 +193,35 @@ export function ActionButton() {
|
|||
<>
|
||||
<ButtonsRow>
|
||||
<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 />
|
||||
</Tooltip>
|
||||
</ToolButton>
|
||||
<ToolButton disabled={!hasSelection} onClick={handleRotate}>
|
||||
<Tooltip label="Rotate" id="TD-Tools-Rotate">
|
||||
<Tooltip label={intl.formatMessage({ id: 'rotate' })} id="TD-Tools-Rotate">
|
||||
<RotateCounterClockwiseIcon />
|
||||
</Tooltip>
|
||||
</ToolButton>
|
||||
<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 />}
|
||||
</Tooltip>
|
||||
</ToolButton>
|
||||
<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 />}
|
||||
</Tooltip>
|
||||
</ToolButton>
|
||||
|
@ -212,34 +229,50 @@ export function ActionButton() {
|
|||
disabled={!hasSelection || (!isAllGrouped && !hasMultipleSelection)}
|
||||
onClick={handleGroup}
|
||||
>
|
||||
<Tooltip label="Group" kbd={`#G`} id="TD-Tools-Group">
|
||||
<Tooltip label={intl.formatMessage({ id: 'group' })} kbd={`#G`} id="TD-Tools-Group">
|
||||
<GroupIcon />
|
||||
</Tooltip>
|
||||
</ToolButton>
|
||||
</ButtonsRow>
|
||||
<ButtonsRow>
|
||||
<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 />
|
||||
</Tooltip>
|
||||
</ToolButton>
|
||||
<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 />
|
||||
</Tooltip>
|
||||
</ToolButton>
|
||||
<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 />
|
||||
</Tooltip>
|
||||
</ToolButton>
|
||||
<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 />
|
||||
</Tooltip>
|
||||
</ToolButton>
|
||||
<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 />
|
||||
</Tooltip>
|
||||
</ToolButton>
|
||||
|
|
|
@ -3,9 +3,11 @@ import { Tooltip } from '~components/Primitives/Tooltip'
|
|||
import { useTldrawApp } from '~hooks'
|
||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||
import { TrashIcon } from '~components/Primitives/icons'
|
||||
import { useIntl } from 'react-intl'
|
||||
|
||||
export function DeleteButton() {
|
||||
const app = useTldrawApp()
|
||||
const intl = useIntl()
|
||||
|
||||
const handleDelete = React.useCallback(() => {
|
||||
app.delete()
|
||||
|
@ -18,7 +20,7 @@ export function DeleteButton() {
|
|||
)
|
||||
|
||||
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}>
|
||||
<TrashIcon />
|
||||
</ToolButton>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react'
|
||||
import { useIntl } from 'react-intl'
|
||||
import {
|
||||
ArrowTopRightIcon,
|
||||
CursorArrowIcon,
|
||||
|
@ -18,6 +19,7 @@ const toolLockedSelector = (s: TDSnapshot) => s.appState.isToolLocked
|
|||
|
||||
export const PrimaryTools = React.memo(function PrimaryTools() {
|
||||
const app = useTldrawApp()
|
||||
const intl = useIntl()
|
||||
|
||||
const activeTool = app.useStore(activeToolSelector)
|
||||
|
||||
|
@ -51,7 +53,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
|||
<Panel side="center" id="TD-PrimaryTools">
|
||||
<ToolButtonWithTooltip
|
||||
kbd={'1'}
|
||||
label={'select'}
|
||||
label={intl.formatMessage({ id: 'select' })}
|
||||
onClick={selectSelectTool}
|
||||
isActive={activeTool === 'select'}
|
||||
id="TD-PrimaryTools-CursorArrow"
|
||||
|
@ -60,7 +62,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
|||
</ToolButtonWithTooltip>
|
||||
<ToolButtonWithTooltip
|
||||
kbd={'2'}
|
||||
label={TDShapeType.Draw}
|
||||
label={intl.formatMessage({ id: 'draw' })}
|
||||
onClick={selectDrawTool}
|
||||
isActive={activeTool === TDShapeType.Draw}
|
||||
id="TD-PrimaryTools-Pencil"
|
||||
|
@ -69,7 +71,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
|||
</ToolButtonWithTooltip>
|
||||
<ToolButtonWithTooltip
|
||||
kbd={'3'}
|
||||
label={'eraser'}
|
||||
label={intl.formatMessage({ id: 'eraser' })}
|
||||
onClick={selectEraseTool}
|
||||
isActive={activeTool === 'erase'}
|
||||
id="TD-PrimaryTools-Eraser"
|
||||
|
@ -79,7 +81,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
|||
<ShapesMenu activeTool={activeTool} isToolLocked={isToolLocked} />
|
||||
<ToolButtonWithTooltip
|
||||
kbd={'8'}
|
||||
label={TDShapeType.Arrow}
|
||||
label={intl.formatMessage({ id: 'arrow' })}
|
||||
onClick={selectArrowTool}
|
||||
isLocked={isToolLocked}
|
||||
isActive={activeTool === TDShapeType.Arrow}
|
||||
|
@ -89,7 +91,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
|||
</ToolButtonWithTooltip>
|
||||
<ToolButtonWithTooltip
|
||||
kbd={'9'}
|
||||
label={TDShapeType.Text}
|
||||
label={intl.formatMessage({ id: 'text' })}
|
||||
onClick={selectTextTool}
|
||||
isLocked={isToolLocked}
|
||||
isActive={activeTool === TDShapeType.Text}
|
||||
|
@ -99,7 +101,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
|
|||
</ToolButtonWithTooltip>
|
||||
<ToolButtonWithTooltip
|
||||
kbd={'0'}
|
||||
label={TDShapeType.Sticky}
|
||||
label={intl.formatMessage({ id: 'sticky' })}
|
||||
onClick={selectStickyTool}
|
||||
isActive={activeTool === TDShapeType.Sticky}
|
||||
id="TD-PrimaryTools-Pencil2"
|
||||
|
|
|
@ -7,6 +7,7 @@ import { useTldrawApp } from '~hooks'
|
|||
import { SquareIcon, CircleIcon, VercelLogoIcon } from '@radix-ui/react-icons'
|
||||
import { Tooltip } from '~components/Primitives/Tooltip'
|
||||
import { LineIcon } from '~components/Primitives/icons'
|
||||
import { useIntl } from 'react-intl'
|
||||
|
||||
interface ShapesMenuProps {
|
||||
activeTool: TDToolType
|
||||
|
@ -44,6 +45,7 @@ export const ShapesMenu = React.memo(function ShapesMenu({
|
|||
isToolLocked,
|
||||
}: ShapesMenuProps) {
|
||||
const app = useTldrawApp()
|
||||
const intl = useIntl()
|
||||
|
||||
const status = app.useStore(statusSelector)
|
||||
|
||||
|
@ -92,7 +94,7 @@ export const ShapesMenu = React.memo(function ShapesMenu({
|
|||
{shapeShapes.map((shape, i) => (
|
||||
<Tooltip
|
||||
key={shape}
|
||||
label={shape[0].toUpperCase() + shape.slice(1)}
|
||||
label={intl.formatMessage({ id: shape[0].toUpperCase() + shape.slice(1) })}
|
||||
kbd={(4 + i).toString()}
|
||||
id={`TD-PrimaryTools-Shapes-${shape}`}
|
||||
>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as React from 'react'
|
||||
import { ToolsPanel } from './ToolsPanel'
|
||||
import { renderWithContext } from '~test'
|
||||
import { renderWithContext, renderWithIntlProvider } from '~test'
|
||||
|
||||
describe('tools panel', () => {
|
||||
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 { TDExportType, TDSnapshot } from '~types'
|
||||
import { Divider } from '~components/Primitives/Divider'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
import { LanguageMenu } from '../LanguageMenu/LanguageMenu'
|
||||
|
||||
interface MenuProps {
|
||||
sponsor: boolean | undefined
|
||||
|
@ -39,6 +41,7 @@ const disableAssetsSelector = (s: TDSnapshot) => {
|
|||
|
||||
export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||
const app = useTldrawApp()
|
||||
const intl = useIntl()
|
||||
|
||||
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
|
||||
|
||||
|
@ -140,38 +143,40 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
</DMTriggerIcon>
|
||||
<DMContent variant="menu" id="TD-Menu">
|
||||
{showFileMenu && (
|
||||
<DMSubMenu label="File..." id="TD-MenuItem-File">
|
||||
<DMSubMenu label={`${intl.formatMessage({ id: 'menu.file' })}...`} id="TD-MenuItem-File">
|
||||
{app.callbacks.onNewProject && (
|
||||
<DMItem onClick={onNewProject} kbd="#N" id="TD-MenuItem-File-New_Project">
|
||||
New Project
|
||||
<FormattedMessage id="new.project" />
|
||||
</DMItem>
|
||||
)}
|
||||
{app.callbacks.onOpenProject && (
|
||||
<DMItem onClick={onOpenProject} kbd="#O" id="TD-MenuItem-File-Open">
|
||||
Open...
|
||||
<FormattedMessage id="open" />
|
||||
...
|
||||
</DMItem>
|
||||
)}
|
||||
{app.callbacks.onSaveProject && (
|
||||
<DMItem onClick={onSaveProject} kbd="#S" id="TD-MenuItem-File-Save">
|
||||
Save
|
||||
<FormattedMessage id="save" />
|
||||
</DMItem>
|
||||
)}
|
||||
{app.callbacks.onSaveProjectAs && (
|
||||
<DMItem onClick={onSaveProjectAs} kbd="#⇧S" id="TD-MenuItem-File-Save_As">
|
||||
Save As...
|
||||
<FormattedMessage id="save.as" />
|
||||
...
|
||||
</DMItem>
|
||||
)}
|
||||
{!disableAssets && (
|
||||
<>
|
||||
<Divider />
|
||||
<DMItem onClick={handleUploadMedia} kbd="#U" id="TD-MenuItem-File-Upload_Media">
|
||||
Upload Media
|
||||
<FormattedMessage id="upload.media" />
|
||||
</DMItem>
|
||||
</>
|
||||
)}
|
||||
</DMSubMenu>
|
||||
)}
|
||||
<DMSubMenu label="Edit..." id="TD-MenuItem-Edit">
|
||||
<DMSubMenu label={`${intl.formatMessage({ id: 'menu.edit' })}...`} id="TD-MenuItem-Edit">
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
onClick={app.undo}
|
||||
|
@ -179,7 +184,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
kbd="#Z"
|
||||
id="TD-MenuItem-Edit-Undo"
|
||||
>
|
||||
Undo
|
||||
<FormattedMessage id="undo" />
|
||||
</DMItem>
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
|
@ -188,7 +193,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
kbd="#⇧Z"
|
||||
id="TD-MenuItem-Edit-Redo"
|
||||
>
|
||||
Redo
|
||||
<FormattedMessage id="redo" />
|
||||
</DMItem>
|
||||
<DMDivider dir="ltr" />
|
||||
<DMItem
|
||||
|
@ -198,7 +203,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
kbd="#X"
|
||||
id="TD-MenuItem-Edit-Cut"
|
||||
>
|
||||
Cut
|
||||
<FormattedMessage id="cut" />
|
||||
</DMItem>
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
|
@ -207,7 +212,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
kbd="#C"
|
||||
id="TD-MenuItem-Edit-Copy"
|
||||
>
|
||||
Copy
|
||||
<FormattedMessage id="copy" />
|
||||
</DMItem>
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
|
@ -215,10 +220,14 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
kbd="#V"
|
||||
id="TD-MenuItem-Edit-Paste"
|
||||
>
|
||||
Paste
|
||||
<FormattedMessage id="paste" />
|
||||
</DMItem>
|
||||
<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">
|
||||
SVG
|
||||
</DMItem>
|
||||
|
@ -229,7 +238,11 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
JSON
|
||||
</DMItem>
|
||||
</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">
|
||||
SVG
|
||||
</DMItem>
|
||||
|
@ -254,7 +267,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
kbd="#A"
|
||||
id="TD-MenuItem-Select_All"
|
||||
>
|
||||
Select All
|
||||
<FormattedMessage id="select.all" />
|
||||
</DMItem>
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
|
@ -262,21 +275,21 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
onClick={handleSelectNone}
|
||||
id="TD-MenuItem-Select_None"
|
||||
>
|
||||
Select None
|
||||
<FormattedMessage id="select.none" />
|
||||
</DMItem>
|
||||
<DMDivider dir="ltr" />
|
||||
<DMItem onSelect={handleDelete} disabled={!hasSelection} kbd="⌫" id="TD-MenuItem-Delete">
|
||||
Delete
|
||||
<FormattedMessage id="delete" />
|
||||
</DMItem>
|
||||
</DMSubMenu>
|
||||
<DMSubMenu label="View" id="TD-MenuItem-Edit">
|
||||
<DMSubMenu label={intl.formatMessage({ id: 'menu.view' })} id="TD-MenuItem-Edit">
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
onClick={app.zoomIn}
|
||||
kbd="#+"
|
||||
id="TD-MenuItem-View-ZoomIn"
|
||||
>
|
||||
Zoom In
|
||||
<FormattedMessage id="zoom.in" />
|
||||
</DMItem>
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
|
@ -284,7 +297,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
kbd="#-"
|
||||
id="TD-MenuItem-View-ZoomOut"
|
||||
>
|
||||
Zoom Out
|
||||
<FormattedMessage id="zoom.out" />
|
||||
</DMItem>
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
|
@ -292,7 +305,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
kbd="⇧+0"
|
||||
id="TD-MenuItem-View-ZoomTo100"
|
||||
>
|
||||
Zoom to 100%
|
||||
<FormattedMessage id="zoom.to" /> 100%
|
||||
</DMItem>
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
|
@ -300,7 +313,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
kbd="⇧+1"
|
||||
id="TD-MenuItem-View-ZoomToFit"
|
||||
>
|
||||
Zoom to Fit
|
||||
<FormattedMessage id="zoom.to.fit" />
|
||||
</DMItem>
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
|
@ -308,12 +321,14 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
kbd="⇧+2"
|
||||
id="TD-MenuItem-View-ZoomToSelection"
|
||||
>
|
||||
Zoom to Selection
|
||||
<FormattedMessage id="zoom.to.selection" />
|
||||
</DMItem>
|
||||
</DMSubMenu>
|
||||
<DMDivider dir="ltr" />
|
||||
<PreferencesMenu />
|
||||
<DMDivider dir="ltr" />
|
||||
<LanguageMenu />
|
||||
<DMDivider dir="ltr" />
|
||||
<a href="https://github.com/Tldraw/Tldraw" target="_blank" rel="nofollow">
|
||||
<DMItem id="TD-MenuItem-Github">
|
||||
GitHub
|
||||
|
@ -341,7 +356,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
{sponsor === false && (
|
||||
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
|
||||
<DMItem isSponsor id="TD-MenuItem-Become_a_Sponsor">
|
||||
Become a Sponsor{' '}
|
||||
<FormattedMessage id="become.a.sponsor" />{' '}
|
||||
<SmallIcon>
|
||||
<HeartIcon />
|
||||
</SmallIcon>
|
||||
|
@ -351,7 +366,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
{sponsor === true && (
|
||||
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
|
||||
<DMItem id="TD-MenuItem-is_a_Sponsor">
|
||||
Sponsored!
|
||||
<FormattedMessage id="sponsored" />!
|
||||
<SmallIcon>
|
||||
<HeartFilledIcon />
|
||||
</SmallIcon>
|
||||
|
@ -363,12 +378,12 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
<DMDivider dir="ltr" />{' '}
|
||||
{app.callbacks.onSignIn && (
|
||||
<DMItem onSelect={handleSignIn} id="TD-MenuItem-Sign_in">
|
||||
Sign In
|
||||
<FormattedMessage id="menu.sign.in" />
|
||||
</DMItem>
|
||||
)}
|
||||
{app.callbacks.onSignOut && (
|
||||
<DMItem onSelect={handleSignOut} id="TD-MenuItem-Sign_out">
|
||||
Sign Out
|
||||
<FormattedMessage id="menu.sign.out" />
|
||||
<SmallIcon>
|
||||
<ExitIcon />
|
||||
</SmallIcon>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { MultiplayerIcon } from '~components/Primitives/icons'
|
|||
import { TDAssetType, TDSnapshot } from '~types'
|
||||
import { TLDR } from '~state/TLDR'
|
||||
import { Utils } from '@tldraw/core'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
|
||||
const roomSelector = (state: TDSnapshot) => state.room
|
||||
|
||||
|
@ -78,7 +79,7 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
|
|||
pageId: app.currentPageId,
|
||||
document: nextDocument,
|
||||
}),
|
||||
}).then(d => d.json())
|
||||
}).then((d) => d.json())
|
||||
|
||||
if (result?.url) {
|
||||
window.location.href = result.url
|
||||
|
@ -99,20 +100,23 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
|
|||
</DMTriggerIcon>
|
||||
<DMContent variant="menu" align="start" id="TD-MultiplayerMenu">
|
||||
<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>
|
||||
<DMDivider id="TD-Multiplayer-CopyInviteLinkDivider" />
|
||||
<DMItem
|
||||
id="TD-Multiplayer-CreateMultiplayerProject"
|
||||
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
|
||||
id="TD-Multiplayer-CopyToMultiplayerProject"
|
||||
onClick={handleCopyToMultiplayerProject}
|
||||
>
|
||||
Copy to Multiplayer Project
|
||||
<FormattedMessage id="copy.multiplayer.project" />
|
||||
</DMItem>
|
||||
</DMContent>
|
||||
</DropdownMenu.Root>
|
||||
|
|
|
@ -9,6 +9,7 @@ import { DMContent, DMDivider } from '~components/Primitives/DropdownMenu'
|
|||
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||
import { RowButton } from '~components/Primitives/RowButton'
|
||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
|
||||
const sortedSelector = (s: TDSnapshot) =>
|
||||
Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))
|
||||
|
@ -102,7 +103,9 @@ function PageMenuContent({ onClose }: { onClose: () => void }) {
|
|||
<DMDivider />
|
||||
<DropdownMenu.Item onSelect={handleCreatePage} asChild>
|
||||
<RowButton>
|
||||
<span>Create Page</span>
|
||||
<span>
|
||||
<FormattedMessage id="create.page" />
|
||||
</span>
|
||||
<SmallIcon>
|
||||
<PlusIcon />
|
||||
</SmallIcon>
|
||||
|
|
|
@ -10,6 +10,7 @@ import { IconButton } from '~components/Primitives/IconButton/IconButton'
|
|||
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||
import { breakpoints } from '~components/breakpoints'
|
||||
import { TextField } from '~components/Primitives/TextField'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
|
||||
const canDeleteSelector = (s: TDSnapshot) => {
|
||||
return Object.keys(s.document.pages).length > 1
|
||||
|
@ -23,6 +24,7 @@ interface PageOptionsDialogProps {
|
|||
|
||||
export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogProps) {
|
||||
const app = useTldrawApp()
|
||||
const intl = useIntl()
|
||||
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
const [pageName, setPageName] = React.useState(page.name || 'Page')
|
||||
|
@ -94,19 +96,23 @@ export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogPr
|
|||
<StyledDialogOverlay onPointerDown={close} />
|
||||
<StyledDialogContent dir="ltr" onKeyDown={stopPropagation} onKeyUp={stopPropagation}>
|
||||
<TextField
|
||||
placeholder="Page name"
|
||||
placeholder={intl.formatMessage({ id: 'page.name' })}
|
||||
value={pageName}
|
||||
onChange={handleRename}
|
||||
icon={<Pencil1Icon />}
|
||||
/>
|
||||
<Divider />
|
||||
<DialogAction onSelect={handleDuplicate}>Duplicate</DialogAction>
|
||||
<DialogAction onSelect={handleDuplicate}>
|
||||
<FormattedMessage id="duplicate" />
|
||||
</DialogAction>
|
||||
<DialogAction disabled={!canDelete} onSelect={handleDelete}>
|
||||
Delete
|
||||
<FormattedMessage id="delete" />
|
||||
</DialogAction>
|
||||
<Divider />
|
||||
<Dialog.Cancel asChild>
|
||||
<RowButton>Cancel</RowButton>
|
||||
<RowButton>
|
||||
<FormattedMessage id="cancel" />
|
||||
</RowButton>
|
||||
</Dialog.Cancel>
|
||||
</StyledDialogContent>
|
||||
</Dialog.Portal>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/Primitives/DropdownMenu'
|
||||
import { useTldrawApp } from '~hooks'
|
||||
import { TDSnapshot } from '~types'
|
||||
|
@ -7,58 +8,59 @@ const settingsSelector = (s: TDSnapshot) => s.settings
|
|||
|
||||
export function PreferencesMenu() {
|
||||
const app = useTldrawApp()
|
||||
const intl = useIntl()
|
||||
|
||||
const settings = app.useStore(settingsSelector)
|
||||
|
||||
const toggleDebugMode = React.useCallback(() => {
|
||||
app.setSetting('isDebugMode', v => !v)
|
||||
app.setSetting('isDebugMode', (v) => !v)
|
||||
}, [app])
|
||||
|
||||
const toggleDarkMode = React.useCallback(() => {
|
||||
app.setSetting('isDarkMode', v => !v)
|
||||
app.setSetting('isDarkMode', (v) => !v)
|
||||
}, [app])
|
||||
|
||||
const toggleFocusMode = React.useCallback(() => {
|
||||
app.setSetting('isFocusMode', v => !v)
|
||||
app.setSetting('isFocusMode', (v) => !v)
|
||||
}, [app])
|
||||
|
||||
const toggleRotateHandle = React.useCallback(() => {
|
||||
app.setSetting('showRotateHandles', v => !v)
|
||||
app.setSetting('showRotateHandles', (v) => !v)
|
||||
}, [app])
|
||||
|
||||
const toggleGrid = React.useCallback(() => {
|
||||
app.setSetting('showGrid', v => !v)
|
||||
app.setSetting('showGrid', (v) => !v)
|
||||
}, [app])
|
||||
|
||||
const toggleBoundShapesHandle = React.useCallback(() => {
|
||||
app.setSetting('showBindingHandles', v => !v)
|
||||
app.setSetting('showBindingHandles', (v) => !v)
|
||||
}, [app])
|
||||
|
||||
const toggleisSnapping = React.useCallback(() => {
|
||||
app.setSetting('isSnapping', v => !v)
|
||||
app.setSetting('isSnapping', (v) => !v)
|
||||
}, [app])
|
||||
|
||||
const toggleKeepStyleMenuOpen = React.useCallback(() => {
|
||||
app.setSetting('keepStyleMenuOpen', v => !v)
|
||||
app.setSetting('keepStyleMenuOpen', (v) => !v)
|
||||
}, [app])
|
||||
|
||||
const toggleCloneControls = React.useCallback(() => {
|
||||
app.setSetting('showCloneHandles', v => !v)
|
||||
app.setSetting('showCloneHandles', (v) => !v)
|
||||
}, [app])
|
||||
|
||||
const toggleCadSelectMode = React.useCallback(() => {
|
||||
app.setSetting('isCadSelectMode', v => !v)
|
||||
app.setSetting('isCadSelectMode', (v) => !v)
|
||||
}, [app])
|
||||
|
||||
return (
|
||||
<DMSubMenu label="Preferences" id="TD-MenuItem-Preferences">
|
||||
<DMSubMenu label={intl.formatMessage({ id: 'menu.preferences' })} id="TD-MenuItem-Preferences">
|
||||
<DMCheckboxItem
|
||||
checked={settings.isDarkMode}
|
||||
onCheckedChange={toggleDarkMode}
|
||||
kbd="#⇧D"
|
||||
id="TD-MenuItem-Preferences-Dark_Mode"
|
||||
>
|
||||
Dark Mode
|
||||
<FormattedMessage id="preferences.dark.mode" />
|
||||
</DMCheckboxItem>
|
||||
<DMCheckboxItem
|
||||
checked={settings.isFocusMode}
|
||||
|
@ -66,14 +68,14 @@ export function PreferencesMenu() {
|
|||
kbd="#."
|
||||
id="TD-MenuItem-Preferences-Focus_Mode"
|
||||
>
|
||||
Focus Mode
|
||||
<FormattedMessage id="preferences.focus.mode" />
|
||||
</DMCheckboxItem>
|
||||
<DMCheckboxItem
|
||||
checked={settings.isDebugMode}
|
||||
onCheckedChange={toggleDebugMode}
|
||||
id="TD-MenuItem-Preferences-Debug_Mode"
|
||||
>
|
||||
Debug Mode
|
||||
<FormattedMessage id="preferences.debug.mode" />
|
||||
</DMCheckboxItem>
|
||||
<DMDivider />
|
||||
<DMCheckboxItem
|
||||
|
@ -82,49 +84,49 @@ export function PreferencesMenu() {
|
|||
kbd="#⇧G"
|
||||
id="TD-MenuItem-Preferences-Grid"
|
||||
>
|
||||
Show Grid
|
||||
<FormattedMessage id="preferences.show.grid" />
|
||||
</DMCheckboxItem>
|
||||
<DMCheckboxItem
|
||||
checked={settings.isCadSelectMode}
|
||||
onCheckedChange={toggleCadSelectMode}
|
||||
id="TD-MenuItem-Preferences-Cad_Selection"
|
||||
>
|
||||
Use CAD Selection
|
||||
<FormattedMessage id="preferences.use.cad.selection" />
|
||||
</DMCheckboxItem>
|
||||
<DMCheckboxItem
|
||||
checked={settings.keepStyleMenuOpen}
|
||||
onCheckedChange={toggleKeepStyleMenuOpen}
|
||||
id="TD-MenuItem-Preferences-Style_menu"
|
||||
>
|
||||
Keep Style Menu Open
|
||||
<FormattedMessage id="preferences.keep.stylemenu.open" />
|
||||
</DMCheckboxItem>
|
||||
<DMCheckboxItem
|
||||
checked={settings.isSnapping}
|
||||
onCheckedChange={toggleisSnapping}
|
||||
id="TD-MenuItem-Preferences-Always_Show_Snaps"
|
||||
>
|
||||
Always Show Snaps
|
||||
<FormattedMessage id="preferences.always.show.snaps" />
|
||||
</DMCheckboxItem>
|
||||
<DMCheckboxItem
|
||||
checked={settings.showRotateHandles}
|
||||
onCheckedChange={toggleRotateHandle}
|
||||
id="TD-MenuItem-Preferences-Rotate_Handles"
|
||||
>
|
||||
Rotate Handles
|
||||
<FormattedMessage id="preferences.rotate.handles" />
|
||||
</DMCheckboxItem>
|
||||
<DMCheckboxItem
|
||||
checked={settings.showBindingHandles}
|
||||
onCheckedChange={toggleBoundShapesHandle}
|
||||
id="TD-MenuItem-Preferences-Binding_Handles"
|
||||
>
|
||||
Binding Handles
|
||||
<FormattedMessage id="preferences.binding.handles" />
|
||||
</DMCheckboxItem>
|
||||
<DMCheckboxItem
|
||||
checked={settings.showCloneHandles}
|
||||
onCheckedChange={toggleCloneControls}
|
||||
id="TD-MenuItem-Preferences-Clone_Handles"
|
||||
>
|
||||
Clone Handles
|
||||
<FormattedMessage id="preferences.clone.handles" />
|
||||
</DMCheckboxItem>
|
||||
</DMSubMenu>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from 'react'
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||
import { strokes, fills, defaultTextStyle } from '~state/shapes/shared/shape-styles'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
import { useTldrawApp } from '~hooks'
|
||||
import {
|
||||
DMCheckboxItem,
|
||||
|
@ -135,9 +136,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
|||
} else {
|
||||
const overrides = new Set<string>([])
|
||||
app.selectedIds
|
||||
.map(id => page.shapes[id])
|
||||
.forEach(shape => {
|
||||
STYLE_KEYS.forEach(key => {
|
||||
.map((id) => page.shapes[id])
|
||||
.forEach((shape) => {
|
||||
STYLE_KEYS.forEach((key) => {
|
||||
if (overrides.has(key)) return
|
||||
if (commonStyle[key] === undefined) {
|
||||
// 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">
|
||||
<ToolButton variant="text">
|
||||
Styles
|
||||
<FormattedMessage id="styles" />
|
||||
<OverlapIcons
|
||||
style={{
|
||||
color: strokes[theme][displayedStyle.color as ColorStyle],
|
||||
|
@ -220,7 +221,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
|||
</DropdownMenu.Trigger>
|
||||
<DMContent>
|
||||
<StyledRow variant="tall" id="TD-Styles-Color-Container">
|
||||
<span>Color</span>
|
||||
<span>
|
||||
<FormattedMessage id="style.menu.color" />
|
||||
</span>
|
||||
<ColorGrid>
|
||||
{Object.keys(strokes.light).map((style: string) => (
|
||||
<DropdownMenu.Item
|
||||
|
@ -253,12 +256,12 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
|||
onCheckedChange={handleToggleFilled}
|
||||
id="TD-Styles-Fill"
|
||||
>
|
||||
Fill
|
||||
<FormattedMessage id="style.menu.fill" />
|
||||
</DMCheckboxItem>
|
||||
<StyledRow id="TD-Styles-Dash-Container">
|
||||
Dash
|
||||
<FormattedMessage id="style.menu.dash" />
|
||||
<StyledGroup dir="ltr" value={displayedStyle.dash} onValueChange={handleDashChange}>
|
||||
{Object.values(DashStyle).map(style => (
|
||||
{Object.values(DashStyle).map((style) => (
|
||||
<DMRadioItem
|
||||
key={style}
|
||||
isActive={style === displayedStyle.dash}
|
||||
|
@ -273,9 +276,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
|||
</StyledGroup>
|
||||
</StyledRow>
|
||||
<StyledRow id="TD-Styles-Size-Container">
|
||||
Size
|
||||
<FormattedMessage id="style.menu.size" />
|
||||
<StyledGroup dir="ltr" value={displayedStyle.size} onValueChange={handleSizeChange}>
|
||||
{Object.values(SizeStyle).map(sizeStyle => (
|
||||
{Object.values(SizeStyle).map((sizeStyle) => (
|
||||
<DMRadioItem
|
||||
key={sizeStyle}
|
||||
isActive={sizeStyle === displayedStyle.size}
|
||||
|
@ -293,9 +296,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
|||
<>
|
||||
<Divider />
|
||||
<StyledRow id="TD-Styles-Font-Container">
|
||||
Font
|
||||
<FormattedMessage id="style.menu.font" />
|
||||
<StyledGroup dir="ltr" value={displayedStyle.font} onValueChange={handleFontChange}>
|
||||
{Object.values(FontStyle).map(fontStyle => (
|
||||
{Object.values(FontStyle).map((fontStyle) => (
|
||||
<DMRadioItem
|
||||
key={fontStyle}
|
||||
isActive={fontStyle === displayedStyle.font}
|
||||
|
@ -311,13 +314,13 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
|||
</StyledRow>
|
||||
{options === 'text' && (
|
||||
<StyledRow id="TD-Styles-Align-Container">
|
||||
Align
|
||||
<FormattedMessage id="style.menu.align" />
|
||||
<StyledGroup
|
||||
dir="ltr"
|
||||
value={displayedStyle.textAlign}
|
||||
onValueChange={handleTextAlignChange}
|
||||
>
|
||||
{Object.values(AlignStyle).map(style => (
|
||||
{Object.values(AlignStyle).map((style) => (
|
||||
<DMRadioItem
|
||||
key={style}
|
||||
isActive={style === displayedStyle.textAlign}
|
||||
|
@ -341,7 +344,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
|||
onCheckedChange={handleToggleKeepOpen}
|
||||
id="TD-Styles-Keep-Open"
|
||||
>
|
||||
Keep Open
|
||||
<FormattedMessage id="style.menu.keep.open" />
|
||||
</DMCheckboxItem>
|
||||
</DMContent>
|
||||
</DropdownMenu.Root>
|
||||
|
|
|
@ -6,6 +6,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
|||
import { DMItem, DMContent } from '~components/Primitives/DropdownMenu'
|
||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||
import { preventEvent } from '~components/preventEvent'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
|
||||
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>
|
||||
<DMContent align="end">
|
||||
<DMItem onSelect={preventEvent} onClick={app.zoomIn} kbd="#+" id="TD-Zoom-Zoom_In">
|
||||
Zoom In
|
||||
<FormattedMessage id="zoom.in" />
|
||||
</DMItem>
|
||||
<DMItem onSelect={preventEvent} onClick={app.zoomOut} kbd="#−" id="TD-Zoom-Zoom_Out">
|
||||
Zoom Out
|
||||
<FormattedMessage id="zoom.out" />
|
||||
</DMItem>
|
||||
<DMItem onSelect={preventEvent} onClick={app.resetZoom} kbd="⇧0" id="TD-Zoom-Zoom_To_100%">
|
||||
To 100%
|
||||
<FormattedMessage id="to" /> 100%
|
||||
</DMItem>
|
||||
<DMItem onSelect={preventEvent} onClick={app.zoomToFit} kbd="⇧1" id="TD-Zoom-To_Fit">
|
||||
To Fit
|
||||
<FormattedMessage id="to.fit" />
|
||||
</DMItem>
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
|
@ -40,7 +41,7 @@ export const ZoomMenu = React.memo(function ZoomMenu() {
|
|||
kbd="⇧2"
|
||||
id="TD-Zoom-To_Selection"
|
||||
>
|
||||
To Selection
|
||||
<FormattedMessage id="to.selection" />
|
||||
</DMItem>
|
||||
</DMContent>
|
||||
</DropdownMenu.Root>
|
||||
|
|
|
@ -22,17 +22,17 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
if (!canHandleEvent(true)) return
|
||||
|
||||
if (app.readOnly) {
|
||||
app.copy(undefined, undefined, e)
|
||||
app.copy(undefined, e)
|
||||
return
|
||||
}
|
||||
|
||||
app.cut(undefined, undefined, e)
|
||||
app.cut(undefined, e)
|
||||
}
|
||||
|
||||
const handleCopy = (e: ClipboardEvent) => {
|
||||
if (!canHandleEvent(true)) return
|
||||
|
||||
app.copy(undefined, undefined, e)
|
||||
app.copy(undefined, e)
|
||||
}
|
||||
|
||||
const handlePaste = (e: ClipboardEvent) => {
|
||||
|
@ -159,7 +159,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'ctrl+shift+d,⌘+shift+d',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent(true)) return
|
||||
app.toggleDarkMode()
|
||||
e.preventDefault()
|
||||
|
@ -192,17 +192,12 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
// File System
|
||||
|
||||
const {
|
||||
onNewProject,
|
||||
onOpenProject,
|
||||
onSaveProject,
|
||||
onSaveProjectAs,
|
||||
onOpenMedia,
|
||||
} = useFileSystemHandlers()
|
||||
const { onNewProject, onOpenProject, onSaveProject, onSaveProjectAs, onOpenMedia } =
|
||||
useFileSystemHandlers()
|
||||
|
||||
useHotkeys(
|
||||
'ctrl+n,⌘+n',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent()) return
|
||||
|
||||
onNewProject(e)
|
||||
|
@ -212,7 +207,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
)
|
||||
useHotkeys(
|
||||
'ctrl+s,⌘+s',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent()) return
|
||||
|
||||
onSaveProject(e)
|
||||
|
@ -223,7 +218,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'ctrl+shift+s,⌘+shift+s',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent()) return
|
||||
|
||||
onSaveProjectAs(e)
|
||||
|
@ -233,7 +228,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
)
|
||||
useHotkeys(
|
||||
'ctrl+o,⌘+o',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent()) return
|
||||
|
||||
onOpenProject(e)
|
||||
|
@ -243,7 +238,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
)
|
||||
useHotkeys(
|
||||
'ctrl+u,⌘+u',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent()) return
|
||||
onOpenMedia(e)
|
||||
},
|
||||
|
@ -311,7 +306,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'ctrl+=,⌘+=,ctrl+num_subtract,⌘+num_subtract',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent(true)) return
|
||||
app.zoomIn()
|
||||
e.preventDefault()
|
||||
|
@ -322,7 +317,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'ctrl+-,⌘+-,ctrl+num_add,⌘+num_add',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent(true)) return
|
||||
|
||||
app.zoomOut()
|
||||
|
@ -366,7 +361,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'ctrl+d,⌘+d',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent()) return
|
||||
|
||||
app.duplicate()
|
||||
|
@ -541,7 +536,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'⌘+shift+c,ctrl+shift+c',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent()) return
|
||||
|
||||
app.copySvg()
|
||||
|
@ -576,7 +571,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'⌘+g,ctrl+g',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent()) return
|
||||
|
||||
app.group()
|
||||
|
@ -588,7 +583,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'⌘+shift+g,ctrl+shift+g',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent()) return
|
||||
|
||||
app.ungroup()
|
||||
|
@ -642,7 +637,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'ctrl+shift+backspace,⌘+shift+backspace',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent()) return
|
||||
if (app.settings.isDebugMode) {
|
||||
app.resetDocument()
|
||||
|
@ -657,7 +652,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'alt+command+l,alt+ctrl+l',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent(true)) return
|
||||
app.style({ textAlign: AlignStyle.Start })
|
||||
e.preventDefault()
|
||||
|
@ -668,7 +663,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'alt+command+t,alt+ctrl+t',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent(true)) return
|
||||
app.style({ textAlign: AlignStyle.Middle })
|
||||
e.preventDefault()
|
||||
|
@ -679,7 +674,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
|
||||
useHotkeys(
|
||||
'alt+command+r,alt+ctrl+r',
|
||||
e => {
|
||||
(e) => {
|
||||
if (!canHandleEvent(true)) return
|
||||
app.style({ textAlign: AlignStyle.End })
|
||||
e.preventDefault()
|
||||
|
|
|
@ -147,7 +147,7 @@ export class StateManager<T extends Record<string, any>> {
|
|||
*/
|
||||
private applyPatch = (patch: Patch<T>, id?: string) => {
|
||||
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)
|
||||
if (this.onStateWillChange) {
|
||||
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.
|
||||
* @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.
|
||||
|
|
|
@ -1713,10 +1713,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
/* Clipboard */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
private getClipboard(
|
||||
ids = this.selectedIds,
|
||||
pageId = this.currentPageId
|
||||
):
|
||||
private getClipboard(ids = this.selectedIds):
|
||||
| {
|
||||
shapes: TDShape[]
|
||||
bindings: TDBinding[]
|
||||
|
@ -1757,10 +1754,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
* Cut (copy and delete) one or more shapes to the clipboard.
|
||||
* @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()
|
||||
|
||||
this.copy(ids, pageId, e)
|
||||
this.copy(ids, e)
|
||||
if (!this.readOnly) {
|
||||
this.delete(ids)
|
||||
}
|
||||
|
@ -1771,10 +1768,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
* Copy one or more shapes to the clipboard.
|
||||
* @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()
|
||||
|
||||
this.clipboard = this.getClipboard(ids, pageId)
|
||||
this.clipboard = this.getClipboard(ids)
|
||||
|
||||
const jsonString = JSON.stringify({
|
||||
type: 'tldr/clipboard',
|
||||
|
@ -1962,9 +1959,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
}
|
||||
|
||||
if (e !== undefined) {
|
||||
let items = e.clipboardData?.items ?? []
|
||||
for (var index in items) {
|
||||
var item = items[index]
|
||||
const items = e.clipboardData?.items ?? []
|
||||
for (const index in items) {
|
||||
const item = items[index]
|
||||
|
||||
// TODO
|
||||
// We could eventually support pasting multiple files / images,
|
||||
|
@ -1989,7 +1986,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
return
|
||||
}
|
||||
case 'file': {
|
||||
var file = item.getAsFile()
|
||||
const file = item.getAsFile()
|
||||
if (file) {
|
||||
this.addMediaFromFile(file)
|
||||
return
|
||||
|
@ -2386,7 +2383,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
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)
|
||||
|
||||
|
@ -2977,7 +2974,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
// Ensure that the pasted shape fits inside of the current viewport
|
||||
|
||||
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[1] = size[0] * r
|
||||
if (size[1] < 32 || size[1] < 32) {
|
||||
|
@ -2985,7 +2982,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
size[0] = size[1] / r
|
||||
}
|
||||
} 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[0] = size[1] * r
|
||||
if (size[1] < 32 || size[1] < 32) {
|
||||
|
@ -4093,6 +4090,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
showBindingHandles: true,
|
||||
showCloneHandles: false,
|
||||
showGrid: false,
|
||||
language: 'en',
|
||||
},
|
||||
appState: {
|
||||
status: TDStatus.Idle,
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import {
|
||||
TLPageState,
|
||||
Utils,
|
||||
TLBoundsWithCenter,
|
||||
TLSnapLine,
|
||||
TLBounds,
|
||||
TLPerformanceMode,
|
||||
} from '@tldraw/core'
|
||||
import { TLPageState, Utils, TLBoundsWithCenter, TLSnapLine, TLBounds } from '@tldraw/core'
|
||||
import { Vec } from '@tldraw/vec'
|
||||
import {
|
||||
TDShape,
|
||||
|
|
|
@ -277,7 +277,7 @@ export class DrawUtil extends TDShapeUtil<T, E> {
|
|||
const bounds = this.getBounds(shape)
|
||||
|
||||
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)) {
|
||||
|
|
|
@ -146,7 +146,7 @@ export class TextUtil extends TDShapeUtil<T, E> {
|
|||
)
|
||||
|
||||
const handlePointerDown = React.useCallback(
|
||||
(e) => {
|
||||
(e: React.PointerEvent<HTMLDivElement | HTMLTextAreaElement>) => {
|
||||
if (isEditing) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from 'react'
|
||||
import { stopPropagation } from '~components/stopPropagation'
|
||||
import { GHOSTED_OPACITY, LETTER_SPACING } from '~constants'
|
||||
import { GHOSTED_OPACITY, LETTER_SPACING } from '~constants'
|
||||
import { TLDR } from '~state/TLDR'
|
||||
import { styled } from '~styles'
|
||||
import { getTextLabelSize } from './getTextSize'
|
||||
|
@ -95,7 +95,7 @@ export const TextLabel = React.memo(function TextLabel({
|
|||
)
|
||||
|
||||
const handlePointerDown = React.useCallback(
|
||||
(e) => {
|
||||
(e: React.PointerEvent<HTMLTextAreaElement | HTMLDivElement>) => {
|
||||
if (isEditing) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import {
|
|||
TLKeyboardEventHandler,
|
||||
TLPinchEventHandler,
|
||||
TLPointerEventHandler,
|
||||
TLWheelEventHandler,
|
||||
Utils,
|
||||
} from '@tldraw/core'
|
||||
import type { TldrawApp } from '../internal'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './mockDocument'
|
||||
export * from './renderWithContext'
|
||||
export * from './TldrawTestApp'
|
||||
export * from './renderWithIntlProvider'
|
||||
|
|
18
packages/tldraw/src/test/renderWithIntlProvider.tsx
Normal file
18
packages/tldraw/src/test/renderWithIntlProvider.tsx
Normal file
|
@ -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>
|
||||
)
|
||||
}
|
99
packages/tldraw/src/translations/en.json
Normal file
99
packages/tldraw/src/translations/en.json
Normal file
|
@ -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"
|
||||
}
|
98
packages/tldraw/src/translations/fr.json
Normal file
98
packages/tldraw/src/translations/fr.json
Normal file
|
@ -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"
|
||||
}
|
98
packages/tldraw/src/translations/it.json
Normal file
98
packages/tldraw/src/translations/it.json
Normal file
|
@ -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
|
||||
}
|
||||
|
||||
export type TDLanguage = 'en' | 'fr' | 'it'
|
||||
|
||||
// The shape of the TldrawApp's React (zustand) store
|
||||
export interface TDSnapshot {
|
||||
settings: {
|
||||
|
@ -94,6 +96,7 @@ export interface TDSnapshot {
|
|||
showBindingHandles: boolean
|
||||
showCloneHandles: boolean
|
||||
showGrid: boolean
|
||||
language: TDLanguage
|
||||
}
|
||||
appState: {
|
||||
currentStyle: ShapeStyles
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"exclude": ["node_modules", "dist", "docs"],
|
||||
"include": ["src"],
|
||||
"include": ["src", "./src/translations/*.json"],
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
|
|
Loading…
Reference in a new issue