feat: refresh workbench visual layout

This commit is contained in:
2026-07-27 11:50:29 +08:00
parent b9bc9c3d39
commit f0485ca86e
33 changed files with 1624 additions and 353 deletions
@@ -9,6 +9,7 @@ import {
import {
WORKBENCH_LAYOUT,
clampAgentPanelWidth,
getAgentPanelMaxWidth,
getWorkbenchViewportLayout
} from "../layout/workbench-layout";
@@ -16,19 +17,31 @@ export function useAgentPanelResize(viewportWidth: number) {
const panelRef = useRef<HTMLDivElement | null>(null);
const resizeLeftRef = useRef(0);
const cleanupRef = useRef<(() => void) | null>(null);
const widthRef = useRef<number | null>(null);
const [width, setWidth] = useState<number | null>(null);
const [committedWidth, setCommittedWidth] = useState<number | null>(null);
const [resizing, setResizing] = useState(false);
useEffect(() => () => cleanupRef.current?.(), []);
useEffect(() => {
setWidth((currentWidth) =>
currentWidth === null ? null : clampAgentPanelWidth(currentWidth, viewportWidth)
);
if (widthRef.current === null) {
return;
}
const nextWidth = clampAgentPanelWidth(widthRef.current, viewportWidth);
widthRef.current = nextWidth;
setWidth(nextWidth);
setCommittedWidth(nextWidth);
}, [viewportWidth]);
const resize = useCallback((clientX: number) => {
setWidth(clampAgentPanelWidth(clientX - resizeLeftRef.current, window.innerWidth));
const nextWidth = clampAgentPanelWidth(
clientX - resizeLeftRef.current,
window.innerWidth
);
widthRef.current = nextWidth;
setWidth(nextWidth);
}, []);
const handlePointerDown = useCallback(
@@ -48,6 +61,7 @@ export function useAgentPanelResize(viewportWidth: number) {
const stopResizing = () => {
cleanupRef.current?.();
setResizing(false);
setCommittedWidth(widthRef.current);
};
const cleanup = () => {
window.removeEventListener("pointermove", handlePointerMove);
@@ -76,16 +90,20 @@ export function useAgentPanelResize(viewportWidth: number) {
} else if (event.key === "Home") {
nextWidth = getWorkbenchViewportLayout(window.innerWidth).agentWidth;
} else if (event.key === "End") {
nextWidth = window.innerWidth * WORKBENCH_LAYOUT.maxAgentViewportRatio;
nextWidth = getAgentPanelMaxWidth(window.innerWidth);
} else {
return;
}
event.preventDefault();
setWidth(clampAgentPanelWidth(nextWidth, window.innerWidth));
const clampedWidth = clampAgentPanelWidth(nextWidth, window.innerWidth);
widthRef.current = clampedWidth;
setWidth(clampedWidth);
setCommittedWidth(clampedWidth);
}, []);
return {
committedWidth,
handleKeyDown,
handlePointerDown,
panelRef,
@@ -7,15 +7,31 @@ export function useWorkbenchMapController({
mapRef,
mapReady,
leftPanelOpen,
rightPanelOpen
rightPanelOpen,
conditionPanelExpanded = false,
agentPanelWidth
}: {
mapRef: RefObject<MapLibreMap | null>;
mapReady: boolean;
leftPanelOpen: boolean;
rightPanelOpen: boolean;
conditionPanelExpanded?: boolean;
agentPanelWidth?: number;
}) {
const valuesRef = useRef({ mapReady, leftPanelOpen, rightPanelOpen });
valuesRef.current = { mapReady, leftPanelOpen, rightPanelOpen };
const valuesRef = useRef({
mapReady,
leftPanelOpen,
rightPanelOpen,
conditionPanelExpanded,
agentPanelWidth
});
valuesRef.current = {
mapReady,
leftPanelOpen,
rightPanelOpen,
conditionPanelExpanded,
agentPanelWidth
};
const controllerRef = useRef<WorkbenchMapController | null>(null);
if (!controllerRef.current) {
controllerRef.current = new WorkbenchMapController({
@@ -24,14 +40,24 @@ export function useWorkbenchMapController({
getPadding: () => {
const map = mapRef.current;
return map
? getResponsiveWorkbenchPadding(map, valuesRef.current.leftPanelOpen, valuesRef.current.rightPanelOpen)
? getResponsiveWorkbenchPadding(
map,
valuesRef.current.leftPanelOpen,
valuesRef.current.rightPanelOpen,
valuesRef.current.conditionPanelExpanded,
valuesRef.current.agentPanelWidth
)
: { top: 48, right: 48, bottom: 48, left: 48 };
}
});
}
const controller = controllerRef.current;
const state = useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
const state = useSyncExternalStore(
controller.subscribe,
controller.getSnapshot,
controller.getSnapshot
);
useEffect(() => () => controller.destroy(), [controller]);
return { controller, state };
}
@@ -47,8 +47,6 @@ declare global {
type UseWorkbenchMapOptions = {
containerRef: RefObject<HTMLDivElement | null>;
leftPanelOpen: boolean;
rightPanelOpen: boolean;
impactVisible: boolean;
onSelectFeature: (feature: DetailFeature) => void;
selectedFeature: DetailFeature | null;
@@ -88,23 +86,16 @@ const SOURCE_GROUP_BY_ID: Record<string, string> = {
export function useWorkbenchMap({
containerRef,
leftPanelOpen,
rightPanelOpen,
impactVisible,
onSelectFeature,
selectedFeature
}: UseWorkbenchMapOptions): UseWorkbenchMapResult {
const mapRef = useRef<MapLibreMap | null>(null);
const impactVisibleRef = useRef(impactVisible);
const layoutOpenRef = useRef({ leftPanelOpen, rightPanelOpen });
const [mapReady, setMapReady] = useState(false);
const [mapError, setMapError] = useState<string | null>(null);
const [sourceStatuses, setSourceStatuses] = useState<Record<string, WorkbenchSourceStatus>>({});
useEffect(() => {
layoutOpenRef.current = { leftPanelOpen, rightPanelOpen };
}, [leftPanelOpen, rightPanelOpen]);
useEffect(() => {
impactVisibleRef.current = impactVisible;
}, [impactVisible]);