@cadcrawl/cad-browser 0.3.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/LICENSE.md +5 -0
- package/README.md +65 -0
- package/bin/cad-browser +4 -0
- package/dist/client/assets/index-C1XW2UmF.css +1 -0
- package/dist/client/assets/index-HFIcPO3j.js +204 -0
- package/dist/client/index.html +14 -0
- package/package.json +62 -0
- package/src/analyzer.js +31 -0
- package/src/args.js +16 -0
- package/src/cache.js +38 -0
- package/src/cli.js +53 -0
- package/src/file-types.js +15 -0
- package/src/main.jsx +567 -0
- package/src/path-safety.js +15 -0
- package/src/project-store.js +127 -0
- package/src/reveal-file.js +36 -0
- package/src/scanner.js +75 -0
- package/src/server.js +103 -0
- package/src/styles.css +435 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="theme-color" content="#f5f3ee" />
|
|
7
|
+
<title>CAD Browser</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-HFIcPO3j.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C1XW2UmF.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cadcrawl/cad-browser",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Local engineering file browser with CAD and PDF previews powered by cad-toolbox.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
7
|
+
"homepage": "https://github.com/art22017/cadcrawl/tree/master/cad-browser#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/art22017/cadcrawl.git",
|
|
11
|
+
"directory": "cad-browser"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/art22017/cadcrawl/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"cad",
|
|
18
|
+
"step",
|
|
19
|
+
"stl",
|
|
20
|
+
"3mf",
|
|
21
|
+
"pdf",
|
|
22
|
+
"engineering",
|
|
23
|
+
"file-browser",
|
|
24
|
+
"preview"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"cad-browser": "bin/cad-browser"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"bin/",
|
|
31
|
+
"dist/",
|
|
32
|
+
"src/",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE.md",
|
|
35
|
+
"package.json"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "vite build",
|
|
42
|
+
"dev": "vite",
|
|
43
|
+
"start": "node bin/cad-browser",
|
|
44
|
+
"test": "node --test",
|
|
45
|
+
"prepublishOnly": "npm run test && npm run build"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@cadcrawl/cad-toolbox": "^0.3.0",
|
|
52
|
+
"express": "^5.1.0",
|
|
53
|
+
"open": "^10.2.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@vitejs/plugin-react": "^4.6.0",
|
|
57
|
+
"lucide-react": "^0.468.0",
|
|
58
|
+
"vite": "^6.1.0",
|
|
59
|
+
"react": "^19.0.0",
|
|
60
|
+
"react-dom": "^19.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/analyzer.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { inspectFile } from '@cadcrawl/cad-toolbox/src/inspect.js';
|
|
2
|
+
import { CAD_VIEW_DIRECTIONS } from '@cadcrawl/cad-toolbox/src/render-options.js';
|
|
3
|
+
import { renderStem } from './cache.js';
|
|
4
|
+
|
|
5
|
+
export async function analyzeFile(absolutePath, relativePath, cache, kind) {
|
|
6
|
+
const fields = new Set(['metadata', 'renders']);
|
|
7
|
+
if (kind === 'cad' && /\.(step|stp)$/i.test(relativePath)) fields.add('tree');
|
|
8
|
+
if (kind === 'pdf') fields.add('text');
|
|
9
|
+
|
|
10
|
+
const options = {
|
|
11
|
+
width: 1200,
|
|
12
|
+
height: 900,
|
|
13
|
+
outputDir: cache.rendersDirectory,
|
|
14
|
+
output: null,
|
|
15
|
+
outputStem: renderStem(relativePath),
|
|
16
|
+
views: [{ name: 'iso-front', direction: CAD_VIEW_DIRECTIONS['iso-front'] }],
|
|
17
|
+
page: 1,
|
|
18
|
+
pages: null,
|
|
19
|
+
background: { r: 247, g: 246, b: 242 },
|
|
20
|
+
edgeOutline: true,
|
|
21
|
+
fullText: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const result = await inspectFile(absolutePath, fields, options);
|
|
25
|
+
return {
|
|
26
|
+
metadata: result.metadata ?? null,
|
|
27
|
+
tree: result.tree ?? null,
|
|
28
|
+
text: result.text ?? null,
|
|
29
|
+
previewPath: result.renders?.[0] ?? null,
|
|
30
|
+
};
|
|
31
|
+
}
|
package/src/args.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function parseArgs(argv) {
|
|
2
|
+
const options = { port: 6767, open: true, host: '127.0.0.1', directory: '.' };
|
|
3
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
4
|
+
const value = argv[index];
|
|
5
|
+
if (value === '--help' || value === '-h') options.help = true;
|
|
6
|
+
else if (value === '--no-open') options.open = false;
|
|
7
|
+
else if (value === '--port') options.port = Number(argv[++index]);
|
|
8
|
+
else if (value === '--host') options.host = argv[++index];
|
|
9
|
+
else if (!value.startsWith('-')) options.directory = value;
|
|
10
|
+
else throw new Error(`Unknown option: ${value}`);
|
|
11
|
+
}
|
|
12
|
+
if (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535) {
|
|
13
|
+
throw new Error('Port must be an integer between 1 and 65535');
|
|
14
|
+
}
|
|
15
|
+
return options;
|
|
16
|
+
}
|
package/src/cache.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
export async function createProjectCache(rootPath) {
|
|
7
|
+
const projectId = crypto.createHash('sha256').update(rootPath.toLowerCase()).digest('hex').slice(0, 20);
|
|
8
|
+
const directory = path.join(os.homedir(), '.cadcrawl', 'cad-browser', 'projects', projectId);
|
|
9
|
+
const rendersDirectory = path.join(directory, 'renders');
|
|
10
|
+
await fs.mkdir(rendersDirectory, { recursive: true });
|
|
11
|
+
return {
|
|
12
|
+
directory,
|
|
13
|
+
rendersDirectory,
|
|
14
|
+
indexPath: path.join(directory, 'index.json'),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function readCache(cache) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(await fs.readFile(cache.indexPath, 'utf8'));
|
|
21
|
+
} catch {
|
|
22
|
+
return { version: 1, files: {} };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function writeCache(cache, value) {
|
|
27
|
+
const temporaryPath = `${cache.indexPath}.tmp`;
|
|
28
|
+
await fs.writeFile(temporaryPath, JSON.stringify(value, null, 2));
|
|
29
|
+
await fs.rename(temporaryPath, cache.indexPath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function renderStem(relativePath) {
|
|
33
|
+
const hash = crypto.createHash('sha1').update(relativePath).digest('hex').slice(0, 10);
|
|
34
|
+
const base = path.basename(relativePath, path.extname(relativePath))
|
|
35
|
+
.replace(/[^\p{L}\p{N}._-]+/gu, '-')
|
|
36
|
+
.slice(0, 60);
|
|
37
|
+
return `${base || 'asset'}-${hash}`;
|
|
38
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import { parseArgs } from './args.js';
|
|
5
|
+
import { ProjectStore } from './project-store.js';
|
|
6
|
+
import { createServer } from './server.js';
|
|
7
|
+
|
|
8
|
+
const HELP = `cad-browser
|
|
9
|
+
|
|
10
|
+
Browse an engineering project with local CAD/PDF previews and metadata.
|
|
11
|
+
|
|
12
|
+
USAGE
|
|
13
|
+
npx @cadcrawl/cad-browser [directory] [options]
|
|
14
|
+
|
|
15
|
+
OPTIONS
|
|
16
|
+
--port <number> Local port, default 6767
|
|
17
|
+
--host <address> Bind address, default 127.0.0.1
|
|
18
|
+
--no-open Do not open the browser automatically
|
|
19
|
+
--help Show this help
|
|
20
|
+
|
|
21
|
+
EXAMPLES
|
|
22
|
+
npx @cadcrawl/cad-browser
|
|
23
|
+
npx @cadcrawl/cad-browser C:\\engineering\\project
|
|
24
|
+
npx @cadcrawl/cad-browser . --port 6767 --no-open
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
export async function run(argv) {
|
|
28
|
+
try {
|
|
29
|
+
const options = parseArgs(argv);
|
|
30
|
+
if (options.help) {
|
|
31
|
+
process.stdout.write(HELP);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const rootPath = path.resolve(options.directory);
|
|
35
|
+
const stat = await fs.stat(rootPath);
|
|
36
|
+
if (!stat.isDirectory()) throw new Error(`Not a directory: ${rootPath}`);
|
|
37
|
+
|
|
38
|
+
const store = new ProjectStore(rootPath);
|
|
39
|
+
await store.initialize();
|
|
40
|
+
const app = await createServer(store);
|
|
41
|
+
const server = app.listen(options.port, options.host, async () => {
|
|
42
|
+
const url = `http://${options.host}:${options.port}`;
|
|
43
|
+
process.stdout.write(`CAD Browser\n${rootPath}\n${url}\n`);
|
|
44
|
+
if (options.open) await open(url);
|
|
45
|
+
});
|
|
46
|
+
const close = () => server.close(() => process.exit(0));
|
|
47
|
+
process.on('SIGINT', close);
|
|
48
|
+
process.on('SIGTERM', close);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
process.stderr.write(`cad-browser: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const CAD_EXTENSIONS = new Set(['.step', '.stp', '.stl', '.3mf']);
|
|
2
|
+
export const PDF_EXTENSIONS = new Set(['.pdf']);
|
|
3
|
+
export const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp', '.svg']);
|
|
4
|
+
|
|
5
|
+
export function classifyExtension(extension) {
|
|
6
|
+
const normalized = extension.toLowerCase();
|
|
7
|
+
if (CAD_EXTENSIONS.has(normalized)) return 'cad';
|
|
8
|
+
if (PDF_EXTENSIONS.has(normalized)) return 'pdf';
|
|
9
|
+
if (IMAGE_EXTENSIONS.has(normalized)) return 'image';
|
|
10
|
+
return 'file';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function canAnalyze(extension) {
|
|
14
|
+
return CAD_EXTENSIONS.has(extension.toLowerCase()) || PDF_EXTENSIONS.has(extension.toLowerCase());
|
|
15
|
+
}
|