@fgv/ts-res-browser 5.0.0-2 → 5.0.0-21

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 (99) hide show
  1. package/cli.js +132 -21
  2. package/dist/bundle.js +1 -1
  3. package/dist/bundle.js.LICENSE.txt +11 -1
  4. package/package.json +42 -36
  5. package/.rush/temp/587277bfc395406ad444e989b6c309b9698f534c.tar.log +0 -15
  6. package/.rush/temp/chunked-rush-logs/ts-res-browser.build.chunks.jsonl +0 -35
  7. package/.rush/temp/operation/build/all.log +0 -35
  8. package/.rush/temp/operation/build/log-chunks.jsonl +0 -35
  9. package/.rush/temp/operation/build/state.json +0 -3
  10. package/.rush/temp/shrinkwrap-deps.json +0 -1225
  11. package/TS_RES_BROWSER_PLAN.md +0 -689
  12. package/babel.config.js +0 -17
  13. package/config/jest.config.json +0 -13
  14. package/config/rig.json +0 -16
  15. package/postcss.config.js +0 -3
  16. package/public/index.html +0 -15
  17. package/rush-logs/ts-res-browser.build.cache.log +0 -3
  18. package/rush-logs/ts-res-browser.build.log +0 -35
  19. package/src/App.tsx +0 -274
  20. package/src/cli.ts +0 -44
  21. package/src/components/common/FileImporter.tsx +0 -143
  22. package/src/components/common/NavigationWarningModal.tsx +0 -62
  23. package/src/components/layout/AppLayout.tsx +0 -46
  24. package/src/components/layout/Header.tsx +0 -22
  25. package/src/components/layout/MessagesWindow.tsx +0 -108
  26. package/src/components/layout/Sidebar.tsx +0 -99
  27. package/src/components/tools/CompiledBrowser.tsx +0 -949
  28. package/src/components/tools/ConfigurationTool.tsx +0 -1405
  29. package/src/components/tools/FilterTool.tsx +0 -1114
  30. package/src/components/tools/ImportTool.tsx +0 -249
  31. package/src/components/tools/ResolutionViewer.tsx +0 -1672
  32. package/src/components/tools/SourceBrowser.tsx +0 -496
  33. package/src/components/tools/ZipLoader.tsx +0 -302
  34. package/src/hooks/useAppState.ts +0 -111
  35. package/src/hooks/useFileImport.ts +0 -86
  36. package/src/hooks/useNavigationWarning.ts +0 -67
  37. package/src/hooks/useResourceManager.ts +0 -295
  38. package/src/hooks/useUrlParams.ts +0 -56
  39. package/src/index.css +0 -38
  40. package/src/main.tsx +0 -16
  41. package/src/types/app.ts +0 -70
  42. package/src/types/fileSystemAccess.d.ts +0 -94
  43. package/src/utils/browserFileTreeAccessors.ts +0 -245
  44. package/src/utils/fileImport.ts +0 -336
  45. package/src/utils/fileTreeConverter.ts +0 -160
  46. package/src/utils/filterResources.ts +0 -677
  47. package/src/utils/tsResIntegration.ts +0 -467
  48. package/src/utils/urlParams.ts +0 -238
  49. package/src/utils/zip/browserZipFileTreeAccessors.ts +0 -385
  50. package/tailwind.config.js +0 -21
  51. package/test-data/README.md +0 -184
  52. package/test-data/config-variations/README.md +0 -64
  53. package/test-data/config-variations/composition-terminology.json +0 -112
  54. package/test-data/config-variations/enterprise-complex.json +0 -129
  55. package/test-data/config-variations/gaming-app.json +0 -87
  56. package/test-data/config-variations/high-priority-language.json +0 -65
  57. package/test-data/config-variations/minimal-basic.json +0 -37
  58. package/test-data/config-variations/qualifier-default-values.json +0 -71
  59. package/test-data/config-variations/territory-first.json +0 -68
  60. package/test-data/config-variations/territory-hierarchy-test.json +0 -31
  61. package/test-data/config-variations/territory-hierarchy-unconstrained.json +0 -32
  62. package/test-data/default-matching-test/greeting.res.json +0 -83
  63. package/test-data/default-matching-test/only-defaults.res.json +0 -48
  64. package/test-data/sample-project/package.json +0 -27
  65. package/test-data/sample-project/src/resources/app-config.json +0 -27
  66. package/test-data/sample-project/src/resources/app.json +0 -177
  67. package/test-data/sample-project/src/resources/config/app.env=development.json +0 -21
  68. package/test-data/sample-project/src/resources/config/app.json +0 -21
  69. package/test-data/sample-project/src/resources/config/app.platform=mobile.json +0 -23
  70. package/test-data/sample-project/src/resources/feature-flags.json +0 -23
  71. package/test-data/sample-project/src/resources/home=CA/common.json +0 -16
  72. package/test-data/sample-project/src/resources/images/density=hdpi.json +0 -29
  73. package/test-data/sample-project/src/resources/images/home.json +0 -29
  74. package/test-data/sample-project/src/resources/images/logout.json +0 -14
  75. package/test-data/sample-project/src/resources/images/settings.json +0 -14
  76. package/test-data/sample-project/src/resources/images/ui-icons.json +0 -29
  77. package/test-data/sample-project/src/resources/images/user.density=hdpi.json +0 -14
  78. package/test-data/sample-project/src/resources/images/user.json +0 -14
  79. package/test-data/sample-project/src/resources/lang=en-US/common.json +0 -15
  80. package/test-data/sample-project/src/resources/language=en-CA/ui-terms.json +0 -19
  81. package/test-data/sample-project/src/resources/language=en-GB/ui-terms.json +0 -30
  82. package/test-data/sample-project/src/resources/language=fr-CA/ui-terms.json +0 -25
  83. package/test-data/sample-project/src/resources/language=nl-BE/ui-terms.json +0 -17
  84. package/test-data/sample-project/src/resources/region=europe/feature-flags.json +0 -17
  85. package/test-data/sample-project/src/resources/region=north-america/feature-flags.json +0 -18
  86. package/test-data/sample-project/src/resources/strings/dashboard.home=CA,language=fr.json +0 -20
  87. package/test-data/sample-project/src/resources/strings/dashboard.json +0 -18
  88. package/test-data/sample-project/src/resources/strings/dashboard.role=admin.json +0 -23
  89. package/test-data/sample-project/src/resources/strings/dashboard.role=user.json +0 -21
  90. package/test-data/sample-project/src/resources/strings/language=en-US.json +0 -16
  91. package/test-data/sample-project/src/resources/strings.json +0 -78
  92. package/test-data/sample-project/src/resources/territory=BE/app-config.json +0 -19
  93. package/test-data/sample-project/src/resources/territory=CA/app-config.json +0 -21
  94. package/test-data/sample-project/src/resources/territory=GB/app-config.json +0 -25
  95. package/test-data/sample-project/src/resources/territory=NL/app-config.json +0 -25
  96. package/test-data/sample-project/src/resources/ui-terms.json +0 -96
  97. package/test-data/sample-project/src/resources-config.json +0 -111
  98. package/tsconfig.json +0 -27
  99. package/webpack.config.js +0 -60
