@fgv/ts-res-browser 1.0.0

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 (95) hide show
  1. package/.rush/temp/5fc90bc7c2ccf812114ea099111568e5429be2a2.tar.log +15 -0
  2. package/.rush/temp/chunked-rush-logs/ts-res-browser.build.chunks.jsonl +35 -0
  3. package/.rush/temp/operation/build/all.log +35 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +35 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +1217 -0
  7. package/README.md +95 -0
  8. package/TS_RES_BROWSER_PLAN.md +689 -0
  9. package/babel.config.js +17 -0
  10. package/config/jest.config.json +13 -0
  11. package/config/rig.json +16 -0
  12. package/dist/bundle.js +2 -0
  13. package/dist/bundle.js.LICENSE.txt +62 -0
  14. package/dist/index.html +1 -0
  15. package/package.json +78 -0
  16. package/postcss.config.js +3 -0
  17. package/public/index.html +15 -0
  18. package/rush-logs/ts-res-browser.build.cache.log +3 -0
  19. package/rush-logs/ts-res-browser.build.log +35 -0
  20. package/src/App.tsx +153 -0
  21. package/src/cli.ts +44 -0
  22. package/src/components/common/FileImporter.tsx +138 -0
  23. package/src/components/common/NavigationWarningModal.tsx +62 -0
  24. package/src/components/layout/AppLayout.tsx +46 -0
  25. package/src/components/layout/Header.tsx +22 -0
  26. package/src/components/layout/MessagesWindow.tsx +108 -0
  27. package/src/components/layout/Sidebar.tsx +92 -0
  28. package/src/components/tools/CompiledBrowser.tsx +949 -0
  29. package/src/components/tools/ConfigurationTool.tsx +1402 -0
  30. package/src/components/tools/FilterTool.tsx +1027 -0
  31. package/src/components/tools/ImportTool.tsx +246 -0
  32. package/src/components/tools/ResolutionViewer.tsx +1672 -0
  33. package/src/components/tools/SourceBrowser.tsx +496 -0
  34. package/src/hooks/useAppState.ts +107 -0
  35. package/src/hooks/useFileImport.ts +86 -0
  36. package/src/hooks/useNavigationWarning.ts +67 -0
  37. package/src/hooks/useResourceManager.ts +244 -0
  38. package/src/index.css +38 -0
  39. package/src/main.tsx +16 -0
  40. package/src/types/app.ts +62 -0
  41. package/src/types/fileSystemAccess.d.ts +94 -0
  42. package/src/utils/browserFileTreeAccessors.ts +245 -0
  43. package/src/utils/fileImport.ts +334 -0
  44. package/src/utils/filterResources.ts +677 -0
  45. package/src/utils/tsResIntegration.ts +467 -0
  46. package/tailwind.config.js +21 -0
  47. package/test-data/README.md +184 -0
  48. package/test-data/config-variations/README.md +64 -0
  49. package/test-data/config-variations/composition-terminology.json +112 -0
  50. package/test-data/config-variations/enterprise-complex.json +129 -0
  51. package/test-data/config-variations/gaming-app.json +87 -0
  52. package/test-data/config-variations/high-priority-language.json +65 -0
  53. package/test-data/config-variations/minimal-basic.json +37 -0
  54. package/test-data/config-variations/qualifier-default-values.json +71 -0
  55. package/test-data/config-variations/territory-first.json +68 -0
  56. package/test-data/config-variations/territory-hierarchy-test.json +31 -0
  57. package/test-data/config-variations/territory-hierarchy-unconstrained.json +32 -0
  58. package/test-data/default-matching-test/greeting.res.json +83 -0
  59. package/test-data/default-matching-test/only-defaults.res.json +48 -0
  60. package/test-data/sample-project/package.json +27 -0
  61. package/test-data/sample-project/src/resources/app-config.json +27 -0
  62. package/test-data/sample-project/src/resources/app.json +177 -0
  63. package/test-data/sample-project/src/resources/config/app.env=development.json +21 -0
  64. package/test-data/sample-project/src/resources/config/app.json +21 -0
  65. package/test-data/sample-project/src/resources/config/app.platform=mobile.json +23 -0
  66. package/test-data/sample-project/src/resources/feature-flags.json +23 -0
  67. package/test-data/sample-project/src/resources/home=CA/common.json +16 -0
  68. package/test-data/sample-project/src/resources/images/density=hdpi.json +29 -0
  69. package/test-data/sample-project/src/resources/images/home.json +29 -0
  70. package/test-data/sample-project/src/resources/images/logout.json +14 -0
  71. package/test-data/sample-project/src/resources/images/settings.json +14 -0
  72. package/test-data/sample-project/src/resources/images/ui-icons.json +29 -0
  73. package/test-data/sample-project/src/resources/images/user.density=hdpi.json +14 -0
  74. package/test-data/sample-project/src/resources/images/user.json +14 -0
  75. package/test-data/sample-project/src/resources/lang=en-US/common.json +15 -0
  76. package/test-data/sample-project/src/resources/language=en-CA/ui-terms.json +19 -0
  77. package/test-data/sample-project/src/resources/language=en-GB/ui-terms.json +30 -0
  78. package/test-data/sample-project/src/resources/language=fr-CA/ui-terms.json +25 -0
  79. package/test-data/sample-project/src/resources/language=nl-BE/ui-terms.json +17 -0
  80. package/test-data/sample-project/src/resources/region=europe/feature-flags.json +17 -0
  81. package/test-data/sample-project/src/resources/region=north-america/feature-flags.json +18 -0
  82. package/test-data/sample-project/src/resources/strings/dashboard.home=CA,language=fr.json +20 -0
  83. package/test-data/sample-project/src/resources/strings/dashboard.json +18 -0
  84. package/test-data/sample-project/src/resources/strings/dashboard.role=admin.json +23 -0
  85. package/test-data/sample-project/src/resources/strings/dashboard.role=user.json +21 -0
  86. package/test-data/sample-project/src/resources/strings/language=en-US.json +16 -0
  87. package/test-data/sample-project/src/resources/strings.json +78 -0
  88. package/test-data/sample-project/src/resources/territory=BE/app-config.json +19 -0
  89. package/test-data/sample-project/src/resources/territory=CA/app-config.json +21 -0
  90. package/test-data/sample-project/src/resources/territory=GB/app-config.json +25 -0
  91. package/test-data/sample-project/src/resources/territory=NL/app-config.json +25 -0
  92. package/test-data/sample-project/src/resources/ui-terms.json +96 -0
  93. package/test-data/sample-project/src/resources-config.json +111 -0
  94. package/tsconfig.json +27 -0
  95. package/webpack.config.js +54 -0
