Everything outside Addin funcitonnal - good generation of the yaml file

This commit is contained in:
LAHAY Damien 2025-07-08 17:41:22 +02:00
parent cb291719d7
commit 70dc28e69c
3 changed files with 265 additions and 154 deletions

View File

@ -239,6 +239,7 @@
<button class="tab-btn" data-tab="counters" id="counters-tab-btn">Counters</button>
<button class="tab-btn" data-tab="setpoints" id="setpoints-tab-btn">Setpoints</button>
<button class="tab-btn" data-tab="realtime" id="realtime-tab-btn">Real Time Variables</button>
<button class="tab-btn" data-tab="states" id="states-tab-btn">States</button>
</nav>
<form id="machine-form">
@ -515,9 +516,56 @@
</table>
<button type="button" class="add-row-btn" id="add-realtime-btn">Add Real Time Variable</button>
</fieldset>
<input type="submit" value="Validate and Submit Description">
<button type="button" class="next-tab-btn">Next</button>
</div>
<div id="states-tab" class="tab-content">
<fieldset>
<legend>States Exposed by the Machine</legend>
<p>The first four states are predefined by PackML and are mandatory. You can add more states as needed.</p>
<table class="dynamic-table">
<thead>
<tr>
<th class="col-id">ID</th>
<th class="col-name">Name</th>
<th>Description</th>
<th class="col-description">Description</th>
</tr>
</thead>
<tbody> <!-- Predefined states -->
<tr>
<td><input type="text" value="1" disabled></td>
<td><input type="text" value="StateCurrent" disabled></td>
<td><textarea rows="2" cols="50" disabled>Current state of the packML State Machine defined as an enumeration described in EOS-002-OPEN-202</textarea></td>
<td><span style="color: #999;">Predefined</span></td>
</tr>
<tr>
<td><input type="text" value="2" disabled></td>
<td><input type="text" value="ModeCurrent" disabled></td>
<td><textarea rows="2" cols="50" disabled>Current Mode of the packML State Machine defined as an enumeration described in EOS-002-OPEN-202</textarea></td>
<td><span style="color: #999;">Predefined</span></td>
</tr>
<tr>
<td><input type="text" value="3" disabled></td>
<td><input type="text" value="EquipmentInterlock : Blocked" disabled></td>
<td><textarea rows="3" cols="50" disabled>If TRUE, then processing is suspended because downstream equipment is unable to receive material (e.g. downstream buffer is full)</textarea></td>
<td><span style="color: #999;">Predefined</span></td>
</tr>
<tr>
<td><input type="text" value="4" disabled></td>
<td><input type="text" value="EquipmentInterlock : Starved" disabled></td>
<td><textarea rows="2" cols="50" disabled>If TRUE, then processing is suspended because upstream equipment is unable to send material.</textarea></td>
<td><span style="color: #999;">Predefined</span></td>
</tr>
</tbody>
<tbody id="states-table-body"></tbody>
</table>
<button type="button" id="add-state-btn" class="add-row-btn" style="width:auto;">Add States</button>
</fieldset>
<button type="button" class="next-tab-btn" style="visibility: hidden;">Next</button>
<input type="submit" value="Validate and Submit Description">
</div>
</form>
</div>
@ -572,6 +620,15 @@
</tr>
</template>
<template id="state-row-template">
<tr>
<td><input type="number" name="state_id" min="5" placeholder="e.g., 5"></td>
<td><input type="text" name="state_name" placeholder="e.g., CUSTOM_STATE"></td>
<td><textarea name="state_description" rows="1" cols="50" placeholder="Description of the state"></textarea></td>
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
</tr>
</template>
<script src="renderer.js"></script>

157
main.js
View File

