prac2025vscode/src/TkinterEditorProvider.ts
2025-12-22 14:39:20 +03:00

137 lines
5.5 KiB
TypeScript

import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { generateTkinterCode } from './generator';
import { parsePythonToGrapes } from './pythonParser';
export class TkinterEditorProvider implements vscode.CustomTextEditorProvider {
public static readonly viewType = 'tkinter-designer.editor';
public static register(context: vscode.ExtensionContext): vscode.Disposable {
const provider = new TkinterEditorProvider(context);
return vscode.window.registerCustomEditorProvider(TkinterEditorProvider.viewType, provider);
}
constructor(
private readonly context: vscode.ExtensionContext
) { }
private isSaving = false;
public async resolveCustomTextEditor(
document: vscode.TextDocument,
webviewPanel: vscode.WebviewPanel,
_token: vscode.CancellationToken
): Promise<void> {
webviewPanel.webview.options = {
enableScripts: true,
localResourceRoots: [vscode.Uri.file(path.join(this.context.extensionPath, 'media'))]
};
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
webviewPanel.webview.onDidReceiveMessage(e => {
switch (e.type) {
case 'request-load':
this.handleRequestLoad(document, webviewPanel);
break;
case 'update-code':
this.isSaving = true;
this.handleSave(document, e.payload);
setTimeout(() => { this.isSaving = false; }, 500);
break;
case 'request-import':
this.handleImport(webviewPanel);
break;
}
});
const folderPath = path.dirname(document.uri.fsPath);
const fileNameBase = path.basename(document.uri.fsPath, path.extname(document.uri.fsPath));
const pyFilePath = path.join(folderPath, `${fileNameBase}.py`);
const watcher = vscode.workspace.createFileSystemWatcher(pyFilePath);
watcher.onDidChange(() => {
//
});
webviewPanel.onDidDispose(() => watcher.dispose());
}
private handleRequestLoad(document: vscode.TextDocument, panel: vscode.WebviewPanel) {
const text = document.getText();
let payload = {};
try {
if (text.trim().length > 0) payload = JSON.parse(text);
} catch (e) { console.error(e); }
panel.webview.postMessage({ type: 'load-data', payload: payload });
}
private async handleSave(document: vscode.TextDocument, jsonPayload: any) {
const folderPath = path.dirname(document.uri.fsPath);
const fileNameBase = path.basename(document.uri.fsPath, path.extname(document.uri.fsPath));
const pyFilePath = path.join(folderPath, `${fileNameBase}.py`);
const pythonCode = generateTkinterCode(jsonPayload);
try {
fs.writeFileSync(pyFilePath, pythonCode);
} catch (e) {
console.error(`Python Save error: ${e}`);
}
// СОХРАНЕНИЕ JSON
const edit = new vscode.WorkspaceEdit();
const fullRange = new vscode.Range(
document.positionAt(0),
document.positionAt(document.getText().length)
);
edit.replace(document.uri, fullRange, JSON.stringify(jsonPayload, null, 2));
await vscode.workspace.applyEdit(edit);
await document.save();
}
private async handleImport(panel: vscode.WebviewPanel) {
const uris = await vscode.window.showOpenDialog({
canSelectMany: false,
openLabel: 'Import Python',
filters: { 'Python Files': ['py'] }
});
if (uris && uris[0]) {
const content = fs.readFileSync(uris[0].fsPath, 'utf-8');
const data = parsePythonToGrapes(content);
panel.webview.postMessage({ type: 'import-data', payload: data });
}
}
private getHtmlForWebview(webview: vscode.Webview): string {
const mediaPath = path.join(this.context.extensionPath, 'media');
const scriptUri = webview.asWebviewUri(vscode.Uri.file(path.join(mediaPath, 'grapes.min.js')));
const styleUri = webview.asWebviewUri(vscode.Uri.file(path.join(mediaPath, 'grapes.min.css')));
const mainScriptUri = webview.asWebviewUri(vscode.Uri.file(path.join(mediaPath, 'main.js')));
const fontUrl = "https://unpkg.com/grapesjs/dist/fonts/grapes.woff";
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource} 'unsafe-inline' https:; script-src ${webview.cspSource} 'unsafe-inline' 'unsafe-eval'; font-src ${webview.cspSource} https: data:;">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleUri}" rel="stylesheet">
<style>
html, body { height: 100%; margin: 0; overflow: hidden; }
#editor { height: 100%; }
@font-face { font-family: 'GrapesJS'; src: url('${fontUrl}') format('woff'); font-weight: normal; font-style: normal; }
</style>
<title>Tkinter Designer</title>
</head>
<body>
<div id="editor"></div>
<script src="${scriptUri}"></script>
<script src="${mainScriptUri}"></script>
</body>
</html>`;
}
}