@@ -0,0 +1,62 @@
1
+ /* @license
2
+ Papa Parse
3
+ v5.4.1
4
+ https://github.com/mholt/PapaParse
5
+ License: MIT
6
+ */
7
+
8
+ /*!
9
+ * mustache.js - Logic-less {{mustache}} templates with JavaScript
10
+ * http://github.com/janl/mustache.js
11
+ */
12
+
13
+ /*!
14
+ * The buffer module from node.js, for the browser.
15
+ *
16
+ * @author Feross Aboukhadijeh <https://feross.org>
17
+ * @license MIT
18
+ */
19
+
20
+ /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
21
+
22
+ /*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
23
+
24
+ /**
25
+ * @license React
26
+ * react-dom-client.production.js
27
+ *
28
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
29
+ *
30
+ * This source code is licensed under the MIT license found in the
31
+ * LICENSE file in the root directory of this source tree.
32
+ */
33
+
34
+ /**
35
+ * @license React
36
+ * react-dom.production.js
37
+ *
38
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
39
+ *
40
+ * This source code is licensed under the MIT license found in the
41
+ * LICENSE file in the root directory of this source tree.
42
+ */
43
+
44
+ /**
45
+ * @license React
46
+ * react.production.js
47
+ *
48
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
49
+ *
50
+ * This source code is licensed under the MIT license found in the
51
+ * LICENSE file in the root directory of this source tree.
52
+ */
53
+
54
+ /**
55
+ * @license React
56
+ * scheduler.production.js
57
+ *
58
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
59
+ *
60
+ * This source code is licensed under the MIT license found in the
61
+ * LICENSE file in the root directory of this source tree.
62
+ */
@@ -0,0 +1 @@
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Visual browser tool for ts-res resources"/><title>TS-RES Browser</title><script defer="defer" src="bundle.js"></script></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@fgv/ts-res-browser",
3
+ "version": "1.0.0",
4
+ "description": "Visual browser tool for ts-res resources",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "bin": {
8
+ "ts-res-browser": "./lib/cli.js"
9
+ },
10
+ "keywords": [
11
+ "ts-res",
12
+ "browser",
13
+ "tool",
14
+ "resource",
15
+ "visualization"
16
+ ],
17
+ "author": "Erik Fortune",
18
+ "license": "MIT",
19
+ "engines": {
20
+ "node": ">=20.0.0"
21
+ },
22
+ "dependencies": {
23
+ "react": "~19.1.0",
24
+ "react-dom": "~19.1.0",
25
+ "@heroicons/react": "~2.2.0",
26
+ "core-js": "~3.44.0",
27
+ "whatwg-fetch": "~3.6.20",
28
+ "@rushstack/heft-web-rig": "~0.29.0",
29
+ "@fgv/ts-res": "5.0.0-0",
30
+ "@fgv/ts-utils": "5.0.0-0",
31
+ "@fgv/ts-json": "5.0.0-0",
32
+ "@fgv/ts-json-base": "5.0.0-0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "~19.1.8",
36
+ "@types/react-dom": "~19.1.6",
37
+ "@types/node": "^20.14.9",
38
+ "typescript": "^5.7.3",
39
+ "webpack": "~5.100.1",
40
+ "webpack-cli": "~6.0.1",
41
+ "webpack-dev-server": "~5.2.2",
42
+ "@babel/core": "~7.28.0",
43
+ "@babel/preset-env": "~7.28.0",
44
+ "@babel/preset-react": "~7.27.1",
45
+ "@babel/preset-typescript": "~7.27.1",
46
+ "babel-loader": "~10.0.0",
47
+ "css-loader": "~7.1.2",
48
+ "style-loader": "~4.0.0",
49
+ "postcss-loader": "~8.1.1",
50
+ "postcss": "~8.5.6",
51
+ "autoprefixer": "~10.4.21",
52
+ "tailwindcss": "~4.1.11",
53
+ "html-webpack-plugin": "~5.6.3",
54
+ "jest": "^29.7.0",
55
+ "rimraf": "^5.0.7",
56
+ "@tailwindcss/postcss": "~4.1.11",
57
+ "path-browserify": "~1.0.1",
58
+ "crypto-browserify": "~3.12.1",
59
+ "stream-browserify": "~3.0.0",
60
+ "util": "~0.12.5",
61
+ "buffer": "~6.0.3",
62
+ "process": "~0.11.10",
63
+ "@rushstack/heft-web-rig": "~0.29.0",
64
+ "@rushstack/heft": "~0.74.0",
65
+ "@rushstack/heft-api-extractor-plugin": "~0.4.8",
66
+ "@rushstack/heft-jest-plugin": "~0.16.9",
67
+ "@rushstack/heft-lint-plugin": "~0.7.0",
68
+ "@rushstack/heft-sass-plugin": "~0.17.8",
69
+ "@rushstack/heft-webpack5-plugin": "~0.11.34",
70
+ "@fgv/ts-utils-jest": "5.0.0-0"
71
+ },
72
+ "scripts": {
73
+ "build": "webpack --mode production",
74
+ "dev": "webpack serve --mode development",
75
+ "clean": "rimraf lib dist",
76
+ "test": "jest"
77
+ }
78
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ plugins: [require('@tailwindcss/postcss'), require('autoprefixer')]
3
+ };
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="/favicon.ico" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta name="theme-color" content="#000000" />
8
+ <meta name="description" content="Visual browser tool for ts-res resources" />
9
+ <title>TS-RES Browser</title>
10
+ </head>
11
+ <body>
12
+ <noscript>You need to enable JavaScript to run this app.</noscript>
13
+ <div id="root"></div>
14
+ </body>
15
+ </html>
@@ -0,0 +1,3 @@
1
+ Caching build output folders: dist, .rush/temp/operation/build
2
+ Successfully set cache entry.
3
+ Cache key: 5fc90bc7c2ccf812114ea099111568e5429be2a2
@@ -0,0 +1,35 @@
1
+ Invoking: webpack --mode production
2
+ asset bundle.js 2.31 MiB [emitted] [minimized] [big] (name: main) 1 related asset
3
+ asset index.html 478 bytes [emitted]
4
+ orphan modules 636 KiB [orphan] 348 modules
5
+ runtime modules 1.02 KiB 6 modules
6
+ modules by path ../../common/temp/node_modules/.pnpm/ 2.31 MiB 846 modules
7
+ modules by path ../../libraries/ 2.23 MiB
8
+ javascript modules 1.4 MiB 312 modules
9
+ json modules 849 KiB 4 modules
10
+ optional modules 60 bytes [optional]
11
+ buffer (ignored) 15 bytes [optional] [built] [code generated]
12
+ crypto (ignored) 15 bytes [optional] [built] [code generated]
13
+ buffer (ignored) 15 bytes [optional] [built] [code generated]
14
+ vm (ignored) 15 bytes [optional] [built] [code generated]
15
+ modules by path ./src/ 362 KiB
16
+ ./src/main.tsx + 47 modules 327 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.6 KiB [built] [code generated]
18
+ + 14 modules
19
+
20
+ WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
21
+ This can impact web performance.
22
+ Assets:
23
+ bundle.js (2.31 MiB)
24
+
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
+ Entrypoints:
27
+ main (2.31 MiB)
28
+ bundle.js
29
+
30
+
31
+ WARNING in webpack performance recommendations:
32
+ You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
33
+ For more info visit https://webpack.js.org/guides/code-splitting/
34
+
35
+ webpack 5.100.1 compiled with 3 warnings in 19176 ms
package/src/App.tsx ADDED
@@ -0,0 +1,153 @@
1
+ import React, { useCallback } from 'react';
2
+ import AppLayout from './components/layout/AppLayout';
3
+ import ImportTool from './components/tools/ImportTool';
4
+ import SourceBrowser from './components/tools/SourceBrowser';
5
+ import FilterTool from './components/tools/FilterTool';
6
+ import CompiledBrowser from './components/tools/CompiledBrowser';
7
+ import ResolutionViewer from './components/tools/ResolutionViewer';
8
+ import ConfigurationTool from './components/tools/ConfigurationTool';
9
+ import NavigationWarningModal from './components/common/NavigationWarningModal';
10
+ import { useAppState } from './hooks/useAppState';
11
+ import { useFileImport } from './hooks/useFileImport';
12
+ import { useResourceManager } from './hooks/useResourceManager';
13
+ import { useNavigationWarning } from './hooks/useNavigationWarning';
14
+ import { FilterResult } from './utils/filterResources';
15
+ import { Tool } from './types/app';
16
+
17
+ const App: React.FC = () => {
18
+ const { state, actions } = useAppState();
19
+ const fileImport = useFileImport();
20
+ const resourceManager = useResourceManager();
21
+ const navigationWarning = useNavigationWarning();
22
+
23
+ // State to share filter result between components
24
+ const [filterResult, setFilterResult] = React.useState<FilterResult | null>(null);
25
+
26
+ // Ref to store the configuration tool's save handler
27
+ const configSaveHandlerRef = React.useRef<(() => void) | null>(null);
28
+
29
+ // Handle navigation with unsaved changes check
30
+ const handleToolSelect = useCallback(
31
+ (tool: Tool) => {
32
+ // If we're trying to navigate away from configuration and have unsaved changes
33
+ if (state.selectedTool === 'configuration' && navigationWarning.state.hasUnsavedChanges) {
34
+ navigationWarning.actions.showWarning(tool);
35
+ return;
36
+ }
37
+
38
+ // Otherwise, navigate normally
39
+ actions.setSelectedTool(tool);
40
+ },
41
+ [state.selectedTool, navigationWarning, actions]
42
+ );
43
+
44
+ // Handle save and continue from navigation warning
45
+ const handleSaveAndContinue = useCallback(() => {
46
+ // Call the configuration tool's save handler if available
47
+ if (configSaveHandlerRef.current) {
48
+ configSaveHandlerRef.current();
49
+ }
50
+
51
+ const pendingTool = navigationWarning.actions.confirmNavigation();
52
+ if (pendingTool) {
53
+ actions.setSelectedTool(pendingTool);
54
+ }
55
+ }, [navigationWarning, actions]);
56
+
57
+ // Handle discard changes and continue
58
+ const handleDiscardAndContinue = useCallback(() => {
59
+ const pendingTool = navigationWarning.actions.confirmNavigation();
60
+ if (pendingTool) {
61
+ actions.setSelectedTool(pendingTool);
62
+ }
63
+ }, [navigationWarning, actions]);
64
+
65
+ const renderTool = () => {
66
+ switch (state.selectedTool) {
67
+ case 'import':
68
+ return (
69
+ <ImportTool
70
+ onMessage={actions.addMessage}
71
+ fileImport={fileImport}
72
+ resourceManager={resourceManager}
73
+ />
74
+ );
75
+ case 'source':
76
+ return <SourceBrowser onMessage={actions.addMessage} resourceManager={resourceManager} />;
77
+ case 'filter':
78
+ return (
79
+ <FilterTool
80
+ onMessage={actions.addMessage}
81
+ resourceManager={resourceManager}
82
+ filterState={state.filterState}
83
+ filterActions={{
84
+ updateFilterEnabled: actions.updateFilterEnabled,
85
+ updateFilterValues: actions.updateFilterValues,
86
+ applyFilterValues: actions.applyFilterValues,
87
+ resetFilterValues: actions.resetFilterValues,
88
+ updateReduceQualifiers: actions.updateReduceQualifiers
89
+ }}
90
+ onFilterResult={setFilterResult}
91
+ />
92
+ );
93
+ case 'compiled':
94
+ return (
95
+ <CompiledBrowser
96
+ onMessage={actions.addMessage}
97
+ resourceManager={resourceManager}
98
+ filterState={state.filterState}
99
+ filterResult={filterResult}
100
+ />
101
+ );
102
+ case 'resolution':
103
+ return (
104
+ <ResolutionViewer
105
+ onMessage={actions.addMessage}
106
+ resourceManager={resourceManager}
107
+ filterState={state.filterState}
108
+ filterResult={filterResult}
109
+ />
110
+ );
111
+ case 'configuration':
112
+ return (
113
+ <ConfigurationTool
114
+ onMessage={actions.addMessage}
115
+ resourceManager={resourceManager}
116
+ onUnsavedChanges={navigationWarning.actions.setHasUnsavedChanges}
117
+ onSaveHandlerRef={configSaveHandlerRef}
118
+ />
119
+ );
120
+ default:
121
+ return (
122
+ <ImportTool
123
+ onMessage={actions.addMessage}
124
+ fileImport={fileImport}
125
+ resourceManager={resourceManager}
126
+ />
127
+ );
128
+ }
129
+ };
130
+
131
+ return (
132
+ <>
133
+ <AppLayout
134
+ selectedTool={state.selectedTool}
135
+ onToolSelect={handleToolSelect}
136
+ messages={state.messages}
137
+ onClearMessages={actions.clearMessages}
138
+ >
139
+ {renderTool()}
140
+ </AppLayout>
141
+
142
+ <NavigationWarningModal
143
+ isOpen={navigationWarning.state.isWarningOpen}
144
+ onCancel={navigationWarning.actions.hideWarning}
145
+ onConfirm={handleDiscardAndContinue}
146
+ onSave={handleSaveAndContinue}
147
+ hasUnsavedChanges={navigationWarning.state.hasUnsavedChanges}
148
+ />
149
+ </>
150
+ );
151
+ };
152
+
153
+ export default App;
package/src/cli.ts ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { exec } from 'child_process';
4
+ import { join } from 'path';
5
+
6
+ const command = process.argv[2];
7
+
8
+ switch (command) {
9
+ case 'dev':
10
+ console.log('Starting TS-RES Browser development server...');
11
+ exec('npm run dev', { cwd: __dirname }, (error, stdout, stderr) => {
12
+ if (error) {
13
+ console.error(`Error: ${error}`);
14
+ return;
15
+ }
16
+ console.log(stdout);
17
+ if (stderr) console.error(stderr);
18
+ });
19
+ break;
20
+
21
+ case 'build':
22
+ console.log('Building TS-RES Browser...');
23
+ exec('npm run build', { cwd: __dirname }, (error, stdout, stderr) => {
24
+ if (error) {
25
+ console.error(`Error: ${error}`);
26
+ return;
27
+ }
28
+ console.log(stdout);
29
+ if (stderr) console.error(stderr);
30
+ });
31
+ break;
32
+
33
+ default:
34
+ console.log('TS-RES Browser CLI');
35
+ console.log('');
36
+ console.log('Usage:');
37
+ console.log(' ts-res-browser dev - Start development server');
38
+ console.log(' ts-res-browser build - Build for production');
39
+ console.log('');
40
+ console.log('For development, you can also use:');
41
+ console.log(' rushx dev - Start development server');
42
+ console.log(' rushx build - Build for production');
43
+ break;
44
+ }
@@ -0,0 +1,138 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { FolderOpenIcon, DocumentArrowUpIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline';
3
+ import {
4
+ fileImporter,
5
+ isFileSystemAccessSupported,
6
+ ImportedDirectory,
7
+ ImportedFile
8
+ } from '../../utils/fileImport';
9
+ import { Result } from '@fgv/ts-utils';
10
+
11
+ interface FileImporterProps {
12
+ onDirectoryImported?: (directory: ImportedDirectory) => void;
13
+ onFilesImported?: (files: ImportedFile[]) => void;
14
+ onError?: (error: string) => void;
15
+ }
16
+
17
+ export const FileImporter: React.FC<FileImporterProps> = ({
18
+ onDirectoryImported,
19
+ onFilesImported,
20
+ onError
21
+ }) => {
22
+ const [isLoading, setIsLoading] = useState(false);
23
+ const [importMethod, setImportMethod] = useState<'modern' | 'fallback'>(
24
+ isFileSystemAccessSupported() ? 'modern' : 'fallback'
25
+ );
26
+
27
+ const handleDirectoryImport = useCallback(async () => {
28
+ setIsLoading(true);
29
+ try {
30
+ const result = await fileImporter.pickDirectory();
31
+
32
+ if (result.isSuccess()) {
33
+ onDirectoryImported?.(result.value);
34
+ } else {
35
+ onError?.(result.error);
36
+ }
37
+ } catch (error) {
38
+ onError?.(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
39
+ } finally {
40
+ setIsLoading(false);
41
+ }
42
+ }, [onDirectoryImported, onError]);
43
+
44
+ const handleFileImport = useCallback(async () => {
45
+ setIsLoading(true);
46
+ try {
47
+ const result = await fileImporter.pickFiles({
48
+ acceptedTypes: ['.json'],
49
+ multiple: true
50
+ });
51
+
52
+ if (result.isSuccess()) {
53
+ onFilesImported?.(result.value);
54
+ } else {
55
+ onError?.(result.error);
56
+ }
57
+ } catch (error) {
58
+ onError?.(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
59
+ } finally {
60
+ setIsLoading(false);
61
+ }
62
+ }, [onFilesImported, onError]);
63
+
64
+ return (
65
+ <div className="space-y-4">
66
+ {/* Import Method Info */}
67
+ <div className="flex items-center space-x-2 text-sm text-gray-600">
68
+ {importMethod === 'modern' ? (
69
+ <>
70
+ <div className="w-2 h-2 bg-green-500 rounded-full"></div>
71
+ <span>Using modern File System Access API</span>
72
+ </>
73
+ ) : (
74
+ <>
75
+ <div className="w-2 h-2 bg-yellow-500 rounded-full"></div>
76
+ <span>Using fallback file input (limited browser support)</span>
77
+ </>
78
+ )}
79
+ </div>
80
+
81
+ {/* Import Controls */}
82
+ <div className="flex flex-col space-y-3">
83
+ {/* Directory Import */}
84
+ <button
85
+ onClick={handleDirectoryImport}
86
+ disabled={isLoading}
87
+ className="flex items-center justify-center space-x-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
88
+ >
89
+ <FolderOpenIcon className="w-5 h-5" />
90
+ <span>{isLoading ? 'Importing...' : 'Import Resource Directory'}</span>
91
+ </button>
92
+
93
+ {/* File Import */}
94
+ <button
95
+ onClick={handleFileImport}
96
+ disabled={isLoading}
97
+ className="flex items-center justify-center space-x-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
98
+ >
99
+ <DocumentArrowUpIcon className="w-5 h-5" />
100
+ <span>{isLoading ? 'Importing...' : 'Import Resource Files'}</span>
101
+ </button>
102
+ </div>
103
+
104
+ {/* Browser Compatibility Warning */}
105
+ {importMethod === 'fallback' && (
106
+ <div className="flex items-start space-x-2 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
107
+ <ExclamationTriangleIcon className="w-5 h-5 text-yellow-600 mt-0.5 flex-shrink-0" />
108
+ <div className="text-sm text-yellow-800">
109
+ <p className="font-medium">Limited Browser Support</p>
110
+ <p className="mt-1">
111
+ Your browser doesn't support the File System Access API. For the best experience, use Chrome,
112
+ Edge, or another Chromium-based browser.
113
+ </p>
114
+ </div>
115
+ </div>
116
+ )}
117
+
118
+ {/* Usage Instructions */}
119
+ <div className="text-sm text-gray-600 space-y-2">
120
+ <p className="font-medium">Import Options:</p>
121
+ <ul className="list-disc list-inside space-y-1 ml-2">
122
+ <li>
123
+ <strong>Import Directory:</strong> Select a folder containing ts-res resources
124
+ </li>
125
+ <li>
126
+ <strong>Import Files:</strong> Select individual JSON resource files
127
+ </li>
128
+ </ul>
129
+ <p className="mt-2">
130
+ The importer will automatically detect and parse ts-res resource formats including Resource
131
+ Collections, Resource Trees, and simple JSON files.
132
+ </p>
133
+ </div>
134
+ </div>
135
+ );
136
+ };
137
+
138
+ export default FileImporter;
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
3
+
4
+ interface NavigationWarningModalProps {
5
+ isOpen: boolean;
6
+ onCancel: () => void;
7
+ onConfirm: () => void;
8
+ onSave?: () => void;
9
+ hasUnsavedChanges: boolean;
10
+ }
11
+
12
+ const NavigationWarningModal: React.FC<NavigationWarningModalProps> = ({
13
+ isOpen,
14
+ onCancel,
15
+ onConfirm,
16
+ onSave,
17
+ hasUnsavedChanges
18
+ }) => {
19
+ if (!isOpen) return null;
20
+
21
+ return (
22
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
23
+ <div className="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4">
24
+ <div className="flex items-center space-x-3 mb-4">
25
+ <ExclamationTriangleIcon className="h-6 w-6 text-amber-500" />
26
+ <h3 className="text-lg font-medium text-gray-900">Unsaved Changes</h3>
27
+ </div>
28
+
29
+ <p className="text-sm text-gray-600 mb-6">
30
+ You have unsaved configuration changes. What would you like to do?
31
+ </p>
32
+
33
+ <div className="flex justify-end space-x-3">
34
+ <button
35
+ onClick={onCancel}
36
+ className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
37
+ >
38
+ Cancel
39
+ </button>
40
+
41
+ <button
42
+ onClick={onConfirm}
43
+ className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
44
+ >
45
+ Discard Changes
46
+ </button>
47
+
48
+ {onSave && (
49
+ <button
50
+ onClick={onSave}
51
+ className="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
52
+ >
53
+ Save & Continue
54
+ </button>
55
+ )}
56
+ </div>
57
+ </div>
58
+ </div>
59
+ );
60
+ };
61
+
62
+ export default NavigationWarningModal;
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import Header from './Header';
3
+ import Sidebar from './Sidebar';
4
+ import MessagesWindow from './MessagesWindow';
5
+ import { Tool } from '../../types/app';
6
+
7
+ interface AppLayoutProps {
8
+ children: React.ReactNode;
9
+ selectedTool: Tool;
10
+ onToolSelect: (tool: Tool) => void;
11
+ messages: Array<{
12
+ id: string;
13
+ type: 'info' | 'warning' | 'error' | 'success';
14
+ message: string;
15
+ timestamp: Date;
16
+ }>;
17
+ onClearMessages: () => void;
18
+ }
19
+
20
+ const AppLayout: React.FC<AppLayoutProps> = ({
21
+ children,
22
+ selectedTool,
23
+ onToolSelect,
24
+ messages,
25
+ onClearMessages
26
+ }) => {
27
+ return (
28
+ <div className="min-h-screen bg-gray-50">
29
+ <Header />
30
+
31
+ <div className="flex">
32
+ <Sidebar selectedTool={selectedTool} onToolSelect={onToolSelect} />
33
+
34
+ <main className="flex-1 overflow-hidden">
35
+ <div className="h-full flex flex-col">
36
+ <div className="flex-1 overflow-auto">{children}</div>
37
+
38
+ <MessagesWindow messages={messages} onClearMessages={onClearMessages} />
39
+ </div>
40
+ </main>
41
+ </div>
42
+ </div>
43
+ );
44
+ };
45
+
46
+ export default AppLayout;
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { DocumentMagnifyingGlassIcon } from '@heroicons/react/24/outline';
3
+
4
+ const Header: React.FC = () => {
5
+ return (
6
+ <header className="bg-white shadow-sm border-b border-gray-200">
7
+ <div className="container mx-auto px-4 py-4">
8
+ <div className="flex items-center space-x-3">
9
+ <DocumentMagnifyingGlassIcon className="h-8 w-8 text-blue-600" />
10
+ <div>
11
+ <h1 className="text-2xl font-bold text-gray-900">TS-RES Browser</h1>
12
+ <p className="text-sm text-gray-600">
13
+ Visual tool for loading, browsing, and experimenting with ts-res resources
14
+ </p>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </header>
19
+ );
20
+ };
21
+
22
+ export default Header;