feat(ui): aggiunge guida utente e menu Help > Documentazione

This commit is contained in:
2026-03-16 11:58:55 +01:00
parent 105a23853a
commit 22801f6b75
4 changed files with 635 additions and 3 deletions

23
main.js
View File

@@ -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';
@@ -79,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();

View File

@@ -19,7 +19,8 @@
"preload.js",
"renderer/**/*",
"services/**/*",
"build/icon.png"
"build/icon.png",
"renderer/docs/**/*"
],
"win": {
"target": "nsis",

277
renderer/docs/index.html Normal file
View 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
View 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;
}