diff --git a/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx b/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx index f5ccc2e2..f07dd8bb 100644 --- a/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx @@ -7,10 +7,6 @@ import Tooltip from '@mui/material/Tooltip'; import RefreshIcon from '@mui/icons-material/Refresh'; import GridViewRoundedIcon from '@mui/icons-material/GridViewRounded'; import VisibilityRoundedIcon from '@mui/icons-material/VisibilityRounded'; -import CodeRoundedIcon from '@mui/icons-material/CodeRounded'; -import TerminalRoundedIcon from '@mui/icons-material/TerminalRounded'; -import HistoryRoundedIcon from '@mui/icons-material/HistoryRounded'; -import AddIcon from '@mui/icons-material/Add'; import KeyboardArrowUpRounded from '@mui/icons-material/KeyboardArrowUpRounded'; import { Output, SERVE_BASE, updateOutput } from '@/shared/state/outputsSlice'; import { setViewCardPosition, setViewDocked, setViewCardSize, setActiveViewCardId, recordClosedCard, addViewCard, toggleMinimizeCard, activateViewCardPreview } from '@/shared/state/dashboardLayoutSlice'; @@ -21,6 +17,8 @@ import { expandSession } from '@/shared/state/agentsSlice'; import WindowControls from './WindowControls'; import { openCardContextMenu, isNativeMenuTarget } from '../desktop/openCardContextMenu'; import { viewCardMenuRows } from './viewCardMenuRows'; +import MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded'; +import type { CardMenuRow } from '../desktop/openCardContextMenu'; import { useDragEndBackstops } from '../hooks/interaction/useDragEndBackstops'; import { useTiledCard } from './useTiledCard'; import { useCardTiling } from './useCardTiling'; @@ -648,6 +646,31 @@ const DashboardViewCard: React.FC = ({ const dockActive = !!dockRect && !dragging && !localResize && !isTiled && !isMinimized; const tiledSize = useTiledCard({ cardId: cardKey, zone: tileZone, active: !isMinimized, originX: displayX, originY: displayY, getCamera: getCanvasState }); + // The old 8-icon header cluster, demoted behind one kebab: view switcher, new window, toolbar + // collapse, then the same rows the card's right-click menu carries. + const headerKebabRows = (): CardMenuRow[] => ([ + ...(hasWorkspace ? [ + ...([['preview', 'Preview'], ['code', 'Code'], ['terminal', 'Terminal'], ['history', 'History']] as const).map(([view, label]) => ({ + label: activeView === view ? `${label} ✓` : label, + onClick: () => setActiveView(view), + })), + { kind: 'separator' as const }, + { label: 'Open another window', onClick: () => dispatch(addViewCard({ outputId: output.id, newInstance: true })) }, + ] : []), + { label: headerCollapsed ? 'Show toolbar' : 'Hide toolbar', onClick: () => { setHeaderPeek(false); setHeaderCollapsed((v) => !v); } }, + { kind: 'separator' as const }, + ...viewCardMenuRows({ + output, cardKey, dispatch, tileZone, isMinimized, + card: { x: cardX, y: cardY, width: cardWidth, height: cardHeight }, + onTile, + onMinimize: () => (isMinimized ? dispatch(toggleMinimizeCard({ cardId: cardKey })) : onMinimize()), + onReload: () => previewRef.current?.reload(), + onHardReload: () => { void handleHardReload(); }, + onShare: () => setShareOpen(true), + onClose: () => handleRemove(), + }), + ]); + return ( = ({ display: 'flex', alignItems: 'center', gap: 0.75, - px: 1.5, - py: 0.75, - bgcolor: c.bg.secondary, - borderBottom: `1px solid ${c.border.subtle}`, + px: 1, + py: 0.25, + // Fullscreen flips the bar to the dark edge-to-edge system-chrome treatment (Zen/Safari + // compact), so it reads as window chrome instead of a floating card fragment. + bgcolor: isFullscreen ? '#211d19' : c.bg.secondary, + borderBottom: `1px solid ${isFullscreen ? 'rgba(255,255,255,0.08)' : c.border.subtle}`, cursor: isDragging ? 'grabbing' : 'grab', flexShrink: 0, - minHeight: 36, + minHeight: 28, userSelect: 'none', }} > e.stopPropagation()} sx={{ display: 'flex', alignItems: 'center', flexShrink: 0, mr: 0.25 }}> handleRemove()} onMinimize={onMinimize} onTile={onTile} tiled={!!tileZone} /> - + {output.name} + {instance > 1 && ( + + #{instance} + + )} - {instance > 1 && ( - - #{instance} - - )} {showControls && ( <> - {hasWorkspace && ( - e.stopPropagation()} - sx={{ - display: 'flex', - alignItems: 'center', - gap: 0.25, - bgcolor: c.bg.page, - borderRadius: 999, - p: 0.25, - flexShrink: 0, - }} - > - {([ - { view: 'preview' as const, label: 'Preview', Icon: VisibilityRoundedIcon }, - { view: 'code' as const, label: 'Code', Icon: CodeRoundedIcon }, - { view: 'terminal' as const, label: 'Terminal', Icon: TerminalRoundedIcon }, - { view: 'history' as const, label: 'History', Icon: HistoryRoundedIcon }, - ]).map(({ view, label, Icon }) => ( - - { e.stopPropagation(); setActiveView(view); }} - sx={{ - p: 0.5, - borderRadius: 999, - color: activeView === view ? c.text.primary : c.text.ghost, - bgcolor: activeView === view ? c.bg.elevated : 'transparent', - '&:hover': { color: c.text.primary, bgcolor: activeView === view ? c.bg.elevated : `${c.text.primary}0a` }, - }} - > - - - - ))} - - )} - - e.stopPropagation()} sx={{ display: 'flex', flexShrink: 0 }}> - - - = ({ }); }} onPointerDown={(e) => e.stopPropagation()} - sx={{ color: c.text.muted, p: 0.5, '&:hover': { color: c.text.primary } }} + sx={{ color: isFullscreen ? 'rgba(232,226,216,0.6)' : c.text.muted, p: 0.5, '&:hover': { color: isFullscreen ? '#fff' : c.text.primary } }} > - + - - {hasWorkspace && ( - - e.stopPropagation()} - sx={{ color: c.text.ghost, p: 0.5, '&:hover': { color: c.text.primary } }} - > - - - - )} + e.stopPropagation()} sx={{ display: 'flex', flexShrink: 0 }}> + + )} - + {isFullscreen && ( + { e.stopPropagation(); onTile('restore'); }} + onPointerDown={(e) => e.stopPropagation()} + sx={{ flexShrink: 0, fontSize: '0.6875rem', fontWeight: 600, color: '#e8e2d8', bgcolor: 'rgba(255,255,255,0.1)', borderRadius: 999, px: 1.25, py: 0.35, cursor: 'pointer', '&:hover': { bgcolor: 'rgba(255,255,255,0.18)' } }} + > + Exit full screen + + )} + + {/* Everything demoted from the old 8-icon cluster lives here: view switcher, new window, toolbar collapse, and the card menu. */} + { e.stopPropagation(); setHeaderPeek(false); setHeaderCollapsed((v) => !v); }} + onClick={(e) => { e.stopPropagation(); openCardContextMenu(e, { items: headerKebabRows() }); }} onPointerDown={(e) => e.stopPropagation()} - sx={{ color: c.text.ghost, p: 0.5, '&:hover': { color: c.text.primary } }} + sx={{ color: isFullscreen ? 'rgba(232,226,216,0.6)' : c.text.ghost, p: 0.5, '&:hover': { color: isFullscreen ? '#fff' : c.text.primary } }} > - +