feat: smistamento file non-CAD, rinomina cartelle speciali, guida utente

- Aggiunge cartella __SALTATI: i file non-CAD vengono ora copiati in
  __SALTATI invece di essere ignorati (folderProcessor, zipProcessor)
- Rinomina cartella duplicati in __DUPLICATI per coerenza con le altre
  cartelle speciali (__NON_SMISTATI, __SALTATI)
- Aggiunge pulsante "Anteprima saltati" in UI con anteprima e pulizia
- Aggiunge guida utente HTML/CSS in renderer/docs/ con sidebar navigabile
- Aggiunge menu Help > Documentazione che apre la guida in una finestra
- Imposta DEFAULT_DESTINATION a X:\
This commit is contained in:
2026-03-16 12:10:43 +01:00
parent 22801f6b75
commit 3fa7758fbe
7 changed files with 77 additions and 19 deletions

View File

@@ -8,8 +8,10 @@ const {
resolveUnroutedDir, resolveUnroutedDir,
listUnroutedFiles, listUnroutedFiles,
listDuplicateFiles, listDuplicateFiles,
listSkippedFiles,
clearUnroutedFiles, clearUnroutedFiles,
clearDuplicateFiles, clearDuplicateFiles,
clearSkippedFiles,
} = require('./services/unrouted'); } = require('./services/unrouted');
const CAD_EXTENSIONS = ['prt', 'asm', 'dwr']; const CAD_EXTENSIONS = ['prt', 'asm', 'dwr'];
@@ -209,5 +211,7 @@ ipcMain.handle('open-unrouted-folder', async () => {
ipcMain.handle('list-unrouted-files', async () => listUnroutedFiles()); ipcMain.handle('list-unrouted-files', async () => listUnroutedFiles());
ipcMain.handle('list-duplicates-files', async () => listDuplicateFiles()); ipcMain.handle('list-duplicates-files', async () => listDuplicateFiles());
ipcMain.handle('list-skipped-files', async () => listSkippedFiles());
ipcMain.handle('clear-unrouted-files', async () => clearUnroutedFiles()); ipcMain.handle('clear-unrouted-files', async () => clearUnroutedFiles());
ipcMain.handle('clear-duplicates-files', async () => clearDuplicateFiles()); ipcMain.handle('clear-duplicates-files', async () => clearDuplicateFiles());
ipcMain.handle('clear-skipped-files', async () => clearSkippedFiles());

View File

@@ -11,6 +11,8 @@ contextBridge.exposeInMainWorld('api', {
openUnroutedFolder: () => ipcRenderer.invoke('open-unrouted-folder'), openUnroutedFolder: () => ipcRenderer.invoke('open-unrouted-folder'),
listUnroutedFiles: () => ipcRenderer.invoke('list-unrouted-files'), listUnroutedFiles: () => ipcRenderer.invoke('list-unrouted-files'),
listDuplicatesFiles: () => ipcRenderer.invoke('list-duplicates-files'), listDuplicatesFiles: () => ipcRenderer.invoke('list-duplicates-files'),
listSkippedFiles: () => ipcRenderer.invoke('list-skipped-files'),
clearUnroutedFiles: () => ipcRenderer.invoke('clear-unrouted-files'), clearUnroutedFiles: () => ipcRenderer.invoke('clear-unrouted-files'),
clearDuplicatesFiles: () => ipcRenderer.invoke('clear-duplicates-files'), clearDuplicatesFiles: () => ipcRenderer.invoke('clear-duplicates-files'),
clearSkippedFiles: () => ipcRenderer.invoke('clear-skipped-files'),
}); });

View File

@@ -277,6 +277,7 @@
<div class="rule-row single-row preview-actions"> <div class="rule-row single-row preview-actions">
<button id="openUnroutedBtn" class="browse">Anteprima non smistati</button> <button id="openUnroutedBtn" class="browse">Anteprima non smistati</button>
<button id="openDuplicatesBtn" class="browse">Anteprima duplicati</button> <button id="openDuplicatesBtn" class="browse">Anteprima duplicati</button>
<button id="openSkippedBtn" class="browse">Anteprima saltati</button>
</div> </div>
<div class="rule-status" id="destinationStatus"></div> <div class="rule-status" id="destinationStatus"></div>
</section> </section>

