@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.
- package/cli.js +132 -21
- package/dist/bundle.js +1 -1
- package/dist/bundle.js.LICENSE.txt +11 -1
- package/package.json +42 -36
- package/.rush/temp/587277bfc395406ad444e989b6c309b9698f534c.tar.log +0 -15
- package/.rush/temp/chunked-rush-logs/ts-res-browser.build.chunks.jsonl +0 -35
- package/.rush/temp/operation/build/all.log +0 -35
- package/.rush/temp/operation/build/log-chunks.jsonl +0 -35
- package/.rush/temp/operation/build/state.json +0 -3
- package/.rush/temp/shrinkwrap-deps.json +0 -1225
- package/TS_RES_BROWSER_PLAN.md +0 -689
- package/babel.config.js +0 -17
- package/config/jest.config.json +0 -13
- package/config/rig.json +0 -16
- package/postcss.config.js +0 -3
- package/public/index.html +0 -15
- package/rush-logs/ts-res-browser.build.cache.log +0 -3
- package/rush-logs/ts-res-browser.build.log +0 -35
- package/src/App.tsx +0 -274
- package/src/cli.ts +0 -44
- package/src/components/common/FileImporter.tsx +0 -143
- package/src/components/common/NavigationWarningModal.tsx +0 -62
- package/src/components/layout/AppLayout.tsx +0 -46
- package/src/components/layout/Header.tsx +0 -22
- package/src/components/layout/MessagesWindow.tsx +0 -108
- package/src/components/layout/Sidebar.tsx +0 -99
- package/src/components/tools/CompiledBrowser.tsx +0 -949
- package/src/components/tools/ConfigurationTool.tsx +0 -1405
- package/src/components/tools/FilterTool.tsx +0 -1114
- package/src/components/tools/ImportTool.tsx +0 -249
- package/src/components/tools/ResolutionViewer.tsx +0 -1672
- package/src/components/tools/SourceBrowser.tsx +0 -496
- package/src/components/tools/ZipLoader.tsx +0 -302
- package/src/hooks/useAppState.ts +0 -111
- package/src/hooks/useFileImport.ts +0 -86
- package/src/hooks/useNavigationWarning.ts +0 -67
- package/src/hooks/useResourceManager.ts +0 -295
- package/src/hooks/useUrlParams.ts +0 -56
- package/src/index.css +0 -38
- package/src/main.tsx +0 -16
- package/src/types/app.ts +0 -70
- package/src/types/fileSystemAccess.d.ts +0 -94
- package/src/utils/browserFileTreeAccessors.ts +0 -245
- package/src/utils/fileImport.ts +0 -336
- package/src/utils/fileTreeConverter.ts +0 -160
- package/src/utils/filterResources.ts +0 -677
- package/src/utils/tsResIntegration.ts +0 -467
- package/src/utils/urlParams.ts +0 -238
- package/src/utils/zip/browserZipFileTreeAccessors.ts +0 -385
- package/tailwind.config.js +0 -21
- package/test-data/README.md +0 -184
- package/test-data/config-variations/README.md +0 -64
- package/test-data/config-variations/composition-terminology.json +0 -112
- package/test-data/config-variations/enterprise-complex.json +0 -129
- package/test-data/config-variations/gaming-app.json +0 -87
- package/test-data/config-variations/high-priority-language.json +0 -65
- package/test-data/config-variations/minimal-basic.json +0 -37
- package/test-data/config-variations/qualifier-default-values.json +0 -71
- package/test-data/config-variations/territory-first.json +0 -68
- package/test-data/config-variations/territory-hierarchy-test.json +0 -31
- package/test-data/config-variations/territory-hierarchy-unconstrained.json +0 -32
- package/test-data/default-matching-test/greeting.res.json +0 -83
- package/test-data/default-matching-test/only-defaults.res.json +0 -48
- package/test-data/sample-project/package.json +0 -27
- package/test-data/sample-project/src/resources/app-config.json +0 -27
- package/test-data/sample-project/src/resources/app.json +0 -177
- package/test-data/sample-project/src/resources/config/app.env=development.json +0 -21
- package/test-data/sample-project/src/resources/config/app.json +0 -21
- package/test-data/sample-project/src/resources/config/app.platform=mobile.json +0 -23
- package/test-data/sample-project/src/resources/feature-flags.json +0 -23
- package/test-data/sample-project/src/resources/home=CA/common.json +0 -16
- package/test-data/sample-project/src/resources/images/density=hdpi.json +0 -29
- package/test-data/sample-project/src/resources/images/home.json +0 -29
- package/test-data/sample-project/src/resources/images/logout.json +0 -14
- package/test-data/sample-project/src/resources/images/settings.json +0 -14
- package/test-data/sample-project/src/resources/images/ui-icons.json +0 -29
- package/test-data/sample-project/src/resources/images/user.density=hdpi.json +0 -14
- package/test-data/sample-project/src/resources/images/user.json +0 -14
- package/test-data/sample-project/src/resources/lang=en-US/common.json +0 -15
- package/test-data/sample-project/src/resources/language=en-CA/ui-terms.json +0 -19
- package/test-data/sample-project/src/resources/language=en-GB/ui-terms.json +0 -30
- package/test-data/sample-project/src/resources/language=fr-CA/ui-terms.json +0 -25
- package/test-data/sample-project/src/resources/language=nl-BE/ui-terms.json +0 -17
- package/test-data/sample-project/src/resources/region=europe/feature-flags.json +0 -17
- package/test-data/sample-project/src/resources/region=north-america/feature-flags.json +0 -18
- package/test-data/sample-project/src/resources/strings/dashboard.home=CA,language=fr.json +0 -20
- package/test-data/sample-project/src/resources/strings/dashboard.json +0 -18
- package/test-data/sample-project/src/resources/strings/dashboard.role=admin.json +0 -23
- package/test-data/sample-project/src/resources/strings/dashboard.role=user.json +0 -21
- package/test-data/sample-project/src/resources/strings/language=en-US.json +0 -16
- package/test-data/sample-project/src/resources/strings.json +0 -78
- package/test-data/sample-project/src/resources/territory=BE/app-config.json +0 -19
- package/test-data/sample-project/src/resources/territory=CA/app-config.json +0 -21
- package/test-data/sample-project/src/resources/territory=GB/app-config.json +0 -25
- package/test-data/sample-project/src/resources/territory=NL/app-config.json +0 -25
- package/test-data/sample-project/src/resources/ui-terms.json +0 -96
- package/test-data/sample-project/src/resources-config.json +0 -111
- package/tsconfig.json +0 -27
- package/webpack.config.js +0 -60
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
ArchiveBoxIcon,
|
|
4
|
-
FolderOpenIcon,
|
|
5
|
-
ExclamationTriangleIcon,
|
|
6
|
-
CheckCircleIcon,
|
|
7
|
-
ArrowRightIcon
|
|
8
|
-
} from '@heroicons/react/24/outline';
|
|
9
|
-
import { Message } from '../../types/app';
|
|
10
|
-
import { UseResourceManagerReturn } from '../../hooks/useResourceManager';
|
|
11
|
-
import { BrowserZipFileTreeAccessors } from '../../utils/zip/browserZipFileTreeAccessors';
|
|
12
|
-
import { FileTree } from '@fgv/ts-utils';
|
|
13
|
-
|
|
14
|
-
interface ZipLoaderProps {
|
|
15
|
-
onMessage: (type: Message['type'], message: string) => void;
|
|
16
|
-
resourceManager: UseResourceManagerReturn;
|
|
17
|
-
zipFile?: string;
|
|
18
|
-
zipPath?: string;
|
|
19
|
-
onLoadComplete?: () => void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface ZipManifest {
|
|
23
|
-
timestamp: string;
|
|
24
|
-
input?: {
|
|
25
|
-
type: 'file' | 'directory';
|
|
26
|
-
originalPath: string;
|
|
27
|
-
archivePath: string;
|
|
28
|
-
};
|
|
29
|
-
config?: {
|
|
30
|
-
type: 'file';
|
|
31
|
-
originalPath: string;
|
|
32
|
-
archivePath: string;
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const ZipLoader: React.FC<ZipLoaderProps> = ({
|
|
37
|
-
onMessage,
|
|
38
|
-
resourceManager,
|
|
39
|
-
zipFile,
|
|
40
|
-
zipPath,
|
|
41
|
-
onLoadComplete
|
|
42
|
-
}) => {
|
|
43
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
44
|
-
const [loadingStep, setLoadingStep] = useState<string>('');
|
|
45
|
-
const [zipFound, setZipFound] = useState<boolean | null>(null);
|
|
46
|
-
const [manifest, setManifest] = useState<ZipManifest | null>(null);
|
|
47
|
-
const [autoloadAttempted, setAutoloadAttempted] = useState(false);
|
|
48
|
-
|
|
49
|
-
// Auto-load ZIP if we have a specific file reference
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if ((zipFile || zipPath) && !autoloadAttempted) {
|
|
52
|
-
setAutoloadAttempted(true);
|
|
53
|
-
handleLoadZip();
|
|
54
|
-
}
|
|
55
|
-
}, [zipFile, zipPath, autoloadAttempted]);
|
|
56
|
-
|
|
57
|
-
const handleLoadZip = async () => {
|
|
58
|
-
if (!zipFile && !zipPath) {
|
|
59
|
-
onMessage('error', 'No ZIP file specified');
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
setIsLoading(true);
|
|
64
|
-
setLoadingStep('Opening file picker...');
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
// Try to load the ZIP file using File System Access API
|
|
68
|
-
let fileHandle: FileSystemFileHandle | null = null;
|
|
69
|
-
|
|
70
|
-
if ('showOpenFilePicker' in window) {
|
|
71
|
-
try {
|
|
72
|
-
const [handle] = await (window as any).showOpenFilePicker({
|
|
73
|
-
types: [
|
|
74
|
-
{
|
|
75
|
-
description: 'ZIP Archives',
|
|
76
|
-
accept: { 'application/zip': ['.zip'] }
|
|
77
|
-
}
|
|
78
|
-
],
|
|
79
|
-
startIn: 'downloads',
|
|
80
|
-
multiple: false
|
|
81
|
-
});
|
|
82
|
-
fileHandle = handle;
|
|
83
|
-
setZipFound(true);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
if ((error as DOMException).name !== 'AbortError') {
|
|
86
|
-
onMessage('warning', 'Could not open file picker. Please select the ZIP file manually.');
|
|
87
|
-
}
|
|
88
|
-
setZipFound(false);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
onMessage('error', 'File System Access API not supported. Please use a modern browser.');
|
|
93
|
-
setZipFound(false);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (!fileHandle) {
|
|
98
|
-
setZipFound(false);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
setLoadingStep('Reading ZIP archive...');
|
|
103
|
-
const file = await fileHandle.getFile();
|
|
104
|
-
|
|
105
|
-
// Create ZIP FileTree
|
|
106
|
-
const zipAccessorsResult = await BrowserZipFileTreeAccessors.fromFile(file);
|
|
107
|
-
if (zipAccessorsResult.isFailure()) {
|
|
108
|
-
onMessage('error', `Failed to read ZIP: ${zipAccessorsResult.message}`);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const zipAccessors = zipAccessorsResult.value;
|
|
113
|
-
const fileTree = FileTree.FileTree.create(zipAccessors);
|
|
114
|
-
if (fileTree.isFailure()) {
|
|
115
|
-
onMessage('error', `Failed to create file tree: ${fileTree.message}`);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
setLoadingStep('Reading manifest...');
|
|
120
|
-
|
|
121
|
-
// Try to read manifest
|
|
122
|
-
const manifestResult = fileTree.value.getFile('/manifest.json');
|
|
123
|
-
if (manifestResult.isSuccess()) {
|
|
124
|
-
const manifestContents = manifestResult.value.getContents();
|
|
125
|
-
if (manifestContents.isSuccess()) {
|
|
126
|
-
const parsedManifest = manifestContents.value as ZipManifest;
|
|
127
|
-
setManifest(parsedManifest);
|
|
128
|
-
onMessage('info', `ZIP created: ${new Date(parsedManifest.timestamp).toLocaleString()}`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Process resources from the ZIP
|
|
133
|
-
setLoadingStep('Processing resources...');
|
|
134
|
-
|
|
135
|
-
// Look for input directory/files
|
|
136
|
-
let inputProcessed = false;
|
|
137
|
-
if (manifest?.input) {
|
|
138
|
-
const inputPath = `/${manifest.input.archivePath}`;
|
|
139
|
-
const inputResult = fileTree.value.getItem(inputPath);
|
|
140
|
-
|
|
141
|
-
if (inputResult.isSuccess()) {
|
|
142
|
-
const inputItem = inputResult.value;
|
|
143
|
-
|
|
144
|
-
if (inputItem.type === 'directory') {
|
|
145
|
-
// Process directory
|
|
146
|
-
await resourceManager.actions.processFileTree(fileTree.value, inputPath);
|
|
147
|
-
inputProcessed = true;
|
|
148
|
-
} else if (inputItem.type === 'file') {
|
|
149
|
-
// Process single file
|
|
150
|
-
await resourceManager.actions.processFileTree(fileTree.value, inputPath);
|
|
151
|
-
inputProcessed = true;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Fallback: look for common input patterns
|
|
157
|
-
if (!inputProcessed) {
|
|
158
|
-
const commonInputPaths = ['/input', '/resources', '/data'];
|
|
159
|
-
for (const inputPath of commonInputPaths) {
|
|
160
|
-
const inputResult = fileTree.value.getItem(inputPath);
|
|
161
|
-
if (inputResult.isSuccess() && inputResult.value.type === 'directory') {
|
|
162
|
-
await resourceManager.actions.processFileTree(fileTree.value, inputPath);
|
|
163
|
-
inputProcessed = true;
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Load configuration if present
|
|
170
|
-
if (manifest?.config) {
|
|
171
|
-
const configPath = `/${manifest.config.archivePath}`;
|
|
172
|
-
const configResult = fileTree.value.getFile(configPath);
|
|
173
|
-
|
|
174
|
-
if (configResult.isSuccess()) {
|
|
175
|
-
const configContents = configResult.value.getContents();
|
|
176
|
-
if (configContents.isSuccess()) {
|
|
177
|
-
resourceManager.actions.applyConfiguration(configContents.value as any);
|
|
178
|
-
onMessage('success', 'Configuration loaded from ZIP');
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (inputProcessed) {
|
|
184
|
-
onMessage('success', 'Resources loaded successfully from ZIP!');
|
|
185
|
-
onLoadComplete?.();
|
|
186
|
-
} else {
|
|
187
|
-
onMessage('warning', 'ZIP loaded but no resources found. Check ZIP contents.');
|
|
188
|
-
}
|
|
189
|
-
} catch (error) {
|
|
190
|
-
onMessage('error', `Failed to load ZIP: ${error instanceof Error ? error.message : String(error)}`);
|
|
191
|
-
setZipFound(false);
|
|
192
|
-
} finally {
|
|
193
|
-
setIsLoading(false);
|
|
194
|
-
setLoadingStep('');
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const expectedFileName = zipFile || 'ts-res-bundle-*.zip';
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<div className="p-6">
|
|
202
|
-
<div className="flex items-center space-x-3 mb-6">
|
|
203
|
-
<ArchiveBoxIcon className="h-8 w-8 text-purple-600" />
|
|
204
|
-
<h2 className="text-2xl font-bold text-gray-900">Load ZIP Archive</h2>
|
|
205
|
-
</div>
|
|
206
|
-
|
|
207
|
-
<div className="max-w-2xl">
|
|
208
|
-
{/* ZIP File Info */}
|
|
209
|
-
<div className="bg-blue-50 rounded-lg p-4 mb-6">
|
|
210
|
-
<div className="flex items-start space-x-3">
|
|
211
|
-
<ArchiveBoxIcon className="h-5 w-5 text-blue-600 mt-0.5" />
|
|
212
|
-
<div className="text-sm text-blue-800">
|
|
213
|
-
<p className="font-medium">ZIP Archive Ready</p>
|
|
214
|
-
{zipPath && (
|
|
215
|
-
<p className="mt-1">
|
|
216
|
-
<span className="font-medium">Location:</span> {zipPath}
|
|
217
|
-
</p>
|
|
218
|
-
)}
|
|
219
|
-
{zipFile && (
|
|
220
|
-
<p className="mt-1">
|
|
221
|
-
<span className="font-medium">File:</span> {zipFile}
|
|
222
|
-
</p>
|
|
223
|
-
)}
|
|
224
|
-
<p className="mt-2">
|
|
225
|
-
The CLI has created a ZIP archive containing your resources and configuration. Click below to
|
|
226
|
-
load it directly into the browser.
|
|
227
|
-
</p>
|
|
228
|
-
</div>
|
|
229
|
-
</div>
|
|
230
|
-
</div>
|
|
231
|
-
|
|
232
|
-
{/* Load Button */}
|
|
233
|
-
<div className="space-y-4">
|
|
234
|
-
<button
|
|
235
|
-
onClick={handleLoadZip}
|
|
236
|
-
disabled={isLoading}
|
|
237
|
-
className="w-full flex items-center justify-center space-x-2 px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
238
|
-
>
|
|
239
|
-
{isLoading ? (
|
|
240
|
-
<>
|
|
241
|
-
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
|
242
|
-
<span>{loadingStep || 'Loading...'}</span>
|
|
243
|
-
</>
|
|
244
|
-
) : (
|
|
245
|
-
<>
|
|
246
|
-
<FolderOpenIcon className="w-5 h-5" />
|
|
247
|
-
<span>Load ZIP Archive</span>
|
|
248
|
-
<ArrowRightIcon className="w-5 h-5" />
|
|
249
|
-
</>
|
|
250
|
-
)}
|
|
251
|
-
</button>
|
|
252
|
-
|
|
253
|
-
{/* Instructions */}
|
|
254
|
-
<div className="bg-gray-50 rounded-lg p-4">
|
|
255
|
-
<h3 className="text-sm font-medium text-gray-900 mb-2">Instructions:</h3>
|
|
256
|
-
<ol className="text-sm text-gray-600 space-y-1 list-decimal list-inside">
|
|
257
|
-
<li>Click "Load ZIP Archive" above</li>
|
|
258
|
-
<li>
|
|
259
|
-
Select the ZIP file: <code className="bg-gray-200 px-1 rounded">{expectedFileName}</code>
|
|
260
|
-
</li>
|
|
261
|
-
<li>The browser will automatically process your resources and configuration</li>
|
|
262
|
-
</ol>
|
|
263
|
-
</div>
|
|
264
|
-
|
|
265
|
-
{/* Manifest Info */}
|
|
266
|
-
{manifest && (
|
|
267
|
-
<div className="bg-green-50 rounded-lg p-4">
|
|
268
|
-
<div className="flex items-start space-x-3">
|
|
269
|
-
<CheckCircleIcon className="h-5 w-5 text-green-600 mt-0.5" />
|
|
270
|
-
<div className="text-sm text-green-800">
|
|
271
|
-
<p className="font-medium">ZIP Archive Contents</p>
|
|
272
|
-
<div className="mt-2 space-y-1">
|
|
273
|
-
{manifest.input && <p>📁 Resources: {manifest.input.originalPath}</p>}
|
|
274
|
-
{manifest.config && <p>⚙️ Configuration: {manifest.config.originalPath}</p>}
|
|
275
|
-
</div>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
</div>
|
|
279
|
-
)}
|
|
280
|
-
|
|
281
|
-
{/* Error State */}
|
|
282
|
-
{zipFound === false && (
|
|
283
|
-
<div className="bg-yellow-50 rounded-lg p-4">
|
|
284
|
-
<div className="flex items-start space-x-3">
|
|
285
|
-
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-600 mt-0.5" />
|
|
286
|
-
<div className="text-sm text-yellow-800">
|
|
287
|
-
<p className="font-medium">ZIP File Not Found</p>
|
|
288
|
-
<p className="mt-1">
|
|
289
|
-
Could not locate or open the ZIP file. Please make sure it exists in your Downloads folder
|
|
290
|
-
and try again.
|
|
291
|
-
</p>
|
|
292
|
-
</div>
|
|
293
|
-
</div>
|
|
294
|
-
</div>
|
|
295
|
-
)}
|
|
296
|
-
</div>
|
|
297
|
-
</div>
|
|
298
|
-
</div>
|
|
299
|
-
);
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
export default ZipLoader;
|
package/src/hooks/useAppState.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
import { AppState, AppActions, Tool, Message, FilterState } from '../types/app';
|
|
3
|
-
|
|
4
|
-
export const useAppState = (): { state: AppState; actions: AppActions } => {
|
|
5
|
-
const [state, setState] = useState<AppState>({
|
|
6
|
-
selectedTool: 'import',
|
|
7
|
-
messages: [],
|
|
8
|
-
filterState: {
|
|
9
|
-
enabled: false,
|
|
10
|
-
values: {},
|
|
11
|
-
appliedValues: {},
|
|
12
|
-
hasPendingChanges: false,
|
|
13
|
-
reduceQualifiers: false
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const setSelectedTool = useCallback((tool: Tool, force?: boolean) => {
|
|
18
|
-
setState((prev) => ({ ...prev, selectedTool: tool }));
|
|
19
|
-
}, []);
|
|
20
|
-
|
|
21
|
-
// Alias for setSelectedTool to match the interface
|
|
22
|
-
const setActiveTool = setSelectedTool;
|
|
23
|
-
|
|
24
|
-
const addMessage = useCallback((type: Message['type'], message: string) => {
|
|
25
|
-
const newMessage: Message = {
|
|
26
|
-
id: Date.now().toString(),
|
|
27
|
-
type,
|
|
28
|
-
message,
|
|
29
|
-
timestamp: new Date()
|
|
30
|
-
};
|
|
31
|
-
setState((prev) => ({
|
|
32
|
-
...prev,
|
|
33
|
-
messages: [...prev.messages, newMessage]
|
|
34
|
-
}));
|
|
35
|
-
}, []);
|
|
36
|
-
|
|
37
|
-
const clearMessages = useCallback(() => {
|
|
38
|
-
setState((prev) => ({ ...prev, messages: [] }));
|
|
39
|
-
}, []);
|
|
40
|
-
|
|
41
|
-
const updateFilterEnabled = useCallback((enabled: boolean) => {
|
|
42
|
-
setState((prev) => ({
|
|
43
|
-
...prev,
|
|
44
|
-
filterState: {
|
|
45
|
-
...prev.filterState,
|
|
46
|
-
enabled,
|
|
47
|
-
// Reset applied values when disabling
|
|
48
|
-
appliedValues: enabled ? prev.filterState.appliedValues : {},
|
|
49
|
-
hasPendingChanges: false
|
|
50
|
-
}
|
|
51
|
-
}));
|
|
52
|
-
}, []);
|
|
53
|
-
|
|
54
|
-
const updateFilterValues = useCallback((values: Record<string, string>) => {
|
|
55
|
-
setState((prev) => ({
|
|
56
|
-
...prev,
|
|
57
|
-
filterState: {
|
|
58
|
-
...prev.filterState,
|
|
59
|
-
values,
|
|
60
|
-
hasPendingChanges: JSON.stringify(values) !== JSON.stringify(prev.filterState.appliedValues)
|
|
61
|
-
}
|
|
62
|
-
}));
|
|
63
|
-
}, []);
|
|
64
|
-
|
|
65
|
-
const applyFilterValues = useCallback(() => {
|
|
66
|
-
setState((prev) => ({
|
|
67
|
-
...prev,
|
|
68
|
-
filterState: {
|
|
69
|
-
...prev.filterState,
|
|
70
|
-
appliedValues: { ...prev.filterState.values },
|
|
71
|
-
hasPendingChanges: false
|
|
72
|
-
}
|
|
73
|
-
}));
|
|
74
|
-
}, []);
|
|
75
|
-
|
|
76
|
-
const resetFilterValues = useCallback(() => {
|
|
77
|
-
setState((prev) => ({
|
|
78
|
-
...prev,
|
|
79
|
-
filterState: {
|
|
80
|
-
...prev.filterState,
|
|
81
|
-
values: {},
|
|
82
|
-
appliedValues: {},
|
|
83
|
-
hasPendingChanges: false
|
|
84
|
-
}
|
|
85
|
-
}));
|
|
86
|
-
}, []);
|
|
87
|
-
|
|
88
|
-
const updateReduceQualifiers = useCallback((reduceQualifiers: boolean) => {
|
|
89
|
-
setState((prev) => ({
|
|
90
|
-
...prev,
|
|
91
|
-
filterState: {
|
|
92
|
-
...prev.filterState,
|
|
93
|
-
reduceQualifiers
|
|
94
|
-
}
|
|
95
|
-
}));
|
|
96
|
-
}, []);
|
|
97
|
-
|
|
98
|
-
const actions: AppActions = {
|
|
99
|
-
setSelectedTool,
|
|
100
|
-
setActiveTool,
|
|
101
|
-
addMessage,
|
|
102
|
-
clearMessages,
|
|
103
|
-
updateFilterEnabled,
|
|
104
|
-
updateFilterValues,
|
|
105
|
-
applyFilterValues,
|
|
106
|
-
resetFilterValues,
|
|
107
|
-
updateReduceQualifiers
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
return { state, actions };
|
|
111
|
-
};
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
import { Result } from '@fgv/ts-utils';
|
|
3
|
-
import { ImportedDirectory, ImportedFile } from '../utils/fileImport';
|
|
4
|
-
|
|
5
|
-
export interface FileImportState {
|
|
6
|
-
isLoading: boolean;
|
|
7
|
-
importedDirectory: ImportedDirectory | null;
|
|
8
|
-
importedFiles: ImportedFile[];
|
|
9
|
-
error: string | null;
|
|
10
|
-
hasImportedData: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface UseFileImportReturn {
|
|
14
|
-
state: FileImportState;
|
|
15
|
-
actions: {
|
|
16
|
-
handleDirectoryImport: (directory: ImportedDirectory) => void;
|
|
17
|
-
handleFilesImport: (files: ImportedFile[]) => void;
|
|
18
|
-
handleError: (error: string) => void;
|
|
19
|
-
clearError: () => void;
|
|
20
|
-
reset: () => void;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const initialState: FileImportState = {
|
|
25
|
-
isLoading: false,
|
|
26
|
-
importedDirectory: null,
|
|
27
|
-
importedFiles: [],
|
|
28
|
-
error: null,
|
|
29
|
-
hasImportedData: false
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const useFileImport = (): UseFileImportReturn => {
|
|
33
|
-
const [state, setState] = useState<FileImportState>(initialState);
|
|
34
|
-
|
|
35
|
-
const handleDirectoryImport = useCallback((directory: ImportedDirectory) => {
|
|
36
|
-
setState((prev) => ({
|
|
37
|
-
...prev,
|
|
38
|
-
importedDirectory: directory,
|
|
39
|
-
importedFiles: [], // Clear individual files when directory is imported
|
|
40
|
-
error: null,
|
|
41
|
-
hasImportedData: true
|
|
42
|
-
}));
|
|
43
|
-
}, []);
|
|
44
|
-
|
|
45
|
-
const handleFilesImport = useCallback((files: ImportedFile[]) => {
|
|
46
|
-
setState((prev) => ({
|
|
47
|
-
...prev,
|
|
48
|
-
importedFiles: files,
|
|
49
|
-
importedDirectory: null, // Clear directory when individual files are imported
|
|
50
|
-
error: null,
|
|
51
|
-
hasImportedData: true
|
|
52
|
-
}));
|
|
53
|
-
}, []);
|
|
54
|
-
|
|
55
|
-
const handleError = useCallback((error: string) => {
|
|
56
|
-
setState((prev) => ({
|
|
57
|
-
...prev,
|
|
58
|
-
error,
|
|
59
|
-
isLoading: false
|
|
60
|
-
}));
|
|
61
|
-
}, []);
|
|
62
|
-
|
|
63
|
-
const clearError = useCallback(() => {
|
|
64
|
-
setState((prev) => ({
|
|
65
|
-
...prev,
|
|
66
|
-
error: null
|
|
67
|
-
}));
|
|
68
|
-
}, []);
|
|
69
|
-
|
|
70
|
-
const reset = useCallback(() => {
|
|
71
|
-
setState(initialState);
|
|
72
|
-
}, []);
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
state,
|
|
76
|
-
actions: {
|
|
77
|
-
handleDirectoryImport,
|
|
78
|
-
handleFilesImport,
|
|
79
|
-
handleError,
|
|
80
|
-
clearError,
|
|
81
|
-
reset
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export default useFileImport;
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
import { Tool } from '../types/app';
|
|
3
|
-
|
|
4
|
-
export interface NavigationWarningState {
|
|
5
|
-
isWarningOpen: boolean;
|
|
6
|
-
pendingTool: Tool | null;
|
|
7
|
-
hasUnsavedChanges: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface NavigationWarningActions {
|
|
11
|
-
setHasUnsavedChanges: (hasChanges: boolean) => void;
|
|
12
|
-
showWarning: (pendingTool: Tool) => void;
|
|
13
|
-
hideWarning: () => void;
|
|
14
|
-
confirmNavigation: () => Tool | null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const useNavigationWarning = (): {
|
|
18
|
-
state: NavigationWarningState;
|
|
19
|
-
actions: NavigationWarningActions;
|
|
20
|
-
} => {
|
|
21
|
-
const [state, setState] = useState<NavigationWarningState>({
|
|
22
|
-
isWarningOpen: false,
|
|
23
|
-
pendingTool: null,
|
|
24
|
-
hasUnsavedChanges: false
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const setHasUnsavedChanges = useCallback((hasChanges: boolean) => {
|
|
28
|
-
setState((prev) => ({ ...prev, hasUnsavedChanges: hasChanges }));
|
|
29
|
-
}, []);
|
|
30
|
-
|
|
31
|
-
const showWarning = useCallback((pendingTool: Tool) => {
|
|
32
|
-
setState((prev) => ({
|
|
33
|
-
...prev,
|
|
34
|
-
isWarningOpen: true,
|
|
35
|
-
pendingTool
|
|
36
|
-
}));
|
|
37
|
-
}, []);
|
|
38
|
-
|
|
39
|
-
const hideWarning = useCallback(() => {
|
|
40
|
-
setState((prev) => ({
|
|
41
|
-
...prev,
|
|
42
|
-
isWarningOpen: false,
|
|
43
|
-
pendingTool: null
|
|
44
|
-
}));
|
|
45
|
-
}, []);
|
|
46
|
-
|
|
47
|
-
const confirmNavigation = useCallback(() => {
|
|
48
|
-
const toolToNavigateTo = state.pendingTool;
|
|
49
|
-
setState((prev) => ({
|
|
50
|
-
...prev,
|
|
51
|
-
isWarningOpen: false,
|
|
52
|
-
pendingTool: null,
|
|
53
|
-
hasUnsavedChanges: false
|
|
54
|
-
}));
|
|
55
|
-
return toolToNavigateTo;
|
|
56
|
-
}, [state.pendingTool]);
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
state,
|
|
60
|
-
actions: {
|
|
61
|
-
setHasUnsavedChanges,
|
|
62
|
-
showWarning,
|
|
63
|
-
hideWarning,
|
|
64
|
-
confirmNavigation
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
};
|