@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.
- package/.rush/temp/5fc90bc7c2ccf812114ea099111568e5429be2a2.tar.log +15 -0
- package/.rush/temp/chunked-rush-logs/ts-res-browser.build.chunks.jsonl +35 -0
- package/.rush/temp/operation/build/all.log +35 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +35 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +1217 -0
- package/README.md +95 -0
- package/TS_RES_BROWSER_PLAN.md +689 -0
- package/babel.config.js +17 -0
- package/config/jest.config.json +13 -0
- package/config/rig.json +16 -0
- package/dist/bundle.js +2 -0
- package/dist/bundle.js.LICENSE.txt +62 -0
- package/dist/index.html +1 -0
- package/package.json +78 -0
- package/postcss.config.js +3 -0
- package/public/index.html +15 -0
- package/rush-logs/ts-res-browser.build.cache.log +3 -0
- package/rush-logs/ts-res-browser.build.log +35 -0
- package/src/App.tsx +153 -0
- package/src/cli.ts +44 -0
- package/src/components/common/FileImporter.tsx +138 -0
- package/src/components/common/NavigationWarningModal.tsx +62 -0
- package/src/components/layout/AppLayout.tsx +46 -0
- package/src/components/layout/Header.tsx +22 -0
- package/src/components/layout/MessagesWindow.tsx +108 -0
- package/src/components/layout/Sidebar.tsx +92 -0
- package/src/components/tools/CompiledBrowser.tsx +949 -0
- package/src/components/tools/ConfigurationTool.tsx +1402 -0
- package/src/components/tools/FilterTool.tsx +1027 -0
- package/src/components/tools/ImportTool.tsx +246 -0
- package/src/components/tools/ResolutionViewer.tsx +1672 -0
- package/src/components/tools/SourceBrowser.tsx +496 -0
- package/src/hooks/useAppState.ts +107 -0
- package/src/hooks/useFileImport.ts +86 -0
- package/src/hooks/useNavigationWarning.ts +67 -0
- package/src/hooks/useResourceManager.ts +244 -0
- package/src/index.css +38 -0
- package/src/main.tsx +16 -0
- package/src/types/app.ts +62 -0
- package/src/types/fileSystemAccess.d.ts +94 -0
- package/src/utils/browserFileTreeAccessors.ts +245 -0
- package/src/utils/fileImport.ts +334 -0
- package/src/utils/filterResources.ts +677 -0
- package/src/utils/tsResIntegration.ts +467 -0
- package/tailwind.config.js +21 -0
- package/test-data/README.md +184 -0
- package/test-data/config-variations/README.md +64 -0
- package/test-data/config-variations/composition-terminology.json +112 -0
- package/test-data/config-variations/enterprise-complex.json +129 -0
- package/test-data/config-variations/gaming-app.json +87 -0
- package/test-data/config-variations/high-priority-language.json +65 -0
- package/test-data/config-variations/minimal-basic.json +37 -0
- package/test-data/config-variations/qualifier-default-values.json +71 -0
- package/test-data/config-variations/territory-first.json +68 -0
- package/test-data/config-variations/territory-hierarchy-test.json +31 -0
- package/test-data/config-variations/territory-hierarchy-unconstrained.json +32 -0
- package/test-data/default-matching-test/greeting.res.json +83 -0
- package/test-data/default-matching-test/only-defaults.res.json +48 -0
- package/test-data/sample-project/package.json +27 -0
- package/test-data/sample-project/src/resources/app-config.json +27 -0
- package/test-data/sample-project/src/resources/app.json +177 -0
- package/test-data/sample-project/src/resources/config/app.env=development.json +21 -0
- package/test-data/sample-project/src/resources/config/app.json +21 -0
- package/test-data/sample-project/src/resources/config/app.platform=mobile.json +23 -0
- package/test-data/sample-project/src/resources/feature-flags.json +23 -0
- package/test-data/sample-project/src/resources/home=CA/common.json +16 -0
- package/test-data/sample-project/src/resources/images/density=hdpi.json +29 -0
- package/test-data/sample-project/src/resources/images/home.json +29 -0
- package/test-data/sample-project/src/resources/images/logout.json +14 -0
- package/test-data/sample-project/src/resources/images/settings.json +14 -0
- package/test-data/sample-project/src/resources/images/ui-icons.json +29 -0
- package/test-data/sample-project/src/resources/images/user.density=hdpi.json +14 -0
- package/test-data/sample-project/src/resources/images/user.json +14 -0
- package/test-data/sample-project/src/resources/lang=en-US/common.json +15 -0
- package/test-data/sample-project/src/resources/language=en-CA/ui-terms.json +19 -0
- package/test-data/sample-project/src/resources/language=en-GB/ui-terms.json +30 -0
- package/test-data/sample-project/src/resources/language=fr-CA/ui-terms.json +25 -0
- package/test-data/sample-project/src/resources/language=nl-BE/ui-terms.json +17 -0
- package/test-data/sample-project/src/resources/region=europe/feature-flags.json +17 -0
- package/test-data/sample-project/src/resources/region=north-america/feature-flags.json +18 -0
- package/test-data/sample-project/src/resources/strings/dashboard.home=CA,language=fr.json +20 -0
- package/test-data/sample-project/src/resources/strings/dashboard.json +18 -0
- package/test-data/sample-project/src/resources/strings/dashboard.role=admin.json +23 -0
- package/test-data/sample-project/src/resources/strings/dashboard.role=user.json +21 -0
- package/test-data/sample-project/src/resources/strings/language=en-US.json +16 -0
- package/test-data/sample-project/src/resources/strings.json +78 -0
- package/test-data/sample-project/src/resources/territory=BE/app-config.json +19 -0
- package/test-data/sample-project/src/resources/territory=CA/app-config.json +21 -0
- package/test-data/sample-project/src/resources/territory=GB/app-config.json +25 -0
- package/test-data/sample-project/src/resources/territory=NL/app-config.json +25 -0
- package/test-data/sample-project/src/resources/ui-terms.json +96 -0
- package/test-data/sample-project/src/resources-config.json +111 -0
- package/tsconfig.json +27 -0
- 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
|
+
*/
|
package/dist/index.html
ADDED
|
@@ -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,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,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;
|