Files
astrolabe/src/app/components/ExtractModal.tsx
T

88 lines
2.6 KiB
TypeScript

/**
* Extract-to-Dataset — the modal body (spec §03F).
*
* Shows a read-only preview of the focused view's embedded data and asks
* for a dataset name. On confirm it saves the data as a new dataset and rewrites
* the draft to reference it by name (logic in ExtractStore), then force-closes
* (the commit is the user's confirmation, so no discard prompt). Cancel leaves
* the spec unchanged.
*/
import { useShallow } from 'zustand/react/shallow';
import { closeModal } from '../modals/ModalCoordinator';
import { useExtractStore } from '../stores/ExtractStore';
import { Button } from './Button';
import styles from './ExtractModal.module.css';
const MAX_PREVIEW = 1500;
function previewOf(values: unknown): string {
let text: string;
try {
text = typeof values === 'string' ? values : JSON.stringify(values, null, 2);
} catch {
text = String(values);
}
return text.length > MAX_PREVIEW ? `${text.slice(0, MAX_PREVIEW)}\n…` : text;
}
export function ExtractModal() {
const { name, source, error } = useExtractStore(
useShallow((s) => ({ name: s.name, source: s.source, error: s.error })),
);
const setName = useExtractStore((s) => s.setName);
const runConfirm = useExtractStore((s) => s.confirm);
const handleCreate = () => {
if (runConfirm()) void closeModal(true);
};
if (!source) {
return <p className={styles.muted}>This snippet has no embedded data to extract.</p>;
}
return (
<div className={styles.extract}>
<p className={styles.intro}>
Save this snippet&rsquo;s embedded data as a reusable dataset. The spec will be rewritten to
reference it by name.
</p>
<label className={styles.field}>
<span className={styles.label}>Dataset name</span>
<input
type="text"
className={styles.input}
value={name}
onChange={(e) => setName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleCreate();
}}
placeholder="e.g. Sales 2024"
autoFocus
/>
</label>
<div className={styles.field}>
<span className={styles.label}>Data to extract ({source.format.toUpperCase()})</span>
<pre className={styles.preview}>{previewOf(source.values)}</pre>
</div>
{error && (
<p className={styles.error} role="alert">
{error}
</p>
)}
<div className={styles.actions}>
<Button size="lg" onClick={() => void closeModal()}>
Cancel
</Button>
<Button variant="primary" size="lg" onClick={handleCreate}>
Create dataset
</Button>
</div>
</div>
);
}