@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,245 +0,0 @@
1
- import { Result, succeed, fail, FileTree } from '@fgv/ts-utils';
2
- import { ImportedDirectory, ImportedFile } from './fileImport';
3
-
4
- /**
5
- * Custom FileTreeAccessors implementation for browser environment
6
- * that preserves directory structure for ts-res qualifier extraction
7
- */
8
- export class BrowserFileTreeAccessors implements FileTree.IFileTreeAccessors {
9
- private readonly rootDirectory: ImportedDirectory;
10
- private readonly pathSeparator = '/';
11
-
12
- constructor(rootDirectory: ImportedDirectory) {
13
- this.rootDirectory = rootDirectory;
14
- }
15
-
16
- public static create(rootDirectory: ImportedDirectory): Result<BrowserFileTreeAccessors> {
17
- try {
18
- return succeed(new BrowserFileTreeAccessors(rootDirectory));
19
- } catch (error) {
20
- return fail(
21
- `Failed to create BrowserFileTreeAccessors: ${error instanceof Error ? error.message : String(error)}`
22
- );
23
- }
24
- }
25
-
26
- // Path manipulation methods (browser-compatible, no Node.js dependencies)
27
- public resolveAbsolutePath(...paths: string[]): string {
28
- // Start with root path or first absolute path
29
- let result = '';
30
-
31
- for (const path of paths) {
32
- if (!path) continue;
33
-
34
- if (path.startsWith('/')) {
35
- // Absolute path - replace current result
36
- result = path;
37
- } else {
38
- // Relative path - append to current result
39
- result = this.joinPaths(result, path);
40
- }
41
- }
42
-
43
- // Ensure it starts with / (absolute)
44
- if (!result.startsWith('/')) {
45
- result = '/' + result;
46
- }
47
-
48
- return this.normalizePath(result);
49
- }
50
-
51
- public getExtension(path: string): string {
52
- const baseName = this.getBaseName(path);
53
- const lastDot = baseName.lastIndexOf('.');
54
- return lastDot === -1 ? '' : baseName.substring(lastDot);
55
- }
56
-
57
- public getBaseName(path: string, suffix?: string): string {
58
- if (!path) return '';
59
-
60
- // Remove trailing slashes
61
- const normalizedPath = path.replace(/\/+$/, '');
62
-
63
- // Get the last segment
64
- const lastSlash = normalizedPath.lastIndexOf('/');
65
- let baseName = lastSlash === -1 ? normalizedPath : normalizedPath.substring(lastSlash + 1);
66
-
67
- // Remove suffix if provided
68
- if (suffix && baseName.endsWith(suffix)) {
69
- baseName = baseName.substring(0, baseName.length - suffix.length);
70
- }
71
-
72
- return baseName;
73
- }
74
-
75
- public joinPaths(...paths: string[]): string {
76
- if (paths.length === 0) return '';
77
-
78
- const parts: string[] = [];
79
-
80
- for (const path of paths) {
81
- if (!path) continue;
82
-
83
- // Split by separator and filter empty parts
84
- const pathParts = path.split(this.pathSeparator).filter((part) => part.length > 0);
85
- parts.push(...pathParts);
86
- }
87
-
88
- return '/' + parts.join(this.pathSeparator);
89
- }
90
-
91
- // File system access methods
92
- public getItem(path: string): Result<FileTree.FileTreeItem> {
93
- try {
94
- const absolutePath = this.ensureAbsolute(path);
95
- const result = this.findItemAtPath(absolutePath);
96
-
97
- if (!result) {
98
- return fail(`Item not found at path: ${absolutePath}`);
99
- }
100
-
101
- if (result.type === 'file') {
102
- return FileTree.FileItem.create(absolutePath, this);
103
- } else {
104
- return FileTree.DirectoryItem.create(absolutePath, this);
105
- }
106
- } catch (error) {
107
- return fail(`Failed to get item: ${error instanceof Error ? error.message : String(error)}`);
108
- }
109
- }
110
-
111
- public getFileContents(path: string): Result<string> {
112
- try {
113
- const absolutePath = this.ensureAbsolute(path);
114
- const result = this.findItemAtPath(absolutePath);
115
-
116
- if (!result) {
117
- return fail(`File not found at path: ${absolutePath}`);
118
- }
119
-
120
- if (result.type !== 'file') {
121
- return fail(`Path is not a file: ${absolutePath}`);
122
- }
123
-
124
- const file = result.item as ImportedFile;
125
- return succeed(file.content);
126
- } catch (error) {
127
- return fail(`Failed to get file contents: ${error instanceof Error ? error.message : String(error)}`);
128
- }
129
- }
130
-
131
- public getChildren(path: string): Result<ReadonlyArray<FileTree.FileTreeItem>> {
132
- try {
133
- const absolutePath = this.ensureAbsolute(path);
134
- const result = this.findItemAtPath(absolutePath);
135
-
136
- if (!result) {
137
- return fail(`Directory not found at path: ${absolutePath}`);
138
- }
139
-
140
- if (result.type !== 'directory') {
141
- return fail(`Path is not a directory: ${absolutePath}`);
142
- }
143
-
144
- const directory = result.item as ImportedDirectory;
145
- const children: FileTree.FileTreeItem[] = [];
146
-
147
- // Add files
148
- for (const file of directory.files) {
149
- const childPath = this.joinPaths(absolutePath, file.name);
150
- const fileItemResult = FileTree.FileItem.create(childPath, this);
151
-
152
- if (fileItemResult.isSuccess()) {
153
- children.push(fileItemResult.value);
154
- }
155
- }
156
-
157
- // Add subdirectories
158
- for (const subDirectory of directory.directories) {
159
- const childPath = this.joinPaths(absolutePath, subDirectory.name);
160
- const dirItemResult = FileTree.DirectoryItem.create(childPath, this);
161
-
162
- if (dirItemResult.isSuccess()) {
163
- children.push(dirItemResult.value);
164
- }
165
- }
166
-
167
- return succeed(children);
168
- } catch (error) {
169
- return fail(`Failed to get children: ${error instanceof Error ? error.message : String(error)}`);
170
- }
171
- }
172
-
173
- // Helper methods
174
- private ensureAbsolute(path: string): string {
175
- return path.startsWith('/') ? path : '/' + path;
176
- }
177
-
178
- private normalizePath(path: string): string {
179
- if (!path) return '/';
180
-
181
- // Split into parts, filter empty ones, and handle . and ..
182
- const parts = path.split('/').filter((part) => part.length > 0 && part !== '.');
183
- const normalized: string[] = [];
184
-
185
- for (const part of parts) {
186
- if (part === '..') {
187
- if (normalized.length > 0) {
188
- normalized.pop();
189
- }
190
- } else {
191
- normalized.push(part);
192
- }
193
- }
194
-
195
- return '/' + normalized.join('/');
196
- }
197
-
198
- private findItemAtPath(
199
- absolutePath: string
200
- ): { item: ImportedDirectory | ImportedFile; type: 'file' | 'directory' } | null {
201
- const normalizedPath = this.normalizePath(absolutePath);
202
-
203
- // Handle root path
204
- if (normalizedPath === '/' || normalizedPath === '') {
205
- return { item: this.rootDirectory, type: 'directory' };
206
- }
207
-
208
- // Split path into segments (excluding root slash)
209
- const segments = normalizedPath
210
- .substring(1)
211
- .split('/')
212
- .filter((seg) => seg.length > 0);
213
-
214
- let currentDir = this.rootDirectory;
215
-
216
- // Navigate through directory segments
217
- for (let i = 0; i < segments.length - 1; i++) {
218
- const segment = segments[i];
219
- const subDir = currentDir.directories.find((dir) => dir.name === segment);
220
-
221
- if (!subDir) {
222
- return null;
223
- }
224
-
225
- currentDir = subDir;
226
- }
227
-
228
- // Get the final item (file or directory)
229
- const finalSegment = segments[segments.length - 1];
230
-
231
- // Check if it's a file
232
- const file = currentDir.files.find((f) => f.name === finalSegment);
233
- if (file) {
234
- return { item: file, type: 'file' };
235
- }
236
-
237
- // Check if it's a directory
238
- const directory = currentDir.directories.find((d) => d.name === finalSegment);
239
- if (directory) {
240
- return { item: directory, type: 'directory' };
241
- }
242
-
243
- return null;
244
- }
245
- }
@@ -1,336 +0,0 @@
1
- import { Result, succeed, fail } from '@fgv/ts-utils';
2
-
3
- export interface ImportedFile {
4
- name: string;
5
- path: string;
6
- content: string;
7
- handle?: FileSystemFileHandle;
8
- }
9
-
10
- export interface ImportedDirectory {
11
- name: string;
12
- path: string;
13
- files: ImportedFile[];
14
- directories: ImportedDirectory[];
15
- }
16
-
17
- export interface FileImportOptions {
18
- acceptedTypes?: string[];
19
- multiple?: boolean;
20
- includeDirectories?: boolean;
21
- startIn?: string | FileSystemHandle;
22
- }
23
-
24
- /**
25
- * Check if File System Access API is supported
26
- */
27
- export function isFileSystemAccessSupported(): boolean {
28
- return 'showDirectoryPicker' in window && 'showOpenFilePicker' in window;
29
- }
30
-
31
- /**
32
- * Modern File System Access API implementation
33
- */
34
- export class ModernFileImporter {
35
- /**
36
- * Pick a directory using File System Access API
37
- */
38
- async pickDirectory(options: FileImportOptions = {}): Promise<Result<ImportedDirectory>> {
39
- try {
40
- if (!isFileSystemAccessSupported()) {
41
- return fail('File System Access API not supported');
42
- }
43
-
44
- const directoryHandle = await window.showDirectoryPicker({
45
- mode: 'read',
46
- startIn: options.startIn || 'documents'
47
- });
48
-
49
- const importedDirectory = await this.processDirectoryHandle(directoryHandle);
50
- return succeed(importedDirectory);
51
- } catch (error) {
52
- if (error instanceof DOMException && error.name === 'AbortError') {
53
- return fail('Directory selection was cancelled');
54
- }
55
- return fail(`Failed to pick directory: ${error instanceof Error ? error.message : String(error)}`);
56
- }
57
- }
58
-
59
- /**
60
- * Pick files using File System Access API
61
- */
62
- async pickFiles(options: FileImportOptions = {}): Promise<Result<ImportedFile[]>> {
63
- try {
64
- if (!isFileSystemAccessSupported()) {
65
- return fail('File System Access API not supported');
66
- }
67
-
68
- const fileHandles = await window.showOpenFilePicker({
69
- multiple: options.multiple ?? true,
70
- types: options.acceptedTypes
71
- ? [
72
- {
73
- description: 'Resource files',
74
- accept: {
75
- 'application/json': options.acceptedTypes
76
- }
77
- }
78
- ]
79
- : undefined,
80
- excludeAcceptAllOption: false,
81
- startIn: options.startIn || 'documents'
82
- });
83
-
84
- const importedFiles: ImportedFile[] = [];
85
- for (const fileHandle of fileHandles) {
86
- const result = await this.processFileHandle(fileHandle);
87
- if (result.isSuccess()) {
88
- importedFiles.push(result.value);
89
- }
90
- }
91
-
92
- return succeed(importedFiles);
93
- } catch (error) {
94
- if (error instanceof DOMException && error.name === 'AbortError') {
95
- return fail('File selection was cancelled');
96
- }
97
- return fail(`Failed to pick files: ${error instanceof Error ? error.message : String(error)}`);
98
- }
99
- }
100
-
101
- private async processDirectoryHandle(
102
- directoryHandle: FileSystemDirectoryHandle,
103
- basePath = ''
104
- ): Promise<ImportedDirectory> {
105
- const files: ImportedFile[] = [];
106
- const directories: ImportedDirectory[] = [];
107
- const currentPath = basePath ? `${basePath}/${directoryHandle.name}` : directoryHandle.name;
108
-
109
- for await (const [name, handle] of directoryHandle.entries()) {
110
- if (handle.kind === 'file') {
111
- const result = await this.processFileHandle(handle as FileSystemFileHandle, currentPath);
112
- if (result.isSuccess()) {
113
- files.push(result.value);
114
- }
115
- } else if (handle.kind === 'directory') {
116
- const subDirectory = await this.processDirectoryHandle(
117
- handle as FileSystemDirectoryHandle,
118
- currentPath
119
- );
120
- directories.push(subDirectory);
121
- }
122
- }
123
-
124
- return {
125
- name: directoryHandle.name,
126
- path: currentPath,
127
- files,
128
- directories
129
- };
130
- }
131
-
132
- private async processFileHandle(
133
- fileHandle: FileSystemFileHandle,
134
- basePath = ''
135
- ): Promise<Result<ImportedFile>> {
136
- try {
137
- const file = await fileHandle.getFile();
138
- const content = await file.text();
139
- const filePath = basePath ? `${basePath}/${file.name}` : file.name;
140
-
141
- return succeed({
142
- name: file.name,
143
- path: filePath,
144
- content,
145
- handle: fileHandle
146
- });
147
- } catch (error) {
148
- return fail(
149
- `Failed to read file ${fileHandle.name}: ${error instanceof Error ? error.message : String(error)}`
150
- );
151
- }
152
- }
153
- }
154
-
155
- /**
156
- * Fallback implementation using traditional file input
157
- */
158
- export class FallbackFileImporter {
159
- /**
160
- * Pick a directory using webkitdirectory attribute
161
- */
162
- async pickDirectory(): Promise<Result<ImportedDirectory>> {
163
- return new Promise((resolve) => {
164
- const input = document.createElement('input');
165
- input.type = 'file';
166
- input.multiple = true;
167
- input.webkitdirectory = true;
168
-
169
- input.addEventListener('change', async (event) => {
170
- const files = (event.target as HTMLInputElement).files;
171
- if (!files || files.length === 0) {
172
- resolve(fail('No directory selected'));
173
- return;
174
- }
175
-
176
- try {
177
- const importedDirectory = await this.processFileList(files);
178
- resolve(succeed(importedDirectory));
179
- } catch (error) {
180
- resolve(
181
- fail(`Failed to process directory: ${error instanceof Error ? error.message : String(error)}`)
182
- );
183
- }
184
- });
185
-
186
- input.addEventListener('cancel', () => {
187
- resolve(fail('Directory selection was cancelled'));
188
- });
189
-
190
- input.click();
191
- });
192
- }
193
-
194
- /**
195
- * Pick files using traditional file input
196
- */
197
- async pickFiles(options: FileImportOptions = {}): Promise<Result<ImportedFile[]>> {
198
- return new Promise((resolve) => {
199
- const input = document.createElement('input');
200
- input.type = 'file';
201
- input.multiple = options.multiple ?? true;
202
-
203
- if (options.acceptedTypes) {
204
- input.accept = options.acceptedTypes.join(',');
205
- }
206
-
207
- input.addEventListener('change', async (event) => {
208
- const files = (event.target as HTMLInputElement).files;
209
- if (!files || files.length === 0) {
210
- resolve(fail('No files selected'));
211
- return;
212
- }
213
-
214
- try {
215
- const importedFiles: ImportedFile[] = [];
216
- for (const file of Array.from(files)) {
217
- const content = await file.text();
218
- importedFiles.push({
219
- name: file.name,
220
- path: file.name,
221
- content
222
- });
223
- }
224
- resolve(succeed(importedFiles));
225
- } catch (error) {
226
- resolve(fail(`Failed to read files: ${error instanceof Error ? error.message : String(error)}`));
227
- }
228
- });
229
-
230
- input.addEventListener('cancel', () => {
231
- resolve(fail('File selection was cancelled'));
232
- });
233
-
234
- input.click();
235
- });
236
- }
237
-
238
- private async processFileList(files: FileList): Promise<ImportedDirectory> {
239
- const fileMap = new Map<string, ImportedFile[]>();
240
- const dirMap = new Map<string, ImportedDirectory>();
241
-
242
- // Process all files
243
- for (const file of Array.from(files)) {
244
- const content = await file.text();
245
- const pathParts = file.webkitRelativePath.split('/');
246
- const fileName = pathParts[pathParts.length - 1];
247
- const dirPath = pathParts.slice(0, -1).join('/');
248
-
249
- const importedFile: ImportedFile = {
250
- name: fileName,
251
- path: file.webkitRelativePath,
252
- content
253
- };
254
-
255
- if (!fileMap.has(dirPath)) {
256
- fileMap.set(dirPath, []);
257
- }
258
- fileMap.get(dirPath)!.push(importedFile);
259
- }
260
-
261
- // Build directory structure
262
- const rootDirName = files[0].webkitRelativePath.split('/')[0];
263
- return this.buildDirectoryTree(rootDirName, '', fileMap, dirMap);
264
- }
265
-
266
- private buildDirectoryTree(
267
- name: string,
268
- path: string,
269
- fileMap: Map<string, ImportedFile[]>,
270
- dirMap: Map<string, ImportedDirectory>
271
- ): ImportedDirectory {
272
- const currentPath = path ? `${path}/${name}` : name;
273
- const files = fileMap.get(currentPath) || [];
274
- const directories: ImportedDirectory[] = [];
275
-
276
- // Find subdirectories
277
- for (const [filePath] of fileMap) {
278
- if (filePath.startsWith(currentPath + '/')) {
279
- const relativePath = filePath.substring(currentPath.length + 1);
280
- const nextDirName = relativePath.split('/')[0];
281
-
282
- if (relativePath.includes('/') && !directories.some((d) => d.name === nextDirName)) {
283
- const subDir = this.buildDirectoryTree(nextDirName, currentPath, fileMap, dirMap);
284
- directories.push(subDir);
285
- }
286
- }
287
- }
288
-
289
- return {
290
- name,
291
- path: currentPath,
292
- files,
293
- directories
294
- };
295
- }
296
- }
297
-
298
- /**
299
- * Universal file importer that automatically selects the best available method
300
- */
301
- export class FileImporter {
302
- private modernImporter = new ModernFileImporter();
303
- private fallbackImporter = new FallbackFileImporter();
304
-
305
- /**
306
- * Pick a directory using the best available method
307
- */
308
- async pickDirectory(options: FileImportOptions = {}): Promise<Result<ImportedDirectory>> {
309
- if (isFileSystemAccessSupported()) {
310
- return this.modernImporter.pickDirectory(options);
311
- } else {
312
- return this.fallbackImporter.pickDirectory();
313
- }
314
- }
315
-
316
- /**
317
- * Pick files using the best available method
318
- */
319
- async pickFiles(options: FileImportOptions = {}): Promise<Result<ImportedFile[]>> {
320
- if (isFileSystemAccessSupported()) {
321
- return this.modernImporter.pickFiles(options);
322
- } else {
323
- return this.fallbackImporter.pickFiles(options);
324
- }
325
- }
326
-
327
- /**
328
- * Get the current import method being used
329
- */
330
- getCurrentMethod(): 'modern' | 'fallback' {
331
- return isFileSystemAccessSupported() ? 'modern' : 'fallback';
332
- }
333
- }
334
-
335
- // Export singleton instance
336
- export const fileImporter = new FileImporter();