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>
pull/714/head
Judicael 2022-06-09 17:33:35 +03:00 zatwierdzone przez GitHub
rodzic 7c08f2f5b6
commit d919bd273e
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
64 zmienionych plików z 1387 dodań i 694 usunięć

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -4,4 +4,6 @@ dist
node_modules node_modules
*.d.ts *.d.ts
*.js *.js
*.md *.md
*.lock
*.tsbuildinfo

Wyświetl plik

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

Wyświetl plik

@ -1,6 +1,6 @@
{ {
"name": "@tldraw/electron", "name": "@tldraw/electron",
"version": "1.6.1", "version": "1.7.0-next.0",
"private": true, "private": true,
"description": "An electron app for tldraw.", "description": "An electron app for tldraw.",
"author": "@steveruizok", "author": "@steveruizok",
@ -18,7 +18,7 @@
"package": "electron-builder" "package": "electron-builder"
}, },
"devDependencies": { "devDependencies": {
"@tldraw/tldraw": "^1.6.1", "@tldraw/tldraw": "^1.16.0-next.0",
"@types/node": "^17.0.14", "@types/node": "^17.0.14",
"@types/react": "^17.0.38", "@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11", "@types/react-dom": "^17.0.11",
@ -32,7 +32,7 @@
"react": "^17.0", "react": "^17.0",
"react-dom": "^17.0", "react-dom": "^17.0",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"typescript": "4.5.5" "typescript": "^4.7.3"
}, },
"build": { "build": {
"appId": "io.comp.tldraw-electron", "appId": "io.comp.tldraw-electron",

Wyświetl plik

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

Wyświetl plik

@ -1,6 +1,6 @@
{ {
"name": "@tldraw/vscode-editor", "name": "@tldraw/vscode-editor",
"version": "1.10.2", "version": "1.11.0-next.0",
"private": true, "private": true,
"description": "An an editor for the tldraw vscode extension.", "description": "An an editor for the tldraw vscode extension.",
"author": "@steveruizok", "author": "@steveruizok",
@ -18,18 +18,18 @@
"devDependencies": { "devDependencies": {
"@tldraw/tldraw": "*", "@tldraw/tldraw": "*",
"@types/node": "^17.0.14", "@types/node": "^17.0.14",
"@types/react": "^17.0.38", "@types/react": "^18.0.12",
"@types/react-dom": "^17.0.11", "@types/react-dom": "^18.0.5",
"@types/react-router-dom": "^5.1.8", "@types/react-router-dom": "^5.1.8",
"concurrently": "7.0.0", "concurrently": "7.0.0",
"create-serve": "1.0.1", "create-serve": "1.0.1",
"esbuild": "^0.14.38", "esbuild": "^0.14.38",
"esbuild-serve": "^1.0.1", "esbuild-serve": "^1.0.1",
"react": "^17.0", "react": "^18.1.0",
"react-dom": "^17.0", "react-dom": "^18.1.0",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"tslib": "^2.3.1", "tslib": "^2.4.0",
"typescript": "4.5.5" "typescript": "^4.7.3"
}, },
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6" "gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
} }

Wyświetl plik

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

Wyświetl plik

@ -2,7 +2,7 @@
"name": "tldraw-vscode", "name": "tldraw-vscode",
"displayName": "tldraw", "displayName": "tldraw",
"description": "The tldraw Extension for VS Code.", "description": "The tldraw Extension for VS Code.",
"version": "1.14.1", "version": "1.15.0-next.0",
"license": "MIT", "license": "MIT",
"publisher": "tldraw-org", "publisher": "tldraw-org",
"repository": { "repository": {
@ -128,9 +128,9 @@
"mocha": "^9.1.1", "mocha": "^9.1.1",
"process": "^0.11.10", "process": "^0.11.10",
"ts-loader": "^9.2.5", "ts-loader": "^9.2.5",
"tslib": "^2.3.1", "tslib": "^2.4.0",
"typescript": "^4.4.3", "typescript": "^4.7.3",
"vsce": "^2.2.0" "vsce": "^2.2.0"
}, },
"gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296" "gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296"
} }

Wyświetl plik

@ -1,5 +1,17 @@
# @tldraw/www # @tldraw/www
## 1.7.0-next.0
### Minor Changes
- Bump dependencies, add international support.
### Patch Changes
- Updated dependencies
- @tldraw/core@1.14.0-next.0
- @tldraw/tldraw@1.16.0-next.0
## 1.6.7 ## 1.6.7
### Patch Changes ### Patch Changes

Wyświetl plik

@ -1,7 +1,7 @@
import { Tldraw, TldrawApp, TldrawProps, useFileSystem } from '@tldraw/tldraw' import { Tldraw, TldrawApp, TldrawProps, useFileSystem } from '@tldraw/tldraw'
import { useAccountHandlers } from 'hooks/useAccountHandlers' import { useAccountHandlers } from 'hooks/useAccountHandlers'
import { useUploadAssets } from 'hooks/useUploadAssets' import { useUploadAssets } from 'hooks/useUploadAssets'
import React, { FC } from 'react' import * as React from 'react'
import * as gtag from 'utils/gtag' import * as gtag from 'utils/gtag'
declare const window: Window & { app: TldrawApp } declare const window: Window & { app: TldrawApp }
@ -12,7 +12,7 @@ interface EditorProps {
isSponsor?: boolean isSponsor?: boolean
} }
const Editor: FC<EditorProps & Partial<TldrawProps>> = ({ const Editor: React.FC<EditorProps & Partial<TldrawProps>> = ({
id = 'home', id = 'home',
isUser = false, isUser = false,
isSponsor = false, isSponsor = false,

Wyświetl plik

@ -1,6 +1,6 @@
import { createClient } from '@liveblocks/client' import { createClient } from '@liveblocks/client'
import { LiveblocksProvider, RoomProvider } from '@liveblocks/react' import { LiveblocksProvider, RoomProvider } from '@liveblocks/react'
import { Tldraw, TldrawApp, useFileSystem } from '@tldraw/tldraw' import { Tldraw, useFileSystem } from '@tldraw/tldraw'
import { useAccountHandlers } from 'hooks/useAccountHandlers' import { useAccountHandlers } from 'hooks/useAccountHandlers'
import { useMultiplayerAssets } from 'hooks/useMultiplayerAssets' import { useMultiplayerAssets } from 'hooks/useMultiplayerAssets'
import { useMultiplayerState } from 'hooks/useMultiplayerState' import { useMultiplayerState } from 'hooks/useMultiplayerState'

Wyświetl plik

@ -3,10 +3,19 @@
import React, { useState, useRef, useCallback } from 'react' import React, { useState, useRef, useCallback } from 'react'
import type { TldrawApp, TDUser, TDShape, TDBinding, TDDocument, TDAsset } from '@tldraw/tldraw' import type { TldrawApp, TDUser, TDShape, TDBinding, TDDocument, TDAsset } from '@tldraw/tldraw'
import { useRedo, useUndo, useRoom, useUpdateMyPresence } from '@liveblocks/react' import { useRedo, useUndo, useRoom, useUpdateMyPresence } from '@liveblocks/react'
import { LiveMap, LiveObject } from '@liveblocks/client' import { LiveMap, LiveObject, Lson, LsonObject } from '@liveblocks/client'
declare const window: Window & { app: TldrawApp } declare const window: Window & { app: TldrawApp }
type TDLsonShape = TDShape & Lson
type TDLsonBinding = TDBinding & Lson
type TDLsonAsset = TDAsset & Lson
type LsonDoc = {
uuid: string
document: TDDocument
migrated?: boolean
} & LsonObject
export function useMultiplayerState(roomId: string) { export function useMultiplayerState(roomId: string) {
const [app, setApp] = useState<TldrawApp>() const [app, setApp] = useState<TldrawApp>()
const [error, setError] = useState<Error>() const [error, setError] = useState<Error>()
@ -17,9 +26,9 @@ export function useMultiplayerState(roomId: string) {
const onRedo = useRedo() const onRedo = useRedo()
const updateMyPresence = useUpdateMyPresence() const updateMyPresence = useUpdateMyPresence()
const rLiveShapes = useRef<LiveMap<string, TDShape>>() const rLiveShapes = useRef<LiveMap<string, TDLsonShape>>()
const rLiveBindings = useRef<LiveMap<string, TDBinding>>() const rLiveBindings = useRef<LiveMap<string, TDLsonBinding>>()
const rLiveAssets = useRef<LiveMap<string, TDAsset>>() const rLiveAssets = useRef<LiveMap<string, TDLsonAsset>>()
// Callbacks -------------- // Callbacks --------------
@ -53,7 +62,7 @@ export function useMultiplayerState(roomId: string) {
if (!shape) { if (!shape) {
lShapes.delete(id) lShapes.delete(id)
} else { } else {
lShapes.set(shape.id, shape) lShapes.set(shape.id, shape as TDLsonShape)
} }
}) })
@ -61,7 +70,7 @@ export function useMultiplayerState(roomId: string) {
if (!binding) { if (!binding) {
lBindings.delete(id) lBindings.delete(id)
} else { } else {
lBindings.set(binding.id, binding) lBindings.set(binding.id, binding as TDLsonBinding)
} }
}) })
@ -69,7 +78,7 @@ export function useMultiplayerState(roomId: string) {
if (!asset) { if (!asset) {
lAssets.delete(id) lAssets.delete(id)
} else { } else {
lAssets.set(asset.id, asset) lAssets.set(asset.id, asset as TDLsonAsset)
} }
}) })
}) })
@ -95,7 +104,7 @@ export function useMultiplayerState(roomId: string) {
// Handle changes to other users' presence // Handle changes to other users' presence
unsubs.push( unsubs.push(
room.subscribe('others', (others, event) => { room.subscribe<{ id: string; user: TDUser }>('others', (others, event) => {
if (event.type === 'leave') { if (event.type === 'leave') {
if (event.user.presence) { if (event.user.presence) {
app?.removeUser(event.user.presence.id) app?.removeUser(event.user.presence.id)
@ -123,23 +132,23 @@ export function useMultiplayerState(roomId: string) {
// Initialize (get or create) maps for shapes/bindings/assets // Initialize (get or create) maps for shapes/bindings/assets
let lShapes: LiveMap<string, TDShape> = storage.root.get('shapes') let lShapes: LiveMap<string, TDLsonShape> = storage.root.get('shapes')
if (!lShapes || !('_serialize' in lShapes)) { if (!lShapes || !('_serialize' in lShapes)) {
storage.root.set('shapes', new LiveMap<string, TDShape>()) storage.root.set('shapes', new LiveMap<string, TDLsonShape>())
lShapes = storage.root.get('shapes') lShapes = storage.root.get('shapes')
} }
rLiveShapes.current = lShapes rLiveShapes.current = lShapes
let lBindings: LiveMap<string, TDBinding> = storage.root.get('bindings') let lBindings: LiveMap<string, TDLsonBinding> = storage.root.get('bindings')
if (!lBindings || !('_serialize' in lBindings)) { if (!lBindings || !('_serialize' in lBindings)) {
storage.root.set('bindings', new LiveMap<string, TDBinding>()) storage.root.set('bindings', new LiveMap<string, TDLsonBinding>())
lBindings = storage.root.get('bindings') lBindings = storage.root.get('bindings')
} }
rLiveBindings.current = lBindings rLiveBindings.current = lBindings
let lAssets: LiveMap<string, TDAsset> = storage.root.get('assets') let lAssets: LiveMap<string, TDLsonAsset> = storage.root.get('assets')
if (!lAssets || !('_serialize' in lAssets)) { if (!lAssets || !('_serialize' in lAssets)) {
storage.root.set('assets', new LiveMap<string, TDAsset>()) storage.root.set('assets', new LiveMap<string, TDLsonAsset>())
lAssets = storage.root.get('assets') lAssets = storage.root.get('assets')
} }
rLiveAssets.current = lAssets rLiveAssets.current = lAssets
@ -150,11 +159,7 @@ export function useMultiplayerState(roomId: string) {
// document was a single LiveObject named 'doc'. If we find a doc, // document was a single LiveObject named 'doc'. If we find a doc,
// then we need to move the shapes and bindings over to the new structures // then we need to move the shapes and bindings over to the new structures
// and then mark the doc as migrated. // and then mark the doc as migrated.
const doc = storage.root.get('doc') as LiveObject<{ const doc = storage.root.get('doc') as LiveObject<LsonDoc>
uuid: string
document: TDDocument
migrated?: boolean
}>
// No doc? No problem. This was likely a newer document // No doc? No problem. This was likely a newer document
if (doc) { if (doc) {
@ -167,9 +172,11 @@ export function useMultiplayerState(roomId: string) {
}, },
} = doc.toObject() } = doc.toObject()
Object.values(shapes).forEach((shape) => lShapes.set(shape.id, shape)) Object.values(shapes).forEach((shape) => lShapes.set(shape.id, shape as TDLsonShape))
Object.values(bindings).forEach((binding) => lBindings.set(binding.id, binding)) Object.values(bindings).forEach((binding) =>
Object.values(assets).forEach((asset) => lAssets.set(asset.id, asset)) lBindings.set(binding.id, binding as TDLsonBinding)
)
Object.values(assets).forEach((asset) => lAssets.set(asset.id, asset as TDLsonAsset))
} }
} }

Wyświetl plik

@ -1,6 +1,6 @@
{ {
"name": "@tldraw/www", "name": "@tldraw/www",
"version": "1.6.7", "version": "1.7.0-next.0",
"private": true, "private": true,
"description": "A tiny little drawing app (site).", "description": "A tiny little drawing app (site).",
"repository": { "repository": {
@ -18,33 +18,31 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@liveblocks/client": "^0.14.0", "@sentry/webpack-plugin": "^1.17.1",
"@liveblocks/react": "^0.14.0", "@types/next-auth": "^3.15.0",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"eslint": "^8.8.0",
"eslint-config-next": "^12.0.10",
"typescript": "^4.7.3",
"@liveblocks/client": "^0.16.17",
"@liveblocks/react": "^0.16.17",
"@sentry/integrations": "^6.13.2", "@sentry/integrations": "^6.13.2",
"@sentry/node": "^6.13.2", "@sentry/node": "^6.13.2",
"@sentry/react": "^6.13.2", "@sentry/react": "^6.13.2",
"@sentry/tracing": "^6.13.2", "@sentry/tracing": "^6.13.2",
"@stitches/react": "^1.2.5", "@stitches/react": "^1.2.8",
"@tldraw/core": "*", "@tldraw/core": "*",
"@tldraw/tldraw": "*", "@tldraw/tldraw": "*",
"aws-sdk": "^2.1053.0", "aws-sdk": "^2.1053.0",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",
"nanoid": "^3.3.4", "nanoid": "^3.3.4",
"next": "^12.0.7", "next": "^12.1.6",
"next-auth": "^4.0.5", "next-auth": "^4.0.5",
"next-pwa": "^5.4.4", "next-pwa": "^5.5.4",
"next-themes": "^0.0.15", "next-themes": "^0.0.15",
"react": "^17.0", "react": "^18.1.0",
"react-dom": "^17.0" "react-dom": "^18.1.0"
},
"devDependencies": {
"@types/next-auth": "^3.15.0",
"@sentry/webpack-plugin": "^1.17.1",
"@types/react": "^17.0.19",
"@types/react-dom": "^17.0.9",
"eslint": "^8.8.0",
"eslint-config-next": "^12.0.10",
"typescript": "^4.5.2"
}, },
"gitHead": "838fabdbff1a66d4d7ee8aa5c5d117bc55acbff2" "gitHead": "838fabdbff1a66d4d7ee8aa5c5d117bc55acbff2"
} }

Wyświetl plik

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

Wyświetl plik

@ -1,5 +1,5 @@
{ {
"version": "1.6.1", "version": "1.7.0-next.0",
"name": "@tldraw/core-example-advanced", "name": "@tldraw/core-example-advanced",
"description": "An advanced example project for @tldraw/core.", "description": "An advanced example project for @tldraw/core.",
"author": "@steveruizok", "author": "@steveruizok",
@ -22,8 +22,6 @@
"@tldraw/intersect": "*", "@tldraw/intersect": "*",
"@tldraw/vec": "*", "@tldraw/vec": "*",
"@types/node": "^17.0.14", "@types/node": "^17.0.14",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"concurrently": "^7.0.0", "concurrently": "^7.0.0",
"create-serve": "^1.0.1", "create-serve": "^1.0.1",
@ -33,11 +31,13 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nanoid": "^3.1.31", "nanoid": "^3.1.31",
"perfect-freehand": "^1.1.0", "perfect-freehand": "^1.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-feather": "^2.0.9", "react-feather": "^2.0.9",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "^4.6.4" "typescript": "^4.7.3",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"react": "^18.1.0",
"react-dom": "^18.1.0"
}, },
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6" "gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
} }

Wyświetl plik

@ -1,10 +1,13 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import { createRoot } from 'react-dom/client'
import App from './app' import App from './app'
import './styles.css'
ReactDOM.render( const container = document.getElementById('root')!
const root = createRoot(container)
root.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>
document.getElementById('root')
) )

Wyświetl plik

@ -1,6 +1,13 @@
# @tldraw/core-example-simple # @tldraw/core-example-simple
## 1.8.0-next.0
### Minor Changes
- Bump dependencies, add international support.
## 1.7.0 ## 1.7.0
### Minor Changes ### Minor Changes
- Fix build error in extension. - Fix build error in extension.

Wyświetl plik

@ -1,5 +1,5 @@
{ {
"version": "1.7.0", "version": "1.8.0-next.0",
"name": "@tldraw/core-example-simple", "name": "@tldraw/core-example-simple",
"description": "A simple example project for @tldraw/core.", "description": "A simple example project for @tldraw/core.",
"author": "@steveruizok", "author": "@steveruizok",
@ -18,18 +18,18 @@
"@tldraw/core": "*", "@tldraw/core": "*",
"@tldraw/vec": "*", "@tldraw/vec": "*",
"@types/node": "^17.0.14", "@types/node": "^17.0.14",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"concurrently": "^7.0.0", "concurrently": "^7.0.0",
"esbuild": "^0.14.18", "esbuild": "^0.14.18",
"esbuild-serve": "^1.0.1", "esbuild-serve": "^1.0.1",
"mobx": "^6.3.13", "mobx": "^6.3.13",
"mobx-react-lite": "^3.2.3", "mobx-react-lite": "^3.2.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "^4.6.4" "typescript": "^4.7.3",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"react": "^18.1.0",
"react-dom": "^18.1.0"
}, },
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6" "gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
} }

Wyświetl plik

@ -1,11 +1,13 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import { createRoot } from 'react-dom/client'
import App from './app' import App from './app'
import './styles.css' import './styles.css'
ReactDOM.render( const container = document.getElementById('root')!
const root = createRoot(container)
root.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>
document.getElementById('root')
) )

Wyświetl plik

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

Wyświetl plik

@ -1,6 +1,6 @@
{ {
"name": "@tldraw/tldraw-example", "name": "@tldraw/tldraw-example",
"version": "1.6.1", "version": "1.7.0-next.0",
"private": true, "private": true,
"description": "An example project for @tldraw/tldraw.", "description": "An example project for @tldraw/tldraw.",
"author": "@steveruizok", "author": "@steveruizok",
@ -18,8 +18,6 @@
"@liveblocks/client": "^0.14.0", "@liveblocks/client": "^0.14.0",
"@liveblocks/react": "^0.14.0", "@liveblocks/react": "^0.14.0",
"@types/node": "^17.0.14", "@types/node": "^17.0.14",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"concurrently": "^7.0.0", "concurrently": "^7.0.0",
"create-serve": "^1.0.1", "create-serve": "^1.0.1",
@ -27,12 +25,14 @@
"esbuild-envfile-plugin": "^1.0.2", "esbuild-envfile-plugin": "^1.0.2",
"esbuild-serve": "^1.0.1", "esbuild-serve": "^1.0.1",
"firebase": "^9.6.5", "firebase": "^9.6.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router": "^6.2.1", "react-router": "^6.2.1",
"react-router-dom": "^6.2.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "^4.6.4" "typescript": "^4.7.3",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-router-dom": "^6.3.0"
}, },
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6" "gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
} }

