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.
301 lines
10 KiB
HTML
301 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Toolbox</title>
|
||
<link rel="stylesheet" href="saucer://embedded/html/styles.css">
|
||
|
||
</head>
|
||
<body>
|
||
<div id="shell">
|
||
<div class="header">
|
||
<div class="header-cluster header-left">
|
||
<button onclick="getNewContent()" class="button icon-button" id="reload-button">↻</button>
|
||
</div>
|
||
<h1>Toolbox</h1>
|
||
<div class="header-cluster header-right">
|
||
<div class="button-group">
|
||
<button onclick="openCmd()" class="button icon-button" id="cmd-button">💻</button>
|
||
<button onclick="openFileSystem()" id="file-system-button" class="button icon-button">📁</button>
|
||
</div>
|
||
<button onclick="openEinstFirst()" class="button icon-button" id="option-button" style="display: none;">⚙</button>
|
||
<button onclick="toggleGeneralSettings()" class="button icon-button" id="general-settings-button" title="Einstellungen">⚙</button>
|
||
</div>
|
||
</div>
|
||
<div class="main" id="toolbox">
|
||
<!-- Tool Cards werden hier dynamisch geladen -->
|
||
<div class="add-tool-card" onclick="openAddTool()">
|
||
<span class="plus-icon">+</span>
|
||
</div>
|
||
</div>
|
||
<div class="footer">
|
||
<!--Schmidti.Digital © 2024-->
|
||
</div>
|
||
</div>
|
||
<div id="settings-overlay" class="settings-overlay" hidden>
|
||
<div class="settings-panel">
|
||
<div class="settings-panel-header">
|
||
<h2>Einstellungen</h2>
|
||
<button type="button" class="settings-panel-close" onclick="toggleGeneralSettings()">×</button>
|
||
</div>
|
||
<div class="settings-panel-body">
|
||
<div class="settings-row">
|
||
<div class="settings-row-label">
|
||
<span class="settings-row-title">Fenster-Deckkraft</span>
|
||
<span class="settings-row-desc">70% bis 100% – bei 100% ist das Fenster vollstaendig deckend.</span>
|
||
</div>
|
||
<div class="settings-row-control">
|
||
<input type="range" id="opacity-slider" min="70" max="100" value="100" step="1">
|
||
<span id="opacity-value" class="settings-row-value">100%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script src="saucer://embedded/html/tool-runtime.js"></script>
|
||
<script>
|
||
function getNewContent(){
|
||
saucer.exposed.getNewContent();
|
||
}
|
||
|
||
async function runLuaWithId(event) {
|
||
event.preventDefault();
|
||
if (event.target.closest('button')) {
|
||
return false;
|
||
}
|
||
|
||
const toolCard = event.currentTarget;
|
||
if (toolCard) {
|
||
const toolId = toolCard.getAttribute('data-tool-id');
|
||
const toolKey = toolCard.dataset.toolKey;
|
||
|
||
if (ToolRuntime.isCardRunBlocked(toolCard)) {
|
||
console.log(`Button is already disabled for tool ID: ${toolId}`);
|
||
return false;
|
||
}
|
||
try {
|
||
const response = JSON.parse(await saucer.exposed.run_lua(toolKey));
|
||
if (response.singleInstance === true) {
|
||
await loadToolboxItems();
|
||
}
|
||
} catch (error) {
|
||
console.error('Fehler beim Starten des Tools:', error);
|
||
}
|
||
}
|
||
}
|
||
|
||
function test(){
|
||
saucer.exposed.debug_print();
|
||
}
|
||
|
||
function openFileSystem() {
|
||
saucer.exposed.open_file_system();
|
||
}
|
||
|
||
function openCmd() {
|
||
saucer.exposed.open_cmd();
|
||
}
|
||
|
||
function openAddTool() {
|
||
saucer.exposed.openAddTool();
|
||
}
|
||
|
||
function createToolCard(item) {
|
||
const toolId = String(item.id);
|
||
const card = document.createElement('div');
|
||
card.className = 'tool-card';
|
||
card.dataset.url = String(item.url || '');
|
||
card.dataset.toolId = toolId;
|
||
card.dataset.toolKey = String(item.path || '');
|
||
card.addEventListener('click', runLuaWithId);
|
||
card.addEventListener('contextmenu', showContextMenu);
|
||
|
||
const icon = document.createElement('img');
|
||
const iconData = typeof item.icon === 'string' ? item.icon : '';
|
||
icon.src = iconData.startsWith('data:image/png;base64,')
|
||
? iconData
|
||
: `data:image/png;base64,${iconData}`;
|
||
icon.alt = String(item.name || '');
|
||
|
||
const info = document.createElement('div');
|
||
info.className = 'tool-info';
|
||
|
||
const name = document.createElement('h3');
|
||
name.textContent = String(item.name || '');
|
||
|
||
const description = document.createElement('p');
|
||
description.textContent = String(item.description || '');
|
||
|
||
const action = document.createElement('div');
|
||
action.className = 'tool-action';
|
||
|
||
const settingsButton = document.createElement('button');
|
||
settingsButton.type = 'button';
|
||
settingsButton.textContent = '⚙';
|
||
settingsButton.title = 'Tool bearbeiten';
|
||
settingsButton.setAttribute('aria-label', 'Tool bearbeiten');
|
||
settingsButton.addEventListener('click', event => {
|
||
openEinst(event, card.dataset.toolKey);
|
||
});
|
||
|
||
const runningStatus = document.createElement('span');
|
||
runningStatus.className = 'tool-running-status';
|
||
runningStatus.textContent = 'Läuft…';
|
||
runningStatus.hidden = true;
|
||
info.append(name, description);
|
||
action.append(runningStatus, settingsButton);
|
||
card.append(icon, info, action);
|
||
ToolRuntime.applyCardState(card, ToolRuntime.initialRunning(item));
|
||
return card;
|
||
}
|
||
|
||
function renderToolboxItems(items) {
|
||
const toolbox = document.getElementById('toolbox');
|
||
toolbox.querySelectorAll('.tool-card').forEach(card => card.remove());
|
||
|
||
const cards = document.createDocumentFragment();
|
||
items.forEach(item => cards.appendChild(createToolCard(item)));
|
||
toolbox.insertBefore(cards, toolbox.querySelector('.add-tool-card'));
|
||
}
|
||
|
||
function loadToolboxItems() {
|
||
return saucer.exposed.getToolboxItems()
|
||
.then(jsonString => {
|
||
const items = JSON.parse(jsonString);
|
||
renderToolboxItems(Array.isArray(items) ? items : []);
|
||
})
|
||
.catch(error => console.error('Fehler beim Abrufen der Toolbox-Elemente:', error));
|
||
}
|
||
|
||
function reloadContent() {
|
||
loadToolboxItems();
|
||
}
|
||
|
||
function setToolRunningByPath(toolKey, running) {
|
||
const card = Array.from(document.querySelectorAll('.tool-card'))
|
||
.find(candidate => candidate.dataset.toolKey === toolKey);
|
||
ToolRuntime.applyCardState(card, running);
|
||
}
|
||
|
||
function openEinst(event, toolPath){
|
||
if (event) {
|
||
event.stopPropagation();
|
||
event.preventDefault();
|
||
}
|
||
console.log('Einst Button geklickt!');
|
||
saucer.exposed.openEinst(toolPath);
|
||
}
|
||
|
||
function openEinstFirst(){
|
||
const firstCard = document.querySelector('.tool-card');
|
||
if (!firstCard) {
|
||
return;
|
||
}
|
||
saucer.exposed.openEinst(firstCard.dataset.toolKey);
|
||
}
|
||
|
||
let activeContextMenu = null;
|
||
|
||
function closeContextMenu() {
|
||
if (activeContextMenu) {
|
||
activeContextMenu.remove();
|
||
activeContextMenu = null;
|
||
}
|
||
}
|
||
|
||
function showContextMenu(event) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
closeContextMenu();
|
||
const card = event.currentTarget;
|
||
const toolKey = card.dataset.toolKey;
|
||
const toolName = card.querySelector('h3')?.textContent || 'dieses Tool';
|
||
|
||
const menu = document.createElement('div');
|
||
menu.className = 'context-menu';
|
||
|
||
const editBtn = document.createElement('button');
|
||
editBtn.type = 'button';
|
||
editBtn.textContent = 'Bearbeiten';
|
||
editBtn.addEventListener('click', () => {
|
||
closeContextMenu();
|
||
openEinst(null, toolKey);
|
||
});
|
||
|
||
const deleteBtn = document.createElement('button');
|
||
deleteBtn.type = 'button';
|
||
deleteBtn.textContent = 'Löschen';
|
||
deleteBtn.className = 'context-menu-danger';
|
||
deleteBtn.addEventListener('click', () => {
|
||
closeContextMenu();
|
||
confirmDeleteTool(toolKey, toolName);
|
||
});
|
||
|
||
menu.append(editBtn, deleteBtn);
|
||
document.body.append(menu);
|
||
activeContextMenu = menu;
|
||
|
||
const mx = Math.min(event.clientX, window.innerWidth - 180);
|
||
const my = Math.min(event.clientY, window.innerHeight - 90);
|
||
menu.style.left = mx + 'px';
|
||
menu.style.top = my + 'px';
|
||
}
|
||
|
||
async function confirmDeleteTool(toolKey, toolName) {
|
||
if (!confirm(`"${toolName}" wirklich löschen?`)) {
|
||
return;
|
||
}
|
||
try {
|
||
const response = JSON.parse(await saucer.exposed.deleteTool(toolKey));
|
||
if (response.success) {
|
||
await loadToolboxItems();
|
||
} else {
|
||
alert(response.message || 'Löschen fehlgeschlagen.');
|
||
}
|
||
} catch (error) {
|
||
console.error('Fehler beim Löschen:', error);
|
||
alert('Löschen fehlgeschlagen.');
|
||
}
|
||
}
|
||
|
||
document.addEventListener('click', closeContextMenu);
|
||
|
||
let settingsOverlayVisible = false;
|
||
let currentOpacity = 100;
|
||
|
||
function toggleGeneralSettings() {
|
||
const overlay = document.getElementById('settings-overlay');
|
||
settingsOverlayVisible = !settingsOverlayVisible;
|
||
overlay.hidden = !settingsOverlayVisible;
|
||
if (settingsOverlayVisible) {
|
||
const slider = document.getElementById('opacity-slider');
|
||
const valueLabel = document.getElementById('opacity-value');
|
||
slider.value = currentOpacity;
|
||
valueLabel.textContent = currentOpacity + '%';
|
||
}
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const slider = document.getElementById('opacity-slider');
|
||
const valueLabel = document.getElementById('opacity-value');
|
||
slider.addEventListener('input', () => {
|
||
const val = parseInt(slider.value, 10);
|
||
valueLabel.textContent = val + '%';
|
||
currentOpacity = val;
|
||
document.getElementById('shell').style.opacity = (val / 100).toString();
|
||
});
|
||
|
||
document.addEventListener('click', (e) => {
|
||
const link = e.target.closest('a');
|
||
if (link) {
|
||
e.preventDefault();
|
||
}
|
||
}, true);
|
||
|
||
loadToolboxItems();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|