mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add distributed settings and workspace import/export (M5)
This commit is contained in:
+47
-4
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { ConfirmDialog } from './components/ConfirmDialog';
|
||||
import { LivePreview } from './components/LivePreview';
|
||||
import { ModalShell } from './components/ModalShell';
|
||||
@@ -8,8 +8,10 @@ import { SpecEditor } from './components/SpecEditor';
|
||||
import { ThemeToggle } from './components/ThemeToggle';
|
||||
import { Toaster } from './components/Toaster';
|
||||
import { openModal, setConfirm, toggleDatasets } from './modals/ModalCoordinator';
|
||||
import { openSettingsPopover } from './stores/SettingsPopoverStore';
|
||||
import { confirm } from './stores/ConfirmStore';
|
||||
import { usePanesStore } from './stores/PanesStore';
|
||||
import { exportWorkspace, importWorkspace } from './services/transfer';
|
||||
import styles from './App.module.css';
|
||||
|
||||
/**
|
||||
@@ -24,6 +26,9 @@ import styles from './App.module.css';
|
||||
export function App() {
|
||||
const libraryWidth = usePanesStore((s) => s.libraryWidth);
|
||||
const previewWidth = usePanesStore((s) => s.previewWidth);
|
||||
// Hidden file input driving Import — the header button proxies its click so the
|
||||
// browser file picker is the only chrome (spec §08 → no intermediate dialog).
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Route the modal coordinator's discard prompt through the in-app confirm
|
||||
// dialog (docs/architecture/03 → "The coordinator seam").
|
||||
@@ -31,19 +36,31 @@ export function App() {
|
||||
setConfirm((message) => confirm({ title: 'Discard changes?', message, danger: true }));
|
||||
}, []);
|
||||
|
||||
// Cmd/Ctrl+K toggles the Datasets manager (spec §05 → Opening). The full
|
||||
// keyboard router (other shortcuts) lands in M6 (docs/architecture/04).
|
||||
// Cmd/Ctrl+K toggles the Datasets manager (spec §05); Cmd/Ctrl+, opens the
|
||||
// editor settings popover — the primary preference cluster, now that settings
|
||||
// are distributed per pane (spec §07). Full key router lands in M6 (arch 04).
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K')) {
|
||||
if (!(e.metaKey || e.ctrlKey)) return;
|
||||
if (e.key === 'k' || e.key === 'K') {
|
||||
e.preventDefault();
|
||||
toggleDatasets();
|
||||
} else if (e.key === ',') {
|
||||
e.preventDefault();
|
||||
openSettingsPopover('editor-settings');
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, []);
|
||||
|
||||
const handleImportFile = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
// Reset the input so picking the same file again still fires onChange.
|
||||
e.target.value = '';
|
||||
if (file) void importWorkspace(file);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.app}>
|
||||
{/* Skip link (WCAG 2.4.1 / GOV.UK): the first focusable element, hidden
|
||||
@@ -64,7 +81,33 @@ export function App() {
|
||||
>
|
||||
Datasets
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.headerButton}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
title="Import a workspace JSON file"
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.headerButton}
|
||||
onClick={() => exportWorkspace()}
|
||||
title="Export your workspace to a JSON file"
|
||||
>
|
||||
Export
|
||||
</button>
|
||||
<ThemeToggle />
|
||||
{/* Hidden picker for Import; restricted to JSON (spec §08). */}
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept="application/json,.json"
|
||||
className={styles.hiddenInput}
|
||||
onChange={handleImportFile}
|
||||
aria-hidden="true"
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</header>
|
||||
|
||||
{/* tabIndex -1 makes the landmark a focus target for the skip link. */}
|
||||
|
||||
Reference in New Issue
Block a user