@fgv/ts-res-ui-components 5.0.0-21 → 5.0.0-23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +401 -155
- package/config/jest.setup.js +10 -0
- package/dist/ts-res-ui-components.d.ts +1657 -76
- package/lib/components/common/QualifierContextControl.js +4 -1
- package/lib/components/common/ResourceTreeView.js +4 -1
- package/lib/components/forms/GenericQualifierTypeEditForm.d.ts +26 -0
- package/lib/components/forms/GenericQualifierTypeEditForm.js +166 -0
- package/lib/components/forms/QualifierEditForm.d.ts +1 -1
- package/lib/components/forms/index.d.ts +2 -0
- package/lib/components/forms/index.js +1 -0
- package/lib/components/orchestrator/ResourceOrchestrator.d.ts +3 -0
- package/lib/components/orchestrator/ResourceOrchestrator.js +118 -51
- package/lib/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
- package/lib/components/pickers/ResourcePicker/index.js +4 -2
- package/lib/components/views/CompiledView/index.js +75 -16
- package/lib/components/views/ConfigurationView/index.js +94 -35
- package/lib/components/views/FilterView/index.js +7 -4
- package/lib/components/views/GridView/EditableGridCell.d.ts +76 -0
- package/lib/components/views/GridView/EditableGridCell.js +224 -0
- package/lib/components/views/GridView/GridSelector.d.ts +43 -0
- package/lib/components/views/GridView/GridSelector.js +89 -0
- package/lib/components/views/GridView/MultiGridView.d.ts +85 -0
- package/lib/components/views/GridView/MultiGridView.js +196 -0
- package/lib/components/views/GridView/ResourceGrid.d.ts +38 -0
- package/lib/components/views/GridView/ResourceGrid.js +232 -0
- package/lib/components/views/GridView/SharedContextControls.d.ts +47 -0
- package/lib/components/views/GridView/SharedContextControls.js +95 -0
- package/lib/components/views/GridView/cells/BooleanCell.d.ts +44 -0
- package/lib/components/views/GridView/cells/BooleanCell.js +49 -0
- package/lib/components/views/GridView/cells/DropdownCell.d.ts +58 -0
- package/lib/components/views/GridView/cells/DropdownCell.js +182 -0
- package/lib/components/views/GridView/cells/StringCell.d.ts +57 -0
- package/lib/components/views/GridView/cells/StringCell.js +106 -0
- package/lib/components/views/GridView/cells/TriStateCell.d.ts +54 -0
- package/lib/components/views/GridView/cells/TriStateCell.js +112 -0
- package/lib/components/views/GridView/cells/index.d.ts +15 -0
- package/lib/components/views/GridView/cells/index.js +11 -0
- package/lib/components/views/GridView/index.d.ts +53 -0
- package/lib/components/views/GridView/index.js +212 -0
- package/lib/components/views/ImportView/index.js +22 -19
- package/lib/components/views/MessagesWindow/index.js +4 -1
- package/lib/components/views/ResolutionView/index.js +8 -5
- package/lib/contexts/ObservabilityContext.d.ts +85 -0
- package/lib/contexts/ObservabilityContext.js +98 -0
- package/lib/contexts/index.d.ts +2 -0
- package/lib/contexts/index.js +24 -0
- package/lib/hooks/useConfigurationState.d.ts +3 -3
- package/lib/hooks/useResolutionState.js +850 -246
- package/lib/hooks/useResourceData.d.ts +7 -4
- package/lib/hooks/useResourceData.js +185 -184
- package/lib/index.d.ts +5 -1
- package/lib/index.js +8 -1
- package/lib/namespaces/GridTools.d.ts +136 -0
- package/lib/namespaces/GridTools.js +138 -0
- package/lib/namespaces/ObservabilityTools.d.ts +3 -0
- package/lib/namespaces/ObservabilityTools.js +23 -0
- package/lib/namespaces/ResolutionTools.d.ts +2 -1
- package/lib/namespaces/ResolutionTools.js +2 -0
- package/lib/namespaces/index.d.ts +2 -0
- package/lib/namespaces/index.js +2 -0
- package/lib/test/integration/observability.integration.test.d.ts +2 -0
- package/lib/test/unit/hooks/useResourceData.test.d.ts +2 -0
- package/lib/test/unit/utils/downloadHelper.test.d.ts +2 -0
- package/lib/test/unit/workflows/resolutionWorkflows.test.d.ts +2 -0
- package/lib/test/unit/workflows/resourceCreation.test.d.ts +2 -0
- package/lib/test/unit/workflows/resultPatternExtensions.test.d.ts +2 -0
- package/lib/test/unit/workflows/validation.test.d.ts +2 -0
- package/lib/types/index.d.ts +387 -20
- package/lib/types/index.js +2 -1
- package/lib/utils/cellValidation.d.ts +113 -0
- package/lib/utils/cellValidation.js +248 -0
- package/lib/utils/downloadHelper.d.ts +66 -0
- package/lib/utils/downloadHelper.js +195 -0
- package/lib/utils/observability/factories.d.ts +29 -0
- package/lib/utils/observability/factories.js +58 -0
- package/lib/utils/observability/implementations.d.ts +61 -0
- package/lib/utils/observability/implementations.js +103 -0
- package/lib/utils/observability/index.d.ts +4 -0
- package/lib/utils/observability/index.js +26 -0
- package/lib/utils/observability/interfaces.d.ts +30 -0
- package/lib/utils/observability/interfaces.js +23 -0
- package/lib/utils/resolutionEditing.js +2 -1
- package/lib/utils/resourceSelector.d.ts +97 -0
- package/lib/utils/resourceSelector.js +195 -0
- package/lib/utils/resourceSelectors.d.ts +146 -0
- package/lib/utils/resourceSelectors.js +233 -0
- package/lib/utils/tsResIntegration.d.ts +6 -41
- package/lib/utils/tsResIntegration.js +20 -16
- package/lib/utils/zipLoader/zipProcessingHelpers.d.ts +3 -2
- package/lib/utils/zipLoader/zipProcessingHelpers.js +6 -5
- package/lib-commonjs/components/common/QualifierContextControl.js +4 -1
- package/lib-commonjs/components/common/ResourceTreeView.js +4 -1
- package/lib-commonjs/components/forms/GenericQualifierTypeEditForm.js +171 -0
- package/lib-commonjs/components/forms/index.js +3 -1
- package/lib-commonjs/components/orchestrator/ResourceOrchestrator.js +118 -51
- package/lib-commonjs/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
- package/lib-commonjs/components/pickers/ResourcePicker/index.js +4 -2
- package/lib-commonjs/components/views/CompiledView/index.js +75 -16
- package/lib-commonjs/components/views/ConfigurationView/index.js +93 -34
- package/lib-commonjs/components/views/FilterView/index.js +7 -4
- package/lib-commonjs/components/views/GridView/EditableGridCell.js +232 -0
- package/lib-commonjs/components/views/GridView/GridSelector.js +94 -0
- package/lib-commonjs/components/views/GridView/MultiGridView.js +201 -0
- package/lib-commonjs/components/views/GridView/ResourceGrid.js +237 -0
- package/lib-commonjs/components/views/GridView/SharedContextControls.js +100 -0
- package/lib-commonjs/components/views/GridView/cells/BooleanCell.js +54 -0
- package/lib-commonjs/components/views/GridView/cells/DropdownCell.js +187 -0
- package/lib-commonjs/components/views/GridView/cells/StringCell.js +111 -0
- package/lib-commonjs/components/views/GridView/cells/TriStateCell.js +117 -0
- package/lib-commonjs/components/views/GridView/cells/index.js +18 -0
- package/lib-commonjs/components/views/GridView/index.js +217 -0
- package/lib-commonjs/components/views/ImportView/index.js +22 -19
- package/lib-commonjs/components/views/MessagesWindow/index.js +4 -1
- package/lib-commonjs/components/views/ResolutionView/index.js +8 -5
- package/lib-commonjs/contexts/ObservabilityContext.js +104 -0
- package/lib-commonjs/contexts/index.js +30 -0
- package/lib-commonjs/hooks/useResolutionState.js +849 -245
- package/lib-commonjs/hooks/useResourceData.js +184 -215
- package/lib-commonjs/index.js +15 -1
- package/lib-commonjs/namespaces/GridTools.js +161 -0
- package/lib-commonjs/namespaces/ObservabilityTools.js +33 -0
- package/lib-commonjs/namespaces/ResolutionTools.js +10 -1
- package/lib-commonjs/namespaces/index.js +3 -1
- package/lib-commonjs/types/index.js +10 -0
- package/lib-commonjs/utils/cellValidation.js +253 -0
- package/lib-commonjs/utils/downloadHelper.js +198 -0
- package/lib-commonjs/utils/observability/factories.js +63 -0
- package/lib-commonjs/utils/observability/implementations.js +109 -0
- package/lib-commonjs/utils/observability/index.js +36 -0
- package/lib-commonjs/utils/observability/interfaces.js +24 -0
- package/lib-commonjs/utils/resolutionEditing.js +2 -1
- package/lib-commonjs/utils/resourceSelector.js +200 -0
- package/lib-commonjs/utils/resourceSelectors.js +242 -0
- package/lib-commonjs/utils/tsResIntegration.js +21 -16
- package/lib-commonjs/utils/zipLoader/zipProcessingHelpers.js +7 -5
- package/package.json +7 -7
- package/src/components/common/QualifierContextControl.tsx +0 -338
- package/src/components/common/ResolutionContextOptionsControl.tsx +0 -450
- package/src/components/common/ResolutionResults/index.tsx +0 -481
- package/src/components/common/ResourceListView.tsx +0 -167
- package/src/components/common/ResourcePickerOptionsControl.tsx +0 -351
- package/src/components/common/ResourceTreeView.tsx +0 -417
- package/src/components/common/SourceResourceDetail/index.tsx +0 -493
- package/src/components/forms/HierarchyEditor.tsx +0 -285
- package/src/components/forms/QualifierEditForm.tsx +0 -487
- package/src/components/forms/QualifierTypeEditForm.tsx +0 -458
- package/src/components/forms/ResourceTypeEditForm.tsx +0 -437
- package/src/components/forms/index.ts +0 -11
- package/src/components/orchestrator/ResourceOrchestrator.tsx +0 -444
- package/src/components/pickers/ResourcePicker/README.md +0 -570
- package/src/components/pickers/ResourcePicker/ResourceItem.tsx +0 -127
- package/src/components/pickers/ResourcePicker/ResourcePickerList.tsx +0 -114
- package/src/components/pickers/ResourcePicker/ResourcePickerTree.tsx +0 -461
- package/src/components/pickers/ResourcePicker/index.tsx +0 -234
- package/src/components/pickers/ResourcePicker/types.ts +0 -301
- package/src/components/pickers/ResourcePicker/utils/treeNavigation.ts +0 -210
- package/src/components/views/CompiledView/index.tsx +0 -1342
- package/src/components/views/ConfigurationView/index.tsx +0 -848
- package/src/components/views/FilterView/index.tsx +0 -681
- package/src/components/views/ImportView/index.tsx +0 -789
- package/src/components/views/MessagesWindow/index.tsx +0 -325
- package/src/components/views/ResolutionView/EditableJsonView.tsx +0 -386
- package/src/components/views/ResolutionView/NewResourceModal.tsx +0 -158
- package/src/components/views/ResolutionView/UnifiedChangeControls.tsx +0 -163
- package/src/components/views/ResolutionView/index.tsx +0 -751
- package/src/components/views/SourceView/index.tsx +0 -291
- package/src/hooks/useConfigurationState.ts +0 -436
- package/src/hooks/useFilterState.ts +0 -150
- package/src/hooks/useResolutionState.ts +0 -893
- package/src/hooks/useResourceData.ts +0 -596
- package/src/hooks/useViewState.ts +0 -97
- package/src/index.ts +0 -68
- package/src/namespaces/ConfigurationTools.ts +0 -59
- package/src/namespaces/FilterTools.ts +0 -47
- package/src/namespaces/ImportTools.ts +0 -42
- package/src/namespaces/PickerTools.ts +0 -104
- package/src/namespaces/ResolutionTools.ts +0 -68
- package/src/namespaces/ResourceTools.ts +0 -106
- package/src/namespaces/TsResTools.ts +0 -49
- package/src/namespaces/ViewStateTools.ts +0 -91
- package/src/namespaces/ZipTools.ts +0 -49
- package/src/namespaces/index.ts +0 -49
- package/src/types/index.ts +0 -1273
- package/src/utils/configurationUtils.ts +0 -339
- package/src/utils/fileProcessing.ts +0 -164
- package/src/utils/filterResources.ts +0 -356
- package/src/utils/resolutionEditing.ts +0 -346
- package/src/utils/resolutionUtils.ts +0 -740
- package/src/utils/tsResIntegration.ts +0 -475
- package/src/utils/zipLoader/index.ts +0 -5
- package/src/utils/zipLoader/zipProcessingHelpers.ts +0 -46
- package/src/utils/zipLoader/zipUtils.ts +0 -7
|
@@ -1,487 +0,0 @@
|
|
|
1
|
-
import React, { useState, useCallback } from 'react';
|
|
2
|
-
import { XMarkIcon, InformationCircleIcon } from '@heroicons/react/24/outline';
|
|
3
|
-
import { Qualifiers, QualifierTypes } from '@fgv/ts-res';
|
|
4
|
-
import { Converters } from '@fgv/ts-utils';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Props for the QualifierEditForm component.
|
|
8
|
-
*
|
|
9
|
-
* @public
|
|
10
|
-
*/
|
|
11
|
-
export interface QualifierEditFormProps {
|
|
12
|
-
/** Existing qualifier to edit (undefined for creating new qualifier) */
|
|
13
|
-
qualifier?: Qualifiers.IQualifierDecl;
|
|
14
|
-
/** Available qualifier types for selection */
|
|
15
|
-
qualifierTypes: QualifierTypes.Config.ISystemQualifierTypeConfig[];
|
|
16
|
-
/** Callback fired when qualifier is saved */
|
|
17
|
-
onSave: (qualifier: Qualifiers.IQualifierDecl) => void;
|
|
18
|
-
/** Callback fired when editing is cancelled */
|
|
19
|
-
onCancel: () => void;
|
|
20
|
-
/** Names of existing qualifiers to prevent duplicates */
|
|
21
|
-
existingNames?: string[];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface FormData {
|
|
25
|
-
name: string;
|
|
26
|
-
typeName: string;
|
|
27
|
-
defaultPriority: number;
|
|
28
|
-
token: string;
|
|
29
|
-
tokenIsOptional: boolean;
|
|
30
|
-
defaultValue: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Modal form component for creating and editing qualifiers in a ts-res system configuration.
|
|
35
|
-
*
|
|
36
|
-
* The QualifierEditForm provides a comprehensive interface for defining qualifiers that control
|
|
37
|
-
* resource resolution behavior. It includes validation, type-specific configuration options,
|
|
38
|
-
* and automatic token generation for streamlined qualifier creation.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```tsx
|
|
42
|
-
* import { ConfigurationTools } from '@fgv/ts-res-ui-components';
|
|
43
|
-
*
|
|
44
|
-
* // Creating a new language qualifier
|
|
45
|
-
* const qualifierTypes = [
|
|
46
|
-
* { name: 'language', systemType: 'language' },
|
|
47
|
-
* { name: 'region', systemType: 'territory' }
|
|
48
|
-
* ];
|
|
49
|
-
*
|
|
50
|
-
* const [showForm, setShowForm] = useState(false);
|
|
51
|
-
* const [qualifiers, setQualifiers] = useState([]);
|
|
52
|
-
*
|
|
53
|
-
* const handleSave = (qualifier) => {
|
|
54
|
-
* setQualifiers(prev => [...prev, qualifier]);
|
|
55
|
-
* setShowForm(false);
|
|
56
|
-
* };
|
|
57
|
-
*
|
|
58
|
-
* {showForm && (
|
|
59
|
-
* <ConfigurationTools.QualifierEditForm
|
|
60
|
-
* qualifierTypes={qualifierTypes}
|
|
61
|
-
* onSave={handleSave}
|
|
62
|
-
* onCancel={() => setShowForm(false)}
|
|
63
|
-
* existingNames={qualifiers.map(q => q.name)}
|
|
64
|
-
* />
|
|
65
|
-
* )}
|
|
66
|
-
* ```
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```tsx
|
|
70
|
-
* // Editing an existing qualifier with validation
|
|
71
|
-
* const existingQualifier = {
|
|
72
|
-
* name: 'language',
|
|
73
|
-
* typeName: 'language',
|
|
74
|
-
* defaultPriority: 100,
|
|
75
|
-
* token: 'lang',
|
|
76
|
-
* tokenIsOptional: false,
|
|
77
|
-
* defaultValue: 'en-US'
|
|
78
|
-
* };
|
|
79
|
-
*
|
|
80
|
-
* <ConfigurationTools.QualifierEditForm
|
|
81
|
-
* qualifier={existingQualifier}
|
|
82
|
-
* qualifierTypes={availableTypes}
|
|
83
|
-
* onSave={updateQualifier}
|
|
84
|
-
* onCancel={closeEditor}
|
|
85
|
-
* existingNames={otherQualifierNames}
|
|
86
|
-
* />
|
|
87
|
-
* ```
|
|
88
|
-
*
|
|
89
|
-
* @example
|
|
90
|
-
* ```tsx
|
|
91
|
-
* // Advanced qualifier configuration with enum values
|
|
92
|
-
* const platformType = {
|
|
93
|
-
* name: 'platform',
|
|
94
|
-
* systemType: 'literal',
|
|
95
|
-
* configuration: {
|
|
96
|
-
* caseSensitive: false,
|
|
97
|
-
* enumeratedValues: ['web', 'mobile', 'desktop'],
|
|
98
|
-
* allowContextList: true
|
|
99
|
-
* }
|
|
100
|
-
* };
|
|
101
|
-
*
|
|
102
|
-
* const platformQualifier = {
|
|
103
|
-
* name: 'platform',
|
|
104
|
-
* typeName: 'platform',
|
|
105
|
-
* defaultPriority: 80,
|
|
106
|
-
* token: 'plat',
|
|
107
|
-
* defaultValue: 'web,mobile' // Multiple values supported
|
|
108
|
-
* };
|
|
109
|
-
*
|
|
110
|
-
* <ConfigurationTools.QualifierEditForm
|
|
111
|
-
* qualifier={platformQualifier}
|
|
112
|
-
* qualifierTypes={[platformType]}
|
|
113
|
-
* onSave={handlePlatformSave}
|
|
114
|
-
* onCancel={cancelEdit}
|
|
115
|
-
* />
|
|
116
|
-
* ```
|
|
117
|
-
*
|
|
118
|
-
* @public
|
|
119
|
-
*/
|
|
120
|
-
export const QualifierEditForm: React.FC<QualifierEditFormProps> = ({
|
|
121
|
-
qualifier,
|
|
122
|
-
qualifierTypes,
|
|
123
|
-
onSave,
|
|
124
|
-
onCancel,
|
|
125
|
-
existingNames = []
|
|
126
|
-
}) => {
|
|
127
|
-
const [formData, setFormData] = useState<FormData>(() => {
|
|
128
|
-
if (qualifier) {
|
|
129
|
-
return {
|
|
130
|
-
name: qualifier.name,
|
|
131
|
-
typeName: qualifier.typeName,
|
|
132
|
-
defaultPriority: qualifier.defaultPriority,
|
|
133
|
-
token: qualifier.token || '',
|
|
134
|
-
tokenIsOptional: qualifier.tokenIsOptional || false,
|
|
135
|
-
defaultValue: qualifier.defaultValue || ''
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
name: '',
|
|
140
|
-
typeName: qualifierTypes[0]?.name || '',
|
|
141
|
-
defaultPriority: 50,
|
|
142
|
-
token: '',
|
|
143
|
-
tokenIsOptional: false,
|
|
144
|
-
defaultValue: ''
|
|
145
|
-
};
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
149
|
-
|
|
150
|
-
// Get the selected qualifier type for context
|
|
151
|
-
const selectedQualifierType = qualifierTypes.find((qt) => qt.name === formData.typeName);
|
|
152
|
-
|
|
153
|
-
// Type-safe extraction of allowContextList property
|
|
154
|
-
const allowsContextList = (() => {
|
|
155
|
-
if (!selectedQualifierType?.configuration) return false;
|
|
156
|
-
const result = Converters.boolean.convert(
|
|
157
|
-
(selectedQualifierType.configuration as Record<string, unknown>).allowContextList
|
|
158
|
-
);
|
|
159
|
-
return result.isSuccess() ? result.value : false;
|
|
160
|
-
})();
|
|
161
|
-
|
|
162
|
-
// Validation
|
|
163
|
-
const validateForm = useCallback((): boolean => {
|
|
164
|
-
const newErrors: Record<string, string> = {};
|
|
165
|
-
|
|
166
|
-
if (!formData.name.trim()) {
|
|
167
|
-
newErrors.name = 'Name is required';
|
|
168
|
-
} else if (existingNames.includes(formData.name) && formData.name !== qualifier?.name) {
|
|
169
|
-
newErrors.name = 'Name must be unique';
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (!formData.typeName) {
|
|
173
|
-
newErrors.typeName = 'Qualifier type is required';
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (formData.defaultPriority < 0 || formData.defaultPriority > 1000) {
|
|
177
|
-
newErrors.defaultPriority = 'Priority must be between 0 and 1000';
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (formData.token && !/^[a-zA-Z][a-zA-Z0-9_]*$/.test(formData.token)) {
|
|
181
|
-
newErrors.token = 'Token must start with a letter and contain only letters, numbers, and underscores';
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
setErrors(newErrors);
|
|
185
|
-
return Object.keys(newErrors).length === 0;
|
|
186
|
-
}, [formData, existingNames, qualifier?.name]);
|
|
187
|
-
|
|
188
|
-
const handleSave = useCallback(() => {
|
|
189
|
-
if (!validateForm()) return;
|
|
190
|
-
|
|
191
|
-
const result: Qualifiers.IQualifierDecl = {
|
|
192
|
-
name: formData.name,
|
|
193
|
-
typeName: formData.typeName,
|
|
194
|
-
defaultPriority: formData.defaultPriority,
|
|
195
|
-
...(formData.token && { token: formData.token }),
|
|
196
|
-
...(formData.token && formData.tokenIsOptional && { tokenIsOptional: true }),
|
|
197
|
-
...(formData.defaultValue && { defaultValue: formData.defaultValue })
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
onSave(result);
|
|
201
|
-
}, [formData, validateForm, onSave]);
|
|
202
|
-
|
|
203
|
-
const updateField = useCallback(
|
|
204
|
-
(field: keyof FormData, value: FormData[keyof FormData]) => {
|
|
205
|
-
setFormData((prev) => {
|
|
206
|
-
const updated = { ...prev, [field]: value };
|
|
207
|
-
|
|
208
|
-
// Auto-generate token from name if no custom token is set
|
|
209
|
-
if (field === 'name' && !prev.token) {
|
|
210
|
-
updated.token = String(value)
|
|
211
|
-
.toLowerCase()
|
|
212
|
-
.replace(/[^a-zA-Z0-9]/g, '');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Clear tokenIsOptional if token is cleared
|
|
216
|
-
if (field === 'token' && !value) {
|
|
217
|
-
updated.tokenIsOptional = false;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return updated;
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
if (errors[field]) {
|
|
224
|
-
setErrors((prev) => ({ ...prev, [field]: '' }));
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
[errors]
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
const getDefaultValuePlaceholder = (): string => {
|
|
231
|
-
if (!selectedQualifierType) return 'Enter default value';
|
|
232
|
-
|
|
233
|
-
switch (selectedQualifierType.systemType) {
|
|
234
|
-
case 'language':
|
|
235
|
-
return allowsContextList ? 'e.g., en-US or en-US,en' : 'e.g., en-US';
|
|
236
|
-
case 'territory':
|
|
237
|
-
return allowsContextList ? 'e.g., US or US,CA' : 'e.g., US';
|
|
238
|
-
case 'literal':
|
|
239
|
-
// Type-safe extraction of enumeratedValues
|
|
240
|
-
const enumValues = (() => {
|
|
241
|
-
if (!selectedQualifierType.configuration) return undefined;
|
|
242
|
-
const result = Converters.arrayOf(Converters.string).convert(
|
|
243
|
-
(selectedQualifierType.configuration as Record<string, unknown>).enumeratedValues
|
|
244
|
-
);
|
|
245
|
-
return result.isSuccess() ? result.value : undefined;
|
|
246
|
-
})();
|
|
247
|
-
if (enumValues && enumValues.length > 0) {
|
|
248
|
-
return allowsContextList
|
|
249
|
-
? `e.g., ${enumValues[0]} or ${enumValues.slice(0, 2).join(',')}`
|
|
250
|
-
: `e.g., ${enumValues[0]}`;
|
|
251
|
-
}
|
|
252
|
-
return allowsContextList ? 'e.g., value or value1,value2' : 'e.g., value';
|
|
253
|
-
default:
|
|
254
|
-
return 'Enter default value';
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
return (
|
|
259
|
-
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
260
|
-
<div className="bg-white rounded-lg shadow-xl max-w-2xl w-full h-full max-h-[calc(100vh-2rem)] flex flex-col">
|
|
261
|
-
{/* Fixed Header */}
|
|
262
|
-
<div className="flex items-center justify-between p-6 border-b flex-shrink-0">
|
|
263
|
-
<h3 className="text-lg font-medium text-gray-900">
|
|
264
|
-
{qualifier ? 'Edit Qualifier' : 'Add Qualifier'}
|
|
265
|
-
</h3>
|
|
266
|
-
<button onClick={onCancel} className="text-gray-400 hover:text-gray-600">
|
|
267
|
-
<XMarkIcon className="w-6 h-6" />
|
|
268
|
-
</button>
|
|
269
|
-
</div>
|
|
270
|
-
|
|
271
|
-
{/* Scrollable Content */}
|
|
272
|
-
<div className="p-6 space-y-6 overflow-y-auto flex-1 min-h-0">
|
|
273
|
-
{/* Basic Properties */}
|
|
274
|
-
<div className="grid grid-cols-2 gap-4">
|
|
275
|
-
<div>
|
|
276
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">Name *</label>
|
|
277
|
-
<input
|
|
278
|
-
type="text"
|
|
279
|
-
value={formData.name}
|
|
280
|
-
onChange={(e) => updateField('name', e.target.value)}
|
|
281
|
-
className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
|
|
282
|
-
errors.name ? 'border-red-300' : 'border-gray-300'
|
|
283
|
-
}`}
|
|
284
|
-
placeholder="Enter qualifier name"
|
|
285
|
-
/>
|
|
286
|
-
{errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
|
|
287
|
-
</div>
|
|
288
|
-
|
|
289
|
-
<div>
|
|
290
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">Qualifier Type *</label>
|
|
291
|
-
<select
|
|
292
|
-
value={formData.typeName}
|
|
293
|
-
onChange={(e) => updateField('typeName', e.target.value)}
|
|
294
|
-
className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
|
|
295
|
-
errors.typeName ? 'border-red-300' : 'border-gray-300'
|
|
296
|
-
}`}
|
|
297
|
-
>
|
|
298
|
-
{qualifierTypes.length === 0 ? (
|
|
299
|
-
<option value="">No qualifier types available</option>
|
|
300
|
-
) : (
|
|
301
|
-
qualifierTypes.map((type) => (
|
|
302
|
-
<option key={type.name} value={type.name}>
|
|
303
|
-
{type.name} ({type.systemType})
|
|
304
|
-
</option>
|
|
305
|
-
))
|
|
306
|
-
)}
|
|
307
|
-
</select>
|
|
308
|
-
{errors.typeName && <p className="mt-1 text-sm text-red-600">{errors.typeName}</p>}
|
|
309
|
-
</div>
|
|
310
|
-
</div>
|
|
311
|
-
|
|
312
|
-
<div className="grid grid-cols-2 gap-4">
|
|
313
|
-
<div>
|
|
314
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">Default Priority *</label>
|
|
315
|
-
<input
|
|
316
|
-
type="number"
|
|
317
|
-
min="0"
|
|
318
|
-
max="1000"
|
|
319
|
-
value={formData.defaultPriority}
|
|
320
|
-
onChange={(e) => updateField('defaultPriority', parseInt(e.target.value) || 0)}
|
|
321
|
-
className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
|
|
322
|
-
errors.defaultPriority ? 'border-red-300' : 'border-gray-300'
|
|
323
|
-
}`}
|
|
324
|
-
placeholder="50"
|
|
325
|
-
/>
|
|
326
|
-
{errors.defaultPriority && (
|
|
327
|
-
<p className="mt-1 text-sm text-red-600">{errors.defaultPriority}</p>
|
|
328
|
-
)}
|
|
329
|
-
<p className="mt-1 text-xs text-gray-500">Higher numbers have higher priority (0-1000)</p>
|
|
330
|
-
</div>
|
|
331
|
-
|
|
332
|
-
<div>
|
|
333
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
334
|
-
Token
|
|
335
|
-
<span className="ml-1 text-gray-500">(optional)</span>
|
|
336
|
-
</label>
|
|
337
|
-
<input
|
|
338
|
-
type="text"
|
|
339
|
-
value={formData.token}
|
|
340
|
-
onChange={(e) => updateField('token', e.target.value)}
|
|
341
|
-
className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
|
|
342
|
-
errors.token ? 'border-red-300' : 'border-gray-300'
|
|
343
|
-
}`}
|
|
344
|
-
placeholder="e.g., lang, locale"
|
|
345
|
-
/>
|
|
346
|
-
{errors.token && <p className="mt-1 text-sm text-red-600">{errors.token}</p>}
|
|
347
|
-
<p className="mt-1 text-xs text-gray-500">Used to identify this qualifier in resource names</p>
|
|
348
|
-
</div>
|
|
349
|
-
</div>
|
|
350
|
-
|
|
351
|
-
{/* Token Options */}
|
|
352
|
-
{formData.token && (
|
|
353
|
-
<div className="flex items-center">
|
|
354
|
-
<input
|
|
355
|
-
type="checkbox"
|
|
356
|
-
id="tokenIsOptional"
|
|
357
|
-
checked={formData.tokenIsOptional}
|
|
358
|
-
onChange={(e) => updateField('tokenIsOptional', e.target.checked)}
|
|
359
|
-
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
|
360
|
-
/>
|
|
361
|
-
<label htmlFor="tokenIsOptional" className="ml-2 text-sm text-gray-700">
|
|
362
|
-
Token is optional in resource names
|
|
363
|
-
</label>
|
|
364
|
-
<div className="ml-2 group relative">
|
|
365
|
-
<InformationCircleIcon className="w-4 h-4 text-gray-400" />
|
|
366
|
-
<div className="absolute left-0 bottom-6 hidden group-hover:block bg-gray-800 text-white text-xs rounded py-1 px-2 whitespace-nowrap z-10">
|
|
367
|
-
Allow resources without this qualifier token
|
|
368
|
-
</div>
|
|
369
|
-
</div>
|
|
370
|
-
</div>
|
|
371
|
-
)}
|
|
372
|
-
|
|
373
|
-
{/* Default Value */}
|
|
374
|
-
<div>
|
|
375
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
376
|
-
Default Value
|
|
377
|
-
<span className="ml-1 text-gray-500">(optional)</span>
|
|
378
|
-
</label>
|
|
379
|
-
<input
|
|
380
|
-
type="text"
|
|
381
|
-
value={formData.defaultValue}
|
|
382
|
-
onChange={(e) => updateField('defaultValue', e.target.value)}
|
|
383
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
384
|
-
placeholder={getDefaultValuePlaceholder()}
|
|
385
|
-
/>
|
|
386
|
-
<div className="mt-1 text-xs text-gray-500">
|
|
387
|
-
{selectedQualifierType && (
|
|
388
|
-
<div>
|
|
389
|
-
<p>
|
|
390
|
-
Qualifier type: <span className="font-medium">{selectedQualifierType.systemType}</span>
|
|
391
|
-
</p>
|
|
392
|
-
{allowsContextList && (
|
|
393
|
-
<p className="text-blue-600">
|
|
394
|
-
This qualifier type supports multiple values (comma-separated)
|
|
395
|
-
</p>
|
|
396
|
-
)}
|
|
397
|
-
</div>
|
|
398
|
-
)}
|
|
399
|
-
</div>
|
|
400
|
-
</div>
|
|
401
|
-
|
|
402
|
-
{/* Qualifier Type Information */}
|
|
403
|
-
{selectedQualifierType && (
|
|
404
|
-
<div className="p-4 bg-gray-50 rounded-lg">
|
|
405
|
-
<h4 className="font-medium text-gray-900 mb-2">Qualifier Type Information</h4>
|
|
406
|
-
<div className="text-sm text-gray-600 space-y-1">
|
|
407
|
-
<p>
|
|
408
|
-
<span className="font-medium">System Type:</span> {selectedQualifierType.systemType}
|
|
409
|
-
</p>
|
|
410
|
-
<p>
|
|
411
|
-
<span className="font-medium">Supports Context List:</span>{' '}
|
|
412
|
-
{allowsContextList ? 'Yes' : 'No'}
|
|
413
|
-
</p>
|
|
414
|
-
{selectedQualifierType.systemType === 'literal' && selectedQualifierType.configuration && (
|
|
415
|
-
<React.Fragment>
|
|
416
|
-
{(selectedQualifierType.configuration as Record<string, unknown>).caseSensitive !==
|
|
417
|
-
undefined && (
|
|
418
|
-
<p>
|
|
419
|
-
<span className="font-medium">Case Sensitive:</span>{' '}
|
|
420
|
-
{((selectedQualifierType.configuration as Record<string, unknown>)
|
|
421
|
-
.caseSensitive as boolean)
|
|
422
|
-
? 'Yes'
|
|
423
|
-
: 'No'}
|
|
424
|
-
</p>
|
|
425
|
-
)}
|
|
426
|
-
{((selectedQualifierType.configuration as Record<string, unknown>).enumeratedValues as
|
|
427
|
-
| string[]
|
|
428
|
-
| undefined) && (
|
|
429
|
-
<p>
|
|
430
|
-
<span className="font-medium">Allowed Values:</span>{' '}
|
|
431
|
-
{(
|
|
432
|
-
(selectedQualifierType.configuration as Record<string, unknown>)
|
|
433
|
-
.enumeratedValues as string[]
|
|
434
|
-
).join(', ')}
|
|
435
|
-
</p>
|
|
436
|
-
)}
|
|
437
|
-
</React.Fragment>
|
|
438
|
-
)}
|
|
439
|
-
{selectedQualifierType.systemType === 'territory' && selectedQualifierType.configuration && (
|
|
440
|
-
<React.Fragment>
|
|
441
|
-
{(selectedQualifierType.configuration as Record<string, unknown>).acceptLowercase !==
|
|
442
|
-
undefined && (
|
|
443
|
-
<p>
|
|
444
|
-
<span className="font-medium">Accept Lowercase:</span>{' '}
|
|
445
|
-
{((selectedQualifierType.configuration as Record<string, unknown>)
|
|
446
|
-
.acceptLowercase as boolean)
|
|
447
|
-
? 'Yes'
|
|
448
|
-
: 'No'}
|
|
449
|
-
</p>
|
|
450
|
-
)}
|
|
451
|
-
{((selectedQualifierType.configuration as Record<string, unknown>).allowedTerritories as
|
|
452
|
-
| string[]
|
|
453
|
-
| undefined) && (
|
|
454
|
-
<p>
|
|
455
|
-
<span className="font-medium">Allowed Territories:</span>{' '}
|
|
456
|
-
{(
|
|
457
|
-
(selectedQualifierType.configuration as Record<string, unknown>)
|
|
458
|
-
.allowedTerritories as string[]
|
|
459
|
-
).join(', ')}
|
|
460
|
-
</p>
|
|
461
|
-
)}
|
|
462
|
-
</React.Fragment>
|
|
463
|
-
)}
|
|
464
|
-
</div>
|
|
465
|
-
</div>
|
|
466
|
-
)}
|
|
467
|
-
</div>
|
|
468
|
-
|
|
469
|
-
{/* Fixed Footer */}
|
|
470
|
-
<div className="flex justify-end space-x-3 px-6 py-4 border-t bg-gray-50 flex-shrink-0">
|
|
471
|
-
<button
|
|
472
|
-
onClick={onCancel}
|
|
473
|
-
className="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"
|
|
474
|
-
>
|
|
475
|
-
Cancel
|
|
476
|
-
</button>
|
|
477
|
-
<button
|
|
478
|
-
onClick={handleSave}
|
|
479
|
-
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
480
|
-
>
|
|
481
|
-
{qualifier ? 'Save Changes' : 'Add Qualifier'}
|
|
482
|
-
</button>
|
|
483
|
-
</div>
|
|
484
|
-
</div>
|
|
485
|
-
</div>
|
|
486
|
-
);
|
|
487
|
-
};
|