mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-22 09:34:53 +02:00
[Haik]: fixed dashboard z index ordering so most recently clicked card is always on top
This commit is contained in:
@@ -185,6 +185,8 @@ interface Props {
|
||||
onMeasuredHeight?: (sessionId: string, height: number) => void;
|
||||
snapColumn?: { x: number; width: number };
|
||||
autoFocusInput?: boolean;
|
||||
cardZOrder?: number;
|
||||
onBringToFront?: (id: string, type: 'agent' | 'view' | 'browser') => void;
|
||||
}
|
||||
|
||||
const MIN_W = 480;
|
||||
@@ -201,7 +203,7 @@ const SNAP_THRESHOLD = 60;
|
||||
const AgentCard: React.FC<Props> = ({
|
||||
session, expanded, cardX, cardY, cardWidth, cardHeight, zoom = 1, spawnFrom, exitTarget,
|
||||
isSelected = false, isHighlighted = false, multiDragDelta, onCardSelect, onDragStart, onDragMove, onDragEnd,
|
||||
onBranch, onMeasuredHeight, snapColumn, autoFocusInput,
|
||||
onBranch, onMeasuredHeight, snapColumn, autoFocusInput, cardZOrder = 0, onBringToFront,
|
||||
}) => {
|
||||
const c = useClaudeTokens();
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -464,9 +466,10 @@ const AgentCard: React.FC<Props> = ({
|
||||
animate={{ opacity: 1, scale: 1, left: activeX, top: activeY }}
|
||||
exit={exitAnimation}
|
||||
transition={spawnTransition}
|
||||
onPointerDownCapture={() => onBringToFront?.(session.id, 'agent')}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: isDragging || isResizing ? 999 : expanded ? 100 : 'auto',
|
||||
zIndex: isDragging || isResizing ? 999999 : cardZOrder,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@@ -539,7 +542,6 @@ const AgentCard: React.FC<Props> = ({
|
||||
boxShadow: c.shadow.sm,
|
||||
},
|
||||
},
|
||||
zIndex: 50,
|
||||
}),
|
||||
...(!isHighlighted && isGlowingRedux && !glowFading && {
|
||||
animation: 'agent-card-glow-pulse 2s ease-in-out infinite',
|
||||
|
||||
@@ -101,12 +101,15 @@ interface Props {
|
||||
onDragStart?: (id: string, type: 'agent' | 'view' | 'browser') => void;
|
||||
onDragMove?: (dx: number, dy: number) => void;
|
||||
onDragEnd?: (dx: number, dy: number, didDrag: boolean) => void;
|
||||
cardZOrder?: number;
|
||||
onBringToFront?: (id: string, type: 'agent' | 'view' | 'browser') => void;
|
||||
}
|
||||
|
||||
|
||||
const BrowserCard: React.FC<Props> = ({
|
||||
browserId, tabs, activeTabId, cardX, cardY, cardWidth, cardHeight, zoom = 1, cmdHeld = false,
|
||||
isSelected = false, isHighlighted = false, multiDragDelta, onCardSelect, onDragStart, onDragMove, onDragEnd,
|
||||
cardZOrder = 0, onBringToFront,
|
||||
}) => {
|
||||
const c = useClaudeTokens();
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -537,6 +540,7 @@ const BrowserCard: React.FC<Props> = ({
|
||||
data-select-type="browser-card"
|
||||
data-select-id={browserId}
|
||||
data-select-meta={JSON.stringify({ name: activeTitle || 'Browser', url: activeUrl })}
|
||||
onPointerDownCapture={() => onBringToFront?.(browserId, 'browser')}
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
if (justDraggedRef.current) return;
|
||||
onCardSelect?.(browserId, 'browser', e.shiftKey);
|
||||
@@ -554,7 +558,7 @@ const BrowserCard: React.FC<Props> = ({
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
zIndex: isHighlighted ? 50 : (isDragging || isResizing) ? 100 : (agentActive || showGlow) ? 50 : 1,
|
||||
zIndex: (isDragging || isResizing) ? 999999 : cardZOrder,
|
||||
transition: noTransition ? 'none' : 'box-shadow 0.4s ease, border 0.3s ease',
|
||||
'&:hover .resize-handle': { opacity: 1 },
|
||||
...(isHighlighted && {
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
pasteBrowserCard,
|
||||
placeCard,
|
||||
removeCard,
|
||||
bringToFront,
|
||||
setGlowingAgentCard,
|
||||
clearGlowingAgentCard,
|
||||
DEFAULT_CARD_W,
|
||||
@@ -205,6 +206,10 @@ const DashboardInner: React.FC = () => {
|
||||
selection.selectCard(id, type, shiftKey);
|
||||
}, [selection]);
|
||||
|
||||
const handleBringToFront = useCallback((id: string, type: CardType) => {
|
||||
dispatch(bringToFront({ id, type }));
|
||||
}, [dispatch]);
|
||||
|
||||
// ---- Viewport event handlers (compose pan + marquee) ----
|
||||
const handleViewportMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
if (e.button === 1) {
|
||||
@@ -1233,6 +1238,7 @@ const DashboardInner: React.FC = () => {
|
||||
cardY={card.y}
|
||||
cardWidth={card.width}
|
||||
cardHeight={card.height}
|
||||
cardZOrder={card.zOrder ?? 0}
|
||||
zoom={canvas.zoom}
|
||||
spawnFrom={origin}
|
||||
exitTarget={exitTarget}
|
||||
@@ -1247,6 +1253,7 @@ const DashboardInner: React.FC = () => {
|
||||
onMeasuredHeight={handleMeasuredHeight}
|
||||
snapColumn={snapColumn}
|
||||
autoFocusInput={autoFocusSessionId === session.id}
|
||||
onBringToFront={handleBringToFront}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -1262,6 +1269,7 @@ const DashboardInner: React.FC = () => {
|
||||
cardY={vc.y}
|
||||
cardWidth={vc.width}
|
||||
cardHeight={vc.height}
|
||||
cardZOrder={vc.zOrder ?? 0}
|
||||
zoom={canvas.zoom}
|
||||
cmdHeld={canvas.cmdHeld}
|
||||
isSelected={selection.isSelected(vc.output_id)}
|
||||
@@ -1271,6 +1279,7 @@ const DashboardInner: React.FC = () => {
|
||||
onDragStart={handleCardDragStart}
|
||||
onDragMove={handleCardDragMove}
|
||||
onDragEnd={handleCardDragEnd}
|
||||
onBringToFront={handleBringToFront}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -1284,6 +1293,7 @@ const DashboardInner: React.FC = () => {
|
||||
cardY={bc.y}
|
||||
cardWidth={bc.width}
|
||||
cardHeight={bc.height}
|
||||
cardZOrder={bc.zOrder ?? 0}
|
||||
zoom={canvas.zoom}
|
||||
cmdHeld={canvas.cmdHeld}
|
||||
isSelected={selection.isSelected(bc.browser_id)}
|
||||
@@ -1293,6 +1303,7 @@ const DashboardInner: React.FC = () => {
|
||||
onDragStart={handleCardDragStart}
|
||||
onDragMove={handleCardDragMove}
|
||||
onDragEnd={handleCardDragEnd}
|
||||
onBringToFront={handleBringToFront}
|
||||
/>
|
||||
))}
|
||||
{/* Marquee selection rectangle */}
|
||||
|
||||
@@ -54,11 +54,14 @@ interface Props {
|
||||
onDragStart?: (id: string, type: 'agent' | 'view') => void;
|
||||
onDragMove?: (dx: number, dy: number) => void;
|
||||
onDragEnd?: (dx: number, dy: number, didDrag: boolean) => void;
|
||||
cardZOrder?: number;
|
||||
onBringToFront?: (id: string, type: 'agent' | 'view' | 'browser') => void;
|
||||
}
|
||||
|
||||
const DashboardViewCard: React.FC<Props> = ({
|
||||
output, cardX, cardY, cardWidth, cardHeight, zoom = 1, cmdHeld = false,
|
||||
isSelected = false, isHighlighted = false, multiDragDelta, onCardSelect, onDragStart, onDragMove, onDragEnd,
|
||||
cardZOrder = 0, onBringToFront,
|
||||
}) => {
|
||||
const c = useClaudeTokens();
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -259,6 +262,7 @@ const DashboardViewCard: React.FC<Props> = ({
|
||||
data-select-type="view-card"
|
||||
data-select-id={output.id}
|
||||
data-select-meta={JSON.stringify({ name: output.name, description: output.description })}
|
||||
onPointerDownCapture={() => onBringToFront?.(output.id, 'view')}
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
if (justDraggedRef.current) return;
|
||||
onCardSelect?.(output.id, 'view', e.shiftKey);
|
||||
@@ -284,7 +288,7 @@ const DashboardViewCard: React.FC<Props> = ({
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
zIndex: isHighlighted ? 50 : (isDragging || isResizing) ? 100 : 1,
|
||||
zIndex: (isDragging || isResizing) ? 999999 : cardZOrder,
|
||||
transition: noTransition ? 'none' : 'box-shadow 0.2s',
|
||||
'&:hover .resize-handle': { opacity: 1 },
|
||||
...(isHighlighted && {
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface CardPosition {
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
zOrder: number;
|
||||
}
|
||||
|
||||
export interface ViewCardPosition {
|
||||
@@ -29,6 +30,7 @@ export interface ViewCardPosition {
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
zOrder: number;
|
||||
}
|
||||
|
||||
export interface BrowserTab {
|
||||
@@ -47,6 +49,7 @@ export interface BrowserCardPosition {
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
zOrder: number;
|
||||
}
|
||||
|
||||
export interface DashboardLayoutState {
|
||||
@@ -57,6 +60,7 @@ export interface DashboardLayoutState {
|
||||
glowingBrowserCards: Record<string, string>;
|
||||
glowingAgentCards: Record<string, { sourceId: string; fading: boolean; sourceYRatio?: number; label?: string }>;
|
||||
persistedExpandedSessionIds: string[];
|
||||
nextZOrder: number;
|
||||
loading: boolean;
|
||||
initialized: boolean;
|
||||
}
|
||||
@@ -69,6 +73,7 @@ const initialState: DashboardLayoutState = {
|
||||
glowingBrowserCards: {},
|
||||
glowingAgentCards: {},
|
||||
persistedExpandedSessionIds: [],
|
||||
nextZOrder: 1,
|
||||
loading: false,
|
||||
initialized: false,
|
||||
};
|
||||
@@ -223,7 +228,25 @@ const dashboardLayoutSlice = createSlice({
|
||||
action: PayloadAction<{ sessionId: string; x: number; y: number; width: number; height: number }>
|
||||
) {
|
||||
const { sessionId, x, y, width, height } = action.payload;
|
||||
state.cards[sessionId] = { session_id: sessionId, x, y, width, height };
|
||||
state.cards[sessionId] = { session_id: sessionId, x, y, width, height, zOrder: state.nextZOrder++ };
|
||||
},
|
||||
|
||||
bringToFront(
|
||||
state,
|
||||
action: PayloadAction<{ id: string; type: 'agent' | 'view' | 'browser' }>,
|
||||
) {
|
||||
const { id, type } = action.payload;
|
||||
const z = state.nextZOrder++;
|
||||
if (type === 'agent') {
|
||||
const card = state.cards[id];
|
||||
if (card) card.zOrder = z;
|
||||
} else if (type === 'view') {
|
||||
const card = state.viewCards[id];
|
||||
if (card) card.zOrder = z;
|
||||
} else {
|
||||
const card = state.browserCards[id];
|
||||
if (card) card.zOrder = z;
|
||||
}
|
||||
},
|
||||
|
||||
removeCard(state, action: PayloadAction<string>) {
|
||||
@@ -250,7 +273,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
if (hasDraftCard && !id.startsWith('draft-')) continue;
|
||||
const savedPos = state.closedCardPositions[id];
|
||||
if (savedPos) {
|
||||
state.cards[id] = { ...savedPos, session_id: id };
|
||||
state.cards[id] = { ...savedPos, session_id: id, zOrder: savedPos.zOrder || state.nextZOrder++ };
|
||||
delete state.closedCardPositions[id];
|
||||
} else {
|
||||
const rects = collectOccupiedRects(state, expandedSessionIds);
|
||||
@@ -261,6 +284,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
y: pos.y,
|
||||
width: DEFAULT_CARD_W,
|
||||
height: DEFAULT_CARD_H,
|
||||
zOrder: state.nextZOrder++,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -334,6 +358,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
y: posY,
|
||||
width: width || DEFAULT_VIEW_CARD_W,
|
||||
height: height || DEFAULT_VIEW_CARD_H,
|
||||
zOrder: state.nextZOrder++,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -376,6 +401,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
y: pos.y,
|
||||
width: DEFAULT_BROWSER_CARD_W,
|
||||
height: DEFAULT_BROWSER_CARD_H,
|
||||
zOrder: state.nextZOrder++,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -386,6 +412,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
...card,
|
||||
width: card.width || DEFAULT_BROWSER_CARD_W,
|
||||
height: card.height || DEFAULT_BROWSER_CARD_H,
|
||||
zOrder: card.zOrder || state.nextZOrder++,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -449,6 +476,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
y: posY,
|
||||
width: width || DEFAULT_BROWSER_CARD_W,
|
||||
height: height || DEFAULT_BROWSER_CARD_H,
|
||||
zOrder: state.nextZOrder++,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -645,6 +673,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
state.glowingBrowserCards = {};
|
||||
state.glowingAgentCards = {};
|
||||
state.persistedExpandedSessionIds = [];
|
||||
state.nextZOrder = 1;
|
||||
state.initialized = false;
|
||||
},
|
||||
|
||||
@@ -661,6 +690,22 @@ const dashboardLayoutSlice = createSlice({
|
||||
state.viewCards = action.payload.viewCards;
|
||||
state.browserCards = action.payload.browserCards;
|
||||
state.persistedExpandedSessionIds = action.payload.expandedSessionIds;
|
||||
|
||||
// Ensure all cards have a zOrder and compute nextZOrder from persisted data
|
||||
let maxZ = 0;
|
||||
for (const c of Object.values(state.cards)) {
|
||||
if (!c.zOrder) c.zOrder = 0;
|
||||
if (c.zOrder > maxZ) maxZ = c.zOrder;
|
||||
}
|
||||
for (const c of Object.values(state.viewCards)) {
|
||||
if (!c.zOrder) c.zOrder = 0;
|
||||
if (c.zOrder > maxZ) maxZ = c.zOrder;
|
||||
}
|
||||
for (const c of Object.values(state.browserCards)) {
|
||||
if (!c.zOrder) c.zOrder = 0;
|
||||
if (c.zOrder > maxZ) maxZ = c.zOrder;
|
||||
}
|
||||
state.nextZOrder = maxZ + 1;
|
||||
})
|
||||
.addCase(fetchLayout.rejected, (state) => {
|
||||
state.loading = false;
|
||||
@@ -671,7 +716,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
const card = state.cards[draftId];
|
||||
if (card) {
|
||||
delete state.cards[draftId];
|
||||
state.cards[session.id] = { ...card, session_id: session.id };
|
||||
state.cards[session.id] = { ...card, session_id: session.id, zOrder: state.nextZOrder++ };
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -682,6 +727,7 @@ export const {
|
||||
placeCard,
|
||||
setCardSize,
|
||||
removeCard,
|
||||
bringToFront,
|
||||
reconcileSessions,
|
||||
replaceDraftId,
|
||||
tidyLayout,
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user