react-scanner-ui 0.0.7 ā 0.0.9
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/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +17 -20
- package/dist/server/index.js.map +1 -1
- package/dist/utils/config.js +5 -5
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/scannerConfig.d.ts +3 -7
- package/dist/utils/scannerConfig.d.ts.map +1 -1
- package/dist/utils/scannerConfig.js +25 -50
- package/dist/utils/scannerConfig.js.map +1 -1
- package/package.json +1 -2
- package/ui/components/App.css +28 -0
- package/ui/components/App.tsx +52 -0
- package/ui/components/ComponentTable.css +149 -0
- package/ui/components/ComponentTable.tsx +140 -0
- package/ui/index.css +59 -0
- package/ui/index.html +10 -10
- package/ui/main.tsx +10 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAqBA;;;GAGG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgE7D"}
|
package/dist/server/index.js
CHANGED
|
@@ -32,14 +32,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
36
|
exports.startServer = startServer;
|
|
40
37
|
const http_1 = require("http");
|
|
41
38
|
const path_1 = require("path");
|
|
42
|
-
const polka_1 = __importDefault(require("polka"));
|
|
43
39
|
const scannerConfig_1 = require("../utils/scannerConfig");
|
|
44
40
|
/**
|
|
45
41
|
* Get the path to the UI directory.
|
|
@@ -57,14 +53,6 @@ function getUiRoot() {
|
|
|
57
53
|
* (similar approach to Storybook's builder-vite)
|
|
58
54
|
*/
|
|
59
55
|
async function startServer(port) {
|
|
60
|
-
const server = (0, http_1.createServer)();
|
|
61
|
-
const app = (0, polka_1.default)({ server });
|
|
62
|
-
// API endpoint to get scan data (served before Vite middleware)
|
|
63
|
-
app.get("/api/scan-data", (_req, res) => {
|
|
64
|
-
res.setHeader("Content-Type", "application/json");
|
|
65
|
-
const result = (0, scannerConfig_1.getScanData)();
|
|
66
|
-
res.end(JSON.stringify(result));
|
|
67
|
-
});
|
|
68
56
|
// Dynamically import Vite to create dev server in middleware mode
|
|
69
57
|
const { createServer: createViteServer } = await Promise.resolve().then(() => __importStar(require("vite")));
|
|
70
58
|
const uiRoot = getUiRoot();
|
|
@@ -73,14 +61,23 @@ async function startServer(port) {
|
|
|
73
61
|
configFile: (0, path_1.resolve)(uiRoot, "vite.config.ts"),
|
|
74
62
|
server: {
|
|
75
63
|
middlewareMode: true,
|
|
76
|
-
hmr: {
|
|
77
|
-
server,
|
|
78
|
-
},
|
|
79
64
|
},
|
|
80
65
|
appType: "spa",
|
|
81
66
|
});
|
|
82
|
-
//
|
|
83
|
-
|
|
67
|
+
// Create HTTP server with manual routing
|
|
68
|
+
const server = (0, http_1.createServer)((req, res) => {
|
|
69
|
+
const url = req.url || "";
|
|
70
|
+
// Handle API routes FIRST - before Vite middleware
|
|
71
|
+
if (url === "/api/scan-data" || url.startsWith("/api/scan-data?")) {
|
|
72
|
+
res.setHeader("Content-Type", "application/json");
|
|
73
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
74
|
+
const result = (0, scannerConfig_1.getScanData)();
|
|
75
|
+
res.end(JSON.stringify(result));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Pass everything else to Vite
|
|
79
|
+
vite.middlewares(req, res);
|
|
80
|
+
});
|
|
84
81
|
// Handle server errors
|
|
85
82
|
server.on("error", (err) => {
|
|
86
83
|
console.error("Server error:", err);
|
|
@@ -96,13 +93,13 @@ async function startServer(port) {
|
|
|
96
93
|
};
|
|
97
94
|
process.on("SIGINT", shutdown);
|
|
98
95
|
process.on("SIGTERM", shutdown);
|
|
99
|
-
return new Promise((
|
|
96
|
+
return new Promise((resolvePromise, reject) => {
|
|
100
97
|
server.on("error", reject);
|
|
101
|
-
|
|
98
|
+
server.listen(port, "127.0.0.1", () => {
|
|
102
99
|
console.log(`\nš React Scanner UI is running at http://localhost:${port}\n`);
|
|
103
100
|
console.log(" ā Hot Module Replacement enabled");
|
|
104
101
|
console.log(" ā Press Ctrl+C to stop the server.\n");
|
|
105
|
-
|
|
102
|
+
resolvePromise();
|
|
106
103
|
});
|
|
107
104
|
});
|
|
108
105
|
}
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/config.js
CHANGED
|
@@ -4,9 +4,9 @@ exports.createReactScannerConfig = createReactScannerConfig;
|
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const path_1 = require("path");
|
|
6
6
|
function createReactScannerConfig() {
|
|
7
|
-
const configPath = (0, path_1.join)(process.cwd(),
|
|
7
|
+
const configPath = (0, path_1.join)(process.cwd(), "react-scanner.config.js");
|
|
8
8
|
if ((0, fs_1.existsSync)(configPath)) {
|
|
9
|
-
console.log(
|
|
9
|
+
console.log("react-scanner.config.js already exists.");
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
const configContent = `module.exports = {
|
|
@@ -14,16 +14,16 @@ function createReactScannerConfig() {
|
|
|
14
14
|
includeSubComponents: true,
|
|
15
15
|
importedFrom: 'PLACEHOLDER_FOR_IMPORTED_FROM',
|
|
16
16
|
processors: [
|
|
17
|
-
['
|
|
17
|
+
['count-components-and-props', { outputTo: './.react-scanner-ui/scan-report.json' }],
|
|
18
18
|
],
|
|
19
19
|
};
|
|
20
20
|
`;
|
|
21
21
|
try {
|
|
22
22
|
(0, fs_1.writeFileSync)(configPath, configContent);
|
|
23
|
-
console.log(
|
|
23
|
+
console.log("Created react-scanner.config.js");
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
|
-
console.error(
|
|
26
|
+
console.error("Failed to create react-scanner.config.js", error);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
//# sourceMappingURL=config.js.map
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export { isReactScannerInstalled, checkPeerDependency, promptInstallReactScanner
|
|
|
2
2
|
export { createReactScannerConfig } from "./config";
|
|
3
3
|
export { getServerPort, isPortAvailable } from "./port";
|
|
4
4
|
export type { PortOptions } from "./port";
|
|
5
|
-
export { readScannerConfig,
|
|
5
|
+
export { readScannerConfig, readScanData, getScanData } from "./scannerConfig";
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getScanData = exports.readScanData = exports.
|
|
3
|
+
exports.getScanData = exports.readScanData = exports.readScannerConfig = exports.isPortAvailable = exports.getServerPort = exports.createReactScannerConfig = exports.installReactScanner = exports.promptInstallReactScanner = exports.checkPeerDependency = exports.isReactScannerInstalled = void 0;
|
|
4
4
|
var dependencies_1 = require("./dependencies");
|
|
5
5
|
Object.defineProperty(exports, "isReactScannerInstalled", { enumerable: true, get: function () { return dependencies_1.isReactScannerInstalled; } });
|
|
6
6
|
Object.defineProperty(exports, "checkPeerDependency", { enumerable: true, get: function () { return dependencies_1.checkPeerDependency; } });
|
|
@@ -13,8 +13,6 @@ Object.defineProperty(exports, "getServerPort", { enumerable: true, get: functio
|
|
|
13
13
|
Object.defineProperty(exports, "isPortAvailable", { enumerable: true, get: function () { return port_1.isPortAvailable; } });
|
|
14
14
|
var scannerConfig_1 = require("./scannerConfig");
|
|
15
15
|
Object.defineProperty(exports, "readScannerConfig", { enumerable: true, get: function () { return scannerConfig_1.readScannerConfig; } });
|
|
16
|
-
Object.defineProperty(exports, "getOutputDir", { enumerable: true, get: function () { return scannerConfig_1.getOutputDir; } });
|
|
17
|
-
Object.defineProperty(exports, "getLatestScanFile", { enumerable: true, get: function () { return scannerConfig_1.getLatestScanFile; } });
|
|
18
16
|
Object.defineProperty(exports, "readScanData", { enumerable: true, get: function () { return scannerConfig_1.readScanData; } });
|
|
19
17
|
Object.defineProperty(exports, "getScanData", { enumerable: true, get: function () { return scannerConfig_1.getScanData; } });
|
|
20
18
|
//# sourceMappingURL=index.js.map
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;AAAA,+CAKwB;AAJtB,uHAAA,uBAAuB,OAAA;AACvB,mHAAA,mBAAmB,OAAA;AACnB,yHAAA,yBAAyB,OAAA;AACzB,mHAAA,mBAAmB,OAAA;AAErB,mCAAoD;AAA3C,kHAAA,wBAAwB,OAAA;AACjC,+BAAwD;AAA/C,qGAAA,aAAa,OAAA;AAAE,uGAAA,eAAe,OAAA;AAEvC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;AAAA,+CAKwB;AAJtB,uHAAA,uBAAuB,OAAA;AACvB,mHAAA,mBAAmB,OAAA;AACnB,yHAAA,yBAAyB,OAAA;AACzB,mHAAA,mBAAmB,OAAA;AAErB,mCAAoD;AAA3C,kHAAA,wBAAwB,OAAA;AACjC,+BAAwD;AAA/C,qGAAA,aAAa,OAAA;AAAE,uGAAA,eAAe,OAAA;AAEvC,iDAA+E;AAAtE,kHAAA,iBAAiB,OAAA;AAAE,6GAAA,YAAY,OAAA;AAAE,4GAAA,WAAW,OAAA"}
|
|
@@ -3,7 +3,7 @@ export interface ScannerConfig {
|
|
|
3
3
|
includeSubComponents?: boolean;
|
|
4
4
|
importedFrom?: string;
|
|
5
5
|
processors?: Array<[string, {
|
|
6
|
-
|
|
6
|
+
outputTo?: string;
|
|
7
7
|
}]>;
|
|
8
8
|
}
|
|
9
9
|
export interface ScanData {
|
|
@@ -17,13 +17,9 @@ export interface ScanData {
|
|
|
17
17
|
*/
|
|
18
18
|
export declare function readScannerConfig(): ScannerConfig | null;
|
|
19
19
|
/**
|
|
20
|
-
* Get the output
|
|
20
|
+
* Get the output file path from the scanner config
|
|
21
21
|
*/
|
|
22
|
-
export declare function
|
|
23
|
-
/**
|
|
24
|
-
* Get the latest scan file from the output directory
|
|
25
|
-
*/
|
|
26
|
-
export declare function getLatestScanFile(outputDir: string): string | null;
|
|
22
|
+
export declare function getOutputFile(config: ScannerConfig): string | null;
|
|
27
23
|
/**
|
|
28
24
|
* Read and parse the scan data from a JSON file
|
|
29
25
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scannerConfig.d.ts","sourceRoot":"","sources":["../../src/utils/scannerConfig.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE;QAAE,
|
|
1
|
+
{"version":3,"file":"scannerConfig.d.ts","sourceRoot":"","sources":["../../src/utils/scannerConfig.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,QAAQ;IACvB,CAAC,aAAa,EAAE,MAAM,GAAG;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,aAAa,GAAG,IAAI,CAmBxD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAgBlE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAgB9D;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI;IAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAqB7E"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.readScannerConfig = readScannerConfig;
|
|
4
|
-
exports.
|
|
5
|
-
exports.getLatestScanFile = getLatestScanFile;
|
|
4
|
+
exports.getOutputFile = getOutputFile;
|
|
6
5
|
exports.readScanData = readScanData;
|
|
7
6
|
exports.getScanData = getScanData;
|
|
8
7
|
const fs_1 = require("fs");
|
|
@@ -11,9 +10,9 @@ const path_1 = require("path");
|
|
|
11
10
|
* Read and parse the react-scanner.config.js file
|
|
12
11
|
*/
|
|
13
12
|
function readScannerConfig() {
|
|
14
|
-
const configPath = (0, path_1.join)(process.cwd(),
|
|
13
|
+
const configPath = (0, path_1.join)(process.cwd(), "react-scanner.config.js");
|
|
15
14
|
if (!(0, fs_1.existsSync)(configPath)) {
|
|
16
|
-
console.error(
|
|
15
|
+
console.error("react-scanner.config.js not found. Run `react-scanner-ui init` first.");
|
|
17
16
|
return null;
|
|
18
17
|
}
|
|
19
18
|
try {
|
|
@@ -23,65 +22,42 @@ function readScannerConfig() {
|
|
|
23
22
|
return config;
|
|
24
23
|
}
|
|
25
24
|
catch (error) {
|
|
26
|
-
console.error(
|
|
25
|
+
console.error("Failed to read react-scanner.config.js:", error);
|
|
27
26
|
return null;
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
29
|
/**
|
|
31
|
-
* Get the output
|
|
30
|
+
* Get the output file path from the scanner config
|
|
32
31
|
*/
|
|
33
|
-
function
|
|
32
|
+
function getOutputFile(config) {
|
|
34
33
|
if (!config.processors || !Array.isArray(config.processors)) {
|
|
35
34
|
return null;
|
|
36
35
|
}
|
|
37
36
|
for (const processor of config.processors) {
|
|
38
|
-
if (Array.isArray(processor) &&
|
|
39
|
-
|
|
37
|
+
if (Array.isArray(processor) &&
|
|
38
|
+
processor[0] === "count-components-and-props" &&
|
|
39
|
+
processor[1]?.outputTo) {
|
|
40
|
+
return processor[1].outputTo;
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
return null;
|
|
43
44
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Get the latest scan file from the output directory
|
|
46
|
-
*/
|
|
47
|
-
function getLatestScanFile(outputDir) {
|
|
48
|
-
const absoluteOutputDir = (0, path_1.resolve)(process.cwd(), outputDir);
|
|
49
|
-
if (!(0, fs_1.existsSync)(absoluteOutputDir)) {
|
|
50
|
-
console.error(`Scan output directory not found: ${absoluteOutputDir}`);
|
|
51
|
-
console.error('Run react-scanner first to generate scan data.');
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
const files = (0, fs_1.readdirSync)(absoluteOutputDir)
|
|
56
|
-
.filter((file) => file.endsWith('.json'))
|
|
57
|
-
.map((file) => ({
|
|
58
|
-
name: file,
|
|
59
|
-
path: (0, path_1.join)(absoluteOutputDir, file),
|
|
60
|
-
mtime: (0, fs_1.statSync)((0, path_1.join)(absoluteOutputDir, file)).mtime,
|
|
61
|
-
}))
|
|
62
|
-
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
63
|
-
if (files.length === 0) {
|
|
64
|
-
console.error('No scan files found in output directory.');
|
|
65
|
-
console.error('Run react-scanner first to generate scan data.');
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
return files[0].path;
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
console.error('Failed to read scan output directory:', error);
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
45
|
/**
|
|
76
46
|
* Read and parse the scan data from a JSON file
|
|
77
47
|
*/
|
|
78
48
|
function readScanData(filePath) {
|
|
49
|
+
const absolutePath = (0, path_1.resolve)(process.cwd(), filePath);
|
|
50
|
+
if (!(0, fs_1.existsSync)(absolutePath)) {
|
|
51
|
+
console.error(`Scan data file not found: ${absolutePath}`);
|
|
52
|
+
console.error("Run react-scanner first to generate scan data.");
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
79
55
|
try {
|
|
80
|
-
const content = (0, fs_1.readFileSync)(
|
|
56
|
+
const content = (0, fs_1.readFileSync)(absolutePath, "utf-8");
|
|
81
57
|
return JSON.parse(content);
|
|
82
58
|
}
|
|
83
59
|
catch (error) {
|
|
84
|
-
console.error(
|
|
60
|
+
console.error("Failed to read scan data:", error);
|
|
85
61
|
return null;
|
|
86
62
|
}
|
|
87
63
|
}
|
|
@@ -91,19 +67,18 @@ function readScanData(filePath) {
|
|
|
91
67
|
function getScanData() {
|
|
92
68
|
const config = readScannerConfig();
|
|
93
69
|
if (!config) {
|
|
94
|
-
return { data: null, error:
|
|
95
|
-
}
|
|
96
|
-
const outputDir = getOutputDir(config);
|
|
97
|
-
if (!outputDir) {
|
|
98
|
-
return { data: null, error: 'Could not find output directory in config. Make sure you have a raw-report processor configured.' };
|
|
70
|
+
return { data: null, error: "Could not read react-scanner.config.js" };
|
|
99
71
|
}
|
|
100
|
-
const scanFile =
|
|
72
|
+
const scanFile = getOutputFile(config);
|
|
101
73
|
if (!scanFile) {
|
|
102
|
-
return {
|
|
74
|
+
return {
|
|
75
|
+
data: null,
|
|
76
|
+
error: "Could not find output file in config. Make sure you have a count-components-and-props processor configured with outputTo.",
|
|
77
|
+
};
|
|
103
78
|
}
|
|
104
79
|
const data = readScanData(scanFile);
|
|
105
80
|
if (!data) {
|
|
106
|
-
return { data: null, error:
|
|
81
|
+
return { data: null, error: "Failed to parse scan data." };
|
|
107
82
|
}
|
|
108
83
|
return { data, error: null };
|
|
109
84
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scannerConfig.js","sourceRoot":"","sources":["../../src/utils/scannerConfig.ts"],"names":[],"mappings":";;AAoBA,
|
|
1
|
+
{"version":3,"file":"scannerConfig.js","sourceRoot":"","sources":["../../src/utils/scannerConfig.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-scanner-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"react-scanner-ui": "dist/index.js"
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"@vitejs/plugin-react": "^4.3.4",
|
|
28
28
|
"commander": "^14.0.2",
|
|
29
29
|
"detect-port": "^1.6.1",
|
|
30
|
-
"polka": "^0.5.2",
|
|
31
30
|
"react": "^18.3.1",
|
|
32
31
|
"react-dom": "^18.3.1",
|
|
33
32
|
"vite": "^5.4.19"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
max-width: 1200px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
padding: 24px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
h1 {
|
|
8
|
+
color: var(--color-text);
|
|
9
|
+
margin: 0 0 24px 0;
|
|
10
|
+
font-size: 28px;
|
|
11
|
+
font-weight: 600;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.loading {
|
|
15
|
+
color: var(--color-text-secondary);
|
|
16
|
+
font-style: italic;
|
|
17
|
+
padding: 20px;
|
|
18
|
+
text-align: center;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.error {
|
|
22
|
+
background-color: var(--color-error-bg);
|
|
23
|
+
border: 1px solid var(--color-error-border);
|
|
24
|
+
color: var(--color-error-text);
|
|
25
|
+
padding: 16px;
|
|
26
|
+
border-radius: var(--radius-sm);
|
|
27
|
+
margin-bottom: 20px;
|
|
28
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { ComponentTable } from "./ComponentTable";
|
|
3
|
+
import "./App.css";
|
|
4
|
+
|
|
5
|
+
export interface ScanData {
|
|
6
|
+
[componentName: string]: {
|
|
7
|
+
instances: number;
|
|
8
|
+
props?: Record<string, number>;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ApiResponse {
|
|
13
|
+
data: ScanData | null;
|
|
14
|
+
error: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function App() {
|
|
18
|
+
const [data, setData] = useState<ScanData | null>(null);
|
|
19
|
+
const [error, setError] = useState<string | null>(null);
|
|
20
|
+
const [loading, setLoading] = useState(true);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
fetch("/api/scan-data")
|
|
24
|
+
.then((res) => res.json())
|
|
25
|
+
.then((result: ApiResponse) => {
|
|
26
|
+
if (result.error) {
|
|
27
|
+
setError(result.error);
|
|
28
|
+
} else {
|
|
29
|
+
setData(result.data);
|
|
30
|
+
}
|
|
31
|
+
setLoading(false);
|
|
32
|
+
})
|
|
33
|
+
.catch((err) => {
|
|
34
|
+
setError("Failed to fetch scan data: " + err.message);
|
|
35
|
+
setLoading(false);
|
|
36
|
+
});
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="container">
|
|
41
|
+
<h1>React Scanner UI</h1>
|
|
42
|
+
{loading && <div className="loading">Loading scan data...</div>}
|
|
43
|
+
{error && <div className="error">{error}</div>}
|
|
44
|
+
{!loading && !error && data && <ComponentTable data={data} />}
|
|
45
|
+
{!loading && !error && !data && (
|
|
46
|
+
<div className="error">No component data found.</div>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default App;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
.component-table {
|
|
2
|
+
width: 100%;
|
|
3
|
+
border-collapse: collapse;
|
|
4
|
+
background-color: var(--color-bg-card);
|
|
5
|
+
box-shadow: var(--shadow-sm);
|
|
6
|
+
border-radius: var(--radius-md);
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.component-table th,
|
|
11
|
+
.component-table td {
|
|
12
|
+
padding: 12px 16px;
|
|
13
|
+
text-align: left;
|
|
14
|
+
border-bottom: 1px solid var(--color-border);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.component-table th {
|
|
18
|
+
background-color: var(--color-bg-header);
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
color: var(--color-text);
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
user-select: none;
|
|
23
|
+
transition: background-color 0.15s ease;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.component-table th:hover {
|
|
27
|
+
background-color: #e9ecef;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.component-table th .sort-indicator {
|
|
31
|
+
margin-left: 5px;
|
|
32
|
+
color: var(--color-text-muted);
|
|
33
|
+
font-size: 0.75em;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.component-table tbody tr:last-child td {
|
|
37
|
+
border-bottom: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.component-table tbody tr:hover td {
|
|
41
|
+
background-color: var(--color-bg-hover);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.component-table .count {
|
|
45
|
+
font-family: var(--font-mono);
|
|
46
|
+
font-weight: 500;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.component-table .total-row {
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
background-color: var(--color-bg-total);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.component-table .total-row td {
|
|
55
|
+
border-top: 2px solid var(--color-border-strong);
|
|
56
|
+
border-bottom: none;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.component-table .total-row:hover td {
|
|
60
|
+
background-color: var(--color-bg-total);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Expandable rows */
|
|
64
|
+
.clickable-row {
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
transition: background-color 0.15s ease;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.clickable-row.has-props:hover td {
|
|
70
|
+
background-color: var(--color-bg-hover);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.clickable-row.expanded td {
|
|
74
|
+
background-color: var(--color-bg-hover);
|
|
75
|
+
border-bottom: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.expand-icon {
|
|
79
|
+
display: inline-block;
|
|
80
|
+
width: 16px;
|
|
81
|
+
margin-right: 8px;
|
|
82
|
+
font-size: 0.7em;
|
|
83
|
+
color: var(--color-text-muted);
|
|
84
|
+
transition: transform 0.2s ease;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.expand-icon.expanded {
|
|
88
|
+
transform: rotate(90deg);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Props row */
|
|
92
|
+
.props-row td {
|
|
93
|
+
padding: 8px 16px 16px 16px;
|
|
94
|
+
background-color: var(--color-bg-hover);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.props-container {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: flex-start;
|
|
100
|
+
gap: 12px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.props-label {
|
|
104
|
+
font-size: 0.85em;
|
|
105
|
+
font-weight: 600;
|
|
106
|
+
color: var(--color-text-muted);
|
|
107
|
+
padding-top: 4px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.props-chips {
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-wrap: wrap;
|
|
113
|
+
gap: 8px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.prop-chip {
|
|
117
|
+
display: inline-flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 6px;
|
|
120
|
+
padding: 4px 10px;
|
|
121
|
+
background-color: var(--color-bg-card);
|
|
122
|
+
border: 1px solid var(--color-border);
|
|
123
|
+
border-radius: 16px;
|
|
124
|
+
font-size: 0.85em;
|
|
125
|
+
color: var(--color-text);
|
|
126
|
+
transition:
|
|
127
|
+
background-color 0.15s ease,
|
|
128
|
+
border-color 0.15s ease;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.prop-chip:hover {
|
|
132
|
+
background-color: var(--color-bg-header);
|
|
133
|
+
border-color: var(--color-border-strong);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.prop-count {
|
|
137
|
+
display: inline-flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
min-width: 20px;
|
|
141
|
+
height: 20px;
|
|
142
|
+
padding: 0 6px;
|
|
143
|
+
background-color: var(--color-primary, #4a90d9);
|
|
144
|
+
color: white;
|
|
145
|
+
border-radius: 10px;
|
|
146
|
+
font-size: 0.8em;
|
|
147
|
+
font-weight: 600;
|
|
148
|
+
font-family: var(--font-mono);
|
|
149
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { ScanData } from "./App";
|
|
3
|
+
import "./ComponentTable.css";
|
|
4
|
+
|
|
5
|
+
interface ComponentTableProps {
|
|
6
|
+
data: ScanData;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type SortKey = "name" | "count";
|
|
10
|
+
type SortDirection = "asc" | "desc";
|
|
11
|
+
|
|
12
|
+
interface SortConfig {
|
|
13
|
+
key: SortKey;
|
|
14
|
+
direction: SortDirection;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ComponentTable({ data }: ComponentTableProps) {
|
|
18
|
+
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
|
19
|
+
key: "count",
|
|
20
|
+
direction: "desc",
|
|
21
|
+
});
|
|
22
|
+
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
|
|
23
|
+
|
|
24
|
+
const handleSort = (key: SortKey) => {
|
|
25
|
+
setSortConfig((prev) => ({
|
|
26
|
+
key,
|
|
27
|
+
direction: prev.key === key && prev.direction === "asc" ? "desc" : "asc",
|
|
28
|
+
}));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getSortIndicator = (key: SortKey) => {
|
|
32
|
+
if (sortConfig.key !== key) return "";
|
|
33
|
+
return sortConfig.direction === "asc" ? "ā²" : "ā¼";
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const toggleRow = (name: string) => {
|
|
37
|
+
setExpandedRows((prev) => {
|
|
38
|
+
const next = new Set(prev);
|
|
39
|
+
if (next.has(name)) {
|
|
40
|
+
next.delete(name);
|
|
41
|
+
} else {
|
|
42
|
+
next.add(name);
|
|
43
|
+
}
|
|
44
|
+
return next;
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Transform data into array and sort
|
|
49
|
+
const components = Object.entries(data).map(([name, info]) => ({
|
|
50
|
+
name,
|
|
51
|
+
count: info.instances,
|
|
52
|
+
props: info.props || {},
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
components.sort((a, b) => {
|
|
56
|
+
const aValue = sortConfig.key === "name" ? a.name.toLowerCase() : a.count;
|
|
57
|
+
const bValue = sortConfig.key === "name" ? b.name.toLowerCase() : b.count;
|
|
58
|
+
|
|
59
|
+
if (aValue < bValue) return sortConfig.direction === "asc" ? -1 : 1;
|
|
60
|
+
if (aValue > bValue) return sortConfig.direction === "asc" ? 1 : -1;
|
|
61
|
+
return 0;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const totalCount = components.reduce((sum, c) => sum + c.count, 0);
|
|
65
|
+
|
|
66
|
+
if (components.length === 0) {
|
|
67
|
+
return <div className="empty-state">No components found in scan data.</div>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className="table-container">
|
|
72
|
+
<table className="component-table">
|
|
73
|
+
<thead>
|
|
74
|
+
<tr>
|
|
75
|
+
<th onClick={() => handleSort("name")}>
|
|
76
|
+
Component
|
|
77
|
+
<span className="sort-indicator">{getSortIndicator("name")}</span>
|
|
78
|
+
</th>
|
|
79
|
+
<th onClick={() => handleSort("count")}>
|
|
80
|
+
Usage Count
|
|
81
|
+
<span className="sort-indicator">
|
|
82
|
+
{getSortIndicator("count")}
|
|
83
|
+
</span>
|
|
84
|
+
</th>
|
|
85
|
+
</tr>
|
|
86
|
+
</thead>
|
|
87
|
+
<tbody>
|
|
88
|
+
{components.map((component) => {
|
|
89
|
+
const isExpanded = expandedRows.has(component.name);
|
|
90
|
+
const hasProps = Object.keys(component.props).length > 0;
|
|
91
|
+
const sortedProps = Object.entries(component.props).sort(
|
|
92
|
+
([, a], [, b]) => b - a,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
<tr
|
|
98
|
+
key={component.name}
|
|
99
|
+
onClick={() => toggleRow(component.name)}
|
|
100
|
+
className={`clickable-row ${isExpanded ? "expanded" : ""} ${hasProps ? "has-props" : ""}`}
|
|
101
|
+
>
|
|
102
|
+
<td className="component-name">
|
|
103
|
+
<span
|
|
104
|
+
className={`expand-icon ${isExpanded ? "expanded" : ""}`}
|
|
105
|
+
>
|
|
106
|
+
{hasProps ? "ā¶" : ""}
|
|
107
|
+
</span>
|
|
108
|
+
{component.name}
|
|
109
|
+
</td>
|
|
110
|
+
<td className="count">{component.count}</td>
|
|
111
|
+
</tr>
|
|
112
|
+
{isExpanded && hasProps && (
|
|
113
|
+
<tr key={`${component.name}-props`} className="props-row">
|
|
114
|
+
<td colSpan={2}>
|
|
115
|
+
<div className="props-container">
|
|
116
|
+
<span className="props-label">Props:</span>
|
|
117
|
+
<div className="props-chips">
|
|
118
|
+
{sortedProps.map(([propName, propCount]) => (
|
|
119
|
+
<span key={propName} className="prop-chip">
|
|
120
|
+
{propName}
|
|
121
|
+
<span className="prop-count">{propCount}</span>
|
|
122
|
+
</span>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</td>
|
|
127
|
+
</tr>
|
|
128
|
+
)}
|
|
129
|
+
</>
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
132
|
+
<tr className="total-row">
|
|
133
|
+
<td>Total</td>
|
|
134
|
+
<td className="count">{totalCount}</td>
|
|
135
|
+
</tr>
|
|
136
|
+
</tbody>
|
|
137
|
+
</table>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
package/ui/index.css
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
* {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
--color-bg: #f5f5f5;
|
|
7
|
+
--color-bg-card: #ffffff;
|
|
8
|
+
--color-bg-header: #f8f9fa;
|
|
9
|
+
--color-bg-hover: #f8f9fa;
|
|
10
|
+
--color-bg-total: #f0f0f0;
|
|
11
|
+
--color-text: #333333;
|
|
12
|
+
--color-text-secondary: #666666;
|
|
13
|
+
--color-text-muted: #999999;
|
|
14
|
+
--color-border: #eeeeee;
|
|
15
|
+
--color-border-strong: #dddddd;
|
|
16
|
+
--color-error-bg: #fee2e2;
|
|
17
|
+
--color-error-border: #fecaca;
|
|
18
|
+
--color-error-text: #dc2626;
|
|
19
|
+
--color-primary: #3b82f6;
|
|
20
|
+
--color-primary-hover: #2563eb;
|
|
21
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
22
|
+
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
23
|
+
--radius-sm: 4px;
|
|
24
|
+
--radius-md: 8px;
|
|
25
|
+
--font-mono: 'SF Mono', Monaco, 'Courier New', monospace;
|
|
26
|
+
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
font-family: var(--font-sans);
|
|
31
|
+
margin: 0;
|
|
32
|
+
padding: 0;
|
|
33
|
+
background-color: var(--color-bg);
|
|
34
|
+
color: var(--color-text);
|
|
35
|
+
line-height: 1.5;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#root {
|
|
39
|
+
min-height: 100vh;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
a {
|
|
43
|
+
color: var(--color-primary);
|
|
44
|
+
text-decoration: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
a:hover {
|
|
48
|
+
color: var(--color-primary-hover);
|
|
49
|
+
text-decoration: underline;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
button {
|
|
53
|
+
font-family: inherit;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
input, select, textarea {
|
|
58
|
+
font-family: inherit;
|
|
59
|
+
}
|
package/ui/index.html
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>React Scanner UI</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="./main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
12
|
</html>
|
package/ui/main.tsx
ADDED