Compare commits
7 Commits
d529e34249
...
22801f6b75
| Author | SHA1 | Date | |
|---|---|---|---|
| 22801f6b75 | |||
| 105a23853a | |||
| 1c758d68fc | |||
| fc1c7990be | |||
| 647bd37fad | |||
| e2f9b209d6 | |||
| a58a3c438c |
0
build/.gitkeep
Normal file
0
build/.gitkeep
Normal file
BIN
build/icon.ico
Normal file
BIN
build/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
build/icon.png
Normal file
BIN
build/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
12
contrib/linux/Dockerfile
Normal file
12
contrib/linux/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM node:20-bookworm-slim
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libudev-dev \
|
||||
libusb-1.0-0-dev \
|
||||
fuse \
|
||||
rpm \
|
||||
dpkg-dev \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /project
|
||||
25
contrib/linux/build.sh
Executable file
25
contrib/linux/build.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
IMAGE_NAME="cadroute-builder-linux"
|
||||
|
||||
echo "[cadroute] Building Docker image..."
|
||||
docker build -t "$IMAGE_NAME" "$SCRIPT_DIR"
|
||||
|
||||
echo "[cadroute] Running Linux build..."
|
||||
docker run --rm \
|
||||
--device /dev/fuse \
|
||||
--cap-add SYS_ADMIN \
|
||||
-v "$PROJECT_ROOT":/project \
|
||||
-w /project \
|
||||
-v cadroute-electron-cache:/root/.cache/electron \
|
||||
-v cadroute-electronbuilder-cache:/root/.cache/electron-builder \
|
||||
-v /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro \
|
||||
-e SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \
|
||||
-e NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt \
|
||||
"$IMAGE_NAME" \
|
||||
bash -c "npm ci && npm run build -- --linux --x64"
|
||||
|
||||
echo "[cadroute] Artefatti in: $PROJECT_ROOT/dist"
|
||||
2
contrib/windows/Dockerfile
Normal file
2
contrib/windows/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM electronuserland/builder:wine-mono
|
||||
WORKDIR /project
|
||||
20
contrib/windows/build.sh
Executable file
20
contrib/windows/build.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
IMAGE_NAME="cadroute-builder-windows"
|
||||
|
||||
echo "[cadroute] Building Docker image..."
|
||||
docker build -t "$IMAGE_NAME" "$SCRIPT_DIR"
|
||||
|
||||
echo "[cadroute] Running Windows build..."
|
||||
docker run --rm \
|
||||
-v "$PROJECT_ROOT":/project \
|
||||
-w /project \
|
||||
-v cadroute-electron-cache:/root/.cache/electron \
|
||||
-v cadroute-electronbuilder-cache:/root/.cache/electron-builder \
|
||||
"$IMAGE_NAME" \
|
||||
bash -c "npm ci && npm run build -- --win --x64"
|
||||
|
||||
echo "[cadroute] Artefatti in: $PROJECT_ROOT/dist"
|
||||
24
main.js
24
main.js
@@ -1,4 +1,4 @@
|
||||
const { app, BrowserWindow, dialog, ipcMain, shell } = require('electron');
|
||||
const { app, BrowserWindow, dialog, ipcMain, shell, Menu } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
@@ -13,7 +13,7 @@ const {
|
||||
} = require('./services/unrouted');
|
||||
|
||||
const CAD_EXTENSIONS = ['prt', 'asm', 'dwr'];
|
||||
const DEFAULT_DESTINATION = './output/cad';
|
||||
const DEFAULT_DESTINATION = 'X:\\';
|
||||
const SETTINGS_FILENAME = 'cad-router-settings.json';
|
||||
const LINUX_RUNTIME_DIR = '.cadroute';
|
||||
|
||||
@@ -68,6 +68,7 @@ function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
width: 900,
|
||||
height: 640,
|
||||
icon: path.join(__dirname, 'build', 'icon.png'),
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
contextIsolation: true,
|
||||
@@ -78,6 +79,25 @@ function createWindow() {
|
||||
win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
||||
}
|
||||
|
||||
function createDocsWindow() {
|
||||
const docs = new BrowserWindow({
|
||||
width: 900,
|
||||
height: 700,
|
||||
title: 'CadRoute — Documentazione',
|
||||
icon: path.join(__dirname, 'build', 'icon.png'),
|
||||
webPreferences: { contextIsolation: true, nodeIntegration: false },
|
||||
});
|
||||
docs.loadFile(path.join(__dirname, 'renderer', 'docs', 'index.html'));
|
||||
docs.setMenuBarVisibility(false);
|
||||
}
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Help',
|
||||
submenu: [{ label: 'Documentazione', click: () => createDocsWindow() }],
|
||||
},
|
||||
]));
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
await ensureRuntimeDirectory();
|
||||
const persistedDestination = await loadPersistedDestination();
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "progetto",
|
||||
"name": "cadroute",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "progetto",
|
||||
"name": "cadroute",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
||||
25
package.json
25
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "progetto",
|
||||
"name": "cadroute",
|
||||
"version": "1.0.0",
|
||||
"description": "CAD File Router MVP",
|
||||
"description": "CadRoute MVP",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "env -u ELECTRON_RUN_AS_NODE electron .",
|
||||
@@ -12,17 +12,32 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"build": {
|
||||
"appId": "com.cad.router",
|
||||
"appId": "com.cadroute",
|
||||
"productName": "CadRoute",
|
||||
"files": [
|
||||
"main.js",
|
||||
"preload.js",
|
||||
"renderer/**/*",
|
||||
"services/**/*",
|
||||
"build/icon.png",
|
||||
"renderer/docs/**/*"
|
||||
],
|
||||
"win": {
|
||||
"target": "nsis"
|
||||
"target": "nsis",
|
||||
"icon": "build/icon.ico"
|
||||
},
|
||||
"linux": {
|
||||
"target": ["AppImage", "deb"],
|
||||
"icon": "build/icon.png",
|
||||
"category": "Utility"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"electron": "^40.7.0",
|
||||
"fs-extra": "^11.3.4",
|
||||
"unzipper": "^0.12.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^40.7.0",
|
||||
"electron-builder": "^26.8.1"
|
||||
}
|
||||
}
|
||||
|
||||
277
renderer/docs/index.html
Normal file
277
renderer/docs/index.html
Normal file
@@ -0,0 +1,277 @@
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>CadRoute — Documentazione</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div class="nav-brand">
|
||||
<div class="logo">CadRoute</div>
|
||||
<div class="version">Guida utente</div>
|
||||
</div>
|
||||
|
||||
<span class="nav-section-label">Introduzione</span>
|
||||
<a href="#intro">Cos'è CadRoute</a>
|
||||
<a href="#requisiti">Requisiti</a>
|
||||
|
||||
<span class="nav-section-label">Utilizzo</span>
|
||||
<a href="#destinazione">Configurare la destinazione</a>
|
||||
<a href="#cartella">Smistare i file</a>
|
||||
<a href="#risultati">Leggere i risultati</a>
|
||||
|
||||
<span class="nav-section-label">Approfondimenti</span>
|
||||
<a href="#nomi">Convenzione nomi file</a>
|
||||
<a href="#nonsmistati">File non smistati</a>
|
||||
<a href="#duplicati">File duplicati</a>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
|
||||
<!-- ── INTRODUZIONE ──────────────────────── -->
|
||||
<section id="intro">
|
||||
<h1>CadRoute — Guida utente</h1>
|
||||
<div class="intro-card">
|
||||
<p><strong>CadRoute</strong> automatizza lo smistamento di file CAD provenienti da Creo. Analizza una cartella o un archivio ZIP, riconosce i file CAD e li copia automaticamente nella sottocartella di destinazione corretta, basandosi sulla struttura numerica del nome file.</p>
|
||||
</div>
|
||||
|
||||
<h3>Formati supportati</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Estensione</th><th>Tipo</th><th>Descrizione</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="badge prt">.prt</span></td>
|
||||
<td>Part</td>
|
||||
<td>File di componente singolo Creo</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge asm">.asm</span></td>
|
||||
<td>Assembly</td>
|
||||
<td>File di assemblaggio Creo</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge dwr">.dwr</span></td>
|
||||
<td>Drawing</td>
|
||||
<td>File di disegno Creo</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge skip">altri</span></td>
|
||||
<td>—</td>
|
||||
<td>Ignorati (PDF, immagini, documenti, ecc.)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ── REQUISITI ────────────────────────── -->
|
||||
<section id="requisiti">
|
||||
<h2>Requisiti</h2>
|
||||
<p>Prima di iniziare, assicurati che la <strong>cartella di destinazione</strong> sia quella corretta.</p>
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- ── CONFIGURARE DESTINAZIONE ─────────── -->
|
||||
<section id="destinazione">
|
||||
<h2>Configurare la destinazione</h2>
|
||||
<p>La <strong>destinazione</strong> è la cartella radice dove CadRoute copierà i file CAD smistati. Va impostata una volta sola e viene ricordata tra le sessioni.</p>
|
||||
|
||||
<ol class="steps">
|
||||
<li><span>Individua la sezione <strong>Destinazione file CAD</strong> nella finestra principale.</span></li>
|
||||
<li><span>Clicca <strong>Sfoglia</strong> per aprire il selettore cartelle, oppure digita il percorso direttamente nel campo di testo.</span></li>
|
||||
<li><span>Clicca <strong>Salva</strong>. La destinazione viene memorizzata e sarà attiva anche alla prossima apertura dell'app.</span></li>
|
||||
</ol>
|
||||
|
||||
<div class="callout tip">
|
||||
<strong>Tip:</strong> il percorso di default è <code>X:\</code>. Assicurati che sia quello corretto prima di avviare lo smistamento.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── SMISTARE FILE ──────────────────────── -->
|
||||
<section id="cartella">
|
||||
<h2>Smistare i file</h2>
|
||||
<p>È possibile smistare una <strong>cartella</strong> (anche con sottocartelle annidate) o un archivio <strong>.zip</strong>, tramite i pulsanti o trascinando direttamente nell'area apposita.</p>
|
||||
|
||||
<ol class="steps">
|
||||
<li><span>Clicca <strong>Process Folder</strong> per selezionare una cartella, oppure <strong>Process ZIP</strong> per selezionare un archivio. In alternativa, trascina la cartella o il file <code>.zip</code> nell'area tratteggiata.</span></li>
|
||||
<li><span>L'app scansiona tutti i file e identifica automaticamente quelli CAD.</span></li>
|
||||
<li><span>I file CAD vengono copiati nella sottocartella di destinazione corrispondente. I file non CAD vengono saltati.</span></li>
|
||||
<li><span>Al termine vengono mostrate le statistiche nell'area di output.</span></li>
|
||||
</ol>
|
||||
|
||||
<div class="callout info">
|
||||
<strong>Nota:</strong> i file originali non vengono mai modificati né cancellati.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- ── RISULTATI ─────────────────────────── -->
|
||||
<section id="risultati">
|
||||
<h2>Leggere i risultati</h2>
|
||||
<p>Al termine di ogni operazione l'area di output mostra un riepilogo. Ecco il significato di ogni voce.</p>
|
||||
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item info">
|
||||
<div class="stat-label">Scansionati</div>
|
||||
<div class="stat-desc">Totale file analizzati (CAD e non).</div>
|
||||
</div>
|
||||
<div class="stat-item ok">
|
||||
<div class="stat-label">Copiati</div>
|
||||
<div class="stat-desc">File effettivamente scritti su disco (destinazione, non smistati o duplicati).</div>
|
||||
</div>
|
||||
<div class="stat-item muted">
|
||||
<div class="stat-label">Saltati</div>
|
||||
<div class="stat-desc">File ignorati perché non CAD (PDF, immagini, ecc.).</div>
|
||||
</div>
|
||||
<div class="stat-item warn">
|
||||
<div class="stat-label">Non smistati</div>
|
||||
<div class="stat-desc">File CAD per cui non è stata trovata una destinazione valida.</div>
|
||||
</div>
|
||||
<div class="stat-item warn">
|
||||
<div class="stat-label">Duplicati</div>
|
||||
<div class="stat-desc">File CAD già presenti nella destinazione al momento dello smistamento.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Sotto le statistiche sono elencati i dettagli di ogni file (massimo 20 voci) con il percorso di destinazione e la motivazione, nel caso di file non smistati o duplicati.</p>
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- ── NOMI FILE ─────────────────────────── -->
|
||||
<section id="nomi">
|
||||
<h2>Convenzione nomi file CAD</h2>
|
||||
<p>CadRoute determina automaticamente la sottocartella di destinazione leggendo il nome del file. È fondamentale che i file seguano la convenzione corretta.</p>
|
||||
|
||||
<h3>Struttura del nome</h3>
|
||||
<pre class="code-block"><code>CODICE.tipo[.versione]
|
||||
|
||||
Esempi:
|
||||
ABC12345.prt → Part, nessuna versione
|
||||
ABC12345.prt.6 → Part, versione 6
|
||||
XYZ67890.asm.10 → Assembly, versione 10
|
||||
DEF11111.drw.15 → Drawing, versione 15</code></pre>
|
||||
|
||||
<h3>Gestione versioni</h3>
|
||||
<p>Se sono presenti più versioni dello stesso file nella sorgente (es. <code>ABC12345.prt.8</code> e <code>ABC12345.prt.9</code>), CadRoute mantiene <strong>solo la versione più alta</strong> e scarta le inferiori.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Scenario</th><th>Comportamento</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>File non presente in destinazione</td>
|
||||
<td>Copia diretta nella sottocartella corretta</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>File già presente in destinazione</td>
|
||||
<td>Copiato in <code>duplicati/</code> per revisione</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Versione inferiore presente nella sorgente</td>
|
||||
<td>Saltata — la versione più alta prende precedenza</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ── NON SMISTATI ──────────────────────── -->
|
||||
<section id="nonsmistati">
|
||||
<h2>File non smistati</h2>
|
||||
<p>Un file CAD finisce in <strong>non smistati</strong> quando CadRoute non riesce a determinare una destinazione valida. Le cause più comuni sono:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Causa</th><th>Messaggio</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Codice con meno di 5 cifre</td>
|
||||
<td><em>"Nome file non conforme: servono almeno 5 cifre…"</em></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sottocartella non trovata in destinazione</td>
|
||||
<td><em>"Sottocartella XXX non trovata nella destinazione"</em></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sottocartella trovata in più percorsi (ambiguo)</td>
|
||||
<td><em>"Sottocartella XXX trovata in N percorsi diversi"</em></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Dove si trovano</h3>
|
||||
<p>I file non smistati vengono copiati nella cartella <code>__NON_SMISTATI</code>:</p>
|
||||
<ul style="margin: 8px 0 12px 20px; color: var(--muted);">
|
||||
<li><strong>Linux:</strong> <code>~/.cadroute/__NON_SMISTATI/</code></li>
|
||||
<li><strong>Windows:</strong> <code>%APPDATA%\CadRoute\__NON_SMISTATI\</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Visualizzare e pulire</h3>
|
||||
<ol class="steps">
|
||||
<li><span>Clicca <strong>Anteprima non smistati</strong> per aprire la lista dei file presenti.</span></li>
|
||||
<li><span>Verifica i file, controlla il motivo dell'esclusione nell'area di output.</span></li>
|
||||
<li><span>Se vuoi svuotare la cartella, clicca <strong>Pulisci cartella</strong> (rosso) e conferma. L'operazione è irreversibile.</span></li>
|
||||
</ol>
|
||||
|
||||
<div class="callout tip">
|
||||
<strong>Suggerimento:</strong> prima di pulire, sposta manualmente i file che vuoi conservare. La pulizia elimina tutto il contenuto della cartella.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── DUPLICATI ─────────────────────────── -->
|
||||
<section id="duplicati">
|
||||
<h2>File duplicati</h2>
|
||||
<p>Un file CAD viene classificato come <strong>duplicato</strong> quando la sua chiave (<code>CODICE.tipo</code>, senza versione) è già presente nella cartella di destinazione al momento dello smistamento.</p>
|
||||
|
||||
<p>I duplicati non sovrascrivono i file già presenti in destinazione: vengono messi da parte nella cartella <code>duplicati/</code> per permettere una revisione manuale.</p>
|
||||
|
||||
<h3>Dove si trovano</h3>
|
||||
<ul style="margin: 8px 0 12px 20px; color: var(--muted);">
|
||||
<li><strong>Linux:</strong> <code>~/.cadroute/duplicati/</code></li>
|
||||
<li><strong>Windows:</strong> <code>%APPDATA%\CadRoute\duplicati\</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Gestione versioni nei duplicati</h3>
|
||||
<p>Anche all'interno della cartella <code>duplicati/</code> viene applicata la logica delle versioni: se arriva una versione più alta di un file già presente nei duplicati, la versione vecchia viene eliminata e sostituita con quella nuova.</p>
|
||||
|
||||
<h3>Visualizzare e pulire</h3>
|
||||
<ol class="steps">
|
||||
<li><span>Clicca <strong>Anteprima duplicati</strong> per aprire la lista dei file presenti.</span></li>
|
||||
<li><span>Verifica se i duplicati sono effettivamente superati o se richiedono un intervento manuale.</span></li>
|
||||
<li><span>Se vuoi svuotare la cartella, clicca <strong>Pulisci cartella</strong> (rosso) e conferma.</span></li>
|
||||
</ol>
|
||||
|
||||
<div class="callout warning">
|
||||
<strong>Attenzione:</strong> la pulizia è definitiva. Assicurati di non aver bisogno dei file prima di procedere.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Evidenzia il link attivo nella sidebar durante lo scroll
|
||||
const sections = document.querySelectorAll('section[id]');
|
||||
const links = document.querySelectorAll('nav a[href^="#"]');
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
links.forEach(l => l.classList.remove('active'));
|
||||
const active = document.querySelector(`nav a[href="#${entry.target.id}"]`);
|
||||
if (active) active.classList.add('active');
|
||||
}
|
||||
});
|
||||
}, { rootMargin: '-20% 0px -70% 0px' });
|
||||
|
||||
sections.forEach(s => observer.observe(s));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
335
renderer/docs/style.css
Normal file
335
renderer/docs/style.css
Normal file
@@ -0,0 +1,335 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--bg: #f6f8fb;
|
||||
--card: #ffffff;
|
||||
--text: #0f172a;
|
||||
--muted: #475569;
|
||||
--accent: #0b5fff;
|
||||
--accent-light: #eef3ff;
|
||||
--border: #dbe2ea;
|
||||
--sidebar-w: 220px;
|
||||
--code-bg: #f1f5f9;
|
||||
--success: #15803d;
|
||||
--warning: #b45309;
|
||||
--danger: #b91c1c;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ── SIDEBAR ─────────────────────────────────── */
|
||||
nav {
|
||||
width: var(--sidebar-w);
|
||||
min-width: var(--sidebar-w);
|
||||
background: var(--card);
|
||||
border-right: 1px solid var(--border);
|
||||
padding: 24px 0 24px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
padding: 0 18px 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.nav-brand .logo {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
.nav-brand .version {
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: block;
|
||||
padding: 7px 18px;
|
||||
text-decoration: none;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
border-left: 3px solid transparent;
|
||||
transition: color 100ms, border-color 100ms, background 100ms;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
color: var(--text);
|
||||
background: var(--accent-light);
|
||||
}
|
||||
|
||||
nav a.active {
|
||||
color: var(--accent);
|
||||
border-left-color: var(--accent);
|
||||
background: var(--accent-light);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-section-label {
|
||||
padding: 14px 18px 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.8px;
|
||||
text-transform: uppercase;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* ── MAIN CONTENT ────────────────────────────── */
|
||||
main {
|
||||
margin-left: var(--sidebar-w);
|
||||
flex: 1;
|
||||
padding: 40px 48px 80px;
|
||||
max-width: 860px;
|
||||
}
|
||||
|
||||
/* ── TYPOGRAPHY ──────────────────────────────── */
|
||||
h1 {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
margin: 48px 0 14px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
scroll-margin-top: 24px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
margin: 22px 0 8px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--muted);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
p:last-child { margin-bottom: 0; }
|
||||
|
||||
/* ── INTRO CARD ──────────────────────────────── */
|
||||
.intro-card {
|
||||
background: var(--accent-light);
|
||||
border: 1px solid #c7d9ff;
|
||||
border-radius: 12px;
|
||||
padding: 18px 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.intro-card p {
|
||||
color: #1e3a8a;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── STEP LIST ───────────────────────────────── */
|
||||
.steps {
|
||||
list-style: none;
|
||||
counter-reset: step;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.steps li {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
counter-increment: step;
|
||||
}
|
||||
|
||||
.steps li::before {
|
||||
content: counter(step);
|
||||
min-width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.steps li span {
|
||||
color: var(--muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ── CALLOUT ─────────────────────────────────── */
|
||||
.callout {
|
||||
border-radius: 10px;
|
||||
padding: 13px 16px;
|
||||
margin: 16px 0;
|
||||
font-size: 13px;
|
||||
border-left: 4px solid;
|
||||
}
|
||||
|
||||
.callout.info {
|
||||
background: #f0f9ff;
|
||||
border-color: #38bdf8;
|
||||
color: #075985;
|
||||
}
|
||||
|
||||
.callout.warning {
|
||||
background: #fffbeb;
|
||||
border-color: #f59e0b;
|
||||
color: #78350f;
|
||||
}
|
||||
|
||||
.callout.tip {
|
||||
background: #f0fdf4;
|
||||
border-color: #22c55e;
|
||||
color: #14532d;
|
||||
}
|
||||
|
||||
.callout strong { font-weight: 700; }
|
||||
|
||||
/* ── CODE & INLINE CODE ──────────────────────── */
|
||||
code {
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 1px 5px;
|
||||
font-family: 'Cascadia Code', 'Consolas', monospace;
|
||||
font-size: 12.5px;
|
||||
color: #be185d;
|
||||
}
|
||||
|
||||
pre.code-block {
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
margin: 12px 0;
|
||||
overflow-x: auto;
|
||||
font-family: 'Cascadia Code', 'Consolas', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
pre.code-block code {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
/* ── BADGE ───────────────────────────────────── */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 20px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2px;
|
||||
vertical-align: middle;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.badge.prt { background: #dbeafe; color: #1e40af; }
|
||||
.badge.asm { background: #dcfce7; color: #166534; }
|
||||
.badge.dwr { background: #fef3c7; color: #92400e; }
|
||||
.badge.skip { background: #f1f5f9; color: #475569; }
|
||||
|
||||
/* ── TABLE ───────────────────────────────────── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding: 9px 12px;
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border);
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tr:nth-child(even) td { background: #fafbfc; }
|
||||
|
||||
/* ── STAT GRID ───────────────────────────────── */
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 10px;
|
||||
margin: 14px 0;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.stat-item .stat-label {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--muted);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-item .stat-desc {
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.stat-item.ok { border-top: 3px solid #22c55e; }
|
||||
.stat-item.info { border-top: 3px solid var(--accent); }
|
||||
.stat-item.warn { border-top: 3px solid #f59e0b; }
|
||||
.stat-item.muted { border-top: 3px solid #94a3b8; }
|
||||
|
||||
/* ── SEPARATOR ───────────────────────────────── */
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
/* ── SECTION ─────────────────────────────────── */
|
||||
section {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>CAD File Router</title>
|
||||
<title>CadRoute</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light;
|
||||
@@ -255,7 +255,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<h1>Smistatore automatico</h1>
|
||||
<h1>CadRoute - Smistatore automatico</h1>
|
||||
<p>Seleziona una cartella o uno ZIP con i disegni da smistare</p>
|
||||
|
||||
<div class="actions">
|
||||
|
||||
@@ -3,7 +3,44 @@ const path = require('path');
|
||||
const { getDestinationDecision, getCadInfo } = require('./router');
|
||||
const { buildDestinationIndex } = require('./destinationIndex');
|
||||
const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex');
|
||||
const { getUnroutedTarget, getDuplicateTarget } = require('./unrouted');
|
||||
const { prepareUnroutedTarget, prepareDuplicateTarget } = require('./unrouted');
|
||||
|
||||
function parseNumericVersion(version) {
|
||||
const rawVersion = String(version || '').trim();
|
||||
if (!rawVersion || !/^\d+$/.test(rawVersion)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return BigInt(rawVersion);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function buildHighestNumericVersionByCadKey(files) {
|
||||
const index = new Map();
|
||||
|
||||
for (const row of files) {
|
||||
const cadInfo = getCadInfo(row.fileName);
|
||||
if (!cadInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const version = parseNumericVersion(cadInfo.version);
|
||||
if (version === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = toCadKey(cadInfo);
|
||||
const current = index.get(key);
|
||||
if (current === undefined || version > current) {
|
||||
index.set(key, version);
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
async function collectFilesRecursively(rootDir) {
|
||||
const files = [];
|
||||
@@ -33,6 +70,7 @@ async function processFolder(folder, config) {
|
||||
const files = await collectFilesRecursively(folder);
|
||||
const destinationIndex = await buildDestinationIndex(config?.destination);
|
||||
const existingCadKeys = await buildExistingCadKeyIndex(config?.destination);
|
||||
const sourceMaxVersions = buildHighestNumericVersionByCadKey(files);
|
||||
const result = {
|
||||
scanned: 0,
|
||||
copied: 0,
|
||||
@@ -53,7 +91,26 @@ async function processFolder(folder, config) {
|
||||
}
|
||||
|
||||
if (existingCadKeys.has(toCadKey(cadInfo))) {
|
||||
const duplicateTarget = await getDuplicateTarget(file);
|
||||
const incomingVersion = parseNumericVersion(cadInfo.version);
|
||||
const highestInSource = sourceMaxVersions.get(toCadKey(cadInfo));
|
||||
if (incomingVersion !== null && highestInSource !== undefined && incomingVersion < highestInSource) {
|
||||
result.details.push({
|
||||
file,
|
||||
reason: 'Versione piu alta presente nei file da smistare',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const duplicateTarget = await prepareDuplicateTarget(file);
|
||||
if (!duplicateTarget.shouldCopy) {
|
||||
result.details.push({
|
||||
file,
|
||||
destination: duplicateTarget.destinationDir,
|
||||
reason: duplicateTarget.reason || 'Versione piu alta gia presente',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
await fs.copy(fullPath, duplicateTarget.destinationPath, { overwrite: false });
|
||||
|
||||
result.copied += 1;
|
||||
@@ -70,7 +127,16 @@ async function processFolder(folder, config) {
|
||||
const destDir = decision.destination;
|
||||
|
||||
if (!destDir) {
|
||||
const unroutedTarget = await getUnroutedTarget(file);
|
||||
const unroutedTarget = await prepareUnroutedTarget(file);
|
||||
if (!unroutedTarget.shouldCopy) {
|
||||
result.details.push({
|
||||
file,
|
||||
destination: unroutedTarget.destinationDir,
|
||||
reason: unroutedTarget.reason || 'Versione piu alta gia presente',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
await fs.copy(fullPath, unroutedTarget.destinationPath, { overwrite: false });
|
||||
|
||||
result.copied += 1;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const { getCadInfo } = require('./router');
|
||||
|
||||
const PRIMARY_UNROUTED_DIR = '/cadroute/__NON_SMISTATI';
|
||||
const HOME_UNROUTED_DIR = path.join(os.homedir(), '.cadroute', '__NON_SMISTATI');
|
||||
@@ -83,6 +84,100 @@ async function getDuplicateTarget(fileName) {
|
||||
return getTarget('duplicates', fileName);
|
||||
}
|
||||
|
||||
function parseNumericVersion(version) {
|
||||
const rawVersion = String(version || '').trim();
|
||||
if (!rawVersion || !/^\d+$/.test(rawVersion)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return BigInt(rawVersion);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeCadKey(cadInfo) {
|
||||
return String(cadInfo?.key || '').toLowerCase();
|
||||
}
|
||||
|
||||
async function findComparableVersionFiles(destinationDir, incomingCadInfo) {
|
||||
const incomingKey = normalizeCadKey(incomingCadInfo);
|
||||
const entries = await fs.readdir(destinationDir, { withFileTypes: true }).catch(() => []);
|
||||
const comparable = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const cadInfo = getCadInfo(entry.name);
|
||||
if (!cadInfo || normalizeCadKey(cadInfo) !== incomingKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const numericVersion = parseNumericVersion(cadInfo.version);
|
||||
if (numericVersion === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
comparable.push({
|
||||
path: path.join(destinationDir, entry.name),
|
||||
version: numericVersion,
|
||||
name: entry.name,
|
||||
});
|
||||
}
|
||||
|
||||
return comparable;
|
||||
}
|
||||
|
||||
async function prepareSpecialTarget(kind, fileName) {
|
||||
const destinationDir = await resolveTargetDir(kind);
|
||||
const incomingCadInfo = getCadInfo(fileName);
|
||||
if (!incomingCadInfo) {
|
||||
const destinationPath = await getUniquePath(destinationDir, fileName);
|
||||
return { shouldCopy: true, destinationDir, destinationPath };
|
||||
}
|
||||
|
||||
const incomingVersion = parseNumericVersion(incomingCadInfo.version);
|
||||
if (incomingVersion === null) {
|
||||
const destinationPath = await getUniquePath(destinationDir, fileName);
|
||||
return { shouldCopy: true, destinationDir, destinationPath };
|
||||
}
|
||||
|
||||
const comparable = await findComparableVersionFiles(destinationDir, incomingCadInfo);
|
||||
if (!comparable.length) {
|
||||
const destinationPath = await getUniquePath(destinationDir, fileName);
|
||||
return { shouldCopy: true, destinationDir, destinationPath };
|
||||
}
|
||||
|
||||
const highest = comparable.reduce((max, row) => (row.version > max.version ? row : max));
|
||||
if (incomingVersion <= highest.version) {
|
||||
return {
|
||||
shouldCopy: false,
|
||||
destinationDir,
|
||||
reason: `Versione piu alta gia presente (${highest.name})`,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(comparable.map((row) => fs.remove(row.path)));
|
||||
const destinationPath = await getUniquePath(destinationDir, fileName);
|
||||
return {
|
||||
shouldCopy: true,
|
||||
destinationDir,
|
||||
destinationPath,
|
||||
cleaned: comparable.length,
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareUnroutedTarget(fileName) {
|
||||
return prepareSpecialTarget('unrouted', fileName);
|
||||
}
|
||||
|
||||
async function prepareDuplicateTarget(fileName) {
|
||||
return prepareSpecialTarget('duplicates', fileName);
|
||||
}
|
||||
|
||||
async function listFilesRecursively(rootDir) {
|
||||
const files = [];
|
||||
|
||||
@@ -156,6 +251,8 @@ async function clearDuplicateFiles() {
|
||||
module.exports = {
|
||||
getUnroutedTarget,
|
||||
getDuplicateTarget,
|
||||
prepareUnroutedTarget,
|
||||
prepareDuplicateTarget,
|
||||
resolveUnroutedDir,
|
||||
resolveDuplicatesDir,
|
||||
listUnroutedFiles,
|
||||
|
||||
@@ -5,12 +5,62 @@ const { pipeline } = require('stream/promises');
|
||||
const { getDestinationDecision, getCadInfo } = require('./router');
|
||||
const { buildDestinationIndex } = require('./destinationIndex');
|
||||
const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex');
|
||||
const { getUnroutedTarget, getDuplicateTarget } = require('./unrouted');
|
||||
const { prepareUnroutedTarget, prepareDuplicateTarget } = require('./unrouted');
|
||||
|
||||
function parseNumericVersion(version) {
|
||||
const rawVersion = String(version || '').trim();
|
||||
if (!rawVersion || !/^\d+$/.test(rawVersion)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return BigInt(rawVersion);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function buildZipHighestNumericVersionByCadKey(zipPath) {
|
||||
const index = new Map();
|
||||
let directory;
|
||||
|
||||
try {
|
||||
directory = await unzipper.Open.file(zipPath);
|
||||
} catch {
|
||||
return index;
|
||||
}
|
||||
|
||||
for (const row of directory.files || []) {
|
||||
if (row.type !== 'File') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const baseName = path.basename(row.path || '');
|
||||
const cadInfo = getCadInfo(baseName);
|
||||
if (!cadInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const version = parseNumericVersion(cadInfo.version);
|
||||
if (version === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = toCadKey(cadInfo);
|
||||
const current = index.get(key);
|
||||
if (current === undefined || version > current) {
|
||||
index.set(key, version);
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
async function processZip(zipPath, config) {
|
||||
const stream = fs.createReadStream(zipPath).pipe(unzipper.Parse({ forceStream: true }));
|
||||
const destinationIndex = await buildDestinationIndex(config?.destination);
|
||||
const existingCadKeys = await buildExistingCadKeyIndex(config?.destination);
|
||||
const sourceMaxVersions = await buildZipHighestNumericVersionByCadKey(zipPath);
|
||||
const result = {
|
||||
scanned: 0,
|
||||
copied: 0,
|
||||
@@ -38,7 +88,28 @@ async function processZip(zipPath, config) {
|
||||
}
|
||||
|
||||
if (existingCadKeys.has(toCadKey(cadInfo))) {
|
||||
const duplicateTarget = await getDuplicateTarget(baseName);
|
||||
const incomingVersion = parseNumericVersion(cadInfo.version);
|
||||
const highestInSource = sourceMaxVersions.get(toCadKey(cadInfo));
|
||||
if (incomingVersion !== null && highestInSource !== undefined && incomingVersion < highestInSource) {
|
||||
result.details.push({
|
||||
file: baseName,
|
||||
reason: 'Versione piu alta presente nei file da smistare',
|
||||
});
|
||||
entry.autodrain();
|
||||
continue;
|
||||
}
|
||||
|
||||
const duplicateTarget = await prepareDuplicateTarget(baseName);
|
||||
if (!duplicateTarget.shouldCopy) {
|
||||
result.details.push({
|
||||
file: baseName,
|
||||
destination: duplicateTarget.destinationDir,
|
||||
reason: duplicateTarget.reason || 'Versione piu alta gia presente',
|
||||
});
|
||||
entry.autodrain();
|
||||
continue;
|
||||
}
|
||||
|
||||
await pipeline(entry, fs.createWriteStream(duplicateTarget.destinationPath));
|
||||
|
||||
result.copied += 1;
|
||||
@@ -55,7 +126,17 @@ async function processZip(zipPath, config) {
|
||||
const destDir = decision.destination;
|
||||
|
||||
if (!destDir) {
|
||||
const unroutedTarget = await getUnroutedTarget(baseName);
|
||||
const unroutedTarget = await prepareUnroutedTarget(baseName);
|
||||
if (!unroutedTarget.shouldCopy) {
|
||||
result.details.push({
|
||||
file: baseName,
|
||||
destination: unroutedTarget.destinationDir,
|
||||
reason: unroutedTarget.reason || 'Versione piu alta gia presente',
|
||||
});
|
||||
entry.autodrain();
|
||||
continue;
|
||||
}
|
||||
|
||||
await pipeline(entry, fs.createWriteStream(unroutedTarget.destinationPath));
|
||||
|
||||
result.copied += 1;
|
||||
|
||||
14
sop.md
14
sop.md
@@ -1,4 +1,4 @@
|
||||
# SOP – CAD File Router MVP
|
||||
# SOP – CadRoute MVP
|
||||
|
||||
## Obiettivo
|
||||
Realizzare un'app desktop (.exe) che:
|
||||
@@ -35,8 +35,8 @@ npm -v
|
||||
## 2. Creazione progetto
|
||||
|
||||
```
|
||||
mkdir cad-file-router
|
||||
cd cad-file-router
|
||||
mkdir cadroute
|
||||
cd cadroute
|
||||
npm init -y
|
||||
```
|
||||
|
||||
@@ -52,7 +52,7 @@ npm install electron-builder --save-dev
|
||||
## 3. Struttura progetto
|
||||
|
||||
```
|
||||
cad-file-router
|
||||
cadroute
|
||||
│
|
||||
├── package.json
|
||||
├── main.js
|
||||
@@ -261,7 +261,7 @@ contextBridge.exposeInMainWorld("api", {
|
||||
`renderer/index.html`
|
||||
|
||||
```
|
||||
<h2>CAD File Router</h2>
|
||||
<h2>CadRoute</h2>
|
||||
|
||||
<button onclick="window.api.selectFolder()">
|
||||
Process Folder
|
||||
@@ -298,7 +298,7 @@ Aggiungere nel package.json:
|
||||
|
||||
```
|
||||
"build": {
|
||||
"appId": "com.cad.router",
|
||||
"appId": "com.cadroute",
|
||||
"win": {
|
||||
"target": "nsis"
|
||||
}
|
||||
@@ -315,7 +315,7 @@ Output:
|
||||
|
||||
```
|
||||
dist/
|
||||
cad-file-router Setup.exe
|
||||
CadRoute Setup.exe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user