@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,214 @@
1
+ import React, { useState, useCallback, useMemo } from 'react';
2
+ import { JsonEditor } from 'json-edit-react';
3
+ import { PencilIcon, CheckIcon, XMarkIcon } from '@heroicons/react/24/outline';
4
+ import { validateEditedResource } from '../../../utils/resolutionEditing';
5
+
6
+ export interface EditableJsonViewProps {
7
+ /** The original JSON value */
8
+ value: any;
9
+ /** The resource ID for tracking edits */
10
+ resourceId: string;
11
+ /** Whether this resource has been edited */
12
+ isEdited?: boolean;
13
+ /** The current edited value if any */
14
+ editedValue?: any;
15
+ /** Callback when the user saves an edit */
16
+ onSave?: (resourceId: string, editedValue: any, originalValue: any) => void;
17
+ /** Callback when the user cancels an edit */
18
+ onCancel?: (resourceId: string) => void;
19
+ /** Whether editing is currently disabled */
20
+ disabled?: boolean;
21
+ /** Additional CSS classes */
22
+ className?: string;
23
+ }
24
+
25
+ export const EditableJsonView: React.FC<EditableJsonViewProps> = ({
26
+ value,
27
+ resourceId,
28
+ isEdited = false,
29
+ editedValue,
30
+ onSave,
31
+ onCancel,
32
+ disabled = false,
33
+ className = ''
34
+ }) => {
35
+ const [isEditing, setIsEditing] = useState(false);
36
+ const [currentEditValue, setCurrentEditValue] = useState<any>(null);
37
+ const [validationErrors, setValidationErrors] = useState<string[]>([]);
38
+
39
+ // The display value is either the edited value or the original value
40
+ const displayValue = useMemo(() => {
41
+ if (isEdited && editedValue !== undefined) {
42
+ return editedValue;
43
+ }
44
+ return value;
45
+ }, [value, editedValue, isEdited]);
46
+
47
+ // Handle starting an edit
48
+ const handleStartEdit = useCallback(() => {
49
+ if (disabled) return;
50
+ setCurrentEditValue(displayValue);
51
+ setIsEditing(true);
52
+ setValidationErrors([]);
53
+ }, [displayValue, disabled]);
54
+
55
+ // Handle canceling an edit
56
+ const handleCancelEdit = useCallback(() => {
57
+ setIsEditing(false);
58
+ setCurrentEditValue(null);
59
+ setValidationErrors([]);
60
+ onCancel?.(resourceId);
61
+ }, [resourceId, onCancel]);
62
+
63
+ // Handle saving an edit
64
+ const handleSaveEdit = useCallback(() => {
65
+ if (!onSave || currentEditValue === null) return;
66
+
67
+ // Validate the edited value
68
+ const validation = validateEditedResource(currentEditValue);
69
+ if (!validation.isValid) {
70
+ setValidationErrors(validation.errors);
71
+ return;
72
+ }
73
+
74
+ // Save the edit
75
+ onSave(resourceId, currentEditValue, value);
76
+ setIsEditing(false);
77
+ setCurrentEditValue(null);
78
+ setValidationErrors([]);
79
+ }, [resourceId, currentEditValue, value, onSave]);
80
+
81
+ // Handle changes in the JSON editor
82
+ const handleJsonChange = useCallback(
83
+ (newValue: any) => {
84
+ setCurrentEditValue(newValue);
85
+
86
+ // Clear validation errors when user starts typing
87
+ if (validationErrors.length > 0) {
88
+ setValidationErrors([]);
89
+ }
90
+ },
91
+ [validationErrors]
92
+ );
93
+
94
+ // JSON editor configuration
95
+ const jsonEditConfig = useMemo(
96
+ () => ({
97
+ minHeight: '200px',
98
+ maxHeight: '400px',
99
+ style: {
100
+ container: {
101
+ backgroundColor: '#f9fafb',
102
+ border: '1px solid #d1d5db',
103
+ borderRadius: '0.375rem',
104
+ fontFamily:
105
+ 'ui-monospace, SFMono-Regular, "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
106
+ },
107
+ key: {
108
+ color: '#1f2937',
109
+ fontWeight: '500'
110
+ },
111
+ string: {
112
+ color: '#059669'
113
+ },
114
+ number: {
115
+ color: '#dc2626'
116
+ },
117
+ boolean: {
118
+ color: '#7c3aed'
119
+ },
120
+ null: {
121
+ color: '#6b7280'
122
+ }
123
+ },
124
+ enableHighlight: true,
125
+ enableClipboard: true
126
+ }),
127
+ []
128
+ );
129
+
130
+ return (
131
+ <div className={`bg-white rounded-lg border ${className}`}>
132
+ {/* Header with edit controls */}
133
+ <div className="flex items-center justify-between p-3 border-b bg-gray-50">
134
+ <div className="flex items-center space-x-2">
135
+ <h4 className="text-sm font-semibold text-gray-900">Resource Content</h4>
136
+ {isEdited && (
137
+ <span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
138
+ Edited
139
+ </span>
140
+ )}
141
+ </div>
142
+
143
+ {!isEditing && (
144
+ <button
145
+ onClick={handleStartEdit}
146
+ disabled={disabled}
147
+ className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
148
+ title="Edit resource content"
149
+ >
150
+ <PencilIcon className="h-4 w-4 mr-1" />
151
+ Edit
152
+ </button>
153
+ )}
154
+
155
+ {isEditing && (
156
+ <div className="flex items-center space-x-2">
157
+ <button
158
+ onClick={handleSaveEdit}
159
+ disabled={validationErrors.length > 0}
160
+ className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-green-600 border border-transparent rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed"
161
+ title="Save changes"
162
+ >
163
+ <CheckIcon className="h-4 w-4 mr-1" />
164
+ Save
165
+ </button>
166
+ <button
167
+ onClick={handleCancelEdit}
168
+ className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
169
+ title="Cancel changes"
170
+ >
171
+ <XMarkIcon className="h-4 w-4 mr-1" />
172
+ Cancel
173
+ </button>
174
+ </div>
175
+ )}
176
+ </div>
177
+
178
+ {/* Validation errors */}
179
+ {validationErrors.length > 0 && (
180
+ <div className="p-3 border-b bg-red-50">
181
+ <div className="text-sm text-red-800">
182
+ <p className="font-medium mb-1">Validation Errors:</p>
183
+ <ul className="list-disc list-inside space-y-1">
184
+ {validationErrors.map((error, index) => (
185
+ <li key={index}>{error}</li>
186
+ ))}
187
+ </ul>
188
+ </div>
189
+ </div>
190
+ )}
191
+
192
+ {/* JSON content */}
193
+ <div className="p-3">
194
+ {isEditing ? (
195
+ <JsonEditor data={currentEditValue} setData={handleJsonChange} {...jsonEditConfig} />
196
+ ) : (
197
+ <pre className="text-sm font-mono text-gray-800 bg-gray-50 p-3 rounded border overflow-x-auto whitespace-pre-wrap">
198
+ {JSON.stringify(displayValue, null, 2)}
199
+ </pre>
200
+ )}
201
+ </div>
202
+
203
+ {/* Help text */}
204
+ {isEditing && (
205
+ <div className="px-3 pb-3">
206
+ <p className="text-xs text-gray-500">
207
+ Edit the JSON content above. Changes will be saved as a new candidate with the current resolution
208
+ context.
209
+ </p>
210
+ </div>
211
+ )}
212
+ </div>
213
+ );
214
+ };
@@ -0,0 +1,170 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ CheckIcon,
4
+ XMarkIcon,
5
+ ExclamationTriangleIcon,
6
+ ArrowPathIcon,
7
+ DocumentTextIcon
8
+ } from '@heroicons/react/24/outline';
9
+
10
+ export interface ResolutionEditControlsProps {
11
+ /** Number of unsaved edits */
12
+ editCount: number;
13
+ /** Whether edit application is currently in progress */
14
+ isApplying: boolean;
15
+ /** Whether any edits exist to operate on */
16
+ hasEdits: boolean;
17
+ /** Callback to apply all pending edits */
18
+ onApplyEdits?: () => Promise<void>;
19
+ /** Callback to discard all pending edits */
20
+ onDiscardEdits?: () => void;
21
+ /** Whether the controls should be disabled */
22
+ disabled?: boolean;
23
+ /** Additional CSS classes */
24
+ className?: string;
25
+ }
26
+
27
+ export const ResolutionEditControls: React.FC<ResolutionEditControlsProps> = ({
28
+ editCount,
29
+ isApplying,
30
+ hasEdits,
31
+ onApplyEdits,
32
+ onDiscardEdits,
33
+ disabled = false,
34
+ className = ''
35
+ }) => {
36
+ const [showDiscardConfirm, setShowDiscardConfirm] = useState(false);
37
+
38
+ const handleApplyEdits = async () => {
39
+ if (onApplyEdits && !isApplying && hasEdits) {
40
+ await onApplyEdits();
41
+ }
42
+ };
43
+
44
+ const handleDiscardEdits = () => {
45
+ if (onDiscardEdits && hasEdits && !isApplying) {
46
+ onDiscardEdits();
47
+ setShowDiscardConfirm(false);
48
+ }
49
+ };
50
+
51
+ const handleDiscardClick = () => {
52
+ if (hasEdits) {
53
+ setShowDiscardConfirm(true);
54
+ }
55
+ };
56
+
57
+ if (!hasEdits && !isApplying) {
58
+ return (
59
+ <div className={`bg-gray-50 rounded-lg border border-gray-200 p-4 ${className}`}>
60
+ <div className="flex items-center justify-center text-gray-500">
61
+ <DocumentTextIcon className="h-5 w-5 mr-2" />
62
+ <span className="text-sm">No pending edits</span>
63
+ </div>
64
+ </div>
65
+ );
66
+ }
67
+
68
+ return (
69
+ <div className={`bg-white rounded-lg border border-gray-200 shadow-sm ${className}`}>
70
+ <div className="p-4">
71
+ {/* Header */}
72
+ <div className="flex items-center justify-between mb-4">
73
+ <div className="flex items-center">
74
+ <div className="flex items-center space-x-2">
75
+ <h3 className="text-lg font-semibold text-gray-900">Pending Edits</h3>
76
+ <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
77
+ {editCount} edit{editCount !== 1 ? 's' : ''}
78
+ </span>
79
+ </div>
80
+ </div>
81
+
82
+ {isApplying && (
83
+ <div className="flex items-center text-blue-600">
84
+ <ArrowPathIcon className="h-4 w-4 mr-1 animate-spin" />
85
+ <span className="text-sm font-medium">Applying...</span>
86
+ </div>
87
+ )}
88
+ </div>
89
+
90
+ {/* Info text */}
91
+ <p className="text-sm text-gray-600 mb-4">
92
+ You have {editCount} unsaved edit{editCount !== 1 ? 's' : ''}.{' '}
93
+ {editCount === 1 ? 'This edit' : 'These edits'} will be applied as new candidate
94
+ {editCount !== 1 ? 's' : ''} with the current resolution context.
95
+ </p>
96
+
97
+ {/* Action buttons */}
98
+ {!showDiscardConfirm ? (
99
+ <div className="flex items-center space-x-3">
100
+ <button
101
+ onClick={handleApplyEdits}
102
+ disabled={disabled || isApplying || !hasEdits}
103
+ className="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-green-600 border border-transparent rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed"
104
+ >
105
+ {isApplying ? (
106
+ <>
107
+ <ArrowPathIcon className="h-4 w-4 mr-2 animate-spin" />
108
+ Applying Edits...
109
+ </>
110
+ ) : (
111
+ <>
112
+ <CheckIcon className="h-4 w-4 mr-2" />
113
+ Apply Edits
114
+ </>
115
+ )}
116
+ </button>
117
+
118
+ <button
119
+ onClick={handleDiscardClick}
120
+ disabled={disabled || isApplying || !hasEdits}
121
+ className="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
122
+ >
123
+ <XMarkIcon className="h-4 w-4 mr-2" />
124
+ Discard Edits
125
+ </button>
126
+ </div>
127
+ ) : (
128
+ /* Discard confirmation */
129
+ <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
130
+ <div className="flex items-start">
131
+ <ExclamationTriangleIcon className="h-5 w-5 text-yellow-400 mt-0.5 mr-3" />
132
+ <div className="flex-1">
133
+ <h4 className="text-sm font-medium text-yellow-800 mb-1">Confirm Discard</h4>
134
+ <p className="text-sm text-yellow-700 mb-3">
135
+ Are you sure you want to discard {editCount} unsaved edit{editCount !== 1 ? 's' : ''}? This
136
+ action cannot be undone.
137
+ </p>
138
+ <div className="flex items-center space-x-3">
139
+ <button
140
+ onClick={handleDiscardEdits}
141
+ className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-red-600 border border-transparent rounded hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
142
+ >
143
+ <XMarkIcon className="h-4 w-4 mr-1" />
144
+ Yes, Discard
145
+ </button>
146
+ <button
147
+ onClick={() => setShowDiscardConfirm(false)}
148
+ className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
149
+ >
150
+ Cancel
151
+ </button>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ )}
157
+ </div>
158
+
159
+ {/* Warning about system rebuild */}
160
+ {hasEdits && !showDiscardConfirm && (
161
+ <div className="border-t border-gray-200 px-4 py-3 bg-blue-50">
162
+ <p className="text-xs text-blue-700">
163
+ <strong>Note:</strong> Applying edits will rebuild the entire resource system with your changes.
164
+ This may take a moment and will update all resolution results.
165
+ </p>
166
+ </div>
167
+ )}
168
+ </div>
169
+ );
170
+ };