This commit is contained in:
Mateusz Gruszczyński
2026-09-21 10:28:28 +02:00
parent 09c10d8008
commit e5013ea819
16 changed files with 124 additions and 70 deletions
+20 -9
View File
@@ -104,14 +104,29 @@ function flowDefaultConfig(kind) {
return {};
}
function flowExecutionType(flow) {
const schedules = flow.compiled_schedule_ids?.length || 0;
const automations = flow.compiled_automation_ids?.length || 0;
if (flow.draft) return { key: 'flow.executionDraft', className: 'draft' };
if (schedules > 0 && automations > 0) return { key: 'flow.executionMixed', className: 'mixed' };
if (schedules > 0) return { key: 'flow.executionSchedule', className: 'schedule' };
if (automations > 0) return { key: 'flow.executionAutomation', className: 'automation' };
return { key: 'flow.executionNone', className: 'none' };
}
function renderFlows() {
const host = $('#flowList'); if (!host) return;
const count = $('#flowListCount'); if (count) count.textContent = tr('flow.listCount', { count: app.flows.length });
host.innerHTML = app.flows.length ? app.flows.map(flow => `<article class="list-card flow-card ${flow.enabled && !flow.draft ? '' : 'is-disabled'} ${flow.draft ? 'is-draft' : ''}" data-flow-card-id="${esc(flow.id)}" tabindex="0" aria-label="${esc(`${tr('flow.openEditor')}: ${flow.name}`)}">
<div class="list-card-head"><div class="flow-card-copy"><h3>${esc(flow.name)}${flow.draft ? ` <span class="badge flow-draft-badge">${esc(tr('flow.draft'))}</span>` : ''}</h3><div class="flow-card-description"><p>${esc(flow.description || tr('flow.defaultDescription'))}</p><button type="button" class="flow-description-edit" data-action="edit-flow-description" data-id="${esc(flow.id)}" aria-label="${esc(tr('flow.editDescription'))}" title="${esc(tr('flow.editDescription'))}">${uiIcon('edit')}</button></div></div>${flow.draft ? `<button type="button" class="enable-toggle" disabled title="${esc(tr('flow.draftDisabledHint'))}"><span>${uiIcon('edit')}</span>${esc(tr('flow.disabledState'))}</button>` : `<button type="button" class="enable-toggle ${flow.enabled ? 'active' : ''}" data-action="toggle-flow-enabled" data-id="${esc(flow.id)}" data-value="${flow.enabled ? 'false' : 'true'}" aria-label="${esc(tr(flow.enabled ? 'flow.disable' : 'flow.enable'))}" title="${esc(tr('flow.quickToggleHint'))}"><span>${uiIcon(flow.enabled ? 'check' : 'circle')}</span>${esc(flow.enabled ? tr('flow.enabledState') : tr('flow.disabledState'))}</button>`}</div>
<div class="card-stats"><div class="card-stat"><small>${esc(tr('flow.blocks'))}</small><strong>${flow.nodes?.length || 0}</strong></div><div class="card-stat"><small>${esc(tr('nav.schedules'))}</small><strong>${flow.compiled_schedule_ids?.length || 0}</strong></div><div class="card-stat"><small>${esc(tr('nav.automations'))}</small><strong>${flow.compiled_automation_ids?.length || 0}</strong></div></div>
<div class="card-footer"><small>${esc(flow.draft ? tr('flow.draftNoExecution') : tr('flow.compileCount', { schedules: flow.compiled_schedule_ids?.length || 0, automations: flow.compiled_automation_ids?.length || 0 }))}</small><div class="card-menu"><button class="flow-simulate-button" data-action="simulate-flow" data-id="${esc(flow.id)}" ${flow.draft ? `disabled title="${esc(tr('flow.simulatorDraftHint'))}"` : ''}>${esc(tr('flow.simpleSimulator'))}</button><button class="card-primary-action" data-action="edit-flow" data-id="${esc(flow.id)}">${esc(tr('flow.openEditor'))}</button><details class="card-overflow"><summary aria-label="${esc(tr('actions.more'))}" title="${esc(tr('actions.more'))}">${uiIcon('more-horizontal')}</summary><div class="card-overflow-menu"><button data-action="export-flow-by-id" data-id="${esc(flow.id)}">${esc(tr('flow.export'))}</button><button class="danger" data-action="delete-flow" data-id="${esc(flow.id)}">${esc(tr('actions.delete'))}</button></div></details></div></div>
</article>`).join('') : `<div class="empty"><strong>${esc(tr('flow.emptyTitle'))}</strong>${esc(tr('flow.emptyText'))}</div>`;
host.innerHTML = app.flows.length ? app.flows.map(flow => {
const schedules = flow.compiled_schedule_ids?.length || 0;
const automations = flow.compiled_automation_ids?.length || 0;
const execution = flowExecutionType(flow);
return `<article class="list-card flow-card ${flow.enabled && !flow.draft ? '' : 'is-disabled'} ${flow.draft ? 'is-draft' : ''}">
<div class="list-card-head"><div class="flow-card-copy"><h3>${esc(flow.name)}${flow.draft ? ` <span class="badge flow-draft-badge">${esc(tr('flow.draft'))}</span>` : ''}</h3><div class="flow-card-description"><p>${esc(flow.description || tr('flow.defaultDescription'))}</p><button type="button" class="flow-description-edit" data-action="edit-flow-description" data-id="${esc(flow.id)}" aria-label="${esc(tr('flow.editDescription'))}" title="${esc(tr('flow.editDescription'))}">${uiIcon('edit')}</button></div></div>${flow.draft ? `<button type="button" class="enable-toggle" disabled title="${esc(tr('flow.draftDisabledHint'))}"><span>${uiIcon('edit')}</span>${esc(tr('flow.disabledState'))}</button>` : `<button type="button" class="enable-toggle ${flow.enabled ? 'active' : ''}" data-action="toggle-flow-enabled" data-id="${esc(flow.id)}" data-value="${flow.enabled ? 'false' : 'true'}" aria-label="${esc(tr(flow.enabled ? 'flow.disable' : 'flow.enable'))}" title="${esc(tr('flow.quickToggleHint'))}"><span>${uiIcon(flow.enabled ? 'check' : 'circle')}</span>${esc(flow.enabled ? tr('flow.enabledState') : tr('flow.disabledState'))}</button>`}</div>
<div class="card-stats"><div class="card-stat"><small>${esc(tr('flow.blocks'))}</small><strong>${flow.nodes?.length || 0}</strong></div><div class="card-stat"><small>${esc(tr('nav.schedules'))}</small><strong>${schedules}</strong></div><div class="card-stat"><small>${esc(tr('nav.automations'))}</small><strong>${automations}</strong></div></div>
<div class="card-footer"><div class="flow-runtime-summary"><div class="flow-execution-type flow-execution-${esc(execution.className)}"><small>${esc(tr('flow.executionType'))}</small><strong>${esc(tr(execution.key))}</strong></div><small class="flow-compile-count">${esc(flow.draft ? tr('flow.draftNoExecution') : tr('flow.compileCount', { schedules, automations }))}</small></div><div class="card-menu"><button class="flow-card-action-button" data-action="simulate-flow" data-id="${esc(flow.id)}" ${flow.draft ? `disabled title="${esc(tr('flow.simulatorDraftHint'))}"` : ''}>${esc(tr('flow.simpleSimulator'))}</button><button class="flow-card-action-button" data-action="edit-flow" data-id="${esc(flow.id)}">${esc(tr('flow.editorButton'))}</button><details class="card-overflow"><summary aria-label="${esc(tr('actions.more'))}" title="${esc(tr('actions.more'))}">${uiIcon('more-horizontal')}</summary><div class="card-overflow-menu"><button data-action="export-flow-by-id" data-id="${esc(flow.id)}">${esc(tr('flow.export'))}</button><button class="danger" data-action="delete-flow" data-id="${esc(flow.id)}">${esc(tr('actions.delete'))}</button></div></details></div></div>
</article>`;
}).join('') : `<div class="empty"><strong>${esc(tr('flow.emptyTitle'))}</strong>${esc(tr('flow.emptyText'))}</div>`;
}
let flowDescriptionTargetId = null;
@@ -1801,8 +1816,6 @@ document.addEventListener('contextmenu', event => {
});
document.addEventListener('keydown', event => {
const flowCard = event.target.closest?.('[data-flow-card-id]');
if (flowCard && event.target === flowCard && (event.key === 'Enter' || event.key === ' ')) { event.preventDefault(); openFlowEditor(flowCard.dataset.flowCardId); return; }
if ($('#flowEditor')?.hidden || !app.flowDraft) return;
if (event.target.closest?.('input,select,textarea,[contenteditable="true"]')) return;
if (event.target.closest?.('dialog[open]') || document.querySelector('dialog[open]')) return;
@@ -1856,8 +1869,6 @@ document.addEventListener('click', event => {
return;
}
if (!event.target.closest?.('#flowContextMenu')) hideFlowContextMenu();
const flowCard = event.target.closest?.('[data-flow-card-id]');
if (flowCard && !event.target.closest?.('button,summary,details,input,select,textarea,a,label')) { openFlowEditor(flowCard.dataset.flowCardId); return; }
const edge = event.target.closest?.('[data-flow-edge]'); if (edge) { removeFlowEdge(edge.dataset.flowEdge); return; }
const add = event.target.closest?.('[data-flow-add]'); if (add) { const dialog = add.closest?.('#flowBlockDialog'); if (!dialog) flowBlockInsertPoint = null; addFlowNode(add.dataset.flowAdd); if (dialog?.open) dialog.close(); return; }
const templateCategory = event.target.closest?.('[data-flow-template-category]'); if (templateCategory) { renderFlowPresetBrowser(templateCategory.dataset.flowTemplateCategory); return; }