mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-12 20:57:42 +02:00
[eric] canvas: right-drag marquee-selects the empty canvas again; the menu waits for a click-only release
This commit is contained in:
@@ -74,7 +74,8 @@ interface DashboardCanvasProps {
|
||||
getCanvasState: () => { panX: number; panY: number; zoom: number };
|
||||
onViewportMouseDown: (e: React.MouseEvent) => void;
|
||||
onViewportMouseMove: (e: React.MouseEvent) => void;
|
||||
onViewportMouseUp: (e: React.MouseEvent) => void;
|
||||
/** Returns true when the release ended a marquee drag rather than a plain click. */
|
||||
onViewportMouseUp: (e: React.MouseEvent) => boolean;
|
||||
onViewportDoubleClick: (e: React.MouseEvent) => void;
|
||||
onCardSelect: (id: string, type: CardType, shiftKey: boolean, originTarget?: EventTarget | null) => void;
|
||||
onDragStart: (id: string, type: CardType) => void;
|
||||
@@ -173,12 +174,10 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
const dispatch = useAppDispatch();
|
||||
const fullscreenCardId = useAppSelector(selectFullscreenCardId);
|
||||
const minimizedCards = useAppSelector((s) => s.dashboardLayout.minimizedCards);
|
||||
// The singleton app windows (Workflows, Settings) carry their own fullscreen flag, not a tiledCard; their fill also hides the dock.
|
||||
const settingsFullscreen = useAppSelector((s) => !!s.dashboardLayout.settingsCard?.fullscreen);
|
||||
const anyFullscreen = !!fullscreenCardId || !!workflowsHub?.fullscreen || settingsFullscreen;
|
||||
const anyFullscreen = !!fullscreenCardId;
|
||||
const [headerRevealed, setHeaderRevealed] = React.useState(false);
|
||||
const [appsWindowOpen, setAppsWindowOpen] = React.useState(false);
|
||||
const onCanvasContextMenu = useCanvasContextMenu({
|
||||
const openCanvasMenu = useCanvasContextMenu({
|
||||
dispatch, dashboardId, expandedSessionIds, selection, canvasEmpty,
|
||||
viewportRef: canvas.viewportRef, getCamera: canvas.actions.getLiveState,
|
||||
onNewAgent, onAddBrowser, onApplications: () => setAppsWindowOpen(true), onTidy, onFitToView,
|
||||
@@ -254,7 +253,8 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
// page->transparent fade here just read as a light-leak band over the themed canvas.
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', pointerEvents: 'auto' }}>
|
||||
{/* Must follow the reveal: an always-auto child overrides the hidden overlay's pointer-events:none and swallowed the whole top strip, so a top/left-tiled window's traffic lights were unclickable. */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', pointerEvents: headerRevealed ? 'auto' : 'none' }}>
|
||||
<DashboardHeader
|
||||
dashboardName={dashboardName}
|
||||
sessions={sessions}
|
||||
@@ -320,9 +320,18 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
data-canvas-viewport
|
||||
onMouseDown={onViewportMouseDown}
|
||||
onMouseMove={onViewportMouseMove}
|
||||
onMouseUp={onViewportMouseUp}
|
||||
onMouseUp={(e) => {
|
||||
const marqueed = onViewportMouseUp(e);
|
||||
// The right button belongs to the marquee, so the canvas menu waits for the release and
|
||||
// only opens when nothing was rubber-banded. Opening on press stole the drag.
|
||||
if (e.button === 2 && !marqueed) openCanvasMenu(e);
|
||||
}}
|
||||
onDoubleClick={onViewportDoubleClick}
|
||||
onContextMenu={onCanvasContextMenu}
|
||||
onContextMenu={(e: React.MouseEvent) => {
|
||||
// Bare canvas: kill the native menu (Inspect Element in dev) so the right-drag stays clean.
|
||||
const t = e.target as HTMLElement;
|
||||
if (!t.closest('[data-select-id]') && !t.closest('input, textarea, [contenteditable]')) e.preventDefault();
|
||||
}}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
|
||||
@@ -24,6 +24,8 @@ interface CanvasContextMenuArgs {
|
||||
export function useCanvasContextMenu(args: CanvasContextMenuArgs): (e: React.MouseEvent) => void {
|
||||
const { dispatch, dashboardId, expandedSessionIds, selection, canvasEmpty, viewportRef, getCamera } = args;
|
||||
const { onNewAgent, onAddBrowser, onApplications, onTidy, onFitToView } = args;
|
||||
// Fired from the viewport's mouseUP (never contextmenu): the right button starts the marquee, so the
|
||||
// menu may only appear once the release proves the gesture was a click.
|
||||
return useCallback((e: React.MouseEvent) => {
|
||||
// Bare canvas only; cards own their own menus and inputs/webviews keep the native one.
|
||||
const t = e.target as HTMLElement;
|
||||
|
||||
@@ -206,9 +206,11 @@ export function useDashboardInteractions({
|
||||
selection.handleCanvasMouseMove(e.nativeEvent);
|
||||
}, [canvas.handlers, selection]);
|
||||
|
||||
const handleViewportMouseUp = useCallback((e: React.MouseEvent) => {
|
||||
// Reports whether the release ended a marquee drag; the canvas uses that to tell a right-DRAG
|
||||
// (rubber band) from a right-CLICK (context menu) without ever arbitrating on mousedown.
|
||||
const handleViewportMouseUp = useCallback((e: React.MouseEvent): boolean => {
|
||||
canvas.handlers.onMouseUp();
|
||||
selection.handleCanvasMouseUp(e.nativeEvent);
|
||||
return selection.handleCanvasMouseUp(e.nativeEvent);
|
||||
}, [canvas.handlers, selection]);
|
||||
|
||||
// Double-click empty canvas → zoom OUT anchored at the cursor (Google Maps style). It must never
|
||||
|
||||
@@ -244,22 +244,22 @@ export function useDashboardSelection(
|
||||
[screenToCanvas, computeMarqueeSelection],
|
||||
);
|
||||
|
||||
// Returns true when the release ended a real marquee DRAG, so the caller can tell a rubber-band
|
||||
// gesture from a plain click without duplicating the threshold bookkeeping.
|
||||
const handleCanvasMouseUp = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
(e: MouseEvent): boolean => {
|
||||
const origin = marqueeOriginRef.current;
|
||||
if (!origin) return;
|
||||
if (!origin) return false;
|
||||
|
||||
if (!isDraggingMarqueeRef.current) {
|
||||
if (!e.shiftKey) {
|
||||
deselectAll();
|
||||
}
|
||||
}
|
||||
const dragged = isDraggingMarqueeRef.current;
|
||||
if (!dragged && !e.shiftKey) deselectAll();
|
||||
|
||||
marqueeOriginRef.current = null;
|
||||
isDraggingMarqueeRef.current = false;
|
||||
setMarquee(null);
|
||||
document.body.style.userSelect = '';
|
||||
document.body.classList.remove('dashboard-marquee-active');
|
||||
return dragged;
|
||||
},
|
||||
[deselectAll],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user