View File

@@ -7,6 +7,7 @@ const browseDestinationBtn = document.getElementById('browseDestinationBtn');
const saveDestinationBtn = document.getElementById('saveDestinationBtn'); const saveDestinationBtn = document.getElementById('saveDestinationBtn');
const openUnroutedBtn = document.getElementById('openUnroutedBtn'); const openUnroutedBtn = document.getElementById('openUnroutedBtn');
const openDuplicatesBtn = document.getElementById('openDuplicatesBtn'); const openDuplicatesBtn = document.getElementById('openDuplicatesBtn');
const openSkippedBtn = document.getElementById('openSkippedBtn');
const destinationStatus = document.getElementById('destinationStatus'); const destinationStatus = document.getElementById('destinationStatus');
const previewOverlay = document.getElementById('previewOverlay'); const previewOverlay = document.getElementById('previewOverlay');
const closePreviewBtn = document.getElementById('closePreviewBtn'); const closePreviewBtn = document.getElementById('closePreviewBtn');
@@ -29,6 +30,7 @@ function setDestinationLoading(isLoading) {
saveDestinationBtn.disabled = isLoading; saveDestinationBtn.disabled = isLoading;
openUnroutedBtn.disabled = isLoading; openUnroutedBtn.disabled = isLoading;
openDuplicatesBtn.disabled = isLoading; openDuplicatesBtn.disabled = isLoading;
openSkippedBtn.disabled = isLoading;
clearPreviewBtn.disabled = isLoading; clearPreviewBtn.disabled = isLoading;
} }
@@ -60,18 +62,14 @@ function hidePreview() {
} }
async function loadPreviewData(kind) { async function loadPreviewData(kind) {
if (kind === 'duplicates') { if (kind === 'duplicates') return window.api.listDuplicatesFiles();
return window.api.listDuplicatesFiles(); if (kind === 'skipped') return window.api.listSkippedFiles();
}
return window.api.listUnroutedFiles(); return window.api.listUnroutedFiles();
} }
async function clearPreviewData(kind) { async function clearPreviewData(kind) {
if (kind === 'duplicates') { if (kind === 'duplicates') return window.api.clearDuplicatesFiles();
return window.api.clearDuplicatesFiles(); if (kind === 'skipped') return window.api.clearSkippedFiles();
}
return window.api.clearUnroutedFiles(); return window.api.clearUnroutedFiles();
} }
@@ -248,7 +246,22 @@ openDuplicatesBtn.addEventListener('click', async () => {
destinationStatus.textContent = 'Caricamento anteprima duplicati...'; destinationStatus.textContent = 'Caricamento anteprima duplicati...';
const result = await window.api.listDuplicatesFiles(); const result = await window.api.listDuplicatesFiles();
showPreview('duplicates', 'Anteprima duplicati', result, 'Nessun file presente in duplicati.'); showPreview('duplicates', 'Anteprima __DUPLICATI', result, 'Nessun file presente in __DUPLICATI.');
destinationStatus.textContent = `Anteprima caricata (${result.files?.length || 0} file).`;
} catch (error) {
destinationStatus.textContent = `Errore: ${error.message}`;
} finally {
setDestinationLoading(false);
}
});
openSkippedBtn.addEventListener('click', async () => {
try {
setDestinationLoading(true);
destinationStatus.textContent = 'Caricamento anteprima saltati...';
const result = await window.api.listSkippedFiles();
showPreview('skipped', 'Anteprima __SALTATI', result, 'Nessun file presente in __SALTATI.');
destinationStatus.textContent = `Anteprima caricata (${result.files?.length || 0} file).`; destinationStatus.textContent = `Anteprima caricata (${result.files?.length || 0} file).`;
} catch (error) { } catch (error) {
destinationStatus.textContent = `Errore: ${error.message}`; destinationStatus.textContent = `Errore: ${error.message}`;
@@ -263,8 +276,11 @@ clearPreviewBtn.addEventListener('click', async () => {
} }
const isDuplicates = currentPreviewKind === 'duplicates'; const isDuplicates = currentPreviewKind === 'duplicates';
const isSkipped = currentPreviewKind === 'skipped';
const confirmMessage = isDuplicates const confirmMessage = isDuplicates
? 'Confermi la pulizia della cartella duplicati?' ? 'Confermi la pulizia della cartella __DUPLICATI?'
: isSkipped
? 'Confermi la pulizia della cartella saltati?'
: 'Confermi la pulizia della cartella non smistati?'; : 'Confermi la pulizia della cartella non smistati?';
if (!window.confirm(confirmMessage)) { if (!window.confirm(confirmMessage)) {
@@ -277,9 +293,11 @@ clearPreviewBtn.addEventListener('click', async () => {
const clearResult = await clearPreviewData(currentPreviewKind); const clearResult = await clearPreviewData(currentPreviewKind);
const refreshed = await loadPreviewData(currentPreviewKind); const refreshed = await loadPreviewData(currentPreviewKind);
const title = isDuplicates ? 'Anteprima duplicati' : 'Anteprima __NON_SMISTATI'; const title = isDuplicates ? 'Anteprima __DUPLICATI' : isSkipped ? 'Anteprima __SALTATI' : 'Anteprima __NON_SMISTATI';
const emptyMessage = isDuplicates const emptyMessage = isDuplicates
? 'Nessun file presente in duplicati.' ? 'Nessun file presente in __DUPLICATI.'
: isSkipped
? 'Nessun file presente in __SALTATI.'
: 'Nessun file presente in __NON_SMISTATI.'; : 'Nessun file presente in __NON_SMISTATI.';
showPreview(currentPreviewKind, title, refreshed, emptyMessage); showPreview(currentPreviewKind, title, refreshed, emptyMessage);

View File

@@ -3,7 +3,7 @@ const path = require('path');
const { getDestinationDecision, getCadInfo } = require('./router'); const { getDestinationDecision, getCadInfo } = require('./router');
const { buildDestinationIndex } = require('./destinationIndex'); const { buildDestinationIndex } = require('./destinationIndex');
const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex'); const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex');
const { prepareUnroutedTarget, prepareDuplicateTarget } = require('./unrouted'); const { prepareUnroutedTarget, prepareDuplicateTarget, getSkippedTarget } = require('./unrouted');
function parseNumericVersion(version) { function parseNumericVersion(version) {
const rawVersion = String(version || '').trim(); const rawVersion = String(version || '').trim();
@@ -86,7 +86,10 @@ async function processFolder(folder, config) {
const cadInfo = getCadInfo(file); const cadInfo = getCadInfo(file);
if (!cadInfo) { if (!cadInfo) {
const skippedTarget = await getSkippedTarget(file);
await fs.copy(fullPath, skippedTarget.destinationPath);
result.skipped += 1; result.skipped += 1;
result.copied += 1;
continue; continue;
} }

View File

@@ -5,8 +5,10 @@ const { getCadInfo } = require('./router');
const PRIMARY_UNROUTED_DIR = '/cadroute/__NON_SMISTATI'; const PRIMARY_UNROUTED_DIR = '/cadroute/__NON_SMISTATI';
const HOME_UNROUTED_DIR = path.join(os.homedir(), '.cadroute', '__NON_SMISTATI'); const HOME_UNROUTED_DIR = path.join(os.homedir(), '.cadroute', '__NON_SMISTATI');
const PRIMARY_DUPLICATES_DIR = '/cadroute/duplicati'; const PRIMARY_DUPLICATES_DIR = '/cadroute/__DUPLICATI';
const HOME_DUPLICATES_DIR = path.join(os.homedir(), '.cadroute', 'duplicati'); const HOME_DUPLICATES_DIR = path.join(os.homedir(), '.cadroute', '__DUPLICATI');
const PRIMARY_SKIPPED_DIR = '/cadroute/__SALTATI';
const HOME_SKIPPED_DIR = path.join(os.homedir(), '.cadroute', '__SALTATI');
const SPECIAL_TARGETS = { const SPECIAL_TARGETS = {
unrouted: { unrouted: {
@@ -17,6 +19,10 @@ const SPECIAL_TARGETS = {
primary: PRIMARY_DUPLICATES_DIR, primary: PRIMARY_DUPLICATES_DIR,
fallback: HOME_DUPLICATES_DIR, fallback: HOME_DUPLICATES_DIR,
}, },
skipped: {
primary: PRIMARY_SKIPPED_DIR,
fallback: HOME_SKIPPED_DIR,
},
}; };
const resolvedDirs = new Map(); const resolvedDirs = new Map();
@@ -178,6 +184,22 @@ async function prepareDuplicateTarget(fileName) {
return prepareSpecialTarget('duplicates', fileName); return prepareSpecialTarget('duplicates', fileName);
} }
async function resolveSkippedDir() {
return resolveTargetDir('skipped');
}
async function getSkippedTarget(fileName) {
return getTarget('skipped', fileName);
}
async function listSkippedFiles() {
return listTargetFiles('skipped');
}
async function clearSkippedFiles() {
return clearTargetFiles('skipped');
}
async function listFilesRecursively(rootDir) { async function listFilesRecursively(rootDir) {
const files = []; const files = [];
@@ -251,16 +273,22 @@ async function clearDuplicateFiles() {
module.exports = { module.exports = {
getUnroutedTarget, getUnroutedTarget,
getDuplicateTarget, getDuplicateTarget,
getSkippedTarget,
prepareUnroutedTarget, prepareUnroutedTarget,
prepareDuplicateTarget, prepareDuplicateTarget,
resolveUnroutedDir, resolveUnroutedDir,
resolveDuplicatesDir, resolveDuplicatesDir,
resolveSkippedDir,
listUnroutedFiles, listUnroutedFiles,
listDuplicateFiles, listDuplicateFiles,
listSkippedFiles,
clearUnroutedFiles, clearUnroutedFiles,
clearDuplicateFiles, clearDuplicateFiles,
clearSkippedFiles,
PRIMARY_UNROUTED_DIR, PRIMARY_UNROUTED_DIR,
HOME_UNROUTED_DIR, HOME_UNROUTED_DIR,
PRIMARY_DUPLICATES_DIR, PRIMARY_DUPLICATES_DIR,
HOME_DUPLICATES_DIR, HOME_DUPLICATES_DIR,
PRIMARY_SKIPPED_DIR,
HOME_SKIPPED_DIR,
}; };

View File

@@ -5,7 +5,7 @@ const { pipeline } = require('stream/promises');
const { getDestinationDecision, getCadInfo } = require('./router'); const { getDestinationDecision, getCadInfo } = require('./router');
const { buildDestinationIndex } = require('./destinationIndex'); const { buildDestinationIndex } = require('./destinationIndex');
const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex'); const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex');
const { prepareUnroutedTarget, prepareDuplicateTarget } = require('./unrouted'); const { prepareUnroutedTarget, prepareDuplicateTarget, getSkippedTarget } = require('./unrouted');
function parseNumericVersion(version) { function parseNumericVersion(version) {
const rawVersion = String(version || '').trim(); const rawVersion = String(version || '').trim();
@@ -82,8 +82,10 @@ async function processZip(zipPath, config) {
const cadInfo = getCadInfo(baseName); const cadInfo = getCadInfo(baseName);
if (!cadInfo) { if (!cadInfo) {
const skippedTarget = await getSkippedTarget(baseName);
await pipeline(entry, fs.createWriteStream(skippedTarget.destinationPath));
result.skipped += 1; result.skipped += 1;
entry.autodrain(); result.copied += 1;
continue; continue;
} }