mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
Merge pull request #226 from JBotwina/graph-filtering-and-node-controls
feat: Graph Filtering and Node Controls
This commit is contained in:
@@ -74,10 +74,11 @@
|
|||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"rehype-raw": "^7.0.0",
|
|
||||||
"react-resizable-panels": "^3.0.6",
|
"react-resizable-panels": "^3.0.6",
|
||||||
|
"rehype-raw": "^7.0.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
|
"usehooks-ts": "^3.1.1",
|
||||||
"zustand": "^5.0.9"
|
"zustand": "^5.0.9"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
|||||||
@@ -1036,8 +1036,13 @@ export function BoardView() {
|
|||||||
currentWorktreePath={currentWorktreePath}
|
currentWorktreePath={currentWorktreePath}
|
||||||
currentWorktreeBranch={currentWorktreeBranch}
|
currentWorktreeBranch={currentWorktreeBranch}
|
||||||
projectPath={currentProject?.path || null}
|
projectPath={currentProject?.path || null}
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
onSearchQueryChange={setSearchQuery}
|
||||||
onEditFeature={(feature) => setEditingFeature(feature)}
|
onEditFeature={(feature) => setEditingFeature(feature)}
|
||||||
onViewOutput={handleViewOutput}
|
onViewOutput={handleViewOutput}
|
||||||
|
onStartTask={handleStartImplementation}
|
||||||
|
onStopTask={handleForceStopFeature}
|
||||||
|
onResumeTask={handleResumeFeature}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { Feature } from '@/store/app-store';
|
|||||||
export interface DependencyEdgeData {
|
export interface DependencyEdgeData {
|
||||||
sourceStatus: Feature['status'];
|
sourceStatus: Feature['status'];
|
||||||
targetStatus: Feature['status'];
|
targetStatus: Feature['status'];
|
||||||
|
isHighlighted?: boolean;
|
||||||
|
isDimmed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEdgeColor = (sourceStatus?: Feature['status'], targetStatus?: Feature['status']) => {
|
const getEdgeColor = (sourceStatus?: Feature['status'], targetStatus?: Feature['status']) => {
|
||||||
@@ -52,11 +54,17 @@ export const DependencyEdge = memo(function DependencyEdge(props: EdgeProps) {
|
|||||||
curvature: 0.25,
|
curvature: 0.25,
|
||||||
});
|
});
|
||||||
|
|
||||||
const edgeColor = edgeData
|
const isHighlighted = edgeData?.isHighlighted ?? false;
|
||||||
? getEdgeColor(edgeData.sourceStatus, edgeData.targetStatus)
|
const isDimmed = edgeData?.isDimmed ?? false;
|
||||||
: 'var(--border)';
|
|
||||||
|
|
||||||
const isCompleted = edgeData?.sourceStatus === 'completed' || edgeData?.sourceStatus === 'verified';
|
const edgeColor = isHighlighted
|
||||||
|
? 'var(--brand-500)'
|
||||||
|
: edgeData
|
||||||
|
? getEdgeColor(edgeData.sourceStatus, edgeData.targetStatus)
|
||||||
|
: 'var(--border)';
|
||||||
|
|
||||||
|
const isCompleted =
|
||||||
|
edgeData?.sourceStatus === 'completed' || edgeData?.sourceStatus === 'verified';
|
||||||
const isInProgress = edgeData?.targetStatus === 'in_progress';
|
const isInProgress = edgeData?.targetStatus === 'in_progress';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -66,8 +74,9 @@ export const DependencyEdge = memo(function DependencyEdge(props: EdgeProps) {
|
|||||||
id={`${id}-bg`}
|
id={`${id}-bg`}
|
||||||
path={edgePath}
|
path={edgePath}
|
||||||
style={{
|
style={{
|
||||||
strokeWidth: 4,
|
strokeWidth: isHighlighted ? 6 : 4,
|
||||||
stroke: 'var(--background)',
|
stroke: 'var(--background)',
|
||||||
|
opacity: isDimmed ? 0.3 : 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -78,13 +87,20 @@ export const DependencyEdge = memo(function DependencyEdge(props: EdgeProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'transition-all duration-300',
|
'transition-all duration-300',
|
||||||
animated && 'animated-edge',
|
animated && 'animated-edge',
|
||||||
isInProgress && 'edge-flowing'
|
isInProgress && 'edge-flowing',
|
||||||
|
isHighlighted && 'graph-edge-highlighted',
|
||||||
|
isDimmed && 'graph-edge-dimmed'
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
strokeWidth: selected ? 3 : 2,
|
strokeWidth: isHighlighted ? 4 : selected ? 3 : isDimmed ? 1 : 2,
|
||||||
stroke: edgeColor,
|
stroke: edgeColor,
|
||||||
strokeDasharray: isCompleted ? 'none' : '5 5',
|
strokeDasharray: isCompleted ? 'none' : '5 5',
|
||||||
filter: selected ? 'drop-shadow(0 0 3px var(--brand-500))' : 'none',
|
filter: isHighlighted
|
||||||
|
? 'drop-shadow(0 0 6px var(--brand-500))'
|
||||||
|
: selected
|
||||||
|
? 'drop-shadow(0 0 3px var(--brand-500))'
|
||||||
|
: 'none',
|
||||||
|
opacity: isDimmed ? 0.2 : 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function GraphControls({
|
|||||||
return (
|
return (
|
||||||
<Panel position="bottom-left" className="flex flex-col gap-2">
|
<Panel position="bottom-left" className="flex flex-col gap-2">
|
||||||
<TooltipProvider delayDuration={200}>
|
<TooltipProvider delayDuration={200}>
|
||||||
<div className="flex flex-col gap-1 p-1.5 rounded-lg bg-popover/90 backdrop-blur-sm border border-border shadow-lg">
|
<div className="flex flex-col gap-1 p-1.5 rounded-lg bg-popover/90 backdrop-blur-sm border border-border shadow-lg text-popover-foreground">
|
||||||
{/* Zoom controls */}
|
{/* Zoom controls */}
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@@ -120,22 +120,13 @@ export function GraphControls({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className={cn(
|
className={cn('h-8 w-8 p-0', isLocked && 'bg-brand-500/20 text-brand-500')}
|
||||||
'h-8 w-8 p-0',
|
|
||||||
isLocked && 'bg-brand-500/20 text-brand-500'
|
|
||||||
)}
|
|
||||||
onClick={onToggleLock}
|
onClick={onToggleLock}
|
||||||
>
|
>
|
||||||
{isLocked ? (
|
{isLocked ? <Lock className="w-4 h-4" /> : <Unlock className="w-4 h-4" />}
|
||||||
<Lock className="w-4 h-4" />
|
|
||||||
) : (
|
|
||||||
<Unlock className="w-4 h-4" />
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<TooltipContent side="right">{isLocked ? 'Unlock Nodes' : 'Lock Nodes'}</TooltipContent>
|
||||||
{isLocked ? 'Unlock Nodes' : 'Lock Nodes'}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
@@ -0,0 +1,329 @@
|
|||||||
|
import { Panel } from '@xyflow/react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
|
import {
|
||||||
|
Filter,
|
||||||
|
X,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
|
ChevronDown,
|
||||||
|
Play,
|
||||||
|
Pause,
|
||||||
|
Clock,
|
||||||
|
CheckCircle2,
|
||||||
|
CircleDot,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import {
|
||||||
|
GraphFilterState,
|
||||||
|
STATUS_FILTER_OPTIONS,
|
||||||
|
StatusFilterValue,
|
||||||
|
} from '../hooks/use-graph-filter';
|
||||||
|
|
||||||
|
// Status display configuration
|
||||||
|
const statusDisplayConfig: Record<
|
||||||
|
StatusFilterValue,
|
||||||
|
{ label: string; icon: typeof Play; colorClass: string }
|
||||||
|
> = {
|
||||||
|
running: { label: 'Running', icon: Play, colorClass: 'text-[var(--status-in-progress)]' },
|
||||||
|
paused: { label: 'Paused', icon: Pause, colorClass: 'text-[var(--status-warning)]' },
|
||||||
|
backlog: { label: 'Backlog', icon: Clock, colorClass: 'text-muted-foreground' },
|
||||||
|
waiting_approval: {
|
||||||
|
label: 'Waiting Approval',
|
||||||
|
icon: CircleDot,
|
||||||
|
colorClass: 'text-[var(--status-waiting)]',
|
||||||
|
},
|
||||||
|
verified: { label: 'Verified', icon: CheckCircle2, colorClass: 'text-[var(--status-success)]' },
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GraphFilterControlsProps {
|
||||||
|
filterState: GraphFilterState;
|
||||||
|
availableCategories: string[];
|
||||||
|
hasActiveFilter: boolean;
|
||||||
|
onCategoriesChange: (categories: string[]) => void;
|
||||||
|
onStatusesChange: (statuses: string[]) => void;
|
||||||
|
onNegativeFilterChange: (isNegative: boolean) => void;
|
||||||
|
onClearFilters: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GraphFilterControls({
|
||||||
|
filterState,
|
||||||
|
availableCategories,
|
||||||
|
hasActiveFilter,
|
||||||
|
onCategoriesChange,
|
||||||
|
onStatusesChange,
|
||||||
|
onNegativeFilterChange,
|
||||||
|
onClearFilters,
|
||||||
|
}: GraphFilterControlsProps) {
|
||||||
|
const { selectedCategories, selectedStatuses, isNegativeFilter } = filterState;
|
||||||
|
|
||||||
|
const handleCategoryToggle = (category: string) => {
|
||||||
|
if (selectedCategories.includes(category)) {
|
||||||
|
onCategoriesChange(selectedCategories.filter((c) => c !== category));
|
||||||
|
} else {
|
||||||
|
onCategoriesChange([...selectedCategories, category]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectAllCategories = () => {
|
||||||
|
if (selectedCategories.length === availableCategories.length) {
|
||||||
|
onCategoriesChange([]);
|
||||||
|
} else {
|
||||||
|
onCategoriesChange([...availableCategories]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStatusToggle = (status: string) => {
|
||||||
|
if (selectedStatuses.includes(status)) {
|
||||||
|
onStatusesChange(selectedStatuses.filter((s) => s !== status));
|
||||||
|
} else {
|
||||||
|
onStatusesChange([...selectedStatuses, status]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectAllStatuses = () => {
|
||||||
|
if (selectedStatuses.length === STATUS_FILTER_OPTIONS.length) {
|
||||||
|
onStatusesChange([]);
|
||||||
|
} else {
|
||||||
|
onStatusesChange([...STATUS_FILTER_OPTIONS]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const categoryButtonLabel =
|
||||||
|
selectedCategories.length === 0
|
||||||
|
? 'All Categories'
|
||||||
|
: selectedCategories.length === 1
|
||||||
|
? selectedCategories[0]
|
||||||
|
: `${selectedCategories.length} Categories`;
|
||||||
|
|
||||||
|
const statusButtonLabel =
|
||||||
|
selectedStatuses.length === 0
|
||||||
|
? 'All Statuses'
|
||||||
|
: selectedStatuses.length === 1
|
||||||
|
? statusDisplayConfig[selectedStatuses[0] as StatusFilterValue]?.label ||
|
||||||
|
selectedStatuses[0]
|
||||||
|
: `${selectedStatuses.length} Statuses`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel position="top-left" className="flex items-center gap-2">
|
||||||
|
<TooltipProvider delayDuration={200}>
|
||||||
|
<div className="flex items-center gap-2 p-2 rounded-lg bg-popover/90 backdrop-blur-sm border border-border shadow-lg text-popover-foreground">
|
||||||
|
{/* Category Filter Dropdown */}
|
||||||
|
<Popover>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className={cn(
|
||||||
|
'h-8 px-2 gap-1.5',
|
||||||
|
selectedCategories.length > 0 && 'bg-brand-500/20 text-brand-500'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Filter className="w-4 h-4" />
|
||||||
|
<span className="text-xs max-w-[100px] truncate">{categoryButtonLabel}</span>
|
||||||
|
<ChevronDown className="w-3 h-3 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Filter by Category</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent align="start" className="w-56 p-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-xs font-medium text-muted-foreground px-2 py-1">
|
||||||
|
Categories
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Select All option */}
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-accent cursor-pointer"
|
||||||
|
onClick={handleSelectAllCategories}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
selectedCategories.length === availableCategories.length &&
|
||||||
|
availableCategories.length > 0
|
||||||
|
}
|
||||||
|
onCheckedChange={handleSelectAllCategories}
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{selectedCategories.length === availableCategories.length
|
||||||
|
? 'Deselect All'
|
||||||
|
: 'Select All'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="h-px bg-border" />
|
||||||
|
|
||||||
|
{/* Category list */}
|
||||||
|
<div className="max-h-48 overflow-y-auto space-y-0.5">
|
||||||
|
{availableCategories.length === 0 ? (
|
||||||
|
<div className="text-xs text-muted-foreground px-2 py-2">
|
||||||
|
No categories available
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
availableCategories.map((category) => (
|
||||||
|
<div
|
||||||
|
key={category}
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-accent cursor-pointer"
|
||||||
|
onClick={() => handleCategoryToggle(category)}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedCategories.includes(category)}
|
||||||
|
onCheckedChange={() => handleCategoryToggle(category)}
|
||||||
|
/>
|
||||||
|
<span className="text-sm truncate">{category}</span>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
{/* Status Filter Dropdown */}
|
||||||
|
<Popover>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className={cn(
|
||||||
|
'h-8 px-2 gap-1.5',
|
||||||
|
selectedStatuses.length > 0 && 'bg-brand-500/20 text-brand-500'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CircleDot className="w-4 h-4" />
|
||||||
|
<span className="text-xs max-w-[120px] truncate">{statusButtonLabel}</span>
|
||||||
|
<ChevronDown className="w-3 h-3 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Filter by Status</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent align="start" className="w-56 p-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-xs font-medium text-muted-foreground px-2 py-1">Status</div>
|
||||||
|
|
||||||
|
{/* Select All option */}
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-accent cursor-pointer"
|
||||||
|
onClick={handleSelectAllStatuses}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedStatuses.length === STATUS_FILTER_OPTIONS.length}
|
||||||
|
onCheckedChange={handleSelectAllStatuses}
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{selectedStatuses.length === STATUS_FILTER_OPTIONS.length
|
||||||
|
? 'Deselect All'
|
||||||
|
: 'Select All'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="h-px bg-border" />
|
||||||
|
|
||||||
|
{/* Status list */}
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
{STATUS_FILTER_OPTIONS.map((status) => {
|
||||||
|
const config = statusDisplayConfig[status];
|
||||||
|
const StatusIcon = config.icon;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={status}
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-accent cursor-pointer"
|
||||||
|
onClick={() => handleStatusToggle(status)}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedStatuses.includes(status)}
|
||||||
|
onCheckedChange={() => handleStatusToggle(status)}
|
||||||
|
/>
|
||||||
|
<StatusIcon className={cn('w-3.5 h-3.5', config.colorClass)} />
|
||||||
|
<span className="text-sm">{config.label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div className="h-6 w-px bg-border" />
|
||||||
|
|
||||||
|
{/* Positive/Negative Filter Toggle */}
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => onNegativeFilterChange(!isNegativeFilter)}
|
||||||
|
aria-label={
|
||||||
|
isNegativeFilter
|
||||||
|
? 'Switch to show matching nodes'
|
||||||
|
: 'Switch to hide matching nodes'
|
||||||
|
}
|
||||||
|
aria-pressed={isNegativeFilter}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-1.5 px-2 py-1 rounded text-xs transition-colors',
|
||||||
|
isNegativeFilter
|
||||||
|
? 'bg-orange-500/20 text-orange-500'
|
||||||
|
: 'hover:bg-accent text-muted-foreground hover:text-foreground'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isNegativeFilter ? (
|
||||||
|
<>
|
||||||
|
<EyeOff className="w-3.5 h-3.5" />
|
||||||
|
<span>Hide</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Eye className="w-3.5 h-3.5" />
|
||||||
|
<span>Show</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<Switch
|
||||||
|
checked={isNegativeFilter}
|
||||||
|
onCheckedChange={onNegativeFilterChange}
|
||||||
|
aria-label="Toggle between show and hide filter modes"
|
||||||
|
className="h-5 w-9 data-[state=checked]:bg-orange-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{isNegativeFilter
|
||||||
|
? 'Negative filter: Highlighting non-matching nodes'
|
||||||
|
: 'Positive filter: Highlighting matching nodes'}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{/* Clear Filters Button - only show when filters are active */}
|
||||||
|
{hasActiveFilter && (
|
||||||
|
<>
|
||||||
|
<div className="h-6 w-px bg-border" />
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 w-8 p-0 text-muted-foreground hover:text-destructive"
|
||||||
|
onClick={onClearFilters}
|
||||||
|
aria-label="Clear all filters"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Clear All Filters</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TooltipProvider>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
import { Panel } from '@xyflow/react';
|
import { Panel } from '@xyflow/react';
|
||||||
import {
|
import { Clock, Play, Pause, CheckCircle2, Lock, AlertCircle } from 'lucide-react';
|
||||||
Clock,
|
|
||||||
Play,
|
|
||||||
Pause,
|
|
||||||
CheckCircle2,
|
|
||||||
Lock,
|
|
||||||
AlertCircle,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
const legendItems = [
|
const legendItems = [
|
||||||
@@ -51,7 +44,7 @@ const legendItems = [
|
|||||||
export function GraphLegend() {
|
export function GraphLegend() {
|
||||||
return (
|
return (
|
||||||
<Panel position="bottom-right" className="pointer-events-none">
|
<Panel position="bottom-right" className="pointer-events-none">
|
||||||
<div className="flex flex-wrap gap-3 p-2 rounded-lg bg-popover/90 backdrop-blur-sm border border-border shadow-lg pointer-events-auto">
|
<div className="flex flex-wrap gap-3 p-2 rounded-lg bg-popover/90 backdrop-blur-sm border border-border shadow-lg pointer-events-auto text-popover-foreground">
|
||||||
{legendItems.map((item) => {
|
{legendItems.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ export { TaskNode } from './task-node';
|
|||||||
export { DependencyEdge } from './dependency-edge';
|
export { DependencyEdge } from './dependency-edge';
|
||||||
export { GraphControls } from './graph-controls';
|
export { GraphControls } from './graph-controls';
|
||||||
export { GraphLegend } from './graph-legend';
|
export { GraphLegend } from './graph-legend';
|
||||||
|
export { GraphFilterControls } from './graph-filter-controls';
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import {
|
|||||||
Play,
|
Play,
|
||||||
Pause,
|
Pause,
|
||||||
Eye,
|
Eye,
|
||||||
MoreHorizontal,
|
MoreVertical,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
|
Terminal,
|
||||||
|
RotateCcw,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { TaskNodeData } from '../hooks/use-graph-nodes';
|
import { TaskNodeData } from '../hooks/use-graph-nodes';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -19,7 +21,6 @@ import {
|
|||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
@@ -72,14 +73,19 @@ const priorityConfig = {
|
|||||||
3: { label: 'Low', colorClass: 'bg-[var(--status-info)] text-white' },
|
3: { label: 'Low', colorClass: 'bg-[var(--status-info)] text-white' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TaskNode = memo(function TaskNode({
|
export const TaskNode = memo(function TaskNode({ data, selected }: TaskNodeProps) {
|
||||||
data,
|
|
||||||
selected,
|
|
||||||
}: TaskNodeProps) {
|
|
||||||
const config = statusConfig[data.status] || statusConfig.backlog;
|
const config = statusConfig[data.status] || statusConfig.backlog;
|
||||||
const StatusIcon = config.icon;
|
const StatusIcon = config.icon;
|
||||||
const priorityConf = data.priority ? priorityConfig[data.priority as 1 | 2 | 3] : null;
|
const priorityConf = data.priority ? priorityConfig[data.priority as 1 | 2 | 3] : null;
|
||||||
|
|
||||||
|
// Filter highlight states
|
||||||
|
const isMatched = data.isMatched ?? false;
|
||||||
|
const isHighlighted = data.isHighlighted ?? false;
|
||||||
|
const isDimmed = data.isDimmed ?? false;
|
||||||
|
|
||||||
|
// Task is stopped if it's in_progress but not actively running
|
||||||
|
const isStopped = data.status === 'in_progress' && !data.isRunning;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Target handle (left side - receives dependencies) */}
|
{/* Target handle (left side - receives dependencies) */}
|
||||||
@@ -89,39 +95,46 @@ export const TaskNode = memo(function TaskNode({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-3 h-3 !bg-border border-2 border-background',
|
'w-3 h-3 !bg-border border-2 border-background',
|
||||||
'transition-colors duration-200',
|
'transition-colors duration-200',
|
||||||
'hover:!bg-brand-500'
|
'hover:!bg-brand-500',
|
||||||
|
isDimmed && 'opacity-30'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'min-w-[240px] max-w-[280px] rounded-xl border-2 bg-card shadow-md',
|
'min-w-[240px] max-w-[280px] rounded-xl border-2 bg-card shadow-md',
|
||||||
'transition-all duration-200',
|
'transition-all duration-300',
|
||||||
config.borderClass,
|
config.borderClass,
|
||||||
selected && 'ring-2 ring-brand-500 ring-offset-2 ring-offset-background',
|
selected && 'ring-2 ring-brand-500 ring-offset-2 ring-offset-background',
|
||||||
data.isRunning && 'animate-pulse-subtle',
|
data.isRunning && 'animate-pulse-subtle',
|
||||||
data.error && 'border-[var(--status-error)]'
|
data.error && 'border-[var(--status-error)]',
|
||||||
|
// Filter highlight states
|
||||||
|
isMatched && 'graph-node-matched',
|
||||||
|
isHighlighted && !isMatched && 'graph-node-highlighted',
|
||||||
|
isDimmed && 'graph-node-dimmed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Header with status and actions */}
|
{/* Header with status and actions */}
|
||||||
<div className={cn(
|
<div
|
||||||
'flex items-center justify-between px-3 py-2 rounded-t-[10px]',
|
className={cn(
|
||||||
config.bgClass
|
'flex items-center justify-between px-3 py-2 rounded-t-[10px]',
|
||||||
)}>
|
config.bgClass
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<StatusIcon className={cn('w-4 h-4', config.colorClass)} />
|
<StatusIcon className={cn('w-4 h-4', config.colorClass)} />
|
||||||
<span className={cn('text-xs font-medium', config.colorClass)}>
|
<span className={cn('text-xs font-medium', config.colorClass)}>{config.label}</span>
|
||||||
{config.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{/* Priority badge */}
|
{/* Priority badge */}
|
||||||
{priorityConf && (
|
{priorityConf && (
|
||||||
<span className={cn(
|
<span
|
||||||
'text-[10px] font-bold px-1.5 py-0.5 rounded',
|
className={cn(
|
||||||
priorityConf.colorClass
|
'text-[10px] font-bold px-1.5 py-0.5 rounded',
|
||||||
)}>
|
priorityConf.colorClass
|
||||||
|
)}
|
||||||
|
>
|
||||||
{data.priority === 1 ? 'H' : data.priority === 2 ? 'M' : 'L'}
|
{data.priority === 1 ? 'H' : data.priority === 2 ? 'M' : 'L'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -158,39 +171,101 @@ export const TaskNode = memo(function TaskNode({
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Stopped indicator - task is in_progress but not actively running */}
|
||||||
|
{isStopped && (
|
||||||
|
<TooltipProvider delayDuration={200}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className="p-1 rounded bg-[var(--status-warning-bg)]">
|
||||||
|
<Pause className="w-3 h-3 text-[var(--status-warning)]" />
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top" className="text-xs max-w-[200px]">
|
||||||
|
<p>Task paused - click menu to resume</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Actions dropdown */}
|
{/* Actions dropdown */}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-6 w-6 p-0 hover:bg-background/50"
|
className={cn(
|
||||||
|
'h-7 w-7 p-0 rounded-md',
|
||||||
|
'bg-background/60 hover:bg-background',
|
||||||
|
'border border-border/50 hover:border-border',
|
||||||
|
'shadow-sm',
|
||||||
|
'transition-all duration-150'
|
||||||
|
)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<MoreHorizontal className="w-4 h-4" />
|
<MoreVertical className="w-4 h-4 text-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-40">
|
<DropdownMenuContent
|
||||||
<DropdownMenuItem className="text-xs">
|
align="end"
|
||||||
|
className="w-44"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="text-xs cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
data.onViewLogs?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Terminal className="w-3 h-3 mr-2" />
|
||||||
|
View Agent Logs
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="text-xs cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
data.onViewDetails?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Eye className="w-3 h-3 mr-2" />
|
<Eye className="w-3 h-3 mr-2" />
|
||||||
View Details
|
View Details
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{data.status === 'backlog' && !data.isBlocked && (
|
{data.status === 'backlog' && !data.isBlocked && (
|
||||||
<DropdownMenuItem className="text-xs">
|
<DropdownMenuItem
|
||||||
|
className="text-xs cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
data.onStartTask?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Play className="w-3 h-3 mr-2" />
|
<Play className="w-3 h-3 mr-2" />
|
||||||
Start Task
|
Start Task
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{data.isRunning && (
|
{data.isRunning && (
|
||||||
<DropdownMenuItem className="text-xs text-[var(--status-error)]">
|
<DropdownMenuItem
|
||||||
|
className="text-xs text-[var(--status-error)] cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
data.onStopTask?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Pause className="w-3 h-3 mr-2" />
|
<Pause className="w-3 h-3 mr-2" />
|
||||||
Stop Task
|
Stop Task
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
<DropdownMenuSeparator />
|
{isStopped && (
|
||||||
<DropdownMenuItem className="text-xs">
|
<DropdownMenuItem
|
||||||
<GitBranch className="w-3 h-3 mr-2" />
|
className="text-xs text-[var(--status-success)] cursor-pointer"
|
||||||
View Branch
|
onClick={(e) => {
|
||||||
</DropdownMenuItem>
|
e.stopPropagation();
|
||||||
|
data.onResumeTask?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RotateCcw className="w-3 h-3 mr-2" />
|
||||||
|
Resume Task
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
@@ -212,14 +287,22 @@ export const TaskNode = memo(function TaskNode({
|
|||||||
{data.isRunning && (
|
{data.isRunning && (
|
||||||
<div className="mt-2 flex items-center gap-2">
|
<div className="mt-2 flex items-center gap-2">
|
||||||
<div className="flex-1 h-1.5 bg-muted rounded-full overflow-hidden">
|
<div className="flex-1 h-1.5 bg-muted rounded-full overflow-hidden">
|
||||||
<div
|
<div className="h-full bg-[var(--status-in-progress)] rounded-full animate-progress-indeterminate" />
|
||||||
className="h-full bg-[var(--status-in-progress)] rounded-full animate-progress-indeterminate"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] text-muted-foreground">Running...</span>
|
<span className="text-[10px] text-muted-foreground">Running...</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Paused indicator for stopped tasks */}
|
||||||
|
{isStopped && (
|
||||||
|
<div className="mt-2 flex items-center gap-2">
|
||||||
|
<div className="flex-1 h-1.5 bg-muted rounded-full overflow-hidden">
|
||||||
|
<div className="h-full w-1/2 bg-[var(--status-warning)] rounded-full" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] text-[var(--status-warning)] font-medium">Paused</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Branch name if assigned */}
|
{/* Branch name if assigned */}
|
||||||
{data.branchName && (
|
{data.branchName && (
|
||||||
<div className="mt-2 flex items-center gap-1 text-[10px] text-muted-foreground">
|
<div className="mt-2 flex items-center gap-1 text-[10px] text-muted-foreground">
|
||||||
@@ -240,7 +323,8 @@ export const TaskNode = memo(function TaskNode({
|
|||||||
'hover:!bg-brand-500',
|
'hover:!bg-brand-500',
|
||||||
data.status === 'completed' || data.status === 'verified'
|
data.status === 'completed' || data.status === 'verified'
|
||||||
? '!bg-[var(--status-success)]'
|
? '!bg-[var(--status-success)]'
|
||||||
: ''
|
: '',
|
||||||
|
isDimmed && 'opacity-30'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
Background,
|
Background,
|
||||||
BackgroundVariant,
|
BackgroundVariant,
|
||||||
MiniMap,
|
MiniMap,
|
||||||
|
Panel,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
@@ -14,9 +15,25 @@ import {
|
|||||||
import '@xyflow/react/dist/style.css';
|
import '@xyflow/react/dist/style.css';
|
||||||
|
|
||||||
import { Feature } from '@/store/app-store';
|
import { Feature } from '@/store/app-store';
|
||||||
import { TaskNode, DependencyEdge, GraphControls, GraphLegend } from './components';
|
import {
|
||||||
import { useGraphNodes, useGraphLayout, type TaskNodeData } from './hooks';
|
TaskNode,
|
||||||
|
DependencyEdge,
|
||||||
|
GraphControls,
|
||||||
|
GraphLegend,
|
||||||
|
GraphFilterControls,
|
||||||
|
} from './components';
|
||||||
|
import {
|
||||||
|
useGraphNodes,
|
||||||
|
useGraphLayout,
|
||||||
|
useGraphFilter,
|
||||||
|
type TaskNodeData,
|
||||||
|
type GraphFilterState,
|
||||||
|
type NodeActionCallbacks,
|
||||||
|
} from './hooks';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useDebounceValue } from 'usehooks-ts';
|
||||||
|
import { SearchX } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
// Define custom node and edge types - using any to avoid React Flow's strict typing
|
// Define custom node and edge types - using any to avoid React Flow's strict typing
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -32,8 +49,10 @@ const edgeTypes: any = {
|
|||||||
interface GraphCanvasProps {
|
interface GraphCanvasProps {
|
||||||
features: Feature[];
|
features: Feature[];
|
||||||
runningAutoTasks: string[];
|
runningAutoTasks: string[];
|
||||||
onNodeClick?: (featureId: string) => void;
|
searchQuery: string;
|
||||||
|
onSearchQueryChange: (query: string) => void;
|
||||||
onNodeDoubleClick?: (featureId: string) => void;
|
onNodeDoubleClick?: (featureId: string) => void;
|
||||||
|
nodeActionCallbacks?: NodeActionCallbacks;
|
||||||
backgroundStyle?: React.CSSProperties;
|
backgroundStyle?: React.CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
@@ -41,18 +60,41 @@ interface GraphCanvasProps {
|
|||||||
function GraphCanvasInner({
|
function GraphCanvasInner({
|
||||||
features,
|
features,
|
||||||
runningAutoTasks,
|
runningAutoTasks,
|
||||||
onNodeClick,
|
searchQuery,
|
||||||
|
onSearchQueryChange,
|
||||||
onNodeDoubleClick,
|
onNodeDoubleClick,
|
||||||
|
nodeActionCallbacks,
|
||||||
backgroundStyle,
|
backgroundStyle,
|
||||||
className,
|
className,
|
||||||
}: GraphCanvasProps) {
|
}: GraphCanvasProps) {
|
||||||
const [isLocked, setIsLocked] = useState(false);
|
const [isLocked, setIsLocked] = useState(false);
|
||||||
const [layoutDirection, setLayoutDirection] = useState<'LR' | 'TB'>('LR');
|
const [layoutDirection, setLayoutDirection] = useState<'LR' | 'TB'>('LR');
|
||||||
|
|
||||||
// Transform features to nodes and edges
|
// Filter state (category, status, and negative toggle are local to graph view)
|
||||||
|
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
||||||
|
const [selectedStatuses, setSelectedStatuses] = useState<string[]>([]);
|
||||||
|
const [isNegativeFilter, setIsNegativeFilter] = useState(false);
|
||||||
|
|
||||||
|
// Debounce search query for performance with large graphs
|
||||||
|
const [debouncedSearchQuery] = useDebounceValue(searchQuery, 200);
|
||||||
|
|
||||||
|
// Combined filter state
|
||||||
|
const filterState: GraphFilterState = {
|
||||||
|
searchQuery: debouncedSearchQuery,
|
||||||
|
selectedCategories,
|
||||||
|
selectedStatuses,
|
||||||
|
isNegativeFilter,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate filter results
|
||||||
|
const filterResult = useGraphFilter(features, filterState, runningAutoTasks);
|
||||||
|
|
||||||
|
// Transform features to nodes and edges with filter results
|
||||||
const { nodes: initialNodes, edges: initialEdges } = useGraphNodes({
|
const { nodes: initialNodes, edges: initialEdges } = useGraphNodes({
|
||||||
features,
|
features,
|
||||||
runningAutoTasks,
|
runningAutoTasks,
|
||||||
|
filterResult,
|
||||||
|
actionCallbacks: nodeActionCallbacks,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply layout
|
// Apply layout
|
||||||
@@ -80,13 +122,13 @@ function GraphCanvasInner({
|
|||||||
[runLayout]
|
[runLayout]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle node click
|
// Handle clear all filters
|
||||||
const handleNodeClick = useCallback(
|
const handleClearFilters = useCallback(() => {
|
||||||
(_event: React.MouseEvent, node: Node<TaskNodeData>) => {
|
onSearchQueryChange('');
|
||||||
onNodeClick?.(node.id);
|
setSelectedCategories([]);
|
||||||
},
|
setSelectedStatuses([]);
|
||||||
[onNodeClick]
|
setIsNegativeFilter(false);
|
||||||
);
|
}, [onSearchQueryChange]);
|
||||||
|
|
||||||
// Handle node double click
|
// Handle node double click
|
||||||
const handleNodeDoubleClick = useCallback(
|
const handleNodeDoubleClick = useCallback(
|
||||||
@@ -122,7 +164,6 @@ function GraphCanvasInner({
|
|||||||
edges={edges}
|
edges={edges}
|
||||||
onNodesChange={isLocked ? undefined : onNodesChange}
|
onNodesChange={isLocked ? undefined : onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onNodeClick={handleNodeClick}
|
|
||||||
onNodeDoubleClick={handleNodeDoubleClick}
|
onNodeDoubleClick={handleNodeDoubleClick}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
@@ -158,7 +199,35 @@ function GraphCanvasInner({
|
|||||||
layoutDirection={layoutDirection}
|
layoutDirection={layoutDirection}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<GraphFilterControls
|
||||||
|
filterState={filterState}
|
||||||
|
availableCategories={filterResult.availableCategories}
|
||||||
|
hasActiveFilter={filterResult.hasActiveFilter}
|
||||||
|
onCategoriesChange={setSelectedCategories}
|
||||||
|
onStatusesChange={setSelectedStatuses}
|
||||||
|
onNegativeFilterChange={setIsNegativeFilter}
|
||||||
|
onClearFilters={handleClearFilters}
|
||||||
|
/>
|
||||||
|
|
||||||
<GraphLegend />
|
<GraphLegend />
|
||||||
|
|
||||||
|
{/* Empty state when all nodes are filtered out */}
|
||||||
|
{filterResult.hasActiveFilter && filterResult.matchedNodeIds.size === 0 && (
|
||||||
|
<Panel position="top-center" className="mt-20">
|
||||||
|
<div className="flex flex-col items-center gap-3 p-6 rounded-lg bg-popover/95 backdrop-blur-sm border border-border shadow-lg text-popover-foreground">
|
||||||
|
<SearchX className="w-10 h-10 text-muted-foreground" />
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-sm font-medium">No matching tasks</p>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Try adjusting your filters or search query
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" size="sm" onClick={handleClearFilters} className="mt-1">
|
||||||
|
Clear Filters
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
)}
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useMemo, useCallback } from 'react';
|
|||||||
import { Feature, useAppStore } from '@/store/app-store';
|
import { Feature, useAppStore } from '@/store/app-store';
|
||||||
import { GraphCanvas } from './graph-canvas';
|
import { GraphCanvas } from './graph-canvas';
|
||||||
import { useBoardBackground } from '../board-view/hooks';
|
import { useBoardBackground } from '../board-view/hooks';
|
||||||
|
import { NodeActionCallbacks } from './hooks';
|
||||||
|
|
||||||
interface GraphViewProps {
|
interface GraphViewProps {
|
||||||
features: Feature[];
|
features: Feature[];
|
||||||
@@ -9,8 +10,13 @@ interface GraphViewProps {
|
|||||||
currentWorktreePath: string | null;
|
currentWorktreePath: string | null;
|
||||||
currentWorktreeBranch: string | null;
|
currentWorktreeBranch: string | null;
|
||||||
projectPath: string | null;
|
projectPath: string | null;
|
||||||
|
searchQuery: string;
|
||||||
|
onSearchQueryChange: (query: string) => void;
|
||||||
onEditFeature: (feature: Feature) => void;
|
onEditFeature: (feature: Feature) => void;
|
||||||
onViewOutput: (feature: Feature) => void;
|
onViewOutput: (feature: Feature) => void;
|
||||||
|
onStartTask?: (feature: Feature) => void;
|
||||||
|
onStopTask?: (feature: Feature) => void;
|
||||||
|
onResumeTask?: (feature: Feature) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GraphView({
|
export function GraphView({
|
||||||
@@ -19,8 +25,13 @@ export function GraphView({
|
|||||||
currentWorktreePath,
|
currentWorktreePath,
|
||||||
currentWorktreeBranch,
|
currentWorktreeBranch,
|
||||||
projectPath,
|
projectPath,
|
||||||
|
searchQuery,
|
||||||
|
onSearchQueryChange,
|
||||||
onEditFeature,
|
onEditFeature,
|
||||||
onViewOutput,
|
onViewOutput,
|
||||||
|
onStartTask,
|
||||||
|
onStopTask,
|
||||||
|
onResumeTask,
|
||||||
}: GraphViewProps) {
|
}: GraphViewProps) {
|
||||||
const { currentProject } = useAppStore();
|
const { currentProject } = useAppStore();
|
||||||
|
|
||||||
@@ -35,7 +46,7 @@ export function GraphView({
|
|||||||
// Skip completed features (they're in archive)
|
// Skip completed features (they're in archive)
|
||||||
if (f.status === 'completed') return false;
|
if (f.status === 'completed') return false;
|
||||||
|
|
||||||
const featureBranch = f.branchName;
|
const featureBranch = f.branchName as string | undefined;
|
||||||
|
|
||||||
if (!featureBranch) {
|
if (!featureBranch) {
|
||||||
// No branch assigned - show only on primary worktree
|
// No branch assigned - show only on primary worktree
|
||||||
@@ -52,17 +63,6 @@ export function GraphView({
|
|||||||
});
|
});
|
||||||
}, [features, currentWorktreePath, currentWorktreeBranch, projectPath]);
|
}, [features, currentWorktreePath, currentWorktreeBranch, projectPath]);
|
||||||
|
|
||||||
// Handle node click - view details
|
|
||||||
const handleNodeClick = useCallback(
|
|
||||||
(featureId: string) => {
|
|
||||||
const feature = features.find((f) => f.id === featureId);
|
|
||||||
if (feature) {
|
|
||||||
onViewOutput(feature);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[features, onViewOutput]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle node double click - edit
|
// Handle node double click - edit
|
||||||
const handleNodeDoubleClick = useCallback(
|
const handleNodeDoubleClick = useCallback(
|
||||||
(featureId: string) => {
|
(featureId: string) => {
|
||||||
@@ -74,13 +74,52 @@ export function GraphView({
|
|||||||
[features, onEditFeature]
|
[features, onEditFeature]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Node action callbacks for dropdown menu
|
||||||
|
const nodeActionCallbacks: NodeActionCallbacks = useMemo(
|
||||||
|
() => ({
|
||||||
|
onViewLogs: (featureId: string) => {
|
||||||
|
const feature = features.find((f) => f.id === featureId);
|
||||||
|
if (feature) {
|
||||||
|
onViewOutput(feature);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onViewDetails: (featureId: string) => {
|
||||||
|
const feature = features.find((f) => f.id === featureId);
|
||||||
|
if (feature) {
|
||||||
|
onEditFeature(feature);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onStartTask: (featureId: string) => {
|
||||||
|
const feature = features.find((f) => f.id === featureId);
|
||||||
|
if (feature) {
|
||||||
|
onStartTask?.(feature);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onStopTask: (featureId: string) => {
|
||||||
|
const feature = features.find((f) => f.id === featureId);
|
||||||
|
if (feature) {
|
||||||
|
onStopTask?.(feature);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onResumeTask: (featureId: string) => {
|
||||||
|
const feature = features.find((f) => f.id === featureId);
|
||||||
|
if (feature) {
|
||||||
|
onResumeTask?.(feature);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[features, onViewOutput, onEditFeature, onStartTask, onStopTask, onResumeTask]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 overflow-hidden relative">
|
<div className="flex-1 overflow-hidden relative">
|
||||||
<GraphCanvas
|
<GraphCanvas
|
||||||
features={filteredFeatures}
|
features={filteredFeatures}
|
||||||
runningAutoTasks={runningAutoTasks}
|
runningAutoTasks={runningAutoTasks}
|
||||||
onNodeClick={handleNodeClick}
|
searchQuery={searchQuery}
|
||||||
|
onSearchQueryChange={onSearchQueryChange}
|
||||||
onNodeDoubleClick={handleNodeDoubleClick}
|
onNodeDoubleClick={handleNodeDoubleClick}
|
||||||
|
nodeActionCallbacks={nodeActionCallbacks}
|
||||||
backgroundStyle={backgroundImageStyle}
|
backgroundStyle={backgroundImageStyle}
|
||||||
className="h-full"
|
className="h-full"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,2 +1,9 @@
|
|||||||
export { useGraphNodes, type TaskNode, type DependencyEdge, type TaskNodeData } from './use-graph-nodes';
|
export {
|
||||||
|
useGraphNodes,
|
||||||
|
type TaskNode,
|
||||||
|
type DependencyEdge,
|
||||||
|
type TaskNodeData,
|
||||||
|
type NodeActionCallbacks,
|
||||||
|
} from './use-graph-nodes';
|
||||||
export { useGraphLayout } from './use-graph-layout';
|
export { useGraphLayout } from './use-graph-layout';
|
||||||
|
export { useGraphFilter, type GraphFilterState, type GraphFilterResult } from './use-graph-filter';
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { Feature } from '@/store/app-store';
|
||||||
|
|
||||||
|
export interface GraphFilterState {
|
||||||
|
searchQuery: string;
|
||||||
|
selectedCategories: string[];
|
||||||
|
selectedStatuses: string[];
|
||||||
|
isNegativeFilter: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Available status filter values
|
||||||
|
export const STATUS_FILTER_OPTIONS = [
|
||||||
|
'running',
|
||||||
|
'paused',
|
||||||
|
'backlog',
|
||||||
|
'waiting_approval',
|
||||||
|
'verified',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type StatusFilterValue = (typeof STATUS_FILTER_OPTIONS)[number];
|
||||||
|
|
||||||
|
export interface GraphFilterResult {
|
||||||
|
matchedNodeIds: Set<string>;
|
||||||
|
highlightedNodeIds: Set<string>;
|
||||||
|
highlightedEdgeIds: Set<string>;
|
||||||
|
availableCategories: string[];
|
||||||
|
hasActiveFilter: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverses up the dependency tree to find all ancestors of a node
|
||||||
|
*/
|
||||||
|
function getAncestors(
|
||||||
|
featureId: string,
|
||||||
|
featureMap: Map<string, Feature>,
|
||||||
|
visited: Set<string>
|
||||||
|
): void {
|
||||||
|
if (visited.has(featureId)) return;
|
||||||
|
visited.add(featureId);
|
||||||
|
|
||||||
|
const feature = featureMap.get(featureId);
|
||||||
|
if (!feature?.dependencies) return;
|
||||||
|
|
||||||
|
const deps = feature.dependencies as string[] | undefined;
|
||||||
|
if (!deps) return;
|
||||||
|
|
||||||
|
for (const depId of deps) {
|
||||||
|
if (featureMap.has(depId)) {
|
||||||
|
getAncestors(depId, featureMap, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverses down to find all descendants (features that depend on this one)
|
||||||
|
*/
|
||||||
|
function getDescendants(featureId: string, features: Feature[], visited: Set<string>): void {
|
||||||
|
if (visited.has(featureId)) return;
|
||||||
|
visited.add(featureId);
|
||||||
|
|
||||||
|
for (const feature of features) {
|
||||||
|
const deps = feature.dependencies as string[] | undefined;
|
||||||
|
if (deps?.includes(featureId)) {
|
||||||
|
getDescendants(feature.id, features, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all edges in the highlighted path
|
||||||
|
*/
|
||||||
|
function getHighlightedEdges(highlightedNodeIds: Set<string>, features: Feature[]): Set<string> {
|
||||||
|
const edges = new Set<string>();
|
||||||
|
|
||||||
|
for (const feature of features) {
|
||||||
|
if (!highlightedNodeIds.has(feature.id)) continue;
|
||||||
|
const deps = feature.dependencies as string[] | undefined;
|
||||||
|
if (!deps) continue;
|
||||||
|
|
||||||
|
for (const depId of deps) {
|
||||||
|
if (highlightedNodeIds.has(depId)) {
|
||||||
|
edges.add(`${depId}->${feature.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the effective status of a feature (accounting for running state)
|
||||||
|
*/
|
||||||
|
function getEffectiveStatus(feature: Feature, runningAutoTasks: string[]): StatusFilterValue {
|
||||||
|
if (feature.status === 'in_progress') {
|
||||||
|
return runningAutoTasks.includes(feature.id) ? 'running' : 'paused';
|
||||||
|
}
|
||||||
|
return feature.status as StatusFilterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to calculate graph filter results based on search query, categories, statuses, and filter mode
|
||||||
|
*/
|
||||||
|
export function useGraphFilter(
|
||||||
|
features: Feature[],
|
||||||
|
filterState: GraphFilterState,
|
||||||
|
runningAutoTasks: string[] = []
|
||||||
|
): GraphFilterResult {
|
||||||
|
const { searchQuery, selectedCategories, selectedStatuses, isNegativeFilter } = filterState;
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
// Extract all unique categories
|
||||||
|
const availableCategories = Array.from(
|
||||||
|
new Set(features.map((f) => f.category).filter(Boolean))
|
||||||
|
).sort();
|
||||||
|
|
||||||
|
const normalizedQuery = searchQuery.toLowerCase().trim();
|
||||||
|
const hasSearchQuery = normalizedQuery.length > 0;
|
||||||
|
const hasCategoryFilter = selectedCategories.length > 0;
|
||||||
|
const hasStatusFilter = selectedStatuses.length > 0;
|
||||||
|
const hasActiveFilter =
|
||||||
|
hasSearchQuery || hasCategoryFilter || hasStatusFilter || isNegativeFilter;
|
||||||
|
|
||||||
|
// If no filters active, return empty sets (show all nodes normally)
|
||||||
|
if (!hasActiveFilter) {
|
||||||
|
return {
|
||||||
|
matchedNodeIds: new Set<string>(),
|
||||||
|
highlightedNodeIds: new Set<string>(),
|
||||||
|
highlightedEdgeIds: new Set<string>(),
|
||||||
|
availableCategories,
|
||||||
|
hasActiveFilter: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find directly matched nodes
|
||||||
|
const matchedNodeIds = new Set<string>();
|
||||||
|
const featureMap = new Map(features.map((f) => [f.id, f]));
|
||||||
|
|
||||||
|
for (const feature of features) {
|
||||||
|
let matchesSearch = true;
|
||||||
|
let matchesCategory = true;
|
||||||
|
let matchesStatus = true;
|
||||||
|
|
||||||
|
// Check search query match (title or description)
|
||||||
|
if (hasSearchQuery) {
|
||||||
|
const titleMatch = feature.title?.toLowerCase().includes(normalizedQuery);
|
||||||
|
const descMatch = feature.description?.toLowerCase().includes(normalizedQuery);
|
||||||
|
matchesSearch = titleMatch || descMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check category match
|
||||||
|
if (hasCategoryFilter) {
|
||||||
|
matchesCategory = selectedCategories.includes(feature.category);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check status match
|
||||||
|
if (hasStatusFilter) {
|
||||||
|
const effectiveStatus = getEffectiveStatus(feature, runningAutoTasks);
|
||||||
|
matchesStatus = selectedStatuses.includes(effectiveStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All conditions must be true for a match
|
||||||
|
if (matchesSearch && matchesCategory && matchesStatus) {
|
||||||
|
matchedNodeIds.add(feature.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply negative filter if enabled (invert the matched set)
|
||||||
|
let effectiveMatchedIds: Set<string>;
|
||||||
|
if (isNegativeFilter) {
|
||||||
|
effectiveMatchedIds = new Set(
|
||||||
|
features.filter((f) => !matchedNodeIds.has(f.id)).map((f) => f.id)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
effectiveMatchedIds = matchedNodeIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate full path (ancestors + descendants) for highlighted nodes
|
||||||
|
const highlightedNodeIds = new Set<string>();
|
||||||
|
|
||||||
|
for (const id of effectiveMatchedIds) {
|
||||||
|
// Add the matched node itself
|
||||||
|
highlightedNodeIds.add(id);
|
||||||
|
|
||||||
|
// Add all ancestors (dependencies)
|
||||||
|
getAncestors(id, featureMap, highlightedNodeIds);
|
||||||
|
|
||||||
|
// Add all descendants (dependents)
|
||||||
|
getDescendants(id, features, highlightedNodeIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get edges in the highlighted path
|
||||||
|
const highlightedEdgeIds = getHighlightedEdges(highlightedNodeIds, features);
|
||||||
|
|
||||||
|
return {
|
||||||
|
matchedNodeIds: effectiveMatchedIds,
|
||||||
|
highlightedNodeIds,
|
||||||
|
highlightedEdgeIds,
|
||||||
|
availableCategories,
|
||||||
|
hasActiveFilter: true,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
features,
|
||||||
|
searchQuery,
|
||||||
|
selectedCategories,
|
||||||
|
selectedStatuses,
|
||||||
|
isNegativeFilter,
|
||||||
|
runningAutoTasks,
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -2,26 +2,63 @@ import { useMemo } from 'react';
|
|||||||
import { Node, Edge } from '@xyflow/react';
|
import { Node, Edge } from '@xyflow/react';
|
||||||
import { Feature } from '@/store/app-store';
|
import { Feature } from '@/store/app-store';
|
||||||
import { getBlockingDependencies } from '@automaker/dependency-resolver';
|
import { getBlockingDependencies } from '@automaker/dependency-resolver';
|
||||||
|
import { GraphFilterResult } from './use-graph-filter';
|
||||||
|
|
||||||
export interface TaskNodeData extends Feature {
|
export interface TaskNodeData extends Feature {
|
||||||
|
// Re-declare properties from BaseFeature that have index signature issues
|
||||||
|
priority?: number;
|
||||||
|
error?: string;
|
||||||
|
branchName?: string;
|
||||||
|
dependencies?: string[];
|
||||||
|
// Task node specific properties
|
||||||
isBlocked: boolean;
|
isBlocked: boolean;
|
||||||
isRunning: boolean;
|
isRunning: boolean;
|
||||||
blockingDependencies: string[];
|
blockingDependencies: string[];
|
||||||
|
// Filter highlight states
|
||||||
|
isMatched?: boolean;
|
||||||
|
isHighlighted?: boolean;
|
||||||
|
isDimmed?: boolean;
|
||||||
|
// Action callbacks
|
||||||
|
onViewLogs?: () => void;
|
||||||
|
onViewDetails?: () => void;
|
||||||
|
onStartTask?: () => void;
|
||||||
|
onStopTask?: () => void;
|
||||||
|
onResumeTask?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TaskNode = Node<TaskNodeData, 'task'>;
|
export type TaskNode = Node<TaskNodeData, 'task'>;
|
||||||
export type DependencyEdge = Edge<{ sourceStatus: Feature['status']; targetStatus: Feature['status'] }>;
|
export type DependencyEdge = Edge<{
|
||||||
|
sourceStatus: Feature['status'];
|
||||||
|
targetStatus: Feature['status'];
|
||||||
|
isHighlighted?: boolean;
|
||||||
|
isDimmed?: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export interface NodeActionCallbacks {
|
||||||
|
onViewLogs?: (featureId: string) => void;
|
||||||
|
onViewDetails?: (featureId: string) => void;
|
||||||
|
onStartTask?: (featureId: string) => void;
|
||||||
|
onStopTask?: (featureId: string) => void;
|
||||||
|
onResumeTask?: (featureId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
interface UseGraphNodesProps {
|
interface UseGraphNodesProps {
|
||||||
features: Feature[];
|
features: Feature[];
|
||||||
runningAutoTasks: string[];
|
runningAutoTasks: string[];
|
||||||
|
filterResult?: GraphFilterResult;
|
||||||
|
actionCallbacks?: NodeActionCallbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms features into React Flow nodes and edges
|
* Transforms features into React Flow nodes and edges
|
||||||
* Creates dependency edges based on feature.dependencies array
|
* Creates dependency edges based on feature.dependencies array
|
||||||
*/
|
*/
|
||||||
export function useGraphNodes({ features, runningAutoTasks }: UseGraphNodesProps) {
|
export function useGraphNodes({
|
||||||
|
features,
|
||||||
|
runningAutoTasks,
|
||||||
|
filterResult,
|
||||||
|
actionCallbacks,
|
||||||
|
}: UseGraphNodesProps) {
|
||||||
const { nodes, edges } = useMemo(() => {
|
const { nodes, edges } = useMemo(() => {
|
||||||
const nodeList: TaskNode[] = [];
|
const nodeList: TaskNode[] = [];
|
||||||
const edgeList: DependencyEdge[] = [];
|
const edgeList: DependencyEdge[] = [];
|
||||||
@@ -30,11 +67,22 @@ export function useGraphNodes({ features, runningAutoTasks }: UseGraphNodesProps
|
|||||||
// Create feature map for quick lookups
|
// Create feature map for quick lookups
|
||||||
features.forEach((f) => featureMap.set(f.id, f));
|
features.forEach((f) => featureMap.set(f.id, f));
|
||||||
|
|
||||||
|
// Extract filter state
|
||||||
|
const hasActiveFilter = filterResult?.hasActiveFilter ?? false;
|
||||||
|
const matchedNodeIds = filterResult?.matchedNodeIds ?? new Set<string>();
|
||||||
|
const highlightedNodeIds = filterResult?.highlightedNodeIds ?? new Set<string>();
|
||||||
|
const highlightedEdgeIds = filterResult?.highlightedEdgeIds ?? new Set<string>();
|
||||||
|
|
||||||
// Create nodes
|
// Create nodes
|
||||||
features.forEach((feature) => {
|
features.forEach((feature) => {
|
||||||
const isRunning = runningAutoTasks.includes(feature.id);
|
const isRunning = runningAutoTasks.includes(feature.id);
|
||||||
const blockingDeps = getBlockingDependencies(feature, features);
|
const blockingDeps = getBlockingDependencies(feature, features);
|
||||||
|
|
||||||
|
// Calculate filter highlight states
|
||||||
|
const isMatched = hasActiveFilter && matchedNodeIds.has(feature.id);
|
||||||
|
const isHighlighted = hasActiveFilter && highlightedNodeIds.has(feature.id);
|
||||||
|
const isDimmed = hasActiveFilter && !highlightedNodeIds.has(feature.id);
|
||||||
|
|
||||||
const node: TaskNode = {
|
const node: TaskNode = {
|
||||||
id: feature.id,
|
id: feature.id,
|
||||||
type: 'task',
|
type: 'task',
|
||||||
@@ -44,19 +92,46 @@ export function useGraphNodes({ features, runningAutoTasks }: UseGraphNodesProps
|
|||||||
isBlocked: blockingDeps.length > 0,
|
isBlocked: blockingDeps.length > 0,
|
||||||
isRunning,
|
isRunning,
|
||||||
blockingDependencies: blockingDeps,
|
blockingDependencies: blockingDeps,
|
||||||
|
// Filter states
|
||||||
|
isMatched,
|
||||||
|
isHighlighted,
|
||||||
|
isDimmed,
|
||||||
|
// Action callbacks (bound to this feature's ID)
|
||||||
|
onViewLogs: actionCallbacks?.onViewLogs
|
||||||
|
? () => actionCallbacks.onViewLogs!(feature.id)
|
||||||
|
: undefined,
|
||||||
|
onViewDetails: actionCallbacks?.onViewDetails
|
||||||
|
? () => actionCallbacks.onViewDetails!(feature.id)
|
||||||
|
: undefined,
|
||||||
|
onStartTask: actionCallbacks?.onStartTask
|
||||||
|
? () => actionCallbacks.onStartTask!(feature.id)
|
||||||
|
: undefined,
|
||||||
|
onStopTask: actionCallbacks?.onStopTask
|
||||||
|
? () => actionCallbacks.onStopTask!(feature.id)
|
||||||
|
: undefined,
|
||||||
|
onResumeTask: actionCallbacks?.onResumeTask
|
||||||
|
? () => actionCallbacks.onResumeTask!(feature.id)
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
nodeList.push(node);
|
nodeList.push(node);
|
||||||
|
|
||||||
// Create edges for dependencies
|
// Create edges for dependencies
|
||||||
if (feature.dependencies && feature.dependencies.length > 0) {
|
const deps = feature.dependencies as string[] | undefined;
|
||||||
feature.dependencies.forEach((depId: string) => {
|
if (deps && deps.length > 0) {
|
||||||
|
deps.forEach((depId: string) => {
|
||||||
// Only create edge if the dependency exists in current view
|
// Only create edge if the dependency exists in current view
|
||||||
if (featureMap.has(depId)) {
|
if (featureMap.has(depId)) {
|
||||||
const sourceFeature = featureMap.get(depId)!;
|
const sourceFeature = featureMap.get(depId)!;
|
||||||
|
const edgeId = `${depId}->${feature.id}`;
|
||||||
|
|
||||||
|
// Calculate edge highlight states
|
||||||
|
const edgeIsHighlighted = hasActiveFilter && highlightedEdgeIds.has(edgeId);
|
||||||
|
const edgeIsDimmed = hasActiveFilter && !highlightedEdgeIds.has(edgeId);
|
||||||
|
|
||||||
const edge: DependencyEdge = {
|
const edge: DependencyEdge = {
|
||||||
id: `${depId}->${feature.id}`,
|
id: edgeId,
|
||||||
source: depId,
|
source: depId,
|
||||||
target: feature.id,
|
target: feature.id,
|
||||||
type: 'dependency',
|
type: 'dependency',
|
||||||
@@ -64,6 +139,8 @@ export function useGraphNodes({ features, runningAutoTasks }: UseGraphNodesProps
|
|||||||
data: {
|
data: {
|
||||||
sourceStatus: sourceFeature.status,
|
sourceStatus: sourceFeature.status,
|
||||||
targetStatus: feature.status,
|
targetStatus: feature.status,
|
||||||
|
isHighlighted: edgeIsHighlighted,
|
||||||
|
isDimmed: edgeIsDimmed,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
edgeList.push(edge);
|
edgeList.push(edge);
|
||||||
@@ -73,7 +150,7 @@ export function useGraphNodes({ features, runningAutoTasks }: UseGraphNodesProps
|
|||||||
});
|
});
|
||||||
|
|
||||||
return { nodes: nodeList, edges: edgeList };
|
return { nodes: nodeList, edges: edgeList };
|
||||||
}, [features, runningAutoTasks]);
|
}, [features, runningAutoTasks, filterResult, actionCallbacks]);
|
||||||
|
|
||||||
return { nodes, edges };
|
return { nodes, edges };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
export { GraphView } from './graph-view';
|
export { GraphView } from './graph-view';
|
||||||
export { GraphCanvas } from './graph-canvas';
|
export { GraphCanvas } from './graph-canvas';
|
||||||
export { TaskNode, DependencyEdge, GraphControls, GraphLegend } from './components';
|
export { TaskNode, DependencyEdge, GraphControls, GraphLegend } from './components';
|
||||||
export { useGraphNodes, useGraphLayout, type TaskNode as TaskNodeType, type DependencyEdge as DependencyEdgeType } from './hooks';
|
export {
|
||||||
|
useGraphNodes,
|
||||||
|
useGraphLayout,
|
||||||
|
type TaskNode as TaskNodeType,
|
||||||
|
type DependencyEdge as DependencyEdgeType,
|
||||||
|
} from './hooks';
|
||||||
|
|||||||
@@ -1049,12 +1049,79 @@
|
|||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Graph Filter Highlight States */
|
||||||
|
|
||||||
|
/* Matched node - direct search/filter match */
|
||||||
|
.graph-node-matched {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 3px var(--brand-500),
|
||||||
|
0 0 20px 4px var(--brand-500);
|
||||||
|
border-color: var(--brand-500) !important;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animated glow for matched nodes */
|
||||||
|
@keyframes matched-node-glow {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 3px var(--brand-500),
|
||||||
|
0 0 15px 2px var(--brand-500);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 3px var(--brand-500),
|
||||||
|
0 0 25px 6px var(--brand-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph-node-matched {
|
||||||
|
animation: matched-node-glow 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Highlighted path node - part of the dependency path */
|
||||||
|
.graph-node-highlighted {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px var(--brand-400),
|
||||||
|
0 0 12px 2px var(--brand-400);
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dimmed node - not part of filter results */
|
||||||
|
.graph-node-dimmed {
|
||||||
|
opacity: 0.25;
|
||||||
|
filter: grayscale(60%);
|
||||||
|
transition:
|
||||||
|
opacity 0.3s ease,
|
||||||
|
filter 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph-node-dimmed:hover {
|
||||||
|
opacity: 0.4;
|
||||||
|
filter: grayscale(40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Highlighted edge styles */
|
||||||
|
.graph-edge-highlighted path {
|
||||||
|
stroke: var(--brand-500) !important;
|
||||||
|
stroke-width: 4px !important;
|
||||||
|
filter: drop-shadow(0 0 6px var(--brand-500));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dimmed edge styles */
|
||||||
|
.graph-edge-dimmed path {
|
||||||
|
opacity: 0.15;
|
||||||
|
stroke-width: 1px !important;
|
||||||
|
filter: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Reduce motion preference */
|
/* Reduce motion preference */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.graph-canvas .animated-edge path,
|
.graph-canvas .animated-edge path,
|
||||||
.graph-canvas .edge-flowing path,
|
.graph-canvas .edge-flowing path,
|
||||||
.animate-pulse-subtle,
|
.animate-pulse-subtle,
|
||||||
.animate-progress-indeterminate {
|
.animate-progress-indeterminate,
|
||||||
|
.graph-node-matched {
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
110
package-lock.json
generated
110
package-lock.json
generated
@@ -115,6 +115,7 @@
|
|||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
|
"usehooks-ts": "^3.1.1",
|
||||||
"zustand": "^5.0.9"
|
"zustand": "^5.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -421,6 +422,7 @@
|
|||||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
@@ -1004,6 +1006,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.4.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.4.tgz",
|
||||||
"integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==",
|
"integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.5.0",
|
"@codemirror/state": "^6.5.0",
|
||||||
"crelt": "^1.0.6",
|
"crelt": "^1.0.6",
|
||||||
@@ -1046,6 +1049,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/accessibility": "^3.1.1",
|
"@dnd-kit/accessibility": "^3.1.1",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
@@ -1866,7 +1870,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-dirname": "^0.1.0",
|
"cross-dirname": "^0.1.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
@@ -1888,7 +1891,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^6.0.1",
|
"jsonfile": "^6.0.1",
|
||||||
@@ -1905,7 +1907,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"universalify": "^2.0.0"
|
"universalify": "^2.0.0"
|
||||||
},
|
},
|
||||||
@@ -1920,7 +1921,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
@@ -2676,7 +2676,6 @@
|
|||||||
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
|
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
@@ -2801,7 +2800,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -2818,7 +2816,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -2835,7 +2832,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -2944,7 +2940,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -2967,7 +2962,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -2990,7 +2984,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -3076,7 +3069,6 @@
|
|||||||
],
|
],
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emnapi/runtime": "^1.7.0"
|
"@emnapi/runtime": "^1.7.0"
|
||||||
},
|
},
|
||||||
@@ -3099,7 +3091,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -3119,7 +3110,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -3458,8 +3448,7 @@
|
|||||||
"version": "16.0.10",
|
"version": "16.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.10.tgz",
|
||||||
"integrity": "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==",
|
"integrity": "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "16.0.10",
|
"version": "16.0.10",
|
||||||
@@ -3473,7 +3462,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
@@ -3490,7 +3478,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
@@ -3507,7 +3494,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
@@ -3524,7 +3510,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
@@ -3541,7 +3526,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
@@ -3558,7 +3542,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
@@ -3575,7 +3558,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
@@ -3592,7 +3574,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
@@ -3683,6 +3664,7 @@
|
|||||||
"integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
|
"integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.57.0"
|
"playwright": "1.57.0"
|
||||||
},
|
},
|
||||||
@@ -5093,7 +5075,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
@@ -5427,6 +5408,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.141.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.141.6.tgz",
|
||||||
"integrity": "sha512-qWFxi2D6eGc1L03RzUuhyEOplZ7Q6q62YOl7Of9Y0q4YjwQwxRm4zxwDVtvUIoy4RLVCpqp5UoE+Nxv2PY9trg==",
|
"integrity": "sha512-qWFxi2D6eGc1L03RzUuhyEOplZ7Q6q62YOl7Of9Y0q4YjwQwxRm4zxwDVtvUIoy4RLVCpqp5UoE+Nxv2PY9trg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/history": "1.141.0",
|
"@tanstack/history": "1.141.0",
|
||||||
"@tanstack/react-store": "^0.8.0",
|
"@tanstack/react-store": "^0.8.0",
|
||||||
@@ -5978,6 +5960,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -5988,6 +5971,7 @@
|
|||||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
}
|
}
|
||||||
@@ -6093,6 +6077,7 @@
|
|||||||
"integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
|
"integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.50.0",
|
"@typescript-eslint/scope-manager": "8.50.0",
|
||||||
"@typescript-eslint/types": "8.50.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
@@ -6586,7 +6571,8 @@
|
|||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@xyflow/react": {
|
"node_modules/@xyflow/react": {
|
||||||
"version": "12.10.0",
|
"version": "12.10.0",
|
||||||
@@ -6684,6 +6670,7 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -6744,6 +6731,7 @@
|
|||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -7303,6 +7291,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -7834,8 +7823,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/cliui": {
|
"node_modules/cliui": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
@@ -8121,8 +8109,7 @@
|
|||||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/cross-env": {
|
"node_modules/cross-env": {
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
@@ -8219,6 +8206,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
@@ -8520,6 +8508,7 @@
|
|||||||
"integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==",
|
"integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-builder-lib": "26.0.12",
|
"app-builder-lib": "26.0.12",
|
||||||
"builder-util": "26.0.11",
|
"builder-util": "26.0.11",
|
||||||
@@ -8846,7 +8835,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/asar": "^3.2.1",
|
"@electron/asar": "^3.2.1",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
@@ -8867,7 +8855,6 @@
|
|||||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^4.0.0",
|
||||||
@@ -9118,6 +9105,7 @@
|
|||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -11023,7 +11011,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -11085,7 +11072,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -11461,6 +11447,12 @@
|
|||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.debounce": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@@ -13498,7 +13490,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.6",
|
"nanoid": "^3.3.6",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
@@ -13515,7 +13506,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^9.4.0"
|
"commander": "^9.4.0"
|
||||||
},
|
},
|
||||||
@@ -13533,7 +13523,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.20.0 || >=14"
|
"node": "^12.20.0 || >=14"
|
||||||
}
|
}
|
||||||
@@ -13722,6 +13711,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -13731,6 +13721,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -14080,7 +14071,6 @@
|
|||||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
},
|
},
|
||||||
@@ -14269,6 +14259,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.0.tgz",
|
||||||
"integrity": "sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==",
|
"integrity": "sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
@@ -14317,7 +14308,6 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@img/colour": "^1.0.0",
|
"@img/colour": "^1.0.0",
|
||||||
"detect-libc": "^2.1.2",
|
"detect-libc": "^2.1.2",
|
||||||
@@ -14368,7 +14358,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -14391,7 +14380,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -14414,7 +14402,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -14431,7 +14418,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -14448,7 +14434,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -14465,7 +14450,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -14482,7 +14466,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -14499,7 +14482,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -14516,7 +14498,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
@@ -14533,7 +14514,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -14556,7 +14536,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -14579,7 +14558,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -14602,7 +14580,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -14625,7 +14602,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -14648,7 +14624,6 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
},
|
},
|
||||||
@@ -15117,7 +15092,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||||
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"client-only": "0.0.1"
|
"client-only": "0.0.1"
|
||||||
},
|
},
|
||||||
@@ -15287,7 +15261,6 @@
|
|||||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"rimraf": "~2.6.2"
|
"rimraf": "~2.6.2"
|
||||||
@@ -15351,7 +15324,6 @@
|
|||||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.6"
|
"minimist": "^1.2.6"
|
||||||
},
|
},
|
||||||
@@ -15449,6 +15421,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -15653,6 +15626,7 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -15922,6 +15896,21 @@
|
|||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/usehooks-ts": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.debounce": "^4.0.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.15.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/utf8-byte-length": {
|
"node_modules/utf8-byte-length": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
||||||
@@ -16009,6 +15998,7 @@
|
|||||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -16098,7 +16088,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz",
|
"resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz",
|
||||||
"integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==",
|
"integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/fdir": {
|
"node_modules/vite/node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
@@ -16124,6 +16115,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -16166,6 +16158,7 @@
|
|||||||
"integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==",
|
"integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "4.0.16",
|
"@vitest/expect": "4.0.16",
|
||||||
"@vitest/mocker": "4.0.16",
|
"@vitest/mocker": "4.0.16",
|
||||||
@@ -16423,6 +16416,7 @@
|
|||||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user