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 { processFolder } = require('./services/folderProcessor');
|
||||||
const { processZip } = require('./services/zipProcessor');
|
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() {
|
function createWindow() {
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
@@ -21,12 +31,7 @@ function createWindow() {
|
|||||||
win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initConfig() {
|
app.whenReady().then(() => {
|
||||||
config = await loadConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.whenReady().then(async () => {
|
|
||||||
await initConfig();
|
|
||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
@@ -67,6 +72,31 @@ ipcMain.handle('select-zip', async () => {
|
|||||||
return { canceled: false, ...routingResult };
|
return { canceled: false, ...routingResult };
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('get-config-path', async () => ({
|
ipcMain.handle('get-destination', async () => ({
|
||||||
configPath: loadConfig.getConfigPath(),
|
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', {
|
contextBridge.exposeInMainWorld('api', {
|
||||||
selectFolder: () => ipcRenderer.invoke('select-folder'),
|
selectFolder: () => ipcRenderer.invoke('select-folder'),
|
||||||
selectZip: () => ipcRenderer.invoke('select-zip'),
|
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;
|
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 {
|
pre {
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
@@ -98,15 +158,23 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main class="page">
|
<main class="page">
|
||||||
<h1>CAD File Router MVP</h1>
|
<h1>Smistatore automatico</h1>
|
||||||
<p>Seleziona una cartella o uno ZIP. I file .prt/.asm/.drw verranno copiati in base alle regole.</p>
|
<p>Seleziona una cartella o uno ZIP con i disegni da smistare</p>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button id="folderBtn">Process Folder</button>
|
<button id="folderBtn">Process Folder</button>
|
||||||
<button id="zipBtn" class="secondary">Process ZIP</button>
|
<button id="zipBtn" class="secondary">Process ZIP</button>
|
||||||
</div>
|
</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>
|
<pre id="output">Pronto.</pre>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
const folderBtn = document.getElementById('folderBtn');
|
const folderBtn = document.getElementById('folderBtn');
|
||||||
const zipBtn = document.getElementById('zipBtn');
|
const zipBtn = document.getElementById('zipBtn');
|
||||||
const output = document.getElementById('output');
|
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) {
|
function setLoading(isLoading) {
|
||||||
folderBtn.disabled = isLoading;
|
folderBtn.disabled = isLoading;
|
||||||
zipBtn.disabled = isLoading;
|
zipBtn.disabled = isLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setDestinationLoading(isLoading) {
|
||||||
|
destinationInput.disabled = isLoading;
|
||||||
|
browseDestinationBtn.disabled = isLoading;
|
||||||
|
saveDestinationBtn.disabled = isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
function renderResult(title, result) {
|
function renderResult(title, result) {
|
||||||
const details = (result.details || []).slice(0, 20);
|
const details = (result.details || []).slice(0, 20);
|
||||||
const detailsText = details.length
|
const detailsText = details.length
|
||||||
@@ -50,6 +59,55 @@ async function handleAction(actionName, actionFn) {
|
|||||||
folderBtn.addEventListener('click', () => handleAction('Process Folder', window.api.selectFolder));
|
folderBtn.addEventListener('click', () => handleAction('Process Folder', window.api.selectFolder));
|
||||||
zipBtn.addEventListener('click', () => handleAction('Process ZIP', window.api.selectZip));
|
zipBtn.addEventListener('click', () => handleAction('Process ZIP', window.api.selectZip));
|
||||||
|
|
||||||
window.api.getConfigPath().then(({ configPath }) => {
|
browseDestinationBtn.addEventListener('click', async () => {
|
||||||
configInfo.textContent = `Config utente: ${configPath}`;
|
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) {
|
function findDestination(filename, config) {
|
||||||
const ext = path.extname(filename).slice(1).toLowerCase();
|
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 || []) {
|
for (const rule of config.rules || []) {
|
||||||
if ((rule.ext || '').toLowerCase() !== ext) {
|
if ((rule.ext || '').toLowerCase() !== ext) {
|
||||||
@@ -23,7 +28,7 @@ function findDestination(filename, config) {
|
|||||||
|
|
||||||
function isCadFile(filename) {
|
function isCadFile(filename) {
|
||||||
const ext = path.extname(filename).slice(1).toLowerCase();
|
const ext = path.extname(filename).slice(1).toLowerCase();
|
||||||
return ['prt', 'asm', 'drw'].includes(ext);
|
return ['prt', 'asm', 'dwr'].includes(ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { findDestination, isCadFile };
|
module.exports = { findDestination, isCadFile };
|
||||||
|
|||||||
83
sop.md
83
sop.md
@@ -3,7 +3,7 @@
|
|||||||
## Obiettivo
|
## Obiettivo
|
||||||
Realizzare un'app desktop (.exe) che:
|
Realizzare un'app desktop (.exe) che:
|
||||||
- accetti una cartella o uno ZIP
|
- accetti una cartella o uno ZIP
|
||||||
- legga file CAD (.prt .asm .drw)
|
- legga file CAD (.prt .asm .dwr)
|
||||||
- analizzi nome file ed estensione
|
- analizzi nome file ed estensione
|
||||||
- applichi regole di routing
|
- applichi regole di routing
|
||||||
- copi i file verso destinazioni configurate (anche share di rete)
|
- copi i file verso destinazioni configurate (anche share di rete)
|
||||||
@@ -65,78 +65,18 @@ cad-file-router
|
|||||||
├── services
|
├── services
|
||||||
│ ├── router.js
|
│ ├── router.js
|
||||||
│ ├── zipProcessor.js
|
│ ├── zipProcessor.js
|
||||||
│ ├── folderProcessor.js
|
│ └── folderProcessor.js
|
||||||
│ └── configService.js
|
|
||||||
│
|
|
||||||
└── config
|
|
||||||
└── defaultConfig.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Configurazione iniziale
|
## 4. Configurazione runtime
|
||||||
|
|
||||||
`config/defaultConfig.json`
|
La destinazione viene gestita in memoria in `main.js` e modificata dalla UI durante l'esecuzione.
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Config Service
|
## 5. Motore di routing
|
||||||
|
|
||||||
`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
|
|
||||||
|
|
||||||
`services/router.js`
|
`services/router.js`
|
||||||
|
|
||||||
@@ -248,9 +188,13 @@ const path = require("path")
|
|||||||
|
|
||||||
const { processFolder } = require("./services/folderProcessor")
|
const { processFolder } = require("./services/folderProcessor")
|
||||||
const { processZip } = require("./services/zipProcessor")
|
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(){
|
function createWindow(){
|
||||||
|
|
||||||
@@ -380,7 +324,7 @@ dist/
|
|||||||
|
|
||||||
- l'app si avvia
|
- l'app si avvia
|
||||||
- si seleziona ZIP o cartella
|
- 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
|
- i file vengono copiati nelle destinazioni configurate
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -389,8 +333,7 @@ dist/
|
|||||||
|
|
||||||
- progress bar
|
- progress bar
|
||||||
- drag and drop ZIP
|
- drag and drop ZIP
|
||||||
- modifica regole da UI
|
- modifica regole avanzate da UI
|
||||||
- logging operazioni
|
- logging operazioni
|
||||||
- parallelismo file
|
- parallelismo file
|
||||||
- watch folder automatico
|
- watch folder automatico
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user