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.
This commit is contained in:
2026-07-17 17:22:15 +02:00
parent 074fa73992
commit c611b008af
45 changed files with 5501 additions and 709 deletions
+203 -131
View File
@@ -20,6 +20,7 @@
<button onclick="openFileSystem()" id="file-system-button" class="button icon-button">&#x1f4c1;</button>
</div>
<button onclick="openEinstFirst()" class="button icon-button" id="option-button" style="display: none;">&#9881;</button>
<button onclick="toggleGeneralSettings()" class="button icon-button" id="general-settings-button" title="Einstellungen">&#9881;</button>
</div>
</div>
<div class="main" id="toolbox">
@@ -32,36 +33,54 @@
<!--Schmidti.Digital &copy; 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()">&times;</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();
}
function runLuaWithId(event) {
async function runLuaWithId(event) {
event.preventDefault();
if (event.target.tagName === 'BUTTON') {
// Falls der Button gedrückt wurde, brich die Funktion ab
if (event.target.closest('button')) {
return false;
}
const button = event.target;
const toolCard = button.closest('.tool-card');
const toolCard = event.currentTarget;
if (toolCard) {
const toolId = toolCard.getAttribute('data-tool-id');
const toolKey = toolCard.dataset.toolKey;
if (sessionStorage.getItem(`toolButtonDisabled_${toolId}`) !== 'true') {
console.log(`Disabling button for tool ID: ${toolId}`);
saucer.exposed.run_lua(parseInt(toolId, 10));
// Button deaktivieren
button.disabled = true;
button.classList.add('running');
button.textContent = 'Wird ausgeführt...';
// Speichern des Zustands im sessionStorage
sessionStorage.setItem(`toolButtonDisabled_${toolId}`, 'true');
} else {
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);
}
}
}
@@ -79,83 +98,93 @@
}
function openAddTool() {
openFileSystem();
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() {
const toolbox = document.getElementById('toolbox');
// Toolbox leeren, aber das add-tool-card Element behalten
const addToolCard = toolbox.querySelector('.add-tool-card');
toolbox.innerHTML = '';
// Toolbox-Daten neu abrufen
saucer.exposed.getToolboxItems().then(jsonString => {
const m_toolboxItems = JSON.parse(jsonString);
m_toolboxItems.forEach(item => {
const iconSrc = item.icon.startsWith('data:image/png;base64,')
? item.icon
: `data:image/png;base64,${item.icon}`;
const toolCardHTML = `
<div class="tool-card" data-url="${item.url}"
data-tool-id="${item.id}"
onclick="runLuaWithId(event)">
<img src="${iconSrc}" alt="${item.name}">
<div class="tool-info">
<h3>${item.name}</h3>
<p>${item.description}</p>
</div>
<div class="tool-action">
<button onclick="openEinst(event, ${item.id})">Einst</button>
</div>
</div>
`;
toolbox.innerHTML += toolCardHTML;
});
// Das add-tool-card Element wieder hinzufügen
if (addToolCard) {
toolbox.appendChild(addToolCard);
}
// Überprüfen und Wiederherstellen des Zustands der Buttons
document.querySelectorAll('.tool-card').forEach(card => {
const toolId = card.getAttribute('data-tool-id');
const button = card.querySelector('button');
if (sessionStorage.getItem(`toolButtonDisabled_${toolId}`) === 'true') {
button.disabled = true;
button.classList.add('running');
button.textContent = 'Wird ausgeführt...';
}
});
}).catch(error => console.error('Fehler beim Abrufen der Toolbox-Elemente:', error));
loadToolboxItems();
}
function enableButtonByToolId(toolId) {
saucer.exposed.debug_print();
// Finden Sie den Button mit dem entsprechenden data-tool-id
const button = document.querySelector(`.tool-card[data-tool-id='${toolId}']`);
if (button) {
button.disabled = false;
button.classList.remove('running');
button.textContent = 'Einst';
// Speichern des Zustands im sessionStorage
sessionStorage.setItem(`toolButtonDisabled_${toolId}`, 'false');
} else {
console.error('Button mit Tool-ID ' + toolId + ' nicht gefunden.');
}
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, toolId){
function openEinst(event, toolPath){
if (event) {
event.stopPropagation();
event.preventDefault();
}
console.log('Einst Button geklickt!');
saucer.exposed.openEinst(toolId);
saucer.exposed.openEinst(toolPath);
}
function openEinstFirst(){
@@ -163,11 +192,100 @@
if (!firstCard) {
return;
}
const toolId = firstCard.getAttribute('data-tool-id');
saucer.exposed.openEinst(parseInt(toolId));
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) {
@@ -175,53 +293,7 @@
}
}, true);
const toolbox = document.getElementById('toolbox');
// Das add-tool-card Element entfernen und später wieder hinzufügen
const addToolCard = toolbox.querySelector('.add-tool-card');
saucer.exposed.getToolboxItems().then(jsonString => {
const m_toolboxItems = JSON.parse(jsonString);
m_toolboxItems.forEach(item => {
const iconSrc = item.icon.startsWith('data:image/png;base64,')
? item.icon
: `data:image/png;base64,${item.icon}`;
const toolCardHTML = `
<div class="tool-card" data-url="${item.url}"
data-tool-id="${item.id}"
onclick="runLuaWithId(event)">
<img src="${iconSrc}" alt="${item.name}">
<div class="tool-info">
<h3>${item.name}</h3>
<p>${item.description}</p>
</div>
<div class="tool-action">
<button onclick="openEinst(event, ${item.id})">Einst</button>
</div>
</div>
`;
toolbox.innerHTML += toolCardHTML;
});
// Das add-tool-card Element wieder hinzufügen
if (addToolCard) {
toolbox.appendChild(addToolCard);
}
// Überprüfen und Wiederherstellen des Zustands beim Laden der Seite
document.querySelectorAll('.tool-card').forEach(card => {
const toolId = card.getAttribute('data-tool-id');
const button = card.querySelector('button');
if (sessionStorage.getItem(`toolButtonDisabled_${toolId}`) === 'true') {
button.disabled = true;
button.classList.add('running');
button.textContent = 'Wird ausgeführt...';
}
});
}).catch(error => console.error('Error fetching toolbox items:', error));
loadToolboxItems();
});
</script>
</body>