diff --git a/config/defaultConfig.json b/config/defaultConfig.json
deleted file mode 100644
index 897bdcc..0000000
--- a/config/defaultConfig.json
+++ /dev/null
@@ -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"
- }
- ]
-}
diff --git a/main.js b/main.js
index 26b08af..f800e74 100644
--- a/main.js
+++ b/main.js
@@ -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 };
+});
diff --git a/preload.js b/preload.js
index 826b5c8..1d93772 100644
--- a/preload.js
+++ b/preload.js
@@ -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 }),
});
diff --git a/renderer/index.html b/renderer/index.html
index 445ae10..1911c17 100644
--- a/renderer/index.html
+++ b/renderer/index.html
@@ -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 @@
- CAD File Router MVP
- Seleziona una cartella o uno ZIP. I file .prt/.asm/.drw verranno copiati in base alle regole.
+ Smistatore automatico
+ Seleziona una cartella o uno ZIP con i disegni da smistare
- Caricamento configurazione...
+
Pronto.
diff --git a/renderer/renderer.js b/renderer/renderer.js
index 887959e..c02d41b 100644
--- a/renderer/renderer.js
+++ b/renderer/renderer.js
@@ -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();
diff --git a/services/configService.js b/services/configService.js
deleted file mode 100644
index b8c0ef4..0000000
--- a/services/configService.js
+++ /dev/null
@@ -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 };
diff --git a/services/router.js b/services/router.js
index 1b33818..5370a20 100644
--- a/services/router.js
+++ b/services/router.js
@@ -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 };
diff --git a/sop.md b/sop.md
index 8e52eeb..d1c8084 100644
--- a/sop.md
+++ b/sop.md
@@ -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
-