added js_native/lxplan_lib_builder.html

This commit is contained in:
2026-01-27 23:08:38 +01:00
parent 7814240b22
commit 619c510443

View File

@@ -0,0 +1,448 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lxplan lib builder</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
line-height: 1.6;
}
textarea {
width: 100%;
height: 200px;
margin: 10px 0;
}
button {
padding: 8px 16px;
background-color: #007BFF;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
.test-result>span {
font-weight: bold;
padding: 5px;
}
.valid {
color: white;
background-color: green;
}
.invalid {
color: white;
background-color: red;
}
.no-answer {
color: black;
background-color: white;
}
.container {
border: 1px solid black;
border-radius: 5px;
margin: 5px;
padding: 10px;
}
</style>
</head>
<body>
<h1>Lxplan lib builder</h1>
<div class="container">
<details>
<summary>Cliquer içi pour lire la doc</summary>
<p>Pour créer une librairie, il faut : <br />
<ul>
<li>Dessiner le symbole dans lxplan</li>
<li>Le symbole doit etre sur le premier layer</li>
<li>Une fois fini, le symbole doit etre dans un groupe</li>
<li>Enregistrer le projet dans un fichier .lxxplot</li>
<li>Utiliser ce script en important le fichier lxxplot puis en remplissant les champs obligatoires et
les autres si besoin</li>
<li>Sauvegarder le fichier qui va donner un fichier .lxkey qui sera à mettre dans le dossier de la
bibliotheque (voir l'onglet "library" dans les options de lxplan)</li>
</ul>
</p>
</details>
</div>
<div class="container">
<h2>Charger le fichier lxxplot</h2>
<p>Sélectionnez un fichier llxplot sur votre appareil :</p>
<input type="file" id="fileInput" accept=".lxxplot" onclick="resetChecks()" />
</div>
<div class="container">
<h2>Tester le fichier lxxplot</h2>
<div>Le fichier est il un lxxplot ? : <span id="isLxxplot" class="test-result">...</span></div>
<div>Le fichier possede t il une shape sur le premier layer ? :<span id="hasShape"
class="test-result">...</span></div>
<div>La shape, commence t elle par un groupe ? :<span id="hasGroup" class="test-result">...</span></div>
<button onclick="loadFile()">Tester le fichier</button>
</div>
<div id="dataFields" class="container" hidden>
<h2>Données de la librairie</h2>
<div class="form-section">
<div id="mandatoryFields">
<h3>Champs obligatoires</h3>
<div>
<label for="name">Type</label>
<input type="text" id="name" name="name" required />
</div>
<div>
<label for="fname">Full Name</label>
<input type="text" id="fname" name="fname" required />
</div>
<div>
<label for="id">Id</label>
<input type="text" id="id" name="id" required />
</div>
<div>
<label for="dataType">Type d'appareil</label>
<select id="dataType">
<option value="">Sélectionner un type d'appareil</option>
</select>
</div>
</div>
<div id="optionalFields">
<h3>Champs optionnels</h3>
<details>
<summary>
Cliquer içi pour editer les champs optionnels
</summary>
<div id="dynamicFields">Selectionner d'abord un type d'appareil</div>
</details>
</div>
<button id="saveButton" onclick="buildOutput()">Créer la librairie</button>
</div>
</div>
<script>
// declaration des variables
var xmlInputContent = "";
var xmlOutputContent = "";
var shape = "";
var isLxxplot = false;
var hasShape = false;
var hasGroup = false;
var filledOptionalFields = undefined;
var filledFields = [];
var isLxxplotDiv = document.getElementById("isLxxplot");
var hasShapeDiv = document.getElementById("hasShape");
var hasGroupDiv = document.getElementById("hasGroup");
var dataFields = document.getElementById("dataFields");
var dataTypeSelect = document.getElementById('dataType');
var dynamicFields = document.getElementById('dynamicFields');
// Tableau des options de la liste de sélection
const selectOptions = [
{ id: "ers", label: "ERS", fields: "sid;have;lamp;watts;frame;beam;cd;abm;wt;note;d_mk;d_adj_x;d_adj_y;d_adj_z" },
{ id: "zers", label: "Zoom Leko / ERS", fields: "sid;have;lamp;watts;frame;bm_w;bm_t;cd_w;cd_t;abm_w;abm_t;wt;note;d_mk;d_adj_x;d_adj_y;d_adj_z" },
{ id: "fres", label: "Fresnel / PC", fields: "sid;have;lamp;watts;frame;bm_w;bm_t;cd_w;cd_t;abm_w;abm_t;wt;note;d_mk;d_adj_x;d_adj_y;d_adj_z" },
{ id: "par", label: "PAR", fields: "sid;have;lamp;watts;frame;bm_x;bm_y;cd;abm_x;abm_y;wt;note;d_mk;d_adj_x;d_adj_y;d_adj_z" },
{ id: "fl", label: "Flood", fields: "sid;have;lamp;watts;frame;beam;cd;abm;wt;note;d_mk;d_adj_x;d_adj_y;d_adj_z" },
{ id: "strip", label: "Striplight", fields: "sid;have;lamp;watts;frame;bm_x;bm_y;cd;abm_x;abm_y;wt;cpf;lpc;dbl;note;d_mk;d_adj_x;d_adj_y;d_adj_z" },
{ id: "scroll", label: "DMX Device", fields: "sid;have;note;scroll;dmode;d_mk;acc_loc" },
{ id: "mirror", label: "Mirror", fields: "sid;have;note;scroll;dmode;d_mk;acc_loc" },
{ id: "focus", label: "Focus Point", fields: "sid" },
{ id: "misc", label: "Other", fields: "sid;have;acc_loc" },
{ id: "mover", label: "Automated Fixture", fields: "sid;have;lamp;watts;frame;bm_w;bm_t;cd_w;cd_t;abm_w;abm_t;wt;note;scroll;dmode;d_mk;d_adj_x;d_adj_y;d_adj_z" },
{ id: "cmymvr", label: "Color Mixing Automated Fixture", fields: "sid;have;lamp;watts;frame;bm_w;bm_t;cd_w;cd_t;abm_w;abm_t;wt;note;scroll;dmode;mixtype;d_mk;d_adj_x;d_adj_y;d_adj_z" },
{ id: "rgb", label: "Color Mixing Rectangular Beam (obsolete rgb)", fields: "sid;have;lamp;watts;frame;bm_x;bm_y;cd;abm_x;abm_y;wt;note;scroll;dmode;mixtype;d_adj_x;d_adj_y;d_adj_z" },
{ id: "rgba", label: "Color Mixing Rectangular Beam (obsolete rgba)", fields: "sid;have;lamp;watts;frame;bm_x;bm_y;cd;abm_x;abm_y;wt;note;scroll;dmode;mixtype;d_adj_x;d_adj_y;d_adj_z" },
{ id: "led7", label: "Color Mixing Rectangular Beam", fields: "sid;have;lamp;watts;frame;bm_x;bm_y;cd;abm_x;abm_y;wt;note;scroll;dmode;mixtype;d_adj_x;d_adj_y;d_adj_z" },
{ id: "led7s", label: "Color Mixing Striplight", fields: "sid;have;lamp;watts;frame;bm_x;bm_y;cd;abm_x;abm_y;wt;cpf;lpc;dbl;note;scroll;dmode;mixtype;d_adj_x;d_adj_y;d_adj_z" },
{ id: "cmers", label: "Color Mixing Leko / ERS", fields: "sid;have;lamp;watts;frame;beam;cd;abm;wt;note;scroll;dmode;mixtype;d_mk;d_adj_x;d_adj_y;d_adj_z" },
{ id: "cmzers", label: "Color Mixing Zoom ERS", fields: "sid;have;lamp;watts;frame;bm_w;bm_t;cd_w;cd_t;abm_w;abm_t;wt;note;scroll;dmode;mixtype;d_mk;d_adj_x;d_adj_y;d_adj_z" },
{ id: "cmscroll", label: "Color Mixing DMX Device", fields: "sid;have;note;scroll;dmode;mixtype;d_mk" },
{ id: "netnode", label: "Network Device", fields: "sid;have;note;dmode" },
];
// Tableau des champs disponibles
const availableFields = [
{ id: "name", label: "Type", type: "text" },
{ id: "fname", label: "Full Name", type: "text" },
{ id: "id", label: "ID", type: "text" },
{ id: "sid", label: "Symbol ID", type: "text" },
{ id: "have", label: "Inventory", type: "number" },
{ id: "balance", label: "Balance", type: "number" },
{ id: "lamp", label: "Lamp", type: "text" },
{ id: "watts", label: "Watts", type: "number" },
{ id: "frame", label: "Color Frame", type: "text" },
{ id: "beam", label: "Field Angle", type: "number" },
{ id: "bm_x", label: "Field X", type: "number" },
{ id: "bm_y", label: "Field Y", type: "number" },
{ id: "bm_w", label: "Field Wide", type: "number" },
{ id: "bm_t", label: "Field Tight", type: "number" },
{ id: "cd", label: "Candela", type: "number" },
{ id: "cd_w", label: "Candela Wide", type: "number" },
{ id: "cd_t", label: "Candela Tight", type: "number" },
{ id: "abm", label: "Beam Angle", type: "number" },
{ id: "abm_x", label: "Beam X", type: "number" },
{ id: "abm_y", label: "Beam Y", type: "number" },
{ id: "abm_w", label: "Beam Wide", type: "number" },
{ id: "abm_t", label: "Beam Tight", type: "number" },
{ id: "wt", label: "Weight", type: "number" },
{ id: "cpf", label: "Sections Per Fixture", type: "number" },
{ id: "lpc", label: "Lamps Per Circuit", type: "number" },
{ id: "dbl", label: "Distance Between Lamps", type: "number" },
{ id: "note", label: "More Info", type: "text" },
{ id: "scroll", label: "Device Params", type: "text" },
{ id: "dmode", label: "Mode", type: "text" },
{ id: "mixtype", label: "Mix Type", type: "text" },
{ id: "d_mk", label: "Default Mark", type: "text" },
{ id: "acc_loc", label: "Placement", type: "text" },
{ id: "d_adj_x", label: "Default X Offset", type: "number" },
{ id: "d_adj_y", label: "Default Y Offset", type: "number" },
{ id: "d_adj_z", label: "Default Z Offset", type: "number" },
];
// reinitialise les resultats du test de fichier
function resetChecks() {
let content = '<span class="no-answer">Sans avis</span>';
isLxxplotDiv.innerHTML = content;
hasShapeDiv.innerHTML = content;
hasGroupDiv.innerHTML = content;
dataFields.hidden = true;
}
// action quand le document est bien chargé
document.addEventListener('DOMContentLoaded', () => {
resetChecks();
fillSelectOptions();
});
// Remplit la liste déroulante de type d'appareil
function fillSelectOptions() {
dataTypeSelect.innerHTML = '<option value="">Sélectionner un type d\'appareil</option>';
selectOptions.forEach((option) => {
const optElement = document.createElement("option");
optElement.value = option.id;
optElement.textContent = option.label;
dataTypeSelect.appendChild(optElement);
});
}
// ecoute si le type d'appareil est changé
dataTypeSelect.addEventListener("change", () => {
const selectedOptionId = dataTypeSelect.value;
displayDynamicFields(selectedOptionId);
});
// Affiche les champs optionnels en fonction du type d'appareil sélectionné
function displayDynamicFields(selectedOptionId) {
dynamicFields.innerHTML = "";// Efface les champs précédents
// Trouve l'option sélectionnée
const selectedOption = selectOptions.find((option) => option.id === selectedOptionId);
if (!selectedOption || !selectedOption.fields) return;
// Récupère les IDs des champs à afficher
const fieldIds = selectedOption.fields.split(";");
// Affiche chaque champ
fieldIds.forEach((fieldId) => {
const field = availableFields.find((f) => f.id === fieldId);
if (field) {
const fieldDiv = document.createElement("div");
fieldDiv.innerHTML = `
<label for="${field.id}">${field.label}:</label>
<input type="${field.type}" id="${field.id}" name="${field.id}" />
`;
dynamicFields.appendChild(fieldDiv);
}
});
}
// charge le fichier lxxplot
function loadFile() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) {
alert("Veuillez sélectionner un fichier lxxplot.");
return;
}
const reader = new FileReader();
reader.onload = function (e) {
xmlInputContent = e.target.result;
dataFields.hidden = !runChecks();
};
reader.readAsText(file);
}
// verifie le contenu du fichier lxxplot
function runChecks() {
isLxxplot = checkIfValidPath("/lxplot", 'isLxxplot');
hasShape = checkIfValidPath("/lxplot/layers/layer/shape", 'hasShape');
if (hasShape) {
getShape();
}
hasGroup = checkIfValidPath("/lxplot/layers/layer/shape/class[text() = 'LXGroup']", 'hasGroup');
return (isLxxplot && hasShape && hasGroup);
}
// petit utilitaire pour tester le contenu du fichier lxxplot
function checkIfValidPath(xpathQuery, resultDiv) {
resultDiv = document.getElementById(resultDiv);
let status = parseXml(xpathQuery).status;
resultDiv.innerHTML = status ? '<span class="valid">oui</span>' : '<span class="invalid">non</span>';
return status;
}
// petit utilitaire permettant de tester le xml du fichier lxxplot
function parseXml(xpathQuery) {
try {
// Parser le XML
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlInputContent, "text/xml");
// Exécuter la requête XPath
const xpathResult = document.evaluate(
xpathQuery,
xmlDoc,
null,
XPathResult.ANY_TYPE,
null
);
let result = [];
let node = xpathResult.iterateNext();
while (node) {
result.push(node.textContent);
node = xpathResult.iterateNext();
}
// Afficher le résultat
if (result.length > 0) {
return { "status": true, "data": result };
} else {
return { "status": false, "data": "Not found" };
}
} catch (e) {
console.error("error parsing xml", e.message);
return { "status": false, "data": "Error : " + e.message };
}
}
// reuperer la shape depuis le fichier lxxplot
function getShape() {
try {
// Parser le XML
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlInputContent, "text/xml");
// Exécuter la requête XPath
const xpathResult = document.evaluate(
"/lxplot/layers/layer/shape",
xmlDoc,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
);
shape = xpathResult.singleNodeValue;
} catch (e) {
console.error("error parsing xml", e.message);
return { "status": false, "data": "Error : " + e.message };
}
}
// action quand on appuie sur le bouton "creer la librairie"
function buildOutput() {
if (checkFilledFields()) {
buildXmlOutput();
}
}
// verifier que les champs soient bien remplis
function checkFilledFields() {
filledFields = [];
getFilledChildren('mandatoryFields');
if (Object.keys(filledFields).length < 3 || dataTypeSelect.value === '') {
alert("Les 4 champs obligatoires doivent être remplis.");
return false;
}
getFilledChildren('dynamicFields');
return true;
}
function getFilledChildren(id) {
// recupere les champs
const divElement = document.getElementById(id);
const inputNodes = divElement.getElementsByTagName('input');
// teste les champs un par un
for (let i = 0; i < inputNodes.length; i++) {
if (inputNodes[i].value.trim() !== '') {
filledFields.push({ "id": inputNodes[i].id, "value": inputNodes[i].value });
}
}
}
// fabrique le fichier de sortie et l'envoie a l'utilisateur
function buildXmlOutput() {
// creation du document xml
const xmlDoc = new DOMParser().parseFromString('<key></key>', 'application/xml');
const root = xmlDoc.documentElement;
// creation des elements basiques
const kentryElement = xmlDoc.createElement('kentry');
const customElement = xmlDoc.createElement("custom");
const symbolElement = xmlDoc.createElement("symbol");
const groupElement = xmlDoc.createElement("group");
const kindElement = xmlDoc.createElement("kind");
kindElement.textContent = dataTypeSelect.value;
// ajout des elements basiques dans l'arbre xml
groupElement.appendChild(shape);
symbolElement.appendChild(groupElement);
customElement.appendChild(symbolElement);
kentryElement.appendChild(kindElement);
// ajout de chaque champ rempli
filledFields.forEach((item) => {
const newItem = xmlDoc.createElement(item.id);
newItem.textContent = item.value;
kentryElement.appendChild(newItem);
});
kentryElement.appendChild(customElement);
root.appendChild(kentryElement);
// conversion en chaine de characteres
const xmlString = "<?xml version='1.0' encoding='utf-8'?>\n" + new XMLSerializer().serializeToString(xmlDoc);
console.log(xmlString);
// creation fichier
const blob = new Blob([xmlString], { type: 'application/xml;charset=utf-8' });
const url = URL.createObjectURL(blob);
// envoi du fichier
const a = document.createElement('a');
a.href = url;
a.download = 'mylib.lxkey';
a.click();
// nettoyage
URL.revokeObjectURL(url);
}
</script>
</body>
</html>