@fgv/ts-res-ui-components 5.0.0-10

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.
Files changed (231) hide show
  1. package/.rush/temp/03c8b056281d9db0a97d8a6e25eea798a160d393.tar.log +271 -0
  2. package/.rush/temp/chunked-rush-logs/ts-res-ui-components.build.chunks.jsonl +9 -0
  3. package/.rush/temp/operation/build/all.log +9 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +1111 -0
  7. package/README.md +18 -0
  8. package/REFACTORING_PLAN.md +171 -0
  9. package/config/jest.config.json +16 -0
  10. package/config/jest.setup.js +64 -0
  11. package/config/rig.json +16 -0
  12. package/lib/components/common/QualifierContextControl.d.ts +14 -0
  13. package/lib/components/common/QualifierContextControl.d.ts.map +1 -0
  14. package/lib/components/common/QualifierContextControl.js +78 -0
  15. package/lib/components/common/QualifierContextControl.js.map +1 -0
  16. package/lib/components/common/ResourceListView.d.ts +11 -0
  17. package/lib/components/common/ResourceListView.d.ts.map +1 -0
  18. package/lib/components/common/ResourceListView.js +20 -0
  19. package/lib/components/common/ResourceListView.js.map +1 -0
  20. package/lib/components/common/ResourceTreeView.d.ts +12 -0
  21. package/lib/components/common/ResourceTreeView.d.ts.map +1 -0
  22. package/lib/components/common/ResourceTreeView.js +162 -0
  23. package/lib/components/common/ResourceTreeView.js.map +1 -0
  24. package/lib/components/forms/HierarchyEditor.d.ts +10 -0
  25. package/lib/components/forms/HierarchyEditor.d.ts.map +1 -0
  26. package/lib/components/forms/HierarchyEditor.js +106 -0
  27. package/lib/components/forms/HierarchyEditor.js.map +1 -0
  28. package/lib/components/forms/QualifierEditForm.d.ts +11 -0
  29. package/lib/components/forms/QualifierEditForm.d.ts.map +1 -0
  30. package/lib/components/forms/QualifierEditForm.js +181 -0
  31. package/lib/components/forms/QualifierEditForm.js.map +1 -0
  32. package/lib/components/forms/QualifierTypeEditForm.d.ts +10 -0
  33. package/lib/components/forms/QualifierTypeEditForm.d.ts.map +1 -0
  34. package/lib/components/forms/QualifierTypeEditForm.js +172 -0
  35. package/lib/components/forms/QualifierTypeEditForm.js.map +1 -0
  36. package/lib/components/forms/ResourceTypeEditForm.d.ts +10 -0
  37. package/lib/components/forms/ResourceTypeEditForm.d.ts.map +1 -0
  38. package/lib/components/forms/ResourceTypeEditForm.js +188 -0
  39. package/lib/components/forms/ResourceTypeEditForm.js.map +1 -0
  40. package/lib/components/forms/index.d.ts +9 -0
  41. package/lib/components/forms/index.d.ts.map +1 -0
  42. package/lib/components/forms/index.js +5 -0
  43. package/lib/components/forms/index.js.map +1 -0
  44. package/lib/components/orchestrator/ResourceOrchestrator.d.ts +14 -0
  45. package/lib/components/orchestrator/ResourceOrchestrator.d.ts.map +1 -0
  46. package/lib/components/orchestrator/ResourceOrchestrator.js +278 -0
  47. package/lib/components/orchestrator/ResourceOrchestrator.js.map +1 -0
  48. package/lib/components/views/CompiledView/index.d.ts +5 -0
  49. package/lib/components/views/CompiledView/index.d.ts.map +1 -0
  50. package/lib/components/views/CompiledView/index.js +595 -0
  51. package/lib/components/views/CompiledView/index.js.map +1 -0
  52. package/lib/components/views/ConfigurationView/index.d.ts +5 -0
  53. package/lib/components/views/ConfigurationView/index.d.ts.map +1 -0
  54. package/lib/components/views/ConfigurationView/index.js +363 -0
  55. package/lib/components/views/ConfigurationView/index.js.map +1 -0
  56. package/lib/components/views/FilterView/index.d.ts +5 -0
  57. package/lib/components/views/FilterView/index.d.ts.map +1 -0
  58. package/lib/components/views/FilterView/index.js +463 -0
  59. package/lib/components/views/FilterView/index.js.map +1 -0
  60. package/lib/components/views/ImportView/index.d.ts +5 -0
  61. package/lib/components/views/ImportView/index.d.ts.map +1 -0
  62. package/lib/components/views/ImportView/index.js +514 -0
  63. package/lib/components/views/ImportView/index.js.map +1 -0
  64. package/lib/components/views/ResolutionView/EditableJsonView.d.ts +21 -0
  65. package/lib/components/views/ResolutionView/EditableJsonView.d.ts.map +1 -0
  66. package/lib/components/views/ResolutionView/EditableJsonView.js +109 -0
  67. package/lib/components/views/ResolutionView/EditableJsonView.js.map +1 -0
  68. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts +19 -0
  69. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts.map +1 -0
  70. package/lib/components/views/ResolutionView/ResolutionEditControls.js +82 -0
  71. package/lib/components/views/ResolutionView/ResolutionEditControls.js.map +1 -0
  72. package/lib/components/views/ResolutionView/index.d.ts +5 -0
  73. package/lib/components/views/ResolutionView/index.d.ts.map +1 -0
  74. package/lib/components/views/ResolutionView/index.js +255 -0
  75. package/lib/components/views/ResolutionView/index.js.map +1 -0
  76. package/lib/components/views/SourceView/index.d.ts +5 -0
  77. package/lib/components/views/SourceView/index.d.ts.map +1 -0
  78. package/lib/components/views/SourceView/index.js +316 -0
  79. package/lib/components/views/SourceView/index.js.map +1 -0
  80. package/lib/components/views/ZipLoaderView/index.d.ts +5 -0
  81. package/lib/components/views/ZipLoaderView/index.d.ts.map +1 -0
  82. package/lib/components/views/ZipLoaderView/index.js +313 -0
  83. package/lib/components/views/ZipLoaderView/index.js.map +1 -0
  84. package/lib/hooks/useConfigurationState.d.ts +46 -0
  85. package/lib/hooks/useConfigurationState.d.ts.map +1 -0
  86. package/lib/hooks/useConfigurationState.js +239 -0
  87. package/lib/hooks/useConfigurationState.js.map +1 -0
  88. package/lib/hooks/useFilterState.d.ts +7 -0
  89. package/lib/hooks/useFilterState.d.ts.map +1 -0
  90. package/lib/hooks/useFilterState.js +80 -0
  91. package/lib/hooks/useFilterState.js.map +1 -0
  92. package/lib/hooks/useResolutionState.d.ts +8 -0
  93. package/lib/hooks/useResolutionState.d.ts.map +1 -0
  94. package/lib/hooks/useResolutionState.js +253 -0
  95. package/lib/hooks/useResolutionState.js.map +1 -0
  96. package/lib/hooks/useResourceData.d.ts +19 -0
  97. package/lib/hooks/useResourceData.d.ts.map +1 -0
  98. package/lib/hooks/useResourceData.js +368 -0
  99. package/lib/hooks/useResourceData.js.map +1 -0
  100. package/lib/hooks/useViewState.d.ts +10 -0
  101. package/lib/hooks/useViewState.d.ts.map +1 -0
  102. package/lib/hooks/useViewState.js +29 -0
  103. package/lib/hooks/useViewState.js.map +1 -0
  104. package/lib/index.d.ts +27 -0
  105. package/lib/index.d.ts.map +1 -0
  106. package/lib/index.js +34 -0
  107. package/lib/index.js.map +1 -0
  108. package/lib/test/helpers/testDataLoader.d.ts +37 -0
  109. package/lib/test/helpers/testDataLoader.d.ts.map +1 -0
  110. package/lib/test/helpers/testDataLoader.js +171 -0
  111. package/lib/test/helpers/testDataLoader.js.map +1 -0
  112. package/lib/test/unit/utils/configurationUtils.test.d.ts +2 -0
  113. package/lib/test/unit/utils/configurationUtils.test.d.ts.map +1 -0
  114. package/lib/test/unit/utils/configurationUtils.test.js +497 -0
  115. package/lib/test/unit/utils/configurationUtils.test.js.map +1 -0
  116. package/lib/test/unit/utils/fileProcessing.test.d.ts +2 -0
  117. package/lib/test/unit/utils/fileProcessing.test.d.ts.map +1 -0
  118. package/lib/test/unit/utils/fileProcessing.test.js +321 -0
  119. package/lib/test/unit/utils/fileProcessing.test.js.map +1 -0
  120. package/lib/test/unit/utils/filterResources.test.d.ts +2 -0
  121. package/lib/test/unit/utils/filterResources.test.d.ts.map +1 -0
  122. package/lib/test/unit/utils/filterResources.test.js +403 -0
  123. package/lib/test/unit/utils/filterResources.test.js.map +1 -0
  124. package/lib/test/unit/utils/resolutionEditing.test.d.ts +2 -0
  125. package/lib/test/unit/utils/resolutionEditing.test.d.ts.map +1 -0
  126. package/lib/test/unit/utils/resolutionEditing.test.js +439 -0
  127. package/lib/test/unit/utils/resolutionEditing.test.js.map +1 -0
  128. package/lib/test/unit/utils/resolutionUtils.test.d.ts +2 -0
  129. package/lib/test/unit/utils/resolutionUtils.test.d.ts.map +1 -0
  130. package/lib/test/unit/utils/resolutionUtils.test.js +397 -0
  131. package/lib/test/unit/utils/resolutionUtils.test.js.map +1 -0
  132. package/lib/test/unit/utils/tsResIntegration.test.d.ts +2 -0
  133. package/lib/test/unit/utils/tsResIntegration.test.d.ts.map +1 -0
  134. package/lib/test/unit/utils/tsResIntegration.test.js +376 -0
  135. package/lib/test/unit/utils/tsResIntegration.test.js.map +1 -0
  136. package/lib/types/index.d.ts +251 -0
  137. package/lib/types/index.d.ts.map +1 -0
  138. package/lib/types/index.js +2 -0
  139. package/lib/types/index.js.map +1 -0
  140. package/lib/utils/configurationUtils.d.ts +74 -0
  141. package/lib/utils/configurationUtils.d.ts.map +1 -0
  142. package/lib/utils/configurationUtils.js +359 -0
  143. package/lib/utils/configurationUtils.js.map +1 -0
  144. package/lib/utils/fileProcessing.d.ts +18 -0
  145. package/lib/utils/fileProcessing.d.ts.map +1 -0
  146. package/lib/utils/fileProcessing.js +142 -0
  147. package/lib/utils/fileProcessing.js.map +1 -0
  148. package/lib/utils/filterResources.d.ts +38 -0
  149. package/lib/utils/filterResources.d.ts.map +1 -0
  150. package/lib/utils/filterResources.js +153 -0
  151. package/lib/utils/filterResources.js.map +1 -0
  152. package/lib/utils/resolutionEditing.d.ts +58 -0
  153. package/lib/utils/resolutionEditing.d.ts.map +1 -0
  154. package/lib/utils/resolutionEditing.js +246 -0
  155. package/lib/utils/resolutionEditing.js.map +1 -0
  156. package/lib/utils/resolutionUtils.d.ts +28 -0
  157. package/lib/utils/resolutionUtils.d.ts.map +1 -0
  158. package/lib/utils/resolutionUtils.js +216 -0
  159. package/lib/utils/resolutionUtils.js.map +1 -0
  160. package/lib/utils/tsResIntegration.d.ts +71 -0
  161. package/lib/utils/tsResIntegration.d.ts.map +1 -0
  162. package/lib/utils/tsResIntegration.js +294 -0
  163. package/lib/utils/tsResIntegration.js.map +1 -0
  164. package/lib/utils/zipLoader/browserZipLoader.d.ts +48 -0
  165. package/lib/utils/zipLoader/browserZipLoader.d.ts.map +1 -0
  166. package/lib/utils/zipLoader/browserZipLoader.js +247 -0
  167. package/lib/utils/zipLoader/browserZipLoader.js.map +1 -0
  168. package/lib/utils/zipLoader/index.d.ts +8 -0
  169. package/lib/utils/zipLoader/index.d.ts.map +1 -0
  170. package/lib/utils/zipLoader/index.js +13 -0
  171. package/lib/utils/zipLoader/index.js.map +1 -0
  172. package/lib/utils/zipLoader/nodeZipBuilder.d.ts +55 -0
  173. package/lib/utils/zipLoader/nodeZipBuilder.d.ts.map +1 -0
  174. package/lib/utils/zipLoader/nodeZipBuilder.js +98 -0
  175. package/lib/utils/zipLoader/nodeZipBuilder.js.map +1 -0
  176. package/lib/utils/zipLoader/types.d.ts +139 -0
  177. package/lib/utils/zipLoader/types.d.ts.map +1 -0
  178. package/lib/utils/zipLoader/types.js +2 -0
  179. package/lib/utils/zipLoader/types.js.map +1 -0
  180. package/lib/utils/zipLoader/zipUtils.d.ts +53 -0
  181. package/lib/utils/zipLoader/zipUtils.d.ts.map +1 -0
  182. package/lib/utils/zipLoader/zipUtils.js +229 -0
  183. package/lib/utils/zipLoader/zipUtils.js.map +1 -0
  184. package/package.json +69 -0
  185. package/rush-logs/ts-res-ui-components.build.cache.log +3 -0
  186. package/rush-logs/ts-res-ui-components.build.log +9 -0
  187. package/src/components/common/QualifierContextControl.tsx +151 -0
  188. package/src/components/common/ResourceListView.tsx +63 -0
  189. package/src/components/common/ResourceTreeView.tsx +271 -0
  190. package/src/components/forms/HierarchyEditor.tsx +204 -0
  191. package/src/components/forms/QualifierEditForm.tsx +355 -0
  192. package/src/components/forms/QualifierTypeEditForm.tsx +347 -0
  193. package/src/components/forms/ResourceTypeEditForm.tsx +331 -0
  194. package/src/components/forms/index.ts +11 -0
  195. package/src/components/orchestrator/ResourceOrchestrator.tsx +372 -0
  196. package/src/components/views/CompiledView/index.tsx +922 -0
  197. package/src/components/views/ConfigurationView/index.tsx +800 -0
  198. package/src/components/views/FilterView/index.tsx +825 -0
  199. package/src/components/views/ImportView/index.tsx +717 -0
  200. package/src/components/views/ResolutionView/EditableJsonView.tsx +214 -0
  201. package/src/components/views/ResolutionView/ResolutionEditControls.tsx +170 -0
  202. package/src/components/views/ResolutionView/index.tsx +591 -0
  203. package/src/components/views/SourceView/index.tsx +536 -0
  204. package/src/components/views/ZipLoaderView/index.tsx +485 -0
  205. package/src/hooks/useConfigurationState.ts +374 -0
  206. package/src/hooks/useFilterState.ts +97 -0
  207. package/src/hooks/useResolutionState.ts +355 -0
  208. package/src/hooks/useResourceData.ts +467 -0
  209. package/src/hooks/useViewState.ts +44 -0
  210. package/src/index.ts +45 -0
  211. package/src/test/helpers/testDataLoader.ts +195 -0
  212. package/src/test/unit/utils/configurationUtils.test.ts +630 -0
  213. package/src/test/unit/utils/fileProcessing.test.ts +391 -0
  214. package/src/test/unit/utils/filterResources.test.ts +574 -0
  215. package/src/test/unit/utils/resolutionEditing.test.ts +556 -0
  216. package/src/test/unit/utils/resolutionUtils.test.ts +521 -0
  217. package/src/test/unit/utils/tsResIntegration.test.ts +433 -0
  218. package/src/types/index.ts +322 -0
  219. package/src/utils/configurationUtils.ts +424 -0
  220. package/src/utils/fileProcessing.ts +160 -0
  221. package/src/utils/filterResources.ts +206 -0
  222. package/src/utils/resolutionEditing.ts +319 -0
  223. package/src/utils/resolutionUtils.ts +289 -0
  224. package/src/utils/tsResIntegration.ts +440 -0
  225. package/src/utils/zipLoader/browserZipLoader.ts +319 -0
  226. package/src/utils/zipLoader/index.ts +26 -0
  227. package/src/utils/zipLoader/nodeZipBuilder.ts +153 -0
  228. package/src/utils/zipLoader/types.ts +175 -0
  229. package/src/utils/zipLoader/zipUtils.ts +266 -0
  230. package/temp/build/typescript/ts_gZid87Hu.json +1 -0
  231. package/tsconfig.json +15 -0
