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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/.rush/temp/03c8b056281d9db0a97d8a6e25eea798a160d393.tar.log +271 -0
  2. package/.rush/temp/chunked-rush-logs/ts-res-ui-components.build.chunks.jsonl +9 -0
  3. package/.rush/temp/operation/build/all.log +9 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +1111 -0
  7. package/README.md +18 -0
  8. package/REFACTORING_PLAN.md +171 -0
  9. package/config/jest.config.json +16 -0
  10. package/config/jest.setup.js +64 -0
  11. package/config/rig.json +16 -0
  12. package/lib/components/common/QualifierContextControl.d.ts +14 -0
  13. package/lib/components/common/QualifierContextControl.d.ts.map +1 -0
  14. package/lib/components/common/QualifierContextControl.js +78 -0
  15. package/lib/components/common/QualifierContextControl.js.map +1 -0
  16. package/lib/components/common/ResourceListView.d.ts +11 -0
  17. package/lib/components/common/ResourceListView.d.ts.map +1 -0
  18. package/lib/components/common/ResourceListView.js +20 -0
  19. package/lib/components/common/ResourceListView.js.map +1 -0
  20. package/lib/components/common/ResourceTreeView.d.ts +12 -0
  21. package/lib/components/common/ResourceTreeView.d.ts.map +1 -0
  22. package/lib/components/common/ResourceTreeView.js +162 -0
  23. package/lib/components/common/ResourceTreeView.js.map +1 -0
  24. package/lib/components/forms/HierarchyEditor.d.ts +10 -0
  25. package/lib/components/forms/HierarchyEditor.d.ts.map +1 -0
  26. package/lib/components/forms/HierarchyEditor.js +106 -0
  27. package/lib/components/forms/HierarchyEditor.js.map +1 -0
  28. package/lib/components/forms/QualifierEditForm.d.ts +11 -0
  29. package/lib/components/forms/QualifierEditForm.d.ts.map +1 -0
  30. package/lib/components/forms/QualifierEditForm.js +181 -0
  31. package/lib/components/forms/QualifierEditForm.js.map +1 -0
  32. package/lib/components/forms/QualifierTypeEditForm.d.ts +10 -0
  33. package/lib/components/forms/QualifierTypeEditForm.d.ts.map +1 -0
  34. package/lib/components/forms/QualifierTypeEditForm.js +172 -0
  35. package/lib/components/forms/QualifierTypeEditForm.js.map +1 -0
  36. package/lib/components/forms/ResourceTypeEditForm.d.ts +10 -0
  37. package/lib/components/forms/ResourceTypeEditForm.d.ts.map +1 -0
  38. package/lib/components/forms/ResourceTypeEditForm.js +188 -0
  39. package/lib/components/forms/ResourceTypeEditForm.js.map +1 -0
  40. package/lib/components/forms/index.d.ts +9 -0
  41. package/lib/components/forms/index.d.ts.map +1 -0
  42. package/lib/components/forms/index.js +5 -0
  43. package/lib/components/forms/index.js.map +1 -0
  44. package/lib/components/orchestrator/ResourceOrchestrator.d.ts +14 -0
  45. package/lib/components/orchestrator/ResourceOrchestrator.d.ts.map +1 -0
  46. package/lib/components/orchestrator/ResourceOrchestrator.js +278 -0
  47. package/lib/components/orchestrator/ResourceOrchestrator.js.map +1 -0
  48. package/lib/components/views/CompiledView/index.d.ts +5 -0
  49. package/lib/components/views/CompiledView/index.d.ts.map +1 -0
  50. package/lib/components/views/CompiledView/index.js +595 -0
  51. package/lib/components/views/CompiledView/index.js.map +1 -0
  52. package/lib/components/views/ConfigurationView/index.d.ts +5 -0
  53. package/lib/components/views/ConfigurationView/index.d.ts.map +1 -0
  54. package/lib/components/views/ConfigurationView/index.js +363 -0
  55. package/lib/components/views/ConfigurationView/index.js.map +1 -0
  56. package/lib/components/views/FilterView/index.d.ts +5 -0
  57. package/lib/components/views/FilterView/index.d.ts.map +1 -0
  58. package/lib/components/views/FilterView/index.js +463 -0
  59. package/lib/components/views/FilterView/index.js.map +1 -0
  60. package/lib/components/views/ImportView/index.d.ts +5 -0
  61. package/lib/components/views/ImportView/index.d.ts.map +1 -0
  62. package/lib/components/views/ImportView/index.js +514 -0
  63. package/lib/components/views/ImportView/index.js.map +1 -0
  64. package/lib/components/views/ResolutionView/EditableJsonView.d.ts +21 -0
  65. package/lib/components/views/ResolutionView/EditableJsonView.d.ts.map +1 -0
  66. package/lib/components/views/ResolutionView/EditableJsonView.js +109 -0
  67. package/lib/components/views/ResolutionView/EditableJsonView.js.map +1 -0
  68. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts +19 -0
  69. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts.map +1 -0
  70. package/lib/components/views/ResolutionView/ResolutionEditControls.js +82 -0
  71. package/lib/components/views/ResolutionView/ResolutionEditControls.js.map +1 -0
  72. package/lib/components/views/ResolutionView/index.d.ts +5 -0
  73. package/lib/components/views/ResolutionView/index.d.ts.map +1 -0
  74. package/lib/components/views/ResolutionView/index.js +255 -0
  75. package/lib/components/views/ResolutionView/index.js.map +1 -0
  76. package/lib/components/views/SourceView/index.d.ts +5 -0
  77. package/lib/components/views/SourceView/index.d.ts.map +1 -0
  78. package/lib/components/views/SourceView/index.js +316 -0
  79. package/lib/components/views/SourceView/index.js.map +1 -0
  80. package/lib/components/views/ZipLoaderView/index.d.ts +5 -0
  81. package/lib/components/views/ZipLoaderView/index.d.ts.map +1 -0
  82. package/lib/components/views/ZipLoaderView/index.js +313 -0
  83. package/lib/components/views/ZipLoaderView/index.js.map +1 -0
  84. package/lib/hooks/useConfigurationState.d.ts +46 -0
  85. package/lib/hooks/useConfigurationState.d.ts.map +1 -0
  86. package/lib/hooks/useConfigurationState.js +239 -0
  87. package/lib/hooks/useConfigurationState.js.map +1 -0
  88. package/lib/hooks/useFilterState.d.ts +7 -0
  89. package/lib/hooks/useFilterState.d.ts.map +1 -0
  90. package/lib/hooks/useFilterState.js +80 -0
  91. package/lib/hooks/useFilterState.js.map +1 -0
  92. package/lib/hooks/useResolutionState.d.ts +8 -0
  93. package/lib/hooks/useResolutionState.d.ts.map +1 -0
  94. package/lib/hooks/useResolutionState.js +253 -0
  95. package/lib/hooks/useResolutionState.js.map +1 -0
  96. package/lib/hooks/useResourceData.d.ts +19 -0
  97. package/lib/hooks/useResourceData.d.ts.map +1 -0
  98. package/lib/hooks/useResourceData.js +368 -0
  99. package/lib/hooks/useResourceData.js.map +1 -0
  100. package/lib/hooks/useViewState.d.ts +10 -0
  101. package/lib/hooks/useViewState.d.ts.map +1 -0
  102. package/lib/hooks/useViewState.js +29 -0
  103. package/lib/hooks/useViewState.js.map +1 -0
  104. package/lib/index.d.ts +27 -0
  105. package/lib/index.d.ts.map +1 -0
  106. package/lib/index.js +34 -0
  107. package/lib/index.js.map +1 -0
  108. package/lib/test/helpers/testDataLoader.d.ts +37 -0
  109. package/lib/test/helpers/testDataLoader.d.ts.map +1 -0
  110. package/lib/test/helpers/testDataLoader.js +171 -0
  111. package/lib/test/helpers/testDataLoader.js.map +1 -0
  112. package/lib/test/unit/utils/configurationUtils.test.d.ts +2 -0
  113. package/lib/test/unit/utils/configurationUtils.test.d.ts.map +1 -0
  114. package/lib/test/unit/utils/configurationUtils.test.js +497 -0
  115. package/lib/test/unit/utils/configurationUtils.test.js.map +1 -0
  116. package/lib/test/unit/utils/fileProcessing.test.d.ts +2 -0
  117. package/lib/test/unit/utils/fileProcessing.test.d.ts.map +1 -0
  118. package/lib/test/unit/utils/fileProcessing.test.js +321 -0
  119. package/lib/test/unit/utils/fileProcessing.test.js.map +1 -0
  120. package/lib/test/unit/utils/filterResources.test.d.ts +2 -0
  121. package/lib/test/unit/utils/filterResources.test.d.ts.map +1 -0
  122. package/lib/test/unit/utils/filterResources.test.js +403 -0
  123. package/lib/test/unit/utils/filterResources.test.js.map +1 -0
  124. package/lib/test/unit/utils/resolutionEditing.test.d.ts +2 -0
  125. package/lib/test/unit/utils/resolutionEditing.test.d.ts.map +1 -0
  126. package/lib/test/unit/utils/resolutionEditing.test.js +439 -0
  127. package/lib/test/unit/utils/resolutionEditing.test.js.map +1 -0
  128. package/lib/test/unit/utils/resolutionUtils.test.d.ts +2 -0
  129. package/lib/test/unit/utils/resolutionUtils.test.d.ts.map +1 -0
  130. package/lib/test/unit/utils/resolutionUtils.test.js +397 -0
  131. package/lib/test/unit/utils/resolutionUtils.test.js.map +1 -0
  132. package/lib/test/unit/utils/tsResIntegration.test.d.ts +2 -0
  133. package/lib/test/unit/utils/tsResIntegration.test.d.ts.map +1 -0
  134. package/lib/test/unit/utils/tsResIntegration.test.js +376 -0
  135. package/lib/test/unit/utils/tsResIntegration.test.js.map +1 -0
  136. package/lib/types/index.d.ts +251 -0
  137. package/lib/types/index.d.ts.map +1 -0
  138. package/lib/types/index.js +2 -0
  139. package/lib/types/index.js.map +1 -0
  140. package/lib/utils/configurationUtils.d.ts +74 -0
  141. package/lib/utils/configurationUtils.d.ts.map +1 -0
  142. package/lib/utils/configurationUtils.js +359 -0
  143. package/lib/utils/configurationUtils.js.map +1 -0
  144. package/lib/utils/fileProcessing.d.ts +18 -0
  145. package/lib/utils/fileProcessing.d.ts.map +1 -0
  146. package/lib/utils/fileProcessing.js +142 -0
  147. package/lib/utils/fileProcessing.js.map +1 -0
  148. package/lib/utils/filterResources.d.ts +38 -0
  149. package/lib/utils/filterResources.d.ts.map +1 -0
  150. package/lib/utils/filterResources.js +153 -0
  151. package/lib/utils/filterResources.js.map +1 -0
  152. package/lib/utils/resolutionEditing.d.ts +58 -0
  153. package/lib/utils/resolutionEditing.d.ts.map +1 -0
  154. package/lib/utils/resolutionEditing.js +246 -0
  155. package/lib/utils/resolutionEditing.js.map +1 -0
  156. package/lib/utils/resolutionUtils.d.ts +28 -0
  157. package/lib/utils/resolutionUtils.d.ts.map +1 -0
  158. package/lib/utils/resolutionUtils.js +216 -0
  159. package/lib/utils/resolutionUtils.js.map +1 -0
  160. package/lib/utils/tsResIntegration.d.ts +71 -0
  161. package/lib/utils/tsResIntegration.d.ts.map +1 -0
  162. package/lib/utils/tsResIntegration.js +294 -0
  163. package/lib/utils/tsResIntegration.js.map +1 -0
  164. package/lib/utils/zipLoader/browserZipLoader.d.ts +48 -0
  165. package/lib/utils/zipLoader/browserZipLoader.d.ts.map +1 -0
  166. package/lib/utils/zipLoader/browserZipLoader.js +247 -0
  167. package/lib/utils/zipLoader/browserZipLoader.js.map +1 -0
  168. package/lib/utils/zipLoader/index.d.ts +8 -0
  169. package/lib/utils/zipLoader/index.d.ts.map +1 -0
  170. package/lib/utils/zipLoader/index.js +13 -0
  171. package/lib/utils/zipLoader/index.js.map +1 -0
  172. package/lib/utils/zipLoader/nodeZipBuilder.d.ts +55 -0
  173. package/lib/utils/zipLoader/nodeZipBuilder.d.ts.map +1 -0
  174. package/lib/utils/zipLoader/nodeZipBuilder.js +98 -0
  175. package/lib/utils/zipLoader/nodeZipBuilder.js.map +1 -0
  176. package/lib/utils/zipLoader/types.d.ts +139 -0
  177. package/lib/utils/zipLoader/types.d.ts.map +1 -0
  178. package/lib/utils/zipLoader/types.js +2 -0
  179. package/lib/utils/zipLoader/types.js.map +1 -0
  180. package/lib/utils/zipLoader/zipUtils.d.ts +53 -0
  181. package/lib/utils/zipLoader/zipUtils.d.ts.map +1 -0
  182. package/lib/utils/zipLoader/zipUtils.js +229 -0
  183. package/lib/utils/zipLoader/zipUtils.js.map +1 -0
  184. package/package.json +69 -0
  185. package/rush-logs/ts-res-ui-components.build.cache.log +3 -0
  186. package/rush-logs/ts-res-ui-components.build.log +9 -0
  187. package/src/components/common/QualifierContextControl.tsx +151 -0
  188. package/src/components/common/ResourceListView.tsx +63 -0
  189. package/src/components/common/ResourceTreeView.tsx +271 -0
  190. package/src/components/forms/HierarchyEditor.tsx +204 -0
  191. package/src/components/forms/QualifierEditForm.tsx +355 -0
  192. package/src/components/forms/QualifierTypeEditForm.tsx +347 -0
  193. package/src/components/forms/ResourceTypeEditForm.tsx +331 -0
  194. package/src/components/forms/index.ts +11 -0
  195. package/src/components/orchestrator/ResourceOrchestrator.tsx +372 -0
  196. package/src/components/views/CompiledView/index.tsx +922 -0
  197. package/src/components/views/ConfigurationView/index.tsx +800 -0
  198. package/src/components/views/FilterView/index.tsx +825 -0
  199. package/src/components/views/ImportView/index.tsx +717 -0
  200. package/src/components/views/ResolutionView/EditableJsonView.tsx +214 -0
  201. package/src/components/views/ResolutionView/ResolutionEditControls.tsx +170 -0
  202. package/src/components/views/ResolutionView/index.tsx +591 -0
  203. package/src/components/views/SourceView/index.tsx +536 -0
  204. package/src/components/views/ZipLoaderView/index.tsx +485 -0
  205. package/src/hooks/useConfigurationState.ts +374 -0
  206. package/src/hooks/useFilterState.ts +97 -0
  207. package/src/hooks/useResolutionState.ts +355 -0
  208. package/src/hooks/useResourceData.ts +467 -0
  209. package/src/hooks/useViewState.ts +44 -0
  210. package/src/index.ts +45 -0
  211. package/src/test/helpers/testDataLoader.ts +195 -0
  212. package/src/test/unit/utils/configurationUtils.test.ts +630 -0
  213. package/src/test/unit/utils/fileProcessing.test.ts +391 -0
  214. package/src/test/unit/utils/filterResources.test.ts +574 -0
  215. package/src/test/unit/utils/resolutionEditing.test.ts +556 -0
  216. package/src/test/unit/utils/resolutionUtils.test.ts +521 -0
  217. package/src/test/unit/utils/tsResIntegration.test.ts +433 -0
  218. package/src/types/index.ts +322 -0
  219. package/src/utils/configurationUtils.ts +424 -0
  220. package/src/utils/fileProcessing.ts +160 -0
  221. package/src/utils/filterResources.ts +206 -0
  222. package/src/utils/resolutionEditing.ts +319 -0
  223. package/src/utils/resolutionUtils.ts +289 -0
  224. package/src/utils/tsResIntegration.ts +440 -0
  225. package/src/utils/zipLoader/browserZipLoader.ts +319 -0
  226. package/src/utils/zipLoader/index.ts +26 -0
  227. package/src/utils/zipLoader/nodeZipBuilder.ts +153 -0
  228. package/src/utils/zipLoader/types.ts +175 -0
  229. package/src/utils/zipLoader/zipUtils.ts +266 -0
  230. package/temp/build/typescript/ts_gZid87Hu.json +1 -0
  231. package/tsconfig.json +15 -0