Wyświetl plik

@ -55,7 +55,9 @@ export default function App() {
<main> <main>
<Routes> <Routes>
{pages.map((page) => {pages.map((page) =>
page === '---' ? null : <Route path={page.path} element={<page.component />} /> page === '---' ? null : (
<Route key={page.path} path={page.path} element={<page.component />} />
)
)} )}
<Route <Route

Wyświetl plik

@ -1,13 +1,16 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import { createRoot } from 'react-dom/client'
import App from './app' import App from './app'
import { HashRouter } from 'react-router-dom' import { HashRouter } from 'react-router-dom'
ReactDOM.render( const container = document.getElementById('root')!
const root = createRoot(container)
root.render(
<React.StrictMode> <React.StrictMode>
<HashRouter> <HashRouter>
<App /> <App />
</HashRouter> </HashRouter>
</React.StrictMode>, </React.StrictMode>
document.getElementById('root')
) )

Wyświetl plik

@ -61,14 +61,12 @@
"mobx": "^6.3.8", "mobx": "^6.3.8",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"react": "^17.0",
"react-dom": "^17.0",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"source-map-loader": "^3.0.1", "source-map-loader": "^3.0.1",
"tslib": "^2.3.1", "tslib": "^2.4.0",
"turbo": "^1.1.2", "turbo": "^1.1.2",
"typedoc": "^0.22.15", "typedoc": "^0.22.15",
"typescript": "^4.6.4", "typescript": "^4.7.3",
"webpack": "^5.68.0" "webpack": "^5.68.0"
}, },
"husky": { "husky": {

Wyświetl plik

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

Wyświetl plik

@ -1,5 +1,5 @@
{ {
"version": "1.13.1", "version": "1.14.0-next.0",
"name": "@tldraw/core", "name": "@tldraw/core",
"description": "The tldraw core renderer and utilities.", "description": "The tldraw core renderer and utilities.",
"author": "@steveruizok", "author": "@steveruizok",
@ -46,7 +46,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"react": ">=16.8", "react": ">=16.8",
"react-dom": "^16.8 || ^17.0" "react-dom": ">=16.8"
}, },
"devDependencies": { "devDependencies": {
"@swc-node/jest": "^1.4.3", "@swc-node/jest": "^1.4.3",
@ -55,14 +55,15 @@
"@tldraw/intersect": "*", "@tldraw/intersect": "*",
"@tldraw/vec": "*", "@tldraw/vec": "*",
"@types/node": "^17.0.14", "@types/node": "^17.0.14",
"@types/react": "^17.0.38", "@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"@typescript-eslint/eslint-plugin": "^5.10.2", "@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2", "@typescript-eslint/parser": "^5.10.2",
"eslint": "^8.8.0", "eslint": "^8.8.0",
"lask": "^0.0.29", "lask": "^0.0.29",
"mobx": "^6.3.8", "mobx": "^6.3.8",
"react": ">=16.8", "react": "^18.1.0",
"react-dom": "^16.8 || ^17.0" "react-dom": "^18.1.0"
}, },
"jest": { "jest": {
"setupFilesAfterEnv": [ "setupFilesAfterEnv": [
@ -98,4 +99,4 @@
} }
}, },
"gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296" "gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296"
} }

Wyświetl plik

@ -1,5 +1,16 @@
# Changelog # Changelog
## 1.16.0-next.0
### Minor Changes
- Bump dependencies, add international support.
### Patch Changes
- Updated dependencies
- @tldraw/core@1.14.0-next.0
## 1.15.1 ## 1.15.1
### Patch Changes ### Patch Changes

Wyświetl plik

@ -1,6 +1,6 @@
{ {
"name": "@tldraw/tldraw", "name": "@tldraw/tldraw",
"version": "1.15.1", "version": "1.16.0-next.0",
"description": "A tiny little drawing app (editor)", "description": "A tiny little drawing app (editor)",
"author": "@steveruizok", "author": "@steveruizok",
"repository": { "repository": {
@ -37,29 +37,26 @@
"docs": "typedoc" "docs": "typedoc"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^17.0", "react": ">=16.8",
"react-dom": "^17.0" "react-dom": ">=16.8"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-alert-dialog": "^0.1.7", "@radix-ui/react-alert-dialog": "^0.1.7",
"@radix-ui/react-checkbox": "^0.1.5",
"@radix-ui/react-context-menu": "^0.1.6", "@radix-ui/react-context-menu": "^0.1.6",
"@radix-ui/react-dropdown-menu": "^0.1.6", "@radix-ui/react-dropdown-menu": "^0.1.6",
"@radix-ui/react-icons": "^1.1.1", "@radix-ui/react-icons": "^1.1.1",
"@radix-ui/react-radio-group": "^0.1.5",
"@radix-ui/react-tooltip": "^0.1.7", "@radix-ui/react-tooltip": "^0.1.7",
"@stitches/react": "^1.2.8", "@stitches/react": "^1.2.8",
"@tldraw/core": "^1.13.1", "@tldraw/core": "^1.14.0-next.0",
"@tldraw/intersect": "^1.7.1", "@tldraw/intersect": "^1.7.1",
"@tldraw/vec": "^1.7.0", "@tldraw/vec": "^1.7.0",
"@types/lz-string": "^1.3.34",
"idb-keyval": "^6.1.0", "idb-keyval": "^6.1.0",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",
"perfect-freehand": "^1.1.0", "perfect-freehand": "^1.1.0",
"react-error-boundary": "^3.1.4", "react-error-boundary": "^3.1.4",
"react-hotkey-hook": "^1.0.2",
"react-hotkeys-hook": "^3.4.4", "react-hotkeys-hook": "^3.4.4",
"tslib": "^2.3.1", "react-intl": "^6.0.3",
"tslib": "^2.4.0",
"zustand": "^3.6.9" "zustand": "^3.6.9"
}, },
"devDependencies": { "devDependencies": {
@ -69,16 +66,18 @@
"@tldraw/core": "*", "@tldraw/core": "*",
"@tldraw/intersect": "*", "@tldraw/intersect": "*",
"@tldraw/vec": "*", "@tldraw/vec": "*",
"@types/lz-string": "^1.3.34",
"@types/node": "^17.0.14", "@types/node": "^17.0.14",
"@types/react": "^17.0.38", "@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"@typescript-eslint/eslint-plugin": "^5.10.2", "@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2", "@typescript-eslint/parser": "^5.10.2",
"eslint": "^8.8.0", "eslint": "^8.8.0",
"lask": "^0.0.29", "lask": "^0.0.29",
"mobx": "^6.3.8", "mobx": "^6.3.8",
"react": "^17.0", "react": "^18.1.0",
"react-dom": "^17.0", "react-dom": "^18.1.0",
"typescript": "^4.6.4" "typescript": "^4.7.3"
}, },
"jest": { "jest": {
"setupFilesAfterEnv": [ "setupFilesAfterEnv": [
@ -106,4 +105,4 @@
} }
}, },
"gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296" "gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296"
} }

