improv 1
This commit is contained in:
46
src/App.tsx
46
src/App.tsx
@@ -39,6 +39,7 @@ import {
|
||||
getDefaultStyle,
|
||||
isConnectionAllowed,
|
||||
} from './lib/nodeRegistry'
|
||||
import type { AppNode, AppEdge } from './lib/nodeTypes'
|
||||
|
||||
const SNAP_GRID: [number, number] = [15, 15]
|
||||
const snapToGrid = (x: number, y: number): { x: number; y: number } => ({
|
||||
@@ -46,7 +47,7 @@ const snapToGrid = (x: number, y: number): { x: number; y: number } => ({
|
||||
y: Math.round(y / SNAP_GRID[1]) * SNAP_GRID[1],
|
||||
})
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
const initialNodes: AppNode[] = [
|
||||
{
|
||||
id: 'cfg_001',
|
||||
position: { x: 50, y: 50 },
|
||||
@@ -63,11 +64,14 @@ const initialNodes: Node[] = [
|
||||
},
|
||||
]
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
const initialEdges: AppEdge[] = [
|
||||
{ id: 'e-cfg_001-rnd_001', source: 'cfg_001', target: 'rnd_001', type: 'animated' },
|
||||
]
|
||||
|
||||
const PROJECT_FILE_EXT = '.zui.json'
|
||||
const PROJECT_VERSION = 1
|
||||
|
||||
export type ProjectMessage = { type: 'success' | 'error'; text: string }
|
||||
|
||||
export default function App() {
|
||||
const { theme } = useTheme()
|
||||
@@ -91,6 +95,7 @@ export default function App() {
|
||||
const [rfInstance, setRfInstance] = React.useState<any | null>(null)
|
||||
const [renamingNodeId, setRenamingNodeId] = React.useState<string | null>(null)
|
||||
const [connectionFrom, setConnectionFrom] = React.useState<{ nodeId: string; sourceHandle?: string } | null>(null)
|
||||
const [projectMessage, setProjectMessage] = React.useState<ProjectMessage | null>(null)
|
||||
|
||||
const wrapperRef = React.useRef<HTMLDivElement | null>(null)
|
||||
const lastClickRef = React.useRef<{ clientX: number; clientY: number } | null>(null)
|
||||
@@ -184,6 +189,12 @@ export default function App() {
|
||||
setRfInstance(instance)
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!projectMessage) return
|
||||
const t = setTimeout(() => setProjectMessage(null), 3000)
|
||||
return () => clearTimeout(t)
|
||||
}, [projectMessage])
|
||||
|
||||
const onNodeDragStart = useCallback(() => {
|
||||
saveForDragEnd()
|
||||
}, [saveForDragEnd])
|
||||
@@ -193,7 +204,7 @@ export default function App() {
|
||||
}, [commitDragEnd])
|
||||
|
||||
const handleExportProject = useCallback(() => {
|
||||
const state = { nodes, edges }
|
||||
const state = { version: PROJECT_VERSION, nodes, edges }
|
||||
const blob = new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
@@ -201,6 +212,7 @@ export default function App() {
|
||||
a.download = `project${PROJECT_FILE_EXT}`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
setProjectMessage({ type: 'success', text: 'Project exported' })
|
||||
}, [nodes, edges])
|
||||
|
||||
const handleImportProject = useCallback(() => {
|
||||
@@ -216,11 +228,19 @@ export default function App() {
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const text = reader.result as string
|
||||
const state = JSON.parse(text) as { nodes?: Node[]; edges?: Edge[] }
|
||||
if (!state || !Array.isArray(state.nodes) || !Array.isArray(state.edges)) return
|
||||
setStateImmediate({ nodes: state.nodes, edges: state.edges })
|
||||
const state = JSON.parse(text) as { version?: number; nodes?: unknown[]; edges?: unknown[] }
|
||||
if (!state || !Array.isArray(state.nodes) || !Array.isArray(state.edges)) {
|
||||
setProjectMessage({ type: 'error', text: 'Invalid file: expected nodes and edges arrays' })
|
||||
return
|
||||
}
|
||||
setStateImmediate({ nodes: state.nodes as AppNode[], edges: state.edges as AppEdge[] })
|
||||
setProjectMessage(
|
||||
state.version != null && state.version > PROJECT_VERSION
|
||||
? { type: 'error', text: 'Project was created with a newer app version' }
|
||||
: { type: 'success', text: 'Project loaded' }
|
||||
)
|
||||
} catch {
|
||||
// Invalid JSON
|
||||
setProjectMessage({ type: 'error', text: 'Invalid file: not valid JSON' })
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
@@ -383,6 +403,18 @@ export default function App() {
|
||||
canUndo={canUndo}
|
||||
canRedo={canRedo}
|
||||
/>
|
||||
{projectMessage && (
|
||||
<div
|
||||
role="status"
|
||||
className={`shrink-0 px-3 py-2 text-sm ${
|
||||
projectMessage.type === 'success'
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200'
|
||||
: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-200'
|
||||
}`}
|
||||
>
|
||||
{projectMessage.text}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 min-h-0 relative flex flex-col">
|
||||
<div className="flex-1 min-h-0 flex flex-col">
|
||||
<FlowContext.Provider value={flowContextValue}>
|
||||
|
||||
Reference in New Issue
Block a user