Compare commits
2 Commits
8e006526b3
...
cb291719d7
| Author | SHA1 | Date | |
|---|---|---|---|
| cb291719d7 | |||
| 3f6d55d36b |
190
index.html
190
index.html
@ -6,6 +6,10 @@
|
|||||||
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> -->
|
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> -->
|
||||||
<title>Machine Description Form</title>
|
<title>Machine Description Form</title>
|
||||||
<style>
|
<style>
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth; /* Enables smooth scrolling */
|
||||||
|
scroll-padding-top: 80px; /* Prevents section titles from hiding under the sticky nav */
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
background-color: #f4f4f9;
|
background-color: #f4f4f9;
|
||||||
@ -15,7 +19,7 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
max-width: 800px;
|
max-width: 1000px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
@ -91,9 +95,29 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="submit"]:hover {
|
input[type="submit"]:hover {
|
||||||
background-color: #2649B2;
|
background-color: #2649B2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.next-tab-btn {
|
||||||
|
background-color: #4A74F3;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 20px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.next-tab-btn:hover {
|
||||||
|
background-color: #2649B2;
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox-container {
|
.checkbox-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr; /* Checkbox column, then text takes remaining space */
|
grid-template-columns: auto 1fr; /* Checkbox column, then text takes remaining space */
|
||||||
@ -120,6 +144,12 @@
|
|||||||
.tab-nav {
|
.tab-nav {
|
||||||
border-bottom: 2px solid #D4D9F0;
|
border-bottom: 2px solid #D4D9F0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: white;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 2px solid #D4D9F0;
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
.tab-nav button {
|
.tab-nav button {
|
||||||
background: none;
|
background: none;
|
||||||
@ -205,6 +235,10 @@
|
|||||||
<nav class="tab-nav">
|
<nav class="tab-nav">
|
||||||
<button class="tab-btn active" data-tab="machine" id="machine-tab-btn">Machine</button>
|
<button class="tab-btn active" data-tab="machine" id="machine-tab-btn">Machine</button>
|
||||||
<button class="tab-btn" data-tab="alarms" id="alarms-tab-btn">Alarms</button>
|
<button class="tab-btn" data-tab="alarms" id="alarms-tab-btn">Alarms</button>
|
||||||
|
<button class="tab-btn" data-tab="warnings" id="warnings-tab-btn">Warnings</button>
|
||||||
|
<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>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<form id="machine-form">
|
<form id="machine-form">
|
||||||
@ -365,35 +399,181 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<button type="button" class="next-tab-btn">Next</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="alarms-tab" class="tab-content">
|
<div id="alarms-tab" class="tab-content">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Alarms Exposed by the Machine</legend>
|
<legend>Alarms Exposed by the Machine</legend>
|
||||||
<p>Define all alarms for the machine. If the message is empty, the alarm will not be generated.</p>
|
<p>Define all alarms for the machine. If the message is empty, the alarm will not be generated.</p>
|
||||||
|
<p>You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)</p>
|
||||||
<table class="dynamic-table">
|
<table class="dynamic-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Message</th><th class="col-actions">Actions</th></tr>
|
<tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Message</th><th class="col-actions">Actions</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="alarms-table-body"></tbody>
|
<tbody id="alarms-table-body"></tbody>
|
||||||
</table>
|
</table>
|
||||||
<button type="button" class="add-row-btn" id="add-alarm-btn">Add Alarm</button>
|
<input type="number" id="bulk-alarm-count" min="1" placeholder="Number of alarms to add" style="width: 180px;">
|
||||||
|
<button type="button" id="add-multiple-alarms-btn" class="add-row-btn" style="width:auto;">Add Alarms</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<button type="button" class="next-tab-btn">Next</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="warnings-tab" class="tab-content">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Warnings Exposed by the Machine</legend>
|
||||||
|
<p>Define all warnings for the machine. If the message is empty, the warning will not be generated.</p>
|
||||||
|
<p>You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)</p>
|
||||||
|
<table class="dynamic-table">
|
||||||
|
<thead>
|
||||||
|
<tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Message</th><th class="col-actions">Actions</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="warnings-table-body"></tbody>
|
||||||
|
</table>
|
||||||
|
<input type="number" id="bulk-warning-count" min="1" placeholder="Number of warnings to add">
|
||||||
|
<button type="button" id="add-multiple-warnings-btn" class="add-row-btn">Add Warnings</button>
|
||||||
|
</fieldset>
|
||||||
|
<button type="button" class="next-tab-btn">Next</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="counters-tab" class="tab-content">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Counters</legend>
|
||||||
|
<p>
|
||||||
|
Define all counters for the machine. If the description is empty, the counter will not be generated.
|
||||||
|
<br>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Note:</strong> The first three counters are predefined by the PackML standard
|
||||||
|
and are automatically included in the generated nodeset.
|
||||||
|
<br>PackML counters described by default contain only the first entry of each array.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.).
|
||||||
|
</p>
|
||||||
|
<p>You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)</p>
|
||||||
|
<table class="dynamic-table">
|
||||||
|
<thead>
|
||||||
|
<tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Type</th><th class="col-description">Description</th><th class="col-actions">Actions</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><input type="text" value="-" disabled></td>
|
||||||
|
<td><input type="text" value="ProdProcessedCount" disabled></td>
|
||||||
|
<td><input type="text" value="ProductionCounter" disabled></td>
|
||||||
|
<td><textarea rows="2" cols="30" disabled>Total products processed (PackML standard)</textarea></td>
|
||||||
|
<td><span style="color: #999;">Predefined</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type="text" value="-" disabled></td>
|
||||||
|
<td><input type="text" value="ProdDefectiveCount" disabled></td>
|
||||||
|
<td><input type="text" value="DefectiveCounter" disabled></td>
|
||||||
|
<td><textarea rows="2" cols="30" disabled>Total defective products (PackML standard)</textarea></td>
|
||||||
|
<td><span style="color: #999;">Predefined</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type="text" value="-" disabled></td>
|
||||||
|
<td><input type="text" value="CycleCount" disabled></td>
|
||||||
|
<td><input type="text" value="CycleCounter" disabled></td>
|
||||||
|
<td><textarea rows="2" cols="30" disabled>Total machine cycles (PackML standard)</textarea></td>
|
||||||
|
<td><span style="color: #999;">Predefined</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tbody id="counters-table-body"></tbody>
|
||||||
|
</table>
|
||||||
|
<button type="button" class="add-row-btn" id="add-counter-btn">Add Counter</button>
|
||||||
|
</fieldset>
|
||||||
|
<button type="button" class="next-tab-btn">Next</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="setpoints-tab" class="tab-content">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Setpoints</legend>
|
||||||
|
<p>Define all setpoints for the machine. If the description is empty, the setpoint will not be generated.</p>
|
||||||
|
<p>You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)</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-actions">Actions</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="setpoints-table-body"></tbody>
|
||||||
|
</table>
|
||||||
|
<button type="button" class="add-row-btn" id="add-setpoint-btn">Add Setpoint</button>
|
||||||
|
</fieldset>
|
||||||
|
<button type="button" class="next-tab-btn">Next</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="realtime-tab" class="tab-content">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Real Time Variables</legend>
|
||||||
|
<p>Define all real time variables for the machine. If the description is empty, the variable will not be generated.</p>
|
||||||
|
<p>You can copy and paste alarms from an Excel spreadsheet directly into the table below. (missing lines will be generated automatically)</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-actions">Actions</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="realtime-table-body"></tbody>
|
||||||
|
</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">
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="submit" value="Validate and Submit Description">
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template id="alarm-row-template">
|
<template id="alarm-row-template">
|
||||||
<tr>
|
<tr>
|
||||||
<td><input type="number" name="alarm_id" min="1" placeholder="e.g., 1"></td>
|
<td><input type="number" name="alarm_id" min="1" placeholder="e.g., 1"></td>
|
||||||
<td><input type="text" name="alarm_name" placeholder="e.g., ALARM_1"></td>
|
<td><input type="text" name="alarm_name" placeholder="e.g., ALARM_001"></td>
|
||||||
<td><textarea name="alarm_message" rows="1" cols="50" placeholder="Message displayed on dashboard"></textarea></td>
|
<td><textarea name="alarm_message" rows="1" cols="50" placeholder="Message displayed to the operator"></textarea></td>
|
||||||
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
|
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template id="warning-row-template">
|
||||||
|
<tr>
|
||||||
|
<td><input type="number" name="warning_id" min="1" placeholder="e.g., 1"></td>
|
||||||
|
<td><input type="text" name="warning_name" placeholder="e.g., WARNING_001"></td>
|
||||||
|
<td><textarea name="warning_message" rows="1" cols="50" placeholder="Message displayed to the operator"></textarea></td>
|
||||||
|
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="realtime-row-template">
|
||||||
|
<tr>
|
||||||
|
<td><input type="number" name="realtime_id" min="1" placeholder="e.g., 1"></td>
|
||||||
|
<td><input type="text" name="realtime_name" placeholder="e.g., REALTIME_001"></td>
|
||||||
|
<td><textarea name="realtime_message" rows="1" cols="50" placeholder="Message displayed to the operator"></textarea></td>
|
||||||
|
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="counter-row-template">
|
||||||
|
<tr>
|
||||||
|
<td><input type="number" name="counter_id" min="1" placeholder="e.g., 1"></td>
|
||||||
|
<td><input type="text" name="counter_name" placeholder="e.g., COUNTER_001"></td>
|
||||||
|
<td><select name="counter_type">
|
||||||
|
<option value="productionCounter">Production Counter</option>
|
||||||
|
<option value="defectiveCounter">Defective Counter</option>
|
||||||
|
<option value="cycleCounter">Cycle Counter</option>
|
||||||
|
<option value="otherCounter">Other</option>
|
||||||
|
</select></td>
|
||||||
|
<td><textarea name="counter_description" rows="1" cols="30" placeholder="Description of the counter"></textarea></td>
|
||||||
|
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="setpoint-row-template">
|
||||||
|
<tr>
|
||||||
|
<td><input type="number" name="setpoint_id" min="1" placeholder="e.g., 1"></td>
|
||||||
|
<td><input type="text" name="setpoint_name" placeholder="e.g., SETPOINT_001"></td>
|
||||||
|
<td><textarea name="setpoint_message" rows="1" cols="30" placeholder="Message displayed to the operator"></textarea></td>
|
||||||
|
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script src="renderer.js"></script>
|
<script src="renderer.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
51
main.js
51
main.js
@ -53,18 +53,37 @@ app.whenReady().then(() => {
|
|||||||
yamlLines.push(`${sp2}version: "${data.version || 'Unset'}"`);
|
yamlLines.push(`${sp2}version: "${data.version || 'Unset'}"`);
|
||||||
|
|
||||||
// 2. Build the 'alarms' section from the alarms data
|
// 2. Build the 'alarms' section from the alarms data
|
||||||
if (data.alarms && data.alarms.length > 0) {
|
if (data.alarms) {
|
||||||
yamlLines.push(''); // Blank line for separation
|
yamlLines.push(''); // Blank line for separation
|
||||||
yamlLines.push(`${sp2}alarms:`);
|
yamlLines.push(`${sp2}alarms:`);
|
||||||
yamlLines.push(`${sp4}packing: OBJECTS`); // As per PowerShell script logic
|
yamlLines.push(`${sp4}packing: OBJECTS`); // As per PowerShell script logic
|
||||||
yamlLines.push(`${sp4}behavior: ${data.alarmBehavior || 'STATIC'}`);
|
yamlLines.push(`${sp4}behavior: ${data.alarmBehavior || 'STATIC'}`);
|
||||||
yamlLines.push(`${sp4}count: ${data.alarms.length}`);
|
yamlLines.push(`${sp4}count: ${data.alarmCount}`);
|
||||||
yamlLines.push(`${sp4}alarms: `); // Note the trailing space
|
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
|
||||||
|
|
||||||
data.alarms.forEach(alarm => {
|
yamlLines.push(`${sp6}- ID: ${id}`);
|
||||||
const id = alarm.id || 0;
|
yamlLines.push(`${sp8}name: ${name}`);
|
||||||
const name = alarm.name || `Alarm_${id}`;
|
yamlLines.push(`${sp8}message: ${message}`);
|
||||||
const message = alarm.message.replace(/:/g, ';'); // Sanitize message as in PowerShell
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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(`${sp6}- ID: ${id}`);
|
yamlLines.push(`${sp6}- ID: ${id}`);
|
||||||
yamlLines.push(`${sp8}name: ${name}`);
|
yamlLines.push(`${sp8}name: ${name}`);
|
||||||
@ -72,6 +91,24 @@ app.whenReady().then(() => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//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:`);
|
||||||
|
|
||||||
|
data.counters.forEach(counter => {
|
||||||
|
const id = counter.id || 0;
|
||||||
|
const name = counter.name || `Counter_${id}`;
|
||||||
|
const type = counter.type || '';
|
||||||
|
const description = counter.description || '';
|
||||||
|
|
||||||
|
yamlLines.push(`${sp4}- name: ${name}`);
|
||||||
|
yamlLines.push(`${sp6}ID: ${id}`);
|
||||||
|
yamlLines.push(`${sp6}type: ${type}`);
|
||||||
|
yamlLines.push(`${sp6}description: ${description}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Build and Append the 'optionals' section if needed
|
// Build and Append the 'optionals' section if needed
|
||||||
if (data.stopReason === 'Yes') {
|
if (data.stopReason === 'Yes') {
|
||||||
yamlLines.push(''); // Add a blank line for spacing
|
yamlLines.push(''); // Add a blank line for spacing
|
||||||
|
|||||||
251
renderer.js
251
renderer.js
@ -1,7 +1,6 @@
|
|||||||
// renderer.js
|
// renderer.js
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
console.log('DOM fully loaded. renderer.js is now running.');
|
console.log('DOM fully loaded. renderer.js is now running.');
|
||||||
|
|
||||||
// --- 0. INITIALIZE FORM DEFAULTS ---
|
// --- 0. INITIALIZE FORM DEFAULTS ---
|
||||||
@ -10,11 +9,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
publicationDateInput.value = new Date().toISOString().split('T')[0];
|
publicationDateInput.value = new Date().toISOString().split('T')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- 1. TAB SWITCHING LOGIC ---
|
// --- 1. TAB SWITCHING LOGIC ---
|
||||||
const tabButtons = document.querySelectorAll('.tab-btn');
|
const tabButtons = document.querySelectorAll('.tab-btn');
|
||||||
const tabContents = document.querySelectorAll('.tab-content');
|
const tabContents = document.querySelectorAll('.tab-content');
|
||||||
|
|
||||||
tabButtons.forEach(button => {
|
tabButtons.forEach(button => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
tabButtons.forEach(btn => btn.classList.remove('active'));
|
tabButtons.forEach(btn => btn.classList.remove('active'));
|
||||||
@ -23,36 +20,179 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
document.getElementById(button.dataset.tab + '-tab').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');
|
document.getElementById('machine-tab').classList.add('active');
|
||||||
|
|
||||||
// --- 2. DYNAMIC ALARM ROW LOGIC ---
|
// --- 2. GENERIC DYNAMIC TABLE LOGIC ---
|
||||||
const addAlarmBtn = document.getElementById('add-alarm-btn');
|
function setupDynamicTable({
|
||||||
const alarmsTableBody = document.getElementById('alarms-table-body');
|
tableBodyId,
|
||||||
const alarmTemplate = document.getElementById('alarm-row-template');
|
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', () => {
|
// Add initial row
|
||||||
const currentIdInputs = alarmsTableBody.querySelectorAll('[name="alarm_id"]');
|
addRow(tableBody, rowTemplate, 1, fields, prefix, nameFormat);
|
||||||
let maxId = 0;
|
|
||||||
currentIdInputs.forEach(input => {
|
// Bulk add
|
||||||
const currentId = parseInt(input.value, 10);
|
if (bulkBtn && bulkInput) {
|
||||||
if (!isNaN(currentId) && currentId > maxId) {
|
bulkBtn.addEventListener('click', () => {
|
||||||
maxId = currentId;
|
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;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
const nextId = maxId + 1;
|
|
||||||
const paddedId = nextId.toString().padStart(3, '0');
|
|
||||||
|
|
||||||
const clone = alarmTemplate.content.cloneNode(true);
|
// Get clipboard data
|
||||||
clone.querySelector('[name="alarm_id"]').value = nextId;
|
const clipboardData = event.clipboardData || window.clipboardData;
|
||||||
clone.querySelector('[name="alarm_name"]').value = `ALARM_${paddedId}`;
|
const pastedData = clipboardData.getData('Text');
|
||||||
const removeBtn = clone.querySelector('.remove-row-btn');
|
if (!pastedData) return;
|
||||||
removeBtn.addEventListener('click', (e) => {
|
|
||||||
e.target.closest('tr').remove();
|
// 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
|
||||||
});
|
});
|
||||||
alarmsTableBody.appendChild(clone);
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
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}`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- 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}`
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- 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}`
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- 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}`
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- 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 ---
|
// --- 3. FORM SUBMISSION LOGIC ---
|
||||||
const form = document.getElementById('machine-form');
|
const form = document.getElementById('machine-form');
|
||||||
@ -86,8 +226,37 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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
|
// C. Combine all data into a single object
|
||||||
const finalData = { ...mainData, alarms: alarmsData };
|
const finalData = { ...mainData, alarms: alarmsData, warnings: warningsData, counters: countersData };
|
||||||
|
|
||||||
console.log('Sending all collected data to main process:', finalData);
|
console.log('Sending all collected data to main process:', finalData);
|
||||||
|
|
||||||
@ -106,4 +275,34 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 4. TAB NAVIGATION BUTTONS ---
|
||||||
|
const tabOrder = [
|
||||||
|
'machine-tab',
|
||||||
|
'alarms-tab',
|
||||||
|
'warnings-tab',
|
||||||
|
'counters-tab',
|
||||||
|
'setpoints-tab',
|
||||||
|
'realtime-tab'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add event listeners to all "Next" buttons
|
||||||
|
document.querySelectorAll('.next-tab-btn').forEach((btn, idx) => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
// 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'));
|
||||||
|
document.querySelector(`[data-tab="${nextTabId.replace('-tab','')}"]`).classList.add('active');
|
||||||
|
// Switch tab content active state
|
||||||
|
document.querySelectorAll('.tab-content').forEach(tc => tc.classList.remove('active'));
|
||||||
|
document.getElementById(nextTabId).classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Loading…
Reference in New Issue
Block a user