mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-28 12:34:50 +02:00
[eric] canvas: a card tiled to fill the screen hides the floating chrome the same way fullscreen does, so tidy and zoom stop sitting on its composer (ENG-259)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, type RefObject } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
|
||||
import { addViewCard, addBrowserTab, clearTiledCard, toggleMinimizeCard, selectFullscreenCardId } from '@/shared/state/dashboardLayoutSlice';
|
||||
import { addViewCard, addBrowserTab, clearTiledCard, toggleMinimizeCard, selectFullscreenCardId, selectViewportCoveringCardId } from '@/shared/state/dashboardLayoutSlice';
|
||||
import { store } from '@/shared/state/store';
|
||||
import { buildDockEntries } from '../desktop/dockEntries';
|
||||
import DashboardHeader from './DashboardHeader';
|
||||
@@ -188,6 +188,8 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
// macOS full screen: one card owns the whole window, every piece of chrome steps aside; Esc exits.
|
||||
const dispatch = useAppDispatch();
|
||||
const fullscreenCardId = useAppSelector(selectFullscreenCardId);
|
||||
// Chrome hides because a card OWNS THE SCREEN, which `fill` does just as much as `fullscreen`.
|
||||
const screenOwnedByCard = !!useAppSelector(selectViewportCoveringCardId);
|
||||
const minimizedCards = useAppSelector((s) => s.dashboardLayout.minimizedCards);
|
||||
const anyFullscreen = !!fullscreenCardId;
|
||||
const [headerRevealed, setHeaderRevealed] = React.useState(false);
|
||||
@@ -458,7 +460,7 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
willChange: 'transform',
|
||||
transform: `translate3d(${canvas.panX % dotSpacing}px, ${canvas.panY % dotSpacing}px, 0)`,
|
||||
// Arc fullscreen: the float sits on a clean themed ground, the dot texture is canvas-only.
|
||||
display: anyFullscreen ? 'none' : undefined,
|
||||
display: screenOwnedByCard ? 'none' : undefined,
|
||||
backgroundImage: gridTileUrl,
|
||||
backgroundSize: `${dotSpacing}px ${dotSpacing}px`,
|
||||
}}
|
||||
@@ -517,7 +519,7 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
</Box>
|
||||
|
||||
{/* display:contents when visible so the overlays' absolute children keep positioning against the canvas root; display:none (not unmount) so the toolbar composer draft survives fullscreen. */}
|
||||
<Box sx={{ display: fullscreenCardId ? 'none' : 'contents' }}>
|
||||
<Box sx={{ display: screenOwnedByCard ? 'none' : 'contents' }}>
|
||||
<DashboardOverlays
|
||||
anyFullscreen={anyFullscreen}
|
||||
canvas={canvas}
|
||||
|
||||
@@ -34,6 +34,7 @@ export const MARKETPLACE_CARD_ID = 'marketplace';
|
||||
export const DEFAULT_MARKETPLACE_CARD_W = DEFAULT_BROWSER_CARD_W;
|
||||
export const DEFAULT_MARKETPLACE_CARD_H = DEFAULT_BROWSER_CARD_H;
|
||||
export const WORKFLOWS_HUB_ID = 'workflows-hub';
|
||||
export const WORKFLOWS_MONITOR_ID = 'workflows-monitor';
|
||||
export const EXPANDED_CARD_MIN_H = 620;
|
||||
export const GRID_GAP = 24;
|
||||
// Gap between the Workflows window and the cards it spawns (run monitor, that monitor's browser). Keeps the hub -> monitor -> browser row evenly spaced.
|
||||
@@ -256,7 +257,10 @@ interface LayoutPayload {
|
||||
function tileOwnerExists(s: DashboardLayoutState, id: string): boolean {
|
||||
return id in s.cards || id in s.viewCards || id in s.browserCards || id in s.workflowCards
|
||||
|| (id === WORKFLOWS_HUB_ID && !!s.workflowsHub) || (id === SETTINGS_CARD_ID && !!s.settingsCard)
|
||||
|| (id === MARKETPLACE_CARD_ID && !!s.marketplaceCard);
|
||||
|| (id === MARKETPLACE_CARD_ID && !!s.marketplaceCard)
|
||||
// The Run Monitor is a real tileable surface everywhere else in this file; leaving it out here
|
||||
// would silently report its fullscreen as "owner gone" and leave the canvas chrome on top of it.
|
||||
|| (id === WORKFLOWS_MONITOR_ID && !!s.workflowsMonitorCard);
|
||||
}
|
||||
|
||||
function ledgerAdd(ledger: string[], id: string): void {
|
||||
@@ -2138,6 +2142,18 @@ export const reopenLastClosed = createAsyncThunk(
|
||||
}
|
||||
);
|
||||
|
||||
// A card in the `fill` zone covers the whole viewport just like `fullscreen` does, but it is not
|
||||
// 'fullscreen', so the canvas chrome stayed up and the tidy/zoom cluster sat directly on the card's
|
||||
// composer (measured: 4 controls over a filled card, 0 over a fullscreen one). Anything that hides
|
||||
// chrome because a card owns the screen must ask THIS, not the fullscreen question.
|
||||
export const selectViewportCoveringCardId = (state: { dashboardLayout: DashboardLayoutState }): string | null => {
|
||||
const s = state.dashboardLayout;
|
||||
const entry = Object.entries(s.tiledCards).find(([, zone]) => zone === 'fullscreen' || zone === 'fill');
|
||||
if (!entry) return null;
|
||||
const id = entry[0];
|
||||
return tileOwnerExists(s, id) && !s.minimizedCards[id] ? id : null;
|
||||
};
|
||||
|
||||
export const selectFullscreenCardId = (state: { dashboardLayout: DashboardLayoutState }): string | null => {
|
||||
const s = state.dashboardLayout;
|
||||
const entry = Object.entries(s.tiledCards).find(([, zone]) => zone === 'fullscreen');
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Run: node --test frontend/src/shared/state/selectViewportCoveringCardId.test.ts
|
||||
//
|
||||
// The canvas hides its floating chrome when a card owns the screen. That used to be keyed on the
|
||||
// 'fullscreen' zone alone, but TILE_ZONES also has `fill`, which covers the whole viewport and is a
|
||||
// different string. Measured live before the fix: 4 controls (minimap, Tidy layout, Zoom out, Zoom
|
||||
// in) sitting on top of a filled card, versus 0 on a fullscreen one. These pin the predicate.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { selectViewportCoveringCardId, selectFullscreenCardId } from './dashboardLayoutSlice.ts';
|
||||
|
||||
function stateWith(tiledCards: Record<string, string>, extra: Record<string, unknown> = {}): any {
|
||||
return {
|
||||
dashboardLayout: {
|
||||
tiledCards,
|
||||
minimizedCards: {},
|
||||
cards: { chat1: { x: 0, y: 0, width: 480, height: 300 } },
|
||||
viewCards: {}, browserCards: {}, workflowCards: {},
|
||||
workflowsHub: null, settingsCard: null, marketplaceCard: null, workflowsMonitorCard: null,
|
||||
...extra,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('a fullscreen card owns the screen', () => {
|
||||
assert.equal(selectViewportCoveringCardId(stateWith({ chat1: 'fullscreen' })), 'chat1');
|
||||
});
|
||||
|
||||
test('a FILLED card owns the screen too, which is the whole bug', () => {
|
||||
assert.equal(selectViewportCoveringCardId(stateWith({ chat1: 'fill' })), 'chat1');
|
||||
});
|
||||
|
||||
test('a half-screen tile does not own the screen, so chrome stays', () => {
|
||||
for (const zone of ['left', 'right', 'top', 'bottom', 'tl', 'tr', 'bl', 'br', 't3l', 't3c', 't3r']) {
|
||||
assert.equal(selectViewportCoveringCardId(stateWith({ chat1: zone })), null, `zone ${zone}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('nothing tiled means nothing owns the screen', () => {
|
||||
assert.equal(selectViewportCoveringCardId(stateWith({})), null);
|
||||
});
|
||||
|
||||
test('a minimized card cannot own the screen', () => {
|
||||
const s = stateWith({ chat1: 'fill' }, { minimizedCards: { chat1: true } });
|
||||
assert.equal(selectViewportCoveringCardId(s), null);
|
||||
});
|
||||
|
||||
test('a tile whose card is gone cannot own the screen', () => {
|
||||
const s = stateWith({ ghost: 'fill' });
|
||||
assert.equal(selectViewportCoveringCardId(s), null);
|
||||
});
|
||||
|
||||
test('the Run Monitor counts as a real tile owner', () => {
|
||||
// It was missing from tileOwnerExists, so its fullscreen read as "owner gone" and left the canvas
|
||||
// chrome on top of it. Latent today (no tiling UI on that card) but a hole in a load-bearing check.
|
||||
const s = stateWith({ 'workflows-monitor': 'fullscreen' },
|
||||
{ workflowsMonitorCard: { x: 0, y: 0, width: 900, height: 600, zOrder: 1 } });
|
||||
assert.equal(selectViewportCoveringCardId(s), 'workflows-monitor');
|
||||
assert.equal(selectFullscreenCardId(s), 'workflows-monitor');
|
||||
});
|
||||
|
||||
test('fullscreen and covering stay different questions', () => {
|
||||
// Only chrome-hiding should treat `fill` as fullscreen; window buttons and the exit pill must not.
|
||||
const filled = stateWith({ chat1: 'fill' });
|
||||
assert.equal(selectViewportCoveringCardId(filled), 'chat1');
|
||||
assert.equal(selectFullscreenCardId(filled), null);
|
||||
});
|
||||
Reference in New Issue
Block a user