@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.
- package/.rush/temp/03c8b056281d9db0a97d8a6e25eea798a160d393.tar.log +271 -0
- package/.rush/temp/chunked-rush-logs/ts-res-ui-components.build.chunks.jsonl +9 -0
- package/.rush/temp/operation/build/all.log +9 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +1111 -0
- package/README.md +18 -0
- package/REFACTORING_PLAN.md +171 -0
- package/config/jest.config.json +16 -0
- package/config/jest.setup.js +64 -0
- package/config/rig.json +16 -0
- package/lib/components/common/QualifierContextControl.d.ts +14 -0
- package/lib/components/common/QualifierContextControl.d.ts.map +1 -0
- package/lib/components/common/QualifierContextControl.js +78 -0
- package/lib/components/common/QualifierContextControl.js.map +1 -0
- package/lib/components/common/ResourceListView.d.ts +11 -0
- package/lib/components/common/ResourceListView.d.ts.map +1 -0
- package/lib/components/common/ResourceListView.js +20 -0
- package/lib/components/common/ResourceListView.js.map +1 -0
- package/lib/components/common/ResourceTreeView.d.ts +12 -0
- package/lib/components/common/ResourceTreeView.d.ts.map +1 -0
- package/lib/components/common/ResourceTreeView.js +162 -0
- package/lib/components/common/ResourceTreeView.js.map +1 -0
- package/lib/components/forms/HierarchyEditor.d.ts +10 -0
- package/lib/components/forms/HierarchyEditor.d.ts.map +1 -0
- package/lib/components/forms/HierarchyEditor.js +106 -0
- package/lib/components/forms/HierarchyEditor.js.map +1 -0
- package/lib/components/forms/QualifierEditForm.d.ts +11 -0
- package/lib/components/forms/QualifierEditForm.d.ts.map +1 -0
- package/lib/components/forms/QualifierEditForm.js +181 -0
- package/lib/components/forms/QualifierEditForm.js.map +1 -0
- package/lib/components/forms/QualifierTypeEditForm.d.ts +10 -0
- package/lib/components/forms/QualifierTypeEditForm.d.ts.map +1 -0
- package/lib/components/forms/QualifierTypeEditForm.js +172 -0
- package/lib/components/forms/QualifierTypeEditForm.js.map +1 -0
- package/lib/components/forms/ResourceTypeEditForm.d.ts +10 -0
- package/lib/components/forms/ResourceTypeEditForm.d.ts.map +1 -0
- package/lib/components/forms/ResourceTypeEditForm.js +188 -0
- package/lib/components/forms/ResourceTypeEditForm.js.map +1 -0
- package/lib/components/forms/index.d.ts +9 -0
- package/lib/components/forms/index.d.ts.map +1 -0
- package/lib/components/forms/index.js +5 -0
- package/lib/components/forms/index.js.map +1 -0
- package/lib/components/orchestrator/ResourceOrchestrator.d.ts +14 -0
- package/lib/components/orchestrator/ResourceOrchestrator.d.ts.map +1 -0
- package/lib/components/orchestrator/ResourceOrchestrator.js +278 -0
- package/lib/components/orchestrator/ResourceOrchestrator.js.map +1 -0
- package/lib/components/views/CompiledView/index.d.ts +5 -0
- package/lib/components/views/CompiledView/index.d.ts.map +1 -0
- package/lib/components/views/CompiledView/index.js +595 -0
- package/lib/components/views/CompiledView/index.js.map +1 -0
- package/lib/components/views/ConfigurationView/index.d.ts +5 -0
- package/lib/components/views/ConfigurationView/index.d.ts.map +1 -0
- package/lib/components/views/ConfigurationView/index.js +363 -0
- package/lib/components/views/ConfigurationView/index.js.map +1 -0
- package/lib/components/views/FilterView/index.d.ts +5 -0
- package/lib/components/views/FilterView/index.d.ts.map +1 -0
- package/lib/components/views/FilterView/index.js +463 -0
- package/lib/components/views/FilterView/index.js.map +1 -0
- package/lib/components/views/ImportView/index.d.ts +5 -0
- package/lib/components/views/ImportView/index.d.ts.map +1 -0
- package/lib/components/views/ImportView/index.js +514 -0
- package/lib/components/views/ImportView/index.js.map +1 -0
- package/lib/components/views/ResolutionView/EditableJsonView.d.ts +21 -0
- package/lib/components/views/ResolutionView/EditableJsonView.d.ts.map +1 -0
- package/lib/components/views/ResolutionView/EditableJsonView.js +109 -0
- package/lib/components/views/ResolutionView/EditableJsonView.js.map +1 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts +19 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts.map +1 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.js +82 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.js.map +1 -0
- package/lib/components/views/ResolutionView/index.d.ts +5 -0
- package/lib/components/views/ResolutionView/index.d.ts.map +1 -0
- package/lib/components/views/ResolutionView/index.js +255 -0
- package/lib/components/views/ResolutionView/index.js.map +1 -0
- package/lib/components/views/SourceView/index.d.ts +5 -0
- package/lib/components/views/SourceView/index.d.ts.map +1 -0
- package/lib/components/views/SourceView/index.js +316 -0
- package/lib/components/views/SourceView/index.js.map +1 -0
- package/lib/components/views/ZipLoaderView/index.d.ts +5 -0
- package/lib/components/views/ZipLoaderView/index.d.ts.map +1 -0
- package/lib/components/views/ZipLoaderView/index.js +313 -0
- package/lib/components/views/ZipLoaderView/index.js.map +1 -0
- package/lib/hooks/useConfigurationState.d.ts +46 -0
- package/lib/hooks/useConfigurationState.d.ts.map +1 -0
- package/lib/hooks/useConfigurationState.js +239 -0
- package/lib/hooks/useConfigurationState.js.map +1 -0
- package/lib/hooks/useFilterState.d.ts +7 -0
- package/lib/hooks/useFilterState.d.ts.map +1 -0
- package/lib/hooks/useFilterState.js +80 -0
- package/lib/hooks/useFilterState.js.map +1 -0
- package/lib/hooks/useResolutionState.d.ts +8 -0
- package/lib/hooks/useResolutionState.d.ts.map +1 -0
- package/lib/hooks/useResolutionState.js +253 -0
- package/lib/hooks/useResolutionState.js.map +1 -0
- package/lib/hooks/useResourceData.d.ts +19 -0
- package/lib/hooks/useResourceData.d.ts.map +1 -0
- package/lib/hooks/useResourceData.js +368 -0
- package/lib/hooks/useResourceData.js.map +1 -0
- package/lib/hooks/useViewState.d.ts +10 -0
- package/lib/hooks/useViewState.d.ts.map +1 -0
- package/lib/hooks/useViewState.js +29 -0
- package/lib/hooks/useViewState.js.map +1 -0
- package/lib/index.d.ts +27 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +34 -0
- package/lib/index.js.map +1 -0
- package/lib/test/helpers/testDataLoader.d.ts +37 -0
- package/lib/test/helpers/testDataLoader.d.ts.map +1 -0
- package/lib/test/helpers/testDataLoader.js +171 -0
- package/lib/test/helpers/testDataLoader.js.map +1 -0
- package/lib/test/unit/utils/configurationUtils.test.d.ts +2 -0
- package/lib/test/unit/utils/configurationUtils.test.d.ts.map +1 -0
- package/lib/test/unit/utils/configurationUtils.test.js +497 -0
- package/lib/test/unit/utils/configurationUtils.test.js.map +1 -0
- package/lib/test/unit/utils/fileProcessing.test.d.ts +2 -0
- package/lib/test/unit/utils/fileProcessing.test.d.ts.map +1 -0
- package/lib/test/unit/utils/fileProcessing.test.js +321 -0
- package/lib/test/unit/utils/fileProcessing.test.js.map +1 -0
- package/lib/test/unit/utils/filterResources.test.d.ts +2 -0
- package/lib/test/unit/utils/filterResources.test.d.ts.map +1 -0
- package/lib/test/unit/utils/filterResources.test.js +403 -0
- package/lib/test/unit/utils/filterResources.test.js.map +1 -0
- package/lib/test/unit/utils/resolutionEditing.test.d.ts +2 -0
- package/lib/test/unit/utils/resolutionEditing.test.d.ts.map +1 -0
- package/lib/test/unit/utils/resolutionEditing.test.js +439 -0
- package/lib/test/unit/utils/resolutionEditing.test.js.map +1 -0
- package/lib/test/unit/utils/resolutionUtils.test.d.ts +2 -0
- package/lib/test/unit/utils/resolutionUtils.test.d.ts.map +1 -0
- package/lib/test/unit/utils/resolutionUtils.test.js +397 -0
- package/lib/test/unit/utils/resolutionUtils.test.js.map +1 -0
- package/lib/test/unit/utils/tsResIntegration.test.d.ts +2 -0
- package/lib/test/unit/utils/tsResIntegration.test.d.ts.map +1 -0
- package/lib/test/unit/utils/tsResIntegration.test.js +376 -0
- package/lib/test/unit/utils/tsResIntegration.test.js.map +1 -0
- package/lib/types/index.d.ts +251 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +2 -0
- package/lib/types/index.js.map +1 -0
- package/lib/utils/configurationUtils.d.ts +74 -0
- package/lib/utils/configurationUtils.d.ts.map +1 -0
- package/lib/utils/configurationUtils.js +359 -0
- package/lib/utils/configurationUtils.js.map +1 -0
- package/lib/utils/fileProcessing.d.ts +18 -0
- package/lib/utils/fileProcessing.d.ts.map +1 -0
- package/lib/utils/fileProcessing.js +142 -0
- package/lib/utils/fileProcessing.js.map +1 -0
- package/lib/utils/filterResources.d.ts +38 -0
- package/lib/utils/filterResources.d.ts.map +1 -0
- package/lib/utils/filterResources.js +153 -0
- package/lib/utils/filterResources.js.map +1 -0
- package/lib/utils/resolutionEditing.d.ts +58 -0
- package/lib/utils/resolutionEditing.d.ts.map +1 -0
- package/lib/utils/resolutionEditing.js +246 -0
- package/lib/utils/resolutionEditing.js.map +1 -0
- package/lib/utils/resolutionUtils.d.ts +28 -0
- package/lib/utils/resolutionUtils.d.ts.map +1 -0
- package/lib/utils/resolutionUtils.js +216 -0
- package/lib/utils/resolutionUtils.js.map +1 -0
- package/lib/utils/tsResIntegration.d.ts +71 -0
- package/lib/utils/tsResIntegration.d.ts.map +1 -0
- package/lib/utils/tsResIntegration.js +294 -0
- package/lib/utils/tsResIntegration.js.map +1 -0
- package/lib/utils/zipLoader/browserZipLoader.d.ts +48 -0
- package/lib/utils/zipLoader/browserZipLoader.d.ts.map +1 -0
- package/lib/utils/zipLoader/browserZipLoader.js +247 -0
- package/lib/utils/zipLoader/browserZipLoader.js.map +1 -0
- package/lib/utils/zipLoader/index.d.ts +8 -0
- package/lib/utils/zipLoader/index.d.ts.map +1 -0
- package/lib/utils/zipLoader/index.js +13 -0
- package/lib/utils/zipLoader/index.js.map +1 -0
- package/lib/utils/zipLoader/nodeZipBuilder.d.ts +55 -0
- package/lib/utils/zipLoader/nodeZipBuilder.d.ts.map +1 -0
- package/lib/utils/zipLoader/nodeZipBuilder.js +98 -0
- package/lib/utils/zipLoader/nodeZipBuilder.js.map +1 -0
- package/lib/utils/zipLoader/types.d.ts +139 -0
- package/lib/utils/zipLoader/types.d.ts.map +1 -0
- package/lib/utils/zipLoader/types.js +2 -0
- package/lib/utils/zipLoader/types.js.map +1 -0
- package/lib/utils/zipLoader/zipUtils.d.ts +53 -0
- package/lib/utils/zipLoader/zipUtils.d.ts.map +1 -0
- package/lib/utils/zipLoader/zipUtils.js +229 -0
- package/lib/utils/zipLoader/zipUtils.js.map +1 -0
- package/package.json +69 -0
- package/rush-logs/ts-res-ui-components.build.cache.log +3 -0
- package/rush-logs/ts-res-ui-components.build.log +9 -0
- package/src/components/common/QualifierContextControl.tsx +151 -0
- package/src/components/common/ResourceListView.tsx +63 -0
- package/src/components/common/ResourceTreeView.tsx +271 -0
- package/src/components/forms/HierarchyEditor.tsx +204 -0
- package/src/components/forms/QualifierEditForm.tsx +355 -0
- package/src/components/forms/QualifierTypeEditForm.tsx +347 -0
- package/src/components/forms/ResourceTypeEditForm.tsx +331 -0
- package/src/components/forms/index.ts +11 -0
- package/src/components/orchestrator/ResourceOrchestrator.tsx +372 -0
- package/src/components/views/CompiledView/index.tsx +922 -0
- package/src/components/views/ConfigurationView/index.tsx +800 -0
- package/src/components/views/FilterView/index.tsx +825 -0
- package/src/components/views/ImportView/index.tsx +717 -0
- package/src/components/views/ResolutionView/EditableJsonView.tsx +214 -0
- package/src/components/views/ResolutionView/ResolutionEditControls.tsx +170 -0
- package/src/components/views/ResolutionView/index.tsx +591 -0
- package/src/components/views/SourceView/index.tsx +536 -0
- package/src/components/views/ZipLoaderView/index.tsx +485 -0
- package/src/hooks/useConfigurationState.ts +374 -0
- package/src/hooks/useFilterState.ts +97 -0
- package/src/hooks/useResolutionState.ts +355 -0
- package/src/hooks/useResourceData.ts +467 -0
- package/src/hooks/useViewState.ts +44 -0
- package/src/index.ts +45 -0
- package/src/test/helpers/testDataLoader.ts +195 -0
- package/src/test/unit/utils/configurationUtils.test.ts +630 -0
- package/src/test/unit/utils/fileProcessing.test.ts +391 -0
- package/src/test/unit/utils/filterResources.test.ts +574 -0
- package/src/test/unit/utils/resolutionEditing.test.ts +556 -0
- package/src/test/unit/utils/resolutionUtils.test.ts +521 -0
- package/src/test/unit/utils/tsResIntegration.test.ts +433 -0
- package/src/types/index.ts +322 -0
- package/src/utils/configurationUtils.ts +424 -0
- package/src/utils/fileProcessing.ts +160 -0
- package/src/utils/filterResources.ts +206 -0
- package/src/utils/resolutionEditing.ts +319 -0
- package/src/utils/resolutionUtils.ts +289 -0
- package/src/utils/tsResIntegration.ts +440 -0
- package/src/utils/zipLoader/browserZipLoader.ts +319 -0
- package/src/utils/zipLoader/index.ts +26 -0
- package/src/utils/zipLoader/nodeZipBuilder.ts +153 -0
- package/src/utils/zipLoader/types.ts +175 -0
- package/src/utils/zipLoader/zipUtils.ts +266 -0
- package/temp/build/typescript/ts_gZid87Hu.json +1 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,922 @@
|
|
|
1
|
+
import React, { useState, useMemo, useCallback, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
CubeIcon,
|
|
4
|
+
FolderIcon,
|
|
5
|
+
FolderOpenIcon,
|
|
6
|
+
DocumentTextIcon,
|
|
7
|
+
ChevronRightIcon,
|
|
8
|
+
ChevronDownIcon,
|
|
9
|
+
DocumentArrowDownIcon,
|
|
10
|
+
CodeBracketIcon,
|
|
11
|
+
ChevronUpIcon,
|
|
12
|
+
ArchiveBoxIcon
|
|
13
|
+
} from '@heroicons/react/24/outline';
|
|
14
|
+
import { CompiledViewProps, ProcessedResources, FilterState, FilterResult } from '../../../types';
|
|
15
|
+
import { ResourceJson, Config, Bundle, Resources } from '@fgv/ts-res';
|
|
16
|
+
|
|
17
|
+
interface TreeNode {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
type: 'folder' | 'resource' | 'section';
|
|
21
|
+
children?: TreeNode[];
|
|
22
|
+
data?: any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const CompiledView: React.FC<CompiledViewProps> = ({
|
|
26
|
+
resources,
|
|
27
|
+
filterState,
|
|
28
|
+
filterResult,
|
|
29
|
+
useNormalization: useNormalizationProp = false,
|
|
30
|
+
onExport,
|
|
31
|
+
onMessage,
|
|
32
|
+
className = ''
|
|
33
|
+
}) => {
|
|
34
|
+
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
|
|
35
|
+
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set(['root', 'resources']));
|
|
36
|
+
const [showJsonView, setShowJsonView] = useState(false);
|
|
37
|
+
const [useNormalization, setUseNormalization] = useState(useNormalizationProp);
|
|
38
|
+
|
|
39
|
+
// Update normalization default when bundle state changes
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (resources?.isLoadedFromBundle && !useNormalization) {
|
|
42
|
+
setUseNormalization(true);
|
|
43
|
+
}
|
|
44
|
+
}, [resources?.isLoadedFromBundle, useNormalization]);
|
|
45
|
+
|
|
46
|
+
// Use filtered resources when filtering is active and successful
|
|
47
|
+
const isFilteringActive = filterState?.enabled && filterResult?.success === true;
|
|
48
|
+
const activeProcessedResources = isFilteringActive ? filterResult?.processedResources : resources;
|
|
49
|
+
|
|
50
|
+
// Get the active compiled collection
|
|
51
|
+
const activeCompiledCollection = useMemo(() => {
|
|
52
|
+
return isFilteringActive
|
|
53
|
+
? filterResult?.processedResources?.compiledCollection
|
|
54
|
+
: resources?.compiledCollection;
|
|
55
|
+
}, [
|
|
56
|
+
isFilteringActive,
|
|
57
|
+
filterResult?.processedResources?.compiledCollection,
|
|
58
|
+
resources?.compiledCollection
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
// Build tree structure using the compiled collection
|
|
62
|
+
const treeData = useMemo(() => {
|
|
63
|
+
if (!activeCompiledCollection) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const tree: TreeNode = {
|
|
68
|
+
id: 'root',
|
|
69
|
+
name: 'Compiled Resources',
|
|
70
|
+
type: 'folder',
|
|
71
|
+
children: []
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Resources section using the compiled collection
|
|
76
|
+
const resourcesCount = activeCompiledCollection.resources?.length || 0;
|
|
77
|
+
const resourcesSection: TreeNode = {
|
|
78
|
+
id: 'resources',
|
|
79
|
+
name: `Resources (${resourcesCount})`,
|
|
80
|
+
type: 'section',
|
|
81
|
+
children: []
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Get all resource IDs from the compiled collection
|
|
85
|
+
if (activeCompiledCollection.resources && activeCompiledCollection.resources.length > 0) {
|
|
86
|
+
resourcesSection.children = activeCompiledCollection.resources.map((resource) => ({
|
|
87
|
+
id: `resource-${resource.id}`,
|
|
88
|
+
name: String(resource.id || 'unnamed'),
|
|
89
|
+
type: 'resource' as const,
|
|
90
|
+
data: { type: 'compiled-resource', resource }
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
tree.children!.push(resourcesSection);
|
|
95
|
+
|
|
96
|
+
// Collectors section - showing from compiled collection
|
|
97
|
+
tree.children!.push({
|
|
98
|
+
id: 'qualifiers',
|
|
99
|
+
name: `Qualifiers (${activeCompiledCollection.qualifiers?.length || 0})`,
|
|
100
|
+
type: 'section',
|
|
101
|
+
data: { type: 'qualifiers', items: activeCompiledCollection.qualifiers }
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
tree.children!.push({
|
|
105
|
+
id: 'qualifier-types',
|
|
106
|
+
name: `Qualifier Types (${activeCompiledCollection.qualifierTypes?.length || 0})`,
|
|
107
|
+
type: 'section',
|
|
108
|
+
data: { type: 'qualifier-types', items: activeCompiledCollection.qualifierTypes }
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
tree.children!.push({
|
|
112
|
+
id: 'resource-types',
|
|
113
|
+
name: `Resource Types (${activeCompiledCollection.resourceTypes?.length || 0})`,
|
|
114
|
+
type: 'section',
|
|
115
|
+
data: { type: 'resource-types', items: activeCompiledCollection.resourceTypes }
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
tree.children!.push({
|
|
119
|
+
id: 'conditions',
|
|
120
|
+
name: `Conditions (${activeCompiledCollection.conditions?.length || 0})`,
|
|
121
|
+
type: 'section',
|
|
122
|
+
data: { type: 'conditions', items: activeCompiledCollection.conditions }
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
tree.children!.push({
|
|
126
|
+
id: 'condition-sets',
|
|
127
|
+
name: `Condition Sets (${activeCompiledCollection.conditionSets?.length || 0})`,
|
|
128
|
+
type: 'section',
|
|
129
|
+
data: { type: 'condition-sets', items: activeCompiledCollection.conditionSets }
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
tree.children!.push({
|
|
133
|
+
id: 'decisions',
|
|
134
|
+
name: `Decisions (${activeCompiledCollection.decisions?.length || 0})`,
|
|
135
|
+
type: 'section',
|
|
136
|
+
data: { type: 'decisions', items: activeCompiledCollection.decisions }
|
|
137
|
+
});
|
|
138
|
+
} catch (error) {
|
|
139
|
+
onMessage?.('error', `Error building tree: ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return tree;
|
|
143
|
+
}, [activeCompiledCollection, onMessage]);
|
|
144
|
+
|
|
145
|
+
const handleExportCompiledData = useCallback(async () => {
|
|
146
|
+
if (!activeProcessedResources?.compiledCollection) {
|
|
147
|
+
onMessage?.('error', 'No compiled data available to export');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let compiledCollection = activeProcessedResources.compiledCollection;
|
|
152
|
+
|
|
153
|
+
if (useNormalization && resources?.activeConfiguration) {
|
|
154
|
+
const systemConfigResult = Config.SystemConfiguration.create(resources.activeConfiguration);
|
|
155
|
+
if (systemConfigResult.isSuccess()) {
|
|
156
|
+
// Check if we have a ResourceManagerBuilder (which supports normalization)
|
|
157
|
+
if ('getCompiledResourceCollection' in activeProcessedResources.system.resourceManager) {
|
|
158
|
+
const resourceManagerResult = Bundle.BundleNormalizer.normalize(
|
|
159
|
+
activeProcessedResources.system.resourceManager as Resources.ResourceManagerBuilder,
|
|
160
|
+
systemConfigResult.value
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (resourceManagerResult.isSuccess()) {
|
|
164
|
+
const normalizedCompiledResult = resourceManagerResult.value.getCompiledResourceCollection({
|
|
165
|
+
includeMetadata: true
|
|
166
|
+
});
|
|
167
|
+
if (normalizedCompiledResult.isSuccess()) {
|
|
168
|
+
compiledCollection = normalizedCompiledResult.value;
|
|
169
|
+
} else {
|
|
170
|
+
console.warn('Failed to get normalized compiled collection:', normalizedCompiledResult.message);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
console.warn('Failed to normalize bundle:', resourceManagerResult.message);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// For IResourceManager from bundles, the compiled collection is already normalized
|
|
177
|
+
} else {
|
|
178
|
+
console.warn('Failed to create system configuration for normalization:', systemConfigResult.message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const compiledData = {
|
|
183
|
+
...compiledCollection,
|
|
184
|
+
metadata: {
|
|
185
|
+
exportedAt: new Date().toISOString(),
|
|
186
|
+
type: isFilteringActive ? 'ts-res-filtered-compiled-collection' : 'ts-res-compiled-collection',
|
|
187
|
+
normalized: useNormalization,
|
|
188
|
+
...(resources?.isLoadedFromBundle && { loadedFromBundle: true }),
|
|
189
|
+
...(isFilteringActive && { filterContext: filterState?.appliedValues })
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
onExport?.(compiledData, 'json');
|
|
194
|
+
}, [
|
|
195
|
+
activeProcessedResources,
|
|
196
|
+
onMessage,
|
|
197
|
+
isFilteringActive,
|
|
198
|
+
filterState?.appliedValues,
|
|
199
|
+
useNormalization,
|
|
200
|
+
resources,
|
|
201
|
+
onExport
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
const handleExportBundle = useCallback(async () => {
|
|
205
|
+
if (!activeProcessedResources?.system?.resourceManager || !resources?.activeConfiguration) {
|
|
206
|
+
onMessage?.('error', 'No resource manager or configuration available to create bundle');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const systemConfigResult = Config.SystemConfiguration.create(resources.activeConfiguration);
|
|
211
|
+
if (systemConfigResult.isFailure()) {
|
|
212
|
+
onMessage?.('error', `Failed to create system configuration: ${systemConfigResult.message}`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const systemConfig = systemConfigResult.value;
|
|
217
|
+
|
|
218
|
+
const bundleParams: Bundle.IBundleCreateParams = {
|
|
219
|
+
version: '1.0.0',
|
|
220
|
+
description: isFilteringActive
|
|
221
|
+
? 'Bundle exported from ts-res-ui-components (filtered)'
|
|
222
|
+
: 'Bundle exported from ts-res-ui-components',
|
|
223
|
+
normalize: true
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Check if we have a ResourceManagerBuilder (which supports bundle creation)
|
|
227
|
+
if (!('getCompiledResourceCollection' in activeProcessedResources.system.resourceManager)) {
|
|
228
|
+
onMessage?.('error', 'Bundle export is not supported for resources loaded from bundles');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const bundleResult = Bundle.BundleBuilder.create(
|
|
233
|
+
activeProcessedResources.system.resourceManager as Resources.ResourceManagerBuilder,
|
|
234
|
+
systemConfig,
|
|
235
|
+
bundleParams
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
if (bundleResult.isFailure()) {
|
|
239
|
+
onMessage?.('error', `Failed to create bundle: ${bundleResult.message}`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const bundle = bundleResult.value;
|
|
244
|
+
|
|
245
|
+
const exportBundle = {
|
|
246
|
+
...bundle,
|
|
247
|
+
exportMetadata: {
|
|
248
|
+
exportedAt: new Date().toISOString(),
|
|
249
|
+
exportedFrom: 'ts-res-ui-components',
|
|
250
|
+
type: isFilteringActive ? 'ts-res-bundle-filtered' : 'ts-res-bundle',
|
|
251
|
+
...(isFilteringActive && { filterContext: filterState?.appliedValues })
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
onExport?.(exportBundle, 'bundle');
|
|
256
|
+
}, [
|
|
257
|
+
activeProcessedResources?.system?.resourceManager,
|
|
258
|
+
resources?.activeConfiguration,
|
|
259
|
+
onMessage,
|
|
260
|
+
isFilteringActive,
|
|
261
|
+
filterState?.appliedValues,
|
|
262
|
+
onExport
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
const handleNodeClick = (node: TreeNode) => {
|
|
266
|
+
setSelectedNodeId(node.id);
|
|
267
|
+
onMessage?.('info', `Selected: ${node.name}`);
|
|
268
|
+
|
|
269
|
+
if (node.type === 'folder' || (node.type === 'section' && node.children)) {
|
|
270
|
+
setExpandedNodes((prev) => {
|
|
271
|
+
const newExpanded = new Set(prev);
|
|
272
|
+
if (newExpanded.has(node.id)) {
|
|
273
|
+
newExpanded.delete(node.id);
|
|
274
|
+
} else {
|
|
275
|
+
newExpanded.add(node.id);
|
|
276
|
+
}
|
|
277
|
+
return newExpanded;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const renderTreeNode = (node: TreeNode, level = 0): React.ReactNode => {
|
|
283
|
+
const isExpanded = expandedNodes.has(node.id);
|
|
284
|
+
const isSelected = selectedNodeId === node.id;
|
|
285
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<div key={node.id}>
|
|
289
|
+
<div
|
|
290
|
+
className={`flex items-center px-2 py-1 cursor-pointer hover:bg-gray-100 ${
|
|
291
|
+
isSelected ? 'bg-blue-50 border-r-2 border-blue-500' : ''
|
|
292
|
+
}`}
|
|
293
|
+
style={{ paddingLeft: `${8 + level * 16}px` }}
|
|
294
|
+
onClick={() => handleNodeClick(node)}
|
|
295
|
+
>
|
|
296
|
+
{hasChildren && (
|
|
297
|
+
<div className="w-4 h-4 mr-1 flex items-center justify-center">
|
|
298
|
+
{isExpanded ? (
|
|
299
|
+
<ChevronDownIcon className="w-3 h-3 text-gray-500" />
|
|
300
|
+
) : (
|
|
301
|
+
<ChevronRightIcon className="w-3 h-3 text-gray-500" />
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
{!hasChildren && <div className="w-5 mr-1" />}
|
|
306
|
+
|
|
307
|
+
<div className="w-4 h-4 mr-2 flex items-center justify-center">
|
|
308
|
+
{node.type === 'folder' ? (
|
|
309
|
+
isExpanded ? (
|
|
310
|
+
<FolderOpenIcon className="w-4 h-4 text-blue-500" />
|
|
311
|
+
) : (
|
|
312
|
+
<FolderIcon className="w-4 h-4 text-blue-500" />
|
|
313
|
+
)
|
|
314
|
+
) : node.type === 'resource' ? (
|
|
315
|
+
<DocumentTextIcon className="w-4 h-4 text-green-500" />
|
|
316
|
+
) : (
|
|
317
|
+
<CubeIcon className="w-4 h-4 text-purple-500" />
|
|
318
|
+
)}
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<span className={`text-sm ${isSelected ? 'font-medium text-blue-900' : 'text-gray-700'}`}>
|
|
322
|
+
{node.name}
|
|
323
|
+
</span>
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
{hasChildren && isExpanded && (
|
|
327
|
+
<div>{node.children!.map((child) => renderTreeNode(child, level + 1))}</div>
|
|
328
|
+
)}
|
|
329
|
+
</div>
|
|
330
|
+
);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
if (!resources) {
|
|
334
|
+
return (
|
|
335
|
+
<div className={`p-6 ${className}`}>
|
|
336
|
+
<div className="flex items-center space-x-3 mb-6">
|
|
337
|
+
<CubeIcon className="h-8 w-8 text-blue-600" />
|
|
338
|
+
<h2 className="text-2xl font-bold text-gray-900">Compiled Resources</h2>
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8 text-center">
|
|
342
|
+
<div className="max-w-2xl mx-auto">
|
|
343
|
+
<h3 className="text-xl font-semibold text-gray-900 mb-4">No Compiled Resources</h3>
|
|
344
|
+
<p className="text-gray-600 mb-6">
|
|
345
|
+
Import resources to explore the compiled resource collection.
|
|
346
|
+
</p>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const selectedNode = selectedNodeId ? findNodeById(treeData!, selectedNodeId) : null;
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<div className={`p-6 ${className}`}>
|
|
357
|
+
<div className="flex items-center justify-between mb-6">
|
|
358
|
+
<div className="flex items-center space-x-3">
|
|
359
|
+
<CubeIcon className="h-8 w-8 text-blue-600" />
|
|
360
|
+
<h2 className="text-2xl font-bold text-gray-900">Compiled Resources</h2>
|
|
361
|
+
{isFilteringActive && (
|
|
362
|
+
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
|
|
363
|
+
Filtered
|
|
364
|
+
</span>
|
|
365
|
+
)}
|
|
366
|
+
</div>
|
|
367
|
+
{activeProcessedResources && (
|
|
368
|
+
<div className="flex items-center space-x-2">
|
|
369
|
+
<button
|
|
370
|
+
onClick={handleExportCompiledData}
|
|
371
|
+
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"
|
|
372
|
+
>
|
|
373
|
+
<DocumentArrowDownIcon className="h-4 w-4 mr-1" />
|
|
374
|
+
Export JSON
|
|
375
|
+
</button>
|
|
376
|
+
<button
|
|
377
|
+
onClick={handleExportBundle}
|
|
378
|
+
className="inline-flex items-center px-3 py-1.5 border border-blue-300 text-xs font-medium rounded text-blue-700 bg-blue-50 hover:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
379
|
+
>
|
|
380
|
+
<ArchiveBoxIcon className="h-4 w-4 mr-1" />
|
|
381
|
+
Export Bundle
|
|
382
|
+
</button>
|
|
383
|
+
</div>
|
|
384
|
+
)}
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
{/* Controls Panel */}
|
|
388
|
+
{activeProcessedResources && (
|
|
389
|
+
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6">
|
|
390
|
+
<div className="flex items-center justify-between">
|
|
391
|
+
<div className="flex items-center space-x-6">
|
|
392
|
+
{/* Normalization Toggle */}
|
|
393
|
+
<div className="flex items-center space-x-2">
|
|
394
|
+
{resources?.isLoadedFromBundle ? (
|
|
395
|
+
<ArchiveBoxIcon className="h-4 w-4 text-blue-600" />
|
|
396
|
+
) : (
|
|
397
|
+
<CubeIcon className="h-4 w-4 text-gray-600" />
|
|
398
|
+
)}
|
|
399
|
+
<label className="text-sm font-medium text-gray-700">Normalize Output:</label>
|
|
400
|
+
<button
|
|
401
|
+
onClick={() => setUseNormalization(!useNormalization)}
|
|
402
|
+
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
|
|
403
|
+
useNormalization ? 'bg-blue-600' : 'bg-gray-300'
|
|
404
|
+
}`}
|
|
405
|
+
>
|
|
406
|
+
<span
|
|
407
|
+
className={`inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${
|
|
408
|
+
useNormalization ? 'translate-x-5' : 'translate-x-1'
|
|
409
|
+
}`}
|
|
410
|
+
/>
|
|
411
|
+
</button>
|
|
412
|
+
<span className="text-xs text-gray-500">{useNormalization ? 'ON' : 'OFF'}</span>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
{/* JSON View Toggle */}
|
|
416
|
+
<button
|
|
417
|
+
onClick={() => setShowJsonView(!showJsonView)}
|
|
418
|
+
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"
|
|
419
|
+
>
|
|
420
|
+
<CodeBracketIcon className="h-4 w-4 mr-2" />
|
|
421
|
+
{showJsonView ? 'Hide' : 'Show'} JSON
|
|
422
|
+
{showJsonView ? (
|
|
423
|
+
<ChevronUpIcon className="h-4 w-4 ml-2" />
|
|
424
|
+
) : (
|
|
425
|
+
<ChevronDownIcon className="h-4 w-4 ml-2" />
|
|
426
|
+
)}
|
|
427
|
+
</button>
|
|
428
|
+
</div>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
{/* JSON View */}
|
|
432
|
+
{showJsonView && (
|
|
433
|
+
<div className="mt-4">
|
|
434
|
+
<div className="bg-gray-50 rounded-lg border border-gray-200 p-4">
|
|
435
|
+
<pre className="text-xs text-gray-800 bg-white p-3 rounded border overflow-x-auto max-h-64 overflow-y-auto">
|
|
436
|
+
{JSON.stringify(activeProcessedResources.compiledCollection, null, 2)}
|
|
437
|
+
</pre>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
)}
|
|
441
|
+
</div>
|
|
442
|
+
)}
|
|
443
|
+
|
|
444
|
+
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
445
|
+
<div className="flex flex-col lg:flex-row gap-6 h-[600px]">
|
|
446
|
+
{/* Tree Navigation */}
|
|
447
|
+
<div className="lg:w-1/2 flex flex-col">
|
|
448
|
+
<h3 className="text-lg font-semibold text-gray-900 mb-4">Compiled Collection</h3>
|
|
449
|
+
<div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50">
|
|
450
|
+
{treeData && renderTreeNode(treeData)}
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
{/* Details Panel */}
|
|
455
|
+
<div className="lg:w-1/2 flex flex-col">
|
|
456
|
+
{selectedNode ? (
|
|
457
|
+
<NodeDetail node={selectedNode} />
|
|
458
|
+
) : (
|
|
459
|
+
<div className="flex-1 flex items-center justify-center border border-gray-200 rounded-lg bg-gray-50">
|
|
460
|
+
<div className="text-center">
|
|
461
|
+
<CubeIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
|
462
|
+
<p className="text-gray-500">Select an item to view details</p>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
)}
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
);
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// Helper function to find node by ID
|
|
474
|
+
const findNodeById = (tree: TreeNode, id: string): TreeNode | null => {
|
|
475
|
+
if (tree.id === id) return tree;
|
|
476
|
+
if (tree.children) {
|
|
477
|
+
for (const child of tree.children) {
|
|
478
|
+
const found = findNodeById(child, id);
|
|
479
|
+
if (found) return found;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
// Runtime-powered NodeDetail component
|
|
486
|
+
interface NodeDetailProps {
|
|
487
|
+
node: TreeNode;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const NodeDetail: React.FC<NodeDetailProps> = ({ node }) => {
|
|
491
|
+
const [showRawJson, setShowRawJson] = useState(false);
|
|
492
|
+
|
|
493
|
+
const renderDetails = () => {
|
|
494
|
+
if (!node.data) {
|
|
495
|
+
return (
|
|
496
|
+
<div className="space-y-4">
|
|
497
|
+
<div className="bg-white rounded-lg border p-4">
|
|
498
|
+
<h4 className="font-medium text-gray-700 mb-2">📁 {node.name}</h4>
|
|
499
|
+
<p className="text-sm text-gray-600">
|
|
500
|
+
{node.children ? `Contains ${node.children.length} items` : 'Empty folder'}
|
|
501
|
+
</p>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const { type, resourceId, manager } = node.data;
|
|
508
|
+
|
|
509
|
+
switch (type) {
|
|
510
|
+
case 'runtime-resource':
|
|
511
|
+
return renderRuntimeResource(resourceId, manager);
|
|
512
|
+
|
|
513
|
+
case 'qualifiers':
|
|
514
|
+
case 'qualifier-types':
|
|
515
|
+
case 'resource-types':
|
|
516
|
+
case 'conditions':
|
|
517
|
+
case 'condition-sets':
|
|
518
|
+
case 'decisions':
|
|
519
|
+
return renderRuntimeCollection(type, manager);
|
|
520
|
+
|
|
521
|
+
default:
|
|
522
|
+
return renderRawData();
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// New function that uses runtime objects instead of manual JSON manipulation
|
|
527
|
+
const renderRuntimeResource = (resourceId: string, manager: any) => {
|
|
528
|
+
// Get the full runtime resource object with all its rich data
|
|
529
|
+
const resourceResult = manager.getBuiltResource(resourceId);
|
|
530
|
+
|
|
531
|
+
if (resourceResult.isFailure()) {
|
|
532
|
+
return (
|
|
533
|
+
<div className="bg-white rounded-lg border p-4">
|
|
534
|
+
<h4 className="font-medium text-red-700 mb-2">Error Loading Resource</h4>
|
|
535
|
+
<p className="text-sm text-red-600">{resourceResult.message}</p>
|
|
536
|
+
</div>
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const resource = resourceResult.value;
|
|
541
|
+
|
|
542
|
+
return (
|
|
543
|
+
<div className="space-y-6">
|
|
544
|
+
{/* Resource Details Header */}
|
|
545
|
+
<div className="bg-gray-50 rounded-lg p-4">
|
|
546
|
+
<h3 className="text-lg font-semibold text-gray-900 mb-4">Compiled Resource Details</h3>
|
|
547
|
+
|
|
548
|
+
<div className="bg-white rounded-lg border p-4 space-y-3">
|
|
549
|
+
<div className="flex items-start">
|
|
550
|
+
<span className="font-semibold text-gray-700 w-32">ID:</span>
|
|
551
|
+
<span className="font-mono text-gray-900">{resource.id}</span>
|
|
552
|
+
</div>
|
|
553
|
+
|
|
554
|
+
<div className="flex items-start">
|
|
555
|
+
<span className="font-semibold text-gray-700 w-32">Resource Type:</span>
|
|
556
|
+
<span className="text-gray-900">
|
|
557
|
+
{resource.resourceType.name || resource.resourceType.key}
|
|
558
|
+
<span className="ml-2 text-xs font-mono bg-gray-100 px-2 py-1 rounded">
|
|
559
|
+
({resource.resourceType.index})
|
|
560
|
+
</span>
|
|
561
|
+
</span>
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<div className="flex items-start">
|
|
565
|
+
<span className="font-semibold text-gray-700 w-32">Decision:</span>
|
|
566
|
+
<span className="text-gray-900">
|
|
567
|
+
<span className="font-mono">
|
|
568
|
+
Decision {resource.decision.baseDecision?.index ?? resource.decision.index ?? 'unknown'}{' '}
|
|
569
|
+
with {resource.decision.candidates.length} candidates
|
|
570
|
+
</span>
|
|
571
|
+
</span>
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
|
|
576
|
+
{/* Candidates Section - Now with full candidate data including bodies! */}
|
|
577
|
+
<div className="bg-gray-50 rounded-lg p-4">
|
|
578
|
+
<h3 className="text-lg font-semibold text-gray-900 mb-4">Candidates</h3>
|
|
579
|
+
|
|
580
|
+
<div className="space-y-4">
|
|
581
|
+
{resource.candidates.map((candidate: any, candidateIdx: number) => {
|
|
582
|
+
// Get the corresponding decision candidate to access condition sets
|
|
583
|
+
const decisionCandidate = resource.decision.candidates[candidateIdx];
|
|
584
|
+
|
|
585
|
+
return (
|
|
586
|
+
<div key={candidateIdx} className="bg-white rounded-lg border p-4">
|
|
587
|
+
<div className="mb-3">
|
|
588
|
+
<h4 className="font-semibold text-gray-900">
|
|
589
|
+
Candidate {candidateIdx + 1}
|
|
590
|
+
{candidate.isPartial && (
|
|
591
|
+
<span className="ml-2 bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-xs">
|
|
592
|
+
Partial
|
|
593
|
+
</span>
|
|
594
|
+
)}
|
|
595
|
+
</h4>
|
|
596
|
+
</div>
|
|
597
|
+
|
|
598
|
+
{/* Candidate JSON Data - This is the actual resource content! */}
|
|
599
|
+
<div className="bg-gray-50 rounded p-3 mb-3">
|
|
600
|
+
<h6 className="text-sm font-semibold text-gray-700 mb-2">Resource Content:</h6>
|
|
601
|
+
<pre className="text-sm font-mono text-gray-800 whitespace-pre-wrap">
|
|
602
|
+
{JSON.stringify(candidate.json, null, 2)}
|
|
603
|
+
</pre>
|
|
604
|
+
</div>
|
|
605
|
+
|
|
606
|
+
{/* Conditions from the condition set */}
|
|
607
|
+
{decisionCandidate?.conditionSet &&
|
|
608
|
+
decisionCandidate.conditionSet.conditions.length > 0 && (
|
|
609
|
+
<div className="border-t pt-3">
|
|
610
|
+
<h5 className="text-sm font-semibold text-gray-700 mb-2">
|
|
611
|
+
Condition Set {decisionCandidate.conditionSet.index}:
|
|
612
|
+
</h5>
|
|
613
|
+
<div className="space-y-2">
|
|
614
|
+
{decisionCandidate.conditionSet.conditions.map((condition: any, idx: number) => (
|
|
615
|
+
<div key={idx} className="flex items-center text-sm bg-blue-50 rounded px-3 py-2">
|
|
616
|
+
<span className="font-mono text-blue-700 mr-2 text-xs">
|
|
617
|
+
{condition.index.toString().padStart(2, '0')}:
|
|
618
|
+
</span>
|
|
619
|
+
<span className="font-medium text-blue-900 mr-2">
|
|
620
|
+
{condition.qualifier.name}
|
|
621
|
+
</span>
|
|
622
|
+
<span className="text-blue-700 mr-2">{condition.operator}</span>
|
|
623
|
+
<span className="font-mono text-blue-800">{condition.value}</span>
|
|
624
|
+
<span className="ml-auto text-xs text-blue-600">
|
|
625
|
+
Priority: {condition.priority}
|
|
626
|
+
{condition.scoreAsDefault !== undefined && (
|
|
627
|
+
<span className="ml-2">Default: {condition.scoreAsDefault}</span>
|
|
628
|
+
)}
|
|
629
|
+
</span>
|
|
630
|
+
</div>
|
|
631
|
+
))}
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
)}
|
|
635
|
+
|
|
636
|
+
{(!decisionCandidate?.conditionSet ||
|
|
637
|
+
decisionCandidate.conditionSet.conditions.length === 0) && (
|
|
638
|
+
<div className="border-t pt-3">
|
|
639
|
+
<span className="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded">
|
|
640
|
+
No conditions (default candidate)
|
|
641
|
+
</span>
|
|
642
|
+
</div>
|
|
643
|
+
)}
|
|
644
|
+
</div>
|
|
645
|
+
);
|
|
646
|
+
})}
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
</div>
|
|
650
|
+
);
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
// New function that uses runtime collections instead of raw JSON
|
|
654
|
+
const renderRuntimeCollection = (collectionType: string, manager: any) => {
|
|
655
|
+
let collector: any;
|
|
656
|
+
let title: string;
|
|
657
|
+
|
|
658
|
+
switch (collectionType) {
|
|
659
|
+
case 'qualifiers':
|
|
660
|
+
collector = manager.qualifiers;
|
|
661
|
+
title = 'Qualifiers';
|
|
662
|
+
break;
|
|
663
|
+
case 'qualifier-types':
|
|
664
|
+
collector = manager.qualifierTypes;
|
|
665
|
+
title = 'Qualifier Types';
|
|
666
|
+
break;
|
|
667
|
+
case 'resource-types':
|
|
668
|
+
collector = manager.resourceTypes;
|
|
669
|
+
title = 'Resource Types';
|
|
670
|
+
break;
|
|
671
|
+
case 'conditions':
|
|
672
|
+
collector = manager.conditions;
|
|
673
|
+
title = 'Conditions';
|
|
674
|
+
break;
|
|
675
|
+
case 'condition-sets':
|
|
676
|
+
collector = manager.conditionSets;
|
|
677
|
+
title = 'Condition Sets';
|
|
678
|
+
break;
|
|
679
|
+
case 'decisions':
|
|
680
|
+
collector = manager.decisions;
|
|
681
|
+
title = 'Decisions';
|
|
682
|
+
break;
|
|
683
|
+
default:
|
|
684
|
+
return (
|
|
685
|
+
<div className="bg-white rounded-lg border p-4">
|
|
686
|
+
<p className="text-sm text-gray-500">Unknown collection type: {collectionType}</p>
|
|
687
|
+
</div>
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const items = Array.from(collector.values());
|
|
692
|
+
|
|
693
|
+
return (
|
|
694
|
+
<div className="bg-gray-50 rounded-lg p-4">
|
|
695
|
+
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
|
696
|
+
{title} <span className="text-sm font-normal text-gray-600">({items.length})</span>
|
|
697
|
+
</h3>
|
|
698
|
+
|
|
699
|
+
{items.length === 0 ? (
|
|
700
|
+
<div className="bg-white rounded-lg border p-4">
|
|
701
|
+
<p className="text-sm text-gray-500">No {title.toLowerCase()} available</p>
|
|
702
|
+
</div>
|
|
703
|
+
) : (
|
|
704
|
+
<div className="space-y-3 max-h-96 overflow-y-auto">
|
|
705
|
+
{items.map((item: any, index: number) => (
|
|
706
|
+
<div key={index} className="bg-white rounded-lg border p-4">
|
|
707
|
+
<div className="flex items-start justify-between mb-3">
|
|
708
|
+
<div>
|
|
709
|
+
<h4 className="font-semibold text-gray-900">
|
|
710
|
+
{getItemDisplayName(item, collectionType, index)}
|
|
711
|
+
</h4>
|
|
712
|
+
<p className="text-sm text-gray-600 font-mono mt-1">
|
|
713
|
+
{getItemDisplayKey(item, collectionType)}
|
|
714
|
+
</p>
|
|
715
|
+
</div>
|
|
716
|
+
{item.index !== undefined && (
|
|
717
|
+
<span className="bg-gray-100 text-gray-700 px-2 py-1 rounded text-xs font-mono">
|
|
718
|
+
Index: {item.index}
|
|
719
|
+
</span>
|
|
720
|
+
)}
|
|
721
|
+
</div>
|
|
722
|
+
|
|
723
|
+
<div className="border-t pt-3">{renderRuntimeItemDetail(item, collectionType)}</div>
|
|
724
|
+
</div>
|
|
725
|
+
))}
|
|
726
|
+
</div>
|
|
727
|
+
)}
|
|
728
|
+
</div>
|
|
729
|
+
);
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// Helper functions for runtime object display
|
|
733
|
+
const getItemDisplayName = (item: any, collectionType: string, index: number): string => {
|
|
734
|
+
switch (collectionType) {
|
|
735
|
+
case 'qualifiers':
|
|
736
|
+
return `${item.index}: ${item.name} (${item.type.name})`;
|
|
737
|
+
case 'qualifier-types':
|
|
738
|
+
return `${item.index}: ${item.name} (${item.systemType})`;
|
|
739
|
+
case 'resource-types':
|
|
740
|
+
return `${item.index}: ${item.name || item.key}`;
|
|
741
|
+
case 'conditions':
|
|
742
|
+
return `${item.index.toString().padStart(2, '0')}: ${item.qualifier.name} ${item.operator} ${
|
|
743
|
+
item.value
|
|
744
|
+
}`;
|
|
745
|
+
case 'condition-sets':
|
|
746
|
+
return `${item.index}: ${
|
|
747
|
+
item.conditions.length === 0 ? 'Unconditional' : `${item.conditions.length} conditions`
|
|
748
|
+
}`;
|
|
749
|
+
case 'decisions':
|
|
750
|
+
return `${item.baseDecision?.index ?? item.index}: Decision with ${
|
|
751
|
+
item.candidates.length
|
|
752
|
+
} candidates`;
|
|
753
|
+
default:
|
|
754
|
+
return `${index}: Item ${index}`;
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const getItemDisplayKey = (item: any, collectionType: string): string => {
|
|
759
|
+
try {
|
|
760
|
+
switch (collectionType) {
|
|
761
|
+
case 'qualifiers':
|
|
762
|
+
return `defaultPriority: ${item?.defaultPriority ?? 'N/A'}`;
|
|
763
|
+
case 'qualifier-types':
|
|
764
|
+
return `allowContextList: ${item?.allowContextList ?? 'N/A'}`;
|
|
765
|
+
case 'resource-types':
|
|
766
|
+
return `key: ${item?.key ?? 'N/A'}`;
|
|
767
|
+
case 'conditions':
|
|
768
|
+
return `priority: ${item?.priority ?? 'N/A'}`;
|
|
769
|
+
case 'condition-sets':
|
|
770
|
+
return (
|
|
771
|
+
item?.conditions
|
|
772
|
+
?.map((c: any) => `${c?.qualifier?.name ?? 'unknown'}=${c?.value ?? 'unknown'}`)
|
|
773
|
+
.join(', ') || 'default'
|
|
774
|
+
);
|
|
775
|
+
case 'decisions':
|
|
776
|
+
return `${item?.conditionSets?.length ?? 0} condition sets`;
|
|
777
|
+
default:
|
|
778
|
+
return '';
|
|
779
|
+
}
|
|
780
|
+
} catch (error) {
|
|
781
|
+
console.warn('Error in getItemDisplayKey:', error, { item, collectionType });
|
|
782
|
+
return 'Display error';
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
const renderRuntimeItemDetail = (item: any, collectionType: string) => {
|
|
787
|
+
switch (collectionType) {
|
|
788
|
+
case 'qualifiers':
|
|
789
|
+
return (
|
|
790
|
+
<div className="space-y-2">
|
|
791
|
+
<div>
|
|
792
|
+
<span className="font-medium">Type:</span> {item.type.name}
|
|
793
|
+
</div>
|
|
794
|
+
<div>
|
|
795
|
+
<span className="font-medium">Default Priority:</span> {item.defaultPriority}
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
);
|
|
799
|
+
case 'qualifier-types':
|
|
800
|
+
return (
|
|
801
|
+
<div className="space-y-2">
|
|
802
|
+
<div>
|
|
803
|
+
<span className="font-medium">System Type:</span> {item.systemType}
|
|
804
|
+
</div>
|
|
805
|
+
<div>
|
|
806
|
+
<span className="font-medium">Allow Context List:</span> {item.allowContextList ? 'Yes' : 'No'}
|
|
807
|
+
</div>
|
|
808
|
+
{item.enumeratedValues && (
|
|
809
|
+
<div>
|
|
810
|
+
<span className="font-medium">Enumerated Values:</span> {item.enumeratedValues.join(', ')}
|
|
811
|
+
</div>
|
|
812
|
+
)}
|
|
813
|
+
</div>
|
|
814
|
+
);
|
|
815
|
+
case 'resource-types':
|
|
816
|
+
return (
|
|
817
|
+
<div className="space-y-2">
|
|
818
|
+
<div>
|
|
819
|
+
<span className="font-medium">Key:</span> {item.key}
|
|
820
|
+
</div>
|
|
821
|
+
<div>
|
|
822
|
+
<span className="font-medium">Name:</span> {item.name}
|
|
823
|
+
</div>
|
|
824
|
+
</div>
|
|
825
|
+
);
|
|
826
|
+
case 'conditions':
|
|
827
|
+
return (
|
|
828
|
+
<div className="space-y-2">
|
|
829
|
+
<div>
|
|
830
|
+
<span className="font-medium">Qualifier:</span> {item.qualifier.name}
|
|
831
|
+
</div>
|
|
832
|
+
<div>
|
|
833
|
+
<span className="font-medium">Operator:</span> {item.operator}
|
|
834
|
+
</div>
|
|
835
|
+
<div>
|
|
836
|
+
<span className="font-medium">Value:</span> {item.value}
|
|
837
|
+
</div>
|
|
838
|
+
<div>
|
|
839
|
+
<span className="font-medium">Priority:</span> {item.priority}
|
|
840
|
+
</div>
|
|
841
|
+
{item.scoreAsDefault !== undefined && (
|
|
842
|
+
<div>
|
|
843
|
+
<span className="font-medium">Score As Default:</span> {item.scoreAsDefault}
|
|
844
|
+
</div>
|
|
845
|
+
)}
|
|
846
|
+
</div>
|
|
847
|
+
);
|
|
848
|
+
case 'condition-sets':
|
|
849
|
+
return (
|
|
850
|
+
<div className="space-y-2">
|
|
851
|
+
<div>
|
|
852
|
+
<span className="font-medium">Conditions:</span> {item.conditions?.length ?? 0}
|
|
853
|
+
</div>
|
|
854
|
+
{(item.conditions?.length ?? 0) > 0 && (
|
|
855
|
+
<div className="space-y-1">
|
|
856
|
+
{item.conditions?.map((condition: any, idx: number) => (
|
|
857
|
+
<div key={idx} className="text-xs bg-blue-50 rounded px-2 py-1">
|
|
858
|
+
{condition.qualifier.name} {condition.operator} {condition.value} (p:{condition.priority})
|
|
859
|
+
</div>
|
|
860
|
+
))}
|
|
861
|
+
</div>
|
|
862
|
+
)}
|
|
863
|
+
</div>
|
|
864
|
+
);
|
|
865
|
+
case 'decisions':
|
|
866
|
+
return (
|
|
867
|
+
<div className="space-y-2">
|
|
868
|
+
<div>
|
|
869
|
+
<span className="font-medium">Condition Sets:</span> {item.conditionSets?.length ?? 0}
|
|
870
|
+
</div>
|
|
871
|
+
<div>
|
|
872
|
+
<span className="font-medium">Candidates:</span> {item.candidates?.length ?? 0}
|
|
873
|
+
</div>
|
|
874
|
+
</div>
|
|
875
|
+
);
|
|
876
|
+
default:
|
|
877
|
+
return (
|
|
878
|
+
<pre className="text-xs bg-gray-50 p-2 rounded overflow-x-auto">
|
|
879
|
+
{JSON.stringify(item, null, 2)}
|
|
880
|
+
</pre>
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
const renderRawData = () => {
|
|
886
|
+
return (
|
|
887
|
+
<div className="bg-white rounded-lg border p-4">
|
|
888
|
+
<h4 className="font-medium text-gray-700 mb-2">{node.name}</h4>
|
|
889
|
+
<pre className="text-xs bg-gray-50 p-2 rounded overflow-x-auto max-h-96 overflow-y-auto">
|
|
890
|
+
{JSON.stringify(node.data, null, 2)}
|
|
891
|
+
</pre>
|
|
892
|
+
</div>
|
|
893
|
+
);
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
return (
|
|
897
|
+
<div className="flex flex-col h-full">
|
|
898
|
+
<div className="flex items-center justify-between mb-4">
|
|
899
|
+
<h3 className="text-lg font-semibold text-gray-900">Details</h3>
|
|
900
|
+
<button
|
|
901
|
+
onClick={() => setShowRawJson(!showRawJson)}
|
|
902
|
+
className="text-xs text-gray-500 hover:text-gray-700 px-2 py-1 rounded hover:bg-gray-100"
|
|
903
|
+
>
|
|
904
|
+
{showRawJson ? 'Rich View' : 'Raw JSON'}
|
|
905
|
+
</button>
|
|
906
|
+
</div>
|
|
907
|
+
<div className="flex-1 overflow-y-auto">
|
|
908
|
+
{showRawJson ? (
|
|
909
|
+
<div className="bg-white rounded-lg border p-4">
|
|
910
|
+
<pre className="text-xs bg-gray-50 p-3 rounded overflow-x-auto">
|
|
911
|
+
{JSON.stringify(node.data, null, 2)}
|
|
912
|
+
</pre>
|
|
913
|
+
</div>
|
|
914
|
+
) : (
|
|
915
|
+
renderDetails()
|
|
916
|
+
)}
|
|
917
|
+
</div>
|
|
918
|
+
</div>
|
|
919
|
+
);
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
export default CompiledView;
|