@fgv/ts-res-browser 1.0.0
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/5fc90bc7c2ccf812114ea099111568e5429be2a2.tar.log +15 -0
- package/.rush/temp/chunked-rush-logs/ts-res-browser.build.chunks.jsonl +35 -0
- package/.rush/temp/operation/build/all.log +35 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +35 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +1217 -0
- package/README.md +95 -0
- package/TS_RES_BROWSER_PLAN.md +689 -0
- package/babel.config.js +17 -0
- package/config/jest.config.json +13 -0
- package/config/rig.json +16 -0
- package/dist/bundle.js +2 -0
- package/dist/bundle.js.LICENSE.txt +62 -0
- package/dist/index.html +1 -0
- package/package.json +78 -0
- package/postcss.config.js +3 -0
- package/public/index.html +15 -0
- package/rush-logs/ts-res-browser.build.cache.log +3 -0
- package/rush-logs/ts-res-browser.build.log +35 -0
- package/src/App.tsx +153 -0
- package/src/cli.ts +44 -0
- package/src/components/common/FileImporter.tsx +138 -0
- package/src/components/common/NavigationWarningModal.tsx +62 -0
- package/src/components/layout/AppLayout.tsx +46 -0
- package/src/components/layout/Header.tsx +22 -0
- package/src/components/layout/MessagesWindow.tsx +108 -0
- package/src/components/layout/Sidebar.tsx +92 -0
- package/src/components/tools/CompiledBrowser.tsx +949 -0
- package/src/components/tools/ConfigurationTool.tsx +1402 -0
- package/src/components/tools/FilterTool.tsx +1027 -0
- package/src/components/tools/ImportTool.tsx +246 -0
- package/src/components/tools/ResolutionViewer.tsx +1672 -0
- package/src/components/tools/SourceBrowser.tsx +496 -0
- package/src/hooks/useAppState.ts +107 -0
- package/src/hooks/useFileImport.ts +86 -0
- package/src/hooks/useNavigationWarning.ts +67 -0
- package/src/hooks/useResourceManager.ts +244 -0
- package/src/index.css +38 -0
- package/src/main.tsx +16 -0
- package/src/types/app.ts +62 -0
- package/src/types/fileSystemAccess.d.ts +94 -0
- package/src/utils/browserFileTreeAccessors.ts +245 -0
- package/src/utils/fileImport.ts +334 -0
- package/src/utils/filterResources.ts +677 -0
- package/src/utils/tsResIntegration.ts +467 -0
- package/tailwind.config.js +21 -0
- package/test-data/README.md +184 -0
- package/test-data/config-variations/README.md +64 -0
- package/test-data/config-variations/composition-terminology.json +112 -0
- package/test-data/config-variations/enterprise-complex.json +129 -0
- package/test-data/config-variations/gaming-app.json +87 -0
- package/test-data/config-variations/high-priority-language.json +65 -0
- package/test-data/config-variations/minimal-basic.json +37 -0
- package/test-data/config-variations/qualifier-default-values.json +71 -0
- package/test-data/config-variations/territory-first.json +68 -0
- package/test-data/config-variations/territory-hierarchy-test.json +31 -0
- package/test-data/config-variations/territory-hierarchy-unconstrained.json +32 -0
- package/test-data/default-matching-test/greeting.res.json +83 -0
- package/test-data/default-matching-test/only-defaults.res.json +48 -0
- package/test-data/sample-project/package.json +27 -0
- package/test-data/sample-project/src/resources/app-config.json +27 -0
- package/test-data/sample-project/src/resources/app.json +177 -0
- package/test-data/sample-project/src/resources/config/app.env=development.json +21 -0
- package/test-data/sample-project/src/resources/config/app.json +21 -0
- package/test-data/sample-project/src/resources/config/app.platform=mobile.json +23 -0
- package/test-data/sample-project/src/resources/feature-flags.json +23 -0
- package/test-data/sample-project/src/resources/home=CA/common.json +16 -0
- package/test-data/sample-project/src/resources/images/density=hdpi.json +29 -0
- package/test-data/sample-project/src/resources/images/home.json +29 -0
- package/test-data/sample-project/src/resources/images/logout.json +14 -0
- package/test-data/sample-project/src/resources/images/settings.json +14 -0
- package/test-data/sample-project/src/resources/images/ui-icons.json +29 -0
- package/test-data/sample-project/src/resources/images/user.density=hdpi.json +14 -0
- package/test-data/sample-project/src/resources/images/user.json +14 -0
- package/test-data/sample-project/src/resources/lang=en-US/common.json +15 -0
- package/test-data/sample-project/src/resources/language=en-CA/ui-terms.json +19 -0
- package/test-data/sample-project/src/resources/language=en-GB/ui-terms.json +30 -0
- package/test-data/sample-project/src/resources/language=fr-CA/ui-terms.json +25 -0
- package/test-data/sample-project/src/resources/language=nl-BE/ui-terms.json +17 -0
- package/test-data/sample-project/src/resources/region=europe/feature-flags.json +17 -0
- package/test-data/sample-project/src/resources/region=north-america/feature-flags.json +18 -0
- package/test-data/sample-project/src/resources/strings/dashboard.home=CA,language=fr.json +20 -0
- package/test-data/sample-project/src/resources/strings/dashboard.json +18 -0
- package/test-data/sample-project/src/resources/strings/dashboard.role=admin.json +23 -0
- package/test-data/sample-project/src/resources/strings/dashboard.role=user.json +21 -0
- package/test-data/sample-project/src/resources/strings/language=en-US.json +16 -0
- package/test-data/sample-project/src/resources/strings.json +78 -0
- package/test-data/sample-project/src/resources/territory=BE/app-config.json +19 -0
- package/test-data/sample-project/src/resources/territory=CA/app-config.json +21 -0
- package/test-data/sample-project/src/resources/territory=GB/app-config.json +25 -0
- package/test-data/sample-project/src/resources/territory=NL/app-config.json +25 -0
- package/test-data/sample-project/src/resources/ui-terms.json +96 -0
- package/test-data/sample-project/src/resources-config.json +111 -0
- package/tsconfig.json +27 -0
- package/webpack.config.js +54 -0
|
@@ -0,0 +1,949 @@
|
|
|
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;
|