feat: unifica lo smistamento CAD su destinazione unica configurabile da UI
This commit is contained in:
@@ -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
50
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 };
|
||||
});
|
||||
|
||||
@@ -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 }),
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 };
|
||||
@@ -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
83
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user