mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
88 lines
2.6 KiB
TypeScript
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’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>
|
|
);
|
|
}
|