@@ -1,467 +0,0 @@
1
- import { Result, succeed, fail, FileTree } from '@fgv/ts-utils';
2
- import {
3
- QualifierTypes,
4
- Qualifiers,
5
- ResourceTypes,
6
- Resources,
7
- Import,
8
- Runtime,
9
- ResourceJson,
10
- Config
11
- } from '@fgv/ts-res';
12
- import { ImportedDirectory, ImportedFile } from './fileImport';
13
- import { BrowserFileTreeAccessors } from './browserFileTreeAccessors';
14
-
15
- /**
16
- * Configuration for setting up ts-res system
17
- */
18
- export interface TsResConfig {
19
- qualifierDecls?: Qualifiers.IQualifierDecl[];
20
- additionalQualifierTypes?: QualifierTypes.QualifierType[];
21
- additionalResourceTypes?: ResourceTypes.ResourceType[];
22
- }
23
-
24
- /**
25
- * Complete ts-res system setup
26
- */
27
- export interface TsResSystem {
28
- qualifierTypes: QualifierTypes.ReadOnlyQualifierTypeCollector;
29
- qualifiers: Qualifiers.IReadOnlyQualifierCollector;
30
- resourceTypes: ResourceTypes.ReadOnlyResourceTypeCollector;
31
- resourceManager: Resources.ResourceManagerBuilder;
32
- importManager: Import.ImportManager;
33
- contextQualifierProvider: Runtime.ValidatingSimpleContextQualifierProvider;
34
- }
35
-
36
- /**
37
- * Result of processing resources
38
- */
39
- export interface ProcessedResources {
40
- system: TsResSystem;
41
- compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection;
42
- resolver: Runtime.ResourceResolver;
43
- resourceCount: number;
44
- summary: {
45
- totalResources: number;
46
- resourceIds: string[];
47
- errorCount: number;
48
- warnings: string[];
49
- };
50
- }
51
-
52
- /**
53
- * Get the default system configuration from ts-res library
54
- */
55
- export function getDefaultSystemConfiguration(): Config.Model.ISystemConfiguration {
56
- const defaultResult = Config.getPredefinedDeclaration('default');
57
- if (defaultResult.isSuccess()) {
58
- return defaultResult.value;
59
- }
60
-
61
- // Fallback configuration if predefined default is not available
62
- return {
63
- name: 'Browser Default Configuration',
64
- description: 'Fallback default configuration for ts-res browser tool',
65
- qualifierTypes: [
66
- {
67
- name: 'language',
68
- systemType: 'language',
69
- configuration: {
70
- allowContextList: true
71
- }
72
- },
73
- {
74
- name: 'territory',
75
- systemType: 'territory',
76
- configuration: {
77
- allowContextList: false
78
- }
79
- }
80
- ],
81
- qualifiers: [
82
- {
83
- name: 'currentTerritory',
84
- token: 'geo',
85
- typeName: 'territory',
86
- defaultPriority: 850
87
- },
88
- {
89
- name: 'language',
90
- token: 'lang',
91
- typeName: 'language',
92
- defaultPriority: 800
93
- }
94
- ],
95
- resourceTypes: [
96
- {
97
- name: 'json',
98
- typeName: 'json'
99
- }
100
- ]
101
- };
102
- }
103
-
104
- /**
105
- * Default system configuration for ts-res browser tool
106
- * Now uses the predefined default from ts-res library
107
- */
108
- export const DEFAULT_SYSTEM_CONFIGURATION: Config.Model.ISystemConfiguration =
109
- getDefaultSystemConfiguration();
110
-
111
- /**
112
- * Get all available predefined configuration options
113
- */
114
- export function getAvailablePredefinedConfigurations(): {
115
- name: Config.PredefinedSystemConfiguration;
116
- label: string;
117
- description: string;
118
- }[] {
119
- return [
120
- {
121
- name: 'default',
122
- label: 'Default',
123
- description: 'Default system configuration'
124
- },
125
- {
126
- name: 'language-priority',
127
- label: 'Language Priority',
128
- description: 'Language priority system configuration'
129
- },
130
- {
131
- name: 'territory-priority',
132
- label: 'Territory Priority',
133
- description: 'Territory priority system configuration'
134
- },
135
- {
136
- name: 'extended-example',
137
- label: 'Extended Example',
138
- description: 'Extended example configuration with market, role, environment, and currency qualifiers'
139
- }
140
- ];
141
- }
142
-
143
- /**
144
- * Get a predefined configuration by name
145
- */
146
- export function getPredefinedConfiguration(
147
- name: Config.PredefinedSystemConfiguration
148
- ): Result<Config.Model.ISystemConfiguration> {
149
- return Config.getPredefinedDeclaration(name);
150
- }
151
-
152
- /**
153
- * Legacy default qualifier declarations for backward compatibility
154
- */
155
- export const DEFAULT_QUALIFIER_DECLARATIONS: Qualifiers.IQualifierDecl[] = [
156
- {
157
- name: 'homeTerritory',
158
- typeName: 'territory',
159
- defaultPriority: 800,
160
- token: 'home'
161
- },
162
- {
163
- name: 'currentTerritory',
164
- typeName: 'territory',
165
- defaultPriority: 700
166
- },
167
- {
168
- name: 'language',
169
- typeName: 'language',
170
- defaultPriority: 600,
171
- token: 'lang'
172
- },
173
- {
174
- name: 'role',
175
- typeName: 'literal',
176
- defaultPriority: 550
177
- },
178
- {
179
- name: 'env',
180
- typeName: 'literal',
181
- defaultPriority: 500
182
- },
183
- {
184
- name: 'platform',
185
- typeName: 'literal',
186
- defaultPriority: 450
187
- },
188
- {
189
- name: 'density',
190
- typeName: 'literal',
191
- defaultPriority: 400
192
- }
193
- ];
194
-
195
- /**
196
- * Creates a complete ts-res system setup using SystemConfiguration
197
- */
198
- export function createTsResSystemFromConfig(
199
- systemConfig?: Config.Model.ISystemConfiguration
200
- ): Result<TsResSystem> {
201
- const configToUse = systemConfig ?? DEFAULT_SYSTEM_CONFIGURATION;
202
-
203
- return Config.SystemConfiguration.create(configToUse).onSuccess((systemConfiguration) => {
204
- try {
205
- // Set up resource manager
206
- const resourceManager = Resources.ResourceManagerBuilder.create({
207
- qualifiers: systemConfiguration.qualifiers,
208
- resourceTypes: systemConfiguration.resourceTypes
209
- }).orThrow();
210
-
211
- // Set up import manager
212
- const importManager = Import.ImportManager.create({
213
- resources: resourceManager
214
- }).orThrow();
215
-
216
- // Set up context qualifier provider
217
- const contextQualifierProvider = Runtime.ValidatingSimpleContextQualifierProvider.create({
218
- qualifiers: systemConfiguration.qualifiers
219
- }).orThrow();
220
-
221
- return succeed({
222
- qualifierTypes: systemConfiguration.qualifierTypes,
223
- qualifiers: systemConfiguration.qualifiers,
224
- resourceTypes: systemConfiguration.resourceTypes,
225
- resourceManager,
226
- importManager,
227
- contextQualifierProvider
228
- });
229
- } catch (error) {
230
- return fail(
231
- `Failed to create ts-res system: ${error instanceof Error ? error.message : String(error)}`
232
- );
233
- }
234
- });
235
- }
236
-
237
- /**
238
- * Legacy function - creates a complete ts-res system setup using manual construction
239
- * @deprecated Use createTsResSystemFromConfig instead
240
- */
241
- export function createTsResSystem(config: TsResConfig = {}): Result<TsResSystem> {
242
- try {
243
- // Set up qualifier types
244
- const defaultQualifierTypes = [
245
- QualifierTypes.LanguageQualifierType.create().orThrow(),
246
- QualifierTypes.TerritoryQualifierType.create().orThrow(),
247
- QualifierTypes.LiteralQualifierType.create().orThrow()
248
- ];
249
-
250
- const qualifierTypes = QualifierTypes.QualifierTypeCollector.create({
251
- qualifierTypes: [...defaultQualifierTypes, ...(config.additionalQualifierTypes ?? [])]
252
- }).orThrow();
253
-
254
- // Set up qualifiers
255
- const qualifierDecls = config.qualifierDecls ?? DEFAULT_QUALIFIER_DECLARATIONS;
256
- const qualifiers = Qualifiers.QualifierCollector.create({
257
- qualifierTypes,
258
- qualifiers: qualifierDecls
259
- }).orThrow();
260
-
261
- // Set up resource types
262
- const defaultResourceTypes = [ResourceTypes.JsonResourceType.create().orThrow()];
263
-
264
- const resourceTypes = ResourceTypes.ResourceTypeCollector.create({
265
- resourceTypes: [...defaultResourceTypes, ...(config.additionalResourceTypes ?? [])]
266
- }).orThrow();
267
-
268
- // Set up resource manager
269
- const resourceManager = Resources.ResourceManagerBuilder.create({
270
- qualifiers,
271
- resourceTypes
272
- }).orThrow();
273
-
274
- // Set up import manager
275
- const importManager = Import.ImportManager.create({
276
- resources: resourceManager
277
- }).orThrow();
278
-
279
- // Set up context qualifier provider
280
- const contextQualifierProvider = Runtime.ValidatingSimpleContextQualifierProvider.create({
281
- qualifiers
282
- }).orThrow();
283
-
284
- return succeed({
285
- qualifierTypes,
286
- qualifiers,
287
- resourceTypes,
288
- resourceManager,
289
- importManager,
290
- contextQualifierProvider
291
- });
292
- } catch (error) {
293
- return fail(`Failed to create ts-res system: ${error instanceof Error ? error.message : String(error)}`);
294
- }
295
- }
296
-
297
- /**
298
- * Processes imported files through ts-res ImportManager
299
- */
300
- export function processImportedFiles(
301
- files: ImportedFile[],
302
- systemConfigOrSystem?: Config.Model.ISystemConfiguration | TsResSystem
303
- ): Result<ProcessedResources> {
304
- if (files.length === 0) {
305
- return fail('No files provided for processing');
306
- }
307
-
308
- // Determine if we have a system or a config
309
- const systemResult =
310
- systemConfigOrSystem &&
311
- 'qualifierTypes' in systemConfigOrSystem &&
312
- 'resourceManager' in systemConfigOrSystem
313
- ? succeed(systemConfigOrSystem as TsResSystem)
314
- : createTsResSystemFromConfig(systemConfigOrSystem as Config.Model.ISystemConfiguration);
315
-
316
- return systemResult.onSuccess((tsResSystem) => {
317
- // Convert ImportedFile[] to IInMemoryFile[] format
318
- const inMemoryFiles = files.map((file) => ({
319
- path: file.path,
320
- contents: file.content
321
- }));
322
-
323
- return FileTree.inMemory(inMemoryFiles)
324
- .onSuccess((fileTree) => {
325
- return Import.ImportManager.create({
326
- fileTree,
327
- resources: tsResSystem.resourceManager
328
- });
329
- })
330
- .onSuccess<ProcessedResources>((importManager) => {
331
- // Import each file using its filesystem path
332
- for (const file of files) {
333
- const importResult = importManager.importFromFileSystem(file.path);
334
- if (importResult.isFailure()) {
335
- return fail(`Failed to import file ${file.path}: ${importResult.message}`);
336
- }
337
- }
338
- // Update the system with the new ImportManager
339
- const updatedSystem = {
340
- ...tsResSystem,
341
- importManager
342
- };
343
- return finalizeProcessing(updatedSystem);
344
- })
345
- .withErrorFormat((message) => `processImportedFiles failed: ${message}`);
346
- });
347
- }
348
-
349
- export function processImportedDirectory(
350
- directory: ImportedDirectory,
351
- systemConfigOrSystem?: Config.Model.ISystemConfiguration | TsResSystem
352
- ): Result<ProcessedResources> {
353
- // Determine if we have a system or a config
354
- const systemResult =
355
- systemConfigOrSystem &&
356
- 'qualifierTypes' in systemConfigOrSystem &&
357
- 'resourceManager' in systemConfigOrSystem
358
- ? succeed(systemConfigOrSystem as TsResSystem)
359
- : createTsResSystemFromConfig(systemConfigOrSystem as Config.Model.ISystemConfiguration);
360
-
361
- return systemResult.onSuccess((tsResSystem) => {
362
- // Create custom FileTree that preserves directory structure
363
- return BrowserFileTreeAccessors.create(directory)
364
- .onSuccess((accessors) => {
365
- return FileTree.FileTree.create(accessors);
366
- })
367
- .onSuccess((fileTree) => {
368
- return Import.ImportManager.create({
369
- fileTree,
370
- resources: tsResSystem.resourceManager
371
- });
372
- })
373
- .onSuccess<ProcessedResources>((importManager) => {
374
- // Import from root path since we set the imported directory as the FileTree root
375
- // The importer will traverse the tree structure and extract qualifiers from directory names
376
- const importResult = importManager.importFromFileSystem('/');
377
- if (importResult.isFailure()) {
378
- return fail(`Failed to import directory ${directory.name}: ${importResult.message}`);
379
- }
380
-
381
- // Update the system with the new ImportManager
382
- const updatedSystem = {
383
- ...tsResSystem,
384
- importManager
385
- };
386
- return finalizeProcessing(updatedSystem);
387
- })
388
- .withErrorFormat((message) => `processImportedDirectory failed: ${message}`);
389
- });
390
- }
391
-
392
- /**
393
- * Finalizes processing and creates compiled resources
394
- */
395
- function finalizeProcessing(system: TsResSystem): Result<ProcessedResources> {
396
- console.log('=== FINALIZING PROCESSING ===');
397
- console.log('Resource manager resources:', system.resourceManager.resources.size);
398
- console.log('Resource manager resource keys:', Array.from(system.resourceManager.resources.keys()));
399
-
400
- return system.resourceManager
401
- .getCompiledResourceCollection({ includeMetadata: true })
402
- .onSuccess((compiledCollection: ResourceJson.Compiled.ICompiledResourceCollection) => {
403
- console.log('=== COMPILED COLLECTION CREATED ===');
404
- console.log('Compiled collection data:', compiledCollection);
405
- console.log('Decisions length:', compiledCollection.decisions?.length);
406
- console.log('ConditionSets length:', compiledCollection.conditionSets?.length);
407
- console.log('Conditions length:', compiledCollection.conditions?.length);
408
- console.log('Resources length:', compiledCollection.resources?.length);
409
-
410
- return Runtime.ResourceResolver.create({
411
- resourceManager: system.resourceManager,
412
- qualifierTypes: system.qualifierTypes,
413
- contextQualifierProvider: system.contextQualifierProvider
414
- }).onSuccess((resolver) => {
415
- // Create summary
416
- const resourceIds = Array.from(system.resourceManager.resources.keys());
417
- const summary = {
418
- totalResources: resourceIds.length,
419
- resourceIds,
420
- errorCount: 0, // TODO: Track errors during processing
421
- warnings: [] // TODO: Collect warnings during processing
422
- };
423
-
424
- console.log('=== FINALIZATION COMPLETE ===');
425
- console.log('Resource count:', resourceIds.length);
426
- console.log('Resource IDs:', resourceIds);
427
-
428
- return succeed({
429
- system,
430
- compiledCollection,
431
- resolver,
432
- resourceCount: resourceIds.length,
433
- summary
434
- });
435
- });
436
- })
437
- .withErrorFormat((message: string) => `Failed to finalize processing: ${message}`);
438
- }
439
-
440
- /**
441
- * Creates a simple context for resource resolution
442
- */
443
- export function createSimpleContext(
444
- contextValues: Record<string, string>,
445
- system: TsResSystem
446
- ): Result<Runtime.ValidatingSimpleContextQualifierProvider> {
447
- return Runtime.ValidatingSimpleContextQualifierProvider.create({
448
- qualifiers: system.qualifiers
449
- })
450
- .onSuccess((provider): Result<Runtime.ValidatingSimpleContextQualifierProvider> => {
451
- // Set context values
452
- for (const [qualifierName, value] of Object.entries(contextValues)) {
453
- try {
454
- provider.set(qualifierName as any, value as any);
455
- } catch (error) {
456
- return fail(
457
- `Failed to set context value ${qualifierName}=${value}: ${
458
- error instanceof Error ? error.message : String(error)
459
- }`
460
- );
461
- }
462
- }
463
-
464
- return succeed(provider);
465
- })
466
- .withErrorFormat((message) => `Failed to create context: ${message}`);
467
- }
@@ -1,238 +0,0 @@
1
- /*
2
- * Copyright (c) 2025 Erik Fortune
3
- *
4
- * Permission is hereby granted, free of charge, to any person obtaining a copy
5
- * of this software and associated documentation files (the "Software"), to deal
6
- * in the Software without restriction, including without limitation the rights
7
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- * copies of the Software, and to permit persons to whom the Software is
9
- * furnished to do so, subject to the following conditions:
10
- *
11
- * The above copyright notice and this permission notice shall be included in all
12
- * copies or substantial portions of the Software.
13
- *
14
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- * SOFTWARE.
21
- */
22
-
23
- /**
24
- * Configuration options that can be passed via URL parameters
25
- */
26
- export interface IUrlConfigOptions {
27
- /**
28
- * Input file path
29
- */
30
- input?: string;
31
-
32
- /**
33
- * Configuration name or path
34
- */
35
- config?: string;
36
-
37
- /**
38
- * Context filter token (pipe-separated)
39
- */
40
- contextFilter?: string;
41
-
42
- /**
43
- * Qualifier defaults token (pipe-separated)
44
- */
45
- qualifierDefaults?: string;
46
-
47
- /**
48
- * Resource types filter (comma-separated)
49
- */
50
- resourceTypes?: string;
51
-
52
- /**
53
- * Maximum distance for language matching
54
- */
55
- maxDistance?: number;
56
-
57
- /**
58
- * Whether to reduce qualifiers
59
- */
60
- reduceQualifiers?: boolean;
61
-
62
- /**
63
- * Whether to launch in interactive mode
64
- */
65
- interactive?: boolean;
66
-
67
- /**
68
- * Starting directory for input file picker (derived from input path)
69
- */
70
- inputStartDir?: string;
71
-
72
- /**
73
- * Starting directory for config file picker (derived from config path)
74
- */
75
- configStartDir?: string;
76
-
77
- /**
78
- * Whether to use ZIP loading mode
79
- */
80
- loadZip?: boolean;
81
-
82
- /**
83
- * Path to ZIP file to load (for CLI-generated ZIPs)
84
- */
85
- zipPath?: string;
86
-
87
- /**
88
- * Name of ZIP file to load from Downloads (filename only)
89
- */
90
- zipFile?: string;
91
- }
92
-
93
- /**
94
- * Parses URL parameters and extracts configuration options
95
- */
96
- export function parseUrlParameters(): IUrlConfigOptions {
97
- const params = new URLSearchParams(window.location.search);
98
-
99
- const options: IUrlConfigOptions = {};
100
-
101
- // Parse string parameters
102
- const input = params.get('input');
103
- if (input) {
104
- options.input = input;
105
- // Set starting directory for input file picker
106
- if (isFilePath(input)) {
107
- options.inputStartDir = extractDirectoryPath(input);
108
- }
109
- }
110
-
111
- const config = params.get('config');
112
- if (config) {
113
- options.config = config;
114
- // Set starting directory for config file picker
115
- if (isFilePath(config)) {
116
- options.configStartDir = extractDirectoryPath(config);
117
- }
118
- }
119
-
120
- const contextFilter = params.get('contextFilter');
121
- if (contextFilter) options.contextFilter = contextFilter;
122
-
123
- const qualifierDefaults = params.get('qualifierDefaults');
124
- if (qualifierDefaults) options.qualifierDefaults = qualifierDefaults;
125
-
126
- const resourceTypes = params.get('resourceTypes');
127
- if (resourceTypes) options.resourceTypes = resourceTypes;
128
-
129
- // Parse numeric parameters
130
- const maxDistance = params.get('maxDistance');
131
- if (maxDistance) {
132
- const parsed = parseInt(maxDistance, 10);
133
- if (!isNaN(parsed)) options.maxDistance = parsed;
134
- }
135
-
136
- // Parse boolean parameters
137
- const reduceQualifiers = params.get('reduceQualifiers');
138
- if (reduceQualifiers === 'true') options.reduceQualifiers = true;
139
-
140
- const interactive = params.get('interactive');
141
- if (interactive === 'true') options.interactive = true;
142
-
143
- // Parse ZIP loading parameters
144
- const loadZip = params.get('loadZip');
145
- if (loadZip === 'true') options.loadZip = true;
146
-
147
- const zipPath = params.get('zipPath');
148
- if (zipPath) options.zipPath = zipPath;
149
-
150
- const zipFile = params.get('zipFile');
151
- if (zipFile) options.zipFile = zipFile;
152
-
153
- return options;
154
- }
155
-
156
- /**
157
- * Converts context filter token to context object
158
- * Example: "language=en-US|territory=US" -> { language: "en-US", territory: "US" }
159
- */
160
- export function parseContextFilter(contextFilter: string): Record<string, string> {
161
- const context: Record<string, string> = {};
162
-
163
- const tokens = contextFilter.split('|');
164
- for (const token of tokens) {
165
- const [key, value] = token.split('=');
166
- if (key && value) {
167
- context[key.trim()] = value.trim();
168
- }
169
- }
170
-
171
- return context;
172
- }
173
-
174
- /**
175
- * Converts qualifier defaults token to structured format
176
- * Example: "language=en-US,en-CA|territory=US" -> { language: ["en-US", "en-CA"], territory: ["US"] }
177
- */
178
- export function parseQualifierDefaults(qualifierDefaults: string): Record<string, string[]> {
179
- const defaults: Record<string, string[]> = {};
180
-
181
- const tokens = qualifierDefaults.split('|');
182
- for (const token of tokens) {
183
- const [key, values] = token.split('=');
184
- if (key && values) {
185
- defaults[key.trim()] = values.split(',').map((v) => v.trim());
186
- }
187
- }
188
-
189
- return defaults;
190
- }
191
-
192
- /**
193
- * Converts resource types string to array
194
- * Example: "json,string" -> ["json", "string"]
195
- */
196
- export function parseResourceTypes(resourceTypes: string): string[] {
197
- return resourceTypes
198
- .split(',')
199
- .map((t) => t.trim())
200
- .filter((t) => t.length > 0);
201
- }
202
-
203
- /**
204
- * Extracts the directory path from a file or directory path
205
- * If the path appears to be a directory (no extension), returns it as-is
206
- * If the path appears to be a file, returns the parent directory
207
- */
208
- export function extractDirectoryPath(path: string): string {
209
- if (!path) return '';
210
-
211
- // Normalize path separators
212
- const normalizedPath = path.replace(/\\/g, '/');
213
-
214
- // Check if it looks like a file (has an extension)
215
- const parts = normalizedPath.split('/');
216
- const lastPart = parts[parts.length - 1];
217
-
218
- // If the last part has an extension, treat as file and return directory
219
- if (lastPart.includes('.') && !lastPart.startsWith('.')) {
220
- return parts.slice(0, -1).join('/') || '/';
221
- }
222
-
223
- // Otherwise, treat as directory
224
- return normalizedPath;
225
- }
226
-
227
- /**
228
- * Determines if a path appears to be a file (has extension) or directory
229
- */
230
- export function isFilePath(path: string): boolean {
231
- if (!path) return false;
232
-
233
- const parts = path.split('/');
234
- const lastPart = parts[parts.length - 1];
235
-
236
- // Has extension and doesn't start with dot (hidden files)
237
- return lastPart.includes('.') && !lastPart.startsWith('.');
238
- }