Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions cueweb/app/utils/get_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ export type JobComment = {
message: string;
};

export type Depend = {
id: string;
type: string | number;
target: string | number;
anyFrame: boolean;
active: boolean;
dependErJob: string;
dependErLayer: string;
dependErFrame: string;
dependOnJob: string;
dependOnLayer: string;
dependOnFrame: string;
};

// Minimal Host shape - matches the host.Host proto fields the dashboard cares about.
export type Host = {
id: string;
Expand Down Expand Up @@ -143,6 +157,19 @@ export async function getFramesForJob(job: Job): Promise<Frame[]> {
return getFrames(JSON.stringify(body));
}

// Fetch all dependencies for a given job. Routes through the validated
// proxy at /api/job/action/getdepends (camelCase fields, double-
// nested response). Tolerates both nested and flat shapes so a gateway
// marshaller change can't silently break the consumer.
export async function getDependsForJob(job: Job): Promise<Depend[]> {
const ENDPOINT = "/api/job/action/getdepends";
const body = JSON.stringify({ job: { id: job.id, name: job.name } });
const data = await accessGetApi(ENDPOINT, body);
if (!data) return [];
const seq: any = data?.depends?.depends ?? data?.depends ?? data;
return Array.isArray(seq) ? (seq as Depend[]) : [];
}

// Get the job that a layer belongs to using the layer's parentId
export async function getJobForLayer(layer: Layer): Promise<Job | null> {
const body = JSON.stringify({ r: { include_finished: true, ids: [`${layer.parentId}`] } });
Expand Down
85 changes: 85 additions & 0 deletions cueweb/app/utils/use_show_dependency_graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright Contributors to the OpenCue Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

"use client";

import * as React from "react";

/**
* Shared show/hide state for the inline Dependency Graph panel.
*
* Persisted in localStorage under `cueweb.jobs.showDependencyGraph` and
* synchronised:
* - across components in the same tab via a CustomEvent dispatched on
* `window` (so the Cuetopia > View Job Graph menu item and the
* graph panel header stay in lockstep without prop drilling);
* - across tabs via the browser `storage` event.
*/

const STORAGE_KEY = "cueweb.jobs.showDependencyGraph";
export const SHOW_DEP_GRAPH_CHANGED_EVENT = "cueweb:show-dependency-graph-changed";

export function useShowDependencyGraph(): {
show: boolean;
set: (next: boolean) => void;
toggle: () => void;
} {
// Hydrate to false on first render so SSR and the first client paint
// agree; the effect below upgrades it from localStorage right away.
const [show, setShow] = React.useState<boolean>(false);

React.useEffect(() => {
if (typeof window === "undefined") return;
setShow(window.localStorage.getItem(STORAGE_KEY) === "1");
}, []);

React.useEffect(() => {
if (typeof window === "undefined") return;
const onCustom = (e: Event) => {
const detail = (e as CustomEvent<{ show: boolean }>).detail;
if (typeof detail?.show === "boolean") setShow(detail.show);
};
const onStorage = (e: StorageEvent) => {
if (e.key === STORAGE_KEY) setShow(e.newValue === "1");
};
window.addEventListener(SHOW_DEP_GRAPH_CHANGED_EVENT, onCustom);
window.addEventListener("storage", onStorage);
return () => {
window.removeEventListener(SHOW_DEP_GRAPH_CHANGED_EVENT, onCustom);
window.removeEventListener("storage", onStorage);
};
}, []);

const set = React.useCallback((next: boolean) => {
if (typeof window === "undefined") {
setShow(next);
return;
}
window.localStorage.setItem(STORAGE_KEY, next ? "1" : "0");
// Same-tab sync via CustomEvent. Other listeners pick it up
// synchronously; cross-tab listeners use the storage event above.
window.dispatchEvent(
new CustomEvent(SHOW_DEP_GRAPH_CHANGED_EVENT, { detail: { show: next } }),
);
setShow(next);
}, []);

const toggle = React.useCallback(() => {
set(!show);
}, [set, show]);

return { show, set, toggle };
}
27 changes: 27 additions & 0 deletions cueweb/components/ui/app-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { useAttributesPanel } from "@/app/utils/use_attributes_panel";
import { useCuebotFacility } from "@/app/utils/use_cuebot_facility";
import { useDisableJobInteraction } from "@/app/utils/use_disable_job_interaction";
import { useShortcutNotifications } from "@/app/utils/use_shortcut_notifications";
import { useShowDependencyGraph } from "@/app/utils/use_show_dependency_graph";
import { NAV_MENUS, type NavMenu } from "@/app/utils/menus";
import {
filterMenuCommands,
Expand Down Expand Up @@ -68,6 +69,11 @@ function NavMenuButton({
pathname: string | null;
}) {
const active = isMenuActive(pathname, menu);
// The Cuetopia menu gets an extra checkable "View Job Graph" entry
// that toggles the inline Dependency Graph panel in JobDetailsInline
// (mirrors CueGUI's `Cuetopia > Job Graph` toggle dock).
const isCuetopia = menu.label === "Cuetopia";
const { show: showGraph, toggle: toggleGraph } = useShowDependencyGraph();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
Expand Down Expand Up @@ -103,6 +109,27 @@ function NavMenuButton({
</DropdownMenuItem>
);
})}
{isCuetopia ? (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
onSelect={(e) => {
// Keep the menu open so the user can see the check
// appear before it dismisses on the next click outside.
e.preventDefault();
toggleGraph();
}}
className="cursor-pointer"
aria-checked={showGraph}
role="menuitemcheckbox"
>
<span className="mr-2 flex h-4 w-4 items-center justify-center">
{showGraph && <Check className="h-4 w-4" aria-hidden="true" />}
</span>
View Job Graph
</DropdownMenuItem>
</>
) : null}
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
49 changes: 49 additions & 0 deletions cueweb/components/ui/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
Receipt,
Send,
Server,
Share2,
Upload,
Wrench,
type LucideIcon,
Expand All @@ -69,6 +70,7 @@ import { HELP_ITEMS } from "@/app/utils/help_menu";
import { useAttributesPanel } from "@/app/utils/use_attributes_panel";
import { useCuebotFacility } from "@/app/utils/use_cuebot_facility";
import { useDisableJobInteraction } from "@/app/utils/use_disable_job_interaction";
import { useShowDependencyGraph } from "@/app/utils/use_show_dependency_graph";
import { useShortcutNotifications } from "@/app/utils/use_shortcut_notifications";
import { CUEWEB_OPEN_SHORTCUTS_EVENT } from "@/components/ui/shortcuts-overlay";
import { cn } from "@/lib/utils";
Expand Down Expand Up @@ -149,6 +151,10 @@ export function AppSidebar() {
const pathname = usePathname();
const { disabled: jobInteractionDisabled, toggle: toggleJobInteraction } =
useDisableJobInteraction();
// Cuetopia > View Job Graph toggle (CueGUI parity). Persisted via the
// shared hook so the dropdown's checked state and the inline panel
// stay in sync without prop drilling.
const { show: showGraph, toggle: toggleGraph } = useShowDependencyGraph();
const { facility, facilities, setFacility } = useCuebotFacility();
const {
isOpen: attributesOpen,
Expand Down Expand Up @@ -488,6 +494,27 @@ export function AppSidebar() {
</li>
);
})}
{group.label === "Cuetopia" ? (
<li>
<button
type="button"
onClick={toggleGraph}
aria-pressed={showGraph}
role="switch"
aria-checked={showGraph}
title={`Cuetopia - View Job Graph${showGraph ? " (on)" : ""}`}
Comment thread
ramonfigueiredo marked this conversation as resolved.
className={cn(
"flex w-full items-center justify-center rounded-md px-3 py-2 text-sm font-medium transition-colors",
showGraph
? "bg-foreground/10 text-foreground"
: "text-foreground/70 hover:bg-foreground/5 hover:text-foreground",
)}
>
<Share2 className="h-4 w-4 shrink-0" aria-hidden="true" />
<span className="sr-only">View Job Graph</span>
</button>
</li>
) : null}
</ul>
))
: // Expanded view: render each group as a Radix Collapsible
Expand Down Expand Up @@ -542,6 +569,28 @@ export function AppSidebar() {
</li>
);
})}
{group.label === "Cuetopia" ? (
<li>
<button
type="button"
onClick={toggleGraph}
role="switch"
aria-checked={showGraph}
className={cn(
"flex w-full items-center gap-3 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
showGraph
? "bg-foreground/10 text-foreground"
: "text-foreground/70 hover:bg-foreground/5 hover:text-foreground",
)}
>
<Share2 className="h-4 w-4 shrink-0" aria-hidden="true" />
<span className="flex-1 truncate text-left">View Job Graph</span>
<span className="flex h-4 w-4 items-center justify-center">
{showGraph && <Check className="h-4 w-4" aria-hidden="true" />}
</span>
</button>
</li>
) : null}
</ul>
</CollapsibleContent>
</Collapsible>
Expand Down
Loading
Loading