sync: contextual substrate UX from private repo
- Add DimensionIconsContext for app-wide dimension icon sharing - Enhanced getNodeIcon() with favicon/YouTube/PDF detection + dimension icons - Propagate dynamic icons to GridView, ListView, SearchModal, FocusPanel, ViewsOverlay, FolderViewOverlay - Add RahEdge component with hover labels for map edges - Update RahNode with dimension-colored borders and icon support - Add MiniMap with dimension color coding to MapPane - MCP server: add description field to rah_update_node, bump standalone to v1.4.2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
d6987a3dc1
commit
d723212ed3
@@ -7,6 +7,7 @@ import ConfirmDialog from '../common/ConfirmDialog';
|
||||
import InputDialog from '../common/InputDialog';
|
||||
import { getNodeIcon } from '@/utils/nodeIcons';
|
||||
import LucideIconPicker, { DynamicIcon } from '../common/LucideIconPicker';
|
||||
import { useDimensionIcons } from '@/context/DimensionIconsContext';
|
||||
import { usePersistentState } from '@/hooks/usePersistentState';
|
||||
|
||||
type DimensionViewMode = 'grid' | 'list' | 'kanban';
|
||||
@@ -121,8 +122,8 @@ export default function FolderViewOverlay({ onClose, onNodeOpen, refreshToken, o
|
||||
// Node priority ordering within dimensions (persisted)
|
||||
const [dimensionOrders, setDimensionOrders] = usePersistentState<Record<string, number[]>>('ui.dimensionOrders', {});
|
||||
|
||||
// Dimension icons (persisted) - maps dimension name to Lucide icon name
|
||||
const [dimensionIcons, setDimensionIcons] = usePersistentState<Record<string, string>>('ui.dimensionIcons', {});
|
||||
// Dimension icons from shared context
|
||||
const { dimensionIcons, setDimensionIcons } = useDimensionIcons();
|
||||
|
||||
// Dimension edit modal state
|
||||
const [editingDimensionModal, setEditingDimensionModal] = useState<DimensionSummary | null>(null);
|
||||
@@ -1235,7 +1236,7 @@ export default function FolderViewOverlay({ onClose, onNodeOpen, refreshToken, o
|
||||
</div>
|
||||
{node.link && (
|
||||
<span style={{ flexShrink: 0 }}>
|
||||
{getNodeIcon(node)}
|
||||
{getNodeIcon(node, dimensionIcons)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -1359,7 +1360,7 @@ export default function FolderViewOverlay({ onClose, onNodeOpen, refreshToken, o
|
||||
borderRadius: '8px',
|
||||
flexShrink: 0
|
||||
}}>
|
||||
{getNodeIcon(node)}
|
||||
{getNodeIcon(node, dimensionIcons)}
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
@@ -1829,7 +1830,7 @@ export default function FolderViewOverlay({ onClose, onNodeOpen, refreshToken, o
|
||||
borderRadius: '8px',
|
||||
flexShrink: 0
|
||||
}}>
|
||||
{getNodeIcon(node)}
|
||||
{getNodeIcon(node, dimensionIcons)}
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
@@ -2560,7 +2561,7 @@ export default function FolderViewOverlay({ onClose, onNodeOpen, refreshToken, o
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0
|
||||
}}>
|
||||
{getNodeIcon(node)}
|
||||
{getNodeIcon(node, dimensionIcons)}
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{
|
||||
@@ -2848,7 +2849,7 @@ export default function FolderViewOverlay({ onClose, onNodeOpen, refreshToken, o
|
||||
}}>
|
||||
#{node.id}
|
||||
</span>
|
||||
<span style={{ flexShrink: 0, marginTop: '1px' }}>{getNodeIcon(node)}</span>
|
||||
<span style={{ flexShrink: 0, marginTop: '1px' }}>{getNodeIcon(node, dimensionIcons)}</span>
|
||||
<div style={{
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import Chip from '../common/Chip';
|
||||
import { getNodeIcon } from '@/utils/nodeIcons';
|
||||
import { useDimensionIcons } from '@/context/DimensionIconsContext';
|
||||
|
||||
interface SearchModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -15,9 +17,11 @@ interface NodeSuggestion {
|
||||
id: number;
|
||||
title: string;
|
||||
dimensions?: string[];
|
||||
link?: string;
|
||||
}
|
||||
|
||||
export default function SearchModal({ isOpen, onClose, onNodeSelect, existingFilters }: SearchModalProps) {
|
||||
const { dimensionIcons } = useDimensionIcons();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [suggestions, setSuggestions] = useState<NodeSuggestion[]>([]);
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
@@ -107,7 +111,8 @@ export default function SearchModal({ isOpen, onClose, onNodeSelect, existingFil
|
||||
const nodeSuggestions: NodeSuggestion[] = result.data.map((node: any) => ({
|
||||
id: node.id,
|
||||
title: node.title,
|
||||
dimensions: node.dimensions || []
|
||||
dimensions: node.dimensions || [],
|
||||
link: node.link || undefined,
|
||||
}));
|
||||
|
||||
setSuggestions(nodeSuggestions);
|
||||
@@ -202,7 +207,8 @@ export default function SearchModal({ isOpen, onClose, onNodeSelect, existingFil
|
||||
onMouseEnter={() => setSelectedIndex(index)}
|
||||
className={`search-result-item ${index === selectedIndex ? 'selected' : ''}`}
|
||||
>
|
||||
<span className="result-id">#{suggestion.id}</span>
|
||||
<span className="result-id">{suggestion.id}</span>
|
||||
<span className="result-icon">{getNodeIcon(suggestion as any, dimensionIcons, 14)}</span>
|
||||
<span className="result-title">{suggestion.title}</span>
|
||||
{index === selectedIndex && (
|
||||
<span className="result-hint">↵</span>
|
||||
@@ -345,6 +351,12 @@ export default function SearchModal({ isOpen, onClose, onNodeSelect, existingFil
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
flex: 1;
|
||||
color: #e5e5e5;
|
||||
|
||||
Reference in New Issue
Block a user