* add electron wrapper

* add to workspaces

* fixes electron setup

* Fix package for dev

* build out electron app communication

* Update README.md
pull/225/head
Steve Ruiz 2021-11-07 13:45:48 +00:00 zatwierdzone przez GitHub
rodzic 0d20994de9
commit 7c980ebb19
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
40 zmienionych plików z 4120 dodań i 96 usunięć

Wyświetl plik

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Stephen Ruiz Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11
electron/README.md 100644
Wyświetl plik

@ -0,0 +1,11 @@
# @tldraw/tldraw-electron
An electron wrapper for TLDraw. Not yet published.
## Development
From the root of the repository, run:
```bash
yarn start:vscode
```

Wyświetl plik

@ -0,0 +1,11 @@
mainConfig:
type: esbuild
path: esbuild.main.config.ts
src: src/main/main.ts
output: dist/main
rendererConfig:
type: esbuild
path: esbuild.renderer.config.ts
html: src/renderer/index.html
src: src/renderer/index.tsx
output: dist/renderer

Wyświetl plik

@ -0,0 +1,12 @@
import { BuildOptions } from 'esbuild'
import path from 'path'
const config: BuildOptions = {
platform: 'node',
entryPoints: [path.resolve('src/main/main.ts'), path.resolve('src/main/preload.ts')],
bundle: true,
target: 'node16.5.0', // electron version target
sourcemap: true,
}
export default config

Wyświetl plik

@ -0,0 +1,12 @@
import { BuildOptions } from 'esbuild'
import path from 'path'
const config: BuildOptions = {
platform: 'browser',
entryPoints: [path.resolve('src/renderer/index.tsx')],
bundle: true,
target: 'chrome94', // electron version target
sourcemap: true,
}
export default config

Wyświetl plik

@ -0,0 +1,78 @@
{
"name": "@tldraw/electron",
"version": "0.1.4",
"private": true,
"description": "An electron app for tldraw.",
"author": "@steveruizok",
"license": "MIT",
"keywords": [
"react",
"typescript",
"esbuild"
],
"scripts": {
"start:electron": "yarn dev",
"dev": "electron-esbuild dev",
"build": "electron-esbuild build",
"package": "electron-builder"
},
"devDependencies": {
"@tldraw/tldraw": "^0.1.4",
"@types/node": "^14.14.35",
"@types/react": "^16.9.55",
"@types/react-dom": "^16.9.9",
"@types/react-router-dom": "^5.1.8",
"electron": "15.3.0",
"electron-builder": "^22.13.1",
"electron-esbuild": "^3.0.0",
"electron-util": "^0.17.2",
"esbuild": "^0.13.8",
"esbuild-serve": "^1.0.1",
"react": ">=16.8",
"react-dom": "^16.8 || ^17.0",
"rimraf": "3.0.2",
"typescript": "4.2.3"
},
"build": {
"appId": "io.comp.tldraw-electron",
"productName": "TLDraw",
"extraMetadata": {
"name": "TLDraw",
"main": "main.js"
},
"files": [
{
"from": ".",
"filter": [
"package.json"
]
},
{
"from": "dist/main"
},
{
"from": "dist/renderer"
}
],
"win": {
"target": [
"zip"
]
},
"mac": {
"target": [
"zip"
]
},
"linux": {
"target": [
"zip"
]
},
"directories": {
"buildResources": "resources"
},
"publish": null
},
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
}

Wyświetl plik

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 128 KiB

Wyświetl plik

@ -0,0 +1,20 @@
/* eslint-disable */
require('dotenv').config()
const { notarize } = require('electron-notarize')
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context
if (electronPlatformName !== 'darwin') {
return
}
const appName = context.packager.appInfo.productFilename
return await notarize({
appBundleId: 'com.tldraw.app',
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLEID,
appleIdPassword: process.env.APPLEIDPASS,
})
}

Wyświetl plik