@@ -0,0 +1,355 @@
1
+ import React, { useState, useCallback, useMemo } from 'react';
2
+ import { Runtime } from '@fgv/ts-res';
3
+ import {
4
+ ResolutionState,
5
+ ResolutionActions,
6
+ ResolutionResult,
7
+ ProcessedResources,
8
+ JsonValue
9
+ } from '../types';
10
+ import {
11
+ createResolverWithContext,
12
+ resolveResourceDetailed,
13
+ getAvailableQualifiers,
14
+ hasPendingContextChanges
15
+ } from '../utils/resolutionUtils';
16
+ import {
17
+ validateEditedResource,
18
+ computeResourceDelta,
19
+ rebuildSystemWithEdits,
20
+ extractResolutionContext,
21
+ checkEditConflicts
22
+ } from '../utils/resolutionEditing';
23
+
24
+ export interface UseResolutionStateReturn {
25
+ state: ResolutionState;
26
+ actions: ResolutionActions;
27
+ availableQualifiers: string[];
28
+ }
29
+
30
+ export function useResolutionState(
31
+ processedResources: ProcessedResources | null,
32
+ onMessage?: (type: 'info' | 'warning' | 'error' | 'success', message: string) => void,
33
+ onSystemUpdate?: (updatedResources: ProcessedResources) => void
34
+ ): UseResolutionStateReturn {
35
+ // Get available qualifiers
36
+ const availableQualifiers = useMemo(() => {
37
+ if (!processedResources) return [];
38
+ return getAvailableQualifiers(processedResources);
39
+ }, [processedResources]);
40
+
41
+ // Initialize context with all qualifiers undefined
42
+ const defaultContextValues = useMemo(() => {
43
+ const defaults: Record<string, string | undefined> = {};
44
+ availableQualifiers.forEach((qualifierName) => {
45
+ defaults[qualifierName] = undefined;
46
+ });
47
+ return defaults;
48
+ }, [availableQualifiers]);
49
+
50
+ // Resolution state
51
+ const [contextValues, setContextValues] = useState<Record<string, string | undefined>>({});
52
+ const [pendingContextValues, setPendingContextValues] = useState<Record<string, string | undefined>>({});
53
+ const [selectedResourceId, setSelectedResourceId] = useState<string | null>(null);
54
+ const [currentResolver, setCurrentResolver] = useState<Runtime.ResourceResolver | null>(null);
55
+ const [resolutionResult, setResolutionResult] = useState<ResolutionResult | null>(null);
56
+ const [viewMode, setViewMode] = useState<'composed' | 'best' | 'all' | 'raw'>('composed');
57
+
58
+ // Edit state - stores original, edited, and delta for each resource
59
+ const [editedResources, setEditedResources] = useState<
60
+ Map<string, { originalValue: JsonValue; editedValue: JsonValue; delta: JsonValue }>
61
+ >(new Map());
62
+ const [isApplyingEdits, setIsApplyingEdits] = useState(false);
63
+
64
+ // Update context state when defaults change
65
+ React.useEffect(() => {
66
+ setContextValues(defaultContextValues);
67
+ setPendingContextValues(defaultContextValues);
68
+ }, [defaultContextValues]);
69
+
70
+ // Check for pending changes
71
+ const hasPendingChanges = useMemo(() => {
72
+ return hasPendingContextChanges(contextValues, pendingContextValues);
73
+ }, [contextValues, pendingContextValues]);
74
+
75
+ // Check for unsaved edits
76
+ const hasUnsavedEdits = useMemo(() => {
77
+ return editedResources.size > 0;
78
+ }, [editedResources]);
79
+
80
+ // Update context value
81
+ const updateContextValue = useCallback((qualifierName: string, value: string | undefined) => {
82
+ setPendingContextValues((prev) => ({
83
+ ...prev,
84
+ [qualifierName]: value
85
+ }));
86
+ }, []);
87
+
88
+ // Apply context changes
89
+ const applyContext = useCallback(() => {
90
+ if (!processedResources) {
91
+ onMessage?.('error', 'No resources loaded');
92
+ return;
93
+ }
94
+
95
+ try {
96
+ // Create resolver with new context
97
+ const resolverResult = createResolverWithContext(processedResources, pendingContextValues, {
98
+ enableCaching: true,
99
+ enableDebugLogging: false
100
+ });
101
+
102
+ if (resolverResult.isFailure()) {
103
+ onMessage?.('error', `Failed to create resolver: ${resolverResult.message}`);
104
+ return;
105
+ }
106
+
107
+ // Update state
108
+ setContextValues({ ...pendingContextValues });
109
+ setCurrentResolver(resolverResult.value);
110
+
111
+ // If a resource is selected, resolve it with the new context
112
+ if (selectedResourceId) {
113
+ const resolutionResult = resolveResourceDetailed(
114
+ resolverResult.value,
115
+ selectedResourceId,
116
+ processedResources
117
+ );
118
+
119
+ if (resolutionResult.isSuccess()) {
120
+ setResolutionResult(resolutionResult.value);
121
+ } else {
122
+ onMessage?.('error', `Failed to resolve resource: ${resolutionResult.message}`);
123
+ }
124
+ }
125
+
126
+ onMessage?.('success', 'Context applied successfully');
127
+ } catch (error) {
128
+ onMessage?.(
129
+ 'error',
130
+ `Failed to apply context: ${error instanceof Error ? error.message : String(error)}`
131
+ );
132
+ }
133
+ }, [processedResources, pendingContextValues, selectedResourceId, onMessage]);
134
+
135
+ // Select resource and resolve it
136
+ const selectResource = useCallback(
137
+ (resourceId: string) => {
138
+ setSelectedResourceId(resourceId);
139
+ setResolutionResult(null);
140
+
141
+ if (currentResolver && processedResources) {
142
+ const resolutionResult = resolveResourceDetailed(currentResolver, resourceId, processedResources);
143
+
144
+ if (resolutionResult.isSuccess()) {
145
+ setResolutionResult(resolutionResult.value);
146
+ onMessage?.('info', `Selected resource: ${resourceId}`);
147
+ } else {
148
+ onMessage?.('error', `Failed to resolve resource: ${resolutionResult.message}`);
149
+ }
150
+ }
151
+ },
152
+ [currentResolver, processedResources, onMessage]
153
+ );
154
+
155
+ // Reset cache
156
+ const resetCache = useCallback(() => {
157
+ if (currentResolver) {
158
+ currentResolver.clearConditionCache();
159
+ onMessage?.('info', 'Cache cleared');
160
+ }
161
+ }, [currentResolver, onMessage]);
162
+
163
+ // Auto-apply default context when resources are loaded
164
+ React.useEffect(() => {
165
+ if (processedResources && Object.keys(defaultContextValues).length > 0) {
166
+ applyContext();
167
+ }
168
+ }, [processedResources, defaultContextValues]);
169
+
170
+ // Edit management functions
171
+ const saveEdit = useCallback(
172
+ (resourceId: string, editedValue: JsonValue, originalValue?: JsonValue) => {
173
+ try {
174
+ // Validate the edited value
175
+ const validation = validateEditedResource(editedValue);
176
+ if (!validation.isValid) {
177
+ onMessage?.('error', `Invalid edit: ${validation.errors.join(', ')}`);
178
+ return;
179
+ }
180
+
181
+ // Show warnings if any
182
+ if (validation.warnings.length > 0) {
183
+ validation.warnings.forEach((warning) => onMessage?.('warning', warning));
184
+ }
185
+
186
+ // Compute the delta between original and edited
187
+ const resolvedValue = originalValue || editedValue; // Use originalValue as the resolved/baseline
188
+ const deltaResult = computeResourceDelta(undefined, resolvedValue, editedValue);
189
+
190
+ if (deltaResult.isFailure()) {
191
+ onMessage?.('warning', `Could not compute delta, saving full value: ${deltaResult.message}`);
192
+ }
193
+
194
+ const delta = deltaResult.isSuccess() ? deltaResult.value : null;
195
+
196
+ // Save the edit with original, edited, and delta
197
+ setEditedResources((prev) => {
198
+ const newMap = new Map(prev);
199
+ newMap.set(resourceId, {
200
+ originalValue: resolvedValue,
201
+ editedValue,
202
+ delta
203
+ });
204
+ return newMap;
205
+ });
206
+
207
+ // Log info about delta
208
+ if (delta !== null && delta !== editedValue) {
209
+ const deltaSize = JSON.stringify(delta).length;
210
+ const fullSize = JSON.stringify(editedValue).length;
211
+ const reduction = Math.round((1 - deltaSize / fullSize) * 100);
212
+ onMessage?.('info', `Edit saved for ${resourceId} (delta: ${reduction}% smaller)`);
213
+ } else {
214
+ onMessage?.('info', `Edit saved for resource ${resourceId}`);
215
+ }
216
+ } catch (error) {
217
+ onMessage?.(
218
+ 'error',
219
+ `Failed to save edit: ${error instanceof Error ? error.message : String(error)}`
220
+ );
221
+ }
222
+ },
223
+ [onMessage]
224
+ );
225
+
226
+ const getEditedValue = useCallback(
227
+ (resourceId: string) => {
228
+ const edit = editedResources.get(resourceId);
229
+ return edit?.editedValue;
230
+ },
231
+ [editedResources]
232
+ );
233
+
234
+ const hasEdit = useCallback(
235
+ (resourceId: string) => {
236
+ return editedResources.has(resourceId);
237
+ },
238
+ [editedResources]
239
+ );
240
+
241
+ const clearEdits = useCallback(() => {
242
+ setEditedResources(new Map());
243
+ onMessage?.('info', 'All edits cleared');
244
+ }, [onMessage]);
245
+
246
+ const discardEdits = useCallback(() => {
247
+ if (hasUnsavedEdits) {
248
+ setEditedResources(new Map());
249
+ onMessage?.('info', 'All unsaved edits discarded');
250
+ }
251
+ }, [hasUnsavedEdits, onMessage]);
252
+
253
+ const applyEdits = useCallback(async () => {
254
+ if (!processedResources || editedResources.size === 0) {
255
+ onMessage?.('warning', 'No edits to apply');
256
+ return;
257
+ }
258
+
259
+ if (!onSystemUpdate) {
260
+ onMessage?.('error', 'System update callback not provided');
261
+ return;
262
+ }
263
+
264
+ setIsApplyingEdits(true);
265
+
266
+ try {
267
+ // Extract current resolution context (filter out undefined values)
268
+ const cleanedContextValues: Record<string, string> = {};
269
+ Object.entries(contextValues).forEach(([key, value]) => {
270
+ if (value !== undefined) {
271
+ cleanedContextValues[key] = value;
272
+ }
273
+ });
274
+
275
+ const currentContext = extractResolutionContext(
276
+ currentResolver as Runtime.ResourceResolver,
277
+ cleanedContextValues
278
+ );
279
+
280
+ // Check for potential conflicts
281
+ const conflictCheck = checkEditConflicts(
282
+ processedResources.system.resourceManager,
283
+ editedResources,
284
+ currentContext
285
+ );
286
+
287
+ // Show warnings about potential conflicts
288
+ conflictCheck.warnings.forEach((warning) => onMessage?.('warning', warning));
289
+
290
+ if (conflictCheck.conflicts.length > 0) {
291
+ onMessage?.('error', `Conflicts detected: ${conflictCheck.conflicts.join(', ')}`);
292
+ return;
293
+ }
294
+
295
+ // Rebuild the system with edits
296
+ const rebuildResult = await rebuildSystemWithEdits(
297
+ processedResources.system,
298
+ editedResources,
299
+ currentContext
300
+ );
301
+
302
+ if (rebuildResult.isFailure()) {
303
+ onMessage?.('error', `Failed to apply edits: ${rebuildResult.message}`);
304
+ return;
305
+ }
306
+
307
+ // Update the system through the callback
308
+ onSystemUpdate(rebuildResult.value);
309
+
310
+ // Clear edits after successful application
311
+ setEditedResources(new Map());
312
+
313
+ onMessage?.('success', `Successfully applied ${editedResources.size} edit(s)`);
314
+ } catch (error) {
315
+ onMessage?.('error', `Error applying edits: ${error instanceof Error ? error.message : String(error)}`);
316
+ } finally {
317
+ setIsApplyingEdits(false);
318
+ }
319
+ }, [processedResources, editedResources, onSystemUpdate, currentResolver, contextValues, onMessage]);
320
+
321
+ const state: ResolutionState = {
322
+ contextValues,
323
+ pendingContextValues,
324
+ selectedResourceId,
325
+ currentResolver,
326
+ resolutionResult,
327
+ viewMode,
328
+ hasPendingChanges,
329
+ // Edit state
330
+ editedResources,
331
+ hasUnsavedEdits,
332
+ isApplyingEdits
333
+ };
334
+
335
+ const actions: ResolutionActions = {
336
+ updateContextValue,
337
+ applyContext,
338
+ selectResource,
339
+ setViewMode,
340
+ resetCache,
341
+ // Edit actions
342
+ saveEdit,
343
+ getEditedValue,
344
+ hasEdit,
345
+ clearEdits,
346
+ applyEdits,
347
+ discardEdits
348
+ };
349
+
350
+ return {
351
+ state,
352
+ actions,
353
+ availableQualifiers
354
+ };
355
+ }