169 lines
5.4 KiB
TypeScript
169 lines
5.4 KiB
TypeScript
|
import * as vscode from 'vscode'
|
||
|
import { TLDrawFile } 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 TLDrawFile as JSON.
|
||
|
const nextFile = JSON.parse(e.text) as TLDrawFile
|
||
|
|
||
|
if (document.getText()) {
|
||
|
try {
|
||
|
// Parse the contents of the current document.
|
||
|
const currentFile = JSON.parse(document.getText()) as TLDrawFile
|
||
|
|
||
|
// 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 TLDrawFile) 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" />
|
||
|
<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>
|
||
|
`
|
||
|
}
|
||
|
}
|