@fgv/ts-res-ui-components 5.0.0-21 → 5.0.0-23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +401 -155
- package/config/jest.setup.js +10 -0
- package/dist/ts-res-ui-components.d.ts +1657 -76
- package/lib/components/common/QualifierContextControl.js +4 -1
- package/lib/components/common/ResourceTreeView.js +4 -1
- package/lib/components/forms/GenericQualifierTypeEditForm.d.ts +26 -0
- package/lib/components/forms/GenericQualifierTypeEditForm.js +166 -0
- package/lib/components/forms/QualifierEditForm.d.ts +1 -1
- package/lib/components/forms/index.d.ts +2 -0
- package/lib/components/forms/index.js +1 -0
- package/lib/components/orchestrator/ResourceOrchestrator.d.ts +3 -0
- package/lib/components/orchestrator/ResourceOrchestrator.js +118 -51
- package/lib/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
- package/lib/components/pickers/ResourcePicker/index.js +4 -2
- package/lib/components/views/CompiledView/index.js +75 -16
- package/lib/components/views/ConfigurationView/index.js +94 -35
- package/lib/components/views/FilterView/index.js +7 -4
- package/lib/components/views/GridView/EditableGridCell.d.ts +76 -0
- package/lib/components/views/GridView/EditableGridCell.js +224 -0
- package/lib/components/views/GridView/GridSelector.d.ts +43 -0
- package/lib/components/views/GridView/GridSelector.js +89 -0
- package/lib/components/views/GridView/MultiGridView.d.ts +85 -0
- package/lib/components/views/GridView/MultiGridView.js +196 -0
- package/lib/components/views/GridView/ResourceGrid.d.ts +38 -0
- package/lib/components/views/GridView/ResourceGrid.js +232 -0
- package/lib/components/views/GridView/SharedContextControls.d.ts +47 -0
- package/lib/components/views/GridView/SharedContextControls.js +95 -0
- package/lib/components/views/GridView/cells/BooleanCell.d.ts +44 -0
- package/lib/components/views/GridView/cells/BooleanCell.js +49 -0
- package/lib/components/views/GridView/cells/DropdownCell.d.ts +58 -0
- package/lib/components/views/GridView/cells/DropdownCell.js +182 -0
- package/lib/components/views/GridView/cells/StringCell.d.ts +57 -0
- package/lib/components/views/GridView/cells/StringCell.js +106 -0
- package/lib/components/views/GridView/cells/TriStateCell.d.ts +54 -0
- package/lib/components/views/GridView/cells/TriStateCell.js +112 -0
- package/lib/components/views/GridView/cells/index.d.ts +15 -0
- package/lib/components/views/GridView/cells/index.js +11 -0
- package/lib/components/views/GridView/index.d.ts +53 -0
- package/lib/components/views/GridView/index.js +212 -0
- package/lib/components/views/ImportView/index.js +22 -19
- package/lib/components/views/MessagesWindow/index.js +4 -1
- package/lib/components/views/ResolutionView/index.js +8 -5
- package/lib/contexts/ObservabilityContext.d.ts +85 -0
- package/lib/contexts/ObservabilityContext.js +98 -0
- package/lib/contexts/index.d.ts +2 -0
- package/lib/contexts/index.js +24 -0
- package/lib/hooks/useConfigurationState.d.ts +3 -3
- package/lib/hooks/useResolutionState.js +850 -246
- package/lib/hooks/useResourceData.d.ts +7 -4
- package/lib/hooks/useResourceData.js +185 -184
- package/lib/index.d.ts +5 -1
- package/lib/index.js +8 -1
- package/lib/namespaces/GridTools.d.ts +136 -0
- package/lib/namespaces/GridTools.js +138 -0
- package/lib/namespaces/ObservabilityTools.d.ts +3 -0
- package/lib/namespaces/ObservabilityTools.js +23 -0
- package/lib/namespaces/ResolutionTools.d.ts +2 -1
- package/lib/namespaces/ResolutionTools.js +2 -0
- package/lib/namespaces/index.d.ts +2 -0
- package/lib/namespaces/index.js +2 -0
- package/lib/test/integration/observability.integration.test.d.ts +2 -0
- package/lib/test/unit/hooks/useResourceData.test.d.ts +2 -0
- package/lib/test/unit/utils/downloadHelper.test.d.ts +2 -0
- package/lib/test/unit/workflows/resolutionWorkflows.test.d.ts +2 -0
- package/lib/test/unit/workflows/resourceCreation.test.d.ts +2 -0
- package/lib/test/unit/workflows/resultPatternExtensions.test.d.ts +2 -0
- package/lib/test/unit/workflows/validation.test.d.ts +2 -0
- package/lib/types/index.d.ts +387 -20
- package/lib/types/index.js +2 -1
- package/lib/utils/cellValidation.d.ts +113 -0
- package/lib/utils/cellValidation.js +248 -0
- package/lib/utils/downloadHelper.d.ts +66 -0
- package/lib/utils/downloadHelper.js +195 -0
- package/lib/utils/observability/factories.d.ts +29 -0
- package/lib/utils/observability/factories.js +58 -0
- package/lib/utils/observability/implementations.d.ts +61 -0
- package/lib/utils/observability/implementations.js +103 -0
- package/lib/utils/observability/index.d.ts +4 -0
- package/lib/utils/observability/index.js +26 -0
- package/lib/utils/observability/interfaces.d.ts +30 -0
- package/lib/utils/observability/interfaces.js +23 -0
- package/lib/utils/resolutionEditing.js +2 -1
- package/lib/utils/resourceSelector.d.ts +97 -0
- package/lib/utils/resourceSelector.js +195 -0
- package/lib/utils/resourceSelectors.d.ts +146 -0
- package/lib/utils/resourceSelectors.js +233 -0
- package/lib/utils/tsResIntegration.d.ts +6 -41
- package/lib/utils/tsResIntegration.js +20 -16
- package/lib/utils/zipLoader/zipProcessingHelpers.d.ts +3 -2
- package/lib/utils/zipLoader/zipProcessingHelpers.js +6 -5
- package/lib-commonjs/components/common/QualifierContextControl.js +4 -1
- package/lib-commonjs/components/common/ResourceTreeView.js +4 -1
- package/lib-commonjs/components/forms/GenericQualifierTypeEditForm.js +171 -0
- package/lib-commonjs/components/forms/index.js +3 -1
- package/lib-commonjs/components/orchestrator/ResourceOrchestrator.js +118 -51
- package/lib-commonjs/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
- package/lib-commonjs/components/pickers/ResourcePicker/index.js +4 -2
- package/lib-commonjs/components/views/CompiledView/index.js +75 -16
- package/lib-commonjs/components/views/ConfigurationView/index.js +93 -34
- package/lib-commonjs/components/views/FilterView/index.js +7 -4
- package/lib-commonjs/components/views/GridView/EditableGridCell.js +232 -0
- package/lib-commonjs/components/views/GridView/GridSelector.js +94 -0
- package/lib-commonjs/components/views/GridView/MultiGridView.js +201 -0
- package/lib-commonjs/components/views/GridView/ResourceGrid.js +237 -0
- package/lib-commonjs/components/views/GridView/SharedContextControls.js +100 -0
- package/lib-commonjs/components/views/GridView/cells/BooleanCell.js +54 -0
- package/lib-commonjs/components/views/GridView/cells/DropdownCell.js +187 -0
- package/lib-commonjs/components/views/GridView/cells/StringCell.js +111 -0
- package/lib-commonjs/components/views/GridView/cells/TriStateCell.js +117 -0
- package/lib-commonjs/components/views/GridView/cells/index.js +18 -0
- package/lib-commonjs/components/views/GridView/index.js +217 -0
- package/lib-commonjs/components/views/ImportView/index.js +22 -19
- package/lib-commonjs/components/views/MessagesWindow/index.js +4 -1
- package/lib-commonjs/components/views/ResolutionView/index.js +8 -5
- package/lib-commonjs/contexts/ObservabilityContext.js +104 -0
- package/lib-commonjs/contexts/index.js +30 -0
- package/lib-commonjs/hooks/useResolutionState.js +849 -245
- package/lib-commonjs/hooks/useResourceData.js +184 -215
- package/lib-commonjs/index.js +15 -1
- package/lib-commonjs/namespaces/GridTools.js +161 -0
- package/lib-commonjs/namespaces/ObservabilityTools.js +33 -0
- package/lib-commonjs/namespaces/ResolutionTools.js +10 -1
- package/lib-commonjs/namespaces/index.js +3 -1
- package/lib-commonjs/types/index.js +10 -0
- package/lib-commonjs/utils/cellValidation.js +253 -0
- package/lib-commonjs/utils/downloadHelper.js +198 -0
- package/lib-commonjs/utils/observability/factories.js +63 -0
- package/lib-commonjs/utils/observability/implementations.js +109 -0
- package/lib-commonjs/utils/observability/index.js +36 -0
- package/lib-commonjs/utils/observability/interfaces.js +24 -0
- package/lib-commonjs/utils/resolutionEditing.js +2 -1
- package/lib-commonjs/utils/resourceSelector.js +200 -0
- package/lib-commonjs/utils/resourceSelectors.js +242 -0
- package/lib-commonjs/utils/tsResIntegration.js +21 -16
- package/lib-commonjs/utils/zipLoader/zipProcessingHelpers.js +7 -5
- package/package.json +7 -7
- package/src/components/common/QualifierContextControl.tsx +0 -338
- package/src/components/common/ResolutionContextOptionsControl.tsx +0 -450
- package/src/components/common/ResolutionResults/index.tsx +0 -481
- package/src/components/common/ResourceListView.tsx +0 -167
- package/src/components/common/ResourcePickerOptionsControl.tsx +0 -351
- package/src/components/common/ResourceTreeView.tsx +0 -417
- package/src/components/common/SourceResourceDetail/index.tsx +0 -493
- package/src/components/forms/HierarchyEditor.tsx +0 -285
- package/src/components/forms/QualifierEditForm.tsx +0 -487
- package/src/components/forms/QualifierTypeEditForm.tsx +0 -458
- package/src/components/forms/ResourceTypeEditForm.tsx +0 -437
- package/src/components/forms/index.ts +0 -11
- package/src/components/orchestrator/ResourceOrchestrator.tsx +0 -444
- package/src/components/pickers/ResourcePicker/README.md +0 -570
- package/src/components/pickers/ResourcePicker/ResourceItem.tsx +0 -127
- package/src/components/pickers/ResourcePicker/ResourcePickerList.tsx +0 -114
- package/src/components/pickers/ResourcePicker/ResourcePickerTree.tsx +0 -461
- package/src/components/pickers/ResourcePicker/index.tsx +0 -234
- package/src/components/pickers/ResourcePicker/types.ts +0 -301
- package/src/components/pickers/ResourcePicker/utils/treeNavigation.ts +0 -210
- package/src/components/views/CompiledView/index.tsx +0 -1342
- package/src/components/views/ConfigurationView/index.tsx +0 -848
- package/src/components/views/FilterView/index.tsx +0 -681
- package/src/components/views/ImportView/index.tsx +0 -789
- package/src/components/views/MessagesWindow/index.tsx +0 -325
- package/src/components/views/ResolutionView/EditableJsonView.tsx +0 -386
- package/src/components/views/ResolutionView/NewResourceModal.tsx +0 -158
- package/src/components/views/ResolutionView/UnifiedChangeControls.tsx +0 -163
- package/src/components/views/ResolutionView/index.tsx +0 -751
- package/src/components/views/SourceView/index.tsx +0 -291
- package/src/hooks/useConfigurationState.ts +0 -436
- package/src/hooks/useFilterState.ts +0 -150
- package/src/hooks/useResolutionState.ts +0 -893
- package/src/hooks/useResourceData.ts +0 -596
- package/src/hooks/useViewState.ts +0 -97
- package/src/index.ts +0 -68
- package/src/namespaces/ConfigurationTools.ts +0 -59
- package/src/namespaces/FilterTools.ts +0 -47
- package/src/namespaces/ImportTools.ts +0 -42
- package/src/namespaces/PickerTools.ts +0 -104
- package/src/namespaces/ResolutionTools.ts +0 -68
- package/src/namespaces/ResourceTools.ts +0 -106
- package/src/namespaces/TsResTools.ts +0 -49
- package/src/namespaces/ViewStateTools.ts +0 -91
- package/src/namespaces/ZipTools.ts +0 -49
- package/src/namespaces/index.ts +0 -49
- package/src/types/index.ts +0 -1273
- package/src/utils/configurationUtils.ts +0 -339
- package/src/utils/fileProcessing.ts +0 -164
- package/src/utils/filterResources.ts +0 -356
- package/src/utils/resolutionEditing.ts +0 -346
- package/src/utils/resolutionUtils.ts +0 -740
- package/src/utils/tsResIntegration.ts +0 -475
- package/src/utils/zipLoader/index.ts +0 -5
- package/src/utils/zipLoader/zipProcessingHelpers.ts +0 -46
- package/src/utils/zipLoader/zipUtils.ts +0 -7
|
@@ -1,417 +0,0 @@
|
|
|
1
|
-
import React, { useState, useMemo, useCallback } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
ChevronRightIcon,
|
|
4
|
-
ChevronDownIcon,
|
|
5
|
-
DocumentTextIcon,
|
|
6
|
-
FolderIcon,
|
|
7
|
-
FolderOpenIcon
|
|
8
|
-
} from '@heroicons/react/24/outline';
|
|
9
|
-
import { Resources, Runtime } from '@fgv/ts-res';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Props for the ResourceTreeView component.
|
|
13
|
-
*
|
|
14
|
-
* @public
|
|
15
|
-
*/
|
|
16
|
-
interface ResourceTreeViewProps {
|
|
17
|
-
/** Resource manager or compiled collection to display as a tree */
|
|
18
|
-
resources: Resources.ResourceManagerBuilder | Runtime.CompiledResourceCollection;
|
|
19
|
-
/** Currently selected resource ID for highlighting */
|
|
20
|
-
selectedResourceId: string | null;
|
|
21
|
-
/** Callback fired when a resource is selected */
|
|
22
|
-
onResourceSelect: (resourceId: string) => void;
|
|
23
|
-
/** Optional search term to filter and highlight resources */
|
|
24
|
-
searchTerm?: string;
|
|
25
|
-
/** Optional CSS classes to apply to the container */
|
|
26
|
-
className?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Internal tree node structure for rendering.
|
|
31
|
-
*
|
|
32
|
-
* @internal
|
|
33
|
-
*/
|
|
34
|
-
interface TreeNode {
|
|
35
|
-
id: string;
|
|
36
|
-
name: string;
|
|
37
|
-
isLeaf: boolean;
|
|
38
|
-
resource?: Resources.Resource | Runtime.IResource;
|
|
39
|
-
children?: Map<string, TreeNode>;
|
|
40
|
-
isExpanded?: boolean;
|
|
41
|
-
isVisible?: boolean;
|
|
42
|
-
matchesSearch?: boolean;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* A hierarchical tree view component for displaying and navigating resource structures.
|
|
47
|
-
*
|
|
48
|
-
* ResourceTreeView provides an interactive tree interface for browsing resource hierarchies,
|
|
49
|
-
* with support for expansion/collapse, search filtering, selection highlighting, and intelligent
|
|
50
|
-
* tree navigation. It automatically builds the tree structure from ts-res resource managers
|
|
51
|
-
* and supports both builder and compiled resource collections.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```tsx
|
|
55
|
-
* import { ResourceTreeView } from '@fgv/ts-res-ui-components';
|
|
56
|
-
*
|
|
57
|
-
* function HierarchicalResourceBrowser() {
|
|
58
|
-
* const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
59
|
-
* const [searchTerm, setSearchTerm] = useState('');
|
|
60
|
-
*
|
|
61
|
-
* return (
|
|
62
|
-
* <div className="flex flex-col h-full">
|
|
63
|
-
* <div className="p-4 border-b">
|
|
64
|
-
* <input
|
|
65
|
-
* type="search"
|
|
66
|
-
* placeholder="Search in tree..."
|
|
67
|
-
* value={searchTerm}
|
|
68
|
-
* onChange={(e) => setSearchTerm(e.target.value)}
|
|
69
|
-
* className="w-full px-3 py-2 border rounded-md"
|
|
70
|
-
* />
|
|
71
|
-
* </div>
|
|
72
|
-
* <ResourceTreeView
|
|
73
|
-
* resources={resourceManager}
|
|
74
|
-
* selectedResourceId={selectedId}
|
|
75
|
-
* onResourceSelect={setSelectedId}
|
|
76
|
-
* searchTerm={searchTerm}
|
|
77
|
-
* className="flex-1 min-h-0 p-2"
|
|
78
|
-
* />
|
|
79
|
-
* </div>
|
|
80
|
-
* );
|
|
81
|
-
* }
|
|
82
|
-
* ```
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```tsx
|
|
86
|
-
* // Using with orchestrator state for integrated navigation
|
|
87
|
-
* import { ResourceTools } from '@fgv/ts-res-ui-components';
|
|
88
|
-
*
|
|
89
|
-
* function OrchestratorTreeView() {
|
|
90
|
-
* const { state, actions } = ResourceTools.useResourceData();
|
|
91
|
-
* const [searchTerm, setSearchTerm] = useState('');
|
|
92
|
-
*
|
|
93
|
-
* if (!state.resources) {
|
|
94
|
-
* return <div className="p-4 text-gray-500">No resources loaded</div>;
|
|
95
|
-
* }
|
|
96
|
-
*
|
|
97
|
-
* return (
|
|
98
|
-
* <div className="h-full flex flex-col">
|
|
99
|
-
* <div className="flex items-center gap-2 p-2 border-b">
|
|
100
|
-
* <input
|
|
101
|
-
* type="search"
|
|
102
|
-
* placeholder="Search resources..."
|
|
103
|
-
* value={searchTerm}
|
|
104
|
-
* onChange={(e) => setSearchTerm(e.target.value)}
|
|
105
|
-
* className="flex-1 px-3 py-1 text-sm border rounded"
|
|
106
|
-
* />
|
|
107
|
-
* <span className="text-xs text-gray-500">
|
|
108
|
-
* {state.resources.summary?.resourceCount || 0} resources
|
|
109
|
-
* </span>
|
|
110
|
-
* </div>
|
|
111
|
-
* <ResourceTreeView
|
|
112
|
-
* resources={state.resources.resourceManager}
|
|
113
|
-
* selectedResourceId={state.selectedResourceId}
|
|
114
|
-
* onResourceSelect={(id) => actions.selectResource(id)}
|
|
115
|
-
* searchTerm={searchTerm}
|
|
116
|
-
* className="flex-1 min-h-0 overflow-auto"
|
|
117
|
-
* />
|
|
118
|
-
* </div>
|
|
119
|
-
* );
|
|
120
|
-
* }
|
|
121
|
-
* ```
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* ```tsx
|
|
125
|
-
* // Advanced usage with custom tree navigation
|
|
126
|
-
* function AdvancedResourceTree({ resources, onResourceAction }) {
|
|
127
|
-
* const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
128
|
-
* const [searchTerm, setSearchTerm] = useState('');
|
|
129
|
-
*
|
|
130
|
-
* const handleResourceSelect = (resourceId: string) => {
|
|
131
|
-
* setSelectedId(resourceId);
|
|
132
|
-
* onResourceAction('select', resourceId);
|
|
133
|
-
* };
|
|
134
|
-
*
|
|
135
|
-
* const handleKeyDown = (event: React.KeyboardEvent) => {
|
|
136
|
-
* if (event.key === 'Enter' && selectedId) {
|
|
137
|
-
* onResourceAction('open', selectedId);
|
|
138
|
-
* }
|
|
139
|
-
* };
|
|
140
|
-
*
|
|
141
|
-
* return (
|
|
142
|
-
* <div
|
|
143
|
-
* className="resource-tree-container"
|
|
144
|
-
* onKeyDown={handleKeyDown}
|
|
145
|
-
* tabIndex={0}
|
|
146
|
-
* >
|
|
147
|
-
* <div className="tree-header">
|
|
148
|
-
* <input
|
|
149
|
-
* type="search"
|
|
150
|
-
* placeholder="Find resources in tree..."
|
|
151
|
-
* value={searchTerm}
|
|
152
|
-
* onChange={(e) => setSearchTerm(e.target.value)}
|
|
153
|
-
* />
|
|
154
|
-
* <div className="tree-stats">
|
|
155
|
-
* {resources && (
|
|
156
|
-
* <span>
|
|
157
|
-
* Tree: {resources.getBuiltResourceTree().isSuccess() ? 'Built' : 'Error'}
|
|
158
|
-
* </span>
|
|
159
|
-
* )}
|
|
160
|
-
* </div>
|
|
161
|
-
* </div>
|
|
162
|
-
* <ResourceTreeView
|
|
163
|
-
* resources={resources}
|
|
164
|
-
* selectedResourceId={selectedId}
|
|
165
|
-
* onResourceSelect={handleResourceSelect}
|
|
166
|
-
* searchTerm={searchTerm}
|
|
167
|
-
* className="tree-content"
|
|
168
|
-
* />
|
|
169
|
-
* </div>
|
|
170
|
-
* );
|
|
171
|
-
* }
|
|
172
|
-
* ```
|
|
173
|
-
*
|
|
174
|
-
* @public
|
|
175
|
-
*/
|
|
176
|
-
export const ResourceTreeView: React.FC<ResourceTreeViewProps> = ({
|
|
177
|
-
resources,
|
|
178
|
-
selectedResourceId,
|
|
179
|
-
onResourceSelect,
|
|
180
|
-
searchTerm = '',
|
|
181
|
-
className = ''
|
|
182
|
-
}) => {
|
|
183
|
-
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
|
|
184
|
-
|
|
185
|
-
// Build the tree structure from resources
|
|
186
|
-
const treeData = useMemo(() => {
|
|
187
|
-
if (!resources) return null;
|
|
188
|
-
|
|
189
|
-
// Get the tree from the resources
|
|
190
|
-
const treeResult = resources.getBuiltResourceTree();
|
|
191
|
-
if (treeResult.isFailure()) {
|
|
192
|
-
console.error('Failed to build resource tree:', treeResult.message);
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return treeResult.value;
|
|
197
|
-
}, [resources]);
|
|
198
|
-
|
|
199
|
-
// Filter tree based on search term
|
|
200
|
-
const filteredTree = useMemo(() => {
|
|
201
|
-
if (!treeData || !searchTerm) return treeData;
|
|
202
|
-
|
|
203
|
-
// Helper function to check if a node or its descendants match the search
|
|
204
|
-
const markMatchingNodes = (
|
|
205
|
-
node: Runtime.ResourceTree.IReadOnlyResourceTreeNode<any>,
|
|
206
|
-
searchLower: string
|
|
207
|
-
): boolean => {
|
|
208
|
-
const nodeIdLower = node.id.toLowerCase();
|
|
209
|
-
let matches = nodeIdLower.includes(searchLower);
|
|
210
|
-
|
|
211
|
-
if (!node.isLeaf && node.children) {
|
|
212
|
-
// Check children recursively
|
|
213
|
-
for (const child of node.children.values()) {
|
|
214
|
-
if (markMatchingNodes(child, searchLower)) {
|
|
215
|
-
matches = true;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return matches;
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// Mark all matching nodes
|
|
224
|
-
const searchLower = searchTerm.toLowerCase();
|
|
225
|
-
for (const child of treeData.children.values()) {
|
|
226
|
-
markMatchingNodes(child, searchLower);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return treeData;
|
|
230
|
-
}, [treeData, searchTerm]);
|
|
231
|
-
|
|
232
|
-
// Toggle node expansion
|
|
233
|
-
const toggleNode = useCallback((nodeId: string) => {
|
|
234
|
-
setExpandedNodes((prev) => {
|
|
235
|
-
const newExpanded = new Set(prev);
|
|
236
|
-
if (newExpanded.has(nodeId)) {
|
|
237
|
-
newExpanded.delete(nodeId);
|
|
238
|
-
} else {
|
|
239
|
-
newExpanded.add(nodeId);
|
|
240
|
-
}
|
|
241
|
-
return newExpanded;
|
|
242
|
-
});
|
|
243
|
-
}, []);
|
|
244
|
-
|
|
245
|
-
// Expand all nodes that contain search matches
|
|
246
|
-
const expandMatchingNodes = useCallback(() => {
|
|
247
|
-
if (!searchTerm || !filteredTree) return;
|
|
248
|
-
|
|
249
|
-
const searchLower = searchTerm.toLowerCase();
|
|
250
|
-
const nodesToExpand = new Set<string>();
|
|
251
|
-
|
|
252
|
-
const checkNode = (node: Runtime.ResourceTree.IReadOnlyResourceTreeNode<any>) => {
|
|
253
|
-
if (node.id.toLowerCase().includes(searchLower)) {
|
|
254
|
-
// Expand all parent nodes
|
|
255
|
-
const parts = node.id.split('.');
|
|
256
|
-
for (let i = 1; i < parts.length; i++) {
|
|
257
|
-
nodesToExpand.add(parts.slice(0, i).join('.'));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (!node.isLeaf && node.children) {
|
|
262
|
-
for (const child of node.children.values()) {
|
|
263
|
-
checkNode(child);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
for (const child of filteredTree.children.values()) {
|
|
269
|
-
checkNode(child);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
setExpandedNodes(nodesToExpand);
|
|
273
|
-
}, [searchTerm, filteredTree]);
|
|
274
|
-
|
|
275
|
-
// Auto-expand when search term changes
|
|
276
|
-
React.useEffect(() => {
|
|
277
|
-
if (searchTerm) {
|
|
278
|
-
expandMatchingNodes();
|
|
279
|
-
}
|
|
280
|
-
}, [searchTerm, expandMatchingNodes]);
|
|
281
|
-
|
|
282
|
-
// Render a single tree node
|
|
283
|
-
const renderTreeNode = (
|
|
284
|
-
node: Runtime.ResourceTree.IReadOnlyResourceTreeNode<Resources.Resource | Runtime.IResource>,
|
|
285
|
-
level: number = 0
|
|
286
|
-
): React.ReactElement | null => {
|
|
287
|
-
const isExpanded = expandedNodes.has(node.id);
|
|
288
|
-
const isSelected = selectedResourceId === node.id;
|
|
289
|
-
const nodeIdLower = node.id.toLowerCase();
|
|
290
|
-
const searchLower = searchTerm.toLowerCase();
|
|
291
|
-
const matchesSearch = !searchTerm || nodeIdLower.includes(searchLower);
|
|
292
|
-
|
|
293
|
-
// Check if any children match
|
|
294
|
-
let hasMatchingChildren = false;
|
|
295
|
-
if (!node.isLeaf && node.children && searchTerm) {
|
|
296
|
-
const checkChildren = (n: Runtime.ResourceTree.IReadOnlyResourceTreeNode<any>): boolean => {
|
|
297
|
-
if (n.id.toLowerCase().includes(searchLower)) return true;
|
|
298
|
-
if (!n.isLeaf && n.children) {
|
|
299
|
-
for (const child of n.children.values()) {
|
|
300
|
-
if (checkChildren(child)) return true;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return false;
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
for (const child of node.children.values()) {
|
|
307
|
-
if (checkChildren(child)) {
|
|
308
|
-
hasMatchingChildren = true;
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Hide nodes that don't match search and don't have matching children
|
|
315
|
-
if (searchTerm && !matchesSearch && !hasMatchingChildren) {
|
|
316
|
-
return null;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return (
|
|
320
|
-
<div key={node.id}>
|
|
321
|
-
<div
|
|
322
|
-
className={`flex items-center px-2 py-1 cursor-pointer hover:bg-gray-100 ${
|
|
323
|
-
isSelected ? 'bg-purple-50 border-l-2 border-purple-500' : ''
|
|
324
|
-
} ${matchesSearch && searchTerm ? 'bg-yellow-50' : ''}`}
|
|
325
|
-
style={{ paddingLeft: `${level * 20 + 8}px` }}
|
|
326
|
-
onClick={() => {
|
|
327
|
-
if (node.isLeaf) {
|
|
328
|
-
onResourceSelect(node.id);
|
|
329
|
-
} else {
|
|
330
|
-
toggleNode(node.id);
|
|
331
|
-
}
|
|
332
|
-
}}
|
|
333
|
-
>
|
|
334
|
-
{/* Expand/Collapse icon for branches */}
|
|
335
|
-
{!node.isLeaf && (
|
|
336
|
-
<button
|
|
337
|
-
onClick={(e) => {
|
|
338
|
-
e.stopPropagation();
|
|
339
|
-
toggleNode(node.id);
|
|
340
|
-
}}
|
|
341
|
-
className="mr-1 p-0.5 hover:bg-gray-200 rounded"
|
|
342
|
-
>
|
|
343
|
-
{isExpanded ? (
|
|
344
|
-
<ChevronDownIcon className="w-3 h-3 text-gray-600" />
|
|
345
|
-
) : (
|
|
346
|
-
<ChevronRightIcon className="w-3 h-3 text-gray-600" />
|
|
347
|
-
)}
|
|
348
|
-
</button>
|
|
349
|
-
)}
|
|
350
|
-
|
|
351
|
-
{/* Folder or document icon */}
|
|
352
|
-
{node.isLeaf ? (
|
|
353
|
-
<DocumentTextIcon className="w-4 h-4 text-green-500 mr-2 flex-shrink-0" />
|
|
354
|
-
) : isExpanded ? (
|
|
355
|
-
<FolderOpenIcon className="w-4 h-4 text-blue-500 mr-2 flex-shrink-0" />
|
|
356
|
-
) : (
|
|
357
|
-
<FolderIcon className="w-4 h-4 text-blue-500 mr-2 flex-shrink-0" />
|
|
358
|
-
)}
|
|
359
|
-
|
|
360
|
-
{/* Node name */}
|
|
361
|
-
<span
|
|
362
|
-
className={`text-sm truncate ${isSelected ? 'font-medium text-purple-900' : 'text-gray-700'} ${
|
|
363
|
-
matchesSearch && searchTerm ? 'font-medium' : ''
|
|
364
|
-
}`}
|
|
365
|
-
title={node.id}
|
|
366
|
-
>
|
|
367
|
-
{node.name}
|
|
368
|
-
</span>
|
|
369
|
-
|
|
370
|
-
{/* Show child count for branches */}
|
|
371
|
-
{!node.isLeaf && node.children && (
|
|
372
|
-
<span className="ml-2 text-xs text-gray-500">({node.children.size})</span>
|
|
373
|
-
)}
|
|
374
|
-
</div>
|
|
375
|
-
|
|
376
|
-
{/* Render children if expanded */}
|
|
377
|
-
{!node.isLeaf && node.children && isExpanded && (
|
|
378
|
-
<div>
|
|
379
|
-
{Array.from(node.children.values())
|
|
380
|
-
.sort((a, b) => {
|
|
381
|
-
// Sort folders first, then by name
|
|
382
|
-
if (a.isLeaf !== b.isLeaf) {
|
|
383
|
-
return a.isLeaf ? 1 : -1;
|
|
384
|
-
}
|
|
385
|
-
return a.name.localeCompare(b.name);
|
|
386
|
-
})
|
|
387
|
-
.map((child) => renderTreeNode(child, level + 1))}
|
|
388
|
-
</div>
|
|
389
|
-
)}
|
|
390
|
-
</div>
|
|
391
|
-
);
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
if (!filteredTree) {
|
|
395
|
-
return (
|
|
396
|
-
<div className={`${className} p-4 text-center text-gray-500`}>
|
|
397
|
-
<p>No resources available</p>
|
|
398
|
-
</div>
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return (
|
|
403
|
-
<div className={`${className} overflow-y-auto`}>
|
|
404
|
-
{Array.from(filteredTree.children.values())
|
|
405
|
-
.sort((a, b) => {
|
|
406
|
-
// Sort folders first, then by name
|
|
407
|
-
if (a.isLeaf !== b.isLeaf) {
|
|
408
|
-
return a.isLeaf ? 1 : -1;
|
|
409
|
-
}
|
|
410
|
-
return a.name.localeCompare(b.name);
|
|
411
|
-
})
|
|
412
|
-
.map((child) => renderTreeNode(child))}
|
|
413
|
-
</div>
|
|
414
|
-
);
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
export default ResourceTreeView;
|