Files
next-tjwater-frontend/src/features/workbench/hooks/use-workbench-responsive-layout.ts
T
jiang 7a304a91a8
Generic Container CI/CD / test-build-publish (push) Successful in 1m13s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m14s
feat(workbench): dock task and chart workspaces
2026-11-12 11:42:25 +08:00

201 lines
6.3 KiB
TypeScript

import { useEffect, useState } from "react";
import type { MobileWorkbenchSheetSnap } from "../components/mobile-workbench-sheet";
import { WORKBENCH_LAYOUT } from "../layout/workbench-layout";
import { CONDITION_PANE_MIN_WIDTH, type ArtifactRequestedView } from "../workspace/workspace-model";
const CONDITION_FEED_EXIT_MS = 170;
const CONDITION_FEED_EXPANDED_MIN_WIDTH = 1440;
const LARGE_SCREEN_QUERY = "(min-width: 1024px)";
type WorkbenchMobileSheet = "agent" | "condition" | "timeline" | null;
type UseWorkbenchResponsiveLayoutOptions = {
activeToolOpen: boolean;
agentPanelOpen: boolean;
devPanelOpen: boolean;
collapseAgentPanel: () => void;
expandAgentPanel: () => void;
onClearActiveTool: () => void;
};
export function useWorkbenchResponsiveLayout({
activeToolOpen,
agentPanelOpen,
devPanelOpen,
collapseAgentPanel,
expandAgentPanel,
onClearActiveTool
}: UseWorkbenchResponsiveLayoutOptions) {
const [isLargeScreen, setIsLargeScreen] = useState(false);
const [viewportWidth, setViewportWidth] = useState<number>(WORKBENCH_LAYOUT.desktopMinWidth);
const [agentPanelWidth, setAgentPanelWidth] = useState<number>(
WORKBENCH_LAYOUT.desktop.agentWidth
);
const [conditionFeedVisible, setConditionFeedVisible] = useState(false);
const [conditionFeedMounted, setConditionFeedMounted] = useState(false);
const [conditionFeedExpanded, setConditionFeedExpanded] = useState(false);
const [conditionFeedDocked, setConditionFeedDocked] = useState(false);
const [conditionView, setConditionView] = useState<ArtifactRequestedView>("map_split");
const [mobileSheet, setMobileSheet] = useState<WorkbenchMobileSheet>(null);
const [mobileSheetSnap, setMobileSheetSnap] = useState<MobileWorkbenchSheetSnap>("half");
const [agentCollapsedForCondition, setAgentCollapsedForCondition] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia(LARGE_SCREEN_QUERY);
const handleViewportChange = () => {
setIsLargeScreen(mediaQuery.matches);
setViewportWidth(window.innerWidth);
};
handleViewportChange();
setConditionFeedVisible(false);
mediaQuery.addEventListener("change", handleViewportChange);
window.addEventListener("resize", handleViewportChange);
return () => {
mediaQuery.removeEventListener("change", handleViewportChange);
window.removeEventListener("resize", handleViewportChange);
};
}, []);
useEffect(() => {
if (!isLargeScreen || mobileSheet === null) {
return;
}
if (mobileSheet === "agent") {
expandAgentPanel();
}
setMobileSheet(null);
}, [expandAgentPanel, isLargeScreen, mobileSheet]);
const shouldShowConditionFeed = conditionFeedVisible && !activeToolOpen;
useEffect(() => {
if (shouldShowConditionFeed) {
setConditionFeedMounted(true);
return;
}
const timeoutId = window.setTimeout(() => {
setConditionFeedMounted(false);
}, CONDITION_FEED_EXIT_MS);
return () => window.clearTimeout(timeoutId);
}, [shouldShowConditionFeed]);
function closeMobileSheet() {
setMobileSheet(null);
}
function openAgentPanelForViewport() {
if (isLargeScreen) {
if (conditionFeedDocked && viewportWidth - agentPanelWidth < CONDITION_PANE_MIN_WIDTH) {
setConditionFeedDocked(false);
setConditionFeedExpanded(false);
}
if (!conditionFeedDocked && conditionFeedExpanded && viewportWidth < CONDITION_FEED_EXPANDED_MIN_WIDTH) {
setConditionFeedExpanded(false);
setAgentCollapsedForCondition(false);
}
expandAgentPanel();
return;
}
setMobileSheet("agent");
setMobileSheetSnap("full");
}
function handleConditionExpandedChange(nextExpanded: boolean) {
if (isLargeScreen) {
setConditionFeedDocked(nextExpanded);
if (nextExpanded) setConditionView("map_split");
}
if (!nextExpanded && agentCollapsedForCondition) {
setAgentCollapsedForCondition(false);
expandAgentPanel();
}
setConditionFeedExpanded(nextExpanded);
}
useEffect(() => {
if (isLargeScreen && conditionFeedDocked && shouldShowConditionFeed && agentPanelOpen
&& viewportWidth - agentPanelWidth < CONDITION_PANE_MIN_WIDTH) {
setAgentCollapsedForCondition(true);
collapseAgentPanel();
}
}, [agentPanelOpen, agentPanelWidth, collapseAgentPanel, conditionFeedDocked, isLargeScreen, shouldShowConditionFeed, viewportWidth]);
useEffect(() => {
if (!isLargeScreen || shouldShowConditionFeed || !conditionFeedExpanded) return;
setConditionFeedExpanded(false);
setConditionFeedDocked(false);
if (agentCollapsedForCondition) {
setAgentCollapsedForCondition(false);
expandAgentPanel();
}
}, [agentCollapsedForCondition, conditionFeedExpanded, expandAgentPanel, isLargeScreen, shouldShowConditionFeed]);
function toggleConditionFeedForViewport() {
onClearActiveTool();
if (isLargeScreen) {
setConditionFeedVisible((current) => !current);
return;
}
if (mobileSheet === "condition") {
closeMobileSheet();
return;
}
setConditionFeedExpanded(false);
setMobileSheetSnap("half");
setMobileSheet("condition");
}
function openConditionFeedForViewport() {
setConditionFeedVisible(true);
onClearActiveTool();
if (!isLargeScreen) {
setMobileSheet("condition");
setMobileSheetSnap("half");
}
}
function openTimelineForViewport() {
if (isLargeScreen) return;
setMobileSheet("timeline");
setMobileSheetSnap("half");
}
const leftPanelOpen = isLargeScreen && agentPanelOpen;
const rightPanelOpen = isLargeScreen && (devPanelOpen || (shouldShowConditionFeed && !conditionFeedDocked));
const rightPanelExpanded =
isLargeScreen && shouldShowConditionFeed && !conditionFeedDocked && conditionFeedExpanded && !devPanelOpen;
return {
agentPanelWidth,
closeMobileSheet,
conditionFeedExpanded,
conditionFeedDocked,
conditionView,
conditionFeedMounted,
handleConditionExpandedChange,
isLargeScreen,
leftPanelOpen,
mobileSheet,
mobileSheetSnap,
openAgentPanelForViewport,
openConditionFeedForViewport,
openTimelineForViewport,
rightPanelExpanded,
rightPanelOpen,
setAgentPanelWidth,
setConditionView,
setMobileSheetSnap,
shouldShowConditionFeed,
toggleConditionFeedForViewport,
viewportWidth
};
}