[Haik]: refactor: restructure Dashboard module boundaries — move ElementSelectionContext (split into ElementSelectionProvider, SelectedElement, useElementSelection), SelectionOverlay, domSelectorHelpers, and useDomElementSelector from global components/ into Dashboard/_shared/element_selection and Dashboard/hooks/useDomElementSelector; relocate types.ts and formatRelativeTime.ts into Dashboard/_shared; update imports in Dashboard.tsx, DashboardCanvas, CanvasControls, DashboardHeader, ViewEditor, useIframeElementSelector, dashboardClipboard, and all Dashboard hooks

This commit is contained in:
haikdc
2026-04-18 14:15:02 -07:00
parent 31e3d66436
commit 2f0ff10d3f
21 changed files with 97 additions and 92 deletions
@@ -5,9 +5,10 @@ import { toggleExpandSession } from '@/shared/state/agentsSlice';
import { UPDATE_DASHBOARD } from '@/shared/backend-bridge/apps/dashboards';
import { useCanvasControls } from './hooks/useCanvasControls/useCanvasControls';
import { useDashboardSelection } from './hooks/useDashboardSelection';
import { ElementSelectionProvider, useElementSelection } from '@/app/components/ElementSelectionContext';
import { useDomElementSelector } from '@/app/components/useDomElementSelector';
import SelectionOverlay from '@/app/components/SelectionOverlay';
import { ElementSelectionProvider } from './_shared/element_selection/ElementSelectionProvider';
import { useElementSelection } from './_shared/element_selection/useElementSelection';
import { useDomElementSelector } from './hooks/useDomElementSelector/useDomElementSelector';
import SelectionOverlay from './SelectionOverlay';
import { useDashboardDrag } from './hooks/useDashboardDrag';
import { useDashboardKeyboard } from './hooks/useDashboardKeyboard';
import { useDashboardThumbnail } from './hooks/useDashboardThumbnail/useDashboardThumbnail';
@@ -10,9 +10,7 @@ import DashboardToolbar from './components/DashboardToolbar/DashboardToolbar';
import DashboardHeader from './components/DashboardHeader/DashboardHeader';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { EXPANDED_CARD_MIN_H, DEFAULT_CARD_W, GRID_GAP } from '@/shared/state/dashboardLayoutSlice';
import type { TetherInfo } from '@/app/pages/Dashboard/types/types';
import type { CardType } from '@/app/pages/Dashboard/types/types';
import type { CanvasActions } from '@/app/pages/Dashboard/types/types';
import { type CardType, type CanvasActions, type TetherInfo } from '@/app/pages/Dashboard/_shared/types';
const TETHER_FADE_MS = 2500;
@@ -8,7 +8,7 @@ import AddIcon from '@mui/icons-material/Add';
import FitScreenIcon from '@mui/icons-material/FitScreen';
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import type { CanvasActions } from '@/app/pages/Dashboard/types/types';
import { type CanvasActions } from '@/app/pages/Dashboard/_shared/types';
interface Props {
zoom: number;
@@ -10,7 +10,7 @@ import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import type { AgentSession } from '@/shared/state/agentsSlice';
import type { CardPosition, ViewCardPosition, BrowserCardPosition } from '@/shared/state/dashboardLayoutSlice';
import type { App } from '@/shared/backend-bridge/apps/app_builder';
import type { CanvasActions } from '@/app/pages/Dashboard/types/types';
import { type CanvasActions } from '@/app/pages/Dashboard/_shared/types';
import { STATUS_DOT, cleanUrl, CategoryGroup, ItemRow } from './DashboardHeaderParts';
interface DashboardHeaderProps {
@@ -1,7 +1,7 @@
import React, { useEffect, useState, useRef } from 'react';
import ReactDOM from 'react-dom';
import { OverlayState, DragRect, DragPreviewElement } from './useDomElementSelector';
import { useElementSelection } from './ElementSelectionContext';
import { type OverlayState, type DragRect, type DragPreviewElement } from './_shared/types';
import { useElementSelection } from './_shared/useElementSelection';
const HIGHLIGHT_COLOR = '#3b82f6';
const HIGHLIGHT_BG = 'rgba(59, 130, 246, 0.08)';
@@ -1,44 +1,5 @@
import React, { createContext, useContext, useState, useRef, useCallback, useMemo, RefObject } from 'react';
export interface SelectedElement {
id: string;
selectorPath: string;
tagName: string;
className: string;
outerHTML: string;
computedStyles: Record<string, string>;
screenshot?: string;
boundingRect: { x: number; y: number; width: number; height: number };
semanticType?: 'agent-card' | 'message' | 'tool-call' | 'tool-group' | 'view-card' | 'browser-card' | 'dom-element';
semanticLabel?: string;
semanticData?: Record<string, any>;
}
interface ElementSelectionContextValue {
selectMode: boolean;
toggleSelectMode: () => void;
setSelectMode: (active: boolean) => void;
excludeSelectId: string | null;
setExcludeSelectId: (id: string | null) => void;
activeOwnerId: string | null;
setActiveOwnerId: (id: string | null) => void;
selectedElements: SelectedElement[];
addSelectedElement: (el: SelectedElement) => void;
updateSelectedElement: (id: string, patch: Partial<SelectedElement>) => void;
removeSelectedElement: (id: string) => void;
clearSelectedElements: () => void;
elementsByOwner: Record<string, SelectedElement[]>;
addElementForOwner: (ownerId: string, el: SelectedElement) => void;
removeOwnerElement: (ownerId: string, elementId: string) => void;
clearOwnerElements: (ownerId: string) => void;
iframeRef: RefObject<HTMLIFrameElement | null>;
}
const ElementSelectionContext = createContext<ElementSelectionContextValue | null>(null);
export function useElementSelection() {
return useContext(ElementSelectionContext);
}
import React, { useState, useRef, useCallback, useMemo } from 'react';
import { type SelectedElement } from './SelectedElement';
export const ElementSelectionProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [selectMode, setSelectMode] = useState(false);
@@ -0,0 +1,13 @@
export interface SelectedElement {
id: string;
selectorPath: string;
tagName: string;
className: string;
outerHTML: string;
computedStyles: Record<string, string>;
screenshot?: string;
boundingRect: { x: number; y: number; width: number; height: number };
semanticType?: 'agent-card' | 'message' | 'tool-call' | 'tool-group' | 'view-card' | 'browser-card' | 'dom-element';
semanticLabel?: string;
semanticData?: Record<string, any>;
}
@@ -0,0 +1,29 @@
import { createContext, useContext, RefObject } from 'react';
import { type SelectedElement } from './SelectedElement';
interface ElementSelectionContextValue {
selectMode: boolean;
toggleSelectMode: () => void;
setSelectMode: (active: boolean) => void;
excludeSelectId: string | null;
setExcludeSelectId: (id: string | null) => void;
activeOwnerId: string | null;
setActiveOwnerId: (id: string | null) => void;
selectedElements: SelectedElement[];
addSelectedElement: (el: SelectedElement) => void;
updateSelectedElement: (id: string, patch: Partial<SelectedElement>) => void;
removeSelectedElement: (id: string) => void;
clearSelectedElements: () => void;
elementsByOwner: Record<string, SelectedElement[]>;
addElementForOwner: (ownerId: string, el: SelectedElement) => void;
removeOwnerElement: (ownerId: string, elementId: string) => void;
clearOwnerElements: (ownerId: string) => void;
iframeRef: RefObject<HTMLIFrameElement | null>;
}
const ElementSelectionContext = createContext<ElementSelectionContextValue | null>(null);
export function useElementSelection() {
return useContext(ElementSelectionContext);
}
@@ -19,4 +19,31 @@ export interface CanvasActions {
maxZoom?: number,
animate?: boolean,
) => void;
}
export interface OverlayState {
visible: boolean;
top: number;
left: number;
width: number;
height: number;
label: string;
}
export interface DragRect {
visible: boolean;
top: number;
left: number;
width: number;
height: number;
}
export interface DragPreviewElement {
selectId: string;
top: number;
left: number;
width: number;
height: number;
label: string;
action: 'add' | 'remove';
}
@@ -1,7 +1,7 @@
import { useState, useCallback, useRef } from 'react';
import { useAppDispatch } from '@/shared/hooks';
import { moveCards, bringToFront } from '@/shared/state/dashboardLayoutSlice';
import type { CardType } from '@/app/pages/Dashboard/types/types';
import { type CardType } from '@/app/pages/Dashboard/_shared/types';
interface DragSelection {
isSelected: (id: string) => boolean;
@@ -14,7 +14,7 @@ import { LIST_APPS } from '@/shared/backend-bridge/apps/app_builder';
import { agentsWs } from '@/shared/ws/WebSocketManager';
import { initBrowserCommandHandler } from '@/shared/browserCommandHandler';
import { clearPendingBrowserUrl, clearPendingFocusAgentId } from '@/shared/state/tempStateSlice';
import type { CanvasActions } from '@/app/pages/Dashboard/types/types';
import { type CanvasActions } from '@/app/pages/Dashboard/_shared/types';
interface InitDeps {
dashboardId: string | undefined;
@@ -13,7 +13,7 @@ import {
placeCard,
} from '@/shared/state/dashboardLayoutSlice';
import { setClipboardCards, getClipboardCards, type ClipboardCard } from '@/shared/dashboardClipboard';
import type { CardType } from '@/app/pages/Dashboard/types/types';
import { type CardType } from '@/app/pages/Dashboard/_shared/types';
interface KeyboardDeps {
newAgentShortcut: string;
@@ -1,6 +1,6 @@
import { useState, useCallback, useRef, useEffect, RefObject } from 'react';
import type { CardPosition, ViewCardPosition, BrowserCardPosition } from '@/shared/state/dashboardLayoutSlice';
import type { CardType } from '@/app/pages/Dashboard/types/types';
import { type CardType } from '@/app/pages/Dashboard/_shared/types';
interface SelectedCard {
id: string;
@@ -1,4 +1,5 @@
import { type SelectedElement } from './ElementSelectionContext';
import { type SelectedElement } from '@/app/pages/Dashboard/_shared/element_selection/SelectedElement';
import { type OverlayState, type DragRect, type DragPreviewElement } from '@/app/pages/Dashboard/_shared/types';
export const SELECT_ATTR = 'data-select-type';
export const SELECT_ID_ATTR = 'data-select-id';
@@ -7,23 +8,6 @@ export const SELECT_META_ATTR = 'data-select-meta';
const DRAG_SELECT_TYPES = ['agent-card', 'view-card', 'browser-card'] as const;
const DRAG_SELECTOR = DRAG_SELECT_TYPES.map((t) => `[${SELECT_ATTR}="${t}"]`).join(',');
export interface OverlayState {
visible: boolean;
top: number;
left: number;
width: number;
height: number;
label: string;
}
export interface DragRect {
visible: boolean;
top: number;
left: number;
width: number;
height: number;
}
export const EMPTY_OVERLAY: OverlayState = { visible: false, top: 0, left: 0, width: 0, height: 0, label: '' };
export const EMPTY_DRAG: DragRect = { visible: false, top: 0, left: 0, width: 0, height: 0 };
@@ -98,16 +82,6 @@ export function buildSelectedElement(el: Element): SelectedElement {
};
}
export interface DragPreviewElement {
selectId: string;
top: number;
left: number;
width: number;
height: number;
label: string;
action: 'add' | 'remove';
}
export const DRAG_THRESHOLD = 5;
export interface DomSelectorState {
@@ -1,14 +1,14 @@
import { useEffect, useRef, useState, useCallback } from 'react';
import { useElementSelection } from './ElementSelectionContext';
import { useElementSelection } from '@/app/pages/Dashboard/_shared/element_selection/useElementSelection';
import {
type OverlayState, type DragRect, type DragPreviewElement, type DomSelectorState, type SelectMeta,
type SelectMeta,
type DomSelectorState,
EMPTY_OVERLAY, EMPTY_DRAG, DRAG_THRESHOLD,
SELECT_ATTR, SELECT_ID_ATTR, SELECT_META_ATTR,
findSelectableAncestor, buildSemanticLabel, buildSelectedElement,
computeDragPreview, processDragSelection,
} from './domSelectorHelpers';
export type { OverlayState, DragRect, DragPreviewElement } from './domSelectorHelpers';
import { type OverlayState, type DragRect, type DragPreviewElement } from '@/app/pages/Dashboard/_shared/types';
export function useDomElementSelector(): DomSelectorState {
const ctx = useElementSelection();
@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import type { RefObject } from 'react';
import { EXPANDED_CARD_MIN_H } from '@/shared/state/dashboardLayoutSlice';
import type { TetherInfo } from '@/app/pages/Dashboard/types/types';
import { type TetherInfo } from '@/app/pages/Dashboard/_shared/types';
interface TetherDeps {
glowingAgentCards: Record<string, { sourceId: string; fading: boolean; sourceYRatio?: number; label?: string }>;
@@ -24,7 +24,7 @@ import {
} from '@/shared/state/dashboardLayoutSlice';
import { GENERATE_DASHBOARD_NAME } from '@/shared/backend-bridge/apps/dashboards';
import type { ContextPath } from '@/shared/state/agentsTypes';
import type { CanvasActions } from '@/app/pages/Dashboard/types/types';
import { type CanvasActions } from '@/app/pages/Dashboard/_shared/types';
interface ToolbarDeps {
cards: Record<string, CardPosition>;
+2 -1
View File
@@ -7,7 +7,8 @@ import AgentChat from '../AgentChat/AgentChat';
import ViewPreview, { ViewPreviewHandle } from './ViewPreview';
import InputSchemaForm, { getDefault, getStubbed } from './InputSchemaForm';
import CodeEditor from './CodeEditor';
import { ElementSelectionProvider } from '@/app/components/ElementSelectionContext';
// TODO: Fix this import, also the the element selection in this component doesnt seem to work so for now its chillin
import { ElementSelectionProvider } from '@/app/pages/Dashboard/_shared/element_selection/ElementSelectionProvider';
import { FileTreeItem, getEditorLanguage } from './FileTree';
import { ConsolePanel } from './ConsolePanel';
import { useViewWorkspace } from './hooks/useViewWorkspace';
@@ -1,5 +1,6 @@
import { useEffect, useRef, useCallback, RefObject } from 'react';
import { SelectedElement, useElementSelection } from '@/app/components/ElementSelectionContext';
import { useElementSelection } from '@/app/pages/Dashboard/_shared/element_selection/useElementSelection';
import { type SelectedElement } from '@/app/pages/Dashboard/_shared/element_selection/SelectedElement';
import {
OVERLAY_ID, LABEL_ID, STYLE_ID,
HIGHLIGHT_COLOR, HIGHLIGHT_BG, FLASH_COLOR, SELECTED_CLASS,
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CardType } from '@/app/pages/Dashboard/types/types';
import { type CardType } from '@/app/pages/Dashboard/_shared/types';
export interface ClipboardCard {
type: CardType;