@ -0,0 +1,114 @@
import { shell, app, Menu, MenuItemConstructorOptions } from 'electron'
import type { Message } from 'src/types'
export async function createMenu(send: (message: Message) => Promise<void>) {
const isMac = process.platform === 'darwin'
const template: MenuItemConstructorOptions[] = []
// About Menu (mac only)
if (isMac) {
template.push({
label: 'Hello world!',
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' },
],
})
}
// File Menu
template.push({
label: 'File',
submenu: [
{ label: 'New Project', click: () => send({ type: 'undo' }) },
{ type: 'separator' },
{ label: 'Open...', click: () => send({ type: 'redo' }) },
{ type: 'separator' },
{ label: 'Save', click: () => send({ type: 'redo' }) },
{ label: 'Save As...', click: () => send({ type: 'redo' }) },
{ type: 'separator' },
{ role: 'quit' },
],
})
// Edit Menu
template.push({
label: 'Edit',
submenu: [
{ label: 'Undo', click: () => send({ type: 'undo' }), accelerator: 'CmdOrCtrl+Z' },
{ label: 'Redo', click: () => send({ type: 'redo' }), accelerator: 'CmdOrCtrl+Shift+Z' },
{ type: 'separator' },
{ label: 'Cut', click: () => send({ type: 'cut' }), accelerator: 'CmdOrCtrl+X' },
{ label: 'Copy', click: () => send({ type: 'copy' }), accelerator: 'CmdOrCtrl+C' },
{ label: 'Paste', click: () => send({ type: 'paste' }), accelerator: 'CmdOrCtrl+V' },
{ label: 'Delete', click: () => send({ type: 'delete' }), accelerator: 'Delete' },
{ label: 'Select All', click: () => send({ type: 'selectAll' }), accelerator: 'CmdOrCtrl+A' },
{ label: 'Select None', click: () => send({ type: 'selectNone' }) },
],
})
// View Menu
template.push({
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{
label: 'Actual Size',
click: () => send({ type: 'resetZoom' }),
},
{ label: 'Zoom In', click: () => send({ type: 'zoomIn' }) },
{ label: 'Zoom Out', click: () => send({ type: 'zoomOut' }) },
{ label: 'Zoom to Fit', click: () => send({ type: 'zoomToFit' }) },
{ label: 'Zoom to Selection', click: () => send({ type: 'zoomToSelection' }) },
{ type: 'separator' },
{ role: 'togglefullscreen' },
],
})
// Window Menu
if (isMac) {
template.push({
label: 'Window',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
{ type: 'separator' },
{ role: 'front' },
{ type: 'separator' },
{ role: 'window' },
],
})
} else {
template.push({
label: 'Window',
submenu: [{ role: 'minimize' }, { role: 'zoom' }, { role: 'close' }],
})
}
template.push({
role: 'help',
submenu: [
{
label: 'Learn More',
click: async () => {
await shell.openExternal('https://electronjs.org')
},
},
],
})
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}

Wyświetl plik

@ -0,0 +1,58 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import path from 'path'
import { format } from 'url'
import { BrowserWindow } from 'electron'
import { is } from 'electron-util'
export async function createWindow() {
let win: BrowserWindow | null = null
win = new BrowserWindow({
width: 720,
height: 450,
minHeight: 480,
minWidth: 600,
titleBarStyle: 'hidden',
title: 'TLDraw',
webPreferences: {
nodeIntegration: true,
devTools: true,
preload: path.join(__dirname, 'preload.js'),
},
frame: false,
show: false,
})
win.setWindowButtonVisibility(false)
const isDev = is.development
if (isDev) {
win.loadURL('http://localhost:9080')
} else {
win.loadURL(path.join(__dirname, 'index.html'))
}
win.setPosition(0, 0, false)
win.setSize(700, 1200)
win.on('closed', () => {
win = null
})
win.webContents.on('devtools-opened', () => {
win!.focus()
})
win.on('ready-to-show', () => {
win!.show()
if (isDev) {
win!.webContents.openDevTools({ mode: 'bottom' })
} else {
win!.focus()
}
})
return win
}

Wyświetl plik

@ -0,0 +1,32 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { app, BrowserWindow } from 'electron'
import { is } from 'electron-util'
import type { Message } from 'src/types'
import { createMenu } from './createMenu'
import { createWindow } from './createWindow'
import './preload'
let win: BrowserWindow | null = null
async function main() {
win = await createWindow()
async function send(message: Message) {
win!.webContents.send('projectMsg', message)
}
await createMenu(send)
}
app
.on('ready', main)
.on('window-all-closed', () => {
if (!is.macos) {
app.quit()
}
})
.on('activate', () => {
if (win === null && app.isReady()) {
main()
}
})