Wyświetl plik

@ -1,5 +1,6 @@
import * as React from 'react' import * as React from 'react'
import { Renderer } from '@tldraw/core' import { Renderer } from '@tldraw/core'
import { IntlConfig, IntlProvider } from 'react-intl'
import { styled, dark } from '~styles' import { styled, dark } from '~styles'
import { TDDocument, TDStatus } from '~types' import { TDDocument, TDStatus } from '~types'
import { TldrawApp, TDCallbacks } from '~state' import { TldrawApp, TDCallbacks } from '~state'
@ -12,9 +13,15 @@ import { FocusButton } from '~components/FocusButton'
import { TLDR } from '~state/TLDR' import { TLDR } from '~state/TLDR'
import { GRID_SIZE } from '~constants' import { GRID_SIZE } from '~constants'
import { Loading } from '~components/Loading' import { Loading } from '~components/Loading'
import { ErrorBoundary } from 'react-error-boundary' import { ErrorBoundary as _Errorboundary } from 'react-error-boundary'
import { ErrorFallback } from '~components/ErrorFallback' import { ErrorFallback } from '~components/ErrorFallback'
import messages_en from './translations/en.json'
import messages_fr from './translations/fr.json'
import messages_it from './translations/it.json'
const ErrorBoundary = _Errorboundary as any
export interface TldrawProps extends TDCallbacks { export interface TldrawProps extends TDCallbacks {
/** /**
* (optional) If provided, the component will load / persist state under this key. * (optional) If provided, the component will load / persist state under this key.
@ -417,112 +424,125 @@ const InnerTldraw = React.memo(function InnerTldraw({
const hideCloneHandles = const hideCloneHandles =
isInSession || !isSelecting || !settings.showCloneHandles || pageState.camera.zoom < 0.2 isInSession || !isSelecting || !settings.showCloneHandles || pageState.camera.zoom < 0.2
const messages = {
en: messages_en,
fr: messages_fr,
it: messages_it,
}
const defaultLanguage = settings.language ?? navigator.language.split(/[-_]/)[0]
return ( return (
<StyledLayout ref={rWrapper} tabIndex={-0} className={settings.isDarkMode ? dark : ''}> <IntlProvider
<Loading /> locale={defaultLanguage}
<OneOff focusableRef={rWrapper} autofocus={autofocus} /> messages={messages[defaultLanguage] as IntlConfig['messages']}
<ContextMenu> >
<ErrorBoundary FallbackComponent={ErrorFallback}> <StyledLayout ref={rWrapper} tabIndex={-0} className={settings.isDarkMode ? dark : ''}>
<Renderer <Loading />
id={id} <OneOff focusableRef={rWrapper} autofocus={autofocus} />
containerRef={rWrapper} <ContextMenu>
shapeUtils={shapeUtils} <ErrorBoundary FallbackComponent={ErrorFallback}>
page={page} <Renderer
pageState={pageState} id={id}
assets={assets} containerRef={rWrapper}
snapLines={appState.snapLines} shapeUtils={shapeUtils}
eraseLine={appState.eraseLine} page={page}
grid={GRID_SIZE} pageState={pageState}
users={room?.users} assets={assets}
userId={room?.userId} snapLines={appState.snapLines}
theme={theme} eraseLine={appState.eraseLine}
meta={meta} grid={GRID_SIZE}
hideBounds={hideBounds} users={room?.users}
hideHandles={hideHandles} userId={room?.userId}
hideResizeHandles={isHideResizeHandlesShape} theme={theme}
hideIndicators={hideIndicators} meta={meta}
hideBindingHandles={!settings.showBindingHandles} hideBounds={hideBounds}
hideCloneHandles={hideCloneHandles} hideHandles={hideHandles}
hideRotateHandles={!settings.showRotateHandles} hideResizeHandles={isHideResizeHandlesShape}
hideGrid={!settings.showGrid} hideIndicators={hideIndicators}
showDashedBrush={showDashedBrush} hideBindingHandles={!settings.showBindingHandles}
performanceMode={app.session?.performanceMode} hideCloneHandles={hideCloneHandles}
onPinchStart={app.onPinchStart} hideRotateHandles={!settings.showRotateHandles}
onPinchEnd={app.onPinchEnd} hideGrid={!settings.showGrid}
onPinch={app.onPinch} showDashedBrush={showDashedBrush}
onPan={app.onPan} performanceMode={app.session?.performanceMode}
onZoom={app.onZoom} onPinchStart={app.onPinchStart}
onPointerDown={app.onPointerDown} onPinchEnd={app.onPinchEnd}
onPointerMove={app.onPointerMove} onPinch={app.onPinch}
onPointerUp={app.onPointerUp} onPan={app.onPan}
onPointCanvas={app.onPointCanvas} onZoom={app.onZoom}
onDoubleClickCanvas={app.onDoubleClickCanvas} onPointerDown={app.onPointerDown}
onRightPointCanvas={app.onRightPointCanvas} onPointerMove={app.onPointerMove}
onDragCanvas={app.onDragCanvas} onPointerUp={app.onPointerUp}
onReleaseCanvas={app.onReleaseCanvas} onPointCanvas={app.onPointCanvas}
onPointShape={app.onPointShape} onDoubleClickCanvas={app.onDoubleClickCanvas}
onDoubleClickShape={app.onDoubleClickShape} onRightPointCanvas={app.onRightPointCanvas}
onRightPointShape={app.onRightPointShape} onDragCanvas={app.onDragCanvas}
onDragShape={app.onDragShape} onReleaseCanvas={app.onReleaseCanvas}
onHoverShape={app.onHoverShape} onPointShape={app.onPointShape}
onUnhoverShape={app.onUnhoverShape} onDoubleClickShape={app.onDoubleClickShape}
onReleaseShape={app.onReleaseShape} onRightPointShape={app.onRightPointShape}
onPointBounds={app.onPointBounds} onDragShape={app.onDragShape}
onDoubleClickBounds={app.onDoubleClickBounds} onHoverShape={app.onHoverShape}
onRightPointBounds={app.onRightPointBounds} onUnhoverShape={app.onUnhoverShape}
onDragBounds={app.onDragBounds} onReleaseShape={app.onReleaseShape}
onHoverBounds={app.onHoverBounds} onPointBounds={app.onPointBounds}
onUnhoverBounds={app.onUnhoverBounds} onDoubleClickBounds={app.onDoubleClickBounds}
onReleaseBounds={app.onReleaseBounds} onRightPointBounds={app.onRightPointBounds}
onPointBoundsHandle={app.onPointBoundsHandle} onDragBounds={app.onDragBounds}
onDoubleClickBoundsHandle={app.onDoubleClickBoundsHandle} onHoverBounds={app.onHoverBounds}
onRightPointBoundsHandle={app.onRightPointBoundsHandle} onUnhoverBounds={app.onUnhoverBounds}
onDragBoundsHandle={app.onDragBoundsHandle} onReleaseBounds={app.onReleaseBounds}
onHoverBoundsHandle={app.onHoverBoundsHandle} onPointBoundsHandle={app.onPointBoundsHandle}
onUnhoverBoundsHandle={app.onUnhoverBoundsHandle} onDoubleClickBoundsHandle={app.onDoubleClickBoundsHandle}
onReleaseBoundsHandle={app.onReleaseBoundsHandle} onRightPointBoundsHandle={app.onRightPointBoundsHandle}
onPointHandle={app.onPointHandle} onDragBoundsHandle={app.onDragBoundsHandle}
onDoubleClickHandle={app.onDoubleClickHandle} onHoverBoundsHandle={app.onHoverBoundsHandle}
onRightPointHandle={app.onRightPointHandle} onUnhoverBoundsHandle={app.onUnhoverBoundsHandle}
onDragHandle={app.onDragHandle} onReleaseBoundsHandle={app.onReleaseBoundsHandle}
onHoverHandle={app.onHoverHandle} onPointHandle={app.onPointHandle}
onUnhoverHandle={app.onUnhoverHandle} onDoubleClickHandle={app.onDoubleClickHandle}
onReleaseHandle={app.onReleaseHandle} onRightPointHandle={app.onRightPointHandle}
onError={app.onError} onDragHandle={app.onDragHandle}
onRenderCountChange={app.onRenderCountChange} onHoverHandle={app.onHoverHandle}
onShapeChange={app.onShapeChange} onUnhoverHandle={app.onUnhoverHandle}
onShapeBlur={app.onShapeBlur} onReleaseHandle={app.onReleaseHandle}
onShapeClone={app.onShapeClone} onError={app.onError}
onBoundsChange={app.updateBounds} onRenderCountChange={app.onRenderCountChange}
onKeyDown={app.onKeyDown} onShapeChange={app.onShapeChange}
onKeyUp={app.onKeyUp} onShapeBlur={app.onShapeBlur}
onDragOver={app.onDragOver} onShapeClone={app.onShapeClone}
onDrop={app.onDrop} onBoundsChange={app.updateBounds}
/> onKeyDown={app.onKeyDown}
</ErrorBoundary> onKeyUp={app.onKeyUp}
</ContextMenu> onDragOver={app.onDragOver}
{showUI && ( onDrop={app.onDrop}
<StyledUI> />
{settings.isFocusMode ? ( </ErrorBoundary>
<FocusButton onSelect={app.toggleFocusMode} /> </ContextMenu>
) : ( {showUI && (
<> <StyledUI>
<TopPanel {settings.isFocusMode ? (
readOnly={readOnly} <FocusButton onSelect={app.toggleFocusMode} />
showPages={showPages} ) : (
showMenu={showMenu} <>
showMultiplayerMenu={showMultiplayerMenu} <TopPanel
showStyles={showStyles} readOnly={readOnly}
showZoom={showZoom} showPages={showPages}
sponsor={showSponsorLink} showMenu={showMenu}
/> showMultiplayerMenu={showMultiplayerMenu}
<StyledSpacer /> showStyles={showStyles}
{showTools && !readOnly && <ToolsPanel />} showZoom={showZoom}
</> sponsor={showSponsorLink}
)} />
</StyledUI> <StyledSpacer />
)} {showTools && !readOnly && <ToolsPanel />}
</StyledLayout> </>
)}
</StyledUI>
)}
</StyledLayout>
</IntlProvider>
) )
}) })

Wyświetl plik

@ -1,13 +1,15 @@
import * as React from 'react' import * as React from 'react'
import { ContextMenu } from './ContextMenu' import { ContextMenu } from './ContextMenu'
import { renderWithContext } from '~test' import { renderWithContext, renderWithIntlProvider } from '~test'
describe('context menu', () => { describe('context menu', () => {
test('mounts component without crashing', () => { test('mounts component without crashing', () => {
renderWithContext( renderWithContext(
<ContextMenu onBlur={jest.fn()}> renderWithIntlProvider(
<div>Hello</div> <ContextMenu onBlur={jest.fn()}>
</ContextMenu> <div>Hello</div>
</ContextMenu>
)
) )
}) })
}) })

Wyświetl plik

@ -19,6 +19,7 @@ import { Divider } from '~components/Primitives/Divider'
import { MenuContent } from '~components/Primitives/MenuContent' import { MenuContent } from '~components/Primitives/MenuContent'
import { RowButton, RowButtonProps } from '~components/Primitives/RowButton' import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
import { ToolButton, ToolButtonProps } from '~components/Primitives/ToolButton' import { ToolButton, ToolButtonProps } from '~components/Primitives/ToolButton'
import { FormattedMessage, useIntl } from 'react-intl'
const numberOfSelectedIdsSelector = (s: TDSnapshot) => { const numberOfSelectedIdsSelector = (s: TDSnapshot) => {
return s.document.pageStates[s.appState.currentPageId].selectedIds.length return s.document.pageStates[s.appState.currentPageId].selectedIds.length
@ -56,6 +57,7 @@ interface InnerContextMenuProps {
const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProps) { const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProps) {
const app = useTldrawApp() const app = useTldrawApp()
const intl = useIntl()
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector) const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
const isDebugMode = app.useStore(isDebugModeSelector) const isDebugMode = app.useStore(isDebugModeSelector)
const hasGroupSelected = app.useStore(hasGroupSelectedSelector) const hasGroupSelected = app.useStore(hasGroupSelectedSelector)
@ -171,45 +173,46 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
{hasSelection ? ( {hasSelection ? (
<> <>
<CMRowButton onClick={handleDuplicate} kbd="#D" id="TD-ContextMenu-Duplicate"> <CMRowButton onClick={handleDuplicate} kbd="#D" id="TD-ContextMenu-Duplicate">
Duplicate <FormattedMessage id="duplicate" />
</CMRowButton> </CMRowButton>
<CMRowButton <CMRowButton
onClick={handleFlipHorizontal} onClick={handleFlipHorizontal}
kbd="⇧H" kbd="⇧H"
id="TD-ContextMenu-Flip_Horizontal" id="TD-ContextMenu-Flip_Horizontal"
> >
Flip Horizontal <FormattedMessage id="flip.horizontal" />
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleFlipVertical} kbd="⇧V" id="TD-ContextMenu-Flip_Vertical"> <CMRowButton onClick={handleFlipVertical} kbd="⇧V" id="TD-ContextMenu-Flip_Vertical">
Flip Vertical <FormattedMessage id="flip.vertical" />
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleLock} kbd="#⇧L" id="TD-ContextMenu- Lock_Unlock"> <CMRowButton onClick={handleLock} kbd="#⇧L" id="TD-ContextMenu- Lock_Unlock">
Lock / Unlock <FormattedMessage id="lock" /> / <FormattedMessage id="unlock" />
</CMRowButton> </CMRowButton>
{(hasTwoOrMore || hasGroupSelected) && <Divider />} {(hasTwoOrMore || hasGroupSelected) && <Divider />}
{hasTwoOrMore && ( {hasTwoOrMore && (
<CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Group"> <CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Group">
Group <FormattedMessage id="group" />
</CMRowButton> </CMRowButton>
)} )}
{hasGroupSelected && ( {hasGroupSelected && (
<CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Ungroup"> <CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Ungroup">
Ungroup <FormattedMessage id="ungroup" />
<FormattedMessage id="ungroup" />
</CMRowButton> </CMRowButton>
)} )}
<Divider /> <Divider />
<ContextMenuSubMenu label="Move" id="TD-ContextMenu-Move"> <ContextMenuSubMenu label={intl.formatMessage({ id: 'move' })} id="TD-ContextMenu-Move">
<CMRowButton onClick={handleMoveToFront} kbd="⇧]" id="TD-ContextMenu-Move-To_Front"> <CMRowButton onClick={handleMoveToFront} kbd="⇧]" id="TD-ContextMenu-Move-To_Front">
To Front <FormattedMessage id="to.front" />
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleMoveForward} kbd="]" id="TD-ContextMenu-Move-Forward"> <CMRowButton onClick={handleMoveForward} kbd="]" id="TD-ContextMenu-Move-Forward">
Forward <FormattedMessage id="forward" />
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleMoveBackward} kbd="[" id="TD-ContextMenu-Move-Backward"> <CMRowButton onClick={handleMoveBackward} kbd="[" id="TD-ContextMenu-Move-Backward">
Backward <FormattedMessage id="backward" />
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleMoveToBack} kbd="⇧[" id="TD-ContextMenu-Move-To_Back"> <CMRowButton onClick={handleMoveToBack} kbd="⇧[" id="TD-ContextMenu-Move-To_Back">
To Back <FormattedMessage id="back" />
</CMRowButton> </CMRowButton>
</ContextMenuSubMenu> </ContextMenuSubMenu>
<MoveToPageMenu /> <MoveToPageMenu />
@ -218,16 +221,20 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
)} )}
<Divider /> <Divider />
<CMRowButton onClick={handleCut} kbd="#X" id="TD-ContextMenu-Cut"> <CMRowButton onClick={handleCut} kbd="#X" id="TD-ContextMenu-Cut">
Cut <FormattedMessage id="cut" />
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleCopy} kbd="#C" id="TD-ContextMenu-Copy"> <CMRowButton onClick={handleCopy} kbd="#C" id="TD-ContextMenu-Copy">
Copy <FormattedMessage id="copy" />
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste"> <CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
Paste <FormattedMessage id="paste" />
</CMRowButton> </CMRowButton>
<Divider /> <Divider />
<ContextMenuSubMenu label="Copy as..." size="small" id="TD-ContextMenu-Copy-As"> <ContextMenuSubMenu
label={`${intl.formatMessage({ id: 'copy.as' })}...`}
size="small"
id="TD-ContextMenu-Copy-As"
>
<CMRowButton onClick={handleCopySVG} id="TD-ContextMenu-Copy-as-SVG"> <CMRowButton onClick={handleCopySVG} id="TD-ContextMenu-Copy-as-SVG">
SVG SVG
</CMRowButton> </CMRowButton>
@ -240,7 +247,11 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
</CMRowButton> </CMRowButton>
)} )}
</ContextMenuSubMenu> </ContextMenuSubMenu>
<ContextMenuSubMenu label="Export as..." size="small" id="TD-ContextMenu-Export"> <ContextMenuSubMenu
label={`${intl.formatMessage({ id: 'export.as' })}...`}
size="small"
id="TD-ContextMenu-Export"
>
<CMRowButton onClick={handleExportSVG} id="TD-ContextMenu-Export-SVG"> <CMRowButton onClick={handleExportSVG} id="TD-ContextMenu-Export-SVG">
SVG SVG
</CMRowButton> </CMRowButton>
@ -261,19 +272,19 @@ const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProp
</ContextMenuSubMenu> </ContextMenuSubMenu>
<Divider /> <Divider />
<CMRowButton onClick={handleDelete} kbd="⌫" id="TD-ContextMenu-Delete"> <CMRowButton onClick={handleDelete} kbd="⌫" id="TD-ContextMenu-Delete">
Delete <FormattedMessage id="delete" />
</CMRowButton> </CMRowButton>
</> </>
) : ( ) : (
<> <>
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste"> <CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
Paste <FormattedMessage id="paste" />
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleUndo} kbd="#Z" id="TD-ContextMenu-Undo"> <CMRowButton onClick={handleUndo} kbd="#Z" id="TD-ContextMenu-Undo">
Undo <FormattedMessage id="undo" />
</CMRowButton> </CMRowButton>
<CMRowButton onClick={handleRedo} kbd="#⇧Z" id="TD-ContextMenu-Redo"> <CMRowButton onClick={handleRedo} kbd="#⇧Z" id="TD-ContextMenu-Redo">
Redo <FormattedMessage id="redo" />
</CMRowButton> </CMRowButton>
</> </>
)} )}
@ -430,7 +441,9 @@ function MoveToPageMenu() {
return ( return (
<RadixContextMenu.Root dir="ltr"> <RadixContextMenu.Root dir="ltr">
<CMTriggerButton isSubmenu>Move To Page</CMTriggerButton> <CMTriggerButton isSubmenu>
<FormattedMessage id="move.to.page" />
</CMTriggerButton>
<RadixContextMenu.Content dir="ltr" sideOffset={2} alignOffset={-2} asChild> <RadixContextMenu.Content dir="ltr" sideOffset={2} alignOffset={-2} asChild>
<MenuContent> <MenuContent>
{sorted.map(({ id, name }, i) => ( {sorted.map(({ id, name }, i) => (

Wyświetl plik

@ -5,7 +5,7 @@ import { RowButton } from '~components/Primitives/RowButton'
import { useTldrawApp } from '~hooks' import { useTldrawApp } from '~hooks'
import { styled } from '~styles' import { styled } from '~styles'
export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps): any {
const app = useTldrawApp() const app = useTldrawApp()
const refreshPage = () => { const refreshPage = () => {

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -32,6 +32,7 @@ import {
import { DMContent } from '~components/Primitives/DropdownMenu' import { DMContent } from '~components/Primitives/DropdownMenu'
import { Divider } from '~components/Primitives/Divider' import { Divider } from '~components/Primitives/Divider'
import { ToolButton } from '~components/Primitives/ToolButton' import { ToolButton } from '~components/Primitives/ToolButton'
import { useIntl } from 'react-intl'
const selectedShapesCountSelector = (s: TDSnapshot) => const selectedShapesCountSelector = (s: TDSnapshot) =>
s.document.pageStates[s.appState.currentPageId].selectedIds.length s.document.pageStates[s.appState.currentPageId].selectedIds.length
@ -74,6 +75,9 @@ const hasMultipleSelectionSelector = (s: TDSnapshot) => {
export function ActionButton() { export function ActionButton() {
const app = useTldrawApp() const app = useTldrawApp()
const intl = useIntl()
const isFrenchLang = navigator.language === 'fr'
const isAllLocked = app.useStore(isAllLockedSelector) const isAllLocked = app.useStore(isAllLockedSelector)
@ -189,22 +193,35 @@ export function ActionButton() {
<> <>
<ButtonsRow> <ButtonsRow>
<ToolButton variant="icon" disabled={!hasSelection} onClick={handleDuplicate}> <ToolButton variant="icon" disabled={!hasSelection} onClick={handleDuplicate}>
<Tooltip label="Duplicate" kbd={`#D`} id="TD-Tools-Copy"> <Tooltip
label={intl.formatMessage({ id: 'duplicate' })}
kbd={`#D`}
id="TD-Tools-Copy"
>
<CopyIcon /> <CopyIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleRotate}> <ToolButton disabled={!hasSelection} onClick={handleRotate}>
<Tooltip label="Rotate" id="TD-Tools-Rotate"> <Tooltip label={intl.formatMessage({ id: 'rotate' })} id="TD-Tools-Rotate">
<RotateCounterClockwiseIcon /> <RotateCounterClockwiseIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleToggleLocked}> <ToolButton disabled={!hasSelection} onClick={handleToggleLocked}>
<Tooltip label="Toggle Locked" kbd={`#L`} id="TD-Tools-Lock"> <Tooltip
label={intl.formatMessage({ id: isAllLocked ? 'unlock' : 'lock' })}
kbd={`#L`}
id="TD-Tools-Lock"
>
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />} {isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleToggleAspectRatio}> <ToolButton disabled={!hasSelection} onClick={handleToggleAspectRatio}>
<Tooltip label="Toggle Aspect Ratio Lock" id="TD-Tools-AspectRatio"> <Tooltip
label={intl.formatMessage({
id: isAllAspectLocked ? 'unlock.aspect.ratio' : 'lock.aspect.ratio',
})}
id="TD-Tools-AspectRatio"
>
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />} {isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
@ -212,34 +229,50 @@ export function ActionButton() {
disabled={!hasSelection || (!isAllGrouped && !hasMultipleSelection)} disabled={!hasSelection || (!isAllGrouped && !hasMultipleSelection)}
onClick={handleGroup} onClick={handleGroup}
> >
<Tooltip label="Group" kbd={`#G`} id="TD-Tools-Group"> <Tooltip label={intl.formatMessage({ id: 'group' })} kbd={`#G`} id="TD-Tools-Group">
<GroupIcon /> <GroupIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
</ButtonsRow> </ButtonsRow>
<ButtonsRow> <ButtonsRow>
<ToolButton disabled={!hasSelection} onClick={handleMoveToBack}> <ToolButton disabled={!hasSelection} onClick={handleMoveToBack}>
<Tooltip label="Move to Back" kbd={`#⇧[`} id="TD-Tools-PinBottom"> <Tooltip
label={intl.formatMessage({ id: 'move.to.back' })}
kbd={`#⇧[`}
id="TD-Tools-PinBottom"
>
<PinBottomIcon /> <PinBottomIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleMoveBackward}> <ToolButton disabled={!hasSelection} onClick={handleMoveBackward}>
<Tooltip label="Move Backward" kbd={`#[`} id="TD-Tools-ArrowDown"> <Tooltip
label={intl.formatMessage({ id: 'move.backward' })}
kbd={`#[`}
id="TD-Tools-ArrowDown"
>
<ArrowDownIcon /> <ArrowDownIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleMoveForward}> <ToolButton disabled={!hasSelection} onClick={handleMoveForward}>
<Tooltip label="Move Forward" kbd={`#]`} id="TD-Tools-ArrowUp"> <Tooltip
label={intl.formatMessage({ id: 'move.forward' })}
kbd={`#]`}
id="TD-Tools-ArrowUp"
>
<ArrowUpIcon /> <ArrowUpIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleMoveToFront}> <ToolButton disabled={!hasSelection} onClick={handleMoveToFront}>
<Tooltip label="Move to Front" kbd={`#⇧]`} id="TD-Tools-PinTop"> <Tooltip
label={intl.formatMessage({ id: 'move.to.front' })}
kbd={`#⇧]`}
id="TD-Tools-PinTop"
>
<PinTopIcon /> <PinTopIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>
<ToolButton disabled={!hasSelection} onClick={handleResetAngle}> <ToolButton disabled={!hasSelection} onClick={handleResetAngle}>
<Tooltip label="Reset Angle" id="TD-Tools-ResetAngle"> <Tooltip label={intl.formatMessage({ id: 'reset.angle' })} id="TD-Tools-ResetAngle">
<AngleIcon /> <AngleIcon />
</Tooltip> </Tooltip>
</ToolButton> </ToolButton>

Wyświetl plik

@ -3,9 +3,11 @@ import { Tooltip } from '~components/Primitives/Tooltip'
import { useTldrawApp } from '~hooks' import { useTldrawApp } from '~hooks'
import { ToolButton } from '~components/Primitives/ToolButton' import { ToolButton } from '~components/Primitives/ToolButton'
import { TrashIcon } from '~components/Primitives/icons' import { TrashIcon } from '~components/Primitives/icons'
import { useIntl } from 'react-intl'
export function DeleteButton() { export function DeleteButton() {
const app = useTldrawApp() const app = useTldrawApp()
const intl = useIntl()
const handleDelete = React.useCallback(() => { const handleDelete = React.useCallback(() => {
app.delete() app.delete()
@ -18,7 +20,7 @@ export function DeleteButton() {
) )
return ( return (
<Tooltip label="Delete" kbd="⌫" id="TD-Delete"> <Tooltip label={intl.formatMessage({ id: 'delete' })} kbd="⌫" id="TD-Delete">
<ToolButton variant="circle" disabled={!hasSelection} onSelect={handleDelete}> <ToolButton variant="circle" disabled={!hasSelection} onSelect={handleDelete}>
<TrashIcon /> <TrashIcon />
</ToolButton> </ToolButton>

Wyświetl plik

@ -1,4 +1,5 @@
import * as React from 'react' import * as React from 'react'
import { useIntl } from 'react-intl'
import { import {
ArrowTopRightIcon, ArrowTopRightIcon,
CursorArrowIcon, CursorArrowIcon,
@ -18,6 +19,7 @@ const toolLockedSelector = (s: TDSnapshot) => s.appState.isToolLocked
export const PrimaryTools = React.memo(function PrimaryTools() { export const PrimaryTools = React.memo(function PrimaryTools() {
const app = useTldrawApp() const app = useTldrawApp()
const intl = useIntl()
const activeTool = app.useStore(activeToolSelector) const activeTool = app.useStore(activeToolSelector)
@ -51,7 +53,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
<Panel side="center" id="TD-PrimaryTools"> <Panel side="center" id="TD-PrimaryTools">
<ToolButtonWithTooltip <ToolButtonWithTooltip
kbd={'1'} kbd={'1'}
label={'select'} label={intl.formatMessage({ id: 'select' })}
onClick={selectSelectTool} onClick={selectSelectTool}
isActive={activeTool === 'select'} isActive={activeTool === 'select'}
id="TD-PrimaryTools-CursorArrow" id="TD-PrimaryTools-CursorArrow"
@ -60,7 +62,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
</ToolButtonWithTooltip> </ToolButtonWithTooltip>
<ToolButtonWithTooltip <ToolButtonWithTooltip
kbd={'2'} kbd={'2'}
label={TDShapeType.Draw} label={intl.formatMessage({ id: 'draw' })}
onClick={selectDrawTool} onClick={selectDrawTool}
isActive={activeTool === TDShapeType.Draw} isActive={activeTool === TDShapeType.Draw}
id="TD-PrimaryTools-Pencil" id="TD-PrimaryTools-Pencil"
@ -69,7 +71,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
</ToolButtonWithTooltip> </ToolButtonWithTooltip>
<ToolButtonWithTooltip <ToolButtonWithTooltip
kbd={'3'} kbd={'3'}
label={'eraser'} label={intl.formatMessage({ id: 'eraser' })}
onClick={selectEraseTool} onClick={selectEraseTool}
isActive={activeTool === 'erase'} isActive={activeTool === 'erase'}
id="TD-PrimaryTools-Eraser" id="TD-PrimaryTools-Eraser"
@ -79,7 +81,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
<ShapesMenu activeTool={activeTool} isToolLocked={isToolLocked} /> <ShapesMenu activeTool={activeTool} isToolLocked={isToolLocked} />
<ToolButtonWithTooltip <ToolButtonWithTooltip
kbd={'8'} kbd={'8'}
label={TDShapeType.Arrow} label={intl.formatMessage({ id: 'arrow' })}
onClick={selectArrowTool} onClick={selectArrowTool}
isLocked={isToolLocked} isLocked={isToolLocked}
isActive={activeTool === TDShapeType.Arrow} isActive={activeTool === TDShapeType.Arrow}
@ -89,7 +91,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
</ToolButtonWithTooltip> </ToolButtonWithTooltip>
<ToolButtonWithTooltip <ToolButtonWithTooltip
kbd={'9'} kbd={'9'}
label={TDShapeType.Text} label={intl.formatMessage({ id: 'text' })}
onClick={selectTextTool} onClick={selectTextTool}
isLocked={isToolLocked} isLocked={isToolLocked}
isActive={activeTool === TDShapeType.Text} isActive={activeTool === TDShapeType.Text}
@ -99,7 +101,7 @@ export const PrimaryTools = React.memo(function PrimaryTools() {
</ToolButtonWithTooltip> </ToolButtonWithTooltip>
<ToolButtonWithTooltip <ToolButtonWithTooltip
kbd={'0'} kbd={'0'}
label={TDShapeType.Sticky} label={intl.formatMessage({ id: 'sticky' })}
onClick={selectStickyTool} onClick={selectStickyTool}
isActive={activeTool === TDShapeType.Sticky} isActive={activeTool === TDShapeType.Sticky}
id="TD-PrimaryTools-Pencil2" id="TD-PrimaryTools-Pencil2"

Wyświetl plik

@ -7,6 +7,7 @@ import { useTldrawApp } from '~hooks'
import { SquareIcon, CircleIcon, VercelLogoIcon } from '@radix-ui/react-icons' import { SquareIcon, CircleIcon, VercelLogoIcon } from '@radix-ui/react-icons'
import { Tooltip } from '~components/Primitives/Tooltip' import { Tooltip } from '~components/Primitives/Tooltip'
import { LineIcon } from '~components/Primitives/icons' import { LineIcon } from '~components/Primitives/icons'
import { useIntl } from 'react-intl'
interface ShapesMenuProps { interface ShapesMenuProps {
activeTool: TDToolType activeTool: TDToolType
@ -44,6 +45,7 @@ export const ShapesMenu = React.memo(function ShapesMenu({
isToolLocked, isToolLocked,
}: ShapesMenuProps) { }: ShapesMenuProps) {
const app = useTldrawApp() const app = useTldrawApp()
const intl = useIntl()
const status = app.useStore(statusSelector) const status = app.useStore(statusSelector)
@ -92,7 +94,7 @@ export const ShapesMenu = React.memo(function ShapesMenu({
{shapeShapes.map((shape, i) => ( {shapeShapes.map((shape, i) => (
<Tooltip <Tooltip
key={shape} key={shape}
label={shape[0].toUpperCase() + shape.slice(1)} label={intl.formatMessage({ id: shape[0].toUpperCase() + shape.slice(1) })}
kbd={(4 + i).toString()} kbd={(4 + i).toString()}
id={`TD-PrimaryTools-Shapes-${shape}`} id={`TD-PrimaryTools-Shapes-${shape}`}
> >

Wyświetl plik

@ -1,9 +1,9 @@
import * as React from 'react' import * as React from 'react'
import { ToolsPanel } from './ToolsPanel' import { ToolsPanel } from './ToolsPanel'
import { renderWithContext } from '~test' import { renderWithContext, renderWithIntlProvider } from '~test'
describe('tools panel', () => { describe('tools panel', () => {
test('mounts component without crashing', () => { test('mounts component without crashing', () => {
renderWithContext(<ToolsPanel onBlur={() => void null} />) renderWithContext(renderWithIntlProvider(<ToolsPanel onBlur={() => void null} />))
}) })
}) })

Wyświetl plik

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

Wyświetl plik

@ -23,6 +23,8 @@ import { preventEvent } from '~components/preventEvent'
import { DiscordIcon } from '~components/Primitives/icons' import { DiscordIcon } from '~components/Primitives/icons'
import { TDExportType, TDSnapshot } from '~types' import { TDExportType, TDSnapshot } from '~types'
import { Divider } from '~components/Primitives/Divider' import { Divider } from '~components/Primitives/Divider'
import { FormattedMessage, useIntl } from 'react-intl'
import { LanguageMenu } from '../LanguageMenu/LanguageMenu'
interface MenuProps { interface MenuProps {
sponsor: boolean | undefined sponsor: boolean | undefined
@ -39,6 +41,7 @@ const disableAssetsSelector = (s: TDSnapshot) => {
export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) { export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
const app = useTldrawApp() const app = useTldrawApp()
const intl = useIntl()
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector) const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
@ -140,38 +143,40 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
</DMTriggerIcon> </DMTriggerIcon>
<DMContent variant="menu" id="TD-Menu"> <DMContent variant="menu" id="TD-Menu">
{showFileMenu && ( {showFileMenu && (
<DMSubMenu label="File..." id="TD-MenuItem-File"> <DMSubMenu label={`${intl.formatMessage({ id: 'menu.file' })}...`} id="TD-MenuItem-File">
{app.callbacks.onNewProject && ( {app.callbacks.onNewProject && (
<DMItem onClick={onNewProject} kbd="#N" id="TD-MenuItem-File-New_Project"> <DMItem onClick={onNewProject} kbd="#N" id="TD-MenuItem-File-New_Project">
New Project <FormattedMessage id="new.project" />
</DMItem> </DMItem>
)} )}
{app.callbacks.onOpenProject && ( {app.callbacks.onOpenProject && (
<DMItem onClick={onOpenProject} kbd="#O" id="TD-MenuItem-File-Open"> <DMItem onClick={onOpenProject} kbd="#O" id="TD-MenuItem-File-Open">
Open... <FormattedMessage id="open" />
...
</DMItem> </DMItem>
)} )}
{app.callbacks.onSaveProject && ( {app.callbacks.onSaveProject && (
<DMItem onClick={onSaveProject} kbd="#S" id="TD-MenuItem-File-Save"> <DMItem onClick={onSaveProject} kbd="#S" id="TD-MenuItem-File-Save">
Save <FormattedMessage id="save" />
</DMItem> </DMItem>
)} )}
{app.callbacks.onSaveProjectAs && ( {app.callbacks.onSaveProjectAs && (
<DMItem onClick={onSaveProjectAs} kbd="#⇧S" id="TD-MenuItem-File-Save_As"> <DMItem onClick={onSaveProjectAs} kbd="#⇧S" id="TD-MenuItem-File-Save_As">
Save As... <FormattedMessage id="save.as" />
...
</DMItem> </DMItem>
)} )}
{!disableAssets && ( {!disableAssets && (
<> <>
<Divider /> <Divider />
<DMItem onClick={handleUploadMedia} kbd="#U" id="TD-MenuItem-File-Upload_Media"> <DMItem onClick={handleUploadMedia} kbd="#U" id="TD-MenuItem-File-Upload_Media">
Upload Media <FormattedMessage id="upload.media" />
</DMItem> </DMItem>
</> </>
)} )}
</DMSubMenu> </DMSubMenu>
)} )}
<DMSubMenu label="Edit..." id="TD-MenuItem-Edit"> <DMSubMenu label={`${intl.formatMessage({ id: 'menu.edit' })}...`} id="TD-MenuItem-Edit">
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
onClick={app.undo} onClick={app.undo}
@ -179,7 +184,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
kbd="#Z" kbd="#Z"
id="TD-MenuItem-Edit-Undo" id="TD-MenuItem-Edit-Undo"
> >
Undo <FormattedMessage id="undo" />
</DMItem> </DMItem>
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
@ -188,7 +193,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
kbd="#⇧Z" kbd="#⇧Z"
id="TD-MenuItem-Edit-Redo" id="TD-MenuItem-Edit-Redo"
> >
Redo <FormattedMessage id="redo" />
</DMItem> </DMItem>
<DMDivider dir="ltr" /> <DMDivider dir="ltr" />
<DMItem <DMItem
@ -198,7 +203,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
kbd="#X" kbd="#X"
id="TD-MenuItem-Edit-Cut" id="TD-MenuItem-Edit-Cut"
> >
Cut <FormattedMessage id="cut" />
</DMItem> </DMItem>
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
@ -207,7 +212,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
kbd="#C" kbd="#C"
id="TD-MenuItem-Edit-Copy" id="TD-MenuItem-Edit-Copy"
> >
Copy <FormattedMessage id="copy" />
</DMItem> </DMItem>
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
@ -215,10 +220,14 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
kbd="#V" kbd="#V"
id="TD-MenuItem-Edit-Paste" id="TD-MenuItem-Edit-Paste"
> >
Paste <FormattedMessage id="paste" />
</DMItem> </DMItem>
<DMDivider dir="ltr" /> <DMDivider dir="ltr" />
<DMSubMenu label="Copy as..." size="small" id="TD-MenuItem-Copy-As"> <DMSubMenu
label={`${intl.formatMessage({ id: 'copy.as' })}...`}
size="small"
id="TD-MenuItem-Copy-As"
>
<DMItem onClick={handleCopySVG} id="TD-MenuItem-Copy-as-SVG"> <DMItem onClick={handleCopySVG} id="TD-MenuItem-Copy-as-SVG">
SVG SVG
</DMItem> </DMItem>
@ -229,7 +238,11 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
JSON JSON
</DMItem> </DMItem>
</DMSubMenu> </DMSubMenu>
<DMSubMenu label="Export as..." size="small" id="TD-MenuItem-Export"> <DMSubMenu
label={`${intl.formatMessage({ id: 'export.as' })}...`}
size="small"
id="TD-MenuItem-Export"
>
<DMItem onClick={handleExportSVG} id="TD-MenuItem-Export-SVG"> <DMItem onClick={handleExportSVG} id="TD-MenuItem-Export-SVG">
SVG SVG
</DMItem> </DMItem>
@ -254,7 +267,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
kbd="#A" kbd="#A"
id="TD-MenuItem-Select_All" id="TD-MenuItem-Select_All"
> >
Select All <FormattedMessage id="select.all" />
</DMItem> </DMItem>
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
@ -262,21 +275,21 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
onClick={handleSelectNone} onClick={handleSelectNone}
id="TD-MenuItem-Select_None" id="TD-MenuItem-Select_None"
> >
Select None <FormattedMessage id="select.none" />
</DMItem> </DMItem>
<DMDivider dir="ltr" /> <DMDivider dir="ltr" />
<DMItem onSelect={handleDelete} disabled={!hasSelection} kbd="⌫" id="TD-MenuItem-Delete"> <DMItem onSelect={handleDelete} disabled={!hasSelection} kbd="⌫" id="TD-MenuItem-Delete">
Delete <FormattedMessage id="delete" />
</DMItem> </DMItem>
</DMSubMenu> </DMSubMenu>
<DMSubMenu label="View" id="TD-MenuItem-Edit"> <DMSubMenu label={intl.formatMessage({ id: 'menu.view' })} id="TD-MenuItem-Edit">
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
onClick={app.zoomIn} onClick={app.zoomIn}
kbd="#+" kbd="#+"
id="TD-MenuItem-View-ZoomIn" id="TD-MenuItem-View-ZoomIn"
> >
Zoom In <FormattedMessage id="zoom.in" />
</DMItem> </DMItem>
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
@ -284,7 +297,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
kbd="#-" kbd="#-"
id="TD-MenuItem-View-ZoomOut" id="TD-MenuItem-View-ZoomOut"
> >
Zoom Out <FormattedMessage id="zoom.out" />
</DMItem> </DMItem>
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
@ -292,7 +305,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
kbd="⇧+0" kbd="⇧+0"
id="TD-MenuItem-View-ZoomTo100" id="TD-MenuItem-View-ZoomTo100"
> >
Zoom to 100% <FormattedMessage id="zoom.to" /> 100%
</DMItem> </DMItem>
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
@ -300,7 +313,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
kbd="⇧+1" kbd="⇧+1"
id="TD-MenuItem-View-ZoomToFit" id="TD-MenuItem-View-ZoomToFit"
> >
Zoom to Fit <FormattedMessage id="zoom.to.fit" />
</DMItem> </DMItem>
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
@ -308,12 +321,14 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
kbd="⇧+2" kbd="⇧+2"
id="TD-MenuItem-View-ZoomToSelection" id="TD-MenuItem-View-ZoomToSelection"
> >
Zoom to Selection <FormattedMessage id="zoom.to.selection" />
</DMItem> </DMItem>
</DMSubMenu> </DMSubMenu>
<DMDivider dir="ltr" /> <DMDivider dir="ltr" />
<PreferencesMenu /> <PreferencesMenu />
<DMDivider dir="ltr" /> <DMDivider dir="ltr" />
<LanguageMenu />
<DMDivider dir="ltr" />
<a href="https://github.com/Tldraw/Tldraw" target="_blank" rel="nofollow"> <a href="https://github.com/Tldraw/Tldraw" target="_blank" rel="nofollow">
<DMItem id="TD-MenuItem-Github"> <DMItem id="TD-MenuItem-Github">
GitHub GitHub
@ -341,7 +356,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
{sponsor === false && ( {sponsor === false && (
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow"> <a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
<DMItem isSponsor id="TD-MenuItem-Become_a_Sponsor"> <DMItem isSponsor id="TD-MenuItem-Become_a_Sponsor">
Become a Sponsor{' '} <FormattedMessage id="become.a.sponsor" />{' '}
<SmallIcon> <SmallIcon>
<HeartIcon /> <HeartIcon />
</SmallIcon> </SmallIcon>
@ -351,7 +366,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
{sponsor === true && ( {sponsor === true && (
<a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow"> <a href="https://github.com/sponsors/steveruizok" target="_blank" rel="nofollow">
<DMItem id="TD-MenuItem-is_a_Sponsor"> <DMItem id="TD-MenuItem-is_a_Sponsor">
Sponsored! <FormattedMessage id="sponsored" />!
<SmallIcon> <SmallIcon>
<HeartFilledIcon /> <HeartFilledIcon />
</SmallIcon> </SmallIcon>
@ -363,12 +378,12 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
<DMDivider dir="ltr" />{' '} <DMDivider dir="ltr" />{' '}
{app.callbacks.onSignIn && ( {app.callbacks.onSignIn && (
<DMItem onSelect={handleSignIn} id="TD-MenuItem-Sign_in"> <DMItem onSelect={handleSignIn} id="TD-MenuItem-Sign_in">
Sign In <FormattedMessage id="menu.sign.in" />
</DMItem> </DMItem>
)} )}
{app.callbacks.onSignOut && ( {app.callbacks.onSignOut && (
<DMItem onSelect={handleSignOut} id="TD-MenuItem-Sign_out"> <DMItem onSelect={handleSignOut} id="TD-MenuItem-Sign_out">
Sign Out <FormattedMessage id="menu.sign.out" />
<SmallIcon> <SmallIcon>
<ExitIcon /> <ExitIcon />
</SmallIcon> </SmallIcon>

Wyświetl plik

@ -8,6 +8,7 @@ import { MultiplayerIcon } from '~components/Primitives/icons'
import { TDAssetType, TDSnapshot } from '~types' import { TDAssetType, TDSnapshot } from '~types'
import { TLDR } from '~state/TLDR' import { TLDR } from '~state/TLDR'
import { Utils } from '@tldraw/core' import { Utils } from '@tldraw/core'
import { FormattedMessage } from 'react-intl'
const roomSelector = (state: TDSnapshot) => state.room const roomSelector = (state: TDSnapshot) => state.room
@ -78,7 +79,7 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
pageId: app.currentPageId, pageId: app.currentPageId,
document: nextDocument, document: nextDocument,
}), }),
}).then(d => d.json()) }).then((d) => d.json())
if (result?.url) { if (result?.url) {
window.location.href = result.url window.location.href = result.url
@ -99,20 +100,23 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
</DMTriggerIcon> </DMTriggerIcon>
<DMContent variant="menu" align="start" id="TD-MultiplayerMenu"> <DMContent variant="menu" align="start" id="TD-MultiplayerMenu">
<DMItem id="TD-Multiplayer-CopyInviteLink" onClick={handleCopySelect} disabled={!room}> <DMItem id="TD-Multiplayer-CopyInviteLink" onClick={handleCopySelect} disabled={!room}>
Copy Invite Link<SmallIcon>{copied ? <CheckIcon /> : <ClipboardIcon />}</SmallIcon> <FormattedMessage id="copy.invite.link" />
<SmallIcon>{copied ? <CheckIcon /> : <ClipboardIcon />}</SmallIcon>
</DMItem> </DMItem>
<DMDivider id="TD-Multiplayer-CopyInviteLinkDivider" /> <DMDivider id="TD-Multiplayer-CopyInviteLinkDivider" />
<DMItem <DMItem
id="TD-Multiplayer-CreateMultiplayerProject" id="TD-Multiplayer-CreateMultiplayerProject"
onClick={handleCreateMultiplayerProject} onClick={handleCreateMultiplayerProject}
> >
<a href="https://tldraw.com/r">Create a Multiplayer Project</a> <a href="https://tldraw.com/r">
<FormattedMessage id="create.multiplayer.project" />
</a>
</DMItem> </DMItem>
<DMItem <DMItem
id="TD-Multiplayer-CopyToMultiplayerProject" id="TD-Multiplayer-CopyToMultiplayerProject"
onClick={handleCopyToMultiplayerProject} onClick={handleCopyToMultiplayerProject}
> >
Copy to Multiplayer Project <FormattedMessage id="copy.multiplayer.project" />
</DMItem> </DMItem>
</DMContent> </DMContent>
</DropdownMenu.Root> </DropdownMenu.Root>

Wyświetl plik

@ -9,6 +9,7 @@ import { DMContent, DMDivider } from '~components/Primitives/DropdownMenu'
import { SmallIcon } from '~components/Primitives/SmallIcon' import { SmallIcon } from '~components/Primitives/SmallIcon'
import { RowButton } from '~components/Primitives/RowButton' import { RowButton } from '~components/Primitives/RowButton'
import { ToolButton } from '~components/Primitives/ToolButton' import { ToolButton } from '~components/Primitives/ToolButton'
import { FormattedMessage } from 'react-intl'
const sortedSelector = (s: TDSnapshot) => const sortedSelector = (s: TDSnapshot) =>
Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0)) Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))
@ -102,7 +103,9 @@ function PageMenuContent({ onClose }: { onClose: () => void }) {
<DMDivider /> <DMDivider />
<DropdownMenu.Item onSelect={handleCreatePage} asChild> <DropdownMenu.Item onSelect={handleCreatePage} asChild>
<RowButton> <RowButton>
<span>Create Page</span> <span>
<FormattedMessage id="create.page" />
</span>
<SmallIcon> <SmallIcon>
<PlusIcon /> <PlusIcon />
</SmallIcon> </SmallIcon>

Wyświetl plik

@ -10,6 +10,7 @@ import { IconButton } from '~components/Primitives/IconButton/IconButton'
import { SmallIcon } from '~components/Primitives/SmallIcon' import { SmallIcon } from '~components/Primitives/SmallIcon'
import { breakpoints } from '~components/breakpoints' import { breakpoints } from '~components/breakpoints'
import { TextField } from '~components/Primitives/TextField' import { TextField } from '~components/Primitives/TextField'
import { FormattedMessage, useIntl } from 'react-intl'
const canDeleteSelector = (s: TDSnapshot) => { const canDeleteSelector = (s: TDSnapshot) => {
return Object.keys(s.document.pages).length > 1 return Object.keys(s.document.pages).length > 1
@ -23,6 +24,7 @@ interface PageOptionsDialogProps {
export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogProps) { export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogProps) {
const app = useTldrawApp() const app = useTldrawApp()
const intl = useIntl()
const [isOpen, setIsOpen] = React.useState(false) const [isOpen, setIsOpen] = React.useState(false)
const [pageName, setPageName] = React.useState(page.name || 'Page') const [pageName, setPageName] = React.useState(page.name || 'Page')
@ -94,19 +96,23 @@ export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogPr
<StyledDialogOverlay onPointerDown={close} /> <StyledDialogOverlay onPointerDown={close} />
<StyledDialogContent dir="ltr" onKeyDown={stopPropagation} onKeyUp={stopPropagation}> <StyledDialogContent dir="ltr" onKeyDown={stopPropagation} onKeyUp={stopPropagation}>
<TextField <TextField
placeholder="Page name" placeholder={intl.formatMessage({ id: 'page.name' })}
value={pageName} value={pageName}
onChange={handleRename} onChange={handleRename}
icon={<Pencil1Icon />} icon={<Pencil1Icon />}
/> />
<Divider /> <Divider />
<DialogAction onSelect={handleDuplicate}>Duplicate</DialogAction> <DialogAction onSelect={handleDuplicate}>
<FormattedMessage id="duplicate" />
</DialogAction>
<DialogAction disabled={!canDelete} onSelect={handleDelete}> <DialogAction disabled={!canDelete} onSelect={handleDelete}>
Delete <FormattedMessage id="delete" />
</DialogAction> </DialogAction>
<Divider /> <Divider />
<Dialog.Cancel asChild> <Dialog.Cancel asChild>
<RowButton>Cancel</RowButton> <RowButton>
<FormattedMessage id="cancel" />
</RowButton>
</Dialog.Cancel> </Dialog.Cancel>
</StyledDialogContent> </StyledDialogContent>
</Dialog.Portal> </Dialog.Portal>

Wyświetl plik

@ -1,4 +1,5 @@
import * as React from 'react' import * as React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/Primitives/DropdownMenu' import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/Primitives/DropdownMenu'
import { useTldrawApp } from '~hooks' import { useTldrawApp } from '~hooks'
import { TDSnapshot } from '~types' import { TDSnapshot } from '~types'
@ -7,58 +8,59 @@ const settingsSelector = (s: TDSnapshot) => s.settings
export function PreferencesMenu() { export function PreferencesMenu() {
const app = useTldrawApp() const app = useTldrawApp()
const intl = useIntl()
const settings = app.useStore(settingsSelector) const settings = app.useStore(settingsSelector)
const toggleDebugMode = React.useCallback(() => { const toggleDebugMode = React.useCallback(() => {
app.setSetting('isDebugMode', v => !v) app.setSetting('isDebugMode', (v) => !v)
}, [app]) }, [app])
const toggleDarkMode = React.useCallback(() => { const toggleDarkMode = React.useCallback(() => {
app.setSetting('isDarkMode', v => !v) app.setSetting('isDarkMode', (v) => !v)
}, [app]) }, [app])
const toggleFocusMode = React.useCallback(() => { const toggleFocusMode = React.useCallback(() => {
app.setSetting('isFocusMode', v => !v) app.setSetting('isFocusMode', (v) => !v)
}, [app]) }, [app])
const toggleRotateHandle = React.useCallback(() => { const toggleRotateHandle = React.useCallback(() => {
app.setSetting('showRotateHandles', v => !v) app.setSetting('showRotateHandles', (v) => !v)
}, [app]) }, [app])
const toggleGrid = React.useCallback(() => { const toggleGrid = React.useCallback(() => {
app.setSetting('showGrid', v => !v) app.setSetting('showGrid', (v) => !v)
}, [app]) }, [app])
const toggleBoundShapesHandle = React.useCallback(() => { const toggleBoundShapesHandle = React.useCallback(() => {
app.setSetting('showBindingHandles', v => !v) app.setSetting('showBindingHandles', (v) => !v)
}, [app]) }, [app])
const toggleisSnapping = React.useCallback(() => { const toggleisSnapping = React.useCallback(() => {
app.setSetting('isSnapping', v => !v) app.setSetting('isSnapping', (v) => !v)
}, [app]) }, [app])
const toggleKeepStyleMenuOpen = React.useCallback(() => { const toggleKeepStyleMenuOpen = React.useCallback(() => {
app.setSetting('keepStyleMenuOpen', v => !v) app.setSetting('keepStyleMenuOpen', (v) => !v)
}, [app]) }, [app])
const toggleCloneControls = React.useCallback(() => { const toggleCloneControls = React.useCallback(() => {
app.setSetting('showCloneHandles', v => !v) app.setSetting('showCloneHandles', (v) => !v)
}, [app]) }, [app])
const toggleCadSelectMode = React.useCallback(() => { const toggleCadSelectMode = React.useCallback(() => {
app.setSetting('isCadSelectMode', v => !v) app.setSetting('isCadSelectMode', (v) => !v)
}, [app]) }, [app])
return ( return (
<DMSubMenu label="Preferences" id="TD-MenuItem-Preferences"> <DMSubMenu label={intl.formatMessage({ id: 'menu.preferences' })} id="TD-MenuItem-Preferences">
<DMCheckboxItem <DMCheckboxItem
checked={settings.isDarkMode} checked={settings.isDarkMode}
onCheckedChange={toggleDarkMode} onCheckedChange={toggleDarkMode}
kbd="#⇧D" kbd="#⇧D"
id="TD-MenuItem-Preferences-Dark_Mode" id="TD-MenuItem-Preferences-Dark_Mode"
> >
Dark Mode <FormattedMessage id="preferences.dark.mode" />
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem <DMCheckboxItem
checked={settings.isFocusMode} checked={settings.isFocusMode}
@ -66,14 +68,14 @@ export function PreferencesMenu() {
kbd="#." kbd="#."
id="TD-MenuItem-Preferences-Focus_Mode" id="TD-MenuItem-Preferences-Focus_Mode"
> >
Focus Mode <FormattedMessage id="preferences.focus.mode" />
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem <DMCheckboxItem
checked={settings.isDebugMode} checked={settings.isDebugMode}
onCheckedChange={toggleDebugMode} onCheckedChange={toggleDebugMode}
id="TD-MenuItem-Preferences-Debug_Mode" id="TD-MenuItem-Preferences-Debug_Mode"
> >
Debug Mode <FormattedMessage id="preferences.debug.mode" />
</DMCheckboxItem> </DMCheckboxItem>
<DMDivider /> <DMDivider />
<DMCheckboxItem <DMCheckboxItem
@ -82,49 +84,49 @@ export function PreferencesMenu() {
kbd="#⇧G" kbd="#⇧G"
id="TD-MenuItem-Preferences-Grid" id="TD-MenuItem-Preferences-Grid"
> >
Show Grid <FormattedMessage id="preferences.show.grid" />
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem <DMCheckboxItem
checked={settings.isCadSelectMode} checked={settings.isCadSelectMode}
onCheckedChange={toggleCadSelectMode} onCheckedChange={toggleCadSelectMode}
id="TD-MenuItem-Preferences-Cad_Selection" id="TD-MenuItem-Preferences-Cad_Selection"
> >
Use CAD Selection <FormattedMessage id="preferences.use.cad.selection" />
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem <DMCheckboxItem
checked={settings.keepStyleMenuOpen} checked={settings.keepStyleMenuOpen}
onCheckedChange={toggleKeepStyleMenuOpen} onCheckedChange={toggleKeepStyleMenuOpen}
id="TD-MenuItem-Preferences-Style_menu" id="TD-MenuItem-Preferences-Style_menu"
> >
Keep Style Menu Open <FormattedMessage id="preferences.keep.stylemenu.open" />
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem <DMCheckboxItem
checked={settings.isSnapping} checked={settings.isSnapping}
onCheckedChange={toggleisSnapping} onCheckedChange={toggleisSnapping}
id="TD-MenuItem-Preferences-Always_Show_Snaps" id="TD-MenuItem-Preferences-Always_Show_Snaps"
> >
Always Show Snaps <FormattedMessage id="preferences.always.show.snaps" />
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem <DMCheckboxItem
checked={settings.showRotateHandles} checked={settings.showRotateHandles}
onCheckedChange={toggleRotateHandle} onCheckedChange={toggleRotateHandle}
id="TD-MenuItem-Preferences-Rotate_Handles" id="TD-MenuItem-Preferences-Rotate_Handles"
> >
Rotate Handles <FormattedMessage id="preferences.rotate.handles" />
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem <DMCheckboxItem
checked={settings.showBindingHandles} checked={settings.showBindingHandles}
onCheckedChange={toggleBoundShapesHandle} onCheckedChange={toggleBoundShapesHandle}
id="TD-MenuItem-Preferences-Binding_Handles" id="TD-MenuItem-Preferences-Binding_Handles"
> >
Binding Handles <FormattedMessage id="preferences.binding.handles" />
</DMCheckboxItem> </DMCheckboxItem>
<DMCheckboxItem <DMCheckboxItem
checked={settings.showCloneHandles} checked={settings.showCloneHandles}
onCheckedChange={toggleCloneControls} onCheckedChange={toggleCloneControls}
id="TD-MenuItem-Preferences-Clone_Handles" id="TD-MenuItem-Preferences-Clone_Handles"
> >
Clone Handles <FormattedMessage id="preferences.clone.handles" />
</DMCheckboxItem> </DMCheckboxItem>
</DMSubMenu> </DMSubMenu>
) )

Wyświetl plik

@ -1,6 +1,7 @@
import * as React from 'react' import * as React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { strokes, fills, defaultTextStyle } from '~state/shapes/shared/shape-styles' import { strokes, fills, defaultTextStyle } from '~state/shapes/shared/shape-styles'
import { FormattedMessage } from 'react-intl'
import { useTldrawApp } from '~hooks' import { useTldrawApp } from '~hooks'
import { import {
DMCheckboxItem, DMCheckboxItem,
@ -135,9 +136,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
} else { } else {
const overrides = new Set<string>([]) const overrides = new Set<string>([])
app.selectedIds app.selectedIds
.map(id => page.shapes[id]) .map((id) => page.shapes[id])
.forEach(shape => { .forEach((shape) => {
STYLE_KEYS.forEach(key => { STYLE_KEYS.forEach((key) => {
if (overrides.has(key)) return if (overrides.has(key)) return
if (commonStyle[key] === undefined) { if (commonStyle[key] === undefined) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@ -201,7 +202,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
> >
<DropdownMenu.Trigger asChild id="TD-Styles"> <DropdownMenu.Trigger asChild id="TD-Styles">
<ToolButton variant="text"> <ToolButton variant="text">
Styles <FormattedMessage id="styles" />
<OverlapIcons <OverlapIcons
style={{ style={{
color: strokes[theme][displayedStyle.color as ColorStyle], color: strokes[theme][displayedStyle.color as ColorStyle],
@ -220,7 +221,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DMContent> <DMContent>
<StyledRow variant="tall" id="TD-Styles-Color-Container"> <StyledRow variant="tall" id="TD-Styles-Color-Container">
<span>Color</span> <span>
<FormattedMessage id="style.menu.color" />
</span>
<ColorGrid> <ColorGrid>
{Object.keys(strokes.light).map((style: string) => ( {Object.keys(strokes.light).map((style: string) => (
<DropdownMenu.Item <DropdownMenu.Item
@ -253,12 +256,12 @@ export const StyleMenu = React.memo(function ColorMenu() {
onCheckedChange={handleToggleFilled} onCheckedChange={handleToggleFilled}
id="TD-Styles-Fill" id="TD-Styles-Fill"
> >
Fill <FormattedMessage id="style.menu.fill" />
</DMCheckboxItem> </DMCheckboxItem>
<StyledRow id="TD-Styles-Dash-Container"> <StyledRow id="TD-Styles-Dash-Container">
Dash <FormattedMessage id="style.menu.dash" />
<StyledGroup dir="ltr" value={displayedStyle.dash} onValueChange={handleDashChange}> <StyledGroup dir="ltr" value={displayedStyle.dash} onValueChange={handleDashChange}>
{Object.values(DashStyle).map(style => ( {Object.values(DashStyle).map((style) => (
<DMRadioItem <DMRadioItem
key={style} key={style}
isActive={style === displayedStyle.dash} isActive={style === displayedStyle.dash}
@ -273,9 +276,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
</StyledGroup> </StyledGroup>
</StyledRow> </StyledRow>
<StyledRow id="TD-Styles-Size-Container"> <StyledRow id="TD-Styles-Size-Container">
Size <FormattedMessage id="style.menu.size" />
<StyledGroup dir="ltr" value={displayedStyle.size} onValueChange={handleSizeChange}> <StyledGroup dir="ltr" value={displayedStyle.size} onValueChange={handleSizeChange}>
{Object.values(SizeStyle).map(sizeStyle => ( {Object.values(SizeStyle).map((sizeStyle) => (
<DMRadioItem <DMRadioItem
key={sizeStyle} key={sizeStyle}
isActive={sizeStyle === displayedStyle.size} isActive={sizeStyle === displayedStyle.size}
@ -293,9 +296,9 @@ export const StyleMenu = React.memo(function ColorMenu() {
<> <>
<Divider /> <Divider />
<StyledRow id="TD-Styles-Font-Container"> <StyledRow id="TD-Styles-Font-Container">
Font <FormattedMessage id="style.menu.font" />
<StyledGroup dir="ltr" value={displayedStyle.font} onValueChange={handleFontChange}> <StyledGroup dir="ltr" value={displayedStyle.font} onValueChange={handleFontChange}>
{Object.values(FontStyle).map(fontStyle => ( {Object.values(FontStyle).map((fontStyle) => (
<DMRadioItem <DMRadioItem
key={fontStyle} key={fontStyle}
isActive={fontStyle === displayedStyle.font} isActive={fontStyle === displayedStyle.font}
@ -311,13 +314,13 @@ export const StyleMenu = React.memo(function ColorMenu() {
</StyledRow> </StyledRow>
{options === 'text' && ( {options === 'text' && (
<StyledRow id="TD-Styles-Align-Container"> <StyledRow id="TD-Styles-Align-Container">
Align <FormattedMessage id="style.menu.align" />
<StyledGroup <StyledGroup
dir="ltr" dir="ltr"
value={displayedStyle.textAlign} value={displayedStyle.textAlign}
onValueChange={handleTextAlignChange} onValueChange={handleTextAlignChange}
> >
{Object.values(AlignStyle).map(style => ( {Object.values(AlignStyle).map((style) => (
<DMRadioItem <DMRadioItem
key={style} key={style}
isActive={style === displayedStyle.textAlign} isActive={style === displayedStyle.textAlign}
@ -341,7 +344,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
onCheckedChange={handleToggleKeepOpen} onCheckedChange={handleToggleKeepOpen}
id="TD-Styles-Keep-Open" id="TD-Styles-Keep-Open"
> >
Keep Open <FormattedMessage id="style.menu.keep.open" />
</DMCheckboxItem> </DMCheckboxItem>
</DMContent> </DMContent>
</DropdownMenu.Root> </DropdownMenu.Root>

Wyświetl plik

@ -6,6 +6,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { DMItem, DMContent } from '~components/Primitives/DropdownMenu' import { DMItem, DMContent } from '~components/Primitives/DropdownMenu'
import { ToolButton } from '~components/Primitives/ToolButton' import { ToolButton } from '~components/Primitives/ToolButton'
import { preventEvent } from '~components/preventEvent' import { preventEvent } from '~components/preventEvent'
import { FormattedMessage } from 'react-intl'
const zoomSelector = (s: TDSnapshot) => s.document.pageStates[s.appState.currentPageId].camera.zoom const zoomSelector = (s: TDSnapshot) => s.document.pageStates[s.appState.currentPageId].camera.zoom
@ -23,16 +24,16 @@ export const ZoomMenu = React.memo(function ZoomMenu() {
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DMContent align="end"> <DMContent align="end">
<DMItem onSelect={preventEvent} onClick={app.zoomIn} kbd="#+" id="TD-Zoom-Zoom_In"> <DMItem onSelect={preventEvent} onClick={app.zoomIn} kbd="#+" id="TD-Zoom-Zoom_In">
Zoom In <FormattedMessage id="zoom.in" />
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} onClick={app.zoomOut} kbd="#" id="TD-Zoom-Zoom_Out"> <DMItem onSelect={preventEvent} onClick={app.zoomOut} kbd="#" id="TD-Zoom-Zoom_Out">
Zoom Out <FormattedMessage id="zoom.out" />
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} onClick={app.resetZoom} kbd="⇧0" id="TD-Zoom-Zoom_To_100%"> <DMItem onSelect={preventEvent} onClick={app.resetZoom} kbd="⇧0" id="TD-Zoom-Zoom_To_100%">
To 100% <FormattedMessage id="to" /> 100%
</DMItem> </DMItem>
<DMItem onSelect={preventEvent} onClick={app.zoomToFit} kbd="⇧1" id="TD-Zoom-To_Fit"> <DMItem onSelect={preventEvent} onClick={app.zoomToFit} kbd="⇧1" id="TD-Zoom-To_Fit">
To Fit <FormattedMessage id="to.fit" />
</DMItem> </DMItem>
<DMItem <DMItem
onSelect={preventEvent} onSelect={preventEvent}
@ -40,7 +41,7 @@ export const ZoomMenu = React.memo(function ZoomMenu() {
kbd="⇧2" kbd="⇧2"
id="TD-Zoom-To_Selection" id="TD-Zoom-To_Selection"
> >
To Selection <FormattedMessage id="to.selection" />
</DMItem> </DMItem>
</DMContent> </DMContent>
</DropdownMenu.Root> </DropdownMenu.Root>

Wyświetl plik

@ -22,17 +22,17 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
if (!canHandleEvent(true)) return if (!canHandleEvent(true)) return
if (app.readOnly) { if (app.readOnly) {
app.copy(undefined, undefined, e) app.copy(undefined, e)
return return
} }
app.cut(undefined, undefined, e) app.cut(undefined, e)
} }
const handleCopy = (e: ClipboardEvent) => { const handleCopy = (e: ClipboardEvent) => {
if (!canHandleEvent(true)) return if (!canHandleEvent(true)) return
app.copy(undefined, undefined, e) app.copy(undefined, e)
} }
const handlePaste = (e: ClipboardEvent) => { const handlePaste = (e: ClipboardEvent) => {
@ -159,7 +159,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'ctrl+shift+d,⌘+shift+d', 'ctrl+shift+d,⌘+shift+d',
e => { (e) => {
if (!canHandleEvent(true)) return if (!canHandleEvent(true)) return
app.toggleDarkMode() app.toggleDarkMode()
e.preventDefault() e.preventDefault()
@ -192,17 +192,12 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
// File System // File System
const { const { onNewProject, onOpenProject, onSaveProject, onSaveProjectAs, onOpenMedia } =
onNewProject, useFileSystemHandlers()
onOpenProject,
onSaveProject,
onSaveProjectAs,
onOpenMedia,
} = useFileSystemHandlers()
useHotkeys( useHotkeys(
'ctrl+n,⌘+n', 'ctrl+n,⌘+n',
e => { (e) => {
if (!canHandleEvent()) return if (!canHandleEvent()) return
onNewProject(e) onNewProject(e)
@ -212,7 +207,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
) )
useHotkeys( useHotkeys(
'ctrl+s,⌘+s', 'ctrl+s,⌘+s',
e => { (e) => {
if (!canHandleEvent()) return if (!canHandleEvent()) return
onSaveProject(e) onSaveProject(e)
@ -223,7 +218,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'ctrl+shift+s,⌘+shift+s', 'ctrl+shift+s,⌘+shift+s',
e => { (e) => {
if (!canHandleEvent()) return if (!canHandleEvent()) return
onSaveProjectAs(e) onSaveProjectAs(e)
@ -233,7 +228,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
) )
useHotkeys( useHotkeys(
'ctrl+o,⌘+o', 'ctrl+o,⌘+o',
e => { (e) => {
if (!canHandleEvent()) return if (!canHandleEvent()) return
onOpenProject(e) onOpenProject(e)
@ -243,7 +238,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
) )
useHotkeys( useHotkeys(
'ctrl+u,⌘+u', 'ctrl+u,⌘+u',
e => { (e) => {
if (!canHandleEvent()) return if (!canHandleEvent()) return
onOpenMedia(e) onOpenMedia(e)
}, },
@ -311,7 +306,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'ctrl+=,⌘+=,ctrl+num_subtract,⌘+num_subtract', 'ctrl+=,⌘+=,ctrl+num_subtract,⌘+num_subtract',
e => { (e) => {
if (!canHandleEvent(true)) return if (!canHandleEvent(true)) return
app.zoomIn() app.zoomIn()
e.preventDefault() e.preventDefault()
@ -322,7 +317,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'ctrl+-,⌘+-,ctrl+num_add,⌘+num_add', 'ctrl+-,⌘+-,ctrl+num_add,⌘+num_add',
e => { (e) => {
if (!canHandleEvent(true)) return if (!canHandleEvent(true)) return
app.zoomOut() app.zoomOut()
@ -366,7 +361,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'ctrl+d,⌘+d', 'ctrl+d,⌘+d',
e => { (e) => {
if (!canHandleEvent()) return if (!canHandleEvent()) return
app.duplicate() app.duplicate()
@ -541,7 +536,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'⌘+shift+c,ctrl+shift+c', '⌘+shift+c,ctrl+shift+c',
e => { (e) => {
if (!canHandleEvent()) return if (!canHandleEvent()) return
app.copySvg() app.copySvg()
@ -576,7 +571,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'⌘+g,ctrl+g', '⌘+g,ctrl+g',
e => { (e) => {
if (!canHandleEvent()) return if (!canHandleEvent()) return
app.group() app.group()
@ -588,7 +583,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'⌘+shift+g,ctrl+shift+g', '⌘+shift+g,ctrl+shift+g',
e => { (e) => {
if (!canHandleEvent()) return if (!canHandleEvent()) return
app.ungroup() app.ungroup()
@ -642,7 +637,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'ctrl+shift+backspace,⌘+shift+backspace', 'ctrl+shift+backspace,⌘+shift+backspace',
e => { (e) => {
if (!canHandleEvent()) return if (!canHandleEvent()) return
if (app.settings.isDebugMode) { if (app.settings.isDebugMode) {
app.resetDocument() app.resetDocument()
@ -657,7 +652,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'alt+command+l,alt+ctrl+l', 'alt+command+l,alt+ctrl+l',
e => { (e) => {
if (!canHandleEvent(true)) return if (!canHandleEvent(true)) return
app.style({ textAlign: AlignStyle.Start }) app.style({ textAlign: AlignStyle.Start })
e.preventDefault() e.preventDefault()
@ -668,7 +663,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'alt+command+t,alt+ctrl+t', 'alt+command+t,alt+ctrl+t',
e => { (e) => {
if (!canHandleEvent(true)) return if (!canHandleEvent(true)) return
app.style({ textAlign: AlignStyle.Middle }) app.style({ textAlign: AlignStyle.Middle })
e.preventDefault() e.preventDefault()
@ -679,7 +674,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys( useHotkeys(
'alt+command+r,alt+ctrl+r', 'alt+command+r,alt+ctrl+r',
e => { (e) => {
if (!canHandleEvent(true)) return if (!canHandleEvent(true)) return
app.style({ textAlign: AlignStyle.End }) app.style({ textAlign: AlignStyle.End })
e.preventDefault() e.preventDefault()

Wyświetl plik

@ -147,7 +147,7 @@ export class StateManager<T extends Record<string, any>> {
*/ */
private applyPatch = (patch: Patch<T>, id?: string) => { private applyPatch = (patch: Patch<T>, id?: string) => {
const prev = this._state const prev = this._state
const next = Utils.deepMerge(this._state, patch) const next = Utils.deepMerge(this._state, patch as any)
const final = this.cleanup(next, prev, patch, id) const final = this.cleanup(next, prev, patch, id)
if (this.onStateWillChange) { if (this.onStateWillChange) {
this.onStateWillChange(final, id) this.onStateWillChange(final, id)
@ -175,7 +175,7 @@ export class StateManager<T extends Record<string, any>> {
* @param id (optional) An id for the just-applied patch. * @param id (optional) An id for the just-applied patch.
* @returns The final new state to apply. * @returns The final new state to apply.
*/ */
protected cleanup = (nextState: T, prevState: T, patch: Patch<T>, id?: string): T => nextState protected cleanup = (nextState: T, _prevState: T, _patch: Patch<T>, _id?: string): T => nextState
/** /**
* A life-cycle method called when the state is about to change. * A life-cycle method called when the state is about to change.

Wyświetl plik

@ -1713,10 +1713,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
/* Clipboard */ /* Clipboard */
/* -------------------------------------------------- */ /* -------------------------------------------------- */
private getClipboard( private getClipboard(ids = this.selectedIds):
ids = this.selectedIds,
pageId = this.currentPageId
):
| { | {
shapes: TDShape[] shapes: TDShape[]
bindings: TDBinding[] bindings: TDBinding[]
@ -1757,10 +1754,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
* Cut (copy and delete) one or more shapes to the clipboard. * Cut (copy and delete) one or more shapes to the clipboard.
* @param ids The ids of the shapes to cut. * @param ids The ids of the shapes to cut.
*/ */
cut = (ids = this.selectedIds, pageId = this.currentPageId, e?: ClipboardEvent): this => { cut = (ids = this.selectedIds, e?: ClipboardEvent): this => {
e?.preventDefault() e?.preventDefault()
this.copy(ids, pageId, e) this.copy(ids, e)
if (!this.readOnly) { if (!this.readOnly) {
this.delete(ids) this.delete(ids)
} }
@ -1771,10 +1768,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
* Copy one or more shapes to the clipboard. * Copy one or more shapes to the clipboard.
* @param ids The ids of the shapes to copy. * @param ids The ids of the shapes to copy.
*/ */
copy = (ids = this.selectedIds, pageId = this.currentPageId, e?: ClipboardEvent): this => { copy = (ids = this.selectedIds, e?: ClipboardEvent): this => {
e?.preventDefault() e?.preventDefault()
this.clipboard = this.getClipboard(ids, pageId) this.clipboard = this.getClipboard(ids)
const jsonString = JSON.stringify({ const jsonString = JSON.stringify({
type: 'tldr/clipboard', type: 'tldr/clipboard',
@ -1962,9 +1959,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
} }
if (e !== undefined) { if (e !== undefined) {
let items = e.clipboardData?.items ?? [] const items = e.clipboardData?.items ?? []
for (var index in items) { for (const index in items) {
var item = items[index] const item = items[index]
// TODO // TODO
// We could eventually support pasting multiple files / images, // We could eventually support pasting multiple files / images,
@ -1989,7 +1986,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
return return
} }
case 'file': { case 'file': {
var file = item.getAsFile() const file = item.getAsFile()
if (file) { if (file) {
this.addMediaFromFile(file) this.addMediaFromFile(file)
return return
@ -2386,7 +2383,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
transparentBackground: boolean transparentBackground: boolean
}> }>
) => { ) => {
const { scale = 2, quality = 1, ids = this.selectedIds, pageId = this.currentPageId } = opts const { pageId = this.currentPageId } = opts
const blob = await this.getImage(format, opts) const blob = await this.getImage(format, opts)
@ -2977,7 +2974,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
// Ensure that the pasted shape fits inside of the current viewport // Ensure that the pasted shape fits inside of the current viewport
if (size[0] > this.viewport.width) { if (size[0] > this.viewport.width) {
let r = size[1] / size[0] const r = size[1] / size[0]
size[0] = this.viewport.width - (FIT_TO_SCREEN_PADDING / this.camera.zoom) * 2 size[0] = this.viewport.width - (FIT_TO_SCREEN_PADDING / this.camera.zoom) * 2
size[1] = size[0] * r size[1] = size[0] * r
if (size[1] < 32 || size[1] < 32) { if (size[1] < 32 || size[1] < 32) {
@ -2985,7 +2982,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
size[0] = size[1] / r size[0] = size[1] / r
} }
} else if (size[1] > this.viewport.height) { } else if (size[1] > this.viewport.height) {
let r = size[0] / size[1] const r = size[0] / size[1]
size[1] = this.viewport.height - (FIT_TO_SCREEN_PADDING / this.camera.zoom) * 2 size[1] = this.viewport.height - (FIT_TO_SCREEN_PADDING / this.camera.zoom) * 2
size[0] = size[1] * r size[0] = size[1] * r
if (size[1] < 32 || size[1] < 32) { if (size[1] < 32 || size[1] < 32) {
@ -4093,6 +4090,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
showBindingHandles: true, showBindingHandles: true,
showCloneHandles: false, showCloneHandles: false,
showGrid: false, showGrid: false,
language: 'en',
}, },
appState: { appState: {
status: TDStatus.Idle, status: TDStatus.Idle,

Wyświetl plik

@ -1,12 +1,5 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import { import { TLPageState, Utils, TLBoundsWithCenter, TLSnapLine, TLBounds } from '@tldraw/core'
TLPageState,
Utils,
TLBoundsWithCenter,
TLSnapLine,
TLBounds,
TLPerformanceMode,
} from '@tldraw/core'
import { Vec } from '@tldraw/vec' import { Vec } from '@tldraw/vec'
import { import {
TDShape, TDShape,

Wyświetl plik

@ -277,7 +277,7 @@ export class DrawUtil extends TDShapeUtil<T, E> {
const bounds = this.getBounds(shape) const bounds = this.getBounds(shape)
if (bounds.width < 8 && bounds.height < 8) { if (bounds.width < 8 && bounds.height < 8) {
return Vec.distanceToLineSegment(A, B, Utils.getBoundsCenter(bounds)) < 5 return Vec.distanceToLineSegment(A, B, Utils.getBoundsCenter(bounds)) < 5 // divide by zoom
} }
if (intersectLineSegmentBounds(ptA, ptB, bounds)) { if (intersectLineSegmentBounds(ptA, ptB, bounds)) {

Wyświetl plik

@ -146,7 +146,7 @@ export class TextUtil extends TDShapeUtil<T, E> {
) )
const handlePointerDown = React.useCallback( const handlePointerDown = React.useCallback(
(e) => { (e: React.PointerEvent<HTMLDivElement | HTMLTextAreaElement>) => {
if (isEditing) { if (isEditing) {
e.stopPropagation() e.stopPropagation()
} }

Wyświetl plik

@ -1,6 +1,6 @@
import * as React from 'react' import * as React from 'react'
import { stopPropagation } from '~components/stopPropagation' 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 { TLDR } from '~state/TLDR'
import { styled } from '~styles' import { styled } from '~styles'
import { getTextLabelSize } from './getTextSize' import { getTextLabelSize } from './getTextSize'
@ -95,7 +95,7 @@ export const TextLabel = React.memo(function TextLabel({
) )
const handlePointerDown = React.useCallback( const handlePointerDown = React.useCallback(
(e) => { (e: React.PointerEvent<HTMLTextAreaElement | HTMLDivElement>) => {
if (isEditing) { if (isEditing) {
e.stopPropagation() e.stopPropagation()
} }

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -76,6 +76,8 @@ export class TDEventHandler {
onShapeClone?: TLShapeCloneHandler onShapeClone?: TLShapeCloneHandler
} }
export type TDLanguage = 'en' | 'fr' | 'it'
// The shape of the TldrawApp's React (zustand) store // The shape of the TldrawApp's React (zustand) store
export interface TDSnapshot { export interface TDSnapshot {
settings: { settings: {
@ -94,6 +96,7 @@ export interface TDSnapshot {
showBindingHandles: boolean showBindingHandles: boolean
showCloneHandles: boolean showCloneHandles: boolean
showGrid: boolean showGrid: boolean
language: TDLanguage
} }
appState: { appState: {
currentStyle: ShapeStyles currentStyle: ShapeStyles

Wyświetl plik

@ -1,7 +1,7 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"exclude": ["node_modules", "dist", "docs"], "exclude": ["node_modules", "dist", "docs"],
"include": ["src"], "include": ["src", "./src/translations/*.json"],
"compilerOptions": { "compilerOptions": {
"skipLibCheck": true, "skipLibCheck": true,
"outDir": "./dist", "outDir": "./dist",

707
yarn.lock

Plik diff jest za duży Load Diff