feat: unifica lo smistamento CAD su destinazione unica configurabile da UI

This commit is contained in:
2026-03-05 16:11:01 +01:00
parent 7008f57119
commit cc38413400
8 changed files with 194 additions and 130 deletions

View File

@@ -1,22 +0,0 @@
{
"rules": [
{
"ext": "prt",
"pattern": "^1",
"destination": "./output/clienti1/PRT"
},
{
"ext": "prt",
"pattern": "^6",
"destination": "./output/clienti2/PRT"
},
{
"ext": "asm",
"destination": "./output/assembly"
},
{
"ext": "drw",
"destination": "./output/drawing"
}
]
}

50
main.js
View File

@@ -3,9 +3,19 @@ const path = require('path');
const { processFolder } = require('./services/folderProcessor');
const { processZip } = require('./services/zipProcessor');
const { loadConfig } = require('./services/configService');
let config;
const CAD_EXTENSIONS = ['prt', 'asm', 'dwr'];
const DEFAULT_DESTINATION = './output/cad';
function buildConfig(destination) {
const resolvedDestination = String(destination || '').trim() || DEFAULT_DESTINATION;
return {
destination: resolvedDestination,
rules: CAD_EXTENSIONS.map((ext) => ({ ext, destination: resolvedDestination })),
};
}
let config = buildConfig(DEFAULT_DESTINATION);
function createWindow() {
const win = new BrowserWindow({
@@ -21,12 +31,7 @@ function createWindow() {
win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
}
async function initConfig() {
config = await loadConfig();
}
app.whenReady().then(async () => {
await initConfig();
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
@@ -67,6 +72,31 @@ ipcMain.handle('select-zip', async () => {
return { canceled: false, ...routingResult };
});
ipcMain.handle('get-config-path', async () => ({
configPath: loadConfig.getConfigPath(),
ipcMain.handle('get-destination', async () => ({
destination: config.destination || DEFAULT_DESTINATION,
}));
ipcMain.handle('select-destination-folder', async () => {
const result = await dialog.showOpenDialog({
properties: ['openDirectory'],
});
if (result.canceled || !result.filePaths[0]) {
return { canceled: true };
}
return {
canceled: false,
path: result.filePaths[0],
};
});
ipcMain.handle('update-destination', async (_event, payload) => {
const destination = String(payload?.destination || '').trim();
if (!destination) {
throw new Error('La destinazione non puo essere vuota');
}
config = buildConfig(destination);
return { destination: config.destination };
});

View File

@@ -3,5 +3,7 @@ const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('api', {
selectFolder: () => ipcRenderer.invoke('select-folder'),
selectZip: () => ipcRenderer.invoke('select-zip'),
getConfigPath: () => ipcRenderer.invoke('get-config-path'),
getDestination: () => ipcRenderer.invoke('get-destination'),
selectDestinationFolder: () => ipcRenderer.invoke('select-destination-folder'),
updateDestination: (destination) => ipcRenderer.invoke('update-destination', { destination }),
});

View File

@@ -84,6 +84,66 @@
font-size: 14px;
}
.rules {
margin-top: 18px;
border: 1px solid var(--border);
border-radius: 10px;
padding: 14px;
background: #f8fafc;
}
.rules h2 {
margin: 0 0 10px;
font-size: 17px;
}
.rule-row {
display: grid;
grid-template-columns: 180px 1fr auto auto;
gap: 8px;
align-items: center;
margin-bottom: 8px;
}
.rule-row.single-row {
grid-template-columns: 1fr auto auto;
margin-top: 10px;
}
.rule-label {
font-size: 13px;
color: var(--muted);
}
.rule-row input {
min-width: 0;
padding: 9px 10px;
border: 1px solid var(--border);
border-radius: 8px;
font-size: 14px;
}
.rule-row button {
padding: 9px 12px;
border-radius: 8px;
}
.rule-row button.browse {
background: #334155;
}
.rule-status {
margin-top: 4px;
font-size: 12px;
color: var(--muted);
}
@media (max-width: 760px) {
.rule-row {
grid-template-columns: 1fr;
}
}
pre {
margin-top: 18px;
padding: 14px;
@@ -98,15 +158,23 @@
</head>
<body>
<main class="page">
<h1>CAD File Router MVP</h1>
<p>Seleziona una cartella o uno ZIP. I file .prt/.asm/.drw verranno copiati in base alle regole.</p>
<h1>Smistatore automatico</h1>
<p>Seleziona una cartella o uno ZIP con i disegni da smistare</p>
<div class="actions">
<button id="folderBtn">Process Folder</button>
<button id="zipBtn" class="secondary">Process ZIP</button>
</div>
<div class="info" id="configInfo">Caricamento configurazione...</div>
<section class="rules">
<h2>Destinazione file CAD</h2>
<div class="rule-row single-row">
<input id="destinationInput" type="text" placeholder="Percorso destinazione..." />
<button id="browseDestinationBtn" class="browse">Sfoglia</button>
<button id="saveDestinationBtn">Salva</button>
</div>
<div class="rule-status" id="destinationStatus"></div>
</section>
<pre id="output">Pronto.</pre>
</main>

View File

@@ -1,13 +1,22 @@
const folderBtn = document.getElementById('folderBtn');
const zipBtn = document.getElementById('zipBtn');
const output = document.getElementById('output');
const configInfo = document.getElementById('configInfo');
const destinationInput = document.getElementById('destinationInput');
const browseDestinationBtn = document.getElementById('browseDestinationBtn');
const saveDestinationBtn = document.getElementById('saveDestinationBtn');
const destinationStatus = document.getElementById('destinationStatus');
function setLoading(isLoading) {
folderBtn.disabled = isLoading;
zipBtn.disabled = isLoading;
}
function setDestinationLoading(isLoading) {
destinationInput.disabled = isLoading;
browseDestinationBtn.disabled = isLoading;
saveDestinationBtn.disabled = isLoading;
}
function renderResult(title, result) {
const details = (result.details || []).slice(0, 20);
const detailsText = details.length
@@ -50,6 +59,55 @@ async function handleAction(actionName, actionFn) {
folderBtn.addEventListener('click', () => handleAction('Process Folder', window.api.selectFolder));
zipBtn.addEventListener('click', () => handleAction('Process ZIP', window.api.selectZip));
window.api.getConfigPath().then(({ configPath }) => {
configInfo.textContent = `Config utente: ${configPath}`;
browseDestinationBtn.addEventListener('click', async () => {
try {
setDestinationLoading(true);
destinationStatus.textContent = 'Seleziona una cartella...';
const result = await window.api.selectDestinationFolder();
if (!result || result.canceled) {
destinationStatus.textContent = 'Selezione annullata.';
return;
}
destinationInput.value = result.path;
destinationStatus.textContent = 'Cartella selezionata. Premi Salva.';
} catch (error) {
destinationStatus.textContent = `Errore: ${error.message}`;
} finally {
setDestinationLoading(false);
}
});
saveDestinationBtn.addEventListener('click', async () => {
const destination = destinationInput.value.trim();
if (!destination) {
destinationStatus.textContent = 'Inserisci una destinazione valida.';
return;
}
try {
setDestinationLoading(true);
destinationStatus.textContent = 'Salvataggio in corso...';
const result = await window.api.updateDestination(destination);
destinationInput.value = result.destination;
destinationStatus.textContent = 'Destinazione salvata.';
} catch (error) {
destinationStatus.textContent = `Errore: ${error.message}`;
} finally {
setDestinationLoading(false);
}
});
async function initConfigUI() {
try {
const destinationResult = await window.api.getDestination();
destinationInput.value = destinationResult.destination || '';
destinationStatus.textContent = '';
} catch (error) {
destinationStatus.textContent = 'Impossibile caricare la destinazione.';
}
}
initConfigUI();

View File

@@ -1,20 +0,0 @@
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const configPath = path.join(os.homedir(), '.cad-router-config.json');
const defaultConfigPath = path.join(__dirname, '..', 'config', 'defaultConfig.json');
async function loadConfig() {
if (await fs.pathExists(configPath)) {
return fs.readJson(configPath);
}
const defaultConfig = await fs.readJson(defaultConfigPath);
await fs.writeJson(configPath, defaultConfig, { spaces: 2 });
return defaultConfig;
}
loadConfig.getConfigPath = () => configPath;
module.exports = { loadConfig };

View File

@@ -2,6 +2,11 @@ const path = require('path');
function findDestination(filename, config) {
const ext = path.extname(filename).slice(1).toLowerCase();
const globalDestination = typeof config?.destination === 'string' ? config.destination.trim() : '';
if (globalDestination && ['prt', 'asm', 'dwr'].includes(ext)) {
return globalDestination;
}
for (const rule of config.rules || []) {
if ((rule.ext || '').toLowerCase() !== ext) {
@@ -23,7 +28,7 @@ function findDestination(filename, config) {
function isCadFile(filename) {
const ext = path.extname(filename).slice(1).toLowerCase();
return ['prt', 'asm', 'drw'].includes(ext);
return ['prt', 'asm', 'dwr'].includes(ext);
}
module.exports = { findDestination, isCadFile };

83
sop.md
View File

@@ -3,7 +3,7 @@
## Obiettivo
Realizzare un'app desktop (.exe) che:
- accetti una cartella o uno ZIP
- legga file CAD (.prt .asm .drw)
- legga file CAD (.prt .asm .dwr)
- analizzi nome file ed estensione
- applichi regole di routing
- copi i file verso destinazioni configurate (anche share di rete)
@@ -65,78 +65,18 @@ cad-file-router
├── services
│ ├── router.js
│ ├── zipProcessor.js
── folderProcessor.js
│ └── configService.js
└── config
└── defaultConfig.json
── folderProcessor.js
```
---
## 4. Configurazione iniziale
## 4. Configurazione runtime
`config/defaultConfig.json`
```
{
"rules": [
{
"ext": "prt",
"pattern": "^1",
"destination": "\\\\SERVER-CAD\\clienti1\\PRT"
},
{
"ext": "prt",
"pattern": "^6",
"destination": "\\\\SERVER-CAD\\clienti2\\PRT"
},
{
"ext": "asm",
"destination": "\\\\SERVER-CAD\\assembly"
},
{
"ext": "drw",
"destination": "\\\\SERVER-CAD\\drawing"
}
]
}
```
La destinazione viene gestita in memoria in `main.js` e modificata dalla UI durante l'esecuzione.
---
## 5. Config Service
`services/configService.js`
```
const fs = require("fs-extra")
const path = require("path")
const os = require("os")
const configPath = path.join(os.homedir(), ".cad-router-config.json")
function loadConfig(){
if(fs.existsSync(configPath)){
return fs.readJsonSync(configPath)
}
const defaultConfig = fs.readJsonSync(
path.join(__dirname,"../config/defaultConfig.json")
)
fs.writeJsonSync(configPath, defaultConfig)
return defaultConfig
}
module.exports = { loadConfig }
```
---
## 6. Motore di routing
## 5. Motore di routing
`services/router.js`
@@ -248,9 +188,13 @@ const path = require("path")
const { processFolder } = require("./services/folderProcessor")
const { processZip } = require("./services/zipProcessor")
const { loadConfig } = require("./services/configService")
const CAD_EXTENSIONS = ["prt", "asm", "dwr"]
const DEFAULT_DESTINATION = "./output/cad"
let config = loadConfig()
let config = {
destination: DEFAULT_DESTINATION,
rules: CAD_EXTENSIONS.map((ext) => ({ ext, destination: DEFAULT_DESTINATION }))
}
function createWindow(){
@@ -380,7 +324,7 @@ dist/
- l'app si avvia
- si seleziona ZIP o cartella
- i file .prt .asm .drw vengono analizzati
- i file .prt .asm .dwr vengono analizzati
- i file vengono copiati nelle destinazioni configurate
---
@@ -389,8 +333,7 @@ dist/
- progress bar
- drag and drop ZIP
- modifica regole da UI
- modifica regole avanzate da UI
- logging operazioni
- parallelismo file
- watch folder automatico