Wyświetl plik

@ -0,0 +1,15 @@
import { contextBridge, ipcRenderer } from 'electron'
import type { Message, TLApi } from 'src/types'
const api: TLApi = {
send: (channel: string, data: Message) => {
ipcRenderer.send(channel, data)
},
on: (channel, cb) => {
ipcRenderer.on(channel, (event, message) => cb(message as Message))
},
}
contextBridge?.exposeInMainWorld('TLApi', api)
export {}

Wyświetl plik

@ -0,0 +1,85 @@
import * as React from 'react'
import { TLDraw, TLDrawState } from '@tldraw/tldraw'
import type { IpcMainEvent, IpcMain, IpcRenderer } from 'electron'
import type { Message, TLApi } from 'src/types'
export default function App(): JSX.Element {
const rTLDrawState = React.useRef<TLDrawState>()
// When the editor mounts, save the tlstate instance in a ref.
const handleMount = React.useCallback((tldr: TLDrawState) => {
rTLDrawState.current = tldr
}, [])
React.useEffect(() => {
function handleEvent(message: Message) {
const tlstate = rTLDrawState.current
if (!tlstate) return
switch (message.type) {
case 'resetZoom': {
tlstate.resetZoom()
break
}
case 'zoomIn': {
tlstate.zoomIn()
break
}
case 'zoomOut': {
tlstate.zoomOut()
break
}
case 'zoomToFit': {
tlstate.zoomToFit()
break
}
case 'zoomToSelection': {
tlstate.zoomToSelection()
break
}
case 'undo': {
tlstate.undo()
break
}
case 'redo': {
tlstate.redo()
break
}
case 'cut': {
tlstate.cut()
break
}
case 'copy': {
tlstate.copy()
break
}
case 'paste': {
tlstate.paste()
break
}
case 'delete': {
tlstate.delete()
break
}
case 'selectAll': {
tlstate.selectAll()
break
}
case 'selectNone': {
tlstate.selectNone()
break
}
}
}
const { send, on } = (window as unknown as Window & { TLApi: TLApi })['TLApi']
on('projectMsg', handleEvent)
})
return (
<div className="tldraw">
<TLDraw id="electron" onMount={handleMount} autofocus showMenu={false} />
</div>
)
}

Wyświetl plik

Wyświetl plik

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="index.js"></script>
</body>
</html>

Wyświetl plik

@ -0,0 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
import './styles.css'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)

Wyświetl plik

@ -0,0 +1,20 @@
html,
* {
box-sizing: border-box;
}
body {
overscroll-behavior: none;
margin: 0px;
padding: 0px;
}
.tldraw {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
width: 100%;
height: 100%;
}

Wyświetl plik

@ -0,0 +1,19 @@
export type Message =
| { type: 'zoomIn' }
| { type: 'zoomOut' }
| { type: 'resetZoom' }
| { type: 'zoomToFit' }
| { type: 'zoomToSelection' }
| { type: 'undo' }
| { type: 'redo' }
| { type: 'cut' }
| { type: 'copy' }
| { type: 'paste' }
| { type: 'delete' }
| { type: 'selectAll' }
| { type: 'selectNone' }
export type TLApi = {
send: (channel: string, data: Message) => void
on: (channel: string, cb: (message: Message) => void) => void
}

Wyświetl plik

@ -0,0 +1,24 @@
{
"extends": "../tsconfig.base.json",
"include": ["src"],
"exclude": ["node_modules", "dist", "docs"],
"compilerOptions": {
"outDir": "./dist/types",
"rootDir": ".",
"baseUrl": ".",
"allowJs": false,
"emitDeclarationOnly": true,
"paths": {
"@tldraw/tldraw": ["../packages/tldraw"]
}
},
"references": [
{
"path": "../packages/tldraw"
}
],
"typedocOptions": {
"entryPoints": ["src/index.ts"],
"out": "docs"
}
}

1892
electron/yarn.lock 100644

Plik diff jest za duży Load Diff

Wyświetl plik

