ui-dev-content/dist/components/TransformerModal.js
autocommit 8b284e01b9 chore: initial package split from monorepo
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
2026-04-20 01:11:45 -07:00

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" }) })] })) }));
}