Skip to content

Commit a165e46

Browse files
committed
feat: added a preview button for duc files
1 parent 08427da commit a165e46

3 files changed

Lines changed: 147 additions & 3 deletions

File tree

package.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,24 @@
7272
"command": "ducPreview.clearCustomSchema",
7373
"title": "Clear Custom Schema (Use Default)",
7474
"category": "Duc"
75+
},
76+
{
77+
"command": "ducPreview.openWebviewPreview",
78+
"title": "Open Preview",
79+
"category": "Duc",
80+
"icon": "$(open-preview)"
7581
}
7682
],
83+
"menus": {
84+
"editor/title": [
85+
{
86+
"command": "ducPreview.openWebviewPreview",
87+
"group": "navigation",
88+
"icon": "$(open-preview)",
89+
"when": "activeCustomEditorId == ducPreview.ducViewer"
90+
}
91+
]
92+
},
7793
"configuration": {
7894
"type": "object",
7995
"title": "Duc",
@@ -120,4 +136,4 @@
120136
"flatbuffers": "25.2.10",
121137
"monaco-editor": "0.52.2"
122138
}
123-
}
139+
}

src/ducPreviewManager.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import * as vscode from 'vscode';
2+
3+
export class DucPreviewManager {
4+
private static instance: DucPreviewManager;
5+
private readonly _previews = new Set<vscode.WebviewPanel>();
6+
7+
private constructor(private readonly _context: vscode.ExtensionContext) { }
8+
9+
public static getInstance(context: vscode.ExtensionContext): DucPreviewManager {
10+
if (!DucPreviewManager.instance) {
11+
DucPreviewManager.instance = new DucPreviewManager(context);
12+
}
13+
return DucPreviewManager.instance;
14+
}
15+
16+
public async openPreview(uri: vscode.Uri) {
17+
// Create or show the webview panel
18+
const fileName = uri.path.split('/').pop() || 'Preview';
19+
const panel = vscode.window.createWebviewPanel(
20+
'duc.preview', // viewType
21+
`Preview: ${fileName}`,
22+
vscode.ViewColumn.Beside,
23+
{
24+
enableScripts: true,
25+
retainContextWhenHidden: true,
26+
localResourceRoots: [vscode.Uri.joinPath(this._context.extensionUri, 'media')]
27+
}
28+
);
29+
30+
this._previews.add(panel);
31+
32+
// Cleanup when module is disposed
33+
panel.onDidDispose(() => {
34+
this._previews.delete(panel);
35+
});
36+
37+
// Set HTML content
38+
panel.webview.html = this._getHtmlForWebview();
39+
40+
// Handle messages from the webview
41+
panel.webview.onDidReceiveMessage(async (message) => {
42+
if (message.type === 'APP_READY') {
43+
await this._sendFileData(panel, uri);
44+
}
45+
});
46+
}
47+
48+
private _getHtmlForWebview(): string {
49+
const targetUrl = 'https://scopture.com/preview';
50+
51+
return `
52+
<!DOCTYPE html>
53+
<html lang="en" style="height: 100%">
54+
<head>
55+
<meta charset="UTF-8">
56+
<style>
57+
body, html { margin: 0; padding: 0; height: 100%; overflow: hidden; }
58+
iframe {
59+
width: 125%;
60+
height: 125%;
61+
border: none;
62+
transform: scale(0.8);
63+
transform-origin: top left;
64+
}
65+
</style>
66+
</head>
67+
<body>
68+
<iframe src="${targetUrl}" id="app-frame" sandbox="allow-scripts allow-same-origin allow-forms allow-popups"></iframe>
69+
70+
<script>
71+
const vscode = acquireVsCodeApi();
72+
const frame = document.getElementById('app-frame');
73+
74+
// Forward messages from VS Code Extension -> Iframe (React App)
75+
window.addEventListener('message', event => {
76+
// Ensure we are forwarding valid data to the iframe
77+
if (frame && frame.contentWindow) {
78+
frame.contentWindow.postMessage(event.data, '*');
79+
}
80+
});
81+
82+
// Forward messages from Iframe -> VS Code (e.g., "I'm ready")
83+
window.addEventListener('message', event => {
84+
// Check if origin matches to avoid processing our own messages if needed,
85+
// but for "APP_READY" usually checking data type is enough.
86+
if (event.data && event.data.type === 'APP_READY') {
87+
vscode.postMessage(event.data);
88+
}
89+
});
90+
</script>
91+
</body>
92+
</html>
93+
`;
94+
}
95+
96+
private async _sendFileData(panel: vscode.WebviewPanel, uri: vscode.Uri) {
97+
try {
98+
const fileName = uri.path.split('/').pop() || 'file.duc';
99+
const fileData = await vscode.workspace.fs.readFile(uri);
100+
const base64 = Buffer.from(fileData).toString('base64');
101+
102+
panel.webview.postMessage({
103+
type: 'FILE_DATA',
104+
name: fileName,
105+
data: base64
106+
});
107+
} catch (error) {
108+
vscode.window.showErrorMessage(`Failed to read file: ${(error as Error).message}`);
109+
}
110+
}
111+
}

src/extension.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import * as vscode from 'vscode';
22
import { DucViewerProvider } from './ducViewerEditor';
33
import { FlatcManager } from './flatcManager';
44
import { CustomSchemaManager } from './customSchemaManager';
5+
import { DucPreviewManager } from './ducPreviewManager';
56

67
export function activate(context: vscode.ExtensionContext) {
78
// Initialize managers
89
const flatcManager = FlatcManager.getInstance(context);
910
const customSchemaManager = CustomSchemaManager.getInstance(context);
10-
11+
1112
// Register our custom editor provider
1213
context.subscriptions.push(DucViewerProvider.register(context));
13-
14+
1415
// Register command to check for flatc
1516
context.subscriptions.push(
1617
vscode.commands.registerCommand('ducPreview.checkFlatc', async () => {
@@ -37,4 +38,20 @@ export function activate(context: vscode.ExtensionContext) {
3738
await customSchemaManager.clearCustomSchema();
3839
})
3940
);
41+
42+
// Register command to open webview preview
43+
const ducPreviewManager = DucPreviewManager.getInstance(context);
44+
context.subscriptions.push(
45+
vscode.commands.registerCommand('ducPreview.openWebviewPreview', async (uri: vscode.Uri) => {
46+
// If uri is undefined (e.g. invoked from command palette without context), we can't easily get the active custom editor in stable API
47+
// So we rely on the uri passed by the menu contribution
48+
const targetUri = uri;
49+
50+
if (targetUri) {
51+
await ducPreviewManager.openPreview(targetUri);
52+
} else {
53+
vscode.window.showErrorMessage('No DUC file selected to preview.');
54+
}
55+
})
56+
);
4057
}

0 commit comments

Comments
 (0)