From de769627da0b348ab20a333ba0334edd936dd327 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 14 Apr 2026 19:37:08 +0200 Subject: [PATCH] Add functionality for resizable schedule pane in App.tsx --- src/app/App.tsx | 105 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/src/app/App.tsx b/src/app/App.tsx index e7f4c44e..b58cf40a 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -400,6 +400,10 @@ interface Vec3Draft { z: string; } +const DEFAULT_SCHEDULE_PANE_HEIGHT = 320; +const MIN_SCHEDULE_PANE_HEIGHT = 180; +const MIN_VIEWPORT_REGION_HEIGHT = 180; + interface PlayerStartMovementTemplateNumberDraft { moveSpeed: string; maxSpeed: string; @@ -2324,6 +2328,7 @@ export function App({ store, initialStatusMessage }: AppProps) { const importBackgroundImageInputRef = useRef(null); const importAudioInputRef = useRef(null); const viewportPanelsRef = useRef(null); + const editorMainRegionRef = useRef(null); const loadedModelAssetsRef = useRef>({}); const loadedImageAssetsRef = useRef>({}); const loadedAudioAssetsRef = useRef>({}); @@ -2342,6 +2347,16 @@ export function App({ store, initialStatusMessage }: AppProps) { }); const [viewportQuadResizeMode, setViewportQuadResizeMode] = useState(null); + const [schedulePaneHeight, setSchedulePaneHeight] = useState( + DEFAULT_SCHEDULE_PANE_HEIGHT + ); + const schedulePaneHeightRef = useRef(DEFAULT_SCHEDULE_PANE_HEIGHT); + const schedulePaneResizeStartRef = useRef<{ + startY: number; + startHeight: number; + } | null>(null); + const [schedulePaneResizeActive, setSchedulePaneResizeActive] = + useState(false); const documentValidation = validateSceneDocument(editorState.document); const projectValidation = validateProjectDocument( editorState.projectDocument @@ -2410,10 +2425,100 @@ export function App({ store, initialStatusMessage }: AppProps) { whiteboxSnapStep ); + const clampSchedulePaneHeight = (nextHeight: number) => { + const editorMainRegionHeight = + editorMainRegionRef.current?.clientHeight ?? + DEFAULT_SCHEDULE_PANE_HEIGHT + MIN_VIEWPORT_REGION_HEIGHT; + const maxHeight = Math.max( + 96, + editorMainRegionHeight - MIN_VIEWPORT_REGION_HEIGHT + ); + const minHeight = Math.min(MIN_SCHEDULE_PANE_HEIGHT, maxHeight); + + return Math.min(Math.max(nextHeight, minHeight), maxHeight); + }; + + const commitSchedulePaneHeight = ( + nextHeight: + | number + | ((previousHeight: number) => number) + ) => { + setSchedulePaneHeight((previousHeight) => { + const resolvedHeight = + typeof nextHeight === "function" ? nextHeight(previousHeight) : nextHeight; + const clampedHeight = clampSchedulePaneHeight(resolvedHeight); + schedulePaneHeightRef.current = clampedHeight; + return clampedHeight; + }); + }; + useEffect(() => { setPlayerStartKeyboardCaptureAction(null); }, [selectedPlayerStart?.id]); + useEffect(() => { + schedulePaneHeightRef.current = schedulePaneHeight; + }, [schedulePaneHeight]); + + useEffect(() => { + if (!schedulePaneOpen) { + setSchedulePaneResizeActive(false); + schedulePaneResizeStartRef.current = null; + return; + } + + const syncSchedulePaneHeight = () => { + commitSchedulePaneHeight(schedulePaneHeightRef.current); + }; + + syncSchedulePaneHeight(); + window.addEventListener("resize", syncSchedulePaneHeight); + + return () => { + window.removeEventListener("resize", syncSchedulePaneHeight); + }; + }, [schedulePaneOpen]); + + useEffect(() => { + if (!schedulePaneOpen || !schedulePaneResizeActive) { + return; + } + + const previousCursor = document.body.style.cursor; + const previousUserSelect = document.body.style.userSelect; + document.body.style.cursor = "row-resize"; + document.body.style.userSelect = "none"; + + const handlePointerMove = (event: globalThis.PointerEvent) => { + const resizeStart = schedulePaneResizeStartRef.current; + + if (resizeStart === null) { + return; + } + + commitSchedulePaneHeight( + resizeStart.startHeight - (event.clientY - resizeStart.startY) + ); + }; + + const stopSchedulePaneResize = () => { + schedulePaneResizeStartRef.current = null; + setSchedulePaneResizeActive(false); + }; + + window.addEventListener("pointermove", handlePointerMove); + window.addEventListener("pointerup", stopSchedulePaneResize); + window.addEventListener("pointercancel", stopSchedulePaneResize); + + return () => { + document.body.style.cursor = previousCursor; + document.body.style.userSelect = previousUserSelect; + window.removeEventListener("pointermove", handlePointerMove); + window.removeEventListener("pointerup", stopSchedulePaneResize); + window.removeEventListener("pointercancel", stopSchedulePaneResize); + }; + }, [schedulePaneOpen, schedulePaneResizeActive]); + useEffect(() => { if (playerStartKeyboardCaptureAction === null) { return;