@ -9,6 +9,7 @@
},
"license": "MIT",
"workspaces": [
"electron",
"vscode/editor",
"packages/tldraw",
"example",
@ -18,6 +19,7 @@
"test": "jest",
"test:watch": "jest --watchAll",
"lerna": "lerna",
"start:electron": "lerna run start:electron --stream --parallel",
"start:vscode": "lerna run start:vscode --stream --parallel",
"start": "lerna run start --stream --parallel",
"start:www": "yarn build:packages && lerna run start --parallel & cd www && yarn dev",

Wyświetl plik

@ -22,6 +22,7 @@
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"scripts": {
"start:electron": "yarn start",
"start:vscode": "yarn start",
"start": "node scripts/dev & yarn types:dev",
"build": "node scripts/build && yarn types:build && node scripts/copy-readme",

Wyświetl plik

@ -24,6 +24,10 @@ export const Menu = React.memo(function Menu({ readOnly }: MenuProps) {
callbacks.onSignOut?.(tlstate)
}, [tlstate])
const handleCut = React.useCallback(() => {
tlstate.cut()
}, [tlstate])
const handleCopy = React.useCallback(() => {
tlstate.copy()
}, [tlstate])
@ -44,8 +48,8 @@ export const Menu = React.memo(function Menu({ readOnly }: MenuProps) {
tlstate.selectAll()
}, [tlstate])
const handleDeselectAll = React.useCallback(() => {
tlstate.deselectAll()
const handleselectNone = React.useCallback(() => {
tlstate.selectNone()
}, [tlstate])
const showFileMenu =
@ -96,6 +100,9 @@ export const Menu = React.memo(function Menu({ readOnly }: MenuProps) {
Redo
</DMItem>
<DMDivider dir="ltr" />
<DMItem onSelect={handleCut} kbd="#X">
Cut
</DMItem>
<DMItem onSelect={handleCopy} kbd="#C">
Copy
</DMItem>
@ -111,7 +118,7 @@ export const Menu = React.memo(function Menu({ readOnly }: MenuProps) {
<DMItem onSelect={handleSelectAll} kbd="#A">
Select All
</DMItem>
<DMItem onSelect={handleDeselectAll}>Select None</DMItem>
<DMItem onSelect={handleselectNone}>Select None</DMItem>
</DMSubMenu>
<DMDivider dir="ltr" />
</>

Wyświetl plik

@ -24,7 +24,7 @@ export function ZoomMenu() {
<DMItem onSelect={tlstate.zoomOut} kbd="#">
Zoom Out
</DMItem>
<DMItem onSelect={tlstate.zoomToActual} kbd="⇧0">
<DMItem onSelect={tlstate.resetZoom} kbd="⇧0">
To 100%
</DMItem>
<DMItem onSelect={tlstate.zoomToFit} kbd="⇧1">

Wyświetl plik

@ -249,7 +249,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
useHotkeys(
'shift+0',
() => {
if (canHandleEvent()) tlstate.zoomToActual()
if (canHandleEvent()) tlstate.resetZoom()
},
undefined,
[tlstate]
@ -398,7 +398,7 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
[tlstate]
)
// Copy & Paste
// Copy, Cut & Paste
useHotkeys(
'command+c,ctrl+c',
@ -409,6 +409,15 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
[tlstate]
)
useHotkeys(
'command+x,ctrl+x',
() => {
if (canHandleEvent()) tlstate.cut()
},
undefined,
[tlstate]
)
useHotkeys(
'command+v,ctrl+v',
() => {

Wyświetl plik

@ -11,7 +11,7 @@ describe('TLDrawState', () => {
describe('When copying and pasting...', () => {
it('copies a shape', () => {
tlstate.loadDocument(mockDocument).deselectAll().copy(['rect1'])
tlstate.loadDocument(mockDocument).selectNone().copy(['rect1'])
})
it('pastes a shape', () => {
@ -19,7 +19,7 @@ describe('TLDrawState', () => {
const prevCount = Object.keys(tlstate.page.shapes).length
tlstate.deselectAll().copy(['rect1']).paste()
tlstate.selectNone().copy(['rect1']).paste()
expect(Object.keys(tlstate.page.shapes).length).toBe(prevCount + 1)
@ -35,7 +35,7 @@ describe('TLDrawState', () => {
it('pastes a shape to a new page', () => {
tlstate.loadDocument(mockDocument)
tlstate.deselectAll().copy(['rect1']).createPage().paste()
tlstate.selectNone().copy(['rect1']).createPage().paste()
expect(Object.keys(tlstate.page.shapes).length).toBe(1)
@ -135,14 +135,14 @@ describe('TLDrawState', () => {
describe('Selection', () => {
it('selects a shape', () => {
tlstate.loadDocument(mockDocument).deselectAll()
tlstate.loadDocument(mockDocument).selectNone()
tlu.clickShape('rect1')
expect(tlstate.selectedIds).toStrictEqual(['rect1'])
expect(tlstate.appState.status).toBe('idle')
})
it('selects and deselects a shape', () => {
tlstate.loadDocument(mockDocument).deselectAll()
tlstate.loadDocument(mockDocument).selectNone()
tlu.clickShape('rect1')
tlu.clickCanvas()
expect(tlstate.selectedIds).toStrictEqual([])
@ -150,7 +150,7 @@ describe('TLDrawState', () => {
})
it('selects multiple shapes', () => {
tlstate.loadDocument(mockDocument).deselectAll()
tlstate.loadDocument(mockDocument).selectNone()
tlu.clickShape('rect1')
tlu.clickShape('rect2', { shiftKey: true })
expect(tlstate.selectedIds).toStrictEqual(['rect1', 'rect2'])
@ -158,7 +158,7 @@ describe('TLDrawState', () => {
})
it('shift-selects to deselect shapes', () => {
tlstate.loadDocument(mockDocument).deselectAll()
tlstate.loadDocument(mockDocument).selectNone()
tlu.clickShape('rect1')
tlu.clickShape('rect2', { shiftKey: true })
tlu.clickShape('rect2', { shiftKey: true })
@ -167,7 +167,7 @@ describe('TLDrawState', () => {
})
it('clears selection when clicking bounds', () => {
tlstate.loadDocument(mockDocument).deselectAll()
tlstate.loadDocument(mockDocument).selectNone()
tlstate.startSession(SessionType.Brush, [-10, -10])
tlstate.updateSession([110, 110])
tlstate.completeSession()
@ -187,7 +187,7 @@ describe('TLDrawState', () => {
// })
it('does not select on meta-click', () => {
tlstate.loadDocument(mockDocument).deselectAll()
tlstate.loadDocument(mockDocument).selectNone()
tlu.clickShape('rect1', { ctrlKey: true })
expect(tlstate.selectedIds).toStrictEqual([])
expect(tlstate.appState.status).toBe('idle')
@ -214,7 +214,7 @@ describe('TLDrawState', () => {
// Single click on a selected shape to select just that shape
it('single-selects shape in selection on click', () => {
tlstate.deselectAll()
tlstate.selectNone()
tlu.clickShape('rect1')
tlu.clickShape('rect2', { shiftKey: true })
tlu.clickShape('rect2')
@ -223,7 +223,7 @@ describe('TLDrawState', () => {
})
it('single-selects shape in selection on pointerup only', () => {
tlstate.deselectAll()
tlstate.selectNone()
tlu.clickShape('rect1')
tlu.clickShape('rect2', { shiftKey: true })
tlu.pointShape('rect2')
@ -234,7 +234,7 @@ describe('TLDrawState', () => {
})
// it('selects shapes if shift key is lifted before pointerup', () => {
// tlstate.deselectAll()
// tlstate.selectNone()
// tlu.clickShape('rect1')
// tlu.pointShape('rect2', { shiftKey: true })
// expect(tlstate.appState.status).toBe('pointingBounds')
@ -390,7 +390,7 @@ describe('TLDrawState', () => {
const tlstate = new TLDrawState()
.loadDocument(mockDocument)
.group(['rect1', 'rect2'], 'groupA')
.deselectAll()
.selectNone()
const tlu = new TLDrawStateUtils(tlstate)
@ -454,7 +454,7 @@ describe('TLDrawState', () => {
expect(tlstate.getShape('groupA').childIndex).toBe(2)
tlstate.deselectAll()
tlstate.selectNone()
tlstate.selectTool(TLDrawShapeType.Rectangle)
const prevB = tlstate.shapes.map((shape) => shape.id)

Wyświetl plik

@ -685,7 +685,7 @@ export class TLDrawState extends StateManager<Data> {
if (!document.pages[pageId]) {
if (pageId === this.appState.currentPageId) {
this.cancelSession()
this.deselectAll()
this.selectNone()
}
currentPageStates[pageId] = undefined as unknown as TLPageState
@ -812,7 +812,7 @@ export class TLDrawState extends StateManager<Data> {
* @param document The document to load
*/
loadDocument = (document: TLDrawDocument): this => {
this.deselectAll()
this.selectNone()
this.resetHistory()
this.clearSelectHistory()
this.session = undefined
@ -1165,6 +1165,16 @@ export class TLDrawState extends StateManager<Data> {
return this
}
/**
* Cut (copy and delete) one or more shapes to the clipboard.
* @param ids The ids of the shapes to cut.
*/
cut = (ids = this.selectedIds): this => {
this.copy(ids)
this.delete(ids)
return this
}
/**
* Paste shapes (or text) from clipboard to a certain point.
* @param point
@ -1579,7 +1589,7 @@ export class TLDrawState extends StateManager<Data> {
/**
* Zoom the camera to 100%.
*/
zoomToActual = (): this => {
resetZoom = (): this => {
return this.zoomTo(1)
}
@ -1714,7 +1724,7 @@ export class TLDrawState extends StateManager<Data> {
/**
* Deselect any selected shapes.
*/
deselectAll = (): this => {
selectNone = (): this => {
this.setSelectedIds([])
this.addToSelectHistory(this.selectedIds)
return this

Wyświetl plik

@ -73,7 +73,7 @@ describe('Delete command', () => {
expect(Object.values(tlstate.page.bindings)[0]).toBe(undefined)
tlstate
.deselectAll()
.selectNone()
.createShapes({
id: 'arrow1',
type: TLDrawShapeType.Arrow,

Wyświetl plik

@ -26,7 +26,7 @@ describe('Group command', () => {
describe('when less than two shapes are selected', () => {
it('does nothing', () => {
tlstate.deselectAll()
tlstate.selectNone()
// @ts-ignore
const stackLength = tlstate.stack.length

Wyświetl plik

@ -47,12 +47,12 @@ describe('when running the command', () => {
.loadDocument(mockDocument)
.select('rect1')
.rotate()
.deselectAll()
.selectNone()
.undo()
expect(tlstate.selectedIds).toEqual(['rect1'])
tlstate.deselectAll().redo()
tlstate.selectNone().redo()
expect(tlstate.selectedIds).toEqual(['rect1'])
})

Wyświetl plik

@ -72,12 +72,12 @@ describe('when running the command', () => {
.loadDocument(mockDocument)
.select('rect1', 'rect2')
.stretch(StretchType.Horizontal)
.deselectAll()
.selectNone()
.undo()
expect(tlstate.selectedIds).toEqual(['rect1', 'rect2'])
tlstate.deselectAll().redo()
tlstate.selectNone().redo()
expect(tlstate.selectedIds).toEqual(['rect1', 'rect2'])
})

Wyświetl plik

@ -82,12 +82,12 @@ describe('when running the command', () => {
.loadDocument(mockDocument)
.select('rect1')
.style({ size: SizeStyle.Small })
.deselectAll()
.selectNone()
.undo()
expect(tlstate.selectedIds).toEqual(['rect1'])
tlstate.deselectAll().redo()
tlstate.selectNone().redo()
expect(tlstate.selectedIds).toEqual(['rect1'])
})

Wyświetl plik

@ -67,12 +67,12 @@ describe('when running the command', () => {
.loadDocument(mockDocument)
.select('rect1')
.toggleHidden()
.deselectAll()
.selectNone()
.undo()
expect(tlstate.selectedIds).toEqual(['rect1'])
tlstate.deselectAll().redo()
tlstate.selectNone().redo()
expect(tlstate.selectedIds).toEqual(['rect1'])
})

Wyświetl plik

@ -6,7 +6,7 @@ describe('Brush session', () => {
it('begins, updateSession', () => {
const tlstate = new TLDrawState()
.loadDocument(mockDocument)
.deselectAll()
.selectNone()
.startSession(SessionType.Brush, [-10, -10])
.updateSession([10, 10])
.completeSession()
@ -17,7 +17,7 @@ describe('Brush session', () => {
it('selects multiple shapes', () => {
const tlstate = new TLDrawState()
.loadDocument(mockDocument)
.deselectAll()
.selectNone()
.startSession(SessionType.Brush, [-10, -10])
.updateSession([110, 110])
.completeSession()
@ -27,7 +27,7 @@ describe('Brush session', () => {
it('does not de-select original shapes', () => {
const tlstate = new TLDrawState()
.loadDocument(mockDocument)
.deselectAll()
.selectNone()
.select('rect1')
.startSession(SessionType.Brush, [300, 300])
.updateSession([301, 301])
@ -38,9 +38,9 @@ describe('Brush session', () => {
// it('does not select hidden shapes', () => {
// const tlstate = new TLDrawState()
// .loadDocument(mockDocument)
// .deselectAll()
// .selectNone()
// .toggleHidden(['rect1'])
// .deselectAll()
// .selectNone()
// .startSession(SessionType.Brush, [-10, -10])
// .updateSession([10, 10])
// .completeSession()
@ -49,9 +49,9 @@ describe('Brush session', () => {
it('when command is held, require the entire shape to be selected', () => {
const tlstate = new TLDrawState()
.loadDocument(mockDocument)
.deselectAll()
.selectNone()
.loadDocument(mockDocument)
.deselectAll()
.selectNone()
.startSession(SessionType.Brush, [-10, -10])
.updateSession([10, 10], false, false, true)
.completeSession()

Wyświetl plik

@ -50,7 +50,7 @@ export class DrawUtil extends TLDrawShapeUtil<T, E> {
return style.dash === DashStyle.Draw
? getDrawStrokePathData(shape)
: getSolidStrokePathData(shape)
}, [points, style.size, style.dash, isComplete, false])
}, [points, style.size, style.dash, isComplete])
const styles = getShapeStyle(style, meta.isDarkMode)

Wyświetl plik

@ -56,7 +56,7 @@ describe('When double clicking link controls', () => {
.startSession(SessionType.Arrow, [200, 200], 'end')
.updateSession([250, 50])
.completeSession()
.deselectAll().document
.selectNone().document
it('moves all linked shapes when center is dragged', () => {
const tlstate = new TLDrawState().loadDocument(doc).select('rect2')

Wyświetl plik

@ -63,8 +63,8 @@ export class SelectTool extends BaseTool<Status> {
this.state.select(...this.state.selectedIds.filter((oid) => oid !== shape.parentId), id)
}
private deselectAll() {
this.state.deselectAll()
private selectNone() {
this.state.selectNone()
}
onEnter = () => {
@ -172,7 +172,7 @@ export class SelectTool extends BaseTool<Status> {
/* ----------------- Event Handlers ----------------- */
onCancel = () => {
this.deselectAll()
this.selectNone()
this.state.cancelSession()
this.setStatus(Status.Idle)
}
@ -379,7 +379,7 @@ export class SelectTool extends BaseTool<Status> {
if (info.target === 'bounds') {
// If we just clicked the selecting bounds's background,
// clear the selection
this.deselectAll()
this.selectNone()
} else if (this.state.isSelected(info.target)) {
// If we're holding shift...
if (info.shiftKey) {
@ -438,7 +438,7 @@ export class SelectTool extends BaseTool<Status> {
return
}
this.deselectAll()
this.selectNone()
}
this.setStatus(Status.PointingCanvas)
@ -494,7 +494,7 @@ export class SelectTool extends BaseTool<Status> {
if (info.metaKey) {
if (!info.shiftKey) {
this.deselectAll()
this.selectNone()
}
const point = this.state.getPagePoint(info.point)
@ -595,7 +595,7 @@ export class SelectTool extends BaseTool<Status> {
onPointBounds: TLBoundsEventHandler = (info) => {
if (info.metaKey) {
if (!info.shiftKey) {
this.deselectAll()
this.selectNone()
}
const point = this.state.getPagePoint(info.point)

1627
yarn.lock

Plik diff jest za duży Load Diff