@fgv/ts-res-browser 1.0.0 → 5.0.0-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.rush/temp/{5fc90bc7c2ccf812114ea099111568e5429be2a2.tar.log → 587277bfc395406ad444e989b6c309b9698f534c.tar.log} +2 -2
- package/.rush/temp/chunked-rush-logs/ts-res-browser.build.chunks.jsonl +12 -12
- package/.rush/temp/operation/build/all.log +12 -12
- package/.rush/temp/operation/build/log-chunks.jsonl +12 -12
- package/.rush/temp/operation/build/state.json +1 -1
- package/.rush/temp/shrinkwrap-deps.json +11 -3
- package/README.md +85 -15
- package/cli.js +45 -0
- package/dist/bundle.js +1 -1
- package/package.json +12 -7
- package/rush-logs/ts-res-browser.build.cache.log +1 -1
- package/rush-logs/ts-res-browser.build.log +12 -12
- package/src/App.tsx +122 -1
- package/src/components/common/FileImporter.tsx +10 -5
- package/src/components/layout/Sidebar.tsx +8 -1
- package/src/components/tools/ConfigurationTool.tsx +4 -1
- package/src/components/tools/FilterTool.tsx +106 -19
- package/src/components/tools/ImportTool.tsx +3 -0
- package/src/components/tools/ZipLoader.tsx +302 -0
- package/src/hooks/useAppState.ts +4 -0
- package/src/hooks/useResourceManager.ts +52 -1
- package/src/hooks/useUrlParams.ts +56 -0
- package/src/types/app.ts +9 -1
- package/src/utils/fileImport.ts +7 -5
- package/src/utils/fileTreeConverter.ts +160 -0
- package/src/utils/urlParams.ts +238 -0
- package/src/utils/zip/browserZipFileTreeAccessors.ts +385 -0
- package/webpack.config.js +7 -1
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fgv/ts-res-browser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-2",
|
|
4
4
|
"description": "Visual browser tool for ts-res resources",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"ts-res-browser": "./
|
|
8
|
+
"ts-res-browser": "./cli.js"
|
|
9
9
|
},
|
|
10
10
|
"keywords": [
|
|
11
11
|
"ts-res",
|
|
@@ -26,10 +26,14 @@
|
|
|
26
26
|
"core-js": "~3.44.0",
|
|
27
27
|
"whatwg-fetch": "~3.6.20",
|
|
28
28
|
"@rushstack/heft-web-rig": "~0.29.0",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"@fgv/ts-
|
|
29
|
+
"jszip": "~3.10.1",
|
|
30
|
+
"browserify-zlib": "~0.2.0",
|
|
31
|
+
"assert": "~2.1.0",
|
|
32
|
+
"@fgv/ts-res": "5.0.0-2",
|
|
33
|
+
"@fgv/ts-json-base": "5.0.0-2",
|
|
34
|
+
"@fgv/ts-utils": "5.0.0-2",
|
|
35
|
+
"@fgv/ts-extras": "5.0.0-2",
|
|
36
|
+
"@fgv/ts-json": "5.0.0-2"
|
|
33
37
|
},
|
|
34
38
|
"devDependencies": {
|
|
35
39
|
"@types/react": "~19.1.8",
|
|
@@ -67,7 +71,8 @@
|
|
|
67
71
|
"@rushstack/heft-lint-plugin": "~0.7.0",
|
|
68
72
|
"@rushstack/heft-sass-plugin": "~0.17.8",
|
|
69
73
|
"@rushstack/heft-webpack5-plugin": "~0.11.34",
|
|
70
|
-
"@
|
|
74
|
+
"@types/jszip": "~3.4.1",
|
|
75
|
+
"@fgv/ts-utils-jest": "5.0.0-2"
|
|
71
76
|
},
|
|
72
77
|
"scripts": {
|
|
73
78
|
"build": "webpack --mode production",
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
Invoking: webpack --mode production
|
|
2
|
-
asset bundle.js 2.
|
|
2
|
+
asset bundle.js 2.41 MiB [emitted] [minimized] [big] (name: main) 1 related asset
|
|
3
3
|
asset index.html 478 bytes [emitted]
|
|
4
|
-
orphan modules
|
|
4
|
+
orphan modules 678 KiB [orphan] 353 modules
|
|
5
5
|
runtime modules 1.02 KiB 6 modules
|
|
6
|
-
modules by path ../../common/temp/node_modules/.pnpm/ 2.
|
|
7
|
-
modules by path ../../libraries/ 2.
|
|
8
|
-
javascript modules 1.
|
|
6
|
+
modules by path ../../common/temp/node_modules/.pnpm/ 2.38 MiB 841 modules
|
|
7
|
+
modules by path ../../libraries/ 2.24 MiB
|
|
8
|
+
javascript modules 1.41 MiB 314 modules
|
|
9
9
|
json modules 849 KiB 4 modules
|
|
10
10
|
optional modules 60 bytes [optional]
|
|
11
11
|
buffer (ignored) 15 bytes [optional] [built] [code generated]
|
|
12
12
|
crypto (ignored) 15 bytes [optional] [built] [code generated]
|
|
13
13
|
buffer (ignored) 15 bytes [optional] [built] [code generated]
|
|
14
14
|
vm (ignored) 15 bytes [optional] [built] [code generated]
|
|
15
|
-
modules by path ./src/
|
|
16
|
-
./src/main.tsx +
|
|
17
|
-
../../common/temp/node_modules/.pnpm/css-loader@7.1.2_webpack@5.100.1/node_modules/css-loader/dist/cjs.js!../../common/temp/node_modules/.pnpm/postcss-loader@8.1.1_postcss@8.5.6_typescript@5.8.3_webpack@5.100.1/node_modules/postcss-loader/dist/cjs.js!./src/index.css 35.
|
|
18
|
-
+
|
|
15
|
+
modules by path ./src/ 406 KiB
|
|
16
|
+
./src/main.tsx + 54 modules 370 KiB [built] [code generated]
|
|
17
|
+
../../common/temp/node_modules/.pnpm/css-loader@7.1.2_webpack@5.100.1/node_modules/css-loader/dist/cjs.js!../../common/temp/node_modules/.pnpm/postcss-loader@8.1.1_postcss@8.5.6_typescript@5.8.3_webpack@5.100.1/node_modules/postcss-loader/dist/cjs.js!./src/index.css 35.7 KiB [built] [code generated]
|
|
18
|
+
+ 15 modules
|
|
19
19
|
|
|
20
20
|
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
|
|
21
21
|
This can impact web performance.
|
|
22
22
|
Assets:
|
|
23
|
-
bundle.js (2.
|
|
23
|
+
bundle.js (2.41 MiB)
|
|
24
24
|
|
|
25
25
|
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
|
|
26
26
|
Entrypoints:
|
|
27
|
-
main (2.
|
|
27
|
+
main (2.41 MiB)
|
|
28
28
|
bundle.js
|
|
29
29
|
|
|
30
30
|
|
|
@@ -32,4 +32,4 @@ WARNING in webpack performance recommendations:
|
|
|
32
32
|
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
|
|
33
33
|
For more info visit https://webpack.js.org/guides/code-splitting/
|
|
34
34
|
|
|
35
|
-
webpack 5.100.1 compiled with 3 warnings in
|
|
35
|
+
webpack 5.100.1 compiled with 3 warnings in 21598 ms
|
package/src/App.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect } from 'react';
|
|
2
2
|
import AppLayout from './components/layout/AppLayout';
|
|
3
3
|
import ImportTool from './components/tools/ImportTool';
|
|
4
4
|
import SourceBrowser from './components/tools/SourceBrowser';
|
|
@@ -6,19 +6,24 @@ import FilterTool from './components/tools/FilterTool';
|
|
|
6
6
|
import CompiledBrowser from './components/tools/CompiledBrowser';
|
|
7
7
|
import ResolutionViewer from './components/tools/ResolutionViewer';
|
|
8
8
|
import ConfigurationTool from './components/tools/ConfigurationTool';
|
|
9
|
+
import ZipLoader from './components/tools/ZipLoader';
|
|
9
10
|
import NavigationWarningModal from './components/common/NavigationWarningModal';
|
|
10
11
|
import { useAppState } from './hooks/useAppState';
|
|
11
12
|
import { useFileImport } from './hooks/useFileImport';
|
|
12
13
|
import { useResourceManager } from './hooks/useResourceManager';
|
|
13
14
|
import { useNavigationWarning } from './hooks/useNavigationWarning';
|
|
15
|
+
import { useUrlParams } from './hooks/useUrlParams';
|
|
14
16
|
import { FilterResult } from './utils/filterResources';
|
|
17
|
+
import { parseContextFilter } from './utils/urlParams';
|
|
15
18
|
import { Tool } from './types/app';
|
|
19
|
+
import * as TsRes from '@fgv/ts-res';
|
|
16
20
|
|
|
17
21
|
const App: React.FC = () => {
|
|
18
22
|
const { state, actions } = useAppState();
|
|
19
23
|
const fileImport = useFileImport();
|
|
20
24
|
const resourceManager = useResourceManager();
|
|
21
25
|
const navigationWarning = useNavigationWarning();
|
|
26
|
+
const { urlParams, hasUrlParams } = useUrlParams();
|
|
22
27
|
|
|
23
28
|
// State to share filter result between components
|
|
24
29
|
const [filterResult, setFilterResult] = React.useState<FilterResult | null>(null);
|
|
@@ -26,6 +31,109 @@ const App: React.FC = () => {
|
|
|
26
31
|
// Ref to store the configuration tool's save handler
|
|
27
32
|
const configSaveHandlerRef = React.useRef<(() => void) | null>(null);
|
|
28
33
|
|
|
34
|
+
// Ref to track if we've already initialized from URL parameters
|
|
35
|
+
const initializedFromUrlRef = React.useRef(false);
|
|
36
|
+
|
|
37
|
+
// Initialize app state from URL parameters (only run once)
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (hasUrlParams && !initializedFromUrlRef.current) {
|
|
40
|
+
initializedFromUrlRef.current = true;
|
|
41
|
+
|
|
42
|
+
// Load and apply configuration if provided
|
|
43
|
+
if (urlParams.config) {
|
|
44
|
+
const loadConfiguration = async () => {
|
|
45
|
+
try {
|
|
46
|
+
// Check if it's a predefined configuration name
|
|
47
|
+
const predefinedNames: TsRes.Config.PredefinedSystemConfiguration[] = [
|
|
48
|
+
'default',
|
|
49
|
+
'language-priority',
|
|
50
|
+
'territory-priority',
|
|
51
|
+
'extended-example'
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
if (predefinedNames.includes(urlParams.config as TsRes.Config.PredefinedSystemConfiguration)) {
|
|
55
|
+
// Load predefined configuration
|
|
56
|
+
const configResult = TsRes.Config.getPredefinedDeclaration(
|
|
57
|
+
urlParams.config as TsRes.Config.PredefinedSystemConfiguration
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (configResult.isSuccess()) {
|
|
61
|
+
// Store the declaration directly, not the SystemConfiguration instance
|
|
62
|
+
resourceManager.actions.applyConfiguration(configResult.value);
|
|
63
|
+
actions.addMessage('success', `Loaded predefined configuration: ${urlParams.config}`);
|
|
64
|
+
} else {
|
|
65
|
+
actions.addMessage(
|
|
66
|
+
'error',
|
|
67
|
+
`Failed to load predefined configuration '${urlParams.config}': ${configResult.message}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// It might be a file path - show a message for now
|
|
72
|
+
actions.addMessage(
|
|
73
|
+
'info',
|
|
74
|
+
`Configuration file specified: ${urlParams.config} (file loading not yet supported)`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
actions.addMessage('error', `Error loading configuration: ${error}`);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
loadConfiguration();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Apply context filter if provided
|
|
86
|
+
if (urlParams.contextFilter) {
|
|
87
|
+
try {
|
|
88
|
+
const contextObj = parseContextFilter(urlParams.contextFilter);
|
|
89
|
+
actions.updateFilterValues(contextObj);
|
|
90
|
+
actions.updateFilterEnabled(true);
|
|
91
|
+
actions.applyFilterValues();
|
|
92
|
+
actions.addMessage('info', `Applied context filter from URL: ${urlParams.contextFilter}`);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
actions.addMessage('error', `Invalid context filter in URL: ${urlParams.contextFilter}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Apply reduceQualifiers setting
|
|
99
|
+
if (urlParams.reduceQualifiers) {
|
|
100
|
+
actions.updateReduceQualifiers(true);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Handle ZIP loading
|
|
104
|
+
if (urlParams.loadZip) {
|
|
105
|
+
actions.setActiveTool('zip-loader');
|
|
106
|
+
actions.addMessage('info', 'ZIP archive ready to load');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Show appropriate messages for other parameters
|
|
110
|
+
if (urlParams.input) {
|
|
111
|
+
actions.addMessage('info', `Ready to load resource file: ${urlParams.input}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (urlParams.resourceTypes) {
|
|
115
|
+
actions.addMessage('info', `Resource types filter: ${urlParams.resourceTypes}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (urlParams.maxDistance !== undefined) {
|
|
119
|
+
actions.addMessage('info', `Language matching distance: ${urlParams.maxDistance}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (urlParams.interactive) {
|
|
123
|
+
actions.addMessage('info', 'Launched in interactive mode - sample data will be loaded');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Navigate to appropriate tool based on URL params
|
|
127
|
+
if (urlParams.input) {
|
|
128
|
+
// If input is specified, start with import tool
|
|
129
|
+
actions.setSelectedTool('import');
|
|
130
|
+
} else if (urlParams.interactive) {
|
|
131
|
+
// If interactive mode, might want to go straight to source browser with sample data
|
|
132
|
+
actions.setSelectedTool('source');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}, [hasUrlParams]); // Remove urlParams and actions to prevent re-runs
|
|
136
|
+
|
|
29
137
|
// Handle navigation with unsaved changes check
|
|
30
138
|
const handleToolSelect = useCallback(
|
|
31
139
|
(tool: Tool) => {
|
|
@@ -117,6 +225,19 @@ const App: React.FC = () => {
|
|
|
117
225
|
onSaveHandlerRef={configSaveHandlerRef}
|
|
118
226
|
/>
|
|
119
227
|
);
|
|
228
|
+
case 'zip-loader':
|
|
229
|
+
return (
|
|
230
|
+
<ZipLoader
|
|
231
|
+
onMessage={actions.addMessage}
|
|
232
|
+
resourceManager={resourceManager}
|
|
233
|
+
zipFile={urlParams.zipFile}
|
|
234
|
+
zipPath={urlParams.zipPath}
|
|
235
|
+
onLoadComplete={() => {
|
|
236
|
+
// Switch to source browser after successful load
|
|
237
|
+
actions.setSelectedTool('source');
|
|
238
|
+
}}
|
|
239
|
+
/>
|
|
240
|
+
);
|
|
120
241
|
default:
|
|
121
242
|
return (
|
|
122
243
|
<ImportTool
|
|
@@ -12,12 +12,14 @@ interface FileImporterProps {
|
|
|
12
12
|
onDirectoryImported?: (directory: ImportedDirectory) => void;
|
|
13
13
|
onFilesImported?: (files: ImportedFile[]) => void;
|
|
14
14
|
onError?: (error: string) => void;
|
|
15
|
+
startInDirectory?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export const FileImporter: React.FC<FileImporterProps> = ({
|
|
18
19
|
onDirectoryImported,
|
|
19
20
|
onFilesImported,
|
|
20
|
-
onError
|
|
21
|
+
onError,
|
|
22
|
+
startInDirectory
|
|
21
23
|
}) => {
|
|
22
24
|
const [isLoading, setIsLoading] = useState(false);
|
|
23
25
|
const [importMethod, setImportMethod] = useState<'modern' | 'fallback'>(
|
|
@@ -27,7 +29,9 @@ export const FileImporter: React.FC<FileImporterProps> = ({
|
|
|
27
29
|
const handleDirectoryImport = useCallback(async () => {
|
|
28
30
|
setIsLoading(true);
|
|
29
31
|
try {
|
|
30
|
-
const result = await fileImporter.pickDirectory(
|
|
32
|
+
const result = await fileImporter.pickDirectory({
|
|
33
|
+
startIn: startInDirectory
|
|
34
|
+
});
|
|
31
35
|
|
|
32
36
|
if (result.isSuccess()) {
|
|
33
37
|
onDirectoryImported?.(result.value);
|
|
@@ -39,14 +43,15 @@ export const FileImporter: React.FC<FileImporterProps> = ({
|
|
|
39
43
|
} finally {
|
|
40
44
|
setIsLoading(false);
|
|
41
45
|
}
|
|
42
|
-
}, [onDirectoryImported, onError]);
|
|
46
|
+
}, [onDirectoryImported, onError, startInDirectory]);
|
|
43
47
|
|
|
44
48
|
const handleFileImport = useCallback(async () => {
|
|
45
49
|
setIsLoading(true);
|
|
46
50
|
try {
|
|
47
51
|
const result = await fileImporter.pickFiles({
|
|
48
52
|
acceptedTypes: ['.json'],
|
|
49
|
-
multiple: true
|
|
53
|
+
multiple: true,
|
|
54
|
+
startIn: startInDirectory
|
|
50
55
|
});
|
|
51
56
|
|
|
52
57
|
if (result.isSuccess()) {
|
|
@@ -59,7 +64,7 @@ export const FileImporter: React.FC<FileImporterProps> = ({
|
|
|
59
64
|
} finally {
|
|
60
65
|
setIsLoading(false);
|
|
61
66
|
}
|
|
62
|
-
}, [onFilesImported, onError]);
|
|
67
|
+
}, [onFilesImported, onError, startInDirectory]);
|
|
63
68
|
|
|
64
69
|
return (
|
|
65
70
|
<div className="space-y-4">
|
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
FunnelIcon,
|
|
6
6
|
CubeIcon,
|
|
7
7
|
MagnifyingGlassIcon,
|
|
8
|
-
CogIcon
|
|
8
|
+
CogIcon,
|
|
9
|
+
ArchiveBoxIcon
|
|
9
10
|
} from '@heroicons/react/24/outline';
|
|
10
11
|
import { Tool } from '../../types/app';
|
|
11
12
|
|
|
@@ -50,6 +51,12 @@ const tools = [
|
|
|
50
51
|
name: 'Configuration',
|
|
51
52
|
icon: CogIcon,
|
|
52
53
|
description: 'Manage qualifier types, qualifiers, and resource types'
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'zip-loader',
|
|
57
|
+
name: 'ZIP Loader',
|
|
58
|
+
icon: ArchiveBoxIcon,
|
|
59
|
+
description: 'Load resources from ZIP archive created by CLI'
|
|
53
60
|
}
|
|
54
61
|
];
|
|
55
62
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from '../../utils/tsResIntegration';
|
|
22
22
|
import { fileImporter } from '../../utils/fileImport';
|
|
23
23
|
import { Result } from '@fgv/ts-utils';
|
|
24
|
+
import { useUrlParams } from '../../hooks/useUrlParams';
|
|
24
25
|
|
|
25
26
|
interface ConfigurationToolProps {
|
|
26
27
|
onMessage?: (type: Message['type'], message: string) => void;
|
|
@@ -38,6 +39,7 @@ const ConfigurationTool: React.FC<ConfigurationToolProps> = ({
|
|
|
38
39
|
onSaveHandlerRef
|
|
39
40
|
}) => {
|
|
40
41
|
const { state: resourceState } = resourceManager;
|
|
42
|
+
const { urlParams } = useUrlParams();
|
|
41
43
|
const [activePanel, setActivePanel] = useState<ActivePanel>('qualifiers');
|
|
42
44
|
|
|
43
45
|
// Check if we have processed resources
|
|
@@ -84,7 +86,8 @@ const ConfigurationTool: React.FC<ConfigurationToolProps> = ({
|
|
|
84
86
|
try {
|
|
85
87
|
const fileResult = await fileImporter.pickFiles({
|
|
86
88
|
acceptedTypes: ['.json'],
|
|
87
|
-
multiple: false
|
|
89
|
+
multiple: false,
|
|
90
|
+
startIn: urlParams.configStartDir
|
|
88
91
|
});
|
|
89
92
|
|
|
90
93
|
if (fileResult.isFailure()) {
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from '@heroicons/react/24/outline';
|
|
14
14
|
import { UseResourceManagerReturn } from '../../hooks/useResourceManager';
|
|
15
15
|
import { Message, FilterState } from '../../types/app';
|
|
16
|
-
import { NoMatch } from '@fgv/ts-res';
|
|
16
|
+
import { NoMatch, Config } from '@fgv/ts-res';
|
|
17
17
|
import { DEFAULT_SYSTEM_CONFIGURATION } from '../../utils/tsResIntegration';
|
|
18
18
|
import {
|
|
19
19
|
createFilteredResourceManager,
|
|
@@ -65,6 +65,64 @@ const FilterTool: React.FC<FilterToolProps> = ({
|
|
|
65
65
|
return config.qualifiers.map((q) => q.name);
|
|
66
66
|
}, [resourceState.processedResources?.compiledCollection.qualifiers, resourceState.activeConfiguration]);
|
|
67
67
|
|
|
68
|
+
// Get qualifier type information for dropdown decisions
|
|
69
|
+
const qualifierTypeInfo = useMemo(() => {
|
|
70
|
+
const info: Record<
|
|
71
|
+
string,
|
|
72
|
+
{
|
|
73
|
+
type: Config.Model.ISystemConfiguration['qualifierTypes'][0];
|
|
74
|
+
enumeratedValues?: string[];
|
|
75
|
+
allowedTerritories?: string[];
|
|
76
|
+
}
|
|
77
|
+
> = {};
|
|
78
|
+
|
|
79
|
+
// Get the active configuration to access qualifier types
|
|
80
|
+
const config = resourceState.activeConfiguration || DEFAULT_SYSTEM_CONFIGURATION;
|
|
81
|
+
|
|
82
|
+
// Create a map of qualifier name to qualifier type for quick lookup
|
|
83
|
+
const qualifierTypeMap: Record<string, Config.Model.ISystemConfiguration['qualifierTypes'][0]> = {};
|
|
84
|
+
|
|
85
|
+
// First, create a mapping of qualifiers to their types
|
|
86
|
+
config.qualifiers.forEach((qualifier) => {
|
|
87
|
+
const qualifierType = config.qualifierTypes.find((qt) => qt.name === qualifier.typeName);
|
|
88
|
+
if (qualifierType) {
|
|
89
|
+
qualifierTypeMap[qualifier.name] = qualifierType;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Build info for each qualifier
|
|
94
|
+
availableQualifiers.forEach((qualifierName) => {
|
|
95
|
+
const qualifierType = qualifierTypeMap[qualifierName];
|
|
96
|
+
if (qualifierType) {
|
|
97
|
+
info[qualifierName] = { type: qualifierType };
|
|
98
|
+
|
|
99
|
+
// Extract enumerated values for literal types
|
|
100
|
+
if (
|
|
101
|
+
qualifierType.systemType === 'literal' &&
|
|
102
|
+
qualifierType.configuration &&
|
|
103
|
+
typeof qualifierType.configuration === 'object' &&
|
|
104
|
+
'enumeratedValues' in qualifierType.configuration &&
|
|
105
|
+
Array.isArray(qualifierType.configuration.enumeratedValues)
|
|
106
|
+
) {
|
|
107
|
+
info[qualifierName].enumeratedValues = qualifierType.configuration.enumeratedValues;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Extract allowed territories for territory types
|
|
111
|
+
if (
|
|
112
|
+
qualifierType.systemType === 'territory' &&
|
|
113
|
+
qualifierType.configuration &&
|
|
114
|
+
typeof qualifierType.configuration === 'object' &&
|
|
115
|
+
'allowedTerritories' in qualifierType.configuration &&
|
|
116
|
+
Array.isArray(qualifierType.configuration.allowedTerritories)
|
|
117
|
+
) {
|
|
118
|
+
info[qualifierName].allowedTerritories = qualifierType.configuration.allowedTerritories;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return info;
|
|
124
|
+
}, [availableQualifiers, resourceState.activeConfiguration]);
|
|
125
|
+
|
|
68
126
|
// Check if we have any applied filter values set
|
|
69
127
|
const hasAppliedFilterValues = useMemo(() => {
|
|
70
128
|
return hasFilterValues(filterState.appliedValues);
|
|
@@ -504,25 +562,54 @@ const FilterTool: React.FC<FilterToolProps> = ({
|
|
|
504
562
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Context Filters</h3>
|
|
505
563
|
<div className="bg-gray-50 rounded-lg p-4">
|
|
506
564
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
507
|
-
{availableQualifiers.map((qualifierName) =>
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
className=
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
565
|
+
{availableQualifiers.map((qualifierName) => {
|
|
566
|
+
const typeInfo = qualifierTypeInfo[qualifierName];
|
|
567
|
+
const hasConstrainedValues =
|
|
568
|
+
(typeInfo?.enumeratedValues && typeInfo.enumeratedValues.length > 0) ||
|
|
569
|
+
(typeInfo?.allowedTerritories && typeInfo.allowedTerritories.length > 0);
|
|
570
|
+
|
|
571
|
+
const constrainedValues = typeInfo?.enumeratedValues || typeInfo?.allowedTerritories || [];
|
|
572
|
+
|
|
573
|
+
return (
|
|
574
|
+
<div key={qualifierName} className="bg-white rounded border border-gray-200 p-2">
|
|
575
|
+
<div className="flex items-center gap-2">
|
|
576
|
+
<label className="text-sm font-medium text-gray-700 min-w-0 flex-shrink-0">
|
|
577
|
+
{qualifierName}:
|
|
578
|
+
</label>
|
|
579
|
+
{hasConstrainedValues ? (
|
|
580
|
+
<select
|
|
581
|
+
value={filterState.values[qualifierName] || ''}
|
|
582
|
+
onChange={(e) => handleFilterChange(qualifierName, e.target.value)}
|
|
583
|
+
disabled={!filterState.enabled}
|
|
584
|
+
className={`flex-1 px-2 py-1 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-transparent text-sm min-w-0 ${
|
|
585
|
+
!filterState.enabled ? 'bg-gray-100 text-gray-400' : 'bg-white'
|
|
586
|
+
}`}
|
|
587
|
+
>
|
|
588
|
+
<option value="">
|
|
589
|
+
{filterState.enabled ? `All ${qualifierName} values` : 'Disabled'}
|
|
590
|
+
</option>
|
|
591
|
+
{constrainedValues.map((value) => (
|
|
592
|
+
<option key={value} value={value}>
|
|
593
|
+
{value}
|
|
594
|
+
</option>
|
|
595
|
+
))}
|
|
596
|
+
</select>
|
|
597
|
+
) : (
|
|
598
|
+
<input
|
|
599
|
+
type="text"
|
|
600
|
+
value={filterState.values[qualifierName] || ''}
|
|
601
|
+
onChange={(e) => handleFilterChange(qualifierName, e.target.value)}
|
|
602
|
+
disabled={!filterState.enabled}
|
|
603
|
+
className={`flex-1 px-2 py-1 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-transparent text-sm min-w-0 ${
|
|
604
|
+
!filterState.enabled ? 'bg-gray-100 text-gray-400' : ''
|
|
605
|
+
}`}
|
|
606
|
+
placeholder={filterState.enabled ? `Filter by ${qualifierName}` : 'Disabled'}
|
|
607
|
+
/>
|
|
608
|
+
)}
|
|
609
|
+
</div>
|
|
523
610
|
</div>
|
|
524
|
-
|
|
525
|
-
)
|
|
611
|
+
);
|
|
612
|
+
})}
|
|
526
613
|
</div>
|
|
527
614
|
{filterState.enabled && (
|
|
528
615
|
<div className="mt-3 text-sm text-gray-600">
|
|
@@ -4,6 +4,7 @@ import { Message } from '../../types/app';
|
|
|
4
4
|
import { UseFileImportReturn } from '../../hooks/useFileImport';
|
|
5
5
|
import { UseResourceManagerReturn } from '../../hooks/useResourceManager';
|
|
6
6
|
import FileImporter from '../common/FileImporter';
|
|
7
|
+
import { useUrlParams } from '../../hooks/useUrlParams';
|
|
7
8
|
|
|
8
9
|
interface ImportToolProps {
|
|
9
10
|
onMessage: (type: Message['type'], message: string) => void;
|
|
@@ -14,6 +15,7 @@ interface ImportToolProps {
|
|
|
14
15
|
const ImportTool: React.FC<ImportToolProps> = ({ onMessage, fileImport, resourceManager }) => {
|
|
15
16
|
const { state: fileImportState, actions: fileImportActions } = fileImport;
|
|
16
17
|
const { state: resourceState, actions: resourceActions } = resourceManager;
|
|
18
|
+
const { urlParams } = useUrlParams();
|
|
17
19
|
|
|
18
20
|
// Track if we've already processed the current import to prevent infinite loops
|
|
19
21
|
const lastProcessedImport = useRef<{
|
|
@@ -114,6 +116,7 @@ const ImportTool: React.FC<ImportToolProps> = ({ onMessage, fileImport, resource
|
|
|
114
116
|
onDirectoryImported={fileImportActions.handleDirectoryImport}
|
|
115
117
|
onFilesImported={fileImportActions.handleFilesImport}
|
|
116
118
|
onError={fileImportActions.handleError}
|
|
119
|
+
startInDirectory={urlParams.inputStartDir}
|
|
117
120
|
/>
|
|
118
121
|
</div>
|
|
119
122
|
|