From cb291719d72f11d0376b25c3f8c28086387a5b66 Mon Sep 17 00:00:00 2001 From: LAHAY Damien Date: Tue, 8 Jul 2025 14:50:43 +0200 Subject: [PATCH] Adding bulk alarm adding - Copy and paste from Excel on all tables --- index.html | 13 ++- renderer.js | 246 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 176 insertions(+), 83 deletions(-) diff --git a/index.html b/index.html index d26a5a0..cb09867 100644 --- a/index.html +++ b/index.html @@ -238,7 +238,7 @@ - +
@@ -406,13 +406,15 @@
Alarms Exposed by the Machine

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

+

You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)

IDNameMessageActions
- + +
@@ -421,13 +423,15 @@
Warnings Exposed by the Machine

Define all warnings for the machine. If the message is empty, the warning will not be generated.

+

You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)

IDNameMessageActions
- + +
@@ -447,6 +451,7 @@

You can add more counters as needed. Additional defective unit counter should be provided per type of defect (bad open flap, underfill, not printed, bad pump orientation, ...etc.) and station (head 1, nozzleX,....etc.).

+

You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)

@@ -485,6 +490,7 @@
Setpoints

Define all setpoints for the machine. If the description is empty, the setpoint will not be generated.

+

You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)

IDNameTypeDescriptionActions
@@ -500,6 +506,7 @@
Real Time Variables

Define all real time variables for the machine. If the description is empty, the variable will not be generated.

+

You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)

