@fgv/ts-res-ui-components 5.0.0-21 → 5.0.0-23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/README.md +401 -155
  2. package/config/jest.setup.js +10 -0
  3. package/dist/ts-res-ui-components.d.ts +1657 -76
  4. package/lib/components/common/QualifierContextControl.js +4 -1
  5. package/lib/components/common/ResourceTreeView.js +4 -1
  6. package/lib/components/forms/GenericQualifierTypeEditForm.d.ts +26 -0
  7. package/lib/components/forms/GenericQualifierTypeEditForm.js +166 -0
  8. package/lib/components/forms/QualifierEditForm.d.ts +1 -1
  9. package/lib/components/forms/index.d.ts +2 -0
  10. package/lib/components/forms/index.js +1 -0
  11. package/lib/components/orchestrator/ResourceOrchestrator.d.ts +3 -0
  12. package/lib/components/orchestrator/ResourceOrchestrator.js +118 -51
  13. package/lib/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
  14. package/lib/components/pickers/ResourcePicker/index.js +4 -2
  15. package/lib/components/views/CompiledView/index.js +75 -16
  16. package/lib/components/views/ConfigurationView/index.js +94 -35
  17. package/lib/components/views/FilterView/index.js +7 -4
  18. package/lib/components/views/GridView/EditableGridCell.d.ts +76 -0
  19. package/lib/components/views/GridView/EditableGridCell.js +224 -0
  20. package/lib/components/views/GridView/GridSelector.d.ts +43 -0
  21. package/lib/components/views/GridView/GridSelector.js +89 -0
  22. package/lib/components/views/GridView/MultiGridView.d.ts +85 -0
  23. package/lib/components/views/GridView/MultiGridView.js +196 -0
  24. package/lib/components/views/GridView/ResourceGrid.d.ts +38 -0
  25. package/lib/components/views/GridView/ResourceGrid.js +232 -0
  26. package/lib/components/views/GridView/SharedContextControls.d.ts +47 -0
  27. package/lib/components/views/GridView/SharedContextControls.js +95 -0
  28. package/lib/components/views/GridView/cells/BooleanCell.d.ts +44 -0
  29. package/lib/components/views/GridView/cells/BooleanCell.js +49 -0
  30. package/lib/components/views/GridView/cells/DropdownCell.d.ts +58 -0
  31. package/lib/components/views/GridView/cells/DropdownCell.js +182 -0
  32. package/lib/components/views/GridView/cells/StringCell.d.ts +57 -0
  33. package/lib/components/views/GridView/cells/StringCell.js +106 -0
  34. package/lib/components/views/GridView/cells/TriStateCell.d.ts +54 -0
  35. package/lib/components/views/GridView/cells/TriStateCell.js +112 -0
  36. package/lib/components/views/GridView/cells/index.d.ts +15 -0
  37. package/lib/components/views/GridView/cells/index.js +11 -0
  38. package/lib/components/views/GridView/index.d.ts +53 -0
  39. package/lib/components/views/GridView/index.js +212 -0
  40. package/lib/components/views/ImportView/index.js +22 -19
  41. package/lib/components/views/MessagesWindow/index.js +4 -1
  42. package/lib/components/views/ResolutionView/index.js +8 -5
  43. package/lib/contexts/ObservabilityContext.d.ts +85 -0
  44. package/lib/contexts/ObservabilityContext.js +98 -0
  45. package/lib/contexts/index.d.ts +2 -0
  46. package/lib/contexts/index.js +24 -0
  47. package/lib/hooks/useConfigurationState.d.ts +3 -3
  48. package/lib/hooks/useResolutionState.js +850 -246
  49. package/lib/hooks/useResourceData.d.ts +7 -4
  50. package/lib/hooks/useResourceData.js +185 -184
  51. package/lib/index.d.ts +5 -1
  52. package/lib/index.js +8 -1
  53. package/lib/namespaces/GridTools.d.ts +136 -0
  54. package/lib/namespaces/GridTools.js +138 -0
  55. package/lib/namespaces/ObservabilityTools.d.ts +3 -0
  56. package/lib/namespaces/ObservabilityTools.js +23 -0
  57. package/lib/namespaces/ResolutionTools.d.ts +2 -1
  58. package/lib/namespaces/ResolutionTools.js +2 -0
  59. package/lib/namespaces/index.d.ts +2 -0
  60. package/lib/namespaces/index.js +2 -0
  61. package/lib/test/integration/observability.integration.test.d.ts +2 -0
  62. package/lib/test/unit/hooks/useResourceData.test.d.ts +2 -0
  63. package/lib/test/unit/utils/downloadHelper.test.d.ts +2 -0
  64. package/lib/test/unit/workflows/resolutionWorkflows.test.d.ts +2 -0
  65. package/lib/test/unit/workflows/resourceCreation.test.d.ts +2 -0
  66. package/lib/test/unit/workflows/resultPatternExtensions.test.d.ts +2 -0
  67. package/lib/test/unit/workflows/validation.test.d.ts +2 -0
  68. package/lib/types/index.d.ts +387 -20
  69. package/lib/types/index.js +2 -1
  70. package/lib/utils/cellValidation.d.ts +113 -0
  71. package/lib/utils/cellValidation.js +248 -0
  72. package/lib/utils/downloadHelper.d.ts +66 -0
  73. package/lib/utils/downloadHelper.js +195 -0
  74. package/lib/utils/observability/factories.d.ts +29 -0
  75. package/lib/utils/observability/factories.js +58 -0
  76. package/lib/utils/observability/implementations.d.ts +61 -0
  77. package/lib/utils/observability/implementations.js +103 -0
  78. package/lib/utils/observability/index.d.ts +4 -0
  79. package/lib/utils/observability/index.js +26 -0
  80. package/lib/utils/observability/interfaces.d.ts +30 -0
  81. package/lib/utils/observability/interfaces.js +23 -0
  82. package/lib/utils/resolutionEditing.js +2 -1
  83. package/lib/utils/resourceSelector.d.ts +97 -0
  84. package/lib/utils/resourceSelector.js +195 -0
  85. package/lib/utils/resourceSelectors.d.ts +146 -0
  86. package/lib/utils/resourceSelectors.js +233 -0
  87. package/lib/utils/tsResIntegration.d.ts +6 -41
  88. package/lib/utils/tsResIntegration.js +20 -16
  89. package/lib/utils/zipLoader/zipProcessingHelpers.d.ts +3 -2
  90. package/lib/utils/zipLoader/zipProcessingHelpers.js +6 -5
  91. package/lib-commonjs/components/common/QualifierContextControl.js +4 -1
  92. package/lib-commonjs/components/common/ResourceTreeView.js +4 -1
  93. package/lib-commonjs/components/forms/GenericQualifierTypeEditForm.js +171 -0
  94. package/lib-commonjs/components/forms/index.js +3 -1
  95. package/lib-commonjs/components/orchestrator/ResourceOrchestrator.js +118 -51
  96. package/lib-commonjs/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
  97. package/lib-commonjs/components/pickers/ResourcePicker/index.js +4 -2
  98. package/lib-commonjs/components/views/CompiledView/index.js +75 -16
  99. package/lib-commonjs/components/views/ConfigurationView/index.js +93 -34
  100. package/lib-commonjs/components/views/FilterView/index.js +7 -4
  101. package/lib-commonjs/components/views/GridView/EditableGridCell.js +232 -0
  102. package/lib-commonjs/components/views/GridView/GridSelector.js +94 -0
  103. package/lib-commonjs/components/views/GridView/MultiGridView.js +201 -0
  104. package/lib-commonjs/components/views/GridView/ResourceGrid.js +237 -0
  105. package/lib-commonjs/components/views/GridView/SharedContextControls.js +100 -0
  106. package/lib-commonjs/components/views/GridView/cells/BooleanCell.js +54 -0
  107. package/lib-commonjs/components/views/GridView/cells/DropdownCell.js +187 -0
  108. package/lib-commonjs/components/views/GridView/cells/StringCell.js +111 -0
  109. package/lib-commonjs/components/views/GridView/cells/TriStateCell.js +117 -0
  110. package/lib-commonjs/components/views/GridView/cells/index.js +18 -0
  111. package/lib-commonjs/components/views/GridView/index.js +217 -0
  112. package/lib-commonjs/components/views/ImportView/index.js +22 -19
  113. package/lib-commonjs/components/views/MessagesWindow/index.js +4 -1
  114. package/lib-commonjs/components/views/ResolutionView/index.js +8 -5
  115. package/lib-commonjs/contexts/ObservabilityContext.js +104 -0
  116. package/lib-commonjs/contexts/index.js +30 -0
  117. package/lib-commonjs/hooks/useResolutionState.js +849 -245
  118. package/lib-commonjs/hooks/useResourceData.js +184 -215
  119. package/lib-commonjs/index.js +15 -1
  120. package/lib-commonjs/namespaces/GridTools.js +161 -0
  121. package/lib-commonjs/namespaces/ObservabilityTools.js +33 -0
  122. package/lib-commonjs/namespaces/ResolutionTools.js +10 -1
  123. package/lib-commonjs/namespaces/index.js +3 -1
  124. package/lib-commonjs/types/index.js +10 -0
  125. package/lib-commonjs/utils/cellValidation.js +253 -0
  126. package/lib-commonjs/utils/downloadHelper.js +198 -0
  127. package/lib-commonjs/utils/observability/factories.js +63 -0
  128. package/lib-commonjs/utils/observability/implementations.js +109 -0
  129. package/lib-commonjs/utils/observability/index.js +36 -0
  130. package/lib-commonjs/utils/observability/interfaces.js +24 -0
  131. package/lib-commonjs/utils/resolutionEditing.js +2 -1
  132. package/lib-commonjs/utils/resourceSelector.js +200 -0
  133. package/lib-commonjs/utils/resourceSelectors.js +242 -0
  134. package/lib-commonjs/utils/tsResIntegration.js +21 -16
  135. package/lib-commonjs/utils/zipLoader/zipProcessingHelpers.js +7 -5
  136. package/package.json +7 -7
  137. package/src/components/common/QualifierContextControl.tsx +0 -338
  138. package/src/components/common/ResolutionContextOptionsControl.tsx +0 -450
  139. package/src/components/common/ResolutionResults/index.tsx +0 -481
  140. package/src/components/common/ResourceListView.tsx +0 -167
  141. package/src/components/common/ResourcePickerOptionsControl.tsx +0 -351
  142. package/src/components/common/ResourceTreeView.tsx +0 -417
  143. package/src/components/common/SourceResourceDetail/index.tsx +0 -493
  144. package/src/components/forms/HierarchyEditor.tsx +0 -285
  145. package/src/components/forms/QualifierEditForm.tsx +0 -487
  146. package/src/components/forms/QualifierTypeEditForm.tsx +0 -458
  147. package/src/components/forms/ResourceTypeEditForm.tsx +0 -437
  148. package/src/components/forms/index.ts +0 -11
  149. package/src/components/orchestrator/ResourceOrchestrator.tsx +0 -444
  150. package/src/components/pickers/ResourcePicker/README.md +0 -570
  151. package/src/components/pickers/ResourcePicker/ResourceItem.tsx +0 -127
  152. package/src/components/pickers/ResourcePicker/ResourcePickerList.tsx +0 -114
  153. package/src/components/pickers/ResourcePicker/ResourcePickerTree.tsx +0 -461
  154. package/src/components/pickers/ResourcePicker/index.tsx +0 -234
  155. package/src/components/pickers/ResourcePicker/types.ts +0 -301
  156. package/src/components/pickers/ResourcePicker/utils/treeNavigation.ts +0 -210
  157. package/src/components/views/CompiledView/index.tsx +0 -1342
  158. package/src/components/views/ConfigurationView/index.tsx +0 -848
  159. package/src/components/views/FilterView/index.tsx +0 -681
  160. package/src/components/views/ImportView/index.tsx +0 -789
  161. package/src/components/views/MessagesWindow/index.tsx +0 -325
  162. package/src/components/views/ResolutionView/EditableJsonView.tsx +0 -386
  163. package/src/components/views/ResolutionView/NewResourceModal.tsx +0 -158
  164. package/src/components/views/ResolutionView/UnifiedChangeControls.tsx +0 -163
  165. package/src/components/views/ResolutionView/index.tsx +0 -751
  166. package/src/components/views/SourceView/index.tsx +0 -291
  167. package/src/hooks/useConfigurationState.ts +0 -436
  168. package/src/hooks/useFilterState.ts +0 -150
  169. package/src/hooks/useResolutionState.ts +0 -893
  170. package/src/hooks/useResourceData.ts +0 -596
  171. package/src/hooks/useViewState.ts +0 -97
  172. package/src/index.ts +0 -68
  173. package/src/namespaces/ConfigurationTools.ts +0 -59
  174. package/src/namespaces/FilterTools.ts +0 -47
  175. package/src/namespaces/ImportTools.ts +0 -42
  176. package/src/namespaces/PickerTools.ts +0 -104
  177. package/src/namespaces/ResolutionTools.ts +0 -68
  178. package/src/namespaces/ResourceTools.ts +0 -106
  179. package/src/namespaces/TsResTools.ts +0 -49
  180. package/src/namespaces/ViewStateTools.ts +0 -91
  181. package/src/namespaces/ZipTools.ts +0 -49
  182. package/src/namespaces/index.ts +0 -49
  183. package/src/types/index.ts +0 -1273
  184. package/src/utils/configurationUtils.ts +0 -339
  185. package/src/utils/fileProcessing.ts +0 -164
  186. package/src/utils/filterResources.ts +0 -356
  187. package/src/utils/resolutionEditing.ts +0 -346
  188. package/src/utils/resolutionUtils.ts +0 -740
  189. package/src/utils/tsResIntegration.ts +0 -475
  190. package/src/utils/zipLoader/index.ts +0 -5
  191. package/src/utils/zipLoader/zipProcessingHelpers.ts +0 -46
  192. package/src/utils/zipLoader/zipUtils.ts +0 -7