@@ -0,0 +1,536 @@
1
+ import React, { useState, useMemo, useCallback } from 'react';
2
+ import {
3
+ DocumentTextIcon,
4
+ MagnifyingGlassIcon,
5
+ DocumentArrowDownIcon,
6
+ CodeBracketIcon,
7
+ ChevronDownIcon,
8
+ ChevronUpIcon,
9
+ ListBulletIcon,
10
+ FolderIcon
11
+ } from '@heroicons/react/24/outline';
12
+ import { SourceViewProps, ResourceDetailData } from '../../../types';
13
+ import { ResourceTreeView } from '../../common/ResourceTreeView';
14
+ import { ResourceListView } from '../../common/ResourceListView';
15
+
16
+ export const SourceView: React.FC<SourceViewProps> = ({ resources, onExport, onMessage, className = '' }) => {
17
+ const [selectedResourceId, setSelectedResourceId] = useState<string | null>(null);
18
+ const [searchTerm, setSearchTerm] = useState('');
19
+ const [showJsonView, setShowJsonView] = useState(false);
20
+ const [viewMode, setViewMode] = useState<'tree' | 'list'>('list');
21
+
22
+ // Sort and filter resource IDs
23
+ const filteredResourceIds = useMemo(() => {
24
+ if (!resources?.summary.resourceIds) {
25
+ return [];
26
+ }
27
+
28
+ const resourceIds = resources.summary.resourceIds;
29
+
30
+ // Filter by search term
31
+ const filtered = searchTerm
32
+ ? resourceIds.filter((id: string) => id.toLowerCase().includes(searchTerm.toLowerCase()))
33
+ : resourceIds;
34
+
35
+ // Sort alphabetically
36
+ return filtered.sort();
37
+ }, [resources?.summary.resourceIds, searchTerm]);
38
+
39
+ const handleResourceSelect = (resourceId: string) => {
40
+ setSelectedResourceId(resourceId);
41
+ onMessage?.('info', `Selected resource: ${resourceId}`);
42
+ };
43
+
44
+ const handleSearch = (term: string) => {
45
+ setSearchTerm(term);
46
+ setSelectedResourceId(null); // Clear selection when searching
47
+ };
48
+
49
+ // Get full resource collection data using the new method
50
+ const getResourceCollectionData = useCallback(() => {
51
+ if (!resources?.system.resourceManager) {
52
+ return null;
53
+ }
54
+
55
+ // Check if this is a ResourceManagerBuilder (has getResourceCollectionDecl method)
56
+ if ('getResourceCollectionDecl' in resources.system.resourceManager) {
57
+ const collectionResult = (resources.system.resourceManager as any).getResourceCollectionDecl();
58
+ if (collectionResult.isSuccess()) {
59
+ return {
60
+ ...collectionResult.value,
61
+ metadata: {
62
+ exportedAt: new Date().toISOString(),
63
+ totalResources: resources.summary.totalResources,
64
+ type: 'ts-res-resource-collection'
65
+ }
66
+ };
67
+ } else {
68
+ onMessage?.('error', `Failed to get resource collection: ${collectionResult.message}`);
69
+ return null;
70
+ }
71
+ } else if (resources.compiledCollection) {
72
+ // For IResourceManager from bundles, use the compiled collection directly
73
+ return {
74
+ resources: resources.compiledCollection.resources || [],
75
+ metadata: {
76
+ exportedAt: new Date().toISOString(),
77
+ totalResources: resources.summary.totalResources,
78
+ type: 'ts-res-resource-collection'
79
+ }
80
+ };
81
+ } else {
82
+ onMessage?.('error', 'Resource collection data not available');
83
+ return null;
84
+ }
85
+ }, [resources, onMessage]);
86
+
87
+ // Export source data to JSON file
88
+ const handleExportSourceData = useCallback(() => {
89
+ try {
90
+ const collectionData = getResourceCollectionData();
91
+ if (!collectionData) {
92
+ onMessage?.('error', 'No source collection data available to export');
93
+ return;
94
+ }
95
+
96
+ onExport?.(collectionData, 'json');
97
+ onMessage?.('success', 'Resource collection exported successfully');
98
+ } catch (error) {
99
+ onMessage?.(
100
+ 'error',
101
+ `Failed to export resource collection: ${error instanceof Error ? error.message : String(error)}`
102
+ );
103
+ }
104
+ }, [getResourceCollectionData, onExport, onMessage]);
105
+
106
+ if (!resources) {
107
+ return (
108
+ <div className={`p-6 ${className}`}>
109
+ <div className="flex items-center space-x-3 mb-6">
110
+ <DocumentTextIcon className="h-8 w-8 text-blue-600" />
111
+ <h2 className="text-2xl font-bold text-gray-900">Source Browser</h2>
112
+ </div>
113
+
114
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8 text-center">
115
+ <div className="max-w-2xl mx-auto">
116
+ <h3 className="text-xl font-semibold text-gray-900 mb-4">No Resources Loaded</h3>
117
+ <p className="text-gray-600 mb-6">Import resources to explore them here.</p>
118
+ <div className="bg-blue-50 rounded-lg p-4">
119
+ <p className="text-sm text-blue-800">
120
+ <strong>Tip:</strong> Use the Import View to load ts-res resource files or directories, then
121
+ return here to browse and explore the loaded resources.
122
+ </p>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ );
128
+ }
129
+
130
+ return (
131
+ <div className={`p-6 ${className}`}>
132
+ <div className="flex items-center justify-between mb-6">
133
+ <div className="flex items-center space-x-3">
134
+ <DocumentTextIcon className="h-8 w-8 text-blue-600" />
135
+ <h2 className="text-2xl font-bold text-gray-900">Source Browser</h2>
136
+ </div>
137
+ {resources && (
138
+ <div className="flex items-center space-x-2">
139
+ <button
140
+ onClick={handleExportSourceData}
141
+ className="inline-flex items-center px-3 py-1.5 border border-gray-300 text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
142
+ >
143
+ <DocumentArrowDownIcon className="h-4 w-4 mr-1" />
144
+ Export JSON
145
+ </button>
146
+ </div>
147
+ )}
148
+ </div>
149
+
150
+ {/* JSON View Toggle */}
151
+ {resources && (
152
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6">
153
+ <button
154
+ onClick={() => setShowJsonView(!showJsonView)}
155
+ className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-gray-700 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
156
+ >
157
+ <CodeBracketIcon className="h-4 w-4 mr-2" />
158
+ {showJsonView ? 'Hide' : 'Show'} JSON Resource Collection
159
+ {showJsonView ? (
160
+ <ChevronUpIcon className="h-4 w-4 ml-2" />
161
+ ) : (
162
+ <ChevronDownIcon className="h-4 w-4 ml-2" />
163
+ )}
164
+ </button>
165
+
166
+ {/* JSON View */}
167
+ {showJsonView && (
168
+ <div className="mt-4">
169
+ <div className="bg-gray-50 rounded-lg border border-gray-200 p-4">
170
+ <div className="flex items-center justify-between mb-2">
171
+ <h3 className="text-sm font-medium text-gray-900">Resource Collection (JSON)</h3>
172
+ <button
173
+ onClick={handleExportSourceData}
174
+ className="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-700 bg-gray-100 rounded hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
175
+ >
176
+ <DocumentArrowDownIcon className="h-3 w-3 mr-1" />
177
+ Export
178
+ </button>
179
+ </div>
180
+ <pre className="text-xs text-gray-800 bg-white p-3 rounded border overflow-x-auto max-h-64 overflow-y-auto">
181
+ {JSON.stringify(getResourceCollectionData(), null, 2)}
182
+ </pre>
183
+ </div>
184
+ </div>
185
+ )}
186
+ </div>
187
+ )}
188
+
189
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
190
+ <div className="flex flex-col lg:flex-row gap-6 h-[600px]">
191
+ {/* Left side: Resource List */}
192
+ <div className="lg:w-1/2 flex flex-col">
193
+ <div className="flex items-center justify-between mb-4">
194
+ <h3 className="text-lg font-semibold text-gray-900">
195
+ Resources ({filteredResourceIds.length})
196
+ </h3>
197
+ {/* View Mode Toggle */}
198
+ <div className="flex items-center space-x-1 bg-gray-100 rounded-lg p-1">
199
+ <button
200
+ onClick={() => setViewMode('list')}
201
+ className={`flex items-center px-2 py-1 text-xs font-medium rounded ${
202
+ viewMode === 'list'
203
+ ? 'bg-white text-gray-900 shadow-sm'
204
+ : 'text-gray-600 hover:text-gray-900'
205
+ }`}
206
+ title="List View"
207
+ >
208
+ <ListBulletIcon className="h-4 w-4" />
209
+ <span className="ml-1">List</span>
210
+ </button>
211
+ <button
212
+ onClick={() => setViewMode('tree')}
213
+ className={`flex items-center px-2 py-1 text-xs font-medium rounded ${
214
+ viewMode === 'tree'
215
+ ? 'bg-white text-gray-900 shadow-sm'
216
+ : 'text-gray-600 hover:text-gray-900'
217
+ }`}
218
+ title="Tree View"
219
+ >
220
+ <FolderIcon className="h-4 w-4" />
221
+ <span className="ml-1">Tree</span>
222
+ </button>
223
+ </div>
224
+ </div>
225
+
226
+ {/* Search Box */}
227
+ <div className="relative mb-4">
228
+ <MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
229
+ <input
230
+ type="text"
231
+ placeholder="Search resources..."
232
+ value={searchTerm}
233
+ onChange={(e) => handleSearch(e.target.value)}
234
+ className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
235
+ />
236
+ </div>
237
+
238
+ {/* Resource List or Tree */}
239
+ <div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50">
240
+ {viewMode === 'tree' && resources?.system.resourceManager ? (
241
+ <ResourceTreeView
242
+ resources={resources.system.resourceManager}
243
+ selectedResourceId={selectedResourceId}
244
+ onResourceSelect={handleResourceSelect}
245
+ searchTerm={searchTerm}
246
+ className=""
247
+ />
248
+ ) : (
249
+ <ResourceListView
250
+ resourceIds={filteredResourceIds}
251
+ selectedResourceId={selectedResourceId}
252
+ onResourceSelect={handleResourceSelect}
253
+ searchTerm={searchTerm}
254
+ className=""
255
+ />
256
+ )}
257
+ </div>
258
+ </div>
259
+
260
+ {/* Right side: Resource Details */}
261
+ <div className="lg:w-1/2 flex flex-col">
262
+ {selectedResourceId ? (
263
+ <ResourceDetail
264
+ resourceId={selectedResourceId}
265
+ processedResources={resources}
266
+ onMessage={onMessage}
267
+ />
268
+ ) : (
269
+ <div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
270
+ <div className="text-center">
271
+ <DocumentTextIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
272
+ <p className="text-gray-500">Select a resource to view details</p>
273
+ </div>
274
+ </div>
275
+ )}
276
+ </div>
277
+ </div>
278
+ </div>
279
+ </div>
280
+ );
281
+ };
282
+
283
+ interface ResourceDetailProps {
284
+ resourceId: string;
285
+ processedResources: any;
286
+ onMessage?: (type: 'info' | 'warning' | 'error' | 'success', message: string) => void;
287
+ }
288
+
289
+ const ResourceDetail: React.FC<ResourceDetailProps> = ({ resourceId, processedResources, onMessage }) => {
290
+ const [resourceDetail, setResourceDetail] = useState<ResourceDetailData | null>(null);
291
+ const [isLoading, setIsLoading] = useState(false);
292
+ const [error, setError] = useState<string | null>(null);
293
+
294
+ React.useEffect(() => {
295
+ const loadResourceDetail = () => {
296
+ setIsLoading(true);
297
+ setError(null);
298
+
299
+ try {
300
+ const resourceManager = processedResources.system.resourceManager;
301
+ const resourceResult = resourceManager.getBuiltResource(resourceId);
302
+
303
+ if (resourceResult.isSuccess()) {
304
+ const resource = resourceResult.value;
305
+
306
+ // Handle different resource formats (ResourceManagerBuilder vs IResourceManager)
307
+ let candidateDetails: any[] = [];
308
+
309
+ // Check if candidates have conditions property (ResourceManagerBuilder format)
310
+ if (resource.candidates.length > 0 && 'conditions' in resource.candidates[0]) {
311
+ // ResourceManagerBuilder format with full condition details
312
+ candidateDetails = resource.candidates.map((candidate: any) => ({
313
+ json: candidate.json,
314
+ conditions:
315
+ candidate.conditions?.conditions?.map((condition: any) => ({
316
+ qualifier: condition.qualifier.name,
317
+ operator: condition.operator,
318
+ value: condition.value,
319
+ priority: condition.priority,
320
+ scoreAsDefault: condition.scoreAsDefault
321
+ })) || [],
322
+ isPartial: candidate.isPartial,
323
+ mergeMethod: candidate.mergeMethod
324
+ }));
325
+ } else {
326
+ // IResourceManager format - extract conditions from compiled collection
327
+ const compiledCollection = processedResources.compiledCollection;
328
+ const compiledResource = compiledCollection?.resources?.find((r: any) => r.id === resourceId);
329
+
330
+ candidateDetails = resource.candidates.map((candidate: any, index: number) => {
331
+ // Try to get conditions from the compiled collection
332
+ let conditions: any[] = [];
333
+
334
+ if (compiledResource && compiledCollection) {
335
+ const decision = compiledCollection.decisions?.[compiledResource.decision];
336
+ if (decision?.conditionSets && index < decision.conditionSets.length) {
337
+ const conditionSetIndex = decision.conditionSets[index];
338
+ const conditionSet = compiledCollection.conditionSets?.[conditionSetIndex];
339
+
340
+ if (conditionSet?.conditions) {
341
+ conditions = conditionSet.conditions
342
+ .map((condIndex: number) => {
343
+ const condition = compiledCollection.conditions?.[condIndex];
344
+ const qualifier = compiledCollection.qualifiers?.[condition?.qualifierIndex];
345
+ return condition && qualifier
346
+ ? {
347
+ qualifier: qualifier.name,
348
+ operator: condition.operator || 'eq',
349
+ value: condition.value,
350
+ priority: condition.priority || qualifier.defaultPriority || 500,
351
+ scoreAsDefault: condition.scoreAsDefault
352
+ }
353
+ : null;
354
+ })
355
+ .filter(Boolean);
356
+ }
357
+ }
358
+ }
359
+
360
+ return {
361
+ json: candidate.json,
362
+ conditions: conditions,
363
+ isPartial: candidate.isPartial,
364
+ mergeMethod: candidate.mergeMethod
365
+ };
366
+ });
367
+ }
368
+
369
+ const detail: ResourceDetailData = {
370
+ id: resource.id,
371
+ resourceType: resource.resourceType.key || resource.resourceType.name || 'unknown',
372
+ candidateCount: resource.candidates.length,
373
+ candidates: candidateDetails
374
+ };
375
+
376
+ setResourceDetail(detail);
377
+ onMessage?.('info', `Loaded details for resource: ${resourceId}`);
378
+ } else {
379
+ setError(`Failed to load resource details: ${resourceResult.message}`);
380
+ onMessage?.('error', `Failed to load resource details: ${resourceResult.message}`);
381
+ }
382
+ } catch (err) {
383
+ const errorMsg = `Error loading resource details: ${
384
+ err instanceof Error ? err.message : String(err)
385
+ }`;
386
+ setError(errorMsg);
387
+ onMessage?.('error', errorMsg);
388
+ } finally {
389
+ setIsLoading(false);
390
+ }
391
+ };
392
+
393
+ loadResourceDetail();
394
+ }, [resourceId, processedResources, onMessage]);
395
+
396
+ if (isLoading) {
397
+ return (
398
+ <div className="flex flex-col h-full">
399
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">Resource Details</h3>
400
+ <div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
401
+ <div className="text-center">
402
+ <div className="animate-spin h-8 w-8 border-4 border-blue-600 border-t-transparent rounded-full mx-auto mb-4"></div>
403
+ <p className="text-gray-500">Loading resource details...</p>
404
+ </div>
405
+ </div>
406
+ </div>
407
+ );
408
+ }
409
+
410
+ if (error) {
411
+ return (
412
+ <div className="flex flex-col h-full">
413
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">Resource Details</h3>
414
+ <div className="flex-1 border border-gray-200 rounded-lg p-4 bg-red-50">
415
+ <div className="text-center">
416
+ <p className="text-red-600 font-medium mb-2">Error Loading Resource</p>
417
+ <p className="text-red-500 text-sm">{error}</p>
418
+ </div>
419
+ </div>
420
+ </div>
421
+ );
422
+ }
423
+
424
+ if (!resourceDetail) {
425
+ return (
426
+ <div className="flex flex-col h-full">
427
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">Resource Details</h3>
428
+ <div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
429
+ <p className="text-gray-500">No resource details available</p>
430
+ </div>
431
+ </div>
432
+ );
433
+ }
434
+
435
+ return (
436
+ <div className="flex flex-col h-full">
437
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">Resource Details</h3>
438
+
439
+ <div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg p-4 bg-gray-50">
440
+ <div className="space-y-6">
441
+ {/* Resource Overview */}
442
+ <div>
443
+ <h4 className="font-medium text-gray-700 mb-3">Resource Overview</h4>
444
+ <div className="bg-white p-4 rounded-lg border space-y-3">
445
+ <div>
446
+ <span className="text-sm font-medium text-gray-600">Fully Qualified ID:</span>
447
+ <code className="text-sm bg-gray-100 px-2 py-1 rounded ml-2 break-all">
448
+ {resourceDetail.id}
449
+ </code>
450
+ </div>
451
+ <div>
452
+ <span className="text-sm font-medium text-gray-600">Resource Type:</span>
453
+ <span className="ml-2 text-sm">{resourceDetail.resourceType}</span>
454
+ </div>
455
+ <div>
456
+ <span className="text-sm font-medium text-gray-600">Candidate Count:</span>
457
+ <span className="ml-2 text-sm font-medium text-blue-600">
458
+ {resourceDetail.candidateCount}
459
+ </span>
460
+ </div>
461
+ </div>
462
+ </div>
463
+
464
+ {/* Candidates */}
465
+ <div>
466
+ <h4 className="font-medium text-gray-700 mb-3">
467
+ Candidates ({resourceDetail.candidates.length})
468
+ </h4>
469
+ <div className="space-y-4">
470
+ {resourceDetail.candidates.map((candidate, index) => (
471
+ <div key={index} className="bg-white p-4 rounded-lg border">
472
+ <div className="flex items-center justify-between mb-3">
473
+ <h5 className="font-medium text-gray-800">Candidate {index + 1}</h5>
474
+ <div className="flex items-center space-x-2 text-xs">
475
+ {candidate.isPartial && (
476
+ <span className="bg-yellow-100 text-yellow-800 px-2 py-1 rounded">Partial</span>
477
+ )}
478
+ <span className="bg-gray-100 text-gray-700 px-2 py-1 rounded">
479
+ {candidate.mergeMethod}
480
+ </span>
481
+ </div>
482
+ </div>
483
+
484
+ {/* Conditions */}
485
+ {candidate.conditions.length > 0 && (
486
+ <div className="mb-3">
487
+ <h6 className="text-sm font-medium text-gray-600 mb-2">Conditions:</h6>
488
+ <div className="space-y-1">
489
+ {candidate.conditions.map((condition, condIndex) => (
490
+ <div
491
+ key={condIndex}
492
+ className="flex items-center text-xs bg-blue-50 px-2 py-1 rounded"
493
+ >
494
+ <span className="font-medium text-blue-800">{condition.qualifier}</span>
495
+ <span className="mx-1 text-blue-600">{condition.operator}</span>
496
+ <span className="text-blue-700">{condition.value}</span>
497
+ <div className="ml-auto flex items-center space-x-2">
498
+ <span className="text-blue-500">priority: {condition.priority}</span>
499
+ {condition.scoreAsDefault !== undefined && (
500
+ <span className="text-amber-600 font-medium">
501
+ default: {condition.scoreAsDefault}
502
+ </span>
503
+ )}
504
+ </div>
505
+ </div>
506
+ ))}
507
+ </div>
508
+ </div>
509
+ )}
510
+
511
+ {candidate.conditions.length === 0 && (
512
+ <div className="mb-3">
513
+ <span className="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded">
514
+ No conditions (default candidate)
515
+ </span>
516
+ </div>
517
+ )}
518
+
519
+ {/* JSON Content */}
520
+ <div>
521
+ <h6 className="text-sm font-medium text-gray-600 mb-2">Content:</h6>
522
+ <pre className="text-xs bg-gray-50 p-3 rounded border overflow-x-auto max-h-40">
523
+ {JSON.stringify(candidate.json, null, 2)}
524
+ </pre>
525
+ </div>
526
+ </div>
527
+ ))}
528
+ </div>
529
+ </div>
530
+ </div>
531
+ </div>
532
+ </div>
533
+ );
534
+ };
535
+
536
+ export default SourceView;