Refactor canvas storage URL handling and integrate adjustment presets
- Introduced a new `CanvasPresetsProvider` to manage adjustment presets for nodes, enhancing state management and reducing reactivity. - Updated storage URL resolution to utilize a mutation instead of a reactive query, improving performance and reducing unnecessary re-renders. - Refactored adjustment nodes (color-adjust, curves, detail-adjust, light-adjust) to use the new preset context for fetching user presets. - Improved overall canvas functionality by streamlining storage ID collection and URL resolution processes.
This commit is contained in:
@@ -29,6 +29,7 @@ app/(app)/canvas/[canvasId]/page.tsx
|
||||
| Datei | Zweck |
|
||||
|------|-------|
|
||||
| `canvas-helpers.ts` | Shared Utility-Layer (Optimistic IDs, Node-Merge, Compare-Resolution, Edge/Hit-Helpers, Konstante Defaults) |
|
||||
| `canvas-presets-context.tsx` | Shared Preset-Provider für Adjustment-Nodes; bündelt `presets.list` zu einer einzigen Query |
|
||||
| `canvas-node-change-helpers.ts` | Dimensions-/Resize-Transformationen für `asset` und `ai-image` Nodes |
|
||||
| `canvas-generation-failures.ts` | Hook für AI-Generation-Error-Tracking mit Schwellenwert-Toast |
|
||||
| `canvas-scissors.ts` | Hook für Scherenmodus (K/Esc Toggle, Click-Cut, Stroke-Cut) |
|
||||
@@ -53,7 +54,9 @@ Convex und React Flow verwenden unterschiedliche Datenmodelle. Das Mapping liegt
|
||||
|
||||
**Status-Injection:** `convexNodeToRF` schreibt `_status`, `_statusMessage` und `retryCount` in `data`, damit Node-Komponenten darauf zugreifen können ohne das Node-Dokument direkt zu kennen.
|
||||
|
||||
**URL-Caching:** Images mit `storageId` werden über einen batch-Storage-URL-Query aufgelöst (`urlByStorage`-Map). Die vorherige URL wird in `previousDataByNodeId` gecacht, um Flackern beim Reload zu vermeiden.
|
||||
**URL-Caching:** Images mit `storageId` werden im Canvas nicht mehr über eine reaktive Query aufgelöst. `canvas.tsx` sammelt die aktuellen `storageId`s aus `nodes.list` und ruft `storage.batchGetUrlsForCanvas` gezielt per Mutation auf, nur wenn sich das Set ändert. Die vorherige URL wird in `previousDataByNodeId` gecacht, um Flackern beim Reload zu vermeiden.
|
||||
|
||||
**Load-Shedding-Hot-Path:** Der Canvas-Hot-Path soll so wenig Convex-Abhängigkeiten wie möglich haben. Direkt reaktiv bleiben nur die Kernmodelle (`nodes.list`, `edges.list`, `canvases.get`). Nebenpfade wie Storage-URL-Auflösung, Adjustment-Presets und Toolbar-Credits sind bewusst entkoppelt oder zusammengefasst.
|
||||
|
||||
---
|
||||
|
||||
@@ -146,7 +149,7 @@ Im **Light Mode** wird der eigentliche Edge-`stroke` ebenfalls aus dieser Akzent
|
||||
| `asset-browser-panel.tsx` | Freepik/Stock-Asset-Browser |
|
||||
| `video-browser-panel.tsx` | Video-Asset-Browser |
|
||||
| `canvas-user-menu.tsx` | User-Avatar und Menü |
|
||||
| `credit-display.tsx` | Credit-Balance Anzeige in der Toolbar |
|
||||
| `credit-display.tsx` | Credit-Balance Anzeige in der Toolbar (nur `credits.getBalance`, kein Tier-Badge) |
|
||||
| `export-button.tsx` | Export-Button mit Format-Auswahl |
|
||||
| `connection-banner.tsx` | Offline-Banner bei Convex-Verbindungsverlust |
|
||||
| `custom-connection-line.tsx` | Angepasste temporäre Verbindungslinie |
|
||||
@@ -168,6 +171,7 @@ Im **Light Mode** wird der eigentliche Edge-`stroke` ebenfalls aus dieser Akzent
|
||||
## Wichtige Gotchas
|
||||
|
||||
- **`data.url` vs `storageId`:** Node-Komponenten erhalten `data.url` (aufgelöste HTTP-URL), nicht `storageId` direkt. Die URL wird von `convexNodeDocWithMergedStorageUrl` injiziert. Bei neuen Node-Typen mit Bild immer diesen Flow prüfen.
|
||||
- **Adjustment-Presets:** `curves`, `color-adjust`, `light-adjust` und `detail-adjust` dürfen keine eigene `presets.list`-Query feuern. Immer `CanvasPresetsProvider` + `useCanvasAdjustmentPresets(...)` verwenden.
|
||||
- **Min-Zoom:** `CANVAS_MIN_ZOOM = 0.5 / 3` — dreimal weiter raus als React-Flow-Default.
|
||||
- **Parent-Nodes:** `parentId` zeigt auf einen Group- oder Frame-Node. React Flow erwartet, dass Parent-Nodes vor Child-Nodes in der `nodes`-Array stehen.
|
||||
- **Bridge-Edges:** Beim Löschen eines mittleren Nodes werden Kanten automatisch neu verbunden (`computeBridgeCreatesForDeletedNodes` aus `lib/canvas-utils.ts`).
|
||||
|
||||
56
components/canvas/canvas-presets-context.tsx
Normal file
56
components/canvas/canvas-presets-context.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useMemo, type ReactNode } from "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[]>;
|
||||
|
||||
const CanvasPresetsContext = createContext<PresetsByNodeType | null>(null);
|
||||
|
||||
type CanvasPresetsProviderProps = {
|
||||
enabled?: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export function CanvasPresetsProvider({
|
||||
enabled = true,
|
||||
children,
|
||||
}: CanvasPresetsProviderProps) {
|
||||
const rawPresets = useAuthQuery(api.presets.list, enabled ? {} : "skip");
|
||||
|
||||
const presetsByNodeType = useMemo<PresetsByNodeType>(() => {
|
||||
const next = new Map<AdjustmentPresetDoc["nodeType"], AdjustmentPresetDoc[]>();
|
||||
|
||||
for (const preset of (rawPresets ?? []) as AdjustmentPresetDoc[]) {
|
||||
const existing = next.get(preset.nodeType);
|
||||
if (existing) {
|
||||
existing.push(preset);
|
||||
} else {
|
||||
next.set(preset.nodeType, [preset]);
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}, [rawPresets]);
|
||||
|
||||
return (
|
||||
<CanvasPresetsContext.Provider value={presetsByNodeType}>
|
||||
{children}
|
||||
</CanvasPresetsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useCanvasAdjustmentPresets(
|
||||
nodeType: AdjustmentPresetDoc["nodeType"],
|
||||
): AdjustmentPresetDoc[] {
|
||||
const context = useContext(CanvasPresetsContext);
|
||||
if (context === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return context.get(nodeType) ?? [];
|
||||
}
|
||||
@@ -72,6 +72,7 @@ import { api } from "@/convex/_generated/api";
|
||||
import type { Doc, Id } from "@/convex/_generated/dataModel";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import {
|
||||
isAdjustmentPresetNodeType,
|
||||
isCanvasNodeType,
|
||||
type CanvasNodeType,
|
||||
} from "@/lib/canvas-node-types";
|
||||
@@ -95,6 +96,7 @@ import {
|
||||
type ConnectionDropMenuState,
|
||||
} from "@/components/canvas/canvas-connection-drop-menu";
|
||||
import { CanvasPlacementProvider } from "@/components/canvas/canvas-placement-context";
|
||||
import { CanvasPresetsProvider } from "@/components/canvas/canvas-presets-context";
|
||||
import {
|
||||
AssetBrowserTargetContext,
|
||||
type AssetBrowserTargetApi,
|
||||
@@ -203,11 +205,6 @@ function summarizeResizePayload(payload: unknown): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
function hasStorageId(node: Doc<"nodes">): boolean {
|
||||
const data = node.data as Record<string, unknown> | undefined;
|
||||
return typeof data?.storageId === "string" && data.storageId.length > 0;
|
||||
}
|
||||
|
||||
function validateCanvasConnection(
|
||||
connection: Connection,
|
||||
nodes: RFNode[],
|
||||
@@ -261,10 +258,6 @@ function CanvasInner({ canvasId }: CanvasInnerProps) {
|
||||
const { isLoading: isAuthLoading, isAuthenticated } = useConvexAuth();
|
||||
const shouldSkipCanvasQueries =
|
||||
isSessionPending || isAuthLoading || !isAuthenticated;
|
||||
const convexAuthUserProbe = useQuery(
|
||||
api.auth.safeGetAuthUser,
|
||||
shouldSkipCanvasQueries ? "skip" : {},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (process.env.NODE_ENV === "production") return;
|
||||
@@ -282,8 +275,6 @@ function CanvasInner({ canvasId }: CanvasInnerProps) {
|
||||
convex: {
|
||||
isAuthenticated,
|
||||
shouldSkipCanvasQueries,
|
||||
probeUserId: convexAuthUserProbe?.userId ?? null,
|
||||
probeRecordId: convexAuthUserProbe?._id ?? null,
|
||||
},
|
||||
session: {
|
||||
hasUser: Boolean(session?.user),
|
||||
@@ -292,8 +283,6 @@ function CanvasInner({ canvasId }: CanvasInnerProps) {
|
||||
});
|
||||
}, [
|
||||
canvasId,
|
||||
convexAuthUserProbe?._id,
|
||||
convexAuthUserProbe?.userId,
|
||||
isAuthLoading,
|
||||
isAuthenticated,
|
||||
isSessionPending,
|
||||
@@ -310,20 +299,71 @@ function CanvasInner({ canvasId }: CanvasInnerProps) {
|
||||
api.edges.list,
|
||||
shouldSkipCanvasQueries ? "skip" : { canvasId },
|
||||
);
|
||||
const shouldSkipStorageUrlQuery = useMemo(() => {
|
||||
if (shouldSkipCanvasQueries) return true;
|
||||
if (convexNodes === undefined) return true;
|
||||
return !convexNodes.some(hasStorageId);
|
||||
}, [convexNodes, shouldSkipCanvasQueries]);
|
||||
const storageUrlsById = useQuery(
|
||||
api.storage.batchGetUrlsForCanvas,
|
||||
shouldSkipStorageUrlQuery ? "skip" : { canvasId },
|
||||
);
|
||||
const storageIdsForCanvas = useMemo(() => {
|
||||
if (!convexNodes) {
|
||||
return [] as Id<"_storage">[];
|
||||
}
|
||||
|
||||
return [...new Set(
|
||||
convexNodes.flatMap((node) => {
|
||||
const data = node.data as Record<string, unknown> | undefined;
|
||||
return typeof data?.storageId === "string" && data.storageId.length > 0
|
||||
? [data.storageId as Id<"_storage">]
|
||||
: [];
|
||||
}),
|
||||
)].sort();
|
||||
}, [convexNodes]);
|
||||
const storageIdsForCanvasKey = storageIdsForCanvas.join(",");
|
||||
const stableStorageIdsForCanvasRef = useRef(storageIdsForCanvas);
|
||||
if (stableStorageIdsForCanvasRef.current.join(",") !== storageIdsForCanvasKey) {
|
||||
stableStorageIdsForCanvasRef.current = storageIdsForCanvas;
|
||||
}
|
||||
const resolveStorageUrlsForCanvas = useMutation(api.storage.batchGetUrlsForCanvas);
|
||||
const [storageUrlsById, setStorageUrlsById] = useState<Record<string, string | undefined>>();
|
||||
const canvas = useQuery(
|
||||
api.canvases.get,
|
||||
shouldSkipCanvasQueries ? "skip" : { canvasId },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const requestedStorageIds = stableStorageIdsForCanvasRef.current;
|
||||
|
||||
if (shouldSkipCanvasQueries || requestedStorageIds.length === 0) {
|
||||
setStorageUrlsById(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
void resolveStorageUrlsForCanvas({
|
||||
canvasId,
|
||||
storageIds: requestedStorageIds,
|
||||
})
|
||||
.then((result) => {
|
||||
if (!cancelled) {
|
||||
setStorageUrlsById(result);
|
||||
}
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (!cancelled) {
|
||||
console.warn("[Canvas] failed to resolve storage URLs", {
|
||||
canvasId,
|
||||
storageIdCount: requestedStorageIds.length,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [
|
||||
canvasId,
|
||||
resolveStorageUrlsForCanvas,
|
||||
shouldSkipCanvasQueries,
|
||||
storageIdsForCanvasKey,
|
||||
]);
|
||||
|
||||
// ─── Convex Mutations (exakte Signaturen aus nodes.ts / edges.ts) ──
|
||||
const moveNode = useMutation(api.nodes.move);
|
||||
const resizeNode = useMutation(api.nodes.resize);
|
||||
@@ -571,6 +611,12 @@ function CanvasInner({ canvasId }: CanvasInnerProps) {
|
||||
|
||||
const [nodes, setNodes] = useState<RFNode[]>([]);
|
||||
const [edges, setEdges] = useState<RFEdge[]>([]);
|
||||
const hasPresetAwareNodes = useMemo(
|
||||
() =>
|
||||
nodes.some((node) => isAdjustmentPresetNodeType(node.type ?? "")) ||
|
||||
(convexNodes ?? []).some((node) => isAdjustmentPresetNodeType(node.type)),
|
||||
[convexNodes, nodes],
|
||||
);
|
||||
const edgesRef = useRef(edges);
|
||||
edgesRef.current = edges;
|
||||
const [pendingSyncCount, setPendingSyncCount] = useState(0);
|
||||
@@ -2970,24 +3016,25 @@ function CanvasInner({ canvasId }: CanvasInnerProps) {
|
||||
|
||||
return (
|
||||
<CanvasSyncProvider value={canvasSyncContextValue}>
|
||||
<CanvasPlacementProvider
|
||||
canvasId={canvasId}
|
||||
createNode={runCreateNodeOnlineOnly}
|
||||
createNodeWithEdgeSplit={runCreateNodeWithEdgeSplitOnlineOnly}
|
||||
createNodeWithEdgeFromSource={runCreateNodeWithEdgeFromSourceOnlineOnly}
|
||||
createNodeWithEdgeToTarget={runCreateNodeWithEdgeToTargetOnlineOnly}
|
||||
onCreateNodeSettled={({ clientRequestId, realId }) => {
|
||||
void syncPendingMoveForClientRequest(clientRequestId, realId).catch(
|
||||
(error: unknown) => {
|
||||
console.error(
|
||||
"[Canvas] onCreateNodeSettled syncPendingMove failed",
|
||||
error,
|
||||
);
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
<AssetBrowserTargetContext.Provider value={assetBrowserTargetApi}>
|
||||
<CanvasPresetsProvider enabled={hasPresetAwareNodes}>
|
||||
<CanvasPlacementProvider
|
||||
canvasId={canvasId}
|
||||
createNode={runCreateNodeOnlineOnly}
|
||||
createNodeWithEdgeSplit={runCreateNodeWithEdgeSplitOnlineOnly}
|
||||
createNodeWithEdgeFromSource={runCreateNodeWithEdgeFromSourceOnlineOnly}
|
||||
createNodeWithEdgeToTarget={runCreateNodeWithEdgeToTargetOnlineOnly}
|
||||
onCreateNodeSettled={({ clientRequestId, realId }) => {
|
||||
void syncPendingMoveForClientRequest(clientRequestId, realId).catch(
|
||||
(error: unknown) => {
|
||||
console.error(
|
||||
"[Canvas] onCreateNodeSettled syncPendingMove failed",
|
||||
error,
|
||||
);
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
<AssetBrowserTargetContext.Provider value={assetBrowserTargetApi}>
|
||||
<div className="relative h-full w-full">
|
||||
<CanvasToolbar
|
||||
canvasName={canvas?.name ?? "canvas"}
|
||||
@@ -3079,8 +3126,9 @@ function CanvasInner({ canvasId }: CanvasInnerProps) {
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</div>
|
||||
</AssetBrowserTargetContext.Provider>
|
||||
</CanvasPlacementProvider>
|
||||
</AssetBrowserTargetContext.Provider>
|
||||
</CanvasPlacementProvider>
|
||||
</CanvasPresetsProvider>
|
||||
</CanvasSyncProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,22 +7,6 @@ import { api } from "@/convex/_generated/api";
|
||||
import { Coins } from "lucide-react";
|
||||
import { toast } from "@/lib/toast";
|
||||
|
||||
const TIER_LABELS: Record<string, string> = {
|
||||
free: "Free",
|
||||
starter: "Starter",
|
||||
pro: "Pro",
|
||||
max: "Max",
|
||||
business: "Business",
|
||||
};
|
||||
|
||||
const TIER_COLORS: Record<string, string> = {
|
||||
free: "text-muted-foreground",
|
||||
starter: "text-blue-500",
|
||||
pro: "text-purple-500",
|
||||
max: "text-amber-500",
|
||||
business: "text-amber-500",
|
||||
};
|
||||
|
||||
const showTestCreditGrant =
|
||||
typeof process.env.NEXT_PUBLIC_ALLOW_TEST_CREDIT_GRANT === "string" &&
|
||||
process.env.NEXT_PUBLIC_ALLOW_TEST_CREDIT_GRANT === "true";
|
||||
@@ -30,10 +14,9 @@ const showTestCreditGrant =
|
||||
export function CreditDisplay() {
|
||||
const t = useTranslations('toasts');
|
||||
const balance = useAuthQuery(api.credits.getBalance);
|
||||
const subscription = useAuthQuery(api.credits.getSubscription);
|
||||
const grantTestCredits = useMutation(api.credits.grantTestCredits);
|
||||
|
||||
if (balance === undefined || subscription === undefined) {
|
||||
if (balance === undefined) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-lg bg-muted/50 px-3 py-1.5 animate-pulse">
|
||||
<Coins className="h-4 w-4 text-muted-foreground" />
|
||||
@@ -43,9 +26,6 @@ export function CreditDisplay() {
|
||||
}
|
||||
|
||||
const available = balance.balance - balance.reserved;
|
||||
const tier = subscription.tier;
|
||||
const tierLabel = TIER_LABELS[tier] ?? tier;
|
||||
const tierColor = TIER_COLORS[tier] ?? "text-muted-foreground";
|
||||
|
||||
const isLow = available < 10;
|
||||
const isEmpty = available <= 0;
|
||||
@@ -82,8 +62,6 @@ export function CreditDisplay() {
|
||||
({balance.reserved} reserved)
|
||||
</span>
|
||||
)}
|
||||
<span className="text-xs text-muted-foreground/70">·</span>
|
||||
<span className={`text-xs font-medium ${tierColor}`}>{tierLabel}</span>
|
||||
</div>
|
||||
{showTestCreditGrant && (
|
||||
<button
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Palette } from "lucide-react";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { useAuthQuery } from "@/hooks/use-auth-query";
|
||||
import { useCanvasAdjustmentPresets } from "@/components/canvas/canvas-presets-context";
|
||||
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
|
||||
import { useCanvasSync } from "@/components/canvas/canvas-sync-context";
|
||||
import BaseNodeWrapper from "@/components/canvas/nodes/base-node-wrapper";
|
||||
@@ -47,7 +47,7 @@ export default function ColorAdjustNode({ id, data, selected, width }: NodeProps
|
||||
const tToasts = useTranslations("toasts");
|
||||
const { queueNodeDataUpdate } = useCanvasSync();
|
||||
const savePreset = useMutation(api.presets.save);
|
||||
const userPresets = (useAuthQuery(api.presets.list, { nodeType: "color-adjust" }) ?? []) as PresetDoc[];
|
||||
const userPresets = useCanvasAdjustmentPresets("color-adjust") as PresetDoc[];
|
||||
|
||||
const [localData, setLocalData] = useState<ColorAdjustData>(() =>
|
||||
normalizeColorAdjustData({ ...cloneAdjustmentData(DEFAULT_COLOR_ADJUST_DATA), ...data }),
|
||||
|
||||
@@ -8,7 +8,7 @@ import { TrendingUp } from "lucide-react";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { useAuthQuery } from "@/hooks/use-auth-query";
|
||||
import { useCanvasAdjustmentPresets } from "@/components/canvas/canvas-presets-context";
|
||||
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
|
||||
import { useCanvasSync } from "@/components/canvas/canvas-sync-context";
|
||||
import BaseNodeWrapper from "@/components/canvas/nodes/base-node-wrapper";
|
||||
@@ -47,7 +47,7 @@ export default function CurvesNode({ id, data, selected, width }: NodeProps<Curv
|
||||
const tToasts = useTranslations("toasts");
|
||||
const { queueNodeDataUpdate } = useCanvasSync();
|
||||
const savePreset = useMutation(api.presets.save);
|
||||
const userPresets = (useAuthQuery(api.presets.list, { nodeType: "curves" }) ?? []) as PresetDoc[];
|
||||
const userPresets = useCanvasAdjustmentPresets("curves") as PresetDoc[];
|
||||
|
||||
const [localData, setLocalData] = useState<CurvesData>(() =>
|
||||
normalizeCurvesData({ ...cloneAdjustmentData(DEFAULT_CURVES_DATA), ...data }),
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Focus } from "lucide-react";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { useAuthQuery } from "@/hooks/use-auth-query";
|
||||
import { useCanvasAdjustmentPresets } from "@/components/canvas/canvas-presets-context";
|
||||
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
|
||||
import { useCanvasSync } from "@/components/canvas/canvas-sync-context";
|
||||
import BaseNodeWrapper from "@/components/canvas/nodes/base-node-wrapper";
|
||||
@@ -47,7 +47,7 @@ export default function DetailAdjustNode({ id, data, selected, width }: NodeProp
|
||||
const tToasts = useTranslations("toasts");
|
||||
const { queueNodeDataUpdate } = useCanvasSync();
|
||||
const savePreset = useMutation(api.presets.save);
|
||||
const userPresets = (useAuthQuery(api.presets.list, { nodeType: "detail-adjust" }) ?? []) as PresetDoc[];
|
||||
const userPresets = useCanvasAdjustmentPresets("detail-adjust") as PresetDoc[];
|
||||
|
||||
const [localData, setLocalData] = useState<DetailAdjustData>(() =>
|
||||
normalizeDetailAdjustData({ ...cloneAdjustmentData(DEFAULT_DETAIL_ADJUST_DATA), ...data }),
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Sun } from "lucide-react";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { useAuthQuery } from "@/hooks/use-auth-query";
|
||||
import { useCanvasAdjustmentPresets } from "@/components/canvas/canvas-presets-context";
|
||||
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
|
||||
import { useCanvasSync } from "@/components/canvas/canvas-sync-context";
|
||||
import BaseNodeWrapper from "@/components/canvas/nodes/base-node-wrapper";
|
||||
@@ -47,7 +47,7 @@ export default function LightAdjustNode({ id, data, selected, width }: NodeProps
|
||||
const tToasts = useTranslations("toasts");
|
||||
const { queueNodeDataUpdate } = useCanvasSync();
|
||||
const savePreset = useMutation(api.presets.save);
|
||||
const userPresets = (useAuthQuery(api.presets.list, { nodeType: "light-adjust" }) ?? []) as PresetDoc[];
|
||||
const userPresets = useCanvasAdjustmentPresets("light-adjust") as PresetDoc[];
|
||||
|
||||
const [localData, setLocalData] = useState<LightAdjustData>(() =>
|
||||
normalizeLightAdjustData({ ...cloneAdjustmentData(DEFAULT_LIGHT_ADJUST_DATA), ...data }),
|
||||
|
||||
Reference in New Issue
Block a user