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...
+
+

Destinazione file CAD

+
+ + + +
+
+
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 -