impl: autosave to local storage
This commit is contained in:
@@ -103,14 +103,6 @@ function StageItemActions({ stage }: { stage: string }) {
|
||||
|
||||
return (
|
||||
<div className="flex flex-row space-x-2 invisible group-hover:visible">
|
||||
<Button
|
||||
variant="small"
|
||||
onClick={() => {
|
||||
deleteStageInEntry(stage, entry.name)
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="small"
|
||||
onClick={() => {
|
||||
|
@@ -1,17 +1,37 @@
|
||||
interface Node {
|
||||
key: string
|
||||
outs: Record<string, Connection>
|
||||
}
|
||||
import {
|
||||
type Infer,
|
||||
array,
|
||||
integer,
|
||||
min,
|
||||
object,
|
||||
record,
|
||||
string,
|
||||
} from "superstruct"
|
||||
|
||||
interface Entry {
|
||||
name: string
|
||||
stages: Node["key"][]
|
||||
}
|
||||
const $Connection = object({
|
||||
nodeKey: string(),
|
||||
weight: min(integer(), 0),
|
||||
})
|
||||
type Connection = Infer<typeof $Connection>
|
||||
|
||||
interface Connection {
|
||||
nodeKey: Node["key"]
|
||||
weight: number
|
||||
}
|
||||
const $Node = object({
|
||||
key: string(),
|
||||
outs: record(string(), $Connection),
|
||||
})
|
||||
type Node = Infer<typeof $Node>
|
||||
|
||||
const $Entry = object({
|
||||
name: string(),
|
||||
stages: array(string()),
|
||||
})
|
||||
type Entry = Infer<typeof $Entry>
|
||||
|
||||
const $Graph = object({
|
||||
nodes: record(string(), $Node),
|
||||
starts: array(string()),
|
||||
entries: record(string(), $Entry),
|
||||
})
|
||||
type Graph = Infer<typeof $Graph>
|
||||
|
||||
const DEFAULT_NODE = {
|
||||
applicationSubmittedNode: {
|
||||
@@ -28,5 +48,5 @@ const DEFAULT_NODE = {
|
||||
},
|
||||
} as const
|
||||
|
||||
export { DEFAULT_NODE }
|
||||
export type { Node, Entry, Connection }
|
||||
export { $Graph, DEFAULT_NODE }
|
||||
export type { Graph, Node, Entry, Connection }
|
||||
|
@@ -1,12 +1,15 @@
|
||||
import { is } from "superstruct"
|
||||
import { create } from "zustand/index"
|
||||
import { immer } from "zustand/middleware/immer"
|
||||
import { DEFAULT_NODE, type Entry, type Node } from "~/home/graph"
|
||||
import { $Graph, DEFAULT_NODE, type Entry, type Node } from "~/home/graph"
|
||||
|
||||
interface RootStore {
|
||||
nodes: Record<string, Node>
|
||||
starts: Node["key"][]
|
||||
entries: Record<string, Entry>
|
||||
|
||||
loadGraphFromLocalStorage: () => boolean
|
||||
resetGraph: () => void
|
||||
addEntry: (name: string) => void
|
||||
hasEntry: (name: string) => boolean
|
||||
addStageInEntry: (stage: string, entryName: string) => void
|
||||
@@ -25,6 +28,38 @@ const useRootStore = create<RootStore>()(
|
||||
starts: [DEFAULT_NODE.applicationSubmittedNode.key],
|
||||
entries: {},
|
||||
|
||||
loadGraphFromLocalStorage: () => {
|
||||
const graphJson = localStorage.getItem("graph")
|
||||
if (!graphJson) {
|
||||
// if there is no saved data, then we simply return
|
||||
// this is not considered to be an error
|
||||
return true
|
||||
}
|
||||
|
||||
const graph = JSON.parse(graphJson)
|
||||
if (!is(graph, $Graph)) {
|
||||
return false
|
||||
}
|
||||
|
||||
set({
|
||||
nodes: graph.nodes,
|
||||
starts: graph.starts,
|
||||
entries: graph.entries,
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
resetGraph: () =>
|
||||
set({
|
||||
nodes: {
|
||||
[DEFAULT_NODE.applicationSubmittedNode.key]:
|
||||
DEFAULT_NODE.applicationSubmittedNode,
|
||||
},
|
||||
starts: [DEFAULT_NODE.applicationSubmittedNode.key],
|
||||
entries: {},
|
||||
}),
|
||||
|
||||
addEntry: (name) =>
|
||||
set((state) => {
|
||||
const currentEntries = state.entries
|
||||
|
@@ -16,6 +16,25 @@ export function meta() {
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const loadGraphFromLocalStorage = useRootStore(
|
||||
(state) => state.loadGraphFromLocalStorage,
|
||||
)
|
||||
|
||||
useEffect(function saveGraphToLocalStorage() {
|
||||
const unsub = useRootStore.subscribe(({ nodes, starts, entries }) => {
|
||||
localStorage.setItem("graph", JSON.stringify({ nodes, starts, entries }))
|
||||
})
|
||||
return () => {
|
||||
unsub()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadGraphFromLocalStorage()) {
|
||||
alert("Unable to load locally saved data due to inconsistency.")
|
||||
}
|
||||
}, [loadGraphFromLocalStorage])
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center p-4 md:px-16 md:pt-20 w-full">
|
||||
<main className="w-full max-w-xl flex flex-col items-start">
|
||||
@@ -37,26 +56,34 @@ export default function Home() {
|
||||
function FlufferChart() {
|
||||
const nodes = useRootStore((state) => state.nodes)
|
||||
const starts = useRootStore((state) => state.starts)
|
||||
const resetGraph = useRootStore((state) => state.resetGraph)
|
||||
const uiMode = useUiMode()
|
||||
|
||||
const data = useMemo(() => {
|
||||
const rows: [string, string, number][] = []
|
||||
const queue = new Queue<Node>()
|
||||
for (const nodeKey of starts) {
|
||||
queue.enqueue(nodes[nodeKey])
|
||||
}
|
||||
while (!queue.isEmpty) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: if queue is non empty, then dequeue will always be non null
|
||||
const currentNode = queue.dequeue()!
|
||||
for (const nodeKey in currentNode.outs) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||
const connection = currentNode.outs[nodeKey]!
|
||||
rows.push([currentNode.key, connection.nodeKey, connection.weight])
|
||||
queue.enqueue(nodes[connection.nodeKey])
|
||||
try {
|
||||
const rows: [string, string, number][] = []
|
||||
const queue = new Queue<Node>()
|
||||
for (const nodeKey of starts) {
|
||||
queue.enqueue(nodes[nodeKey])
|
||||
}
|
||||
while (!queue.isEmpty) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: if queue is non empty, then dequeue will always be non null
|
||||
const currentNode = queue.dequeue()!
|
||||
for (const nodeKey in currentNode.outs) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||
const connection = currentNode.outs[nodeKey]!
|
||||
rows.push([currentNode.key, connection.nodeKey, connection.weight])
|
||||
queue.enqueue(nodes[connection.nodeKey])
|
||||
}
|
||||
}
|
||||
return rows
|
||||
} catch {
|
||||
if (confirm("Invalid data detected. Erase data and reset?")) {
|
||||
resetGraph()
|
||||
}
|
||||
return []
|
||||
}
|
||||
return rows
|
||||
}, [starts, nodes])
|
||||
}, [starts, nodes, resetGraph])
|
||||
|
||||
const hasData = data.length > 0
|
||||
|
||||
|
Reference in New Issue
Block a user