diff --git a/index.html b/index.html index 3231525..25650d6 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + Machine Description Form - +

Machine Description

+ + +
-
- Machine Details -
- - -
-
- - - Company name of the machine vendor. Avoid whitespaces or special characters. -
-
- - -
-
- - -
-
- - -
-
- - - This is the name of the machine which appears in the OPC UA server. Recommendation: MachineType + Numeric Suffix (e.g., Labeller1). -
-
+
+
+ Machine Details +
+ + +
+
+ + + Company name of the machine vendor. Avoid whitespaces or special characters. +
+
+ + +
+
+ + +
+
+ + +
+
+ + + This is the name of the machine which appears in the OPC UA server. Recommendation: MachineType + Numeric Suffix (e.g., Labeller1). +
+
-
- Publication & Versioning -
- - -
-
- - - Email of the person to contact about this spreadsheet. -
-
- - -
-
- - -

- The filename is automatically generated from the BrowseName above. -
-
+
+ Publication & Versioning +
+ + +
+
+ + + Email of the person to contact about this spreadsheet. +
+
+ + +
+
+ + +

+ The filename is automatically generated from the BrowseName above. +
+
-
- Alarm Configuration -
- - -
-
- - -
-
- - - Used as the size of the array for DYNAMIC alarm behavior. -
-
- - -
-
+
+ Alarm Configuration +
+ + +
+
+ + +
+
+ + + Used as the size of the array for DYNAMIC alarm behavior. +
+
+ + +
+
-
- Optional Features -
- -
- - - - +
+ Optional Features +
+ +
+ + + + +
+ Generate the special variable for the root cause of the machine stop. Boolean variable to trigger the read of properties alarmCode and alarmMessage. The StopReason is defined by PackML.
- Generate the special variable for the root cause of the machine stop. Boolean variable to trigger the read of properties alarmCode and alarmMessage. The StopReason is defined by PackML. -
-
+ -
- ADD-INs -
-
- - +
+ ADD-INs +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
-
- - -
-
- - -
-
- - -
-
-
+ +
+
+
+ Alarms Exposed by the Machine +

Define all alarms for the machine. If the message is empty, the alarm will not be generated.

