tldraw/apps/vscode/extension/src/TldrawWebviewManager.ts

169 lines
5.3 KiB
TypeScript
Raw Normal View History

import * as vscode from 'vscode'
import { TDFile } from '@tldraw/tldraw'
import { MessageFromWebview, MessageFromExtension } from './types'
/**
* When a new editor is opened, an instance of this class will
* be created to configure the webview and handle its events.
*/
export class TldrawWebviewManager {
private disposables: vscode.Disposable[] = []
constructor(
private context: vscode.ExtensionContext,
private document: vscode.TextDocument,
private webviewPanel: vscode.WebviewPanel
) {
// Configure the webview. For now all we do is enable scripts and also
// provide the initial webview's html content.
Object.assign(webviewPanel.webview, {
options: { enableScripts: true },
html: this.getHtmlForWebview(),
})
// Listen for changes to the document when saved from VS Code.m
vscode.workspace.onDidSaveTextDocument(
this.handleDidSaveTextDocument,
undefined,
this.disposables
)
// Listen for changes made to the text document by VS Code or by some other app.
vscode.workspace.onDidChangeTextDocument(
this.handleDidChangeTextDocument,
undefined,
this.disposables
)
// Listen for messages sent from the extensions webview.
webviewPanel.webview.onDidReceiveMessage(
this.handleMessageFromWebview,
undefined,
this.disposables
)
// Send the initial document content to bootstrap the Tldraw/Tldraw component.
webviewPanel.webview.postMessage({
type: 'openedFile',
text: document.getText(),
} as MessageFromExtension)
// Clean up disposables when the editor is closed.
webviewPanel.onDidDispose(this.handleDidDispose)
}
private handleDidDispose = () => {
this.disposables.forEach(({ dispose }) => dispose())
}
private handleDidSaveTextDocument = () => {
const { webviewPanel, document } = this
if (!(webviewPanel && document)) return
webviewPanel.webview.postMessage({
type: 'fileSaved',
text: document.getText(),
} as MessageFromExtension)
}
private handleDidChangeTextDocument = (event: vscode.TextDocumentChangeEvent) => {
// TODO
// If we can figure out whether the change came from inside of the
// editor vs. from some other app, we can update the document to
// show that external change.
}
private handleMessageFromWebview = (e: MessageFromWebview) => {
const { document } = this
if (!document) return
switch (e.type) {
case 'editorUpdated': {
// The event will contain the new TDFile as JSON.
const nextFile = JSON.parse(e.text) as TDFile
if (document.getText()) {
try {
// Parse the contents of the current document.
const currentFile = JSON.parse(document.getText()) as TDFile
// Ensure that the current file's pageStates are preserved
// in the next file, unless the associated pages have been deleted.
Object.values(currentFile.document.pageStates).forEach((pageState) => {
if (nextFile.document.pages[pageState.id] !== undefined) {
nextFile.document.pageStates[pageState.id] = pageState
}
})
} catch (e) {
console.error('Could not parse the current file to JSON.')
}
}
// Create an edit that replaces the document's current text
// content (a serialized TDFile) with the next file.
const edit = new vscode.WorkspaceEdit()
edit.replace(
document.uri,
new vscode.Range(0, 0, document.lineCount, 0),
JSON.stringify(nextFile, null, 2)
)
vscode.workspace.applyEdit(edit)
break
}
}
}
private getHtmlForWebview = (): string => {
const { document, context, webviewPanel } = this
let documentContent: string
let cssUrl: string | vscode.Uri
let jsUrl: string | vscode.Uri
try {
JSON.parse(document.getText())
documentContent = document.getText()
} catch (error) {
// For now we're going to tread badly formed .tldr files as freshly created files.
// This will happen if say a user creates a new .tldr file using New File or if they
// have a bad auto-merge that messes up the json of an existing .tldr file
// We pass null as the initialDocument value if we can't parse as json.
documentContent = 'null'
}
if (process.env.NODE_ENV === 'production') {
cssUrl = webviewPanel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'editor/', 'index.css')
)
jsUrl = webviewPanel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'editor/', 'index.js')
)
} else {
const localhost = 'http://localhost:5420/'
cssUrl = `${localhost}index.css`
jsUrl = `${localhost}index.js`
}
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="${cssUrl}" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
2021-11-16 16:31:50 +00:00
<title>tldraw</title>
</head>
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script>var currentFile = ${documentContent};</script>
<script src="${jsUrl}"></script>
</body>
</html>
`
}
}