@fgv/ts-res-browser 5.0.0-2 → 5.0.0-21

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 (99) hide show
  1. package/cli.js +132 -21
  2. package/dist/bundle.js +1 -1
  3. package/dist/bundle.js.LICENSE.txt +11 -1
  4. package/package.json +42 -36
  5. package/.rush/temp/587277bfc395406ad444e989b6c309b9698f534c.tar.log +0 -15
  6. package/.rush/temp/chunked-rush-logs/ts-res-browser.build.chunks.jsonl +0 -35
  7. package/.rush/temp/operation/build/all.log +0 -35
  8. package/.rush/temp/operation/build/log-chunks.jsonl +0 -35
  9. package/.rush/temp/operation/build/state.json +0 -3
  10. package/.rush/temp/shrinkwrap-deps.json +0 -1225
  11. package/TS_RES_BROWSER_PLAN.md +0 -689
  12. package/babel.config.js +0 -17
  13. package/config/jest.config.json +0 -13
  14. package/config/rig.json +0 -16
  15. package/postcss.config.js +0 -3
  16. package/public/index.html +0 -15
  17. package/rush-logs/ts-res-browser.build.cache.log +0 -3
  18. package/rush-logs/ts-res-browser.build.log +0 -35
  19. package/src/App.tsx +0 -274
  20. package/src/cli.ts +0 -44
  21. package/src/components/common/FileImporter.tsx +0 -143
  22. package/src/components/common/NavigationWarningModal.tsx +0 -62
  23. package/src/components/layout/AppLayout.tsx +0 -46
  24. package/src/components/layout/Header.tsx +0 -22
  25. package/src/components/layout/MessagesWindow.tsx +0 -108
  26. package/src/components/layout/Sidebar.tsx +0 -99
  27. package/src/components/tools/CompiledBrowser.tsx +0 -949
  28. package/src/components/tools/ConfigurationTool.tsx +0 -1405
  29. package/src/components/tools/FilterTool.tsx +0 -1114
  30. package/src/components/tools/ImportTool.tsx +0 -249
  31. package/src/components/tools/ResolutionViewer.tsx +0 -1672
  32. package/src/components/tools/SourceBrowser.tsx +0 -496
  33. package/src/components/tools/ZipLoader.tsx +0 -302
  34. package/src/hooks/useAppState.ts +0 -111
  35. package/src/hooks/useFileImport.ts +0 -86
  36. package/src/hooks/useNavigationWarning.ts +0 -67
  37. package/src/hooks/useResourceManager.ts +0 -295
  38. package/src/hooks/useUrlParams.ts +0 -56
  39. package/src/index.css +0 -38
  40. package/src/main.tsx +0 -16
  41. package/src/types/app.ts +0 -70
  42. package/src/types/fileSystemAccess.d.ts +0 -94
  43. package/src/utils/browserFileTreeAccessors.ts +0 -245
  44. package/src/utils/fileImport.ts +0 -336
  45. package/src/utils/fileTreeConverter.ts +0 -160
  46. package/src/utils/filterResources.ts +0 -677
  47. package/src/utils/tsResIntegration.ts +0 -467
  48. package/src/utils/urlParams.ts +0 -238
  49. package/src/utils/zip/browserZipFileTreeAccessors.ts +0 -385
  50. package/tailwind.config.js +0 -21
  51. package/test-data/README.md +0 -184
  52. package/test-data/config-variations/README.md +0 -64
  53. package/test-data/config-variations/composition-terminology.json +0 -112
  54. package/test-data/config-variations/enterprise-complex.json +0 -129
  55. package/test-data/config-variations/gaming-app.json +0 -87
  56. package/test-data/config-variations/high-priority-language.json +0 -65
  57. package/test-data/config-variations/minimal-basic.json +0 -37
  58. package/test-data/config-variations/qualifier-default-values.json +0 -71
  59. package/test-data/config-variations/territory-first.json +0 -68
  60. package/test-data/config-variations/territory-hierarchy-test.json +0 -31
  61. package/test-data/config-variations/territory-hierarchy-unconstrained.json +0 -32
  62. package/test-data/default-matching-test/greeting.res.json +0 -83
  63. package/test-data/default-matching-test/only-defaults.res.json +0 -48
  64. package/test-data/sample-project/package.json +0 -27
  65. package/test-data/sample-project/src/resources/app-config.json +0 -27
  66. package/test-data/sample-project/src/resources/app.json +0 -177
  67. package/test-data/sample-project/src/resources/config/app.env=development.json +0 -21
  68. package/test-data/sample-project/src/resources/config/app.json +0 -21
  69. package/test-data/sample-project/src/resources/config/app.platform=mobile.json +0 -23
  70. package/test-data/sample-project/src/resources/feature-flags.json +0 -23
  71. package/test-data/sample-project/src/resources/home=CA/common.json +0 -16
  72. package/test-data/sample-project/src/resources/images/density=hdpi.json +0 -29
  73. package/test-data/sample-project/src/resources/images/home.json +0 -29
  74. package/test-data/sample-project/src/resources/images/logout.json +0 -14
  75. package/test-data/sample-project/src/resources/images/settings.json +0 -14
  76. package/test-data/sample-project/src/resources/images/ui-icons.json +0 -29
  77. package/test-data/sample-project/src/resources/images/user.density=hdpi.json +0 -14
  78. package/test-data/sample-project/src/resources/images/user.json +0 -14
  79. package/test-data/sample-project/src/resources/lang=en-US/common.json +0 -15
  80. package/test-data/sample-project/src/resources/language=en-CA/ui-terms.json +0 -19
  81. package/test-data/sample-project/src/resources/language=en-GB/ui-terms.json +0 -30
  82. package/test-data/sample-project/src/resources/language=fr-CA/ui-terms.json +0 -25
  83. package/test-data/sample-project/src/resources/language=nl-BE/ui-terms.json +0 -17
  84. package/test-data/sample-project/src/resources/region=europe/feature-flags.json +0 -17
  85. package/test-data/sample-project/src/resources/region=north-america/feature-flags.json +0 -18
  86. package/test-data/sample-project/src/resources/strings/dashboard.home=CA,language=fr.json +0 -20
  87. package/test-data/sample-project/src/resources/strings/dashboard.json +0 -18
  88. package/test-data/sample-project/src/resources/strings/dashboard.role=admin.json +0 -23
  89. package/test-data/sample-project/src/resources/strings/dashboard.role=user.json +0 -21
  90. package/test-data/sample-project/src/resources/strings/language=en-US.json +0 -16
  91. package/test-data/sample-project/src/resources/strings.json +0 -78
  92. package/test-data/sample-project/src/resources/territory=BE/app-config.json +0 -19
  93. package/test-data/sample-project/src/resources/territory=CA/app-config.json +0 -21
  94. package/test-data/sample-project/src/resources/territory=GB/app-config.json +0 -25
  95. package/test-data/sample-project/src/resources/territory=NL/app-config.json +0 -25
  96. package/test-data/sample-project/src/resources/ui-terms.json +0 -96
  97. package/test-data/sample-project/src/resources-config.json +0 -111
  98. package/tsconfig.json +0 -27
  99. package/webpack.config.js +0 -60
