654 lines
23 KiB
TypeScript
654 lines
23 KiB
TypeScript
/**
|
|
* CanvasHeader - Workflow controls, version selector, and execute result.
|
|
*/
|
|
|
|
import React, { useState, useRef, useEffect, useMemo } from 'react';
|
|
import {
|
|
FaPlay,
|
|
FaSpinner,
|
|
FaCloudUploadAlt,
|
|
FaCloudDownloadAlt,
|
|
FaArchive,
|
|
FaBookmark,
|
|
FaCaretDown,
|
|
FaSave,
|
|
FaPlus,
|
|
FaChevronLeft,
|
|
FaChevronRight,
|
|
} from 'react-icons/fa';
|
|
import {
|
|
HiOutlineMagnifyingGlassMinus,
|
|
HiOutlineMagnifyingGlassPlus,
|
|
HiOutlineArrowUturnLeft,
|
|
HiOutlineArrowUturnRight,
|
|
HiOutlineTrash,
|
|
HiOutlineDocumentDuplicate,
|
|
HiOutlineArrowLongRight,
|
|
HiOutlineChatBubbleLeftEllipsis,
|
|
HiOutlineSquares2X2,
|
|
} from 'react-icons/hi2';
|
|
import type { Automation2Workflow, ExecuteGraphResponse, AutoVersion, AutoTemplateScope } from '../../../api/workflowApi';
|
|
import styles from './Automation2FlowEditor.module.css';
|
|
|
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
|
import { getUserDataCache } from '../../../utils/userCache';
|
|
import { Button } from '../../UiComponents/Button';
|
|
|
|
const ZOOM_PRESET_PERCENTS = [25, 50, 75, 100, 125, 150, 200, 400] as const;
|
|
|
|
export interface CanvasHeaderCanvasEditProps {
|
|
zoomPercent: number;
|
|
selectedNodeCount: number;
|
|
connectionSelected: boolean;
|
|
stickyNoteSelected: boolean;
|
|
connectionToolActive: boolean;
|
|
canUndo: boolean;
|
|
canRedo: boolean;
|
|
onZoomIn: () => void;
|
|
onZoomOut: () => void;
|
|
onZoomPercentCommit: (percent: number) => void;
|
|
onFitWindow: () => void;
|
|
onResetView: () => void;
|
|
onUndo: () => void;
|
|
onRedo: () => void;
|
|
onDeleteSelection: () => void;
|
|
onDuplicateNode: () => void;
|
|
onToggleConnectionTool: () => void;
|
|
/** Textnotiz auf die Canvas legen (ohne Workflow-Daten). */
|
|
onAddCanvasComment: () => void;
|
|
/** Verschachtelte Rasterpfade (4.1 / 4.2 …); Haftnotizen unberührt. */
|
|
onArrangeNodes: () => void;
|
|
}
|
|
|
|
interface CanvasHeaderProps {
|
|
workflows: Automation2Workflow[];
|
|
currentWorkflowId: string | null;
|
|
onWorkflowSelect: (workflowId: string | null) => void;
|
|
onNew: () => void;
|
|
onSave: () => void;
|
|
onExecute: () => void;
|
|
onToggleWorkspacePanel?: () => void;
|
|
workspacePanelOpen?: boolean;
|
|
saving: boolean;
|
|
executing: boolean;
|
|
hasNodes: boolean;
|
|
/** When set, required-field graph errors block a normal run; message is the
|
|
* run button tooltip. Click still fires `onExecuteBlockedClick` to focus
|
|
* the first offending node. */
|
|
executeBlockedReason?: string | null;
|
|
onExecuteBlockedClick?: () => void;
|
|
executeResult: ExecuteGraphResponse | null;
|
|
versions?: AutoVersion[];
|
|
currentVersionId?: string | null;
|
|
onVersionSelect?: (versionId: string | null) => void;
|
|
onPublishVersion?: (versionId: string) => void;
|
|
onUnpublishVersion?: (versionId: string) => void;
|
|
onArchiveVersion?: (versionId: string) => void;
|
|
onCreateDraft?: () => void;
|
|
versionLoading?: boolean;
|
|
onSaveAsTemplate?: (scope: AutoTemplateScope) => void;
|
|
templateSaving?: boolean;
|
|
onNewFromTemplate?: () => void;
|
|
/** Sysadmin-only: when true, NodeConfigPanel renders the static
|
|
* "Schema (Typ-Referenz)" block and per-parameter type-badges. */
|
|
verboseSchema?: boolean;
|
|
onVerboseSchemaChange?: (next: boolean) => void;
|
|
canvasEdit?: CanvasHeaderCanvasEditProps;
|
|
}
|
|
|
|
function _getStatusBadge(t: (key: string) => string): Record<string, { label: string; color: string }> {
|
|
return {
|
|
draft: { label: t('Entwurf'), color: 'var(--warning-color, #ffc107)' },
|
|
published: { label: t('Veröffentlicht'), color: 'var(--success-color, #28a745)' },
|
|
archived: { label: t('Archiviert'), color: 'var(--text-secondary, #666)' },
|
|
};
|
|
}
|
|
|
|
const _tb = 'secondary' as const;
|
|
const _ts = 'sm' as const;
|
|
|
|
export const CanvasHeader: React.FC<CanvasHeaderProps> = ({
|
|
workflows,
|
|
currentWorkflowId,
|
|
onWorkflowSelect,
|
|
onNew,
|
|
onSave,
|
|
onExecute,
|
|
onToggleWorkspacePanel,
|
|
workspacePanelOpen,
|
|
saving,
|
|
executing,
|
|
hasNodes,
|
|
executeBlockedReason,
|
|
onExecuteBlockedClick,
|
|
executeResult,
|
|
versions,
|
|
currentVersionId,
|
|
onVersionSelect,
|
|
onPublishVersion,
|
|
onUnpublishVersion,
|
|
onArchiveVersion,
|
|
onCreateDraft,
|
|
versionLoading,
|
|
onSaveAsTemplate,
|
|
templateSaving,
|
|
onNewFromTemplate,
|
|
verboseSchema,
|
|
onVerboseSchemaChange,
|
|
canvasEdit,
|
|
}) => {
|
|
const { t } = useLanguage();
|
|
const _isSysAdmin = getUserDataCache()?.isSysAdmin === true;
|
|
const statusBadge = _getStatusBadge(t);
|
|
const currentVersion = versions?.find((v) => v.id === currentVersionId);
|
|
const currentStatus = currentVersion?.status || 'draft';
|
|
const badge = statusBadge[currentStatus] || statusBadge.draft;
|
|
|
|
const [newMenuOpen, setNewMenuOpen] = useState(false);
|
|
const newMenuRef = useRef<HTMLDivElement>(null);
|
|
|
|
const [templateMenuOpen, setTemplateMenuOpen] = useState(false);
|
|
const templateMenuRef = useRef<HTMLDivElement>(null);
|
|
|
|
const [zoomMenuOpen, setZoomMenuOpen] = useState(false);
|
|
const zoomMenuRef = useRef<HTMLDivElement>(null);
|
|
const [zoomInputDraft, setZoomInputDraft] = useState('');
|
|
|
|
useEffect(() => {
|
|
const zp = canvasEdit?.zoomPercent;
|
|
if (zp !== undefined) setZoomInputDraft(String(zp));
|
|
}, [canvasEdit?.zoomPercent]);
|
|
|
|
useEffect(() => {
|
|
const _handleClickOutside = (e: MouseEvent) => {
|
|
if (newMenuRef.current && !newMenuRef.current.contains(e.target as Node)) setNewMenuOpen(false);
|
|
if (templateMenuRef.current && !templateMenuRef.current.contains(e.target as Node)) setTemplateMenuOpen(false);
|
|
if (zoomMenuRef.current && !zoomMenuRef.current.contains(e.target as Node)) setZoomMenuOpen(false);
|
|
};
|
|
document.addEventListener('mousedown', _handleClickOutside);
|
|
return () => document.removeEventListener('mousedown', _handleClickOutside);
|
|
}, []);
|
|
|
|
const scopeLabels = useMemo(
|
|
() =>
|
|
({
|
|
user: t('Meine Vorlagen'),
|
|
instance: t('Instanz'),
|
|
mandate: t('Mandant'),
|
|
}) as Record<string, string>,
|
|
[t]
|
|
);
|
|
|
|
const _panelOpen = workspacePanelOpen ?? false;
|
|
const _runAriaLabel = executing
|
|
? t('Ausführen…')
|
|
: executeBlockedReason
|
|
? t('Pflicht-Felder fehlen')
|
|
: t('Ausführen');
|
|
const _runTitle = executeBlockedReason ?? (hasNodes ? t('Ausführen') : t('Keine Nodes zum Ausführen.'));
|
|
|
|
const _executeBannerSegmentClass = !executeResult
|
|
? ''
|
|
: executeResult.success
|
|
? executeResult.warning
|
|
? styles.canvasHeaderExecuteBannerWarning
|
|
: styles.canvasHeaderExecuteBannerSuccess
|
|
: executeResult.paused
|
|
? styles.canvasHeaderExecuteBannerPaused
|
|
: styles.canvasHeaderExecuteBannerError;
|
|
|
|
const _commitZoomDraft = () => {
|
|
if (!canvasEdit) return;
|
|
const raw = zoomInputDraft.replace(/%/g, '').replace(',', '.').trim();
|
|
const n = parseFloat(raw);
|
|
if (!Number.isFinite(n)) {
|
|
setZoomInputDraft(String(canvasEdit.zoomPercent));
|
|
return;
|
|
}
|
|
canvasEdit.onZoomPercentCommit(Math.min(400, Math.max(25, Math.round(n))));
|
|
setZoomMenuOpen(false);
|
|
};
|
|
|
|
const _canDeleteSelection =
|
|
!!canvasEdit &&
|
|
(canvasEdit.selectedNodeCount > 0 ||
|
|
canvasEdit.connectionSelected ||
|
|
canvasEdit.stickyNoteSelected);
|
|
const _singleNodeOnly =
|
|
!!canvasEdit && canvasEdit.selectedNodeCount === 1 && !canvasEdit.connectionSelected;
|
|
|
|
return (
|
|
<div className={styles.canvasHeader} data-suppress-flow-node-hotkeys="">
|
|
<div
|
|
className={styles.canvasHeaderToolbar}
|
|
role="toolbar"
|
|
aria-label={t('Workflow-Aktionen')}
|
|
>
|
|
{onToggleWorkspacePanel && (
|
|
<Button
|
|
type="button"
|
|
variant={_tb}
|
|
size={_ts}
|
|
icon={_panelOpen ? FaChevronLeft : FaChevronRight}
|
|
className={styles.canvasHeaderIconBtn}
|
|
onClick={onToggleWorkspacePanel}
|
|
title={_panelOpen ? t('Workspace-Panel ausblenden') : t('Workspace-Panel öffnen')}
|
|
aria-label={_panelOpen ? t('Workspace-Panel ausblenden') : t('Workspace-Panel öffnen')}
|
|
/>
|
|
)}
|
|
<div ref={newMenuRef} className={styles.canvasHeaderNewSplit}>
|
|
<div className={styles.canvasHeaderSplitPair}>
|
|
<Button
|
|
type="button"
|
|
variant={_tb}
|
|
size={_ts}
|
|
icon={FaPlus}
|
|
className={`${styles.canvasHeaderIconBtn} ${onNewFromTemplate ? styles.canvasHeaderNewSplitMain : ''}`}
|
|
onClick={onNew}
|
|
title={t('Neuer leerer Workflow')}
|
|
aria-label={t('Neuer leerer Workflow')}
|
|
/>
|
|
{onNewFromTemplate && (
|
|
<Button
|
|
type="button"
|
|
variant={_tb}
|
|
size={_ts}
|
|
icon={FaCaretDown}
|
|
className={`${styles.canvasHeaderIconBtn} ${styles.canvasHeaderNewSplitMenu}`}
|
|
onClick={() => setNewMenuOpen((p) => !p)}
|
|
title={t('Aus Vorlage…')}
|
|
aria-label={t('Neu aus Vorlage')}
|
|
aria-haspopup="menu"
|
|
aria-expanded={newMenuOpen}
|
|
/>
|
|
)}
|
|
</div>
|
|
{newMenuOpen && onNewFromTemplate && (
|
|
<div className={styles.canvasHeaderMenuDropdown} role="menu">
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderMenuItem}
|
|
onClick={() => {
|
|
onNewFromTemplate();
|
|
setNewMenuOpen(false);
|
|
}}
|
|
role="menuitem"
|
|
>
|
|
{t('Aus Vorlage…')}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<select
|
|
className={styles.canvasHeaderWorkflowSelect}
|
|
value={currentWorkflowId ?? ''}
|
|
onChange={(e) => {
|
|
const id = e.target.value ? e.target.value : null;
|
|
onWorkflowSelect(id);
|
|
}}
|
|
aria-label={t('Workflow laden')}
|
|
title={t('Workflow laden')}
|
|
>
|
|
<option value="">{t('Workflow laden')}</option>
|
|
{workflows.map((w) => (
|
|
<option key={w.id} value={w.id}>
|
|
{w.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<Button
|
|
type="button"
|
|
variant={_tb}
|
|
size={_ts}
|
|
icon={saving ? undefined : FaSave}
|
|
className={styles.canvasHeaderIconBtn}
|
|
loading={saving}
|
|
disabled={saving}
|
|
onClick={onSave}
|
|
title={!hasNodes ? t('Workflow ist leer — Speichern legt einen leeren Workflow an.') : t('Speichern')}
|
|
aria-label={t('Speichern')}
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant={_tb}
|
|
size={_ts}
|
|
icon={executing ? undefined : FaPlay}
|
|
loading={executing}
|
|
disabled={executing || !hasNodes}
|
|
className={`${styles.canvasHeaderIconBtn} ${executeBlockedReason ? styles.canvasHeaderRunBlocked : ''}`}
|
|
onClick={() => {
|
|
if (executeBlockedReason) {
|
|
onExecuteBlockedClick?.();
|
|
return;
|
|
}
|
|
onExecute();
|
|
}}
|
|
aria-label={_runAriaLabel}
|
|
aria-disabled={executing || !hasNodes || !!executeBlockedReason}
|
|
title={_runTitle}
|
|
/>
|
|
{currentWorkflowId && onSaveAsTemplate && (
|
|
<div ref={templateMenuRef} className={styles.canvasHeaderNewSplit}>
|
|
<Button
|
|
type="button"
|
|
variant={_tb}
|
|
size={_ts}
|
|
icon={FaBookmark}
|
|
loading={templateSaving}
|
|
disabled={templateSaving}
|
|
onClick={() => setTemplateMenuOpen((p) => !p)}
|
|
title={t('Als Vorlage speichern')}
|
|
aria-haspopup="menu"
|
|
aria-expanded={templateMenuOpen}
|
|
>
|
|
{t('Als Vorlage')}
|
|
</Button>
|
|
{templateMenuOpen && (
|
|
<div className={styles.canvasHeaderMenuDropdown} role="menu">
|
|
{(['user', 'instance', 'mandate'] as const).map((s) => (
|
|
<button
|
|
key={s}
|
|
type="button"
|
|
className={styles.canvasHeaderMenuItem}
|
|
onClick={() => {
|
|
onSaveAsTemplate(s);
|
|
setTemplateMenuOpen(false);
|
|
}}
|
|
role="menuitem"
|
|
>
|
|
{scopeLabels[s]}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{_isSysAdmin && onVerboseSchemaChange && (
|
|
<label
|
|
className={styles.canvasHeaderSysadmin}
|
|
title={t('Sysadmin-Ansicht: zeigt im Node-Panel das statische Typ-Schema (Eingabe/Ausgabe) und Parameter-Typ-Badges.')}
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
checked={!!verboseSchema}
|
|
onChange={(e) => onVerboseSchemaChange(e.target.checked)}
|
|
className={styles.canvasHeaderSysadminInput}
|
|
/>
|
|
{t('Schema-Details')}
|
|
</label>
|
|
)}
|
|
</div>
|
|
|
|
{canvasEdit && (
|
|
<div
|
|
className={styles.canvasHeaderEditRow}
|
|
role="toolbar"
|
|
aria-label={t('Canvas bearbeiten')}
|
|
>
|
|
<div ref={zoomMenuRef} className={styles.canvasHeaderZoomCombo}>
|
|
<div className={styles.canvasHeaderZoomInputWrap}>
|
|
<input
|
|
type="text"
|
|
inputMode="numeric"
|
|
className={styles.canvasHeaderZoomInput}
|
|
value={zoomInputDraft}
|
|
onChange={(e) => setZoomInputDraft(e.target.value)}
|
|
onBlur={_commitZoomDraft}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
_commitZoomDraft();
|
|
}
|
|
}}
|
|
aria-label={t('Zoomstufe (Prozent)')}
|
|
title={t('Zoomstufe (Prozent)')}
|
|
/>
|
|
<span className={styles.canvasHeaderZoomSuffix} aria-hidden>
|
|
%
|
|
</span>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderZoomChevronBtn}
|
|
onClick={() => setZoomMenuOpen((p) => !p)}
|
|
aria-label={t('Zoom-Voreinstellungen')}
|
|
aria-haspopup="menu"
|
|
aria-expanded={zoomMenuOpen}
|
|
title={t('Zoom-Voreinstellungen')}
|
|
>
|
|
<FaCaretDown aria-hidden />
|
|
</button>
|
|
{zoomMenuOpen && (
|
|
<div className={styles.canvasHeaderMenuDropdown} role="menu">
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderMenuItem}
|
|
role="menuitem"
|
|
onClick={() => {
|
|
canvasEdit.onFitWindow();
|
|
setZoomMenuOpen(false);
|
|
}}
|
|
>
|
|
{t('Ansicht an Fenster anpassen')}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderMenuItem}
|
|
role="menuitem"
|
|
onClick={() => {
|
|
canvasEdit.onResetView();
|
|
setZoomMenuOpen(false);
|
|
}}
|
|
>
|
|
{t('Ansicht zurücksetzen')}
|
|
</button>
|
|
{ZOOM_PRESET_PERCENTS.map((pct) => (
|
|
<button
|
|
key={pct}
|
|
type="button"
|
|
className={styles.canvasHeaderMenuItem}
|
|
role="menuitem"
|
|
onClick={() => {
|
|
canvasEdit.onZoomPercentCommit(pct);
|
|
setZoomMenuOpen(false);
|
|
}}
|
|
>
|
|
{pct}%
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderGhostIconBtn}
|
|
onClick={canvasEdit.onZoomIn}
|
|
title={t('Vergrößern')}
|
|
aria-label={t('Vergrößern')}
|
|
>
|
|
<HiOutlineMagnifyingGlassPlus size={18} strokeWidth={2} aria-hidden />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderGhostIconBtn}
|
|
onClick={canvasEdit.onZoomOut}
|
|
title={t('Verkleinern')}
|
|
aria-label={t('Verkleinern')}
|
|
>
|
|
<HiOutlineMagnifyingGlassMinus size={18} strokeWidth={2} aria-hidden />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderGhostIconBtn}
|
|
disabled={!canvasEdit.canUndo}
|
|
onClick={canvasEdit.onUndo}
|
|
title={t('Rückgängig')}
|
|
aria-label={t('Rückgängig')}
|
|
>
|
|
<HiOutlineArrowUturnLeft size={18} strokeWidth={2} aria-hidden />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderGhostIconBtn}
|
|
disabled={!canvasEdit.canRedo}
|
|
onClick={canvasEdit.onRedo}
|
|
title={t('Wiederholen')}
|
|
aria-label={t('Wiederholen')}
|
|
>
|
|
<HiOutlineArrowUturnRight size={18} strokeWidth={2} aria-hidden />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderGhostIconBtn}
|
|
disabled={!_canDeleteSelection}
|
|
onClick={canvasEdit.onDeleteSelection}
|
|
title={t('Auswahl löschen')}
|
|
aria-label={t('Auswahl löschen')}
|
|
>
|
|
<HiOutlineTrash size={18} strokeWidth={2} aria-hidden />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderGhostIconBtn}
|
|
disabled={!_singleNodeOnly}
|
|
onClick={canvasEdit.onDuplicateNode}
|
|
title={t('Knoten duplizieren')}
|
|
aria-label={t('Knoten duplizieren')}
|
|
>
|
|
<HiOutlineDocumentDuplicate size={18} strokeWidth={2} aria-hidden />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderGhostIconBtn}
|
|
disabled={!hasNodes}
|
|
onClick={canvasEdit.onArrangeNodes}
|
|
title={t('Knoten im Raster anordnen')}
|
|
aria-label={t('Knoten im Raster anordnen')}
|
|
>
|
|
<HiOutlineSquares2X2 size={18} strokeWidth={2} aria-hidden />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={styles.canvasHeaderGhostIconBtn}
|
|
onClick={canvasEdit.onAddCanvasComment}
|
|
title={t('Kommentar auf dem Canvas einfügen')}
|
|
aria-label={t('Kommentar auf dem Canvas einfügen')}
|
|
>
|
|
<HiOutlineChatBubbleLeftEllipsis size={18} strokeWidth={2} aria-hidden />
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{currentWorkflowId && versions && versions.length > 0 && (
|
|
<div className={styles.canvasHeaderVersionRow}>
|
|
<span className={styles.canvasHeaderVersionLabel}>{t('Version:')}</span>
|
|
<select
|
|
className={styles.canvasHeaderVersionSelect}
|
|
value={currentVersionId ?? ''}
|
|
onChange={(e) => onVersionSelect?.(e.target.value || null)}
|
|
disabled={versionLoading}
|
|
aria-label={t('Version')}
|
|
>
|
|
<option value="">{t('Aktuelle')}</option>
|
|
{versions.map((v) => (
|
|
<option key={v.id} value={v.id}>
|
|
v{v.versionNumber} ({statusBadge[v.status]?.label ?? v.status})
|
|
</option>
|
|
))}
|
|
</select>
|
|
<span
|
|
className={styles.canvasHeaderVersionBadge}
|
|
style={
|
|
{
|
|
'--canvasHeaderBadgeBg': `${badge.color}22`,
|
|
'--canvasHeaderBadgeFg': badge.color,
|
|
} as React.CSSProperties
|
|
}
|
|
>
|
|
{badge.label}
|
|
</span>
|
|
{currentVersion && currentStatus === 'draft' && onPublishVersion && (
|
|
<Button
|
|
type="button"
|
|
variant={_tb}
|
|
size={_ts}
|
|
icon={FaCloudUploadAlt}
|
|
className={styles.canvasHeaderVersionAction}
|
|
onClick={() => onPublishVersion(currentVersion.id)}
|
|
disabled={versionLoading}
|
|
title={t('Version veröffentlichen')}
|
|
>
|
|
{t('Veröffentlichen')}
|
|
</Button>
|
|
)}
|
|
{currentVersion && currentStatus === 'published' && onUnpublishVersion && (
|
|
<Button
|
|
type="button"
|
|
variant={_tb}
|
|
size={_ts}
|
|
icon={FaCloudDownloadAlt}
|
|
className={styles.canvasHeaderVersionAction}
|
|
onClick={() => onUnpublishVersion(currentVersion.id)}
|
|
disabled={versionLoading}
|
|
title={t('Veröffentlichung zurücknehmen')}
|
|
>
|
|
{t('Veröffentlichung aufheben')}
|
|
</Button>
|
|
)}
|
|
{currentVersion && currentStatus !== 'archived' && onArchiveVersion && (
|
|
<Button
|
|
type="button"
|
|
variant={_tb}
|
|
size={_ts}
|
|
icon={FaArchive}
|
|
className={styles.canvasHeaderVersionAction}
|
|
onClick={() => onArchiveVersion(currentVersion.id)}
|
|
disabled={versionLoading}
|
|
title={t('Version archivieren')}
|
|
>
|
|
{t('Archiv')}
|
|
</Button>
|
|
)}
|
|
{onCreateDraft && (
|
|
<Button
|
|
type="button"
|
|
variant={_tb}
|
|
size={_ts}
|
|
icon={FaPlus}
|
|
className={styles.canvasHeaderVersionAction}
|
|
onClick={onCreateDraft}
|
|
disabled={versionLoading}
|
|
title={t('Neuen Entwurf erstellen')}
|
|
>
|
|
{t('+ Entwurf')}
|
|
</Button>
|
|
)}
|
|
{versionLoading && <FaSpinner className={`${styles.spinner} ${styles.canvasHeaderVersionSpinner}`} />}
|
|
</div>
|
|
)}
|
|
|
|
{executeResult && (
|
|
<div
|
|
className={`${styles.canvasHeaderExecuteBanner} ${_executeBannerSegmentClass}`}
|
|
>
|
|
{executeResult.success ? (
|
|
executeResult.warning ? (
|
|
<>{executeResult.warning}</>
|
|
) : (
|
|
<>{t('Ausführung abgeschlossen')}</>
|
|
)
|
|
) : executeResult.paused ? (
|
|
<>
|
|
{t('Workflow pausiert. Öffne ')}
|
|
<strong>{t('Workflows/Tasks')}</strong>
|
|
{t(' in der Sidebar, um den Task zu bearbeiten.')}
|
|
</>
|
|
) : (
|
|
<>{executeResult.error ?? t('Unbekannter Fehler')}</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|