[Haik]: refactor: restructure Dashboard page from flat file layout into nested component directories — move DashboardCanvas, DashboardHeader, DashboardHeaderParts, DashboardToolbar, ToolbarButtons, HistoryPanel, ViewPickerPanel, CanvasControls, and DashboardViewCard into DashboardCanvas/components/ subtree with dedicated folders for DashboardToolbar and DashboardHeader; consolidate shared types (CardType, CanvasActions, TetherInfo) from useDashboardSelection, useCanvasControls, and useTetherPaths into types/types.ts; extract duplicated formatRelativeTime into utils/formatRelativeTime.ts; update all import paths across hooks, shared modules, and DashboardCard

This commit is contained in:
haikdc
2026-04-18 12:39:05 -07:00
parent 6a9dd59eec
commit 0ee6fe7ccb
24 changed files with 130 additions and 136 deletions
@@ -15,7 +15,7 @@ import { useDashboardInit } from './hooks/useDashboardInit';
import { useSubAgentAutoReveal } from './hooks/useSubAgentAutoReveal';
import { useTetherPaths } from './hooks/useTetherPaths';
import { useToolbarActions } from './hooks/useToolbarActions';
import DashboardCanvas from './DashboardCanvas';
import DashboardCanvas from './DashboardCanvas/DashboardCanvas';
const SELECT_ATTR = 'data-select-type';
@@ -2,17 +2,17 @@ import React from 'react';
import { AnimatePresence } from 'framer-motion';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import AgentCard from './AgentCard/AgentCard';
import DashboardViewCard from './DashboardViewCard';
import BrowserCard from './BrowserCard/BrowserCard';
import CanvasControls from './CanvasControls';
import DashboardToolbar from './DashboardToolbar';
import DashboardHeader from './DashboardHeader';
import AgentCard from '@/app/pages/Dashboard/AgentCard/AgentCard';
import DashboardViewCard from './components/DashboardViewCard';
import BrowserCard from '@/app/pages/Dashboard/BrowserCard/BrowserCard';
import CanvasControls from './components/CanvasControls';
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 './hooks/useTetherPaths';
import type { CardType } from './useDashboardSelection';
import type { CanvasActions } from './useCanvasControls';
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';
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 './useCanvasControls';
import type { CanvasActions } from '@/app/pages/Dashboard/types/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 './useCanvasControls';
import type { CanvasActions } from '@/app/pages/Dashboard/types/types';
import { STATUS_DOT, cleanUrl, CategoryGroup, ItemRow } from './DashboardHeaderParts';
interface DashboardHeaderProps {
@@ -1,13 +1,12 @@
import React from 'react';
import { motion } from 'framer-motion';
import ChatInput from '@/app/pages/AgentChat/ChatInput/ChatInput';
import type { Props } from './toolbarShared';
import { MotionBox, TOOLBAR_OWNER_ID } from './toolbarShared';
import { useDashboardToolbar } from './useDashboardToolbar';
import HistoryPanel from './HistoryPanel';
import ViewPickerPanel from './ViewPickerPanel';
import ToolbarButtons from './ToolbarButtons';
import { useDashboardToolbar, TOOLBAR_OWNER_ID, ToolbarProps } from './useDashboardToolbar';
import HistoryPanel from './components/HistoryPanel';
import ViewPickerPanel from './components/ViewPickerPanel';
import ToolbarButtons from './components/ToolbarButtons/ToolbarButtons';
const DashboardToolbar = React.forwardRef<HTMLDivElement, Props>(
const DashboardToolbar = React.forwardRef<HTMLDivElement, ToolbarProps>(
({ inputOpen, onNewAgent, onCancel, onSend, onAddView, onHistoryResume, onAddBrowser, dashboardId }, ref) => {
const {
c, containerRef, searchInputRef, historyInputRef, historyListRef,
@@ -26,7 +25,7 @@ const DashboardToolbar = React.forwardRef<HTMLDivElement, Props>(
React.useImperativeHandle(ref, () => containerRef.current!, []);
return (
<MotionBox
<motion.div
ref={containerRef}
layout
transition={{ layout: { duration: 0.15, ease: [0.25, 0.1, 0.25, 1] } }}
@@ -87,7 +86,7 @@ const DashboardToolbar = React.forwardRef<HTMLDivElement, Props>(
c={c}
/>
)}
</MotionBox>
</motion.div>
);
},
);
@@ -5,7 +5,7 @@ import InputBase from '@mui/material/InputBase';
import CircularProgress from '@mui/material/CircularProgress';
import SearchIcon from '@mui/icons-material/Search';
import type { ClaudeTokens } from '@/shared/styles/claudeTokens';
import { formatRelativeTime } from './toolbarShared';
import { formatRelativeTime } from '@/app/pages/Dashboard/utils/formatRelativeTime';
interface HistoryPanelProps {
historyInputRef: React.RefObject<HTMLInputElement>;
@@ -6,7 +6,9 @@ import StickyNote2OutlinedIcon from '@mui/icons-material/StickyNote2Outlined';
import HistoryRoundedIcon from '@mui/icons-material/HistoryRounded';
import LanguageIcon from '@mui/icons-material/Language';
import type { ClaudeTokens } from '@/shared/styles/claudeTokens';
import { WarmTooltip, BTN } from './toolbarShared';
import { WarmTooltip } from './WarmTooltip';
const BUTTON_SIZE = 40;
interface ToolbarButtonsProps {
onNewAgent: () => void;
@@ -36,8 +38,8 @@ export default function ToolbarButtons({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: BTN,
height: BTN,
width: BUTTON_SIZE,
height: BUTTON_SIZE,
borderRadius: `${c.radius.lg}px`,
bgcolor: c.accent.primary,
color: '#fff',
@@ -71,8 +73,8 @@ export default function ToolbarButtons({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: BTN,
height: BTN,
width: BUTTON_SIZE,
height: BUTTON_SIZE,
borderRadius: `${c.radius.md}px`,
color: c.text.tertiary,
cursor: 'pointer',
@@ -104,8 +106,8 @@ export default function ToolbarButtons({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: BTN,
height: BTN,
width: BUTTON_SIZE,
height: BUTTON_SIZE,
borderRadius: `${c.radius.md}px`,
color: c.text.tertiary,
cursor: 'pointer',
@@ -137,8 +139,8 @@ export default function ToolbarButtons({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: BTN,
height: BTN,
width: BUTTON_SIZE,
height: BUTTON_SIZE,
borderRadius: `${c.radius.md}px`,
color: c.text.tertiary,
cursor: 'pointer',
@@ -169,8 +171,8 @@ export default function ToolbarButtons({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: BTN,
height: BTN,
width: BUTTON_SIZE,
height: BUTTON_SIZE,
borderRadius: `${c.radius.md}px`,
color: c.text.tertiary,
opacity: 0.45,
@@ -0,0 +1,25 @@
import React from 'react';
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip';
import { styled } from '@mui/material/styles';
import type { ClaudeTokens } from '@/shared/styles/claudeTokens';
export const WarmTooltip = styled(
({ className, ...props }: React.ComponentProps<typeof Tooltip> & { className?: string }) => (
<Tooltip {...props} classes={{ popper: className }} />
)
)<{ tokens: ClaudeTokens }>(({ tokens: c }) => ({
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: c.bg.inverse,
color: c.text.inverse,
fontFamily: c.font.sans,
fontSize: '0.78rem',
fontWeight: 500,
padding: '6px 12px',
borderRadius: c.radius.md,
boxShadow: c.shadow.md,
letterSpacing: '0.01em',
},
[`& .${tooltipClasses.arrow}`]: {
color: c.bg.inverse,
},
}));
@@ -6,12 +6,33 @@ import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { clearHistorySearch } from '@/shared/state/agentsSlice';
import { GET_HISTORY } from '@/shared/backend-bridge/apps/agents';
import type { App } from '@/shared/backend-bridge/apps/app_builder';
import type { Props } from './toolbarShared';
import { TOOLBAR_OWNER_ID, HISTORY_PAGE_SIZE } from './toolbarShared';
export interface ToolbarProps {
inputOpen: boolean;
onNewAgent: () => void;
onCancel: () => void;
onSend: (
prompt: string,
mode: string,
model: string,
images?: Array<{ data: string; media_type: string }>,
contextPaths?: ContextPath[],
forcedTools?: string[],
attachedSkills?: Array<{ id: string; name: string; content: string }>,
selectedBrowserIds?: string[],
) => void;
onAddView: (outputId: string) => void;
onHistoryResume: (sessionId: string) => void;
onAddBrowser: () => void;
dashboardId?: string;
}
export const TOOLBAR_OWNER_ID = '__toolbar__';
const HISTORY_PAGE_SIZE = 20;
export function useDashboardToolbar({
inputOpen, onCancel, onSend, onAddView, onHistoryResume, onAddBrowser, dashboardId,
}: Omit<Props, 'onNewAgent'>) {
}: Omit<ToolbarProps, 'onNewAgent'>) {
const c = useClaudeTokens();
const dispatch = useAppDispatch();
const elementSelection = useElementSelection();
@@ -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 '../useDashboardSelection';
import type { CardType } from '@/app/pages/Dashboard/types/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 '../useCanvasControls';
import type { CanvasActions } from '@/app/pages/Dashboard/types/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 '../useDashboardSelection';
import type { CardType } from '@/app/pages/Dashboard/types/types';
interface KeyboardDeps {
newAgentShortcut: string;
@@ -1,15 +1,7 @@
import { useMemo } from 'react';
import type { RefObject } from 'react';
import { EXPANDED_CARD_MIN_H } from '@/shared/state/dashboardLayoutSlice';
export interface TetherInfo {
key: string;
path: string;
labelX: number;
labelY: number;
label: string;
fading: boolean;
}
import type { TetherInfo } from '@/app/pages/Dashboard/types/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 '../useCanvasControls';
import type { CanvasActions } from '@/app/pages/Dashboard/types/types';
interface ToolbarDeps {
cards: Record<string, CardPosition>;
@@ -1,65 +0,0 @@
import React from 'react';
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip';
import { styled } from '@mui/material/styles';
import { motion } from 'framer-motion';
import type { ClaudeTokens } from '@/shared/styles/claudeTokens';
import type { ContextPath } from '@/shared/state/agentsTypes';
export interface Props {
inputOpen: boolean;
onNewAgent: () => void;
onCancel: () => void;
onSend: (
prompt: string,
mode: string,
model: string,
images?: Array<{ data: string; media_type: string }>,
contextPaths?: ContextPath[],
forcedTools?: string[],
attachedSkills?: Array<{ id: string; name: string; content: string }>,
selectedBrowserIds?: string[],
) => void;
onAddView: (outputId: string) => void;
onHistoryResume: (sessionId: string) => void;
onAddBrowser: () => void;
dashboardId?: string;
}
export const TOOLBAR_OWNER_ID = '__toolbar__';
export const BTN = 40;
export const HISTORY_PAGE_SIZE = 20;
export const WarmTooltip = styled(
({ className, ...props }: React.ComponentProps<typeof Tooltip> & { className?: string }) => (
<Tooltip {...props} classes={{ popper: className }} />
)
)<{ tokens: ClaudeTokens }>(({ tokens: c }) => ({
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: c.bg.inverse,
color: c.text.inverse,
fontFamily: c.font.sans,
fontSize: '0.78rem',
fontWeight: 500,
padding: '6px 12px',
borderRadius: c.radius.md,
boxShadow: c.shadow.md,
letterSpacing: '0.01em',
},
[`& .${tooltipClasses.arrow}`]: {
color: c.bg.inverse,
},
}));
export const MotionBox = motion.div;
export function formatRelativeTime(dateStr: string | null): string {
if (!dateStr) return '';
const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000);
if (seconds < 60) return 'just now';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
}
@@ -1,6 +1,28 @@
export interface TabLocalState {
loading: boolean;
canGoBack: boolean;
canGoForward: boolean;
}
loading: boolean;
canGoBack: boolean;
canGoForward: boolean;
}
export interface TetherInfo {
key: string;
path: string;
labelX: number;
labelY: number;
label: string;
fading: boolean;
}
export type CardType = 'agent' | 'view' | 'browser';
export interface CanvasActions {
zoomIn: () => void;
zoomOut: () => void;
resetZoom: () => void;
fitToView: () => void;
fitToCards: (
cardRects: Array<{ x: number; y: number; width: number; height: number }>,
maxZoom?: number,
animate?: boolean,
) => void;
}
@@ -170,5 +170,3 @@ export function useCanvasControls(zoomSensitivity: number = 50) {
actions,
} as const;
}
export type CanvasActions = ReturnType<typeof useCanvasControls>['actions'];
@@ -1,7 +1,6 @@
import { useState, useCallback, useRef, useEffect, RefObject } from 'react';
import type { CardPosition, ViewCardPosition, BrowserCardPosition } from '@/shared/state/dashboardLayoutSlice';
export type CardType = 'agent' | 'view' | 'browser';
import type { CardType } from '@/app/pages/Dashboard/types/types';
interface SelectedCard {
id: string;
@@ -0,0 +1,12 @@
export function formatRelativeTime(dateStr: string | null): string {
if (!dateStr) return '';
const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000);
if (seconds < 60) return 'just now';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
}
@@ -8,18 +8,7 @@ import DashboardIcon from '@mui/icons-material/Dashboard';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import { Dashboard } from '@/shared/state/dashboardsSlice';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
function formatRelativeTime(dateStr: string | null): string {
if (!dateStr) return '';
const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000);
if (seconds < 60) return 'just now';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
}
import { formatRelativeTime } from '@/app/pages/Dashboard/utils/formatRelativeTime';
interface DashboardCardProps {
dashboard: Dashboard;
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CardType } from '@/app/pages/Dashboard/useDashboardSelection';
import type { CardType } from '@/app/pages/Dashboard/types/types';
export interface ClipboardCard {
type: CardType;