0.2.1
This commit is contained in:
@@ -3,6 +3,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const BETA_TOOLS_ENABLED_KEY = 'hplToolbox.betaTools.enabled';
|
||||
const TOOL_ORDER_KEY = 'hplToolbox.toolOrder.v1';
|
||||
|
||||
interface ToolDefinition {
|
||||
id?: string;
|
||||
@@ -54,7 +55,8 @@ export class LauncherViewProvider implements vscode.WebviewViewProvider {
|
||||
const version = this.context.extension.packageJSON.version as string;
|
||||
const tools = loadToolDefinitions(this.context.extensionPath);
|
||||
const betaToolsEnabled = this.context.globalState.get<boolean>(BETA_TOOLS_ENABLED_KEY, false);
|
||||
view.webview.html = getHtml(version, betaToolsEnabled, tools);
|
||||
const toolOrder = this.context.globalState.get<string[]>(TOOL_ORDER_KEY, []);
|
||||
view.webview.html = getHtml(version, betaToolsEnabled, tools, toolOrder);
|
||||
view.webview.onDidReceiveMessage(async (msg) => {
|
||||
if (msg?.type === 'open' && typeof msg.command === 'string') {
|
||||
vscode.commands.executeCommand(msg.command);
|
||||
@@ -62,7 +64,9 @@ export class LauncherViewProvider implements vscode.WebviewViewProvider {
|
||||
vscode.commands.executeCommand('hplToolbox.openChangelog');
|
||||
} else if (msg?.type === 'setBetaToolsEnabled' && typeof msg.enabled === 'boolean') {
|
||||
await this.context.globalState.update(BETA_TOOLS_ENABLED_KEY, msg.enabled);
|
||||
view.webview.html = getHtml(version, msg.enabled, tools);
|
||||
view.webview.html = getHtml(version, msg.enabled, tools, this.context.globalState.get<string[]>(TOOL_ORDER_KEY, []));
|
||||
} else if (msg?.type === 'setToolOrder' && Array.isArray(msg.order)) {
|
||||
await this.context.globalState.update(TOOL_ORDER_KEY, msg.order.filter((id: unknown) => typeof id === 'string'));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -88,8 +92,8 @@ function loadToolDefinitions(extensionPath: string): ToolDefinition[] {
|
||||
}
|
||||
}
|
||||
|
||||
function getHtml(version: string, betaToolsEnabled: boolean, tools: ToolDefinition[]): string {
|
||||
const visibleTools = sortToolsForDisplay(tools.filter(tool => betaToolsEnabled || !tool.beta));
|
||||
function getHtml(version: string, betaToolsEnabled: boolean, tools: ToolDefinition[], toolOrder: string[]): string {
|
||||
const visibleTools = sortToolsForDisplay(tools.filter(tool => betaToolsEnabled || !tool.beta), toolOrder);
|
||||
const toolButtons = visibleTools.map(renderToolButton).join('\n');
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -127,6 +131,9 @@ function getHtml(version: string, betaToolsEnabled: boolean, tools: ToolDefiniti
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
.tool-btn.dragging {
|
||||
opacity: 0.55;
|
||||
}
|
||||
.tool-btn:hover {
|
||||
background: var(--vscode-list-hoverBackground, var(--vscode-button-secondaryHoverBackground));
|
||||
border-color: var(--vscode-focusBorder, var(--vscode-panel-border));
|
||||
@@ -217,9 +224,44 @@ ${toolButtons}
|
||||
const vscode = acquireVsCodeApi();
|
||||
document.querySelectorAll('.tool-btn').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
if (btn.dataset.dragging === 'true') {
|
||||
btn.dataset.dragging = 'false';
|
||||
return;
|
||||
}
|
||||
vscode.postMessage({ type: 'open', command: btn.dataset.cmd });
|
||||
});
|
||||
});
|
||||
const toolsEl = document.querySelector('.tools');
|
||||
let dragged = null;
|
||||
toolsEl.querySelectorAll('.tool-btn').forEach((btn) => {
|
||||
btn.draggable = true;
|
||||
btn.addEventListener('dragstart', (event) => {
|
||||
dragged = btn;
|
||||
btn.classList.add('dragging');
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData('text/plain', btn.dataset.id || '');
|
||||
});
|
||||
btn.addEventListener('dragend', () => {
|
||||
btn.classList.remove('dragging');
|
||||
btn.dataset.dragging = 'true';
|
||||
dragged = null;
|
||||
persistToolOrder();
|
||||
setTimeout(() => { btn.dataset.dragging = 'false'; }, 0);
|
||||
});
|
||||
btn.addEventListener('dragover', (event) => {
|
||||
event.preventDefault();
|
||||
if (!dragged || dragged === btn) return;
|
||||
const rect = btn.getBoundingClientRect();
|
||||
const after = event.clientY > rect.top + rect.height / 2;
|
||||
toolsEl.insertBefore(dragged, after ? btn.nextSibling : btn);
|
||||
});
|
||||
});
|
||||
function persistToolOrder() {
|
||||
vscode.postMessage({
|
||||
type: 'setToolOrder',
|
||||
order: Array.from(toolsEl.querySelectorAll('.tool-btn')).map(btn => btn.dataset.id).filter(Boolean),
|
||||
});
|
||||
}
|
||||
document.getElementById('betaToggle').addEventListener('click', (event) => {
|
||||
const enabled = event.currentTarget.dataset.enabled === 'true';
|
||||
vscode.postMessage({ type: 'setBetaToolsEnabled', enabled: !enabled });
|
||||
@@ -232,17 +274,29 @@ ${toolButtons}
|
||||
</html>`;
|
||||
}
|
||||
|
||||
function sortToolsForDisplay(tools: ToolDefinition[]): ToolDefinition[] {
|
||||
return [...tools].sort((a, b) => Number(Boolean(a.beta)) - Number(Boolean(b.beta)));
|
||||
function sortToolsForDisplay(tools: ToolDefinition[], toolOrder: string[]): ToolDefinition[] {
|
||||
const order = new Map(toolOrder.map((id, index) => [id, index]));
|
||||
return [...tools].sort((a, b) => {
|
||||
const aId = toolId(a);
|
||||
const bId = toolId(b);
|
||||
const ai = order.has(aId) ? order.get(aId)! : Number.MAX_SAFE_INTEGER;
|
||||
const bi = order.has(bId) ? order.get(bId)! : Number.MAX_SAFE_INTEGER;
|
||||
if (ai !== bi) return ai - bi;
|
||||
return Number(Boolean(a.beta)) - Number(Boolean(b.beta));
|
||||
});
|
||||
}
|
||||
|
||||
function renderToolButton(tool: ToolDefinition): string {
|
||||
return ` <button class="tool-btn" data-cmd="${escapeHtml(tool.command)}">
|
||||
return ` <button class="tool-btn" data-id="${escapeHtml(toolId(tool))}" data-cmd="${escapeHtml(tool.command)}">
|
||||
<span class="tool-title">${escapeHtml(tool.title)}${tool.beta ? ' <span class="beta-badge">Beta</span>' : ''}</span>
|
||||
<span class="tool-desc">${escapeHtml(tool.description)}</span>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
function toolId(tool: ToolDefinition): string {
|
||||
return tool.id || tool.command;
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value.replace(/[&<>"']/g, ch => ({
|
||||
'&': '&',
|
||||
|
||||
@@ -234,7 +234,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse, shar
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Cache-Control': 'no-store',
|
||||
});
|
||||
res.end(file.buf);
|
||||
res.end(mobilePreviewPage(file));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -296,6 +296,35 @@ function chooserPage(files: SharedFile[]): string {
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
function mobilePreviewPage(file: SharedFile): string {
|
||||
const playableJson = JSON.stringify(file.buf.toString('utf8')).replace(/<\/script/gi, '<\\/script');
|
||||
const title = file.filename.replace(/[&<>"]/g, (c) =>
|
||||
({ '&': '&', '<': '<', '>': '>', '"': '"' }[c] as string)
|
||||
);
|
||||
return `<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<title>${title}</title>
|
||||
<style>
|
||||
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: #000; }
|
||||
iframe { position: fixed; inset: 0; width: 100%; height: 100%; border: 0; background: #000; }
|
||||
.boot { position: fixed; inset: 0; display: grid; place-items: center; color: #777; font: 12px system-ui, sans-serif; }
|
||||
</style>
|
||||
</head><body>
|
||||
<div id="boot" class="boot">Loading preview...</div>
|
||||
<iframe id="playable" sandbox="allow-scripts allow-same-origin allow-pointer-lock allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"></iframe>
|
||||
<script>
|
||||
const playableHtml = ${playableJson};
|
||||
setTimeout(() => {
|
||||
const frame = document.getElementById('playable');
|
||||
document.getElementById('boot').style.display = 'none';
|
||||
frame.srcdoc = playableHtml;
|
||||
}, 750);
|
||||
</script>
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
interface LanIp { address: string; iface: string; }
|
||||
|
||||
function getLanIps(): LanIp[] {
|
||||
|
||||
Reference in New Issue
Block a user