IDNameDescriptionActions
diff --git a/renderer.js b/renderer.js index 9c2d737..99aedcb 100644 --- a/renderer.js +++ b/renderer.js @@ -1,7 +1,6 @@ // renderer.js document.addEventListener('DOMContentLoaded', () => { - console.log('DOM fully loaded. renderer.js is now running.'); // --- 0. INITIALIZE FORM DEFAULTS --- @@ -10,11 +9,9 @@ document.addEventListener('DOMContentLoaded', () => { 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')); @@ -23,87 +20,178 @@ document.addEventListener('DOMContentLoaded', () => { 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'); + // --- 2. GENERIC DYNAMIC TABLE LOGIC --- + function setupDynamicTable({ + tableBodyId, + templateId, + bulkInputId, + bulkBtnId, + 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}` + }) { + const tableBody = document.getElementById(tableBodyId); + const rowTemplate = document.getElementById(templateId); + const bulkInput = bulkInputId ? document.getElementById(bulkInputId) : null; + const bulkBtn = bulkBtnId ? document.getElementById(bulkBtnId) : null; - 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; + // Add initial row + addRow(tableBody, rowTemplate, 1, fields, prefix, nameFormat); + + // Bulk add + if (bulkBtn && bulkInput) { + bulkBtn.addEventListener('click', () => { + let count = parseInt(bulkInput.value, 10); + if (isNaN(count) || count < 1) count = 1; + 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; + }); + for (let i = 1; i <= count; i++) { + addRow(tableBody, rowTemplate, maxId + i, fields, prefix, nameFormat); + } + }); + } + + // Paste from Excel + tableBody.addEventListener('paste', (event) => { + const targetCell = event.target.closest('td'); + const targetRow = event.target.closest('tr'); + const allRows = Array.from(tableBody.querySelectorAll('tr')); + let startRowIdx = allRows.indexOf(targetRow); + if (startRowIdx === -1) startRowIdx = allRows.length; + + // Find the cell index in the row + let startCellIdx = 0; + if (targetCell && targetRow) { + const cells = Array.from(targetRow.children); + startCellIdx = cells.indexOf(targetCell); + if (startCellIdx === -1) startCellIdx = 0; } + + // Get clipboard data + const clipboardData = event.clipboardData || window.clipboardData; + const pastedData = clipboardData.getData('Text'); + if (!pastedData) return; + + // Find current max ID for auto-increment + 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; + }); + + // Split rows by newline, columns by tab + const rows = pastedData.trim().split(/\r?\n/); + rows.forEach((row, rowOffset) => { + const cols = row.split('\t'); + if (cols.every(col => !col.trim())) return; // Ignore empty rows + + let rowToFill = allRows[startRowIdx + rowOffset]; + if (!rowToFill) { + // Add new row if needed + maxId += 1; + rowToFill = addRow(tableBody, rowTemplate, maxId, fields, prefix, nameFormat); + } + + // Get all editable fields in the row + const editableFields = fields.map(f => rowToFill.querySelector(`[name="${f}"]`)); + + // If only last field (description/message) is pasted, auto-fill ID and name + if (cols.length === 1 || (cols.length > 0 && cols.slice(0, -1).every(col => !col.trim()))) { + maxId += (!rowToFill.querySelector(`[name="${fields[0]}"]`).value ? 1 : 0); + const paddedId = maxId.toString().padStart(3, '0'); + if (editableFields[0]) editableFields[0].value = maxId; + if (editableFields[1] && nameFormat) editableFields[1].value = nameFormat(maxId, paddedId); + if (editableFields[editableFields.length - 1]) editableFields[editableFields.length - 1].value = cols[cols.length - 1].trim(); + } else { + // Paste values to the right, only filling available cells + for (let colOffset = 0; colOffset < cols.length; colOffset++) { + const fieldIdx = startCellIdx + colOffset; + if (fieldIdx < editableFields.length && editableFields[fieldIdx]) { + editableFields[fieldIdx].value = cols[colOffset].trim(); + } + } + } + }); + + event.preventDefault(); // Prevent default paste }); - 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}`; + } + + function addRow(tableBody, rowTemplate, id, fields, prefix, nameFormat) { + const paddedId = id.toString().padStart(3, '0'); + const clone = rowTemplate.content.cloneNode(true); + if (fields[0]) clone.querySelector(`[name="${fields[0]}"]`).value = id; + if (fields[1] && nameFormat) clone.querySelector(`[name="${fields[1]}"]`).value = nameFormat(id, paddedId); const removeBtn = clone.querySelector('.remove-row-btn'); - removeBtn.addEventListener('click', (e) => { - e.target.closest('tr').remove(); - }); - alarmsTableBody.appendChild(clone); + if (removeBtn) { + removeBtn.addEventListener('click', (e) => { + e.target.closest('tr').remove(); + }); + } + tableBody.appendChild(clone); + return tableBody.querySelectorAll('tr')[tableBody.querySelectorAll('tr').length - 1]; + } + + // --- ALARMS --- + setupDynamicTable({ + tableBodyId: 'alarms-table-body', + templateId: 'alarm-row-template', + bulkInputId: 'bulk-alarm-count', + bulkBtnId: 'add-multiple-alarms-btn', + fields: ['alarm_id', 'alarm_name', 'alarm_message'], + prefix: 'ALARM', + nameFormat: (id, paddedId) => `ALARM_${paddedId}` }); - const addWarningBtn = document.getElementById('add-warning-btn'); - const warningsTableBody = document.getElementById('warnings-table-body'); - const warningTemplate = document.getElementById('warning-row-template'); - - addWarningBtn.addEventListener('click', () => { - const currentIdInputs = warningsTableBody.querySelectorAll('[name="warning_id"]'); - let maxId = 0; - currentIdInputs.forEach(input => { - const currentId = parseInt(input.value, 10); - if (!isNaN(currentId) && currentId > maxId) { - maxId = currentId; - } - }); - const nextWarningId = maxId + 1; - const paddedWarningId = nextWarningId.toString().padStart(3, '0'); - - const clone = warningTemplate.content.cloneNode(true); - clone.querySelector('[name="warning_id"]').value = nextWarningId; - clone.querySelector('[name="warning_name"]').value = `WARNING_${paddedWarningId}`; - const removeBtn = clone.querySelector('.remove-row-btn'); - removeBtn.addEventListener('click', (e) => { - e.target.closest('tr').remove(); - }); - warningsTableBody.appendChild(clone); + // --- WARNINGS --- + setupDynamicTable({ + tableBodyId: 'warnings-table-body', + templateId: 'warning-row-template', + bulkInputId: 'bulk-warning-count', + bulkBtnId: 'add-multiple-warnings-btn', + fields: ['warning_id', 'warning_name', 'warning_message'], + prefix: 'WARNING', + nameFormat: (id, paddedId) => `WARNING_${paddedId}` }); - //dynamic row for the counter tab - const addCounterBtn = document.getElementById('add-counter-btn'); - const countersTableBody = document.getElementById('counters-table-body'); - const counterTemplate = document.getElementById('counter-row-template'); + // --- COUNTERS --- + setupDynamicTable({ + tableBodyId: 'counters-table-body', + templateId: 'counter-row-template', + bulkInputId: null, // Add bulk if you want + bulkBtnId: null, // Add bulk if you want + fields: ['counter_id', 'counter_name', 'counter_type', 'counter_description'], + prefix: 'COUNTER', + nameFormat: (id, paddedId) => `COUNTER_${paddedId}` + }); - addCounterBtn.addEventListener('click', () => { - const currentIdInputs = countersTableBody.querySelectorAll('[name="counter_id"]'); - let maxId = 0; - currentIdInputs.forEach(input => { - const currentId = parseInt(input.value, 10); - if (!isNaN(currentId) && currentId > maxId) { - maxId = currentId; - } - }); - const nextCounterId = maxId + 1; - const paddedCounterId = nextCounterId.toString().padStart(3, '0'); + // --- SETPOINTS --- + setupDynamicTable({ + tableBodyId: 'setpoints-table-body', + templateId: 'setpoint-row-template', + bulkInputId: null, // Add bulk if you want + bulkBtnId: null, // Add bulk if you want + fields: ['setpoint_id', 'setpoint_name', 'setpoint_message'], + prefix: 'SETPOINT', + nameFormat: (id, paddedId) => `SETPOINT_${paddedId}` + }); - const clone = counterTemplate.content.cloneNode(true); - clone.querySelector('[name="counter_id"]').value = nextCounterId; - clone.querySelector('[name="counter_name"]').value = `COUNTER_${paddedCounterId}`; - const removeBtn = clone.querySelector('.remove-row-btn'); - removeBtn.addEventListener('click', (e) => { - e.target.closest('tr').remove(); - }); - countersTableBody.appendChild(clone); + // --- REALTIME VARIABLES --- + setupDynamicTable({ + tableBodyId: 'realtime-table-body', + templateId: 'realtime-row-template', + bulkInputId: null, // Add bulk if you want + bulkBtnId: null, // Add bulk if you want + fields: ['realtime_id', 'realtime_name', 'realtime_message'], + prefix: 'REALTIME', + nameFormat: (id, paddedId) => `REALTIME_${paddedId}` }); // --- 3. FORM SUBMISSION LOGIC --- @@ -201,8 +289,12 @@ document.addEventListener('DOMContentLoaded', () => { // Add event listeners to all "Next" buttons document.querySelectorAll('.next-tab-btn').forEach((btn, idx) => { btn.addEventListener('click', () => { - // Move to the next tab - const nextTabId = tabOrder[idx + 1]; + // Find the currently active tab + const currentTab = document.querySelector('.tab-content.active'); + if (!currentTab) return; + const currentTabId = currentTab.id; + const currentIdx = tabOrder.indexOf(currentTabId); + const nextTabId = tabOrder[currentIdx + 1]; if (nextTabId) { // Switch tab button active state document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); @@ -210,12 +302,6 @@ document.addEventListener('DOMContentLoaded', () => { // Switch tab content active state document.querySelectorAll('.tab-content').forEach(tc => tc.classList.remove('active')); document.getElementById(nextTabId).classList.add('active'); - // Show submit button only on last tab - if (nextTabId === 'realtime-tab') { - document.getElementById('submit-container').style.display = ''; - } else { - document.getElementById('submit-container').style.display = 'none'; - } } }); });
IDNameDescriptionActions