@@ -1,949 +0,0 @@
1
- import React, { useState, useMemo, useCallback } from 'react';
2
- import {
3
- CubeIcon,
4
- FolderIcon,
5
- FolderOpenIcon,
6
- DocumentTextIcon,
7
- ChevronRightIcon,
8
- ChevronDownIcon,
9
- DocumentArrowDownIcon,
10
- CodeBracketIcon,
11
- ChevronUpIcon
12
- } from '@heroicons/react/24/outline';
13
- import { UseResourceManagerReturn } from '../../hooks/useResourceManager';
14
- import { Message, FilterState } from '../../types/app';
15
- import { ResourceJson, NoMatch } from '@fgv/ts-res';
16
-
17
- interface CompiledBrowserProps {
18
- onMessage?: (type: Message['type'], message: string) => void;
19
- resourceManager: UseResourceManagerReturn;
20
- filterState: FilterState;
21
- filterResult?: {
22
- success: boolean;
23
- processedResources?: {
24
- system: any;
25
- compiledCollection: any;
26
- summary: { resourceIds?: string[] };
27
- };
28
- } | null;
29
- }
30
-
31
- interface TreeNode {
32
- id: string;
33
- name: string;
34
- type: 'folder' | 'resource' | 'section';
35
- children?: TreeNode[];
36
- data?: any;
37
- }
38
-
39
- const CompiledBrowser: React.FC<CompiledBrowserProps> = ({
40
- onMessage,
41
- resourceManager,
42
- filterState,
43
- filterResult
44
- }) => {
45
- const { state: resourceState } = resourceManager;
46
-
47
- // Use filtered resources when filtering is active and successful
48
- const isFilteringActive = filterState.enabled && filterResult?.success === true;
49
- const activeProcessedResources = isFilteringActive
50
- ? filterResult?.processedResources
51
- : resourceState.processedResources;
52
- const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
53
- const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set(['root', 'resources']));
54
- const [showJsonView, setShowJsonView] = useState(false);
55
-
56
- // Helper functions to resolve indices to meaningful keys
57
- const getConditionKey = (
58
- condition: ResourceJson.Compiled.ICompiledCondition,
59
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection
60
- ): string => {
61
- try {
62
- // Use metadata if available
63
- if (condition.metadata?.key) {
64
- return condition.metadata.key;
65
- }
66
-
67
- // Fall back to manual construction
68
- const qualifier = compiledCollection.qualifiers[condition.qualifierIndex];
69
- if (!qualifier) return `unknown-qualifier`;
70
-
71
- // Create a meaningful key like "language=en-US" or "territory=US"
72
- const key = `${qualifier.name}=${condition.value}`;
73
- return key;
74
- } catch (error) {
75
- return `condition-${condition.qualifierIndex}`;
76
- }
77
- };
78
-
79
- const getConditionSetKey = (
80
- conditionSet: ResourceJson.Compiled.ICompiledConditionSet,
81
- conditionSetIndex: number,
82
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection
83
- ): string => {
84
- try {
85
- // Use metadata if available
86
- if (conditionSet.metadata?.key) {
87
- return conditionSet.metadata.key;
88
- }
89
-
90
- if (conditionSetIndex === 0) {
91
- return 'unconditional';
92
- }
93
-
94
- // Fall back to manual construction
95
- if (!conditionSet.conditions || conditionSet.conditions.length === 0) {
96
- return `condition-set-${conditionSetIndex}`;
97
- }
98
-
99
- // Build a composite key from all conditions in the set
100
- const conditionKeys = conditionSet.conditions.map((conditionIndex) => {
101
- const condition = compiledCollection.conditions[conditionIndex];
102
- if (!condition) return `unknown-${conditionIndex}`;
103
- return getConditionKey(condition, compiledCollection);
104
- });
105
-
106
- return conditionKeys.join(',');
107
- } catch (error) {
108
- return `condition-set-${conditionSetIndex}`;
109
- }
110
- };
111
-
112
- const getDecisionKey = (
113
- decision: ResourceJson.Compiled.ICompiledAbstractDecision,
114
- decisionIndex: number,
115
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection
116
- ): string => {
117
- try {
118
- // Use metadata if available
119
- if (decision.metadata?.key) {
120
- return decision.metadata.key;
121
- }
122
-
123
- // Fall back to manual construction
124
- if (!decision.conditionSets || decision.conditionSets.length === 0) {
125
- return `decision-${decisionIndex}`;
126
- }
127
-
128
- // Build a composite key from all condition sets in the decision
129
- const conditionSetKeys = decision.conditionSets.map((conditionSetIndex) => {
130
- const conditionSet = compiledCollection.conditionSets[conditionSetIndex];
131
- if (!conditionSet) return `unknown-${conditionSetIndex}`;
132
- return getConditionSetKey(conditionSet, conditionSetIndex, compiledCollection);
133
- });
134
-
135
- return conditionSetKeys.join(' OR ');
136
- } catch (error) {
137
- return `decision-${decisionIndex}`;
138
- }
139
- };
140
-
141
- // Helper function to format display name as "key (index)" or just "index"
142
- const formatDisplayName = (key: string, index: number): string => {
143
- // If key looks like it's just "condition-X", "condition-set-X", or "decision-X", just show the index
144
- if (key.match(/^(condition|condition-set|decision)-\d+$/)) {
145
- return `${index}`;
146
- }
147
- return `${key} (${index})`;
148
- };
149
-
150
- // Helper function to get condition set key for a candidate
151
- const getCandidateConditionSetKey = (
152
- candidateIndex: number,
153
- resource: ResourceJson.Compiled.ICompiledResource,
154
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection
155
- ): string | null => {
156
- try {
157
- // Get the decision for this resource
158
- const decision = compiledCollection.decisions[resource.decision];
159
- if (!decision || !decision.conditionSets) {
160
- return null;
161
- }
162
-
163
- // Map candidate index to condition set index
164
- // Assuming candidates correspond to condition sets in order
165
- if (candidateIndex >= decision.conditionSets.length) {
166
- return null;
167
- }
168
-
169
- const conditionSetIndex = decision.conditionSets[candidateIndex];
170
- const conditionSet = compiledCollection.conditionSets[conditionSetIndex];
171
- if (!conditionSet) {
172
- return null;
173
- }
174
-
175
- return getConditionSetKey(conditionSet, conditionSetIndex, compiledCollection);
176
- } catch (error) {
177
- return null;
178
- }
179
- };
180
-
181
- // Helper function to get resource type name
182
- const getResourceTypeName = (
183
- resourceTypeIndex: number,
184
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection
185
- ): string => {
186
- try {
187
- const resourceType = compiledCollection.resourceTypes[resourceTypeIndex];
188
- return resourceType?.name || `resource-type-${resourceTypeIndex}`;
189
- } catch (error) {
190
- return `resource-type-${resourceTypeIndex}`;
191
- }
192
- };
193
-
194
- // Build tree structure from compiled resources
195
- const treeData = useMemo(() => {
196
- if (!activeProcessedResources?.compiledCollection) {
197
- return null;
198
- }
199
-
200
- const compiledCollection = activeProcessedResources.compiledCollection;
201
-
202
- const tree: TreeNode = {
203
- id: 'root',
204
- name: 'Compiled Resources',
205
- type: 'folder',
206
- children: []
207
- };
208
-
209
- try {
210
- // 1. Resources section
211
- const resourcesCount = compiledCollection.resources?.length || 0;
212
- const resourcesSection: TreeNode = {
213
- id: 'resources',
214
- name: `Resources (${resourcesCount})`,
215
- type: 'section',
216
- children: []
217
- };
218
-
219
- // Build simple resource list from compiled resources
220
- if (compiledCollection.resources && compiledCollection.resources.length > 0) {
221
- resourcesSection.children = compiledCollection.resources.map((resource, index) => ({
222
- id: `resource-${index}`,
223
- name: resource.id || `Resource ${index}`,
224
- type: 'resource' as const,
225
- data: { type: 'compiled-resource', resource }
226
- }));
227
- }
228
-
229
- tree.children!.push(resourcesSection);
230
-
231
- // 2. Decisions section
232
- const decisionsCount = compiledCollection.decisions?.length || 0;
233
- const decisionsSection: TreeNode = {
234
- id: 'decisions',
235
- name: `Decisions (${decisionsCount})`,
236
- type: 'section',
237
- data: { type: 'decisions', collection: compiledCollection.decisions }
238
- };
239
- tree.children!.push(decisionsSection);
240
-
241
- // 3. Condition Sets section
242
- const conditionSetsCount = compiledCollection.conditionSets?.length || 0;
243
- const conditionSetsSection: TreeNode = {
244
- id: 'condition-sets',
245
- name: `Condition Sets (${conditionSetsCount})`,
246
- type: 'section',
247
- data: { type: 'condition-sets', collection: compiledCollection.conditionSets }
248
- };
249
- tree.children!.push(conditionSetsSection);
250
-
251
- // 4. Conditions section
252
- const conditionsCount = compiledCollection.conditions?.length || 0;
253
- const conditionsSection: TreeNode = {
254
- id: 'conditions',
255
- name: `Conditions (${conditionsCount})`,
256
- type: 'section',
257
- data: { type: 'conditions', collection: compiledCollection.conditions }
258
- };
259
- tree.children!.push(conditionsSection);
260
- } catch (error) {
261
- onMessage?.('error', `Error building tree: ${error instanceof Error ? error.message : String(error)}`);
262
- }
263
-
264
- return tree;
265
- }, [activeProcessedResources?.compiledCollection, onMessage, isFilteringActive]);
266
-
267
- // Export compiled collection to JSON file
268
- const handleExportCompiledData = useCallback(() => {
269
- try {
270
- if (!activeProcessedResources?.compiledCollection) {
271
- onMessage?.('error', 'No compiled data available to export');
272
- return;
273
- }
274
-
275
- const compiledData = {
276
- ...activeProcessedResources.compiledCollection,
277
- metadata: {
278
- exportedAt: new Date().toISOString(),
279
- type: isFilteringActive ? 'ts-res-filtered-compiled-collection' : 'ts-res-compiled-collection',
280
- ...(isFilteringActive && { filterContext: filterState.appliedValues })
281
- }
282
- };
283
-
284
- const compiledJson = JSON.stringify(compiledData, null, 2);
285
- const blob = new Blob([compiledJson], { type: 'application/json' });
286
- const url = URL.createObjectURL(blob);
287
-
288
- const a = document.createElement('a');
289
- a.href = url;
290
- a.download = isFilteringActive ? 'filtered-compiled-collection.json' : 'compiled-collection.json';
291
- document.body.appendChild(a);
292
- a.click();
293
- document.body.removeChild(a);
294
- URL.revokeObjectURL(url);
295
-
296
- onMessage?.(
297
- 'success',
298
- `${isFilteringActive ? 'Filtered c' : 'C'}ompiled collection exported successfully`
299
- );
300
- } catch (error) {
301
- onMessage?.(
302
- 'error',
303
- `Failed to export compiled data: ${error instanceof Error ? error.message : String(error)}`
304
- );
305
- }
306
- }, [activeProcessedResources?.compiledCollection, onMessage, isFilteringActive, filterState.appliedValues]);
307
-
308
- const handleNodeClick = (node: TreeNode) => {
309
- setSelectedNodeId(node.id);
310
- onMessage?.('info', `Selected: ${node.name}`);
311
-
312
- // Toggle expansion for folders
313
- if (node.type === 'folder' || (node.type === 'section' && node.children)) {
314
- setExpandedNodes((prev) => {
315
- const newExpanded = new Set(prev);
316
- if (newExpanded.has(node.id)) {
317
- newExpanded.delete(node.id);
318
- } else {
319
- newExpanded.add(node.id);
320
- }
321
- return newExpanded;
322
- });
323
- }
324
- };
325
-
326
- const renderTreeNode = (node: TreeNode, level = 0): React.ReactNode => {
327
- const isExpanded = expandedNodes.has(node.id);
328
- const isSelected = selectedNodeId === node.id;
329
- const hasChildren = node.children && node.children.length > 0;
330
-
331
- return (
332
- <div key={node.id}>
333
- <div
334
- className={`flex items-center px-2 py-1 cursor-pointer hover:bg-gray-100 ${
335
- isSelected ? 'bg-blue-50 border-r-2 border-blue-500' : ''
336
- }`}
337
- style={{ paddingLeft: `${8 + level * 16}px` }}
338
- onClick={() => handleNodeClick(node)}
339
- >
340
- {hasChildren && (
341
- <div className="w-4 h-4 mr-1 flex items-center justify-center">
342
- {isExpanded ? (
343
- <ChevronDownIcon className="w-3 h-3 text-gray-500" />
344
- ) : (
345
- <ChevronRightIcon className="w-3 h-3 text-gray-500" />
346
- )}
347
- </div>
348
- )}
349
- {!hasChildren && <div className="w-5 mr-1" />}
350
-
351
- <div className="w-4 h-4 mr-2 flex items-center justify-center">
352
- {node.type === 'folder' ? (
353
- isExpanded ? (
354
- <FolderOpenIcon className="w-4 h-4 text-blue-500" />
355
- ) : (
356
- <FolderIcon className="w-4 h-4 text-blue-500" />
357
- )
358
- ) : node.type === 'resource' ? (
359
- <DocumentTextIcon className="w-4 h-4 text-green-500" />
360
- ) : (
361
- <CubeIcon className="w-4 h-4 text-purple-500" />
362
- )}
363
- </div>
364
-
365
- <span className={`text-sm ${isSelected ? 'font-medium text-blue-900' : 'text-gray-700'}`}>
366
- {node.name}
367
- </span>
368
- </div>
369
-
370
- {hasChildren && isExpanded && (
371
- <div>{node.children!.map((child) => renderTreeNode(child, level + 1))}</div>
372
- )}
373
- </div>
374
- );
375
- };
376
-
377
- if (!resourceState.processedResources) {
378
- return (
379
- <div className="p-6">
380
- <div className="flex items-center space-x-3 mb-6">
381
- <CubeIcon className="h-8 w-8 text-blue-600" />
382
- <h2 className="text-2xl font-bold text-gray-900">Compiled Browser</h2>
383
- </div>
384
-
385
- <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8 text-center">
386
- <div className="max-w-2xl mx-auto">
387
- <h3 className="text-xl font-semibold text-gray-900 mb-4">No Compiled Resources</h3>
388
- <p className="text-gray-600 mb-6">
389
- Import resources to explore the compiled resource collection.
390
- </p>
391
- <div className="bg-blue-50 rounded-lg p-4">
392
- <p className="text-sm text-blue-800">
393
- <strong>Tip:</strong> Use the Import Tool to load resources, then return here to browse the
394
- compiled collection structure including resources, decisions, condition sets, and conditions.
395
- </p>
396
- </div>
397
- </div>
398
- </div>
399
- </div>
400
- );
401
- }
402
-
403
- if (!treeData) {
404
- return (
405
- <div className="p-6">
406
- <div className="flex items-center space-x-3 mb-6">
407
- <CubeIcon className="h-8 w-8 text-blue-600" />
408
- <h2 className="text-2xl font-bold text-gray-900">Compiled Browser</h2>
409
- </div>
410
-
411
- <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8 text-center">
412
- <div className="max-w-2xl mx-auto">
413
- <h3 className="text-xl font-semibold text-gray-900 mb-4">Error Loading Compiled Resources</h3>
414
- <p className="text-gray-600">Unable to load the compiled resource collection.</p>
415
- </div>
416
- </div>
417
- </div>
418
- );
419
- }
420
-
421
- const selectedNode = selectedNodeId ? findNodeById(treeData, selectedNodeId) : null;
422
-
423
- return (
424
- <div className="p-6">
425
- <div className="flex items-center justify-between mb-6">
426
- <div className="flex items-center space-x-3">
427
- <CubeIcon className="h-8 w-8 text-blue-600" />
428
- <h2 className="text-2xl font-bold text-gray-900">Compiled Browser</h2>
429
- {isFilteringActive && (
430
- <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
431
- Filtered
432
- </span>
433
- )}
434
- </div>
435
- {activeProcessedResources && (
436
- <div className="flex items-center space-x-2">
437
- <button
438
- onClick={handleExportCompiledData}
439
- 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"
440
- >
441
- <DocumentArrowDownIcon className="h-4 w-4 mr-1" />
442
- Export JSON
443
- </button>
444
- </div>
445
- )}
446
- </div>
447
-
448
- {/* JSON View Toggle */}
449
- {activeProcessedResources && (
450
- <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6">
451
- <button
452
- onClick={() => setShowJsonView(!showJsonView)}
453
- 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"
454
- >
455
- <CodeBracketIcon className="h-4 w-4 mr-2" />
456
- {showJsonView ? 'Hide' : 'Show'} JSON Compiled Collection
457
- {showJsonView ? (
458
- <ChevronUpIcon className="h-4 w-4 ml-2" />
459
- ) : (
460
- <ChevronDownIcon className="h-4 w-4 ml-2" />
461
- )}
462
- </button>
463
-
464
- {/* JSON View */}
465
- {showJsonView && (
466
- <div className="mt-4">
467
- <div className="bg-gray-50 rounded-lg border border-gray-200 p-4">
468
- <div className="flex items-center justify-between mb-2">
469
- <h3 className="text-sm font-medium text-gray-900">Compiled Collection (JSON)</h3>
470
- <button
471
- onClick={handleExportCompiledData}
472
- 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"
473
- >
474
- <DocumentArrowDownIcon className="h-3 w-3 mr-1" />
475
- Export
476
- </button>
477
- </div>
478
- <pre className="text-xs text-gray-800 bg-white p-3 rounded border overflow-x-auto max-h-64 overflow-y-auto">
479
- {JSON.stringify(
480
- {
481
- ...activeProcessedResources.compiledCollection,
482
- metadata: {
483
- exportedAt: new Date().toISOString(),
484
- type: isFilteringActive
485
- ? 'ts-res-filtered-compiled-collection'
486
- : 'ts-res-compiled-collection',
487
- ...(isFilteringActive && { filterContext: filterState.appliedValues })
488
- }
489
- },
490
- null,
491
- 2
492
- )}
493
- </pre>
494
- </div>
495
- </div>
496
- )}
497
- </div>
498
- )}
499
-
500
- <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
501
- <div className="flex flex-col lg:flex-row gap-6 h-[600px]">
502
- {/* Left side: Tree Navigation */}
503
- <div className="lg:w-1/2 flex flex-col">
504
- <div className="flex items-center space-x-3 mb-4">
505
- <h3 className="text-lg font-semibold text-gray-900">Compiled Collection</h3>
506
- </div>
507
-
508
- <div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50">
509
- {renderTreeNode(treeData)}
510
- </div>
511
- </div>
512
-
513
- {/* Right side: Details Panel */}
514
- <div className="lg:w-1/2 flex flex-col">
515
- {selectedNode ? (
516
- <NodeDetail
517
- node={selectedNode}
518
- onMessage={onMessage}
519
- resourceState={resourceState}
520
- activeProcessedResources={activeProcessedResources}
521
- isFilteringActive={isFilteringActive}
522
- getConditionKey={getConditionKey}
523
- getConditionSetKey={getConditionSetKey}
524
- getDecisionKey={getDecisionKey}
525
- formatDisplayName={formatDisplayName}
526
- getCandidateConditionSetKey={getCandidateConditionSetKey}
527
- getResourceTypeName={getResourceTypeName}
528
- />
529
- ) : (
530
- <div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
531
- <div className="text-center">
532
- <CubeIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
533
- <p className="text-gray-500">Select an item to view details</p>
534
- </div>
535
- </div>
536
- )}
537
- </div>
538
- </div>
539
- </div>
540
- </div>
541
- );
542
- };
543
-
544
- // Helper function to find node by ID
545
- const findNodeById = (tree: TreeNode, id: string): TreeNode | null => {
546
- if (tree.id === id) return tree;
547
- if (tree.children) {
548
- for (const child of tree.children) {
549
- const found = findNodeById(child, id);
550
- if (found) return found;
551
- }
552
- }
553
- return null;
554
- };
555
-
556
- // Node detail component
557
- interface NodeDetailProps {
558
- node: TreeNode;
559
- onMessage?: (type: Message['type'], message: string) => void;
560
- resourceState: any; // UseResourceManagerReturn['state']
561
- activeProcessedResources: any;
562
- isFilteringActive: boolean;
563
- getConditionKey: (
564
- condition: ResourceJson.Compiled.ICompiledCondition,
565
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection
566
- ) => string;
567
- getConditionSetKey: (
568
- conditionSet: ResourceJson.Compiled.ICompiledConditionSet,
569
- conditionSetIndex: number,
570
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection
571
- ) => string;
572
- getDecisionKey: (
573
- decision: ResourceJson.Compiled.ICompiledAbstractDecision,
574
- decisionIndex: number,
575
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection
576
- ) => string;
577
- formatDisplayName: (key: string, index: number) => string;
578
- getCandidateConditionSetKey: (
579
- candidateIndex: number,
580
- resource: ResourceJson.Compiled.ICompiledResource,
581
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection
582
- ) => string | null;
583
- getResourceTypeName: (
584
- resourceTypeIndex: number,
585
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection
586
- ) => string;
587
- }
588
-
589
- const NodeDetail: React.FC<NodeDetailProps> = ({
590
- node,
591
- onMessage,
592
- resourceState,
593
- activeProcessedResources,
594
- isFilteringActive,
595
- getConditionKey,
596
- getConditionSetKey,
597
- getDecisionKey,
598
- formatDisplayName,
599
- getCandidateConditionSetKey,
600
- getResourceTypeName
601
- }) => {
602
- const renderNodeDetails = () => {
603
- if (!node.data) {
604
- return (
605
- <div className="p-4">
606
- <h4 className="font-medium text-gray-700 mb-2">Folder: {node.name}</h4>
607
- <p className="text-sm text-gray-600">
608
- {node.children ? `Contains ${node.children.length} items` : 'Empty folder'}
609
- </p>
610
- </div>
611
- );
612
- }
613
-
614
- const { type, collection, resource } = node.data;
615
-
616
- switch (type) {
617
- case 'compiled-resource':
618
- return (
619
- <div className="space-y-4">
620
- <div>
621
- <h4 className="font-medium text-gray-700 mb-2">Compiled Resource Details</h4>
622
- <div className="bg-white p-3 rounded border space-y-2 text-sm">
623
- <div>
624
- <strong>ID:</strong> {resource?.id || 'Unknown'}
625
- </div>
626
- <div>
627
- <strong>Resource Type:</strong>{' '}
628
- {(() => {
629
- const resourceTypeIndex = resource?.type;
630
- if (resourceTypeIndex === undefined) return 'Unknown';
631
- const resourceTypeName = getResourceTypeName(
632
- resourceTypeIndex,
633
- activeProcessedResources!.compiledCollection
634
- );
635
- return formatDisplayName(resourceTypeName, resourceTypeIndex);
636
- })()}
637
- </div>
638
- <div>
639
- <strong>Decision:</strong>{' '}
640
- {(() => {
641
- const decisionIndex = resource?.decision;
642
- if (decisionIndex === undefined) return 'Unknown';
643
- const decision = activeProcessedResources!.compiledCollection.decisions[decisionIndex];
644
- if (!decision) return `${decisionIndex}`;
645
- const decisionKey = getDecisionKey(
646
- decision,
647
- decisionIndex,
648
- activeProcessedResources!.compiledCollection
649
- );
650
- return formatDisplayName(decisionKey, decisionIndex);
651
- })()}
652
- </div>
653
- </div>
654
- </div>
655
-
656
- {resource?.candidates && (
657
- <div>
658
- <h4 className="font-medium text-gray-700 mb-2">Candidates</h4>
659
- <div className="space-y-2">
660
- {resource.candidates.map((candidateItem: any, index: number) => {
661
- const conditionSetKey = getCandidateConditionSetKey(
662
- index,
663
- resource,
664
- activeProcessedResources!.compiledCollection
665
- );
666
-
667
- return (
668
- <div key={index} className="bg-white p-3 rounded border">
669
- <div className="text-sm">
670
- <strong>
671
- Candidate {index + 1}
672
- {conditionSetKey && ` (${conditionSetKey})`}
673
- </strong>
674
- </div>
675
- <div className="text-xs text-gray-600 mt-1">
676
- {typeof candidateItem === 'object' && candidateItem ? (
677
- <pre className="text-xs bg-gray-50 p-2 rounded mt-2 overflow-x-auto">
678
- {JSON.stringify(candidateItem, null, 2)}
679
- </pre>
680
- ) : (
681
- <div>Candidate Index: {candidateItem}</div>
682
- )}
683
- </div>
684
- </div>
685
- );
686
- })}
687
- </div>
688
- </div>
689
- )}
690
- </div>
691
- );
692
-
693
- case 'resource':
694
- return (
695
- <div className="space-y-4">
696
- <div>
697
- <h4 className="font-medium text-gray-700 mb-2">Resource Details</h4>
698
- <div className="bg-white p-3 rounded border space-y-2 text-sm">
699
- <div>
700
- <strong>ID:</strong> {resource?.id || 'Unknown'}
701
- </div>
702
- <div>
703
- <strong>Type:</strong> {resource?.resourceType?.key || 'Unknown'}
704
- </div>
705
- <div>
706
- <strong>Candidates:</strong> {resource?.candidates?.length || 0}
707
- </div>
708
- </div>
709
- </div>
710
-
711
- {resource?.candidates && (
712
- <div>
713
- <h4 className="font-medium text-gray-700 mb-2">Candidates</h4>
714
- <div className="space-y-2">
715
- {resource.candidates.map((candidate: any, index: number) => (
716
- <div key={index} className="bg-white p-3 rounded border">
717
- <div className="text-sm">
718
- <strong>Candidate {index + 1}</strong>
719
- {candidate.isPartial && (
720
- <span className="ml-2 text-xs bg-yellow-100 text-yellow-800 px-1 rounded">
721
- Partial
722
- </span>
723
- )}
724
- </div>
725
- <div className="text-xs text-gray-600 mt-1">
726
- {candidate.mergeMethod && (
727
- <div>
728
- <strong>Merge Method:</strong> {candidate.mergeMethod}
729
- </div>
730
- )}
731
- </div>
732
- {candidate.json && (
733
- <pre className="text-xs bg-gray-50 p-2 rounded mt-2 overflow-x-auto max-h-32">
734
- {JSON.stringify(candidate.json, null, 2)}
735
- </pre>
736
- )}
737
- </div>
738
- ))}
739
- </div>
740
- </div>
741
- )}
742
- </div>
743
- );
744
-
745
- case 'decisions':
746
- return (
747
- <div>
748
- <h4 className="font-medium text-gray-700 mb-2">Decisions Collection</h4>
749
- <div className="bg-white p-3 rounded border text-sm">
750
- <div>
751
- <strong>Count:</strong> {collection?.length || 0}
752
- </div>
753
- <div className="mt-2 text-xs text-gray-600">
754
- Decisions contain the logic for selecting resource candidates based on condition sets.
755
- </div>
756
- </div>
757
- {collection && collection.length > 0 && (
758
- <div className="mt-4">
759
- <h5 className="font-medium text-gray-700 mb-2">Decision Details</h5>
760
- <div className="space-y-2 max-h-64 overflow-y-auto">
761
- {collection.map(
762
- (decision: ResourceJson.Compiled.ICompiledAbstractDecision, index: number) => {
763
- const decisionKey = getDecisionKey(
764
- decision,
765
- index,
766
- activeProcessedResources!.compiledCollection
767
- );
768
- return (
769
- <div key={index} className="bg-white p-3 rounded border">
770
- <div className="text-sm">
771
- <strong>Decision {formatDisplayName(decisionKey, index)}</strong>
772
- </div>
773
- <div className="text-xs text-gray-600 mt-1">
774
- Condition Sets: {decision.conditionSets?.length || 0}
775
- </div>
776
- {decision.conditionSets && decision.conditionSets.length > 0 && (
777
- <div className="mt-2">
778
- <div className="text-xs font-medium text-gray-700 mb-1">
779
- Referenced Condition Sets:
780
- </div>
781
- <div className="space-y-1">
782
- {decision.conditionSets.map((conditionSetIndex, idx) => {
783
- const conditionSet =
784
- activeProcessedResources!.compiledCollection.conditionSets[
785
- conditionSetIndex
786
- ];
787
- if (!conditionSet)
788
- return <div key={idx}>Unknown condition set {conditionSetIndex}</div>;
789
- const conditionSetKey = getConditionSetKey(
790
- conditionSet,
791
- conditionSetIndex,
792
- activeProcessedResources!.compiledCollection
793
- );
794
- return (
795
- <div key={idx} className="text-xs bg-gray-50 p-1 rounded">
796
- [{formatDisplayName(conditionSetKey, conditionSetIndex)}]
797
- </div>
798
- );
799
- })}
800
- </div>
801
- </div>
802
- )}
803
- </div>
804
- );
805
- }
806
- )}
807
- </div>
808
- </div>
809
- )}
810
- </div>
811
- );
812
-
813
- case 'condition-sets':
814
- return (
815
- <div>
816
- <h4 className="font-medium text-gray-700 mb-2">Condition Sets Collection</h4>
817
- <div className="bg-white p-3 rounded border text-sm">
818
- <div>
819
- <strong>Count:</strong> {collection?.length || 0}
820
- </div>
821
- <div className="mt-2 text-xs text-gray-600">
822
- Condition sets group related conditions together for decision making.
823
- </div>
824
- </div>
825
- {collection && collection.length > 0 && (
826
- <div className="mt-4">
827
- <h5 className="font-medium text-gray-700 mb-2">Condition Set Details</h5>
828
- <div className="space-y-2 max-h-64 overflow-y-auto">
829
- {collection.map(
830
- (conditionSet: ResourceJson.Compiled.ICompiledConditionSet, index: number) => {
831
- const conditionSetKey = getConditionSetKey(
832
- conditionSet,
833
- index,
834
- activeProcessedResources!.compiledCollection
835
- );
836
- return (
837
- <div key={index} className="bg-white p-3 rounded border">
838
- <div className="text-sm">
839
- <strong>Condition Set {formatDisplayName(conditionSetKey, index)}</strong>
840
- </div>
841
- <div className="text-xs text-gray-600 mt-1">
842
- Conditions: {conditionSet.conditions?.length || 0}
843
- </div>
844
- {conditionSet.conditions && conditionSet.conditions.length > 0 && (
845
- <div className="mt-2">
846
- <div className="text-xs font-medium text-gray-700 mb-1">
847
- Referenced Conditions:
848
- </div>
849
- <div className="space-y-1">
850
- {conditionSet.conditions.map((conditionIndex, idx) => {
851
- const condition =
852
- activeProcessedResources!.compiledCollection.conditions[conditionIndex];
853
- if (!condition)
854
- return <div key={idx}>Unknown condition {conditionIndex}</div>;
855
- const conditionKey = getConditionKey(
856
- condition,
857
- activeProcessedResources!.compiledCollection
858
- );
859
- return (
860
- <div key={idx} className="text-xs bg-gray-50 p-1 rounded">
861
- [{formatDisplayName(conditionKey, conditionIndex)}]
862
- </div>
863
- );
864
- })}
865
- </div>
866
- </div>
867
- )}
868
- </div>
869
- );
870
- }
871
- )}
872
- </div>
873
- </div>
874
- )}
875
- </div>
876
- );
877
-
878
- case 'conditions':
879
- return (
880
- <div>
881
- <h4 className="font-medium text-gray-700 mb-2">Conditions Collection</h4>
882
- <div className="bg-white p-3 rounded border text-sm">
883
- <div>
884
- <strong>Count:</strong> {collection?.length || 0}
885
- </div>
886
- <div className="mt-2 text-xs text-gray-600">
887
- Individual conditions that test qualifier values for resource selection.
888
- </div>
889
- </div>
890
- {collection && collection.length > 0 && (
891
- <div className="mt-4">
892
- <h5 className="font-medium text-gray-700 mb-2">Condition Details</h5>
893
- <div className="space-y-2 max-h-64 overflow-y-auto">
894
- {collection.map((condition: ResourceJson.Compiled.ICompiledCondition, index: number) => {
895
- const conditionKey = getConditionKey(
896
- condition,
897
- activeProcessedResources!.compiledCollection
898
- );
899
- return (
900
- <div key={index} className="bg-white p-3 rounded border">
901
- <div className="text-sm">
902
- <strong>Condition {formatDisplayName(conditionKey, index)}</strong>
903
- </div>
904
- <div className="text-xs text-gray-600 mt-1">
905
- <div>Qualifier Index: {condition.qualifierIndex}</div>
906
- <div>Value: {condition.value}</div>
907
- <div>Priority: {condition.priority}</div>
908
- {condition.operator && <div>Operator: {condition.operator}</div>}
909
- {condition.scoreAsDefault !== undefined && (
910
- <div>
911
- <span className="text-xs font-medium text-amber-700">
912
- Score as Default: {condition.scoreAsDefault}
913
- </span>
914
- </div>
915
- )}
916
- </div>
917
- </div>
918
- );
919
- })}
920
- </div>
921
- </div>
922
- )}
923
- </div>
924
- );
925
-
926
- default:
927
- return (
928
- <div className="p-4">
929
- <h4 className="font-medium text-gray-700 mb-2">{node.name}</h4>
930
- <p className="text-sm text-gray-600">Unknown node type</p>
931
- </div>
932
- );
933
- }
934
- };
935
-
936
- return (
937
- <div className="flex flex-col h-full">
938
- <div className="flex items-center justify-between mb-4">
939
- <h3 className="text-lg font-semibold text-gray-900">Details</h3>
940
- </div>
941
-
942
- <div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg p-4 bg-gray-50">
943
- {renderNodeDetails()}
944
- </div>
945
- </div>
946
- );
947
- };
948
-
949
- export default CompiledBrowser;