@ -16,17 +16,14 @@ const createWindow = () => {
});
win.maximize();
win.loadFile('index.html');
win.webContents.openDevTools();
// win.webContents.openDevTools(); // You can comment this out for production
};
app.whenReady().then(() => {
ipcMain.handle('form:submit', (event, data) => {
console.log('--- Data received in main.js ---', data);
// --- YAML Generation by Building an Array of Lines ---
// Define spacing constants for indentation
const sp2 = ' ';
const sp4 = ' ';
@ -36,7 +33,9 @@ app.whenReady().then(() => {
// Create an array to hold each line of the YAML file
const yamlLines = [];
// Build the 'machine' section line by line
// --- Build the YAML content section by section ---
// 1. Build the 'machine' section
const machineType = data.machineType || 'Unspecified';
const machineModel = data.model || '';
const serialNo = data.serialNo || '';
@ -52,56 +51,66 @@ app.whenReady().then(() => {
yamlLines.push(`${sp4}yearofconstruction: "${data.buildYear || ''}"`);
yamlLines.push(`${sp2}version: "${data.version || 'Unset'}"`);
// 2. Build the 'alarms' section from the alarms data
// 2. Build the 'alarms' section
if (data.alarms) {
yamlLines.push(''); // Blank line for separation
yamlLines.push('');
yamlLines.push(`${sp2}alarms:`);
yamlLines.push(`${sp4}packing: OBJECTS`); // As per PowerShell script logic
yamlLines.push(`${sp4}packing: ${data.alarmPacking || 'OBJECTS'}`);
yamlLines.push(`${sp4}behavior: ${data.alarmBehavior || 'STATIC'}`);
yamlLines.push(`${sp4}count: ${data.alarmCount}`);
if (data.alarms.length > 0) {
yamlLines.push(`${sp4}alarms: `); // Note the trailing space
data.alarms.forEach(alarm => {
const id = alarm.id || 0;
const name = alarm.name || `Alarm_${id}`;
const message = alarm.message.replace(/:/g, ';'); // Sanitize message as in PowerShell
yamlLines.push(`${sp6}- ID: ${id}`);
yamlLines.push(`${sp8}name: ${name}`);
yamlLines.push(`${sp8}message: ${message}`);
});
}
}
//3. Build the 'warning' section from the warnings data
if (data.warnings && data.warnings.length > 0) {
yamlLines.push(''); // Blank line for separation
yamlLines.push(`${sp2}warnings:`);
yamlLines.push(`${sp4}count: ${data.warnings.length}`);
yamlLines.push(`${sp4}warnings: `); // Note the trailing space
data.warnings.forEach(warning => {
const id = warning.id || 0;
const name = warning.name || `Warning_${id}`;
const message = warning.message.replace(/:/g, ';'); // Sanitize message as in PowerShell
yamlLines.push(`${sp4}count: ${data.alarmCount || data.alarms.length}`);
yamlLines.push(`${sp4}alarms:`);
data.alarms.forEach(alarm => {
const id = alarm.id || 0;
const name = alarm.name || `Alarm_${id}`;
const message = (alarm.message || '').replace(/:/g, ';');
yamlLines.push(`${sp6}- ID: ${id}`);
yamlLines.push(`${sp8}name: ${name}`);
yamlLines.push(`${sp8}message: ${message}`);
});
}
//Build the 'counters' section from the counters data
if (data.counters && data.counters.length > 0) {
yamlLines.push(''); // Blank line for separation
yamlLines.push(`${sp2}counters:`);
// 3. Build the 'warnings' section
if (data.warnings && data.warnings.length > 0) {
yamlLines.push('');
yamlLines.push(`${sp2}warnings:`);
yamlLines.push(`${sp4}packing: ${data.warningPacking || 'OBJECTS'}`);
yamlLines.push(`${sp4}behavior: ${data.warningBehavior || 'STATIC'}`);
yamlLines.push(`${sp4}count: ${data.warningCount || data.warnings.length}`);
yamlLines.push(`${sp4}warnings:`);
data.warnings.forEach(warning => {
const id = warning.id || 0;
const name = warning.name || `Warning_${id}`;
const message = (warning.message || '').replace(/:/g, ';');
yamlLines.push(`${sp6}- ID: ${id}`);
yamlLines.push(`${sp8}name: ${name}`);
yamlLines.push(`${sp8}message: ${message}`);
});
}
// 4. Build the 'states' section
if (data.states && data.states.length > 0) {
yamlLines.push('');
yamlLines.push(`${sp2}states:`);
yamlLines.push(`${sp4}states:`);
data.states.forEach(state => {
const id = state.id || 0;
const name = state.name || `State_${id}`;
const description = (state.description || '').replace(/:/g, ';');
yamlLines.push(`${sp6}- ID: ${id}`);
yamlLines.push(`${sp8}name: ${name}`);
yamlLines.push(`${sp8}description: ${description}`);
});
}
// 5. Build the 'counters' section
if (data.counters && data.counters.length > 0) {
yamlLines.push('');
yamlLines.push(`${sp2}counters:`);
data.counters.forEach(counter => {
const id = counter.id || 0;
const name = counter.name || `Counter_${id}`;
const type = counter.type || '';
const description = counter.description || '';
const description = (counter.description || '').replace(/:/g, ';');
yamlLines.push(`${sp4}- name: ${name}`);
yamlLines.push(`${sp6}ID: ${id}`);
yamlLines.push(`${sp6}type: ${type}`);
@ -109,20 +118,48 @@ app.whenReady().then(() => {
});
}
// Build and Append the 'optionals' section if needed
// 6. Build the 'setpoints' section
if (data.setpoints && data.setpoints.length > 0) {
yamlLines.push('');
yamlLines.push(`${sp2}setpoints:`);
yamlLines.push(`${sp4}setpoints:`);
data.setpoints.forEach(setpoint => {
const id = setpoint.id || 0;
const name = setpoint.name || `Setpoint_${id}`;
const message = (setpoint.message || '').replace(/:/g, ';');
yamlLines.push(`${sp6}- ID: ${id}`);
yamlLines.push(`${sp8}name: ${name}`);
yamlLines.push(`${sp8}message: ${message}`);
});
}
// 7. Build the 'realtimes' section
if (data.realtime && data.realtime.length > 0) {
yamlLines.push('');
yamlLines.push(`${sp2}realtimes:`);
yamlLines.push(`${sp4}realtimes:`);
data.realtime.forEach(rt => {
const id = rt.id || 0;
const name = rt.name || `Realtime_${id}`;
const message = (rt.message || '').replace(/:/g, ';');
yamlLines.push(`${sp6}- ID: ${id}`);
yamlLines.push(`${sp8}name: ${name}`);
yamlLines.push(`${sp8}message: ${message}`);
});
}
// 8. Build the 'optionals' section
if (data.stopReason === 'Yes') {
yamlLines.push(''); // Add a blank line for spacing
yamlLines.push('');
yamlLines.push(`${sp2}optionals:`);
yamlLines.push(`${sp4}- StopReason`);
}
// Build and Append the 'addins' section if needed
const selectedAddins = data.addins || []; // Default to an empty array
// 9. Build the 'addins' section
const selectedAddins = data.addins || [];
if (selectedAddins.length > 0) {
yamlLines.push('');
yamlLines.push(`${sp2}addins:`);
if (selectedAddins.includes('AcPower')) {
yamlLines.push(`${sp4}- name: AcPower`);
}
@ -135,36 +172,19 @@ app.whenReady().then(() => {
yamlLines.push(`${sp6}# Requires data from a future 'TrackAdvance ADDIN' UI.`);
}
if (selectedAddins.includes('AGV')) {
yamlLines.push(`${sp4}- name: AGV`);
yamlLines.push(`${sp6}# Requires data from a future 'AGV ADDIN' UI.`);
yamlLines.push(`${sp4}- name: AGV`);
yamlLines.push(`${sp6}# Requires data from a future 'AGV ADDIN' UI.`);
}
}
// 4. Join all the generated lines into a single string
// --- Finalize and Save the File ---
const generatedContent = yamlLines.join('\n');
// --- End of YAML Generation Logic ---
console.log('--- Generated YAML Content ---\n', generatedContent);
// File saving logic
const nameParts = [
data.machineType,
data.vendor,
data.model,
data.serialNo,
data.browseName
];
// Filter out any empty or null values
const nameParts = [data.machineType, data.vendor, data.model, data.serialNo, data.browseName];
const nonEmptyParts = nameParts.filter(Boolean);
// Join the parts with an underscore
const baseName = nonEmptyParts.join('_');
// Sanitize the final string to remove characters invalid for filenames, but keep underscores and hyphens.
const safeBaseName = baseName.replace(/[/\\?%*:|"<>]/g, '-');
const yamlFileName = `${safeBaseName}.yml`;
const documentsPath = app.getPath('documents');
const yamlFilePath = path.join(documentsPath, yamlFileName);
@ -188,4 +208,5 @@ app.whenReady().then(() => {
createWindow();
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
});
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });

View File

@ -31,6 +31,7 @@ document.addEventListener('DOMContentLoaded', () => {
fields, // Array of field names in order (e.g. ['alarm_id', 'alarm_name', 'alarm_message'])
prefix, // e.g. 'ALARM'
nameFormat, // e.g. (id, paddedId) => `ALARM_${paddedId}`
initialRow = true, // Number of initial rows to add
}) {
const tableBody = document.getElementById(tableBodyId);
const rowTemplate = document.getElementById(templateId);
@ -38,7 +39,9 @@ document.addEventListener('DOMContentLoaded', () => {
const bulkBtn = bulkBtnId ? document.getElementById(bulkBtnId) : null;
// Add initial row
addRow(tableBody, rowTemplate, 1, fields, prefix, nameFormat);
if (initialRow) {
addRow(tableBody, rowTemplate, 1, fields, prefix, nameFormat);
}
// Bulk add
if (bulkBtn && bulkInput) {
@ -55,6 +58,16 @@ document.addEventListener('DOMContentLoaded', () => {
addRow(tableBody, rowTemplate, maxId + i, fields, prefix, nameFormat);
}
});
} else if (bulkBtn){
bulkBtn.addEventListener('click', () => {
const currentIdInputs = tableBody.querySelectorAll(`[name="${fields[0]}"]`);
let maxId = 0;
currentIdInputs.forEach(input => {
const currentId = parseInt(input.value, 10);
if (!isNaN(currentId) && currentId > maxId) maxId = currentId;
});
addRow(tableBody, rowTemplate, maxId + 1, fields, prefix, nameFormat);
});
}
// Paste from Excel
@ -147,7 +160,8 @@ document.addEventListener('DOMContentLoaded', () => {
bulkBtnId: 'add-multiple-alarms-btn',
fields: ['alarm_id', 'alarm_name', 'alarm_message'],
prefix: 'ALARM',
nameFormat: (id, paddedId) => `ALARM_${paddedId}`
nameFormat: (id, paddedId) => `ALARM_${paddedId}`,
initialRow: true
});
// --- WARNINGS ---
@ -158,7 +172,8 @@ document.addEventListener('DOMContentLoaded', () => {
bulkBtnId: 'add-multiple-warnings-btn',
fields: ['warning_id', 'warning_name', 'warning_message'],
prefix: 'WARNING',
nameFormat: (id, paddedId) => `WARNING_${paddedId}`
nameFormat: (id, paddedId) => `WARNING_${paddedId}`,
initialRow: true
});
// --- COUNTERS ---
@ -166,10 +181,11 @@ document.addEventListener('DOMContentLoaded', () => {
tableBodyId: 'counters-table-body',
templateId: 'counter-row-template',
bulkInputId: null, // Add bulk if you want
bulkBtnId: null, // Add bulk if you want
bulkBtnId: 'add-counter-btn', // Add bulk if you want
fields: ['counter_id', 'counter_name', 'counter_type', 'counter_description'],
prefix: 'COUNTER',
nameFormat: (id, paddedId) => `COUNTER_${paddedId}`
nameFormat: (id, paddedId) => `COUNTER_${paddedId}`,
initialRow: true
});
// --- SETPOINTS ---
@ -177,10 +193,11 @@ document.addEventListener('DOMContentLoaded', () => {
tableBodyId: 'setpoints-table-body',
templateId: 'setpoint-row-template',
bulkInputId: null, // Add bulk if you want
bulkBtnId: null, // Add bulk if you want
bulkBtnId: 'add-setpoint-btn', // Add bulk if you want
fields: ['setpoint_id', 'setpoint_name', 'setpoint_message'],
prefix: 'SETPOINT',
nameFormat: (id, paddedId) => `SETPOINT_${paddedId}`
nameFormat: (id, paddedId) => `SETPOINT_${paddedId}`,
initialRow: false
});
// --- REALTIME VARIABLES ---
@ -188,93 +205,108 @@ document.addEventListener('DOMContentLoaded', () => {
tableBodyId: 'realtime-table-body',
templateId: 'realtime-row-template',
bulkInputId: null, // Add bulk if you want
bulkBtnId: null, // Add bulk if you want
bulkBtnId: 'add-realtime-btn', // Add bulk if you want
fields: ['realtime_id', 'realtime_name', 'realtime_message'],
prefix: 'REALTIME',
nameFormat: (id, paddedId) => `REALTIME_${paddedId}`
nameFormat: (id, paddedId) => `REALTIME_${paddedId}`,
initialRow: false
});
// --- 3. FORM SUBMISSION LOGIC ---
const form = document.getElementById('machine-form');
if (form) {
form.addEventListener('submit', async (event) => {
event.preventDefault();
// --- STATES ---
setupDynamicTable({
tableBodyId: 'states-table-body',
templateId: 'state-row-template',
bulkInputId: null,
bulkBtnId: 'add-state-btn',
fields: ['state_id', 'state_name', 'state_description'],
prefix: 'STATE',
nameFormat: (id, paddedId) => `STATE_${paddedId}`,
initialRow: false
});
// A. Collect data from the main "Machine" tab using FormData
const formData = new FormData(form);
// Convert FormData to a plain object
const mainData = {};
for (const [key, value] of formData.entries()) {
// This check prevents multi-value fields from overwriting themselves
if (!mainData[key]) {
mainData[key] = value;
// 3. FORM SUBMISSION LOGIC (MODULARIZED)
/**
* Collects data from a dynamic table.
* @param {string} tableBodyId - The ID of the table's tbody element.
* @param {string[]} fields - An array of field names (name attributes of inputs).
* @param {string} keyField - A field name that must have a value for the row to be included.
* @returns {Object[]} An array of objects, where each object represents a row.
*/
const collectTableData = (tableBodyId, fields, keyField) => {
const tableBody = document.getElementById(tableBodyId);
if (!tableBody) return [];
const rows = tableBody.querySelectorAll('tr');
const data = [];
rows.forEach(row => {
const rowData = {};
let hasKeyField = false;
fields.forEach(field => {
const input = row.querySelector(`[name="${field}"]`);
if (input) {
rowData[field.split('_').pop()] = input.value;
if (field === keyField && input.value) {
hasKeyField = true;
}
}
});
// Only add the row if the key field is present and has a value
if (hasKeyField) {
data.push(rowData);
}
});
return data;
};
const form = document.getElementById('machine-form');
if (form) {
form.addEventListener('submit', async (event) => {
event.preventDefault();
// A. Collect data from the main "Machine" tab using FormData
const formData = new FormData(form);
const mainData = {};
for (const [key, value] of formData.entries()) {
// This check prevents multi-value fields (like addins) from being overwritten
if (!mainData[key]) {
mainData[key] = value;
}
}
}
mainData.addins = formData.getAll('addins');
// B. Collect all alarm rows from the table
const alarmRows = alarmsTableBody.querySelectorAll('tr');
const alarmsData = [];
alarmRows.forEach(row => {
const alarm = {
id: row.querySelector('[name="alarm_id"]').value,
name: row.querySelector('[name="alarm_name"]').value,
message: row.querySelector('[name="alarm_message"]').value,
mainData.addins = formData.getAll('addins');
// B. Collect data from all dynamic tables using the modular function
const alarmsData = collectTableData('alarms-table-body', ['alarm_id', 'alarm_name', 'alarm_message'], 'alarm_message');
const warningsData = collectTableData('warnings-table-body', ['warning_id', 'warning_name', 'warning_message'], 'warning_message');
const statesData = collectTableData('states-table-body', ['state_id', 'state_name', 'state_description'], 'state_description');
const countersData = collectTableData('counters-table-body', ['counter_id', 'counter_name', 'counter_type', 'counter_description'], 'counter_type');
const setpointsData = collectTableData('setpoints-table-body', ['setpoint_id', 'setpoint_name', 'setpoint_message'], 'setpoint_message');
const realtimeData = collectTableData('realtime-table-body', ['realtime_id', 'realtime_name', 'realtime_message'], 'realtime_message');
// C. Combine all data into a single object
const finalData = {
...mainData,
alarms: alarmsData,
warnings: warningsData,
states: statesData,
counters: countersData,
setpoints: setpointsData,
realtime: realtimeData,
states: statesData
};
if (alarm.message) {
alarmsData.push(alarm);
console.log('Sending all collected data to main process:', finalData);
try {
const result = await window.electronAPI.submitForm(finalData);
if (result.success) {
console.log('Form submitted successfully!');
// Optionally reset the form or give user feedback
// form.reset();
// document.getElementById('publicationDate').value = new Date().toISOString().split('T')[0];
}
} catch (error) {
console.error('Error during form submission process:', error);
}
});
}
// D. Collect all warning rows from the table
const warningRows = warningsTableBody.querySelectorAll('tr');
const warningsData = [];
warningRows.forEach(row => {
const warning = {
id: row.querySelector('[name="warning_id"]').value,
name: row.querySelector('[name="warning_name"]').value,
message: row.querySelector('[name="warning_message"]').value,
};
if (warning.message) {
warningsData.push(warning);
}
});
// E. Collect all counter rows from the table
const counterRows = countersTableBody.querySelectorAll('tr');
const countersData = [];
counterRows.forEach(row => {
const counter = {
id: row.querySelector('[name="counter_id"]').value,
name: row.querySelector('[name="counter_name"]').value,
type: row.querySelector('[name="counter_type"]').value,
description: row.querySelector('[name="counter_description"]').value,
};
if (counter.type) {
countersData.push(counter);
}
});
// C. Combine all data into a single object
const finalData = { ...mainData, alarms: alarmsData, warnings: warningsData, counters: countersData };
console.log('Sending all collected data to main process:', finalData);
try {
const result = await window.electronAPI.submitForm(finalData);
if (result.success) {
// // Reset the entire form to its initial state
// form.reset();
// // Re-apply the defaults that are not part of the standard reset
// document.getElementById('publicationDate').value = new Date().toISOString().split('T')[0];
// document.querySelector('#stopReasonYes').checked = true; // Set StopReason back to Yes
// updateFilename(); // Clear the filename preview
}
} catch (error) {
console.error('Error during form submission process:', error);
}
});
}
// --- 4. TAB NAVIGATION BUTTONS ---
const tabOrder = [
@ -283,7 +315,8 @@ document.addEventListener('DOMContentLoaded', () => {
'warnings-tab',
'counters-tab',
'setpoints-tab',
'realtime-tab'
'realtime-tab',
'states-tab'
];
// Add event listeners to all "Next" buttons