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:
Judicael 2022-06-09 17:33:35 +03:00 committed by GitHub
parent 7c08f2f5b6
commit d919bd273e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 1387 additions and 694 deletions

View 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
View 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"
]
}

View file

@ -5,3 +5,5 @@ node_modules
*.d.ts
*.js
*.md
*.lock
*.tsbuildinfo

View file

@ -0,0 +1,6 @@
# @tldraw/electron
## 1.7.0-next.0
### Minor Changes
- Bump dependencies, add international support.

View file

@ -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",

View file

@ -1,5 +1,11 @@
# @tldraw/vscode-editor
## 1.11.0-next.0
### Minor Changes
- Bump dependencies, add international support.
## 1.10.2
### Patch Changes

View file

@ -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"
}

View file

@ -1,5 +1,11 @@
## 1.2.4
## 1.15.0-next.0
### Minor Changes
- Bump dependencies, add international support.
## 1.14.1
### Patch Changes

View file

@ -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"

View file

@ -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

View file

@ -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,

View file

@ -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'

View file

@ -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))
}
}

View file

@ -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"
}

View file

@ -0,0 +1,6 @@
# @tldraw/core-example-advanced
## 1.7.0-next.0
### Minor Changes
- Bump dependencies, add international support.

View file

@ -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"
}

View file

@ -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>
)

View file

@ -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.

View file

@ -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"
}

View file

@ -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>
)

View file

@ -0,0 +1,6 @@
# @tldraw/tldraw-example
## 1.7.0-next.0
### Minor Changes
- Bump dependencies, add international support.

View file

@ -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"
}

View file

@ -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

View file

@ -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>
)

View file

@ -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": {

View file

@ -1,5 +1,11 @@
# Changelog
## 1.14.0-next.0
### Minor Changes
- Bump dependencies, add international support.
## 1.13.1
### Patch Changes

View file

@ -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": [

View file

@ -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

View file

@ -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": [

View file

@ -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>
)
})

View file

@ -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>
)
)
})
})

View file

@ -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) => (

View file

@ -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 = () => {

View file

@ -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 })

View file

@ -1,4 +1,3 @@
export * from './DMArrow'
export * from './DMItem'
export * from './DMCheckboxItem'
export * from './DMContent'

View file

@ -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>

View file

@ -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>

View file

@ -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"

View file

@ -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}`}
>

View file

@ -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} />))
})
})

View file

@ -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>
)
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>
)

View file

@ -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>

View file

@ -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>

View file

@ -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()

View file

@ -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.

View file

@ -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,

View file

@ -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,

View file

@ -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)) {

View file

@ -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()
}

View file

@ -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()
}

View file

@ -2,7 +2,6 @@ import {
TLKeyboardEventHandler,
TLPinchEventHandler,
TLPointerEventHandler,
TLWheelEventHandler,
Utils,
} from '@tldraw/core'
import type { TldrawApp } from '../internal'

View file

@ -1,3 +1,4 @@
export * from './mockDocument'
export * from './renderWithContext'
export * from './TldrawTestApp'
export * from './renderWithIntlProvider'

View 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>
)
}

View 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"
}

View 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"
}

View 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"
}

View file

@ -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

View file

@ -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",

707
yarn.lock

File diff suppressed because it is too large Load diff