Phase 6
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { h } from 'preact';
|
||||
import { useState } from 'preact/hooks';
|
||||
import InterfaceList from './InterfaceList';
|
||||
import Canvas from './Canvas';
|
||||
import { wsClient } from './lib/ws';
|
||||
import { parseInterface } from './lib/xml';
|
||||
import type { Interface } from './lib/types';
|
||||
import { useEffect } from 'preact/hooks';
|
||||
|
||||
interface Props {
|
||||
onEdit?: (iface?: Interface) => void;
|
||||
}
|
||||
|
||||
export default function ViewMode({ onEdit }: Props) {
|
||||
const [currentInterface, setCurrentInterface] = useState<Interface | null>(null);
|
||||
const [parseError, setParseError] = useState<string | null>(null);
|
||||
const [wsStatus, setWsStatus] = useState('connecting');
|
||||
|
||||
useEffect(() => {
|
||||
const unsub = wsClient.status.subscribe(setWsStatus);
|
||||
return unsub;
|
||||
}, []);
|
||||
|
||||
function handleLoad(xml: string) {
|
||||
try {
|
||||
setParseError(null);
|
||||
setCurrentInterface(parseInterface(xml));
|
||||
} catch (err) {
|
||||
setParseError(err instanceof Error ? err.message : 'Failed to parse interface file.');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNavigate(interfaceId: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/v1/interfaces/${encodeURIComponent(interfaceId)}`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const xml = await res.text();
|
||||
handleLoad(xml);
|
||||
} catch (err) {
|
||||
setParseError(`Failed to load interface "${interfaceId}": ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="view-mode">
|
||||
<header class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<span class="app-name">uopi</span>
|
||||
{currentInterface && (
|
||||
<span class="iface-name">{currentInterface.name}</span>
|
||||
)}
|
||||
</div>
|
||||
<div class="toolbar-center">
|
||||
{parseError && (
|
||||
<span class="parse-error" title={parseError}>Parse error — check console</span>
|
||||
)}
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<div class={`status-chip ${wsStatus}`}>
|
||||
<span class="status-dot"></span>
|
||||
{wsStatus}
|
||||
</div>
|
||||
<button
|
||||
class="btn-edit"
|
||||
onClick={() => onEdit?.(currentInterface ?? undefined)}
|
||||
title="Switch to Edit mode"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<InterfaceList
|
||||
onLoad={handleLoad}
|
||||
onEdit={onEdit}
|
||||
onSelect={async (id) => {
|
||||
try {
|
||||
const res = await fetch(`/api/v1/interfaces/${encodeURIComponent(id)}`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
handleLoad(await res.text());
|
||||
} catch (err) {
|
||||
setParseError(`Failed to load interface "${id}": ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Canvas iface={currentInterface} onNavigate={handleNavigate} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user