Files
Toolbox/res/html/einstellungen_custom.html
Yadciel c611b008af Fügt CLI-Toolverwaltung und erweiterte UI-Funktionen hinzu
Ermöglicht die Verwaltung (Erstellen, Auflisten, Ausführen) von Tools über eine Befehlszeilenschnittstelle für Automatisierung und Integration. Die Benutzeroberfläche wurde mit dedizierten Ansichten für Toolerstellung und -bearbeitung, In-App-Lua-Hilfe sowie kontextsensitiven Menüs erweitert. Neue Abhängigkeiten (CLI11, rang) wurden integriert und eine umfassende Testsuite hinzugefügt. Die Lizenzierung wurde auf AGPL-3.0 umgestellt und ein Kontributionsleitfaden bereitgestellt.
2026-07-17 17:22:15 +02:00

473 lines
22 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tool bearbeiten</title>
<link rel="stylesheet" href="saucer://embedded/html/styles.css">
</head>
<body class="settings-page">
<div id="shell" class="settings-shell">
<div class="resize-handle resize-top" data-webview-resize="t"></div>
<div class="resize-handle resize-right" data-webview-resize="r"></div>
<div class="resize-handle resize-bottom" data-webview-resize="b"></div>
<div class="resize-handle resize-left" data-webview-resize="l"></div>
<div class="resize-handle resize-top-left" data-webview-resize="tl"></div>
<div class="resize-handle resize-top-right" data-webview-resize="tr"></div>
<div class="resize-handle resize-bottom-left" data-webview-resize="bl"></div>
<div class="resize-handle resize-bottom-right" data-webview-resize="br"></div>
<div class="titlebar" data-webview-drag>
<div class="titlebar-left" data-webview-drag>
<button class="titlebar-btn" id="back-button" data-webview-ignore></button>
<span class="titlebar-title" data-webview-drag>Tool bearbeiten</span>
</div>
<div class="titlebar-actions" data-webview-drag>
<button class="titlebar-btn" data-webview-minimize data-webview-ignore></button>
<button class="titlebar-btn close" data-webview-close data-webview-ignore>×</button>
</div>
</div>
<main class="tool-settings-page">
<header class="tool-settings-heading">
<div id="settings-icon-preview" class="add-tool-icon-preview" aria-label="Tool-Icon">λ</div>
<div>
<span class="add-tool-eyebrow">Toolbox</span>
<h1 id="settings-heading">Tool bearbeiten</h1>
<p>Eigenschaften und Startverhalten dieses Tools.</p>
</div>
</header>
<nav class="tool-settings-tabs" aria-label="Einstellungsbereiche">
<button type="button" class="tool-settings-tab is-active" data-panel="general">Allgemein</button>
<button type="button" class="tool-settings-tab" data-panel="launch">Startverhalten</button>
<button type="button" class="tool-settings-tab" data-panel="actions">Aktionen</button>
</nav>
<form id="tool-settings-form" class="tool-settings-form">
<section class="tool-settings-panel is-active" data-panel-content="general">
<div class="add-tool-section">
<h2>Allgemein</h2>
<label for="tool-name">Name</label>
<input id="tool-name" class="settings-input" maxlength="80" autocomplete="off" required>
<label for="tool-description">Beschreibung</label>
<textarea id="tool-description" class="settings-input add-tool-description" maxlength="8192"
placeholder="Was erledigt dieses Tool?"></textarea>
<div class="add-tool-grid">
<div>
<label for="tool-timeout">Lua-Timeout</label>
<div class="add-tool-input-suffix">
<input id="tool-timeout" class="settings-input" type="number" min="0" max="86400" value="30">
<span>Sek.</span>
</div>
<small class="tool-settings-help">0 deaktiviert den Timeout.</small>
</div>
<div>
<label for="tool-icon">Icon (PNG)</label>
<input id="tool-icon" class="settings-input add-tool-file" type="file" accept="image/png">
<button type="button" id="remove-icon" class="tool-settings-link">Icon entfernen</button>
</div>
</div>
</div>
</section>
<section class="tool-settings-panel" data-panel-content="launch">
<div class="add-tool-section add-tool-template-section">
<div class="add-tool-section-heading">
<div>
<h2>Starttyp</h2>
<p>Standardvorlagen bleiben ohne Lua-Kenntnisse bearbeitbar.</p>
</div>
<span id="template-badge" class="add-tool-template-badge">Lua</span>
</div>
<div class="add-tool-template-grid" role="group" aria-label="Starttyp">
<button type="button" class="add-tool-template" data-template="program">
<span class="add-tool-template-icon"></span>
<span><strong>Programm</strong><small>Eine .exe starten</small></span>
</button>
<button type="button" class="add-tool-template" data-template="folder">
<span class="add-tool-template-icon"></span>
<span><strong>Ordner</strong><small>Im Explorer öffnen</small></span>
</button>
<button type="button" class="add-tool-template" data-template="website">
<span class="add-tool-template-icon"></span>
<span><strong>Website</strong><small>Eine URL öffnen</small></span>
</button>
<button type="button" class="add-tool-template" data-template="lua">
<span class="add-tool-template-icon">λ</span>
<span><strong>Freies Lua</strong><small>Eigenes Skript</small></span>
</button>
</div>
<div id="native-target-controls" class="tool-settings-target" hidden>
<label for="template-target">Ausgewähltes Ziel</label>
<div class="add-tool-template-controls">
<input id="template-target" class="settings-input" readonly>
<button type="button" id="select-target" class="add-tool-secondary">Neu auswählen</button>
</div>
</div>
<div id="website-target-controls" class="tool-settings-target" hidden>
<label for="website-url">Website-Adresse</label>
<div class="add-tool-template-controls">
<input id="website-url" class="settings-input" type="url" autocomplete="url"
placeholder="https://example.com">
<button type="button" id="apply-website" class="add-tool-secondary">Übernehmen</button>
</div>
</div>
</div>
<div id="lua-section" class="add-tool-section">
<div class="add-tool-section-heading">
<h2>start.lua</h2>
<button type="button" class="lua-help-trigger" id="lua-help-button">Lua-Cheatsheet</button>
</div>
<p class="tool-settings-help">Wird beim Klick auf die Toolkarte ausgeführt.</p>
<div class="add-tool-code-editor tool-settings-editor">
<pre id="tool-script-highlight" aria-hidden="true"><code class="language-lua"></code></pre>
<textarea id="tool-script" class="settings-input add-tool-script" spellcheck="false" wrap="off"
aria-label="Lua-Skript"></textarea>
</div>
</div>
<div id="generated-script-note" class="add-tool-section tool-settings-generated" hidden>
<span class="tool-settings-generated-icon"></span>
<div>
<h2>Startskript wird automatisch gepflegt</h2>
<p>Toolbox erzeugt <code>start.lua</code> beim Speichern sicher aus dem ausgewählten Ziel.</p>
</div>
</div>
</section>
<section class="tool-settings-panel" data-panel-content="actions">
<div class="add-tool-section tool-settings-action-card">
<div>
<h2>Toolordner</h2>
<p>Öffnet den Ordner mit <code>start.lua</code>, <code>config.ini</code> und weiteren Tooldateien.</p>
</div>
<button type="button" id="open-tool-folder" class="add-tool-secondary">Im Explorer öffnen</button>
</div>
<div class="add-tool-section tool-settings-action-card">
<div>
<h2 style="color:#ff6b6b">Tool löschen</h2>
<p>Entfernt dieses Tool unwiderruflich inklusive aller Dateien.</p>
</div>
<button type="button" id="delete-tool" class="add-tool-secondary" style="color:#ff6b6b;border-color:rgba(255,107,107,.4)">Löschen…</button>
</div>
</section>
</form>
</main>
<footer class="tool-settings-footer">
<div id="settings-status" class="settings-status" role="status" aria-live="polite"></div>
<button type="button" id="cancel-button" class="add-tool-secondary">Zurück</button>
<button type="submit" form="tool-settings-form" id="save-button" class="back-button settings-save">Speichern</button>
</footer>
</div>
<script src="saucer://embedded/vendor/prism/prism-core.min.js"></script>
<script src="saucer://embedded/vendor/prism/prism-lua.min.js"></script>
<script>
const form = document.getElementById('tool-settings-form');
const nameInput = document.getElementById('tool-name');
const descriptionInput = document.getElementById('tool-description');
const timeoutInput = document.getElementById('tool-timeout');
const iconInput = document.getElementById('tool-icon');
const iconPreview = document.getElementById('settings-icon-preview');
const heading = document.getElementById('settings-heading');
const scriptInput = document.getElementById('tool-script');
const scriptHighlight = document.querySelector('#tool-script-highlight code');
const scriptHighlightContainer = document.getElementById('tool-script-highlight');
const nativeTargetControls = document.getElementById('native-target-controls');
const websiteTargetControls = document.getElementById('website-target-controls');
const targetInput = document.getElementById('template-target');
const websiteInput = document.getElementById('website-url');
const luaSection = document.getElementById('lua-section');
const generatedNote = document.getElementById('generated-script-note');
const templateBadge = document.getElementById('template-badge');
const templateButtons = [...document.querySelectorAll('[data-template]')];
const status = document.getElementById('settings-status');
const saveButton = document.getElementById('save-button');
let templateType = 'lua';
let templateTarget = '';
let iconBase64 = '';
let iconChanged = false;
function setStatus(message = '', kind = '') {
status.textContent = message;
if (kind) {
status.dataset.kind = kind;
} else {
delete status.dataset.kind;
}
}
function returnToToolbox() {
saucer.exposed.openIndex();
}
function highlightLua() {
scriptHighlight.textContent = scriptInput.value + (scriptInput.value.endsWith('\n') ? ' ' : '');
Prism.highlightElement(scriptHighlight);
}
function renderIcon() {
iconPreview.replaceChildren();
if (!iconBase64) {
iconPreview.textContent = 'λ';
return;
}
const image = document.createElement('img');
image.src = `data:image/png;base64,${iconBase64}`;
image.alt = 'Tool-Icon';
iconPreview.appendChild(image);
}
function selectTemplate(kind) {
templateType = kind;
templateButtons.forEach(button => {
const selected = button.dataset.template === kind;
button.classList.toggle('is-active', selected);
button.setAttribute('aria-pressed', String(selected));
});
const labels = {program: 'Programm', folder: 'Ordner', website: 'Website', lua: 'Lua'};
templateBadge.textContent = labels[kind] || 'Lua';
nativeTargetControls.hidden = kind !== 'program' && kind !== 'folder';
websiteTargetControls.hidden = kind !== 'website';
luaSection.hidden = kind !== 'lua';
generatedNote.hidden = kind === 'lua';
targetInput.value = kind === 'program' || kind === 'folder' ? templateTarget : '';
websiteInput.value = kind === 'website' ? templateTarget : '';
}
function applyTemplateResponse(responseText) {
const response = JSON.parse(responseText);
if (response.cancelled) {
setStatus();
return false;
}
if (!response.success || !response.draft) {
setStatus(response.message || 'Das Ziel konnte nicht übernommen werden.', 'error');
return false;
}
templateType = response.draft.templateType;
templateTarget = response.draft.templateTarget;
scriptInput.value = response.draft.luaScript;
selectTemplate(templateType);
highlightLua();
setStatus('Startziel wurde übernommen. Zum Anwenden noch speichern.', 'success');
return true;
}
async function chooseNativeTarget(kind) {
setStatus(kind === 'program' ? 'Programm auswählen…' : 'Ordner auswählen…', 'pending');
try {
const response = kind === 'program'
? await saucer.exposed.selectProgramTemplate()
: await saucer.exposed.selectFolderTemplate();
applyTemplateResponse(response);
} catch (error) {
console.error('Zielauswahl fehlgeschlagen:', error);
setStatus('Der Auswahldialog konnte nicht geöffnet werden.', 'error');
}
}
async function applyWebsite() {
setStatus('Website wird geprüft…', 'pending');
try {
applyTemplateResponse(await saucer.exposed.createWebsiteTemplate(websiteInput.value.trim()));
} catch (error) {
console.error('Website-Prüfung fehlgeschlagen:', error);
setStatus('Die Website konnte nicht übernommen werden.', 'error');
}
}
function showPanel(name) {
document.querySelectorAll('[data-panel-content]').forEach(panel => {
panel.classList.toggle('is-active', panel.dataset.panelContent === name);
});
document.querySelectorAll('[data-panel]').forEach(tab => {
tab.classList.toggle('is-active', tab.dataset.panel === name);
});
}
document.querySelectorAll('[data-panel]').forEach(tab => {
tab.addEventListener('click', () => showPanel(tab.dataset.panel));
});
templateButtons.forEach(button => {
button.addEventListener('click', () => {
const kind = button.dataset.template;
if (kind === 'program' || kind === 'folder') {
void chooseNativeTarget(kind);
return;
}
if (kind === 'website') {
templateTarget = '';
selectTemplate('website');
websiteInput.focus();
return;
}
templateTarget = '';
selectTemplate('lua');
scriptInput.focus();
});
});
document.getElementById('select-target').addEventListener('click', () => void chooseNativeTarget(templateType));
document.getElementById('apply-website').addEventListener('click', () => void applyWebsite());
websiteInput.addEventListener('keydown', event => {
if (event.key === 'Enter') {
event.preventDefault();
void applyWebsite();
}
});
scriptInput.addEventListener('input', highlightLua);
scriptInput.addEventListener('scroll', () => {
scriptHighlightContainer.scrollTop = scriptInput.scrollTop;
scriptHighlightContainer.scrollLeft = scriptInput.scrollLeft;
});
iconInput.addEventListener('change', () => {
const file = iconInput.files[0];
if (!file) {
return;
}
if (file.type !== 'image/png' || file.size > 2 * 1024 * 1024) {
iconInput.value = '';
setStatus('Bitte ein PNG mit maximal 2 MB auswählen.', 'error');
return;
}
const reader = new FileReader();
reader.addEventListener('load', () => {
const dataUrl = String(reader.result || '');
iconBase64 = dataUrl.includes(',') ? dataUrl.split(',', 2)[1] : '';
iconChanged = true;
renderIcon();
setStatus('Neues Icon ausgewählt. Zum Anwenden noch speichern.', 'success');
});
reader.readAsDataURL(file);
});
document.getElementById('remove-icon').addEventListener('click', () => {
iconBase64 = '';
iconChanged = true;
iconInput.value = '';
renderIcon();
setStatus('Icon wird beim Speichern entfernt.', 'success');
});
document.getElementById('open-tool-folder').addEventListener('click', async () => {
try {
const response = JSON.parse(await saucer.exposed.openToolFolder());
setStatus(response.message, response.success ? 'success' : 'error');
} catch (error) {
console.error('Toolordner konnte nicht geöffnet werden:', error);
setStatus('Der Toolordner konnte nicht geöffnet werden.', 'error');
}
});
document.getElementById('lua-help-button').addEventListener('click', () => saucer.exposed.openLuaHelp());
document.getElementById('delete-tool')?.addEventListener('click', async () => {
const toolName = nameInput.value.trim() || 'dieses Tool';
if (!confirm(`"${toolName}" unwiderruflich löschen?`)) {
return;
}
try {
const details = JSON.parse(await saucer.exposed.getToolDetails());
const toolPath = details.success ? (details.details?.path || details.path || '') : '';
if (!toolPath) {
setStatus('Tool-Pfad konnte nicht ermittelt werden.', 'error');
return;
}
const response = JSON.parse(await saucer.exposed.deleteTool(toolPath));
if (response.success) {
returnToToolbox();
} else {
setStatus(response.message || 'Löschen fehlgeschlagen.', 'error');
}
} catch (error) {
console.error('Löschen fehlgeschlagen:', error);
setStatus('Löschen fehlgeschlagen.', 'error');
}
});
document.getElementById('back-button').addEventListener('click', returnToToolbox);
document.getElementById('cancel-button').addEventListener('click', returnToToolbox);
form.addEventListener('submit', async event => {
event.preventDefault();
if (!nameInput.value.trim()) {
showPanel('general');
nameInput.focus();
setStatus('Bitte einen Toolnamen eingeben.', 'error');
return;
}
if (templateType === 'website') {
templateTarget = websiteInput.value.trim();
}
saveButton.disabled = true;
setStatus('Änderungen werden gespeichert…', 'pending');
const payload = {
name: nameInput.value.trim(),
description: descriptionInput.value,
timeoutSeconds: Number.parseInt(timeoutInput.value, 10) || 0,
luaScript: scriptInput.value,
templateType,
templateTarget,
};
if (iconChanged) {
payload.iconBase64 = iconBase64;
}
try {
const response = JSON.parse(await saucer.exposed.saveToolDetails(JSON.stringify(payload)));
setStatus(response.message || (response.success ? 'Tool wurde gespeichert.' : 'Speichern fehlgeschlagen.'),
response.success ? 'success' : 'error');
if (response.success) {
heading.textContent = nameInput.value.trim();
iconChanged = false;
}
} catch (error) {
console.error('Tool konnte nicht gespeichert werden:', error);
setStatus('Tool konnte nicht gespeichert werden.', 'error');
}
saveButton.disabled = false;
});
async function loadTool() {
setStatus('Tool wird geladen…', 'pending');
try {
const response = JSON.parse(await saucer.exposed.getToolDetails());
if (!response.success || !response.details) {
setStatus(response.message || 'Tool konnte nicht geladen werden.', 'error');
saveButton.disabled = true;
return;
}
const details = response.details;
nameInput.value = details.name || '';
descriptionInput.value = details.description || '';
timeoutInput.value = details.timeoutSeconds ?? 30;
scriptInput.value = details.luaScript || '';
templateType = details.templateType || 'lua';
templateTarget = details.templateTarget || '';
iconBase64 = details.iconBase64 || '';
iconChanged = false;
heading.textContent = details.name || 'Tool bearbeiten';
renderIcon();
selectTemplate(templateType);
highlightLua();
setStatus();
} catch (error) {
console.error('Tool konnte nicht geladen werden:', error);
setStatus('Tool konnte nicht geladen werden.', 'error');
saveButton.disabled = true;
}
}
void loadTool();
</script>
</body>
</html>