Skip to content

Instantly share code, notes, and snippets.

@jobelenus
Created January 17, 2025 16:47
Show Gist options
  • Save jobelenus/1b3a3992c0912e0db9988329a51ec797 to your computer and use it in GitHub Desktop.
Save jobelenus/1b3a3992c0912e0db9988329a51ec797 to your computer and use it in GitHub Desktop.
Graph Vue Store
import { defineStore } from "pinia";
import { addStoreHooks } from "@si/vue-lib/pinia";
import { reactive } from "vue";
import { ChangeSetId } from "@/api/sdf/dal/change_set";
import { useWorkspacesStore } from "../workspaces.store";
import { useChangeSetsStore } from "../change_sets.store";
import {
Edge,
RootComponent,
Ulid,
kindsToRoots,
Node,
EdgeWithoutId,
Neighbors,
EdgeKind,
UniqueEdgeKind,
ASYMETRIC_EDGE_KINDS,
} from "./roots";
export const useViewsStore = (forceChangeSetId?: ChangeSetId) => {
const workspacesStore = useWorkspacesStore();
const workspaceId = workspacesStore.selectedWorkspacePk;
const changeSetsStore = useChangeSetsStore();
let changeSetId: ChangeSetId | undefined;
if (forceChangeSetId) {
changeSetId = forceChangeSetId;
} else {
changeSetId = changeSetsStore.selectedChangeSetId;
}
return addStoreHooks(
workspaceId,
changeSetId,
defineStore(
`ws${workspaceId || "NONE"}/cs${changeSetId || "NONE"}/pool`,
() => {
const pool = reactive({
nodes: new Map<Ulid, Node>(),
edges: new Map<Ulid, Edge>(),
});
const addNodeToPool = (node: Node) => {
pool.nodes.set(node.id, node);
addNodeOnGraph(node);
const root = kindsToRoots.get(node.kind);
if (!root) throw new Error(`Root node not found for ${node}`);
addEdgeOnGraph("root", root, node);
};
// add all root nodes
addNodeToPool(RootComponent);
const setEdgeId = (edge: EdgeWithoutId): Edge => {
edge.id = `${edge.kind}_${edge.fromId}_${edge.toId}`; // i dont like how to/from order matters
return edge as Edge;
};
const addEdgeToPool = (e: EdgeWithoutId) => {
const edge = setEdgeId(e);
pool.edges.set(edge.id, edge);
const node1 = pool.nodes.get(edge.fromId);
const node2 = pool.nodes.get(edge.toId);
if (!node1) throw new Error(`{edge.fromId} node not found`);
if (!node2) throw new Error(`{edge.toId} node not found`);
addEdgeOnGraph(edge.kind, node1, node2);
};
const deleteEdgeFromPool = (e: EdgeWithoutId) => {
const edge = setEdgeId(e);
pool.edges.delete(edge.id);
const node1 = pool.nodes.get(e.fromId);
const node2 = pool.nodes.get(e.toId);
if (!node1) throw new Error(`{edge.fromId} node not found`);
if (!node2) throw new Error(`{edge.toId} node not found`);
getNeighborsForKind(node1, edge.kind)?.delete(node2);
const asymetricKind = ASYMETRIC_EDGE_KINDS[edge.kind];
getNeighborsForKind(node2, asymetricKind ?? edge.kind)?.delete(node1);
};
const deleteNodeFromPool = (node: Node) => {
const neighbors = getNeighbors(node);
pool.nodes.delete(node.id);
neighbors?.forEach((forEdge, edgeKind) => {
const asymetricKind = ASYMETRIC_EDGE_KINDS[edgeKind];
forEdge.forEach((n) => {
getNeighborsForKind(n, asymetricKind ?? edgeKind)?.delete(node);
});
});
};
const updateNodeInPool = (updatedNode: Node) => {
const node = pool.nodes.get(updatedNode.id);
if (!node) throw new Error(`${updatedNode.id} not found in pool`);
Object.assign(node, updatedNode); // reference remains in place (e.g. no new object created)
};
const adjacencyList = reactive(new Map<Node, Neighbors>());
const addNodeOnGraph = (node: Node) => {
adjacencyList.set(node, new Map());
};
const getNeighbors = (node: Node) => {
return adjacencyList.get(node);
};
const getNeighborsForKind = (node: Node, kind: EdgeKind) => {
return adjacencyList.get(node)?.get(kind);
};
const getEdgeKinds = (neighbors: Neighbors, kind: EdgeKind) => {
let n = neighbors.get(kind);
if (!n) {
n = new Set<Node>();
neighbors.set(kind, n);
}
return n;
};
const _addEdge = (kind: EdgeKind, node1: Node, node2: Node) => {
const neighbors = getNeighbors(node1);
if (!neighbors) throw new Error(`node1 ${node1} does not exist`);
const forEdge = getEdgeKinds(neighbors, kind);
forEdge.add(node2);
};
const addEdgeOnGraph = (kind: EdgeKind, from: Node, to: Node) => {
const asymetricKind = ASYMETRIC_EDGE_KINDS[kind];
_addEdge(kind, from, to);
_addEdge(asymetricKind ?? kind, to, from);
};
// unique edges are not bi-directional
const addUniqueEdgeOnGraph = (
kind: UniqueEdgeKind,
from: Node,
to: Node,
) => {
getNeighborsForKind(from, kind)?.clear();
_addEdge(kind, from, to);
};
const hasEdgeKind = (
node1: Node,
node2: Node,
kind: EdgeKind,
): boolean => {
return !!getNeighborsForKind(node1, kind)?.has(node2);
};
const onActivated = () => {
// TODO subscribe
};
return {
pool,
adjacencyList,
addNodeToPool,
addEdgeToPool,
deleteNodeFromPool,
deleteEdgeFromPool,
addUniqueEdgeOnGraph,
updateNodeInPool,
getNeighbors,
getNeighborsForKind,
getEdgeKinds,
hasEdgeKind,
onActivated,
};
},
),
);
};
export type Version = number;
export type Ulid = string;
export type NodeKind = string;
export type EdgeKind = string;
export type UniqueEdgeKind = string;
export type Node = {
id: Ulid;
kind: NodeKind;
v: Version;
};
export type EdgeWithoutId = {
id?: string;
toKind: NodeKind;
toId: Ulid;
fromId: Ulid;
fromKind: NodeKind;
kind: EdgeKind;
v: Version;
};
export type Edge = Required<EdgeWithoutId>;
export type Neighbors = Map<EdgeKind, Set<Node>>;
export const RootComponent: Node = {
id: "RootComponent",
kind: "RootComponent",
v: 1,
};
export const kindsToRoots: Map<NodeKind, Node> = new Map();
kindsToRoots.set("Component" as NodeKind, RootComponent);
export const ASYMETRIC_EDGE_KINDS: Record<EdgeKind, EdgeKind> = {
FrameContains: "FrameChild",
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment