@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.
- package/.rush/temp/03c8b056281d9db0a97d8a6e25eea798a160d393.tar.log +271 -0
- package/.rush/temp/chunked-rush-logs/ts-res-ui-components.build.chunks.jsonl +9 -0
- package/.rush/temp/operation/build/all.log +9 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +1111 -0
- package/README.md +18 -0
- package/REFACTORING_PLAN.md +171 -0
- package/config/jest.config.json +16 -0
- package/config/jest.setup.js +64 -0
- package/config/rig.json +16 -0
- package/lib/components/common/QualifierContextControl.d.ts +14 -0
- package/lib/components/common/QualifierContextControl.d.ts.map +1 -0
- package/lib/components/common/QualifierContextControl.js +78 -0
- package/lib/components/common/QualifierContextControl.js.map +1 -0
- package/lib/components/common/ResourceListView.d.ts +11 -0
- package/lib/components/common/ResourceListView.d.ts.map +1 -0
- package/lib/components/common/ResourceListView.js +20 -0
- package/lib/components/common/ResourceListView.js.map +1 -0
- package/lib/components/common/ResourceTreeView.d.ts +12 -0
- package/lib/components/common/ResourceTreeView.d.ts.map +1 -0
- package/lib/components/common/ResourceTreeView.js +162 -0
- package/lib/components/common/ResourceTreeView.js.map +1 -0
- package/lib/components/forms/HierarchyEditor.d.ts +10 -0
- package/lib/components/forms/HierarchyEditor.d.ts.map +1 -0
- package/lib/components/forms/HierarchyEditor.js +106 -0
- package/lib/components/forms/HierarchyEditor.js.map +1 -0
- package/lib/components/forms/QualifierEditForm.d.ts +11 -0
- package/lib/components/forms/QualifierEditForm.d.ts.map +1 -0
- package/lib/components/forms/QualifierEditForm.js +181 -0
- package/lib/components/forms/QualifierEditForm.js.map +1 -0
- package/lib/components/forms/QualifierTypeEditForm.d.ts +10 -0
- package/lib/components/forms/QualifierTypeEditForm.d.ts.map +1 -0
- package/lib/components/forms/QualifierTypeEditForm.js +172 -0
- package/lib/components/forms/QualifierTypeEditForm.js.map +1 -0
- package/lib/components/forms/ResourceTypeEditForm.d.ts +10 -0
- package/lib/components/forms/ResourceTypeEditForm.d.ts.map +1 -0
- package/lib/components/forms/ResourceTypeEditForm.js +188 -0
- package/lib/components/forms/ResourceTypeEditForm.js.map +1 -0
- package/lib/components/forms/index.d.ts +9 -0
- package/lib/components/forms/index.d.ts.map +1 -0
- package/lib/components/forms/index.js +5 -0
- package/lib/components/forms/index.js.map +1 -0
- package/lib/components/orchestrator/ResourceOrchestrator.d.ts +14 -0
- package/lib/components/orchestrator/ResourceOrchestrator.d.ts.map +1 -0
- package/lib/components/orchestrator/ResourceOrchestrator.js +278 -0
- package/lib/components/orchestrator/ResourceOrchestrator.js.map +1 -0
- package/lib/components/views/CompiledView/index.d.ts +5 -0
- package/lib/components/views/CompiledView/index.d.ts.map +1 -0
- package/lib/components/views/CompiledView/index.js +595 -0
- package/lib/components/views/CompiledView/index.js.map +1 -0
- package/lib/components/views/ConfigurationView/index.d.ts +5 -0
- package/lib/components/views/ConfigurationView/index.d.ts.map +1 -0
- package/lib/components/views/ConfigurationView/index.js +363 -0
- package/lib/components/views/ConfigurationView/index.js.map +1 -0
- package/lib/components/views/FilterView/index.d.ts +5 -0
- package/lib/components/views/FilterView/index.d.ts.map +1 -0
- package/lib/components/views/FilterView/index.js +463 -0
- package/lib/components/views/FilterView/index.js.map +1 -0
- package/lib/components/views/ImportView/index.d.ts +5 -0
- package/lib/components/views/ImportView/index.d.ts.map +1 -0
- package/lib/components/views/ImportView/index.js +514 -0
- package/lib/components/views/ImportView/index.js.map +1 -0
- package/lib/components/views/ResolutionView/EditableJsonView.d.ts +21 -0
- package/lib/components/views/ResolutionView/EditableJsonView.d.ts.map +1 -0
- package/lib/components/views/ResolutionView/EditableJsonView.js +109 -0
- package/lib/components/views/ResolutionView/EditableJsonView.js.map +1 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts +19 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts.map +1 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.js +82 -0
- package/lib/components/views/ResolutionView/ResolutionEditControls.js.map +1 -0
- package/lib/components/views/ResolutionView/index.d.ts +5 -0
- package/lib/components/views/ResolutionView/index.d.ts.map +1 -0
- package/lib/components/views/ResolutionView/index.js +255 -0
- package/lib/components/views/ResolutionView/index.js.map +1 -0
- package/lib/components/views/SourceView/index.d.ts +5 -0
- package/lib/components/views/SourceView/index.d.ts.map +1 -0
- package/lib/components/views/SourceView/index.js +316 -0
- package/lib/components/views/SourceView/index.js.map +1 -0
- package/lib/components/views/ZipLoaderView/index.d.ts +5 -0
- package/lib/components/views/ZipLoaderView/index.d.ts.map +1 -0
- package/lib/components/views/ZipLoaderView/index.js +313 -0
- package/lib/components/views/ZipLoaderView/index.js.map +1 -0
- package/lib/hooks/useConfigurationState.d.ts +46 -0
- package/lib/hooks/useConfigurationState.d.ts.map +1 -0
- package/lib/hooks/useConfigurationState.js +239 -0
- package/lib/hooks/useConfigurationState.js.map +1 -0
- package/lib/hooks/useFilterState.d.ts +7 -0
- package/lib/hooks/useFilterState.d.ts.map +1 -0
- package/lib/hooks/useFilterState.js +80 -0
- package/lib/hooks/useFilterState.js.map +1 -0
- package/lib/hooks/useResolutionState.d.ts +8 -0
- package/lib/hooks/useResolutionState.d.ts.map +1 -0
- package/lib/hooks/useResolutionState.js +253 -0
- package/lib/hooks/useResolutionState.js.map +1 -0
- package/lib/hooks/useResourceData.d.ts +19 -0
- package/lib/hooks/useResourceData.d.ts.map +1 -0
- package/lib/hooks/useResourceData.js +368 -0
- package/lib/hooks/useResourceData.js.map +1 -0
- package/lib/hooks/useViewState.d.ts +10 -0
- package/lib/hooks/useViewState.d.ts.map +1 -0
- package/lib/hooks/useViewState.js +29 -0
- package/lib/hooks/useViewState.js.map +1 -0
- package/lib/index.d.ts +27 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +34 -0
- package/lib/index.js.map +1 -0
- package/lib/test/helpers/testDataLoader.d.ts +37 -0
- package/lib/test/helpers/testDataLoader.d.ts.map +1 -0
- package/lib/test/helpers/testDataLoader.js +171 -0
- package/lib/test/helpers/testDataLoader.js.map +1 -0
- package/lib/test/unit/utils/configurationUtils.test.d.ts +2 -0
- package/lib/test/unit/utils/configurationUtils.test.d.ts.map +1 -0
- package/lib/test/unit/utils/configurationUtils.test.js +497 -0
- package/lib/test/unit/utils/configurationUtils.test.js.map +1 -0
- package/lib/test/unit/utils/fileProcessing.test.d.ts +2 -0
- package/lib/test/unit/utils/fileProcessing.test.d.ts.map +1 -0
- package/lib/test/unit/utils/fileProcessing.test.js +321 -0
- package/lib/test/unit/utils/fileProcessing.test.js.map +1 -0
- package/lib/test/unit/utils/filterResources.test.d.ts +2 -0
- package/lib/test/unit/utils/filterResources.test.d.ts.map +1 -0
- package/lib/test/unit/utils/filterResources.test.js +403 -0
- package/lib/test/unit/utils/filterResources.test.js.map +1 -0
- package/lib/test/unit/utils/resolutionEditing.test.d.ts +2 -0
- package/lib/test/unit/utils/resolutionEditing.test.d.ts.map +1 -0
- package/lib/test/unit/utils/resolutionEditing.test.js +439 -0
- package/lib/test/unit/utils/resolutionEditing.test.js.map +1 -0
- package/lib/test/unit/utils/resolutionUtils.test.d.ts +2 -0
- package/lib/test/unit/utils/resolutionUtils.test.d.ts.map +1 -0
- package/lib/test/unit/utils/resolutionUtils.test.js +397 -0
- package/lib/test/unit/utils/resolutionUtils.test.js.map +1 -0
- package/lib/test/unit/utils/tsResIntegration.test.d.ts +2 -0
- package/lib/test/unit/utils/tsResIntegration.test.d.ts.map +1 -0
- package/lib/test/unit/utils/tsResIntegration.test.js +376 -0
- package/lib/test/unit/utils/tsResIntegration.test.js.map +1 -0
- package/lib/types/index.d.ts +251 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +2 -0
- package/lib/types/index.js.map +1 -0
- package/lib/utils/configurationUtils.d.ts +74 -0
- package/lib/utils/configurationUtils.d.ts.map +1 -0
- package/lib/utils/configurationUtils.js +359 -0
- package/lib/utils/configurationUtils.js.map +1 -0
- package/lib/utils/fileProcessing.d.ts +18 -0
- package/lib/utils/fileProcessing.d.ts.map +1 -0
- package/lib/utils/fileProcessing.js +142 -0
- package/lib/utils/fileProcessing.js.map +1 -0
- package/lib/utils/filterResources.d.ts +38 -0
- package/lib/utils/filterResources.d.ts.map +1 -0
- package/lib/utils/filterResources.js +153 -0
- package/lib/utils/filterResources.js.map +1 -0
- package/lib/utils/resolutionEditing.d.ts +58 -0
- package/lib/utils/resolutionEditing.d.ts.map +1 -0
- package/lib/utils/resolutionEditing.js +246 -0
- package/lib/utils/resolutionEditing.js.map +1 -0
- package/lib/utils/resolutionUtils.d.ts +28 -0
- package/lib/utils/resolutionUtils.d.ts.map +1 -0
- package/lib/utils/resolutionUtils.js +216 -0
- package/lib/utils/resolutionUtils.js.map +1 -0
- package/lib/utils/tsResIntegration.d.ts +71 -0
- package/lib/utils/tsResIntegration.d.ts.map +1 -0
- package/lib/utils/tsResIntegration.js +294 -0
- package/lib/utils/tsResIntegration.js.map +1 -0
- package/lib/utils/zipLoader/browserZipLoader.d.ts +48 -0
- package/lib/utils/zipLoader/browserZipLoader.d.ts.map +1 -0
- package/lib/utils/zipLoader/browserZipLoader.js +247 -0
- package/lib/utils/zipLoader/browserZipLoader.js.map +1 -0
- package/lib/utils/zipLoader/index.d.ts +8 -0
- package/lib/utils/zipLoader/index.d.ts.map +1 -0
- package/lib/utils/zipLoader/index.js +13 -0
- package/lib/utils/zipLoader/index.js.map +1 -0
- package/lib/utils/zipLoader/nodeZipBuilder.d.ts +55 -0
- package/lib/utils/zipLoader/nodeZipBuilder.d.ts.map +1 -0
- package/lib/utils/zipLoader/nodeZipBuilder.js +98 -0
- package/lib/utils/zipLoader/nodeZipBuilder.js.map +1 -0
- package/lib/utils/zipLoader/types.d.ts +139 -0
- package/lib/utils/zipLoader/types.d.ts.map +1 -0
- package/lib/utils/zipLoader/types.js +2 -0
- package/lib/utils/zipLoader/types.js.map +1 -0
- package/lib/utils/zipLoader/zipUtils.d.ts +53 -0
- package/lib/utils/zipLoader/zipUtils.d.ts.map +1 -0
- package/lib/utils/zipLoader/zipUtils.js +229 -0
- package/lib/utils/zipLoader/zipUtils.js.map +1 -0
- package/package.json +69 -0
- package/rush-logs/ts-res-ui-components.build.cache.log +3 -0
- package/rush-logs/ts-res-ui-components.build.log +9 -0
- package/src/components/common/QualifierContextControl.tsx +151 -0
- package/src/components/common/ResourceListView.tsx +63 -0
- package/src/components/common/ResourceTreeView.tsx +271 -0
- package/src/components/forms/HierarchyEditor.tsx +204 -0
- package/src/components/forms/QualifierEditForm.tsx +355 -0
- package/src/components/forms/QualifierTypeEditForm.tsx +347 -0
- package/src/components/forms/ResourceTypeEditForm.tsx +331 -0
- package/src/components/forms/index.ts +11 -0
- package/src/components/orchestrator/ResourceOrchestrator.tsx +372 -0
- package/src/components/views/CompiledView/index.tsx +922 -0
- package/src/components/views/ConfigurationView/index.tsx +800 -0
- package/src/components/views/FilterView/index.tsx +825 -0
- package/src/components/views/ImportView/index.tsx +717 -0
- package/src/components/views/ResolutionView/EditableJsonView.tsx +214 -0
- package/src/components/views/ResolutionView/ResolutionEditControls.tsx +170 -0
- package/src/components/views/ResolutionView/index.tsx +591 -0
- package/src/components/views/SourceView/index.tsx +536 -0
- package/src/components/views/ZipLoaderView/index.tsx +485 -0
- package/src/hooks/useConfigurationState.ts +374 -0
- package/src/hooks/useFilterState.ts +97 -0
- package/src/hooks/useResolutionState.ts +355 -0
- package/src/hooks/useResourceData.ts +467 -0
- package/src/hooks/useViewState.ts +44 -0
- package/src/index.ts +45 -0
- package/src/test/helpers/testDataLoader.ts +195 -0
- package/src/test/unit/utils/configurationUtils.test.ts +630 -0
- package/src/test/unit/utils/fileProcessing.test.ts +391 -0
- package/src/test/unit/utils/filterResources.test.ts +574 -0
- package/src/test/unit/utils/resolutionEditing.test.ts +556 -0
- package/src/test/unit/utils/resolutionUtils.test.ts +521 -0
- package/src/test/unit/utils/tsResIntegration.test.ts +433 -0
- package/src/types/index.ts +322 -0
- package/src/utils/configurationUtils.ts +424 -0
- package/src/utils/fileProcessing.ts +160 -0
- package/src/utils/filterResources.ts +206 -0
- package/src/utils/resolutionEditing.ts +319 -0
- package/src/utils/resolutionUtils.ts +289 -0
- package/src/utils/tsResIntegration.ts +440 -0
- package/src/utils/zipLoader/browserZipLoader.ts +319 -0
- package/src/utils/zipLoader/index.ts +26 -0
- package/src/utils/zipLoader/nodeZipBuilder.ts +153 -0
- package/src/utils/zipLoader/types.ts +175 -0
- package/src/utils/zipLoader/zipUtils.ts +266 -0
- package/temp/build/typescript/ts_gZid87Hu.json +1 -0
- 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
|
+
}
|