@@ -1,751 +0,0 @@
1
- import React, { useState, useMemo, useCallback, useEffect, useRef } from 'react';
2
- import {
3
- MagnifyingGlassIcon,
4
- DocumentTextIcon,
5
- CubeIcon,
6
- CheckIcon,
7
- XMarkIcon,
8
- PencilIcon,
9
- TrashIcon,
10
- ListBulletIcon,
11
- FolderIcon,
12
- PlusIcon
13
- } from '@heroicons/react/24/outline';
14
- import {
15
- ResolutionViewProps,
16
- CandidateInfo,
17
- ResolutionActions,
18
- ResolutionState,
19
- ResourceEditorFactory,
20
- ResourceEditorResult,
21
- ResolutionContextOptions
22
- } from '../../../types';
23
- import { ResourceId } from '@fgv/ts-res';
24
- import { QualifierContextControl } from '../../common/QualifierContextControl';
25
- import { UnifiedChangeControls } from './UnifiedChangeControls';
26
- import { ResourcePicker } from '../../pickers/ResourcePicker';
27
- import {
28
- ResourceSelection,
29
- ResourceAnnotations,
30
- ResourcePickerOptions,
31
- PendingResource
32
- } from '../../pickers/ResourcePicker/types';
33
- import { ResourcePickerOptionsControl } from '../../common/ResourcePickerOptionsControl';
34
- import { ResolutionContextOptionsControl } from '../../common/ResolutionContextOptionsControl';
35
- import { ResolutionResults } from '../../common/ResolutionResults';
36
- import { NewResourceModal } from './NewResourceModal';
37
-
38
- /**
39
- * ResolutionView component for resource resolution testing and editing.
40
- *
41
- * Provides a comprehensive interface for testing resource resolution with different
42
- * qualifier contexts, viewing resolution results, and editing resource values with
43
- * custom editors. Supports real-time resolution testing and conflict detection.
44
- *
45
- * **Key Features:**
46
- * - **Context management**: Set and update resolution context (qualifier values)
47
- * - **Real-time resolution**: See how resources resolve with current context
48
- * - **Resource editing**: Edit resource values with custom type-specific editors
49
- * - **Conflict detection**: Detect when edits would conflict with existing resources
50
- * - **Preview mode**: See how edits affect resolution without committing changes
51
- * - **Custom editors**: Support for type-specific resource editors via factory pattern
52
- * - **Fallback editing**: JSON editor fallback when custom editors aren't available
53
- *
54
- * @example
55
- * ```tsx
56
- * import { ResolutionView } from '@fgv/ts-res-ui-components';
57
- *
58
- * // Custom editor factory for specific resource types
59
- * const editorFactory = {
60
- * createEditor: (resourceId, resourceType, value) => {
61
- * if (resourceType === 'market-info') {
62
- * return {
63
- * success: true,
64
- * editor: MarketInfoEditor
65
- * };
66
- * }
67
- * return { success: false };
68
- * }
69
- * };
70
- *
71
- * function MyResolutionTool() {
72
- * return (
73
- * <ResolutionView
74
- * resources={processedResources}
75
- * resolutionState={resolutionState}
76
- * resolutionActions={resolutionActions}
77
- * availableQualifiers={['language', 'territory', 'platform']}
78
- * resourceEditorFactory={editorFactory}
79
- * onMessage={(type, message) => console.log(`${type}: ${message}`)}
80
- * />
81
- * );
82
- * }
83
- * ```
84
- *
85
- * @public
86
- */
87
- export const ResolutionView: React.FC<ResolutionViewProps> = ({
88
- resources,
89
- filterState,
90
- filterResult,
91
- resolutionState,
92
- resolutionActions,
93
- availableQualifiers = [],
94
- resourceEditorFactory,
95
- onMessage,
96
- pickerOptions,
97
- pickerOptionsPresentation = 'hidden',
98
- contextOptions,
99
- lockedViewMode,
100
- sectionTitles,
101
- allowResourceCreation = false,
102
- defaultResourceType,
103
- resourceTypeFactory,
104
- onPendingResourcesApplied,
105
- showPendingResourcesInList = true,
106
- className = ''
107
- }) => {
108
- // State for picker options control
109
- const [currentPickerOptions, setCurrentPickerOptions] = useState<ResourcePickerOptions>(
110
- pickerOptions || {}
111
- );
112
-
113
- // State for context options control
114
- const [currentContextOptions, setCurrentContextOptions] = useState<ResolutionContextOptions>(
115
- contextOptions || {}
116
- );
117
-
118
- // State for new resource modal
119
- const [showNewResourceModal, setShowNewResourceModal] = useState(false);
120
-
121
- // Local toggles for editing/creation features (controllable via options dialog)
122
- const [allowResourceCreationInternal, setAllowResourceCreationInternal] = useState<boolean>(
123
- !!allowResourceCreation
124
- );
125
- const [showPendingResourcesInListInternal, setShowPendingResourcesInListInternal] = useState<boolean>(
126
- !!showPendingResourcesInList
127
- );
128
-
129
- // Sync internal toggles when props change
130
- useEffect(() => {
131
- setAllowResourceCreationInternal(!!allowResourceCreation);
132
- }, [allowResourceCreation]);
133
- useEffect(() => {
134
- setShowPendingResourcesInListInternal(!!showPendingResourcesInList);
135
- }, [showPendingResourcesInList]);
136
-
137
- // Update currentContextOptions when contextOptions prop changes
138
- // This is important for host-managed values
139
- React.useEffect(() => {
140
- if (contextOptions?.hostManagedValues) {
141
- setCurrentContextOptions((prev) => ({
142
- ...prev,
143
- hostManagedValues: contextOptions.hostManagedValues
144
- }));
145
- }
146
- }, [contextOptions?.hostManagedValues]);
147
-
148
- // Use filtered resources when filtering is active and successful
149
- const isFilteringActive = filterState?.enabled && filterResult?.success === true;
150
- const baseProcessedResources = isFilteringActive ? filterResult?.processedResources : resources;
151
-
152
- // For now, just use the base processed resources directly
153
- // TODO: Implement merging of pending resources for display
154
- const activeProcessedResources = baseProcessedResources;
155
-
156
- // Merge picker options with resolution-specific defaults
157
- const effectivePickerOptions = useMemo(
158
- () => ({
159
- defaultView: 'list' as const,
160
- showViewToggle: true,
161
- enableSearch: true,
162
- searchPlaceholder: 'Search resources for resolution testing...',
163
- searchScope: 'all' as const,
164
- height: '520px',
165
- emptyMessage: 'No resources available for resolution testing',
166
- // Override with user-provided options
167
- ...pickerOptions,
168
- // Override with current picker options from control
169
- ...currentPickerOptions
170
- }),
171
- [pickerOptions, currentPickerOptions]
172
- );
173
-
174
- // Create resource annotations for resolution results and edit states
175
- const resourceAnnotations = useMemo(() => {
176
- const annotations: ResourceAnnotations = {};
177
-
178
- // Get all resource IDs (existing + pending)
179
- const allResourceIds = new Set<string>();
180
-
181
- if (activeProcessedResources?.summary?.resourceIds) {
182
- activeProcessedResources.summary.resourceIds.forEach((id) => allResourceIds.add(id));
183
- }
184
-
185
- // Add pending resource IDs
186
- if (resolutionState?.pendingResources) {
187
- resolutionState.pendingResources.forEach((_, id) => allResourceIds.add(id));
188
- }
189
-
190
- allResourceIds.forEach((resourceId) => {
191
- const hasEdit = resolutionActions?.hasEdit?.(resourceId);
192
- const isPending = resolutionState?.pendingResources?.has(resourceId);
193
- const isMarkedForDeletion = resolutionState?.pendingResourceDeletions?.has(resourceId);
194
- const isSelected = resolutionState?.selectedResourceId === resourceId;
195
- const hasResolutionResult = isSelected && resolutionState?.resolutionResult;
196
-
197
- // Base annotation with appropriate indicator
198
- let indicator = undefined;
199
- if (isPending) {
200
- indicator = {
201
- type: 'icon' as const,
202
- value: '➕',
203
- tooltip: 'New resource (pending)'
204
- };
205
- } else if (isMarkedForDeletion) {
206
- indicator = {
207
- type: 'icon' as const,
208
- value: '🗑️',
209
- tooltip: 'Marked for deletion'
210
- };
211
- } else if (hasEdit) {
212
- indicator = {
213
- type: 'icon' as const,
214
- value: '✏️',
215
- tooltip: 'Resource has unsaved edits'
216
- };
217
- }
218
-
219
- annotations[resourceId] = {
220
- indicator
221
- };
222
-
223
- // Add resolution result annotations for selected resource
224
- if (hasResolutionResult && resolutionState?.resolutionResult?.success) {
225
- const result = resolutionState.resolutionResult;
226
-
227
- // Show match status as badge
228
- if (result.bestCandidate) {
229
- annotations[resourceId].badge = {
230
- text: 'Resolved',
231
- variant: 'info'
232
- };
233
- } else if (result.candidateDetails) {
234
- const matchingCount = result.candidateDetails.filter((c: CandidateInfo) => c.matched).length;
235
- const totalCount = result.candidateDetails.length;
236
-
237
- if (matchingCount === 0) {
238
- annotations[resourceId].badge = {
239
- text: 'No Match',
240
- variant: 'error'
241
- };
242
- } else {
243
- annotations[resourceId].badge = {
244
- text: `${matchingCount}/${totalCount}`,
245
- variant: 'warning'
246
- };
247
- }
248
- }
249
-
250
- // Add suffix with candidate count
251
- if (result.resource) {
252
- const totalCandidates = result.resource.candidates.length;
253
- annotations[resourceId].suffix = `${totalCandidates} candidate${totalCandidates !== 1 ? 's' : ''}`;
254
- }
255
- } else if (
256
- isSelected &&
257
- resolutionState?.resolutionResult &&
258
- !resolutionState.resolutionResult.success
259
- ) {
260
- // Show error state
261
- annotations[resourceId].badge = {
262
- text: 'Error',
263
- variant: 'error'
264
- };
265
- }
266
- });
267
-
268
- return annotations;
269
- }, [
270
- activeProcessedResources?.summary?.resourceIds,
271
- resolutionActions,
272
- resolutionState?.selectedResourceId,
273
- resolutionState?.resolutionResult,
274
- resolutionState?.pendingResources,
275
- resolutionState?.pendingResourceDeletions
276
- ]);
277
-
278
- // Merge context options with current options from control
279
- const effectiveContextOptions = useMemo(() => {
280
- // Deep merge to preserve hostManagedValues from contextOptions
281
- const merged = {
282
- ...contextOptions,
283
- ...currentContextOptions,
284
- // Preserve hostManagedValues from contextOptions if currentContextOptions doesn't explicitly set it
285
- hostManagedValues:
286
- currentContextOptions?.hostManagedValues !== undefined
287
- ? currentContextOptions.hostManagedValues
288
- : contextOptions?.hostManagedValues
289
- };
290
- console.log('ResolutionView - effectiveContextOptions:', merged);
291
- console.log('ResolutionView - contextOptions hostManagedValues:', contextOptions?.hostManagedValues);
292
- console.log(
293
- 'ResolutionView - currentContextOptions hostManagedValues:',
294
- currentContextOptions?.hostManagedValues
295
- );
296
- console.log('ResolutionView - final hostManagedValues:', merged.hostManagedValues);
297
- return merged;
298
- }, [contextOptions, currentContextOptions]);
299
-
300
- // Handle context value changes using the shared component's callback pattern
301
- const handleQualifierChange = useCallback(
302
- (qualifierName: string, value: string | undefined) => {
303
- // Don't update context if this qualifier is host-managed
304
- const qualifierOptions = effectiveContextOptions?.qualifierOptions?.[qualifierName];
305
- const isHostManaged = qualifierOptions?.hostValue !== undefined;
306
-
307
- if (!isHostManaged) {
308
- resolutionActions?.updateContextValue(qualifierName, value);
309
- }
310
- },
311
- [resolutionActions, effectiveContextOptions?.qualifierOptions]
312
- );
313
-
314
- // Apply host-managed values when they change
315
- const prevHostValuesRef = useRef<string | undefined>(undefined);
316
- React.useEffect(() => {
317
- if (!effectiveContextOptions?.hostManagedValues || !resolutionActions?.applyContext) return;
318
-
319
- const hostValuesStr = JSON.stringify(effectiveContextOptions.hostManagedValues);
320
- if (prevHostValuesRef.current !== hostValuesStr) {
321
- console.log(
322
- 'ResolutionView: Host values changed, applying:',
323
- effectiveContextOptions.hostManagedValues
324
- );
325
- prevHostValuesRef.current = hostValuesStr;
326
- // Pass host values to the resolution state
327
- resolutionActions.applyContext(effectiveContextOptions.hostManagedValues);
328
- }
329
- }, [effectiveContextOptions?.hostManagedValues, resolutionActions]);
330
-
331
- // Determine which qualifiers to show and their options
332
- const visibleQualifiers = useMemo(() => {
333
- if (!effectiveContextOptions?.qualifierOptions) {
334
- return availableQualifiers;
335
- }
336
-
337
- return availableQualifiers.filter((qualifierName) => {
338
- const options = effectiveContextOptions.qualifierOptions![qualifierName];
339
- return options?.visible !== false;
340
- });
341
- }, [availableQualifiers, effectiveContextOptions?.qualifierOptions]);
342
-
343
- // Get effective context values - contextValues already includes host values from the hook
344
- const effectiveContextValues = useMemo(() => {
345
- // contextValues from state already includes host values (it's effectiveContext from the hook)
346
- // Don't double-apply host values here
347
- return resolutionState?.contextValues || {};
348
- }, [resolutionState?.contextValues]);
349
-
350
- // Convert pending resources to PendingResource format for ResourcePicker
351
- const pendingResourcesList = useMemo<PendingResource[]>(() => {
352
- const pending: PendingResource[] = [];
353
-
354
- // Add new pending resources
355
- if (resolutionState?.pendingResources) {
356
- resolutionState.pendingResources.forEach((resource, id) => {
357
- pending.push({
358
- id,
359
- type: 'new',
360
- resourceType: resource.resourceTypeName,
361
- displayName: id // Use the resource ID as display name
362
- });
363
- });
364
- }
365
-
366
- // Add deleted resources
367
- if (resolutionState?.pendingResourceDeletions) {
368
- resolutionState.pendingResourceDeletions.forEach((id) => {
369
- pending.push({
370
- id,
371
- type: 'deleted',
372
- displayName: id
373
- });
374
- });
375
- }
376
-
377
- // Add edited resources
378
- if (resolutionState?.editedResources) {
379
- resolutionState.editedResources.forEach((_, id) => {
380
- // Only add if not already in pending as new
381
- if (!resolutionState.pendingResources?.has(id)) {
382
- pending.push({
383
- id,
384
- type: 'modified',
385
- displayName: id
386
- });
387
- }
388
- });
389
- }
390
-
391
- return pending;
392
- }, [
393
- resolutionState?.pendingResources,
394
- resolutionState?.pendingResourceDeletions,
395
- resolutionState?.editedResources
396
- ]);
397
-
398
- // Handle resource selection from ResourcePicker
399
- const handleResourceSelect = useCallback(
400
- (selection: ResourceSelection) => {
401
- if (selection.resourceId) {
402
- resolutionActions?.selectResource(selection.resourceId);
403
- }
404
- },
405
- [resolutionActions]
406
- );
407
-
408
- // Handle new resource creation
409
- const handleStartNewResource = useCallback(() => {
410
- resolutionActions?.startNewResource(defaultResourceType);
411
- setShowNewResourceModal(true);
412
- }, [resolutionActions]);
413
-
414
- const handleCloseNewResourceModal = useCallback(() => {
415
- setShowNewResourceModal(false);
416
- resolutionActions?.cancelNewResource();
417
- }, [resolutionActions]);
418
-
419
- const handleApplyPendingResources = useCallback(async () => {
420
- await resolutionActions?.applyPendingResources();
421
- if (onPendingResourcesApplied && resolutionState) {
422
- const added = Array.from(resolutionState.pendingResources.values());
423
- const deleted = Array.from(resolutionState.pendingResourceDeletions);
424
- onPendingResourcesApplied(added, deleted);
425
- }
426
- }, [resolutionActions, resolutionState, onPendingResourcesApplied]);
427
-
428
- // Automatically set locked view mode when provided
429
- useEffect(() => {
430
- if (lockedViewMode && resolutionActions?.setViewMode) {
431
- resolutionActions.setViewMode(lockedViewMode);
432
- }
433
- }, [lockedViewMode, resolutionActions]);
434
-
435
- // Handle view mode change
436
- const handleViewModeChange = useCallback(
437
- (mode: 'composed' | 'best' | 'all' | 'raw') => {
438
- // Don't allow view mode changes when locked
439
- if (!lockedViewMode) {
440
- resolutionActions?.setViewMode(mode);
441
- }
442
- },
443
- [resolutionActions, lockedViewMode]
444
- );
445
-
446
- if (!resources) {
447
- return (
448
- <div className={`p-6 ${className}`}>
449
- <div className="flex items-center space-x-3 mb-6">
450
- <MagnifyingGlassIcon className="h-8 w-8 text-blue-600" />
451
- <h2 className="text-2xl font-bold text-gray-900">Resolution Viewer</h2>
452
- </div>
453
-
454
- <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8 text-center">
455
- <div className="max-w-2xl mx-auto">
456
- <h3 className="text-xl font-semibold text-gray-900 mb-4">No Resources Loaded</h3>
457
- <p className="text-gray-600 mb-6">
458
- Import resources first to test resource resolution with different contexts.
459
- </p>
460
- <div className="bg-blue-50 rounded-lg p-4">
461
- <p className="text-sm text-blue-800">
462
- <strong>Resolution Viewer:</strong> Test how resources resolve with different qualifier
463
- contexts. Set context values and see which candidates match.
464
- </p>
465
- </div>
466
- </div>
467
- </div>
468
- </div>
469
- );
470
- }
471
-
472
- return (
473
- <div className={`p-6 ${className}`}>
474
- <div className="flex items-center space-x-3 mb-6">
475
- <MagnifyingGlassIcon className="h-8 w-8 text-blue-600" />
476
- <h2 className="text-2xl font-bold text-gray-900">Resolution Viewer</h2>
477
- {isFilteringActive && (
478
- <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
479
- Filtered
480
- </span>
481
- )}
482
- </div>
483
-
484
- {/* ResourcePicker Options Control */}
485
- <ResourcePickerOptionsControl
486
- options={currentPickerOptions}
487
- onOptionsChange={setCurrentPickerOptions}
488
- presentation={pickerOptionsPresentation}
489
- title="Resolution Viewer Picker Options"
490
- className="mb-6"
491
- />
492
-
493
- {/* ResolutionContext Options Control */}
494
- <ResolutionContextOptionsControl
495
- options={currentContextOptions}
496
- onOptionsChange={setCurrentContextOptions}
497
- availableQualifiers={availableQualifiers}
498
- presentation={pickerOptionsPresentation}
499
- title="Resolution Context Options"
500
- className="mb-6"
501
- allowResourceCreation={allowResourceCreationInternal}
502
- onAllowResourceCreationChange={setAllowResourceCreationInternal}
503
- showPendingResourcesInList={showPendingResourcesInListInternal}
504
- onShowPendingResourcesInListChange={setShowPendingResourcesInListInternal}
505
- />
506
-
507
- <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
508
- {/* Context Configuration Panel */}
509
- {effectiveContextOptions?.showContextControls !== false && (
510
- <div className="mb-6">
511
- <h3 className="text-lg font-semibold text-gray-900 mb-4">
512
- {effectiveContextOptions?.contextPanelTitle || 'Context Configuration'}
513
- </h3>
514
- <div
515
- className={`bg-gray-50 rounded-lg p-4 ${effectiveContextOptions?.contextPanelClassName || ''}`}
516
- >
517
- <div className="mb-4">
518
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
519
- {visibleQualifiers.map((qualifierName) => {
520
- const qualifierOptions = effectiveContextOptions?.qualifierOptions?.[qualifierName];
521
- const hostManagedValue = effectiveContextOptions?.hostManagedValues?.[qualifierName];
522
- const globalPlaceholder =
523
- typeof effectiveContextOptions?.globalPlaceholder === 'function'
524
- ? effectiveContextOptions.globalPlaceholder(qualifierName)
525
- : effectiveContextOptions?.globalPlaceholder;
526
-
527
- // Merge host-managed values with qualifier options
528
- const mergedOptions = {
529
- ...qualifierOptions,
530
- // Host-managed values override qualifier-specific host values
531
- hostValue:
532
- hostManagedValue !== undefined ? hostManagedValue : qualifierOptions?.hostValue
533
- };
534
-
535
- return (
536
- <QualifierContextControl
537
- key={qualifierName}
538
- qualifierName={qualifierName}
539
- value={resolutionState?.pendingContextValues[qualifierName]}
540
- onChange={handleQualifierChange}
541
- placeholder={globalPlaceholder || `Enter ${qualifierName} value`}
542
- resources={activeProcessedResources}
543
- options={mergedOptions}
544
- />
545
- );
546
- })}
547
- </div>
548
- </div>
549
-
550
- {effectiveContextOptions?.showCurrentContext !== false && (
551
- <div className="flex items-center justify-between">
552
- <div className="text-sm text-gray-600">
553
- Current:{' '}
554
- {Object.entries(effectiveContextValues)
555
- .map(([key, value]) => `${key}=${value === undefined ? '(undefined)' : value}`)
556
- .join(', ')}
557
- </div>
558
- {effectiveContextOptions?.showContextActions !== false && (
559
- <div className="flex items-center space-x-2">
560
- <button
561
- onClick={resolutionActions?.resetCache}
562
- className="px-3 py-1 text-xs font-medium text-gray-600 bg-gray-100 rounded hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500"
563
- title="Clear resolution cache"
564
- >
565
- Clear Cache
566
- </button>
567
- <button
568
- onClick={() => resolutionActions?.applyContext()}
569
- disabled={!resolutionState?.hasPendingChanges}
570
- className={`px-4 py-2 rounded-md text-sm font-medium ${
571
- resolutionState?.hasPendingChanges
572
- ? 'bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500'
573
- : 'bg-gray-300 text-gray-500 cursor-not-allowed'
574
- }`}
575
- >
576
- {resolutionState?.hasPendingChanges
577
- ? 'Apply Changes'
578
- : resolutionState?.currentResolver
579
- ? 'Context Applied'
580
- : 'Apply Context'}
581
- </button>
582
- </div>
583
- )}
584
- </div>
585
- )}
586
- </div>
587
- </div>
588
- )}
589
-
590
- {/* Unified Change Controls - replaces separate edit/pending controls */}
591
- {(resolutionState?.hasUnsavedEdits || resolutionState?.hasPendingResourceChanges) && (
592
- <div className="mt-6">
593
- <UnifiedChangeControls
594
- editCount={resolutionState?.editedResources?.size || 0}
595
- addCount={resolutionState?.pendingResources?.size || 0}
596
- deleteCount={resolutionState?.pendingResourceDeletions?.size || 0}
597
- isApplying={resolutionState?.isApplyingEdits}
598
- disabled={!resolutionState?.currentResolver}
599
- onApplyAll={async () => {
600
- await handleApplyPendingResources();
601
- }}
602
- onDiscardAll={() => {
603
- resolutionActions?.discardEdits?.();
604
- resolutionActions?.discardPendingResources?.();
605
- }}
606
- />
607
- </div>
608
- )}
609
-
610
- {/* Main Browser/Details Layout */}
611
- <div className="flex flex-col lg:flex-row gap-6 h-[600px]">
612
- {/* Left side: Resource Selection */}
613
- <div className="lg:w-1/2 flex flex-col">
614
- <div className="flex items-center justify-between mb-4">
615
- <h3 className="text-lg font-semibold text-gray-900">
616
- {sectionTitles?.resources || 'Resources'}
617
- </h3>
618
- {allowResourceCreationInternal && (
619
- <button
620
- onClick={handleStartNewResource}
621
- className="flex items-center space-x-1 px-3 py-1.5 text-sm font-medium text-blue-600 bg-blue-50 rounded-md hover:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
622
- title="Create a new resource"
623
- >
624
- <PlusIcon className="h-4 w-4" />
625
- <span>Add Resource</span>
626
- </button>
627
- )}
628
- </div>
629
-
630
- <div className="flex-1">
631
- <ResourcePicker
632
- resources={activeProcessedResources || null}
633
- selectedResourceId={resolutionState?.selectedResourceId || null}
634
- onResourceSelect={handleResourceSelect}
635
- resourceAnnotations={resourceAnnotations}
636
- pendingResources={showPendingResourcesInListInternal ? pendingResourcesList : undefined}
637
- options={effectivePickerOptions}
638
- onMessage={onMessage}
639
- />
640
- </div>
641
- </div>
642
-
643
- {/* Right side: Resolution Results */}
644
- <div className="lg:w-1/2 flex flex-col">
645
- <div className="flex items-center justify-between mb-4">
646
- <h3 className="text-lg font-semibold text-gray-900">
647
- {sectionTitles?.results || 'Results'}
648
- {lockedViewMode && (
649
- <span className="ml-2 inline-flex items-center px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 rounded-full">
650
- {lockedViewMode.charAt(0).toUpperCase() + lockedViewMode.slice(1)} View
651
- </span>
652
- )}
653
- </h3>
654
- {resolutionState?.selectedResourceId && !lockedViewMode && (
655
- <div className="flex space-x-2">
656
- <button
657
- onClick={() => handleViewModeChange('composed')}
658
- className={`px-3 py-1 text-xs rounded ${
659
- resolutionState?.viewMode === 'composed'
660
- ? 'bg-blue-600 text-white'
661
- : 'bg-gray-200 text-gray-700'
662
- }`}
663
- >
664
- Composed
665
- </button>
666
- <button
667
- onClick={() => handleViewModeChange('best')}
668
- className={`px-3 py-1 text-xs rounded ${
669
- resolutionState?.viewMode === 'best'
670
- ? 'bg-blue-600 text-white'
671
- : 'bg-gray-200 text-gray-700'
672
- }`}
673
- >
674
- Best
675
- </button>
676
- <button
677
- onClick={() => handleViewModeChange('all')}
678
- className={`px-3 py-1 text-xs rounded ${
679
- resolutionState?.viewMode === 'all'
680
- ? 'bg-blue-600 text-white'
681
- : 'bg-gray-200 text-gray-700'
682
- }`}
683
- >
684
- All
685
- </button>
686
- <button
687
- onClick={() => handleViewModeChange('raw')}
688
- className={`px-3 py-1 text-xs rounded ${
689
- resolutionState?.viewMode === 'raw'
690
- ? 'bg-blue-600 text-white'
691
- : 'bg-gray-200 text-gray-700'
692
- }`}
693
- >
694
- Raw
695
- </button>
696
- </div>
697
- )}
698
- </div>
699
-
700
- <div className="flex-1 overflow-y-auto border border-gray-200 rounded-lg p-4 bg-gray-50">
701
- {!resolutionState?.selectedResourceId ? (
702
- <div className="flex items-center justify-center h-full">
703
- <div className="text-center">
704
- <CubeIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
705
- <p className="text-gray-500">Select a resource to view resolution results</p>
706
- </div>
707
- </div>
708
- ) : !resolutionState?.currentResolver ? (
709
- <div className="text-center text-gray-500">
710
- <p>Apply a context to resolve resources</p>
711
- </div>
712
- ) : !resolutionState?.resolutionResult ? (
713
- <div className="text-center text-gray-500">
714
- <p>Resolving...</p>
715
- </div>
716
- ) : (
717
- <ResolutionResults
718
- result={resolutionState.resolutionResult}
719
- viewMode={resolutionState.viewMode}
720
- contextValues={resolutionState.contextValues}
721
- resolutionActions={resolutionActions}
722
- resolutionState={resolutionState}
723
- resourceEditorFactory={resourceEditorFactory}
724
- onMessage={onMessage}
725
- />
726
- )}
727
- </div>
728
- </div>
729
- </div>
730
- </div>
731
-
732
- {/* New Resource Modal */}
733
- {resolutionState?.newResourceDraft && (
734
- <NewResourceModal
735
- isOpen={showNewResourceModal}
736
- onClose={handleCloseNewResourceModal}
737
- resourceId={resolutionState.newResourceDraft.resourceId}
738
- resourceType={resolutionState.newResourceDraft.resourceType}
739
- availableResourceTypes={resolutionState.availableResourceTypes}
740
- isValid={resolutionState.newResourceDraft.isValid}
741
- defaultResourceType={defaultResourceType}
742
- onUpdateResourceId={resolutionActions?.updateNewResourceId || (() => {})}
743
- onSelectResourceType={resolutionActions?.selectResourceType || (() => {})}
744
- onSave={resolutionActions?.saveNewResourceAsPending || (() => {})}
745
- />
746
- )}
747
- </div>
748
- );
749
- };
750
-
751
- export default ResolutionView;