transfer-out: transfer out
This commit is contained in:
parent
ec84f64e63
commit
29ed921c67
1056 changed files with 154507 additions and 0 deletions
81
apps/vscode/editor/CHANGELOG.md
Normal file
81
apps/vscode/editor/CHANGELOG.md
Normal file
|
@ -0,0 +1,81 @@
|
|||
# @tldraw/vscode-editor
|
||||
|
||||
## 1.13.1-alpha.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Release day!
|
||||
|
||||
## 1.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- - Adds missing Arabic translations for dialogs. @abedshamia
|
||||
- Updates core-example. @brydenfogelman
|
||||
- Updates Polish translations. @adan2013
|
||||
- Adds missing Aria-Labels. @KDSBrowne
|
||||
- Improves Japanese translation. @yashkumarbarot
|
||||
- Fixes height and width in app.viewport. @hiroshisuga
|
||||
- Improves labels on StlyeMenu @proke03
|
||||
- Adds missing tooltips to undo / redo buttons. @proke03
|
||||
|
||||
## 1.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- - Improve middle mouse panning
|
||||
- Fix bug with assets in VS Code plugin
|
||||
- Improve performance of draw-style shapes
|
||||
- Fix bug with creating assets
|
||||
- Fix bug with text align in labels when outputting images
|
||||
- Fix bug with middle mouse panning on Linux
|
||||
- Fix bug with zoom shortcuts on number pad
|
||||
- Fix bug with draw and erase direction when holding shift
|
||||
|
||||
## 1.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d919bd27: Bump dependencies, add international support.
|
||||
|
||||
## 1.11.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Bump dependencies, add international support.
|
||||
|
||||
## 1.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix tldraw assets for vscode extension.
|
||||
|
||||
## 1.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix build.
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Fix build error in extension.
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Bump underlying packages.
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c09d6a3a: Adds text field for page rename, undo buttons on all screen sizes, arrow behavior with alt key.
|
||||
|
||||
## 1.7.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix bug with missing parents / children.
|
190
apps/vscode/editor/LICENSE
Normal file
190
apps/vscode/editor/LICENSE
Normal file
|
@ -0,0 +1,190 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2023 tldraw GB Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
9
apps/vscode/editor/README.md
Normal file
9
apps/vscode/editor/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
<div style="text-align: center; transform: scale(.5);">
|
||||
<img src="https://github.com/tldraw/tldraw/raw/main/assets/card-repo.png"/>
|
||||
</div>
|
||||
|
||||
# @tldraw/vscode-editor
|
||||
|
||||
The app for the tldraw VS Code Extension.
|
||||
|
||||
See the README at `vscode` for more about this project.
|
61
apps/vscode/editor/package.json
Normal file
61
apps/vscode/editor/package.json
Normal file
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"name": "@tldraw/vscode-editor",
|
||||
"description": "An an editor for the tldraw vscode extension.",
|
||||
"version": "2.0.0-alpha.0",
|
||||
"private": true,
|
||||
"packageManager": "yarn@3.5.0",
|
||||
"author": {
|
||||
"name": "tldraw GB Ltd.",
|
||||
"email": "hello@tldraw.com"
|
||||
},
|
||||
"homepage": "https://tldraw.dev",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tldraw/tldraw"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/tldraw/tldraw/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"tldraw",
|
||||
"drawing",
|
||||
"app",
|
||||
"development",
|
||||
"whiteboard",
|
||||
"canvas",
|
||||
"infinite"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "yarn run -T tsx scripts/build.ts",
|
||||
"build:vscode-editor": "yarn run -T tsx scripts/build.ts",
|
||||
"dev:vscode": "yarn run -T tsx scripts/dev.ts",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
||||
"lint": "yarn run -T tsx ../../../scripts/lint.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tldraw/assets": "workspace:*",
|
||||
"@tldraw/editor": "workspace:*",
|
||||
"@tldraw/file-format": "workspace:*",
|
||||
"@tldraw/tldraw": "workspace:*",
|
||||
"@tldraw/tlsync-client": "workspace:*",
|
||||
"@tldraw/ui": "workspace:*",
|
||||
"@tldraw/utils": "workspace:*",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/node": "^17.0.14",
|
||||
"@types/react": "^18.0.24",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"concurrently": "7.0.0",
|
||||
"create-serve": "1.0.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"esbuild": "^0.16.7",
|
||||
"fs-extra": "^11.1.0",
|
||||
"lazyrepo": "0.0.0-alpha.20",
|
||||
"nanoid": "^4.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
|
||||
}
|
46
apps/vscode/editor/public/index.css
Normal file
46
apps/vscode/editor/public/index.css
Normal file
|
@ -0,0 +1,46 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap');
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
overscroll-behavior: none;
|
||||
touch-action: none;
|
||||
overflow: hidden;
|
||||
min-height: 100vh;
|
||||
/* mobile viewport bug fix */
|
||||
min-height: -webkit-fill-available;
|
||||
}
|
||||
|
||||
#root {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tldraw--editor {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: var(--color-background);
|
||||
}
|
58
apps/vscode/editor/scripts/build.ts
Normal file
58
apps/vscode/editor/scripts/build.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import esbuild from 'esbuild'
|
||||
import fs from 'fs'
|
||||
import fse from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { logEnv } from '../../vscode-script-utils/cli'
|
||||
import { exists, getDirname } from '../../vscode-script-utils/path'
|
||||
|
||||
const rootDir = getDirname(import.meta.url, '../')
|
||||
const log = logEnv('editor')
|
||||
|
||||
export async function build() {
|
||||
try {
|
||||
const targetFolder = `${rootDir}dist/`
|
||||
if (await exists(targetFolder)) {
|
||||
log({ cmd: 'remove', args: { target: targetFolder } })
|
||||
await fs.promises.rm(targetFolder, { recursive: true })
|
||||
}
|
||||
|
||||
await fs.promises.mkdir(targetFolder)
|
||||
|
||||
const topSource = `${rootDir}public`
|
||||
const files = await fs.promises.readdir(topSource)
|
||||
for (const file of files) {
|
||||
const dest = targetFolder + path.basename(file)
|
||||
const source = path.join(topSource, file)
|
||||
log({ cmd: 'copy', args: { source, dest } })
|
||||
await fse.copy(source, dest)
|
||||
}
|
||||
const entryPoints = [`${rootDir}src/index.tsx`]
|
||||
|
||||
log({ cmd: 'esbuild', args: { entryPoints } })
|
||||
esbuild.buildSync({
|
||||
entryPoints,
|
||||
outfile: `${rootDir}/dist/index.js`,
|
||||
minify: false,
|
||||
bundle: true,
|
||||
target: 'es6',
|
||||
jsxFactory: 'React.createElement',
|
||||
jsxFragment: 'React.Fragment',
|
||||
loader: {
|
||||
'.woff2': 'dataurl',
|
||||
'.woff': 'dataurl',
|
||||
'.svg': 'file',
|
||||
'.png': 'file',
|
||||
'.json': 'file',
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
},
|
||||
})
|
||||
log({ cmd: 'esbuild:success', args: {} })
|
||||
} catch (error) {
|
||||
log({ cmd: 'esbuild:error', args: { error } })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
build()
|
71
apps/vscode/editor/scripts/dev.ts
Normal file
71
apps/vscode/editor/scripts/dev.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import dotenv from 'dotenv'
|
||||
import esbuild from 'esbuild'
|
||||
import fs from 'fs'
|
||||
import fse from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { logEnv } from '../../vscode-script-utils/cli'
|
||||
import { copyEditor } from '../../vscode-script-utils/helpers'
|
||||
import { exists, getDirname } from '../../vscode-script-utils/path'
|
||||
|
||||
dotenv.config()
|
||||
const rootDir = getDirname(import.meta.url, '../')
|
||||
const log = logEnv('editor')
|
||||
|
||||
export async function run() {
|
||||
try {
|
||||
const targetFolder = `${rootDir}dist/`
|
||||
if (await exists(targetFolder)) {
|
||||
log({ cmd: 'remove', args: { target: targetFolder } })
|
||||
await fs.promises.rm(targetFolder, { recursive: true })
|
||||
}
|
||||
|
||||
await fs.promises.mkdir(targetFolder)
|
||||
|
||||
const topSource = `${rootDir}public`
|
||||
const files = await fs.promises.readdir(topSource)
|
||||
for (const file of files) {
|
||||
const dest = targetFolder + path.basename(file)
|
||||
const source = path.join(topSource, file)
|
||||
log({ cmd: 'copy', args: { source, dest } })
|
||||
await fse.copy(source, dest)
|
||||
}
|
||||
const entryPoints = [`${rootDir}src/index.tsx`]
|
||||
|
||||
log({ cmd: 'esbuild', args: { entryPoints } })
|
||||
esbuild.build({
|
||||
entryPoints,
|
||||
outfile: `${rootDir}/dist/index.js`,
|
||||
minify: false,
|
||||
bundle: true,
|
||||
incremental: true,
|
||||
target: 'es6',
|
||||
jsxFactory: 'React.createElement',
|
||||
jsxFragment: 'React.Fragment',
|
||||
loader: {
|
||||
'.woff2': 'dataurl',
|
||||
'.woff': 'dataurl',
|
||||
'.svg': 'file',
|
||||
'.png': 'file',
|
||||
'.json': 'file',
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"development"',
|
||||
},
|
||||
watch: {
|
||||
onRebuild(err) {
|
||||
if (err) {
|
||||
log({ cmd: 'esbuild:error', args: { err } })
|
||||
} else {
|
||||
copyEditor({ log })
|
||||
log({ cmd: 'esbuild:success', args: {} })
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
log({ cmd: 'esbuild:error', args: { error } })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
77
apps/vscode/editor/src/ChangeResponder.tsx
Normal file
77
apps/vscode/editor/src/ChangeResponder.tsx
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { SyncedStore, TLInstanceId, TLUserId, useApp } from '@tldraw/editor'
|
||||
import { parseAndLoadDocument, serializeTldrawJson } from '@tldraw/file-format'
|
||||
import { useDefaultHelpers } from '@tldraw/ui'
|
||||
import { debounce } from '@tldraw/utils'
|
||||
import React from 'react'
|
||||
import '../public/index.css'
|
||||
import { vscode } from './utils/vscode'
|
||||
|
||||
// @ts-ignore
|
||||
import type { VscodeMessage } from '../../messages'
|
||||
|
||||
export const ChangeResponder = ({
|
||||
syncedStore,
|
||||
userId,
|
||||
instanceId,
|
||||
}: {
|
||||
syncedStore: SyncedStore
|
||||
userId: TLUserId
|
||||
instanceId: TLInstanceId
|
||||
}) => {
|
||||
const app = useApp()
|
||||
const { addToast, clearToasts, msg } = useDefaultHelpers()
|
||||
|
||||
React.useEffect(() => {
|
||||
// When a message is received from the VS Code extension, handle it
|
||||
function handleMessage({ data: message }: MessageEvent<VscodeMessage>) {
|
||||
switch (message.type) {
|
||||
// case 'vscode:undo': {
|
||||
// app.undo()
|
||||
// break
|
||||
// }
|
||||
// case 'vscode:redo': {
|
||||
// app.redo()
|
||||
// break
|
||||
// }
|
||||
case 'vscode:revert': {
|
||||
parseAndLoadDocument(app, message.data.fileContents, msg, addToast)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMessage)
|
||||
|
||||
return () => {
|
||||
clearToasts()
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
}, [app, userId, instanceId, msg, addToast, clearToasts])
|
||||
|
||||
React.useEffect(() => {
|
||||
// When the history changes, send the new file contents to VSCode
|
||||
const handleChange = debounce(async () => {
|
||||
if (syncedStore.store) {
|
||||
vscode.postMessage({
|
||||
type: 'vscode:editor-updated',
|
||||
data: {
|
||||
fileContents: await serializeTldrawJson(syncedStore.store),
|
||||
},
|
||||
})
|
||||
}
|
||||
}, 250)
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'vscode:editor-loaded',
|
||||
})
|
||||
|
||||
app.on('change-history', handleChange)
|
||||
|
||||
return () => {
|
||||
handleChange()
|
||||
app.off('change-history', handleChange)
|
||||
}
|
||||
}, [app, syncedStore, userId, instanceId])
|
||||
|
||||
return null
|
||||
}
|
60
apps/vscode/editor/src/FileOpen.tsx
Normal file
60
apps/vscode/editor/src/FileOpen.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { TLInstanceId, TLUserId, useApp } from '@tldraw/editor'
|
||||
import { parseAndLoadDocument } from '@tldraw/file-format'
|
||||
import { useDefaultHelpers } from '@tldraw/ui'
|
||||
import React from 'react'
|
||||
import { vscode } from './utils/vscode'
|
||||
|
||||
export function FileOpen({
|
||||
userId,
|
||||
fileContents,
|
||||
instanceId,
|
||||
forceDarkMode,
|
||||
}: {
|
||||
instanceId: TLInstanceId
|
||||
userId: TLUserId
|
||||
fileContents: string
|
||||
forceDarkMode: boolean
|
||||
}) {
|
||||
const app = useApp()
|
||||
const { msg, addToast, clearToasts } = useDefaultHelpers()
|
||||
const [isFileLoaded, setIsFileLoaded] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isFileLoaded) return
|
||||
function onV1FileLoad() {
|
||||
vscode.postMessage({
|
||||
type: 'vscode:v1-file-opened',
|
||||
data: {
|
||||
description: msg('vscode.file-open.desc'),
|
||||
backup: msg('vscode.file-open.backup'),
|
||||
backupSaved: msg('vscode.file-open.backup-saved'),
|
||||
backupFailed: msg('vscode.file-open.backup-failed'),
|
||||
dontAskAgain: msg('vscode.file-open.dont-show-again'),
|
||||
open: msg('vscode.file-open.open'),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function loadFile() {
|
||||
await parseAndLoadDocument(app, fileContents, msg, addToast, onV1FileLoad, forceDarkMode)
|
||||
}
|
||||
|
||||
loadFile()
|
||||
setIsFileLoaded(true)
|
||||
return () => {
|
||||
clearToasts()
|
||||
}
|
||||
}, [
|
||||
fileContents,
|
||||
app,
|
||||
userId,
|
||||
instanceId,
|
||||
addToast,
|
||||
msg,
|
||||
clearToasts,
|
||||
forceDarkMode,
|
||||
isFileLoaded,
|
||||
])
|
||||
|
||||
return null
|
||||
}
|
17
apps/vscode/editor/src/FullPageMessage.tsx
Normal file
17
apps/vscode/editor/src/FullPageMessage.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
export function FullPageMessage({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'var(--vscode-editor-background)',
|
||||
color: 'var(--vscode-editor-foreground)',
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
WebkitTransform: 'translate(-50%, -50%)',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
164
apps/vscode/editor/src/app.tsx
Normal file
164
apps/vscode/editor/src/app.tsx
Normal file
|
@ -0,0 +1,164 @@
|
|||
import { getBundlerAssetUrls } from '@tldraw/assets'
|
||||
import {
|
||||
App,
|
||||
Canvas,
|
||||
ErrorBoundary,
|
||||
setRuntimeOverrides,
|
||||
TldrawEditor,
|
||||
TLUserId,
|
||||
} from '@tldraw/editor'
|
||||
import { linksUiOverrides } from './utils/links'
|
||||
// eslint-disable-next-line import/no-internal-modules
|
||||
import '@tldraw/editor/editor.css'
|
||||
import { TAB_ID, useLocalSyncClient } from '@tldraw/tlsync-client'
|
||||
import { ContextMenu, MenuSchema, TldrawUi } from '@tldraw/ui'
|
||||
// eslint-disable-next-line import/no-internal-modules
|
||||
import '@tldraw/ui/ui.css'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { VscodeMessage } from '../../messages'
|
||||
import '../public/index.css'
|
||||
import { ChangeResponder } from './ChangeResponder'
|
||||
import { FileOpen } from './FileOpen'
|
||||
import { FullPageMessage } from './FullPageMessage'
|
||||
import { onCreateBookmarkFromUrl } from './utils/bookmarks'
|
||||
import { vscode } from './utils/vscode'
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
setRuntimeOverrides({
|
||||
openWindow: (url, target) => {
|
||||
vscode.postMessage({
|
||||
type: 'vscode:open-window',
|
||||
data: {
|
||||
url,
|
||||
target,
|
||||
},
|
||||
})
|
||||
},
|
||||
refreshPage: () => {
|
||||
vscode.postMessage({
|
||||
type: 'vscode:refresh-page',
|
||||
})
|
||||
},
|
||||
hardReset: async () => {
|
||||
await (window as any).__tldraw__hardReset?.()
|
||||
vscode.postMessage({
|
||||
type: 'vscode:hard-reset',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const handleError = (error: any) => {
|
||||
console.error(error.message)
|
||||
}
|
||||
|
||||
export function WrappedTldrawEditor() {
|
||||
return (
|
||||
<div className="tldraw--editor">
|
||||
<ErrorBoundary
|
||||
fallback={() => <FullPageMessage>Fallback</FullPageMessage>}
|
||||
onError={handleError}
|
||||
>
|
||||
<TldrawWrapper />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const menuOverrides = {
|
||||
menu: (_app: App, schema: MenuSchema, _helpers: any) => {
|
||||
schema.forEach((item) => {
|
||||
if (item.id === 'menu' && item.type === 'group') {
|
||||
item.children = item.children.filter((menuItem) => {
|
||||
if (menuItem.id === 'file' && menuItem.type === 'submenu') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return schema
|
||||
},
|
||||
}
|
||||
|
||||
export const TldrawWrapper = () => {
|
||||
const [tldrawInnerProps, setTldrawInnerProps] = useState<TLDrawInnerProps | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
function handleMessage({ data: message }: MessageEvent<VscodeMessage>) {
|
||||
switch (message.type) {
|
||||
case 'vscode:opened-file': {
|
||||
setTldrawInnerProps({
|
||||
assetSrc: message.data.assetSrc,
|
||||
fileContents: message.data.fileContents,
|
||||
uri: message.data.uri,
|
||||
userId: message.data.userId as TLUserId,
|
||||
isDarkMode: message.data.isDarkMode,
|
||||
})
|
||||
// We only want to listen for this message once
|
||||
window.removeEventListener('message', handleMessage)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMessage)
|
||||
|
||||
vscode.postMessage({ type: 'vscode:ready-to-receive-file' })
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
}, [setTldrawInnerProps])
|
||||
|
||||
return tldrawInnerProps === null ? (
|
||||
<FullPageMessage>Loading</FullPageMessage>
|
||||
) : (
|
||||
<TldrawInner {...tldrawInnerProps} />
|
||||
)
|
||||
}
|
||||
|
||||
export type TLDrawInnerProps = {
|
||||
assetSrc: string
|
||||
fileContents: string
|
||||
uri: string
|
||||
userId: TLUserId
|
||||
isDarkMode: boolean
|
||||
}
|
||||
|
||||
function TldrawInner({ uri, assetSrc, userId, isDarkMode, fileContents }: TLDrawInnerProps) {
|
||||
const instanceId = TAB_ID
|
||||
const syncedStore = useLocalSyncClient({
|
||||
universalPersistenceKey: uri,
|
||||
instanceId,
|
||||
userId,
|
||||
})
|
||||
|
||||
const assetUrls = useMemo(() => getBundlerAssetUrls({ baseUrl: assetSrc }), [assetSrc])
|
||||
|
||||
return (
|
||||
<TldrawEditor
|
||||
assetUrls={assetUrls}
|
||||
instanceId={TAB_ID}
|
||||
userId={userId}
|
||||
store={syncedStore}
|
||||
onCreateBookmarkFromUrl={onCreateBookmarkFromUrl}
|
||||
autoFocus
|
||||
>
|
||||
{/* <DarkModeHandler themeKind={themeKind} /> */}
|
||||
<TldrawUi assetUrls={assetUrls} overrides={[menuOverrides, linksUiOverrides]}>
|
||||
<FileOpen
|
||||
instanceId={instanceId}
|
||||
userId={userId}
|
||||
fileContents={fileContents}
|
||||
forceDarkMode={isDarkMode}
|
||||
/>
|
||||
<ChangeResponder syncedStore={syncedStore} userId={userId} instanceId={instanceId} />
|
||||
<ContextMenu>
|
||||
<Canvas />
|
||||
</ContextMenu>
|
||||
</TldrawUi>
|
||||
</TldrawEditor>
|
||||
)
|
||||
}
|
9
apps/vscode/editor/src/index.tsx
Normal file
9
apps/vscode/editor/src/index.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import * as React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { WrappedTldrawEditor } from './app'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<WrappedTldrawEditor />
|
||||
</React.StrictMode>
|
||||
)
|
47
apps/vscode/editor/src/utils/bookmarks.ts
Normal file
47
apps/vscode/editor/src/utils/bookmarks.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { rpc } from './rpc'
|
||||
|
||||
async function onCreateBookmarkFromUrlFallback(
|
||||
url: string
|
||||
): Promise<{ image: string; title: string; description: string }> {
|
||||
const meta = {
|
||||
image: '',
|
||||
title: '',
|
||||
description: '',
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch(url, { method: 'GET', mode: 'no-cors' })
|
||||
const html = await resp.text()
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html')
|
||||
|
||||
meta.image = doc.head
|
||||
.querySelector('meta[property="og:image"]')
|
||||
?.getAttribute('content') as string
|
||||
meta.title = doc.head
|
||||
.querySelector('meta[property="og:title"]')
|
||||
?.getAttribute('content') as string
|
||||
meta.description = doc.head
|
||||
.querySelector('meta[property="og:description"]')
|
||||
?.getAttribute('content') as string
|
||||
|
||||
return meta
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
export async function onCreateBookmarkFromUrl(url: string) {
|
||||
try {
|
||||
const data = await rpc('vscode:bookmark', { url })
|
||||
|
||||
return {
|
||||
title: data.title || '',
|
||||
description: data.description || '',
|
||||
image: data.image || '',
|
||||
}
|
||||
} catch (error) {
|
||||
return onCreateBookmarkFromUrlFallback(url)
|
||||
}
|
||||
}
|
57
apps/vscode/editor/src/utils/links.ts
Normal file
57
apps/vscode/editor/src/utils/links.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { menuGroup, menuItem, TldrawUiOverrides } from '@tldraw/ui'
|
||||
import { openUrl } from './openUrl'
|
||||
|
||||
export const GITHUB_URL = 'https://github.com/tldraw/tldraw'
|
||||
|
||||
const linksMenuGroup = menuGroup(
|
||||
'links',
|
||||
menuItem({
|
||||
id: 'github',
|
||||
label: 'help-menu.github',
|
||||
readonlyOk: true,
|
||||
icon: 'github',
|
||||
onSelect() {
|
||||
openUrl(GITHUB_URL)
|
||||
},
|
||||
}),
|
||||
menuItem({
|
||||
id: 'twitter',
|
||||
label: 'help-menu.twitter',
|
||||
icon: 'twitter',
|
||||
readonlyOk: true,
|
||||
onSelect() {
|
||||
openUrl('https://twitter.com/tldraw')
|
||||
},
|
||||
}),
|
||||
menuItem({
|
||||
id: 'discord',
|
||||
label: 'help-menu.discord',
|
||||
icon: 'discord',
|
||||
readonlyOk: true,
|
||||
onSelect() {
|
||||
openUrl('https://discord.gg/SBBEVCA4PG')
|
||||
},
|
||||
}),
|
||||
menuItem({
|
||||
id: 'about',
|
||||
label: 'help-menu.about',
|
||||
icon: 'external-link',
|
||||
readonlyOk: true,
|
||||
onSelect() {
|
||||
openUrl('https://www.tldraw.dev')
|
||||
},
|
||||
})
|
||||
)!
|
||||
|
||||
export const linksUiOverrides: TldrawUiOverrides = {
|
||||
helpMenu(app, schema) {
|
||||
schema.push(linksMenuGroup)
|
||||
return schema
|
||||
},
|
||||
menu(app, schema, { isMobile }) {
|
||||
if (isMobile) {
|
||||
schema.push(linksMenuGroup)
|
||||
}
|
||||
return schema
|
||||
},
|
||||
}
|
8
apps/vscode/editor/src/utils/openUrl.ts
Normal file
8
apps/vscode/editor/src/utils/openUrl.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { vscode } from './vscode'
|
||||
|
||||
export function openUrl(url: string) {
|
||||
vscode.postMessage({
|
||||
type: 'vscode:open-window',
|
||||
data: { url, target: '_blank' },
|
||||
})
|
||||
}
|
62
apps/vscode/editor/src/utils/rpc.ts
Normal file
62
apps/vscode/editor/src/utils/rpc.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { nanoid } from 'nanoid'
|
||||
import type { VscodeMessagePairs } from '../../../messages'
|
||||
import { vscode } from './vscode'
|
||||
|
||||
type SimpleRpcOpts = {
|
||||
timeout: number
|
||||
}
|
||||
class SimpleRpcError extends Error {
|
||||
id: string
|
||||
data: any
|
||||
constructor(id: keyof VscodeMessagePairs, data: any) {
|
||||
super(`Failed ${id}`)
|
||||
this.id = id
|
||||
this.data = data
|
||||
}
|
||||
}
|
||||
|
||||
export function rpc(
|
||||
id: keyof VscodeMessagePairs,
|
||||
data: Omit<VscodeMessagePairs[typeof id]['request'], 'uuid'>['data'],
|
||||
opts: SimpleRpcOpts = { timeout: 5 * 1000 }
|
||||
) {
|
||||
const { timeout } = opts
|
||||
type RequestType = VscodeMessagePairs[typeof id]['request']
|
||||
type ResponseType = VscodeMessagePairs[typeof id]['response']
|
||||
type ErrorType = VscodeMessagePairs[typeof id]['error']
|
||||
|
||||
const type = (id + '/request') as RequestType['type']
|
||||
const uuid = nanoid()
|
||||
return new Promise<ResponseType['data']>((resolve, reject) => {
|
||||
const inMessage = {
|
||||
uuid,
|
||||
type,
|
||||
data,
|
||||
}
|
||||
vscode.postMessage(inMessage)
|
||||
|
||||
const handler = ({ data: response }: MessageEvent<ResponseType | ErrorType>) => {
|
||||
if (uuid === response.uuid) {
|
||||
return
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
window.removeEventListener('message', handler)
|
||||
}
|
||||
|
||||
if (response.type === `${id}/response`) {
|
||||
cleanup()
|
||||
resolve(response.data as ResponseType['data'])
|
||||
}
|
||||
if (response.type === `${id}/error`) {
|
||||
cleanup()
|
||||
reject(new SimpleRpcError(id, response.data as ErrorType['data']))
|
||||
}
|
||||
setTimeout(() => {
|
||||
cleanup()
|
||||
reject(new SimpleRpcError(id, { timeout: true }))
|
||||
}, timeout)
|
||||
}
|
||||
window.addEventListener('message', handler)
|
||||
})
|
||||
}
|
9
apps/vscode/editor/src/utils/vscode.ts
Normal file
9
apps/vscode/editor/src/utils/vscode.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
// @ts-ignore
|
||||
import type { VscodeMessage } from '../../../messages'
|
||||
|
||||
// Will be placed in global scope by extension
|
||||
declare function acquireVsCodeApi(): {
|
||||
postMessage(options: VscodeMessage): void
|
||||
}
|
||||
|
||||
export const vscode = acquireVsCodeApi()
|
35
apps/vscode/editor/tsconfig.json
Normal file
35
apps/vscode/editor/tsconfig.json
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"removeComments": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"baseUrl": ".",
|
||||
"composite": true,
|
||||
"importHelpers": false,
|
||||
"skipDefaultLibCheck": true,
|
||||
"experimentalDecorators": true,
|
||||
"rootDir": ".."
|
||||
},
|
||||
"include": ["src", "../messages", "scripts", "../vscode-script-utils"],
|
||||
"references": [
|
||||
{ "path": "../../../packages/file-format" },
|
||||
{ "path": "../../../packages/ui" },
|
||||
{ "path": "../../../packages/editor" },
|
||||
{ "path": "../../../packages/tlsync-client" },
|
||||
{ "path": "../../../packages/utils" }
|
||||
]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue