@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,424 @@
1
+ import { Result, succeed, fail } from '@fgv/ts-utils';
2
+ import { Config, QualifierTypes, Qualifiers, ResourceTypes } from '@fgv/ts-res';
3
+
4
+ /**
5
+ * Configuration change tracking
6
+ */
7
+ export interface ConfigurationChanges {
8
+ hasChanges: boolean;
9
+ changedSections: string[];
10
+ timestamp: Date;
11
+ }
12
+
13
+ /**
14
+ * Configuration validation result
15
+ */
16
+ export interface ConfigurationValidationResult {
17
+ isValid: boolean;
18
+ errors: string[];
19
+ warnings: string[];
20
+ }
21
+
22
+ /**
23
+ * Configuration export options
24
+ */
25
+ export interface ConfigurationExportOptions {
26
+ format: 'json' | 'yaml';
27
+ pretty: boolean;
28
+ includeComments?: boolean;
29
+ filename?: string;
30
+ }
31
+
32
+ /**
33
+ * Predefined configuration templates
34
+ */
35
+ export interface ConfigurationTemplate {
36
+ id: string;
37
+ name: string;
38
+ description: string;
39
+ configuration: Config.Model.ISystemConfiguration;
40
+ category: 'basic' | 'intermediate' | 'advanced' | 'enterprise';
41
+ }
42
+
43
+ /**
44
+ * Default system configuration
45
+ */
46
+ export function getDefaultConfiguration(): Config.Model.ISystemConfiguration {
47
+ return {
48
+ qualifierTypes: [
49
+ {
50
+ name: 'language',
51
+ systemType: 'language'
52
+ },
53
+ {
54
+ name: 'territory',
55
+ systemType: 'territory'
56
+ }
57
+ ],
58
+ qualifiers: [
59
+ {
60
+ name: 'language',
61
+ typeName: 'language',
62
+ defaultPriority: 100,
63
+ token: 'lang'
64
+ },
65
+ {
66
+ name: 'territory',
67
+ typeName: 'territory',
68
+ defaultPriority: 90,
69
+ token: 'territory'
70
+ }
71
+ ],
72
+ resourceTypes: [
73
+ {
74
+ name: 'string',
75
+ typeName: 'string'
76
+ },
77
+ {
78
+ name: 'object',
79
+ typeName: 'object'
80
+ }
81
+ ]
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Validate a system configuration
87
+ */
88
+ export function validateConfiguration(
89
+ config: Config.Model.ISystemConfiguration
90
+ ): ConfigurationValidationResult {
91
+ const errors: string[] = [];
92
+ const warnings: string[] = [];
93
+
94
+ // Validate qualifierTypes
95
+ if (!config.qualifierTypes || config.qualifierTypes.length === 0) {
96
+ errors.push('Configuration must have at least one qualifier type');
97
+ } else {
98
+ const typeNames = new Set<string>();
99
+ (config.qualifierTypes as QualifierTypes.Config.ISystemQualifierTypeConfig[]).forEach((type, index) => {
100
+ const typeName = type.name || `<type-${index}>`;
101
+
102
+ if (!type.name) {
103
+ errors.push(`Qualifier type at index ${index} is missing a name`);
104
+ } else if (typeNames.has(type.name)) {
105
+ errors.push(`Duplicate qualifier type name: ${type.name}`);
106
+ } else {
107
+ typeNames.add(type.name);
108
+ }
109
+
110
+ if (!type.systemType) {
111
+ errors.push(`Qualifier type '${typeName}' is missing systemType`);
112
+ }
113
+ });
114
+ }
115
+
116
+ // Validate qualifiers
117
+ if (!config.qualifiers || config.qualifiers.length === 0) {
118
+ warnings.push('Configuration has no qualifiers defined');
119
+ } else {
120
+ const qualifierNames = new Set<string>();
121
+ const qualifierTypeNames = new Set(config.qualifierTypes?.map((t) => t.name) || []);
122
+
123
+ (config.qualifiers as Qualifiers.IQualifierDecl[]).forEach((qualifier, index) => {
124
+ if (!qualifier.name) {
125
+ errors.push(`Qualifier at index ${index} is missing a name`);
126
+ } else if (qualifierNames.has(qualifier.name)) {
127
+ errors.push(`Duplicate qualifier name: ${qualifier.name}`);
128
+ } else {
129
+ qualifierNames.add(qualifier.name);
130
+ }
131
+
132
+ if (!qualifier.typeName) {
133
+ errors.push(`Qualifier '${qualifier.name}' is missing typeName`);
134
+ } else if (!qualifierTypeNames.has(qualifier.typeName)) {
135
+ errors.push(`Qualifier '${qualifier.name}' references unknown qualifier type: ${qualifier.typeName}`);
136
+ }
137
+
138
+ if (qualifier.defaultPriority === undefined || qualifier.defaultPriority < 0) {
139
+ errors.push(`Qualifier '${qualifier.name}' has invalid defaultPriority`);
140
+ }
141
+ });
142
+ }
143
+
144
+ // Validate resourceTypes
145
+ if (!config.resourceTypes || config.resourceTypes.length === 0) {
146
+ errors.push('Configuration must have at least one resource type');
147
+ } else {
148
+ const resourceTypeNames = new Set<string>();
149
+ (config.resourceTypes as ResourceTypes.Config.IResourceTypeConfig[]).forEach((type, index) => {
150
+ if (!type.name) {
151
+ errors.push(`Resource type at index ${index} is missing a name`);
152
+ } else if (resourceTypeNames.has(type.name)) {
153
+ errors.push(`Duplicate resource type name: ${type.name}`);
154
+ } else {
155
+ resourceTypeNames.add(type.name);
156
+ }
157
+
158
+ if (!type.typeName) {
159
+ errors.push(`Resource type '${type.name}' is missing typeName`);
160
+ }
161
+ });
162
+ }
163
+
164
+ return {
165
+ isValid: errors.length === 0,
166
+ errors,
167
+ warnings
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Create a deep copy of a configuration
173
+ */
174
+ export function cloneConfiguration(
175
+ config: Config.Model.ISystemConfiguration
176
+ ): Config.Model.ISystemConfiguration {
177
+ return JSON.parse(JSON.stringify(config));
178
+ }
179
+
180
+ /**
181
+ * Compare two configurations for equality
182
+ */
183
+ export function compareConfigurations(
184
+ config1: Config.Model.ISystemConfiguration,
185
+ config2: Config.Model.ISystemConfiguration
186
+ ): boolean {
187
+ return JSON.stringify(config1) === JSON.stringify(config2);
188
+ }
189
+
190
+ /**
191
+ * Track changes between configurations
192
+ */
193
+ export function trackConfigurationChanges(
194
+ original: Config.Model.ISystemConfiguration,
195
+ current: Config.Model.ISystemConfiguration
196
+ ): ConfigurationChanges {
197
+ const changedSections: string[] = [];
198
+
199
+ // Check qualifierTypes
200
+ if (JSON.stringify(original.qualifierTypes) !== JSON.stringify(current.qualifierTypes)) {
201
+ changedSections.push('qualifierTypes');
202
+ }
203
+
204
+ // Check qualifiers
205
+ if (JSON.stringify(original.qualifiers) !== JSON.stringify(current.qualifiers)) {
206
+ changedSections.push('qualifiers');
207
+ }
208
+
209
+ // Check resourceTypes
210
+ if (JSON.stringify(original.resourceTypes) !== JSON.stringify(current.resourceTypes)) {
211
+ changedSections.push('resourceTypes');
212
+ }
213
+
214
+ return {
215
+ hasChanges: changedSections.length > 0,
216
+ changedSections,
217
+ timestamp: new Date()
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Export configuration to JSON string
223
+ */
224
+ export function exportConfiguration(
225
+ config: Config.Model.ISystemConfiguration,
226
+ options: ConfigurationExportOptions = { format: 'json', pretty: true }
227
+ ): Result<string> {
228
+ try {
229
+ if (options.format === 'json') {
230
+ return succeed(JSON.stringify(config, null, options.pretty ? 2 : 0));
231
+ } else {
232
+ return fail('YAML export not implemented yet');
233
+ }
234
+ } catch (error) {
235
+ return fail(`Failed to export configuration: ${error instanceof Error ? error.message : String(error)}`);
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Import configuration from JSON string
241
+ */
242
+ export function importConfiguration(data: string): Result<Config.Model.ISystemConfiguration> {
243
+ try {
244
+ const parsed = JSON.parse(data);
245
+
246
+ // Basic structure validation
247
+ if (!parsed || typeof parsed !== 'object') {
248
+ return fail('Invalid configuration: not an object');
249
+ }
250
+
251
+ const validation = validateConfiguration(parsed);
252
+ if (!validation.isValid) {
253
+ return fail(`Invalid configuration: ${validation.errors.join(', ')}`);
254
+ }
255
+
256
+ return succeed(parsed as Config.Model.ISystemConfiguration);
257
+ } catch (error) {
258
+ return fail(`Failed to parse configuration: ${error instanceof Error ? error.message : String(error)}`);
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Get predefined configuration templates
264
+ */
265
+ export function getConfigurationTemplates(): ConfigurationTemplate[] {
266
+ return [
267
+ {
268
+ id: 'basic',
269
+ name: 'Basic Configuration',
270
+ description: 'Simple language and territory-based configuration',
271
+ category: 'basic',
272
+ configuration: getDefaultConfiguration()
273
+ },
274
+ {
275
+ id: 'multilingual',
276
+ name: 'Multilingual Application',
277
+ description: 'Configuration for applications with multiple languages and regions',
278
+ category: 'intermediate',
279
+ configuration: {
280
+ qualifierTypes: [
281
+ {
282
+ name: 'language',
283
+ systemType: 'language'
284
+ },
285
+ {
286
+ name: 'territory',
287
+ systemType: 'territory'
288
+ },
289
+ {
290
+ name: 'platform',
291
+ systemType: 'literal'
292
+ }
293
+ ],
294
+ qualifiers: [
295
+ {
296
+ name: 'language',
297
+ typeName: 'language',
298
+ defaultPriority: 100,
299
+ token: 'lang'
300
+ },
301
+ {
302
+ name: 'territory',
303
+ typeName: 'territory',
304
+ defaultPriority: 90,
305
+ token: 'territory'
306
+ },
307
+ {
308
+ name: 'platform',
309
+ typeName: 'platform',
310
+ defaultPriority: 80,
311
+ token: 'platform'
312
+ }
313
+ ],
314
+ resourceTypes: [
315
+ {
316
+ name: 'string',
317
+ typeName: 'string'
318
+ },
319
+ {
320
+ name: 'object',
321
+ typeName: 'object'
322
+ },
323
+ {
324
+ name: 'array',
325
+ typeName: 'array'
326
+ }
327
+ ]
328
+ }
329
+ },
330
+ {
331
+ id: 'enterprise',
332
+ name: 'Enterprise Configuration',
333
+ description: 'Complex configuration for enterprise applications with roles and departments',
334
+ category: 'enterprise',
335
+ configuration: {
336
+ qualifierTypes: [
337
+ {
338
+ name: 'language',
339
+ systemType: 'language'
340
+ },
341
+ {
342
+ name: 'territory',
343
+ systemType: 'territory'
344
+ },
345
+ {
346
+ name: 'role',
347
+ systemType: 'literal'
348
+ },
349
+ {
350
+ name: 'department',
351
+ systemType: 'literal'
352
+ },
353
+ {
354
+ name: 'securityLevel',
355
+ systemType: 'literal'
356
+ }
357
+ ],
358
+ qualifiers: [
359
+ {
360
+ name: 'language',
361
+ typeName: 'language',
362
+ defaultPriority: 100,
363
+ token: 'lang'
364
+ },
365
+ {
366
+ name: 'territory',
367
+ typeName: 'territory',
368
+ defaultPriority: 95,
369
+ token: 'territory'
370
+ },
371
+ {
372
+ name: 'role',
373
+ typeName: 'role',
374
+ defaultPriority: 90,
375
+ token: 'role'
376
+ },
377
+ {
378
+ name: 'department',
379
+ typeName: 'department',
380
+ defaultPriority: 85,
381
+ token: 'dept'
382
+ },
383
+ {
384
+ name: 'securityLevel',
385
+ typeName: 'securityLevel',
386
+ defaultPriority: 80,
387
+ token: 'security'
388
+ }
389
+ ],
390
+ resourceTypes: [
391
+ {
392
+ name: 'string',
393
+ typeName: 'string'
394
+ },
395
+ {
396
+ name: 'localizedString',
397
+ typeName: 'string'
398
+ },
399
+ {
400
+ name: 'config',
401
+ typeName: 'object'
402
+ },
403
+ {
404
+ name: 'permissions',
405
+ typeName: 'array'
406
+ },
407
+ {
408
+ name: 'settings',
409
+ typeName: 'object'
410
+ }
411
+ ]
412
+ }
413
+ }
414
+ ];
415
+ }
416
+
417
+ /**
418
+ * Generate a filename for configuration export
419
+ */
420
+ export function generateConfigurationFilename(configName?: string, format: 'json' | 'yaml' = 'json'): string {
421
+ const timestamp = new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-');
422
+ const baseName = configName ? `${configName}-config` : 'ts-res-config';
423
+ return `${baseName}-${timestamp}.${format}`;
424
+ }
@@ -0,0 +1,160 @@
1
+ import { ImportedFile, ImportedDirectory } from '../types';
2
+
3
+ /**
4
+ * Read files from file input element
5
+ */
6
+ export async function readFilesFromInput(files: FileList): Promise<ImportedFile[]> {
7
+ const importedFiles: ImportedFile[] = [];
8
+
9
+ for (let i = 0; i < files.length; i++) {
10
+ const file = files[i];
11
+ const content = await readFileContent(file);
12
+ importedFiles.push({
13
+ name: file.name,
14
+ path: file.webkitRelativePath || file.name,
15
+ content,
16
+ type: file.type
17
+ });
18
+ }
19
+
20
+ return importedFiles;
21
+ }
22
+
23
+ /**
24
+ * Read file content as text
25
+ */
26
+ function readFileContent(file: File): Promise<string> {
27
+ return new Promise((resolve, reject) => {
28
+ const reader = new FileReader();
29
+ reader.onload = (e) => {
30
+ resolve(e.target?.result as string);
31
+ };
32
+ reader.onerror = (e) => {
33
+ reject(new Error(`Failed to read file ${file.name}: ${e}`));
34
+ };
35
+ reader.readAsText(file);
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Convert flat file list to directory structure
41
+ */
42
+ export function filesToDirectory(files: ImportedFile[]): ImportedDirectory {
43
+ // Group files by directory path
44
+ const filesByPath = new Map<string, ImportedFile[]>();
45
+ const dirPaths = new Set<string>();
46
+
47
+ files.forEach((file) => {
48
+ if (file.path) {
49
+ const parts = file.path.split('/');
50
+ if (parts.length > 1) {
51
+ // File is in a subdirectory
52
+ const dirPath = parts.slice(0, -1).join('/');
53
+ dirPaths.add(dirPath);
54
+
55
+ if (!filesByPath.has(dirPath)) {
56
+ filesByPath.set(dirPath, []);
57
+ }
58
+ filesByPath.get(dirPath)!.push({
59
+ ...file,
60
+ name: parts[parts.length - 1]
61
+ });
62
+ } else {
63
+ // File is in root
64
+ if (!filesByPath.has('')) {
65
+ filesByPath.set('', []);
66
+ }
67
+ filesByPath.get('')!.push(file);
68
+ }
69
+ } else {
70
+ // No path, add to root
71
+ if (!filesByPath.has('')) {
72
+ filesByPath.set('', []);
73
+ }
74
+ filesByPath.get('')!.push(file);
75
+ }
76
+ });
77
+
78
+ // Build directory tree
79
+ const buildDirectory = (path: string, name: string): ImportedDirectory => {
80
+ const dir: ImportedDirectory = {
81
+ name,
82
+ path,
83
+ files: filesByPath.get(path) || [],
84
+ subdirectories: []
85
+ };
86
+
87
+ // Find subdirectories
88
+ const prefix = path ? `${path}/` : '';
89
+ dirPaths.forEach((dirPath) => {
90
+ if (dirPath.startsWith(prefix)) {
91
+ const remaining = dirPath.slice(prefix.length);
92
+ if (remaining && !remaining.includes('/')) {
93
+ // This is a direct subdirectory
94
+ dir.subdirectories!.push(buildDirectory(dirPath, remaining));
95
+ }
96
+ }
97
+ });
98
+
99
+ return dir;
100
+ };
101
+
102
+ return buildDirectory('', 'root');
103
+ }
104
+
105
+ /**
106
+ * Export data as JSON file
107
+ */
108
+ export function exportAsJson(data: any, filename: string): void {
109
+ const json = JSON.stringify(data, null, 2);
110
+ const blob = new Blob([json], { type: 'application/json' });
111
+ const url = URL.createObjectURL(blob);
112
+
113
+ const a = document.createElement('a');
114
+ a.href = url;
115
+ a.download = filename;
116
+ document.body.appendChild(a);
117
+ a.click();
118
+ document.body.removeChild(a);
119
+ URL.revokeObjectURL(url);
120
+ }
121
+
122
+ /**
123
+ * Export data using File System Access API if available
124
+ */
125
+ export async function exportUsingFileSystemAPI(
126
+ data: any,
127
+ suggestedName: string,
128
+ description: string = 'JSON files'
129
+ ): Promise<boolean> {
130
+ if (!('showSaveFilePicker' in window)) {
131
+ return false;
132
+ }
133
+
134
+ try {
135
+ const fileHandle = await (window as any).showSaveFilePicker({
136
+ suggestedName,
137
+ types: [
138
+ {
139
+ description,
140
+ accept: {
141
+ 'application/json': ['.json']
142
+ }
143
+ }
144
+ ]
145
+ });
146
+
147
+ const json = JSON.stringify(data, null, 2);
148
+ const writable = await fileHandle.createWritable();
149
+ await writable.write(json);
150
+ await writable.close();
151
+
152
+ return true;
153
+ } catch (error) {
154
+ // User cancelled or other error
155
+ if ((error as Error).name === 'AbortError') {
156
+ return false;
157
+ }
158
+ throw error;
159
+ }
160
+ }