Canvas: Auto-assign position to nodes if missing
Change-Id: Ica80878e0cb280c9a44f58637c11b53d78e07892
diff --git a/apps/canvas/config/src/graph.ts b/apps/canvas/config/src/graph.ts
index e309926..c2f8e05 100644
--- a/apps/canvas/config/src/graph.ts
+++ b/apps/canvas/config/src/graph.ts
@@ -577,6 +577,14 @@
targetHandle: z.string().optional(),
});
+export const ViewportTransformSchema = z.object({
+ transformX: z.number(),
+ transformY: z.number(),
+ transformZoom: z.number(),
+ width: z.number(),
+ height: z.number(),
+});
+
export const GraphSchema = z.object({
nodes: z.array(NodeSchema),
edges: z.array(EdgeSchema),
@@ -587,6 +595,7 @@
zoom: z.number(),
})
.optional(),
+ viewportTransform: ViewportTransformSchema.optional(),
});
export const GraphOrConfigSchema = z.discriminatedUnion("type", [
@@ -601,4 +610,5 @@
]);
export type Edge = z.infer<typeof EdgeSchema>;
+export type ViewportTransform = z.infer<typeof ViewportTransformSchema>;
export type Graph = z.infer<typeof GraphSchema>;
diff --git a/apps/canvas/config/src/index.ts b/apps/canvas/config/src/index.ts
index 4a6121e..c10ce31 100644
--- a/apps/canvas/config/src/index.ts
+++ b/apps/canvas/config/src/index.ts
@@ -51,8 +51,10 @@
Access,
AgentAccess,
Graph,
+ GraphSchema,
Edge,
GraphOrConfigSchema,
+ ViewportTransform,
} from "./graph.js";
export { generateDodoConfig, configToGraph } from "./config.js";
diff --git a/apps/canvas/front/src/lib/state.ts b/apps/canvas/front/src/lib/state.ts
index b0237a9..fcfe011 100644
--- a/apps/canvas/front/src/lib/state.ts
+++ b/apps/canvas/front/src/lib/state.ts
@@ -14,7 +14,17 @@
import type { DeepPartial } from "react-hook-form";
import { v4 as uuidv4 } from "uuid";
import { create } from "zustand";
-import { AppNode, Env, NodeType, VolumeNode, GatewayTCPData, envSchema, AgentAccess } from "config";
+import {
+ AppNode,
+ Env,
+ NodeType,
+ VolumeNode,
+ GatewayTCPData,
+ envSchema,
+ AgentAccess,
+ ViewportTransform,
+ GraphSchema,
+} from "config";
export function nodeLabel(n: AppNode): string {
try {
@@ -183,14 +193,6 @@
type NodeUpdate<T extends NodeType> = DeepPartial<Extract<AppNode, { type: T }>>;
type NodeDataUpdate<T extends NodeType> = DeepPartial<Extract<AppNode, { type: T }>["data"]>;
-type Viewport = {
- transformX: number;
- transformY: number;
- transformZoom: number;
- width: number;
- height: number;
-};
-
let refreshEnvIntervalId: number | null = null;
export type AppState = {
@@ -204,8 +206,8 @@
categories: Category[];
messages: Message[];
env: Env;
- viewport: Viewport;
- setViewport: (viewport: Viewport) => void;
+ viewport: ViewportTransform;
+ setViewport: (viewport: ViewportTransform) => void;
githubService: GitHubService | null;
githubRepositories: GitHubRepository[];
githubRepositoriesLoading: boolean;
@@ -323,7 +325,7 @@
const v: Validator = CreateValidators();
-function getRandomPosition({ width, height, transformX, transformY, transformZoom }: Viewport): XYPosition {
+function getRandomPosition({ width, height, transformX, transformY, transformZoom }: ViewportTransform): XYPosition {
const zoomMultiplier = 1 / transformZoom;
const realWidth = width * zoomMultiplier;
const realHeight = height * zoomMultiplier;
@@ -629,11 +631,41 @@
set({ stateEventSource: eventSource });
eventSource.onmessage = (event) => {
- const inst = JSON.parse(event.data);
- setN(inst.nodes);
+ const { mode, viewport } = get();
+ const inst = GraphSchema.parse(JSON.parse(event.data));
+ let positionChanged = false;
+ let nodes = inst.nodes;
+ if (mode === "edit") {
+ nodes = inst.nodes.map((n) => {
+ if (n.position.x === 0 && n.position.y === 0) {
+ positionChanged = true;
+ return {
+ ...n,
+ position: getRandomPosition(viewport),
+ };
+ } else {
+ return n;
+ }
+ });
+ }
+ setN(nodes);
set({ edges: inst.edges });
injectNetworkNodes();
- // TODO(gio): set viewport
+ if (positionChanged) {
+ fetch(`/api/project/${projectId}/saved`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ type: "graph",
+ graph: {
+ ...inst,
+ nodes,
+ },
+ }),
+ });
+ }
};
eventSource.onerror = (err) => {