perf(canvas): reduce Convex hot-path query load
This commit is contained in:
@@ -31,6 +31,9 @@ vi.mock("@/convex/_generated/api", () => ({
|
||||
create: "edges.create",
|
||||
remove: "edges.remove",
|
||||
},
|
||||
canvasGraph: {
|
||||
get: "canvasGraph.get",
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
80
components/canvas/canvas-graph-query-cache.ts
Normal file
80
components/canvas/canvas-graph-query-cache.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type { OptimisticLocalStore } from "convex/browser";
|
||||
import type { FunctionReference } from "convex/server";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Doc, Id } from "@/convex/_generated/dataModel";
|
||||
|
||||
export const canvasGraphQuery = (api as unknown as {
|
||||
canvasGraph: {
|
||||
get: FunctionReference<
|
||||
"query",
|
||||
"public",
|
||||
{ canvasId: Id<"canvases"> },
|
||||
{ nodes: Doc<"nodes">[]; edges: Doc<"edges">[] }
|
||||
>;
|
||||
};
|
||||
}).canvasGraph.get;
|
||||
|
||||
type CanvasGraphQueryResult = {
|
||||
nodes: Doc<"nodes">[];
|
||||
edges: Doc<"edges">[];
|
||||
};
|
||||
|
||||
type CanvasGraphArgs = {
|
||||
canvasId: Id<"canvases">;
|
||||
};
|
||||
|
||||
function getCanvasGraphFromQuery(
|
||||
localStore: OptimisticLocalStore,
|
||||
args: CanvasGraphArgs,
|
||||
): CanvasGraphQueryResult | undefined {
|
||||
return localStore.getQuery(canvasGraphQuery, args) as CanvasGraphQueryResult | undefined;
|
||||
}
|
||||
|
||||
export function getCanvasGraphNodesFromQuery(
|
||||
localStore: OptimisticLocalStore,
|
||||
args: CanvasGraphArgs,
|
||||
): Doc<"nodes">[] | undefined {
|
||||
return getCanvasGraphFromQuery(localStore, args)?.nodes;
|
||||
}
|
||||
|
||||
export function getCanvasGraphEdgesFromQuery(
|
||||
localStore: OptimisticLocalStore,
|
||||
args: CanvasGraphArgs,
|
||||
): Doc<"edges">[] | undefined {
|
||||
return getCanvasGraphFromQuery(localStore, args)?.edges;
|
||||
}
|
||||
|
||||
export function setCanvasGraphNodesInQuery(
|
||||
localStore: OptimisticLocalStore,
|
||||
args: CanvasGraphArgs & { nodes: Doc<"nodes">[] },
|
||||
): void {
|
||||
const current = getCanvasGraphFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStore.setQuery(canvasGraphQuery, { canvasId: args.canvasId }, {
|
||||
nodes: args.nodes,
|
||||
edges: current.edges,
|
||||
});
|
||||
}
|
||||
|
||||
export function setCanvasGraphEdgesInQuery(
|
||||
localStore: OptimisticLocalStore,
|
||||
args: CanvasGraphArgs & { edges: Doc<"edges">[] },
|
||||
): void {
|
||||
const current = getCanvasGraphFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStore.setQuery(canvasGraphQuery, { canvasId: args.canvasId }, {
|
||||
nodes: current.nodes,
|
||||
edges: args.edges,
|
||||
});
|
||||
}
|
||||
@@ -1,15 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useConvex, useConvexAuth, useMutation } from "convex/react";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Doc } from "@/convex/_generated/dataModel";
|
||||
import { useAuthQuery } from "@/hooks/use-auth-query";
|
||||
|
||||
type AdjustmentPresetDoc = Doc<"adjustmentPresets">;
|
||||
type PresetsByNodeType = Map<AdjustmentPresetDoc["nodeType"], AdjustmentPresetDoc[]>;
|
||||
type SaveAdjustmentPresetArgs = {
|
||||
name: string;
|
||||
nodeType: AdjustmentPresetDoc["nodeType"];
|
||||
params: unknown;
|
||||
};
|
||||
|
||||
const CanvasPresetsContext = createContext<PresetsByNodeType | null>(null);
|
||||
type CanvasPresetsContextValue = {
|
||||
presetsByNodeType: PresetsByNodeType;
|
||||
savePreset: (args: SaveAdjustmentPresetArgs) => Promise<void>;
|
||||
};
|
||||
|
||||
const MAX_AUTO_LOAD_ATTEMPTS = 2;
|
||||
const RETRY_DELAY_MS = 1000;
|
||||
const CanvasPresetsContext = createContext<CanvasPresetsContextValue | null>(null);
|
||||
|
||||
function groupPresetsByNodeType(rawPresets: AdjustmentPresetDoc[]): PresetsByNodeType {
|
||||
const next = new Map<AdjustmentPresetDoc["nodeType"], AdjustmentPresetDoc[]>();
|
||||
|
||||
for (const preset of rawPresets) {
|
||||
const existing = next.get(preset.nodeType);
|
||||
if (existing) {
|
||||
existing.push(preset);
|
||||
} else {
|
||||
next.set(preset.nodeType, [preset]);
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
type CanvasPresetsProviderProps = {
|
||||
enabled?: boolean;
|
||||
@@ -20,25 +56,163 @@ export function CanvasPresetsProvider({
|
||||
enabled = true,
|
||||
children,
|
||||
}: CanvasPresetsProviderProps) {
|
||||
const rawPresets = useAuthQuery(api.presets.list, enabled ? {} : "skip");
|
||||
const convex = useConvex();
|
||||
const { isAuthenticated } = useConvexAuth();
|
||||
const savePresetMutation = useMutation(api.presets.save);
|
||||
const latestLoadRequestIdRef = useRef(0);
|
||||
const autoLoadInFlightRef = useRef(false);
|
||||
const retryTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const [rawPresets, setRawPresets] = useState<AdjustmentPresetDoc[]>([]);
|
||||
const [hasLoadedOnce, setHasLoadedOnce] = useState(false);
|
||||
const [retryNonce, setRetryNonce] = useState(0);
|
||||
|
||||
const presetsByNodeType = useMemo<PresetsByNodeType>(() => {
|
||||
const next = new Map<AdjustmentPresetDoc["nodeType"], AdjustmentPresetDoc[]>();
|
||||
const clearScheduledRetry = useCallback(() => {
|
||||
if (retryTimeoutRef.current !== null) {
|
||||
clearTimeout(retryTimeoutRef.current);
|
||||
retryTimeoutRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
for (const preset of (rawPresets ?? []) as AdjustmentPresetDoc[]) {
|
||||
const existing = next.get(preset.nodeType);
|
||||
if (existing) {
|
||||
existing.push(preset);
|
||||
} else {
|
||||
next.set(preset.nodeType, [preset]);
|
||||
}
|
||||
const scheduleRetry = useCallback(() => {
|
||||
if (retryTimeoutRef.current !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return next;
|
||||
}, [rawPresets]);
|
||||
retryTimeoutRef.current = setTimeout(() => {
|
||||
retryTimeoutRef.current = null;
|
||||
setRetryNonce((current) => current + 1);
|
||||
}, RETRY_DELAY_MS);
|
||||
}, []);
|
||||
|
||||
const loadPresets = useCallback(async () => {
|
||||
const presets = await convex.query(api.presets.list, {});
|
||||
return presets as AdjustmentPresetDoc[];
|
||||
}, [convex]);
|
||||
|
||||
const refreshPresets = useCallback(async () => {
|
||||
const requestId = ++latestLoadRequestIdRef.current;
|
||||
|
||||
try {
|
||||
const presets = await loadPresets();
|
||||
|
||||
if (requestId === latestLoadRequestIdRef.current) {
|
||||
clearScheduledRetry();
|
||||
setRawPresets(presets);
|
||||
}
|
||||
|
||||
return { requestId, presets };
|
||||
} catch (error: unknown) {
|
||||
throw { requestId, error };
|
||||
}
|
||||
}, [clearScheduledRetry, loadPresets]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearScheduledRetry();
|
||||
};
|
||||
}, [clearScheduledRetry]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!enabled ||
|
||||
!isAuthenticated ||
|
||||
hasLoadedOnce ||
|
||||
autoLoadInFlightRef.current
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
autoLoadInFlightRef.current = true;
|
||||
|
||||
void (async () => {
|
||||
for (let attempt = 0; attempt < MAX_AUTO_LOAD_ATTEMPTS; attempt += 1) {
|
||||
try {
|
||||
await refreshPresets();
|
||||
if (!cancelled) {
|
||||
setHasLoadedOnce(true);
|
||||
}
|
||||
return;
|
||||
} catch (caught: unknown) {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const refreshError =
|
||||
typeof caught === "object" && caught !== null && "error" in caught
|
||||
? ((caught as { error: unknown }).error ?? caught)
|
||||
: caught;
|
||||
|
||||
console.warn("[CanvasPresetsProvider] failed to load presets", {
|
||||
message:
|
||||
refreshError instanceof Error
|
||||
? refreshError.message
|
||||
: String(refreshError),
|
||||
attempt: attempt + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!cancelled) {
|
||||
scheduleRetry();
|
||||
}
|
||||
})().finally(() => {
|
||||
autoLoadInFlightRef.current = false;
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [enabled, hasLoadedOnce, isAuthenticated, refreshPresets, retryNonce, scheduleRetry]);
|
||||
|
||||
const savePreset = useCallback(
|
||||
async (args: SaveAdjustmentPresetArgs) => {
|
||||
await savePresetMutation(args);
|
||||
|
||||
try {
|
||||
await refreshPresets();
|
||||
setHasLoadedOnce(true);
|
||||
} catch (caught: unknown) {
|
||||
const refreshError =
|
||||
typeof caught === "object" && caught !== null && "error" in caught
|
||||
? ((caught as { error: unknown }).error ?? caught)
|
||||
: caught;
|
||||
const requestId =
|
||||
typeof caught === "object" && caught !== null && "requestId" in caught
|
||||
? ((caught as { requestId: unknown }).requestId as number)
|
||||
: latestLoadRequestIdRef.current;
|
||||
|
||||
console.warn("[CanvasPresetsProvider] failed to refresh presets after save", {
|
||||
message:
|
||||
refreshError instanceof Error
|
||||
? refreshError.message
|
||||
: String(refreshError),
|
||||
});
|
||||
|
||||
if (requestId === latestLoadRequestIdRef.current) {
|
||||
setHasLoadedOnce(false);
|
||||
scheduleRetry();
|
||||
}
|
||||
}
|
||||
},
|
||||
[refreshPresets, savePresetMutation, scheduleRetry],
|
||||
);
|
||||
|
||||
const presetsByNodeType = useMemo<PresetsByNodeType>(
|
||||
() => groupPresetsByNodeType(rawPresets),
|
||||
[rawPresets],
|
||||
);
|
||||
|
||||
const contextValue = useMemo<CanvasPresetsContextValue>(
|
||||
() => ({
|
||||
presetsByNodeType,
|
||||
savePreset,
|
||||
}),
|
||||
[presetsByNodeType, savePreset],
|
||||
);
|
||||
|
||||
return (
|
||||
<CanvasPresetsContext.Provider value={presetsByNodeType}>
|
||||
<CanvasPresetsContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</CanvasPresetsContext.Provider>
|
||||
);
|
||||
@@ -52,5 +226,14 @@ export function useCanvasAdjustmentPresets(
|
||||
return [];
|
||||
}
|
||||
|
||||
return context.get(nodeType) ?? [];
|
||||
return context.presetsByNodeType.get(nodeType) ?? [];
|
||||
}
|
||||
|
||||
export function useSaveCanvasAdjustmentPreset(): CanvasPresetsContextValue["savePreset"] {
|
||||
const context = useContext(CanvasPresetsContext);
|
||||
if (context === null) {
|
||||
throw new Error("useSaveCanvasAdjustmentPreset must be used within CanvasPresetsProvider");
|
||||
}
|
||||
|
||||
return context.savePreset;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Handle, Position, type Node, type NodeProps } from "@xyflow/react";
|
||||
import { useMutation } from "convex/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Palette } from "lucide-react";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { useCanvasAdjustmentPresets } from "@/components/canvas/canvas-presets-context";
|
||||
import {
|
||||
useCanvasAdjustmentPresets,
|
||||
useSaveCanvasAdjustmentPreset,
|
||||
} from "@/components/canvas/canvas-presets-context";
|
||||
import { useCanvasSync } from "@/components/canvas/canvas-sync-context";
|
||||
import BaseNodeWrapper from "@/components/canvas/nodes/base-node-wrapper";
|
||||
import AdjustmentPreview from "@/components/canvas/nodes/adjustment-preview";
|
||||
@@ -46,7 +47,7 @@ export default function ColorAdjustNode({ id, data, selected, width }: NodeProps
|
||||
const tNodes = useTranslations("nodes");
|
||||
const tToasts = useTranslations("toasts");
|
||||
const { queueNodeDataUpdate } = useCanvasSync();
|
||||
const savePreset = useMutation(api.presets.save);
|
||||
const savePreset = useSaveCanvasAdjustmentPreset();
|
||||
const userPresets = useCanvasAdjustmentPresets("color-adjust") as PresetDoc[];
|
||||
|
||||
const [presetSelection, setPresetSelection] = useState("custom");
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Handle, Position, type Node, type NodeProps } from "@xyflow/react";
|
||||
import { useMutation } from "convex/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { TrendingUp } from "lucide-react";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { useCanvasAdjustmentPresets } from "@/components/canvas/canvas-presets-context";
|
||||
import {
|
||||
useCanvasAdjustmentPresets,
|
||||
useSaveCanvasAdjustmentPreset,
|
||||
} from "@/components/canvas/canvas-presets-context";
|
||||
import { useCanvasSync } from "@/components/canvas/canvas-sync-context";
|
||||
import BaseNodeWrapper from "@/components/canvas/nodes/base-node-wrapper";
|
||||
import AdjustmentPreview from "@/components/canvas/nodes/adjustment-preview";
|
||||
@@ -46,7 +47,7 @@ export default function CurvesNode({ id, data, selected, width }: NodeProps<Curv
|
||||
const tNodes = useTranslations("nodes");
|
||||
const tToasts = useTranslations("toasts");
|
||||
const { queueNodeDataUpdate } = useCanvasSync();
|
||||
const savePreset = useMutation(api.presets.save);
|
||||
const savePreset = useSaveCanvasAdjustmentPreset();
|
||||
const userPresets = useCanvasAdjustmentPresets("curves") as PresetDoc[];
|
||||
|
||||
const [presetSelection, setPresetSelection] = useState("custom");
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Handle, Position, type Node, type NodeProps } from "@xyflow/react";
|
||||
import { useMutation } from "convex/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Focus } from "lucide-react";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { useCanvasAdjustmentPresets } from "@/components/canvas/canvas-presets-context";
|
||||
import {
|
||||
useCanvasAdjustmentPresets,
|
||||
useSaveCanvasAdjustmentPreset,
|
||||
} from "@/components/canvas/canvas-presets-context";
|
||||
import { useCanvasSync } from "@/components/canvas/canvas-sync-context";
|
||||
import BaseNodeWrapper from "@/components/canvas/nodes/base-node-wrapper";
|
||||
import AdjustmentPreview from "@/components/canvas/nodes/adjustment-preview";
|
||||
@@ -46,7 +47,7 @@ export default function DetailAdjustNode({ id, data, selected, width }: NodeProp
|
||||
const tNodes = useTranslations("nodes");
|
||||
const tToasts = useTranslations("toasts");
|
||||
const { queueNodeDataUpdate } = useCanvasSync();
|
||||
const savePreset = useMutation(api.presets.save);
|
||||
const savePreset = useSaveCanvasAdjustmentPreset();
|
||||
const userPresets = useCanvasAdjustmentPresets("detail-adjust") as PresetDoc[];
|
||||
|
||||
const [presetSelection, setPresetSelection] = useState("custom");
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Handle, Position, type Node, type NodeProps } from "@xyflow/react";
|
||||
import { useMutation } from "convex/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Sun } from "lucide-react";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { useCanvasAdjustmentPresets } from "@/components/canvas/canvas-presets-context";
|
||||
import {
|
||||
useCanvasAdjustmentPresets,
|
||||
useSaveCanvasAdjustmentPreset,
|
||||
} from "@/components/canvas/canvas-presets-context";
|
||||
import { useCanvasSync } from "@/components/canvas/canvas-sync-context";
|
||||
import BaseNodeWrapper from "@/components/canvas/nodes/base-node-wrapper";
|
||||
import AdjustmentPreview from "@/components/canvas/nodes/adjustment-preview";
|
||||
@@ -46,7 +47,7 @@ export default function LightAdjustNode({ id, data, selected, width }: NodeProps
|
||||
const tNodes = useTranslations("nodes");
|
||||
const tToasts = useTranslations("toasts");
|
||||
const { queueNodeDataUpdate } = useCanvasSync();
|
||||
const savePreset = useMutation(api.presets.save);
|
||||
const savePreset = useSaveCanvasAdjustmentPreset();
|
||||
const userPresets = useCanvasAdjustmentPresets("light-adjust") as PresetDoc[];
|
||||
|
||||
const [presetSelection, setPresetSelection] = useState("custom");
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useConvexAuth, useMutation, useQuery } from "convex/react";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { canvasGraphQuery } from "./canvas-graph-query-cache";
|
||||
|
||||
type UseCanvasDataParams = {
|
||||
canvasId: Id<"canvases">;
|
||||
@@ -45,14 +47,12 @@ export function useCanvasData({ canvasId }: UseCanvasDataParams) {
|
||||
shouldSkipCanvasQueries,
|
||||
]);
|
||||
|
||||
const convexNodes = useQuery(
|
||||
api.nodes.list,
|
||||
shouldSkipCanvasQueries ? "skip" : { canvasId },
|
||||
);
|
||||
const convexEdges = useQuery(
|
||||
api.edges.list,
|
||||
const graph = useQuery(
|
||||
canvasGraphQuery,
|
||||
shouldSkipCanvasQueries ? "skip" : { canvasId },
|
||||
);
|
||||
const convexNodes = graph?.nodes;
|
||||
const convexEdges = graph?.edges;
|
||||
const canvas = useQuery(
|
||||
api.canvases.get,
|
||||
shouldSkipCanvasQueries ? "skip" : { canvasId },
|
||||
|
||||
@@ -45,6 +45,12 @@ import {
|
||||
OPTIMISTIC_NODE_PREFIX,
|
||||
type PendingEdgeSplit,
|
||||
} from "./canvas-helpers";
|
||||
import {
|
||||
getCanvasGraphEdgesFromQuery,
|
||||
getCanvasGraphNodesFromQuery,
|
||||
setCanvasGraphEdgesInQuery,
|
||||
setCanvasGraphNodesInQuery,
|
||||
} from "./canvas-graph-query-cache";
|
||||
|
||||
type QueueSyncMutation = <TType extends keyof CanvasSyncOpPayloadByType>(
|
||||
type: TType,
|
||||
@@ -588,7 +594,7 @@ export function useCanvasSyncEngine({
|
||||
|
||||
const createNode = useMutation(api.nodes.create).withOptimisticUpdate(
|
||||
(localStore, args) => {
|
||||
const current = localStore.getQuery(api.nodes.list, {
|
||||
const current = getCanvasGraphNodesFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
if (current === undefined) return;
|
||||
@@ -615,20 +621,20 @@ export function useCanvasSyncEngine({
|
||||
zIndex: args.zIndex,
|
||||
};
|
||||
|
||||
localStore.setQuery(api.nodes.list, { canvasId: args.canvasId }, [
|
||||
...current,
|
||||
synthetic,
|
||||
]);
|
||||
setCanvasGraphNodesInQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
nodes: [...current, synthetic],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const createNodeWithEdgeFromSource = useMutation(
|
||||
api.nodes.createWithEdgeFromSource,
|
||||
).withOptimisticUpdate((localStore, args) => {
|
||||
const nodeList = localStore.getQuery(api.nodes.list, {
|
||||
const nodeList = getCanvasGraphNodesFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
const edgeList = localStore.getQuery(api.edges.list, {
|
||||
const edgeList = getCanvasGraphEdgesFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
if (nodeList === undefined || edgeList === undefined) return;
|
||||
@@ -674,23 +680,23 @@ export function useCanvasSyncEngine({
|
||||
targetHandle: args.targetHandle,
|
||||
};
|
||||
|
||||
localStore.setQuery(api.nodes.list, { canvasId: args.canvasId }, [
|
||||
...nodeList,
|
||||
syntheticNode,
|
||||
]);
|
||||
localStore.setQuery(api.edges.list, { canvasId: args.canvasId }, [
|
||||
...edgeList,
|
||||
syntheticEdge,
|
||||
]);
|
||||
setCanvasGraphNodesInQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
nodes: [...nodeList, syntheticNode],
|
||||
});
|
||||
setCanvasGraphEdgesInQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
edges: [...edgeList, syntheticEdge],
|
||||
});
|
||||
});
|
||||
|
||||
const createNodeWithEdgeToTarget = useMutation(
|
||||
api.nodes.createWithEdgeToTarget,
|
||||
).withOptimisticUpdate((localStore, args) => {
|
||||
const nodeList = localStore.getQuery(api.nodes.list, {
|
||||
const nodeList = getCanvasGraphNodesFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
const edgeList = localStore.getQuery(api.edges.list, {
|
||||
const edgeList = getCanvasGraphEdgesFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
if (nodeList === undefined || edgeList === undefined) return;
|
||||
@@ -736,24 +742,24 @@ export function useCanvasSyncEngine({
|
||||
targetHandle: args.targetHandle,
|
||||
};
|
||||
|
||||
localStore.setQuery(api.nodes.list, { canvasId: args.canvasId }, [
|
||||
...nodeList,
|
||||
syntheticNode,
|
||||
]);
|
||||
localStore.setQuery(api.edges.list, { canvasId: args.canvasId }, [
|
||||
...edgeList,
|
||||
syntheticEdge,
|
||||
]);
|
||||
setCanvasGraphNodesInQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
nodes: [...nodeList, syntheticNode],
|
||||
});
|
||||
setCanvasGraphEdgesInQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
edges: [...edgeList, syntheticEdge],
|
||||
});
|
||||
});
|
||||
|
||||
const createNodeWithEdgeSplitMut = useMutation(api.nodes.createWithEdgeSplit);
|
||||
|
||||
const createEdge = useMutation(api.edges.create).withOptimisticUpdate(
|
||||
(localStore, args) => {
|
||||
const edgeList = localStore.getQuery(api.edges.list, {
|
||||
const edgeList = getCanvasGraphEdgesFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
const nodeList = localStore.getQuery(api.nodes.list, {
|
||||
const nodeList = getCanvasGraphNodesFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
if (edgeList === undefined || nodeList === undefined) return;
|
||||
@@ -776,10 +782,10 @@ export function useCanvasSyncEngine({
|
||||
sourceHandle: args.sourceHandle,
|
||||
targetHandle: args.targetHandle,
|
||||
};
|
||||
localStore.setQuery(api.edges.list, { canvasId: args.canvasId }, [
|
||||
...edgeList,
|
||||
synthetic,
|
||||
]);
|
||||
setCanvasGraphEdgesInQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
edges: [...edgeList, synthetic],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1167,10 +1173,10 @@ export function useCanvasSyncEngine({
|
||||
const splitEdgeAtExistingNodeMut = useMutation(
|
||||
api.nodes.splitEdgeAtExistingNode,
|
||||
).withOptimisticUpdate((localStore, args) => {
|
||||
const edgeList = localStore.getQuery(api.edges.list, {
|
||||
const edgeList = getCanvasGraphEdgesFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
const nodeList = localStore.getQuery(api.nodes.list, {
|
||||
const nodeList = getCanvasGraphNodesFromQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
});
|
||||
if (edgeList === undefined || nodeList === undefined) return;
|
||||
@@ -1205,15 +1211,17 @@ export function useCanvasSyncEngine({
|
||||
targetHandle: args.splitTargetHandle,
|
||||
},
|
||||
);
|
||||
localStore.setQuery(api.edges.list, { canvasId: args.canvasId }, nextEdges);
|
||||
setCanvasGraphEdgesInQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
edges: nextEdges,
|
||||
});
|
||||
|
||||
if (args.positionX !== undefined && args.positionY !== undefined) {
|
||||
const px = args.positionX;
|
||||
const py = args.positionY;
|
||||
localStore.setQuery(
|
||||
api.nodes.list,
|
||||
{ canvasId: args.canvasId },
|
||||
nodeList.map((n: Doc<"nodes">) =>
|
||||
setCanvasGraphNodesInQuery(localStore, {
|
||||
canvasId: args.canvasId,
|
||||
nodes: nodeList.map((n: Doc<"nodes">) =>
|
||||
n._id === args.middleNodeId
|
||||
? {
|
||||
...n,
|
||||
@@ -1222,7 +1230,7 @@ export function useCanvasSyncEngine({
|
||||
}
|
||||
: n,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user