Skip to content
Merged
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
62 changes: 39 additions & 23 deletions apps/web/src/components/ThreadTerminalDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,8 @@ function maxDrawerHeight(): number {
return Math.max(MIN_DRAWER_HEIGHT, Math.floor(window.innerHeight * MAX_DRAWER_HEIGHT_RATIO));
}

function clampDrawerHeight(height: number): number {
function clampDrawerHeight(height: number, maxHeight = maxDrawerHeight()): number {
const safeHeight = Number.isFinite(height) ? height : DEFAULT_THREAD_TERMINAL_HEIGHT;
const maxHeight = maxDrawerHeight();
return Math.min(Math.max(Math.round(safeHeight), MIN_DRAWER_HEIGHT), maxHeight);
}

Expand Down Expand Up @@ -1181,13 +1180,16 @@ export default function ThreadTerminalDrawer({
false,
Schema.Boolean,
);
const controlledDrawerHeight = clampDrawerHeight(height);
// The stored height is the user's preference; the window only clamps what renders.
const controlledDrawerHeight = height;
const [windowMaxDrawerHeight, setWindowMaxDrawerHeight] = useState(maxDrawerHeight);
const [drawerHeightState, setDrawerHeightState] = useState(() => ({
threadId,
height: controlledDrawerHeight,
}));
const drawerHeight =
const preferredDrawerHeight =
drawerHeightState.threadId === threadId ? drawerHeightState.height : controlledDrawerHeight;
const drawerHeight = clampDrawerHeight(preferredDrawerHeight, windowMaxDrawerHeight);
Comment thread
maria-rcks marked this conversation as resolved.
const setDrawerHeight = useCallback(
(update: SetStateAction<number>) => {
setDrawerHeightState((current) => {
Expand All @@ -1201,11 +1203,9 @@ export default function ThreadTerminalDrawer({
},
[controlledDrawerHeight, threadId],
);
const setDrawerHeightFromWindowResize = useEffectEvent((nextHeight: number) => {
setDrawerHeight(nextHeight);
});
const [resizeEpoch, setResizeEpoch] = useState(0);
const drawerHeightRef = useRef(drawerHeight);
const preferredDrawerHeightRef = useRef(preferredDrawerHeight);
const lastSyncedHeightRef = useRef(controlledDrawerHeight);
const onHeightChangeRef = useRef(onHeightChange);
const resizeStateRef = useRef<{
Expand Down Expand Up @@ -1395,15 +1395,24 @@ export default function ThreadTerminalDrawer({
onHeightChangeRef.current = onHeightChange;
}, [onHeightChange]);

useEffect(() => {
useLayoutEffect(() => {
// A render mid-drag (the window cap moved) must not replace the live height.
const resizeState = resizeStateRef.current;
if (resizeState) {
resizeState.drawer?.style.setProperty("height", `${drawerHeightRef.current}px`);
return;
}
drawerHeightRef.current = drawerHeight;
}, [drawerHeight]);

useEffect(() => {
preferredDrawerHeightRef.current = preferredDrawerHeight;
}, [preferredDrawerHeight]);

const syncHeight = useCallback((nextHeight: number) => {
const clampedHeight = clampDrawerHeight(nextHeight);
if (lastSyncedHeightRef.current === clampedHeight) return;
lastSyncedHeightRef.current = clampedHeight;
onHeightChangeRef.current(clampedHeight);
if (lastSyncedHeightRef.current === nextHeight) return;
lastSyncedHeightRef.current = nextHeight;
onHeightChangeRef.current(nextHeight);
}, []);

useEffect(() => {
Expand Down Expand Up @@ -1448,7 +1457,14 @@ export default function ThreadTerminalDrawer({
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
event.currentTarget.releasePointerCapture(event.pointerId);
}
if (!didResizeDuringDragRef.current) {
// A drag that ends where it started keeps the stored preference, which may
// be taller than the window-capped height the drag started from.
if (!didResizeDuringDragRef.current || drawerHeightRef.current === resizeState.startHeight) {
// React skips the write when the rendered height did not change, so put
// back the preference as the current window caps it.
const renderedHeight = clampDrawerHeight(preferredDrawerHeightRef.current);
drawerHeightRef.current = renderedHeight;
resizeState.drawer?.style.setProperty("height", `${renderedHeight}px`);
return;
}
setDrawerHeight(drawerHeightRef.current);
Expand All @@ -1464,33 +1480,33 @@ export default function ThreadTerminalDrawer({
}

const onWindowResize = () => {
const clampedHeight = clampDrawerHeight(drawerHeightRef.current);
const changed = clampedHeight !== drawerHeightRef.current;
if (changed) {
setDrawerHeightFromWindowResize(clampedHeight);
drawerHeightRef.current = clampedHeight;
}
if (!resizeStateRef.current) {
syncHeight(clampedHeight);
// React only renders the stored preference, so clamp a live drag height here.
const resizeState = resizeStateRef.current;
const liveHeight = clampDrawerHeight(drawerHeightRef.current);
if (resizeState && liveHeight !== drawerHeightRef.current) {
drawerHeightRef.current = liveHeight;
resizeState.drawer?.style.setProperty("height", `${liveHeight}px`);
}
setWindowMaxDrawerHeight(maxDrawerHeight());
setResizeEpoch((value) => value + 1);
};
window.addEventListener("resize", onWindowResize);
return () => {
window.removeEventListener("resize", onWindowResize);
};
}, [syncHeight, visible]);
}, [visible]);

useEffect(() => {
if (!visible) {
return;
}
setWindowMaxDrawerHeight(maxDrawerHeight());
setResizeEpoch((value) => value + 1);
}, [visible]);

useEffect(() => {
return () => {
syncHeight(drawerHeightRef.current);
syncHeight(preferredDrawerHeightRef.current);
};
}, [syncHeight]);

Expand Down
Loading