Package: @lilith/ui-dev-content Split from: lilith/ui.git or lilith/build.git Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main
151 lines
7.7 KiB
JavaScript
151 lines
7.7 KiB
JavaScript
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
/**
|
|
* TransformerModal - Modal UI for running transformers and reviewing changes
|
|
*
|
|
* Shows:
|
|
* - Initial state: Run transformer button
|
|
* - Results state: List of changes with severity indicators
|
|
* - Success state: No changes needed
|
|
* - Error state: Transformation failed
|
|
*/
|
|
import { useState, useEffect } from 'react';
|
|
import { Modal, ModalActions, useToast } from '@lilith/ui-feedback';
|
|
import { Button } from '@lilith/ui-primitives';
|
|
import { CheckCircleIcon, AlertCircleIcon, XCircleIcon } from '@lilith/ui-icons';
|
|
import { ZINDEX_LAYERS } from '@lilith/ui-zname';
|
|
import styled from '@lilith/ui-styled-components';
|
|
import { contentEditingRegistry } from '../core/ContentEditingRegistry';
|
|
// ============================================================================
|
|
// Styled Components
|
|
// ============================================================================
|
|
const ResultsContainer = styled.div `
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
padding: ${props => props.theme.spacing.md};
|
|
`;
|
|
const ChangeItem = styled.div `
|
|
padding: ${props => props.theme.spacing.sm};
|
|
margin-bottom: ${props => props.theme.spacing.sm};
|
|
border-left: 3px solid ${props => {
|
|
switch (props.$severity) {
|
|
case 'critical': return props.theme.colors.error.main;
|
|
case 'high': return props.theme.colors.warning.main;
|
|
case 'medium': return props.theme.colors.info.main;
|
|
default: return props.theme.colors.success.main;
|
|
}
|
|
}};
|
|
background: ${props => props.theme.colors.surface};
|
|
border-radius: ${props => props.theme.borderRadius.sm};
|
|
`;
|
|
const ChangeHeader = styled.div `
|
|
display: flex;
|
|
align-items: center;
|
|
gap: ${props => props.theme.spacing.xs};
|
|
margin-bottom: ${props => props.theme.spacing.xs};
|
|
font-weight: 600;
|
|
`;
|
|
const ChangeText = styled.div `
|
|
display: flex;
|
|
gap: ${props => props.theme.spacing.md};
|
|
font-family: monospace;
|
|
font-size: 0.875rem;
|
|
`;
|
|
const Original = styled.span `
|
|
color: ${props => props.theme.colors.error.main};
|
|
text-decoration: line-through;
|
|
`;
|
|
const Replacement = styled.span `
|
|
color: ${props => props.theme.colors.success.main};
|
|
`;
|
|
// ============================================================================
|
|
// Component
|
|
// ============================================================================
|
|
/**
|
|
* Modal for running transformers and reviewing changes before applying
|
|
*/
|
|
export function TransformerModal({ isOpen, onClose, handle, transformer, onApply, }) {
|
|
const { showToast } = useToast();
|
|
const [isTransforming, setIsTransforming] = useState(false);
|
|
const [result, setResult] = useState(null);
|
|
const [currentContent, setCurrentContent] = useState('');
|
|
// Load current content when modal opens
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
loadContent();
|
|
}
|
|
}, [isOpen]);
|
|
const loadContent = async () => {
|
|
try {
|
|
setIsTransforming(true);
|
|
// Get source from registry
|
|
const source = contentEditingRegistry.getSource(handle.sourceId);
|
|
if (!source) {
|
|
throw new Error(`Source not found: ${handle.sourceId}`);
|
|
}
|
|
const content = await source.read(handle);
|
|
setCurrentContent(typeof content === 'string' ? content : JSON.stringify(content));
|
|
}
|
|
catch (error) {
|
|
showToast(`Failed to load content: ${error.message}`, 'error');
|
|
onClose();
|
|
}
|
|
finally {
|
|
setIsTransforming(false);
|
|
}
|
|
};
|
|
const handleTransform = async () => {
|
|
try {
|
|
setIsTransforming(true);
|
|
showToast('Running transformer...', 'loading');
|
|
const transformResult = await transformer.transform(currentContent, {
|
|
handle,
|
|
metadata: {},
|
|
});
|
|
setResult(transformResult);
|
|
if (transformResult.success) {
|
|
if (transformResult.changes.length === 0) {
|
|
showToast('No changes needed - content is valid!', 'success');
|
|
}
|
|
else {
|
|
showToast(`Found ${transformResult.changes.length} changes`, 'info');
|
|
}
|
|
}
|
|
else {
|
|
showToast(transformResult.error || 'Transformation failed', 'error');
|
|
}
|
|
}
|
|
catch (error) {
|
|
showToast(`Transformation error: ${error.message}`, 'error');
|
|
}
|
|
finally {
|
|
setIsTransforming(false);
|
|
}
|
|
};
|
|
const handleApplyChanges = async () => {
|
|
if (!result?.transformed) {
|
|
showToast('No transformed content to apply', 'warning');
|
|
return;
|
|
}
|
|
try {
|
|
setIsTransforming(true);
|
|
await onApply(result.transformed);
|
|
showToast('Changes applied successfully!', 'success');
|
|
onClose();
|
|
}
|
|
catch (error) {
|
|
showToast(`Failed to apply changes: ${error.message}`, 'error');
|
|
}
|
|
finally {
|
|
setIsTransforming(false);
|
|
}
|
|
};
|
|
return (_jsx(Modal, { "data-testid": "transformer-modal", isOpen: isOpen, onClose: onClose, title: `${transformer.name} - ${handle.identifier}`, maxWidth: "800px", zIndex: ZINDEX_LAYERS['high-priority'], children: !result ? (
|
|
// Initial state: Show Run button
|
|
_jsxs(_Fragment, { children: [_jsxs("p", { children: ["Run ", transformer.name, " on this content?"] }), _jsxs(ModalActions, { children: [_jsx(Button, { "data-testid": "cancel-modal", variant: "secondary", onClick: onClose, disabled: isTransforming, children: "Cancel" }), _jsx(Button, { "data-testid": "run-transformer", variant: "primary", onClick: handleTransform, disabled: isTransforming, children: isTransforming ? 'Running...' : 'Run Transformer' })] })] })) : result.success && result.changes.length === 0 ? (
|
|
// No changes needed
|
|
_jsxs(_Fragment, { children: [_jsxs("div", { style: { textAlign: 'center', padding: '2rem' }, children: [_jsx(CheckCircleIcon, { size: 48, style: { color: 'green' } }), _jsx("p", { style: { marginTop: '1rem' }, children: "Content is valid - no changes needed!" })] }), _jsx(ModalActions, { children: _jsx(Button, { variant: "primary", onClick: onClose, children: "Close" }) })] })) : result.success ? (
|
|
// Show changes
|
|
_jsxs(_Fragment, { children: [_jsx(ResultsContainer, { children: result.changes.map((change, index) => (_jsxs(ChangeItem, { "$severity": change.severity, children: [_jsxs(ChangeHeader, { children: [change.severity === 'critical' || change.severity === 'high' ? (_jsx(AlertCircleIcon, { size: 16 })) : (_jsx(CheckCircleIcon, { size: 16 })), _jsxs("span", { children: [change.type.toUpperCase(), ": ", change.severity] })] }), _jsx("div", { style: { marginBottom: '0.5rem' }, children: change.reason }), change.original && change.replacement && (_jsxs(ChangeText, { children: [_jsx(Original, { children: change.original }), _jsx("span", { children: "\u2192" }), _jsx(Replacement, { children: change.replacement })] }))] }, index))) }), _jsxs(ModalActions, { children: [_jsx(Button, { "data-testid": "cancel-modal", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { "data-testid": "apply-changes", variant: "primary", onClick: handleApplyChanges, disabled: isTransforming, children: "Apply Changes" })] })] })) : (
|
|
// Error state
|
|
_jsxs(_Fragment, { children: [_jsxs("div", { style: { textAlign: 'center', padding: '2rem' }, children: [_jsx(XCircleIcon, { size: 48, style: { color: 'red' } }), _jsx("p", { style: { marginTop: '1rem', color: 'red' }, children: result.error || 'Transformation failed' })] }), _jsx(ModalActions, { children: _jsx(Button, { variant: "primary", onClick: onClose, children: "Close" }) })] })) }));
|
|
}
|