+ + + + + +
IDNameMessageActions
+ +
+
+
+ + + + \ No newline at end of file diff --git a/main.js b/main.js index 8cd2dd5..b377fb1 100644 --- a/main.js +++ b/main.js @@ -29,44 +29,55 @@ app.whenReady().then(() => { const sp2 = ' '; const sp4 = ' '; const sp6 = ' '; + const sp8 = ' '; // Create an array to hold each line of the YAML file const yamlLines = []; - // 1. Build the 'machine' section line by line - yamlLines.push('machine:'); - yamlLines.push(`${sp2}profile: ${data.machineType || 'Unspecified'}`); - yamlLines.push(`${sp2}identification:`); - - // Build the 'identification' sub-section, using the [VendorName] placeholder literally + // Build the 'machine' section line by line const machineType = data.machineType || 'Unspecified'; const machineModel = data.model || ''; const serialNo = data.serialNo || ''; const VendorName = data.vendor || ''; + yamlLines.push('machine:'); + yamlLines.push(`${sp2}profile: ${machineType}`); + yamlLines.push(`${sp2}identification:`); yamlLines.push(`${sp4}namespace: http://${VendorName}/${machineType}/${machineModel}/${serialNo}`); yamlLines.push(`${sp4}browseName: ${data.browseName || ''}`); yamlLines.push(`${sp4}manufacturer: "[${VendorName}]"`); - - // Append the user-provided vendor as a comment, if it exists - if (data.vendor) { - yamlLines.push(`${sp4}# User-provided Vendor: ${data.vendor}`); - } - yamlLines.push(`${sp4}model: "${machineModel}"`); yamlLines.push(`${sp4}serialNumber: "${serialNo}"`); yamlLines.push(`${sp4}yearofconstruction: "${data.buildYear || ''}"`); - - // Add the final line for the 'machine' section yamlLines.push(`${sp2}version: "${data.version || 'Unset'}"`); - // 2. Build and Append the 'optionals' section if needed + // 2. Build the 'alarms' section from the alarms data + if (data.alarms && data.alarms.length > 0) { + yamlLines.push(''); // Blank line for separation + yamlLines.push(`${sp2}alarms:`); + yamlLines.push(`${sp4}packing: OBJECTS`); // As per PowerShell script logic + yamlLines.push(`${sp4}behavior: ${data.alarmBehavior || 'STATIC'}`); + yamlLines.push(`${sp4}count: ${data.alarms.length}`); + 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}`); + }); + } + + // Build and Append the 'optionals' section if needed if (data.stopReason === 'Yes') { yamlLines.push(''); // Add a blank line for spacing yamlLines.push(`${sp2}optionals:`); yamlLines.push(`${sp4}- StopReason`); } - // 3. Build and Append the 'addins' section if needed + // Build and Append the 'addins' section if needed const selectedAddins = data.addins || []; // Default to an empty array if (selectedAddins.length > 0) { diff --git a/renderer.js b/renderer.js index e002cde..c17ea54 100644 --- a/renderer.js +++ b/renderer.js @@ -4,59 +4,102 @@ document.addEventListener('DOMContentLoaded', () => { console.log('DOM fully loaded. renderer.js is now running.'); - // --- Real-time Filename Generation --- - const browseNameInput = document.getElementById('BrowseName'); - const filenameDisplay = document.getElementById('output-filename-display'); - - const updateFilename = () => { - const browseName = browseNameInput.value; - if (browseName) { - // Sanitize the name for the file system - const safeBrowseName = browseName.replace(/[^a-zA-Z0-9_-]/g, '_'); - filenameDisplay.textContent = `${safeBrowseName}.yml`; - } else { - filenameDisplay.textContent = ''; // Clear if BrowseName is empty - } - }; - - if (browseNameInput && filenameDisplay) { - // Listen for any input in the BrowseName field and update the display - browseNameInput.addEventListener('input', updateFilename); - // Initial call to set the name if the field is pre-filled - updateFilename(); - } - + // --- 0. INITIALIZE FORM DEFAULTS --- const publicationDateInput = document.getElementById('publicationDate'); if (publicationDateInput) { publicationDateInput.value = new Date().toISOString().split('T')[0]; } + + // --- 1. TAB SWITCHING LOGIC --- + const tabButtons = document.querySelectorAll('.tab-btn'); + const tabContents = document.querySelectorAll('.tab-content'); + + tabButtons.forEach(button => { + button.addEventListener('click', () => { + tabButtons.forEach(btn => btn.classList.remove('active')); + tabContents.forEach(content => content.classList.remove('active')); + button.classList.add('active'); + document.getElementById(button.dataset.tab + '-tab').classList.add('active'); + }); + }); + // Set the initial active tab content + document.getElementById('machine-tab').classList.add('active'); + + // --- 2. DYNAMIC ALARM ROW LOGIC --- + const addAlarmBtn = document.getElementById('add-alarm-btn'); + const alarmsTableBody = document.getElementById('alarms-table-body'); + const alarmTemplate = document.getElementById('alarm-row-template'); + + addAlarmBtn.addEventListener('click', () => { + const currentIdInputs = alarmsTableBody.querySelectorAll('[name="alarm_id"]'); + let maxId = 0; + currentIdInputs.forEach(input => { + const currentId = parseInt(input.value, 10); + if (!isNaN(currentId) && currentId > maxId) { + maxId = currentId; + } + }); + const nextId = maxId + 1; + const paddedId = nextId.toString().padStart(3, '0'); + + const clone = alarmTemplate.content.cloneNode(true); + clone.querySelector('[name="alarm_id"]').value = nextId; + clone.querySelector('[name="alarm_name"]').value = `ALARM_${paddedId}`; + const removeBtn = clone.querySelector('.remove-row-btn'); + removeBtn.addEventListener('click', (e) => { + e.target.closest('tr').remove(); + }); + alarmsTableBody.appendChild(clone); + }); + + + // --- 3. FORM SUBMISSION LOGIC --- 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); - - // The FormData API handles checkboxes and dropdowns with the same name identically. - // We just need to make sure the data object is built correctly. - const data = {}; + // 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 (!data[key]) { - data[key] = value; + if (!mainData[key]) { + mainData[key] = value; } } - // Use .getAll('addins') to get an array of all CHECKED addins - data.addins = formData.getAll('addins'); - - console.log('Collected form data:', data); + 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, + }; + if (alarm.message) { + alarmsData.push(alarm); + } + }); + // C. Combine all data into a single object + const finalData = { ...mainData, alarms: alarmsData }; + + console.log('Sending all collected data to main process:', finalData); + try { - const result = await window.electronAPI.submitForm(data); + const result = await window.electronAPI.submitForm(finalData); if (result.success) { - form.reset(); - // ... (reset logic remains the same) + // // 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);