first commit

This commit is contained in:
2026-02-17 12:03:32 +01:00
parent 5eda8ce45d
commit 1509ca8e5e
14 changed files with 68891 additions and 0 deletions

View File

@@ -1,2 +1,4 @@
# lxplan_lib_creator
## Comment installer
Telecharger le code puis ouvrir le fichier "index.html" avec un navigateur web (pas besoin de serveur web).

File diff suppressed because it is too large Load Diff

1478
examples/mylib.lxkey Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

11056
examples/test-symbol.lxxplot Normal file

File diff suppressed because it is too large Load Diff

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

102
index.html Normal file
View File

@@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LxPlan keyGen</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<nav class="tab-nav-container" id="tab-nav">
<div class="buttons-set">
<button class="tab" data-tab="home">Home</button>
<button class="tab" data-tab="makeKey">Clef</button>
<button class="tab active" data-tab="makeKeySet">Trousseau</button>
<button class="tab" id="generateKeyFile" onclick="generateKeyFile()" hidden>Générer le fichier</button>
</div>
</nav>
<main class="tab-content">
<section id="home" class="tab-pane">
<h1>LxPlan KeyGen</h1>
<div class="container">
<h2>Doc</h2>
<details>
<summary>Cliquer içi pour voir le topo</summary>
<p>LxPlan utilise des fichiers de librairies au format texte xml.<br />On peut l'éditer avec un editeur de
texte.<br />Dans ce fichier, chaque bécane s'appelle une "kentry" qui est dans l'ensemble principal qui
s'appelle key.<br />Les 2 fichiers que génere ce script sont les memes sauf que l'un part d'un fichier
*.lxxplot pour faire une seule lib et le deuxieme rassemble plusieurs libs en une seule.</p>
</details>
</div>
<div class="container">
<h2>Clef</h2>
<details>
<summary>Cliquer içi pour voir comment faire une lib</summary>
<p>Pour créer une lib, il faut :
<ul>
<li>Dessiner le symbole dans lxplan ou poser un symbole existant</li>
<li>Le symbole doit etre sur le premier layer</li>
<li>Une fois fini, le symbole doit etre mis 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>Quand tout est valide, un bouton rouge "Générer le fichier" apparait en haut à droite.</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>Note: si il y a des symboles déjà existants, les originaux seront automatiquement ajoutés dans la lib. Par
exemple : si on fait une barre de pars, un par solo original sera ajouté à la fin de la lib.</p>
</p>
</details>
</div>
<div class="container">
<h2>Trousseau</h2>
<details>
<summary>Cliquer içi pour voir comment faire un trousseau de clefs</summary>
<p>Pour créer un trousseau, il faut :
<ul>
<li>Charger plusieurs fichiers de clefs.</li>
<li>Le script vérifie si les fichiers ont l'air crédibles.</li>
<li>On peut choisir les clefs à garder ou pas.</li>
<li>Quand tout est valide, un bouton rouge "Générer le fichier" apparait en haut à droite.</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>
</section>
<section id="makeKey" class="tab-pane">
<div id="makeKeyLogMessage"></div>
<div class="container">
<h2>Charger les fichiers lxxplot</h2>
<p>Sélectionnez un ou plusieurs fichiers llxplot sur votre appareil :</p>
<input type="file" multiple id="plotFilesInput" accept=".lxxplot" onclick="resetMakeKeyChecks()" />
</div>
<div class="container">
<div id="plotFilesList">
<p>... En attente</p>
</div>
</div>
</section>
<section id="makeKeySet" class="tab-pane active">
<div id="makeKeySetLogMessages"></div>
<div class="container">
<h2>Charger les fichiers lxkey</h2>
<p>Sélectionnez plusieurs fichiers lxkey sur votre appareil :</p>
<input type="file" multiple id="keyFilesInput" accept=".lxkey" onclick="resetMakeKeySetChecks()" />
</div>
<div class="container">
<div id="keyFilesList">
<p>... En attente</p>
</div>
</div>
<div id="filesList"></div>
</section>
</main>
<script src="script.js"></script>
</body>
</html>

32
notes.md Normal file
View File

@@ -0,0 +1,32 @@
<code js>
resultsDiv.innerHTML += `<p><strong>${file.name}</strong> - Failed to read ❌</p>`;
</code>
<code js>
Object.keys(file.fields.mandatory).length
</code>
<code js>
Object.entries(file.fields.mandatory).forEach(([key, value]) => {
</code>
<code js>
Array.from(files).map(file => {
</code>
<code js>
document.querySelectorAll('input[id^="mandatoryFields-"]').forEach(input => {
</code>
<code js>
for (let shape of shapes) {
</code>
<code js>
for (const [cle, valeur] of Object.entries(addedKeys)) { console.log("key", cle);console.log("valeur", valeur)}
</code>
length of an associative array
<code js>
console.log(Reflect.ownKeys(addedKeys).length);
</code>

721
script.js Normal file
View File

@@ -0,0 +1,721 @@
/*
* Variables
*/
var inputFiles = [];
var isValidFileCount = 0;
var displayKeyFields = false;
var filledFields = 0;
var canGenerateKeyFile = false;
var addedKeys = [];
var selectedKeys = [];
var customSymbolsXml = '';
const makeKeyLogMessageDiv = document.getElementById('makeKeyLogMessage');
const makeKeySetLogMessagesDiv = document.getElementById('makeKeySetLogMessages');
const plotFilesListDiv = document.getElementById("plotFilesList");
const keyFilesListDiv = document.getElementById("keyFilesList");
const plotFilesInput = document.getElementById('plotFilesInput');
const keyFilesInput = document.getElementById('keyFilesInput');
const generateKeyFileButton = document.getElementById('generateKeyFile');
// 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", help: "Nom court" },
{ id: "fname", label: "Full Name", type: "text", help: "Nom complet" },
{ id: "id", label: "ID", type: "text", help: "" },
{ id: "sid", label: "Symbol ID", type: "text", help: "" },
{ id: "have", label: "Inventory", type: "number", help: "Quantités en stock" },
{ id: "balance", label: "Balance", type: "number", help: "Poids" },
{ id: "lamp", label: "Lamp", type: "text", help: "cp70 ? cp90 ?" },
{ id: "watts", label: "Watts", type: "number", help: "" },
{ id: "frame", label: "Color Frame", type: "text", help: "" },
{ id: "beam", label: "Field Angle", type: "number", help: "" },
{ id: "bm_x", label: "Field X", type: "number", help: "" },
{ id: "bm_y", label: "Field Y", type: "number", help: "" },
{ id: "bm_w", label: "Field Wide", type: "number", help: "" },
{ id: "bm_t", label: "Field Tight", type: "number", help: "" },
{ id: "cd", label: "Candela", type: "number", help: "" },
{ id: "cd_w", label: "Candela Wide", type: "number", help: "" },
{ id: "cd_t", label: "Candela Tight", type: "number", help: "" },
{ id: "abm", label: "Beam Angle", type: "number", help: "" },
{ id: "abm_x", label: "Beam X", type: "number", help: "" },
{ id: "abm_y", label: "Beam Y", type: "number", help: "" },
{ id: "abm_w", label: "Beam Wide", type: "number", help: "" },
{ id: "abm_t", label: "Beam Tight", type: "number", help: "" },
{ id: "wt", label: "Weight", type: "number", help: "" },
{ id: "cpf", label: "Sections Per Fixture", type: "number", help: "" },
{ id: "lpc", label: "Lamps Per Circuit", type: "number", help: "" },
{ id: "dbl", label: "Distance Between Lamps", type: "number", help: "" },
{ id: "note", label: "More Info", type: "text", help: "" },
{ id: "scroll", label: "Device Params", type: "text", help: "" },
{ id: "dmode", label: "Mode", type: "text", help: "" },
{ id: "mixtype", label: "Mix Type", type: "text", help: "" },
{ id: "d_mk", label: "Default Mark", type: "text", help: "" },
{ id: "acc_loc", label: "Placement", type: "text", help: "" },
{ id: "d_adj_x", label: "Default X Offset", type: "number", help: "" },
{ id: "d_adj_y", label: "Default Y Offset", type: "number", help: "" },
{ id: "d_adj_z", label: "Default Z Offset", type: "number", help: "" },
];
/**
* Listeners
*/
// listener une fois que la page est chargée
document.addEventListener('DOMContentLoaded', () => {
manageHtmlPanels();
//loadCustomSymbols();
});
// listener pour savoir quand un champ d'appareil est changé
document.body.addEventListener('change', function (event) {
let index = event.target.id.substring(event.target.id.indexOf('-') + 1);
// ecoute si la liste de selection de type d'appareil est changée
if (event.target.tagName === 'SELECT' && event.target.classList.contains('kind-select-list')) {
displayoptionalFields(index, event.target.value);
inputFiles[index].fields.mandatory.kind = event.target.value;
checkFilledFields();
}
// ecoute si un champ obligatoire est changé
if (event.target.tagName === 'INPUT' && event.target.classList.contains('mandatory-field')) {
inputFiles[index].fields.mandatory[event.target.name] = event.target.value;
checkFilledFields();
}
// ecoute si un champ optionnel est changé
if (event.target.tagName === 'INPUT' && event.target.classList.contains('optional-field')) {
inputFiles[index].fields.optional[event.target.name] = event.target.value;
}
// ecoute si une checkbox de la liste d'appareils est changée
if (event.target.tagName === 'INPUT' && (event.target.type === "checkbox")) {
countCheckedKentries();
}
});
/**
* Analyse
*/
function loadCustomSymbols() {
customSymbolsXml = '';
var xhr = new XMLHttpRequest();
xhr.open('GET', 'symbols.xml', false); // Synchronous request
xhr.overrideMimeType('text/xml');
xhr.send(null);
customSymbolsXml = xhr.responseXML;
}
// listener pour voir quand on selectionne les fichiers de plot
plotFilesInput.addEventListener('change', async function (event) {
const files = event.target.files;
const results = [];
// Cree un tableau de promesse pour chaque fichier
const filePromises = Array.from(files).map(file => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
file.isXML = false;
file.isLxplot = false;
file.hasShape = false;
file.hasGroup = false;
// recupere le contenu du fichier
file.content = reader.result;
// Parse la chaine xml
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(file.content, "text/xml");
// Verifie si le parsing s'est bien passé
const parserErrors = xmlDoc.getElementsByTagName("parsererror");
if (parserErrors.length === 0) {
file.isXML = true;
}
// Verifie si l'element root est nommé "lxplot"
const rootElement = xmlDoc.documentElement;
if (rootElement.tagName === "lxplot") {
file.isLxplot = true;
}
// vérifie si il y a un element shape dans le premier layer
const firstLayer = rootElement.getElementsByTagName("layer")[0];
const firstShapeOfLayer = firstLayer.getElementsByTagName("shape")[0];
if (firstShapeOfLayer) {
file.hasShape = true;
file.shapeXml = firstShapeOfLayer;
// verifie si il y a d'autres symboles intégrés dans la shape
const subShapes = firstShapeOfLayer.getElementsByTagName("shape");
for (let shape of subShapes) {
const shapeFirstClass = shape.getElementsByTagName("class")[0];
if (shapeFirstClass.textContent === "LXLight") {
// on recupere le kid
const kid = shape.getElementsByTagName("kid")[0];
// si oui, on cherche la clef dans le fichier
const kentries = rootElement.getElementsByTagName("kentry");
for (let kentry of kentries) {
if (kentry.getElementsByTagName("id")[0].textContent === kid.textContent) {
addedKeys.push(kentry);
}
}
}
}
}
// verifie si la premiere shape contient un groupe nommé lxgroup
const classElement = firstShapeOfLayer.getElementsByTagName("class")[0];
if (classElement && classElement.textContent === "LXGroup") {
file.hasGroup = true;
}
file.isValidFile =
file.isXML &&
file.isLxplot &&
file.hasShape &&
file.hasGroup;
if (file.isValidFile) {
isValidFileCount++;
}
file.index = inputFiles.length;
inputFiles.push(file);
resolve({
name: file.name,
isXML: file.isXML,
isLxplot: file.isLxplot,
hasShape: file.hasShape,
hasGroup: file.hasGroup
});
};
reader.onerror = () => reject(new Error(`Failed to read ${file.name}`));
reader.readAsText(file); // or readAsDataURL for binary
});
});
try {
// attendre que tous les fichiers soient traités
const allResults = await Promise.all(filePromises);
// affiche les resultats seulement quand tous les fichiers sont traités
displayKeyFields = (isValidFileCount === inputFiles.length);
if (!displayKeyFields) {
makeKeyLogMessageDiv.innerHTML +=
`<p class="error-message">Pour générer une clef, il ne faut que des fichiers valides.</p>`
}
fillPlotFileInfosDiv(allResults);
} catch (error) {
console.error('Error processing files:', error);
}
});
// listener pour voir quand on selectionne les fichiers de key
keyFilesInput.addEventListener('change', async function (event) {
const files = event.target.files;
const results = [];
// Cree un tableau de promesse pour chaque fichier
const filePromises = Array.from(files).map(file => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
file.isXML = false;
file.isLxKey = false;
// recupere le contenu du fichier
file.content = reader.result;
// Parse la chaine xml
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(file.content, "text/xml");
// Verifie si le parsing s'est bien passé
const parserErrors = xmlDoc.getElementsByTagName("parsererror");
if (parserErrors.length === 0) {
file.isXML = true;
}
// Verifie si l'element root est nommé "key"
const rootElement = xmlDoc.documentElement;
if (rootElement.tagName === "key") {
file.isLxKey = true;
}
// recupere toutes les clefs
const kentries = rootElement.getElementsByTagName("kentry");
for (let kentry of kentries) {
const keyId = kentry.getElementsByTagName("id")[0].textContent;
//addedKeys.push(kentry);
if (!addedKeys[keyId]) {
addedKeys[keyId] = kentry;
}
}
file.isValidFile =
file.isXML &&
file.isLxKey;
if (file.isValidFile) {
isValidFileCount++;
}
file.index = inputFiles.length;
inputFiles.push(file);
resolve({
name: file.name,
isXML: file.isXML,
isLxKey: file.isLxKey
});
};
reader.onerror = () => reject(new Error(`Failed to read ${file.name}`));
reader.readAsText(file); // or readAsDataURL for binary
});
});
try {
// attendre que tous les fichiers soient traités
const allResults = await Promise.all(filePromises);
// affiche les resultats seulement quand tous les fichiers sont traités
displayKeyFields = (isValidFileCount === inputFiles.length);
if (!displayKeyFields) {
makeKeySetLogMessagesDiv.innerHTML +=
`<p class="error-message">Pour générer une clef, il ne faut que des fichiers valides.</p>`
}
fillKeyFileInfosDiv(allResults);
} catch (error) {
console.error('Error processing files:', error);
}
});
/**
* Affichage
*/
// affiche la liste des fichiers de plot
function fillPlotFileInfosDiv(results) {
generateKeyFileButton.name = "key";
plotFilesListDiv.innerHTML = ``;
let index = 0;
results.forEach(result => {
let fileInfosDiv = document.createElement('div');
fileInfosDiv.className = "container";
fileInfosDiv.id = "fileInformations-" + index;
let fileInfos = document.createElement('ul');
fileInfos.className = "fileInformations";
fileInfos.innerHTML =
'<li>Nom du fichier : <span class="file-name">' + result.name + '</span></li>' +
'<li>Le fichier est il un xml ? : ' + (result.isXML ?
'<span class="valid">oui</span>' :
'<span class="invalid">non</span>') + '</li>' +
'<li>Le fichier est il un lxplot ? : ' + (result.isLxplot ?
'<span class="valid">oui</span>' :
'<span class="invalid">non</span>') + '</li>' +
'<li>Le fichier possede une shape ? : ' + (result.hasShape ?
'<span class="valid">oui</span>' :
'<span class="invalid">non</span>') + '</li>' +
'<li>La shape est dans un groupe ? : ' + (result.hasGroup ?
'<span class="valid">oui</span>' :
'<span class="invalid">non</span>') + '</li>';
fileInfosDiv.appendChild(fileInfos);
if (displayKeyFields) {
fileInfosDiv.appendChild(buildKeyFields(index));
}
plotFilesListDiv.appendChild(fileInfosDiv);
index++;
});
}
// affiche la liste des fichiers de clefs
function fillKeyFileInfosDiv(results) {
generateKeyFileButton.name = "keySet";
keyFilesListDiv.innerHTML = ``;
let index = 0;
results.forEach(result => {
let fileInfosDiv = document.createElement('div');
fileInfosDiv.className = "container";
fileInfosDiv.id = "fileInformations-" + index;
let fileInfos = document.createElement('ul');
fileInfos.className = "fileInformations";
fileInfos.innerHTML =
'<li>Nom du fichier : <span class="file-name">' + result.name + '</span></li>' +
'<li>Le fichier est il un xml ? : ' + (result.isXML ?
'<span class="valid">oui</span>' :
'<span class="invalid">non</span>') + '</li>' +
'<li>Le fichier est il une lib lxkey ? : ' + (result.isLxKey ?
'<span class="valid">oui</span>' :
'<span class="invalid">non</span>') + '</li>';
fileInfosDiv.appendChild(fileInfos);
keyFilesListDiv.appendChild(fileInfosDiv);
index++;
});
if (displayKeyFields) {
keyFilesListDiv.appendChild(buildKeyList());
// coche tout le monde ou personne
const checkAll = document.getElementById('selectAll');
const checkboxes = document.querySelectorAll('input[name="kentrySelection"]');
checkAll.addEventListener('change', function () {
checkboxes.forEach(checkbox => {
checkbox.checked = checkAll.checked;
});
});
}
}
function buildKeyList() {
let index = 0
const keylist = document.createElement("div");
keylist.classList.add("container");
const keyListTable = document.createElement("table");
const keyListTableHeaders = document.createElement("tr");
keyListTableHeaders.innerHTML = `
<th><input type="checkbox" id="selectAll" checked /></th>
<th>Name</th>
<th>Full Name</th>
<th>Kind</th>
`;
keyListTable.appendChild(keyListTableHeaders);
//const keylistUl = document.createElement("ul");
for (const [key, value] of Object.entries(addedKeys)) {
let name = addedKeys[key].getElementsByTagName("name")[0].textContent;
let fname = addedKeys[key].getElementsByTagName("fname")[0].textContent;
let kind = addedKeys[key].getElementsByTagName("kind")[0].textContent;
// cree le tag tr
const keyListTableTr = document.createElement("tr");
// cree le tag td
keyListTableTr.innerHTML = `
<td><input type="checkbox" name="kentrySelection" value="${key}" checked="false"></input></td>
<td>${name}</td>
<td>${fname}</td>
<td>${kind}</td>
`;
keyListTable.appendChild(keyListTableTr);
selectedKeys.push(key);
index++;
}
canGenerateKeyFile = false;
generateKeyFileButton.hidden = true;
if (selectedKeys.length > 0) {
canGenerateKeyFile = true;
generateKeyFileButton.hidden = false;
}
keylist.appendChild(keyListTable);
return keylist;
}
// construit les champs pour chaque fichier
function buildKeyFields(index) {
// div principale
let keyFieldsDiv = document.createElement('div');
keyFieldsDiv.id = "dataFields-" + index;
keyFieldsDiv.className = 'form-section';
keyFieldsDiv.innerHTML += `<h2>Données de la librairie</h2>`;
inputFiles[index].fields = [];
// champs obligatoires
inputFiles[index].fields.mandatory = [];
keyFieldsDiv.innerHTML += `<h3>Champs obligatoires</h3>`;
let mandatoryFieldsDiv = buildMandatoryFields(index);
keyFieldsDiv.appendChild(mandatoryFieldsDiv);
// champs optionnels
inputFiles[index].fields.optional = [];
keyFieldsDiv.innerHTML += `<h3>Champs optionnels</h3>`;
let optionalFieldsDiv = document.createElement('div');
optionalFieldsDiv.id = "optionalFields-" + index;
optionalFieldsDiv.innerHTML += `<p>Selectionner d'abord un type d'appareil</p>`;
// on met sa div dans la div principale
keyFieldsDiv.appendChild(optionalFieldsDiv);
return keyFieldsDiv;
}
// construit la select list pour les types d'appareils
function buildKindSelectList(index) {
// div
let kindSelectListDiv = document.createElement('div');
kindSelectListDiv.id = "kindSelect-" + index;
kindSelectListDiv.classList.add('mandatory-field');
// label
kindSelectListDiv.innerHTML += `<label for="kindSelect-${index}">Type d'appareil</label>`;
// select
let kindSelectList = document.createElement('select');
kindSelectList.id = "kindSelect-" + index;
kindSelectList.name = "kindSelect-" + index;
kindSelectList.classList.add('kind-select-list');
// option par defaut
let kindSelectListDefault = document.createElement('option');
kindSelectListDefault.value = "none";
kindSelectListDefault.textContent = "Sélectionner un élément dans la liste";
kindSelectListDefault.selected = true;
// on ajoute la valeur par defaut dans la balise select
kindSelectList.appendChild(kindSelectListDefault);
// on fabrique la liste deroulante
selectOptions.forEach((option) => {
const optElement = document.createElement("option");
optElement.value = option.id;
optElement.textContent = option.label;
kindSelectList.appendChild(optElement);
});
// on ajoute la balise select dans la div de depart
kindSelectListDiv.appendChild((kindSelectList));
return kindSelectListDiv;
}
// construit la liste des champs obligatoires
function buildMandatoryFields(index) {
let mandatoryFieldsDiv = document.createElement('div');
mandatoryFieldsDiv.id = "mandatoryFields-" + index;
mandatoryFieldsDiv.classList.add("container");
mandatoryFieldsDiv.innerHTML = `
<div>
<label for="name-${index}">Type</label>
<input class="mandatory-field" type="text" id="name-${index}" name="name" title="Nom court" required />
</div>
<div>
<label for="fname-${index}">Full Name</label>
<input class="mandatory-field" type="text" id="fname-${index}" name="fname" title="Nom complet" required />
</div>
<div>
<label for="id-${index}">Id</label>
<input class="mandatory-field" type="text" id="id-${index}" name="id" title="Id unique" required />
</div>
`;
mandatoryFieldsDiv.appendChild(buildKindSelectList(index));
return mandatoryFieldsDiv;
}
// Affiche les champs optionnels en fonction du type d'appareil sélectionné
function displayoptionalFields(index, selectedOptionId) {
let optionalFields = document.getElementById("optionalFields-" + index);
optionalFields.classList.add("fields-set");
optionalFields.classList.add("container");
optionalFields.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}-${index}">${field.label}:</label>
<input class="optional-field" type="${field.type}" id="${field.id}-${index}" name="${field.id}" title="${field.help}" />
`;
optionalFields.appendChild(fieldDiv);
}
});
return optionalFields;
}
/**
* Generation du fichier
*/
function generateKeyFile() {
// const inputValue = document.querySelector('#mandatoryFields-1 input[id="name"]').value;
const xmlDoc = new DOMParser().parseFromString('<key></key>', 'application/xml');
const root = xmlDoc.documentElement;
switch (generateKeyFileButton.name) {
case "key":
// ajoute les clefs de chaque fichier
inputFiles.forEach((file) => {
root.appendChild(buildXmlKentry(xmlDoc, file));
});
// ajoute les clefs supplementaires si existantes
if (addedKeys.length > 0) {
// recuperer la clef
for (let addedKey of addedKeys) {
root.appendChild(addedKey);
}
}
break;
case "keySet":
// ajoute les clefs si existantes
if (selectedKeys.length > 0) {
// recuperer la clef
for (let selectedKey of selectedKeys) {
root.appendChild(addedKeys[selectedKey]);
console.log(addedKeys[selectedKey]);
}
}
break;
}
// 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);
}
// verifier que les champs soient bien remplis
function checkFilledFields() {
let mandatoryFieldsCount = 0;
inputFiles.forEach((file) => {
let filledFieldsCount = 0;
Object.entries(file.fields.mandatory).forEach(([key, value]) => {
if (value !== '' && value !== 'none') {
filledFieldsCount++;
}
});
// check if mandatory fields are filled
if (filledFieldsCount === 4) {
mandatoryFieldsCount++;
}
});
if (mandatoryFieldsCount === inputFiles.length) {
canGenerateKeyFile = true;
generateKeyFileButton.hidden = false;
}
else {
canGenerateKeyFile = false;
generateKeyFileButton.hidden = 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 });
}
}
}
function countCheckedKentries() {
selectedKeys = [];
canGenerateKeyFile = false;
generateKeyFileButton.hidden = true;
const checkboxes = document.querySelectorAll('input[name="kentrySelection"]');
checkboxes.forEach((checkbox) => {
if (checkbox.checked) {
selectedKeys.push(checkbox.value);
}
});
if (selectedKeys.length > 0) {
canGenerateKeyFile = true;
generateKeyFileButton.hidden = false;
}
}
function buildXmlKentry(xmlDoc, file) {
// creation des elements basiques
const kentryElement = xmlDoc.createElement('kentry');
const customElement = xmlDoc.createElement("custom");
const symbolElement = xmlDoc.createElement("symbol");
const groupElement = xmlDoc.createElement("group");
// ajout de la shape dans un groupe
groupElement.appendChild(file.shapeXml);
// ajout du groupe dans un symbole
symbolElement.appendChild(groupElement);
// ajout du symbole dans un custom
customElement.appendChild(symbolElement);
// ajout des champs obligatoires
Object.entries(file.fields.mandatory).forEach(([key, value]) => {
const mandatoryField = xmlDoc.createElement(key);
mandatoryField.textContent = value;
kentryElement.appendChild(mandatoryField);
});
// ajout des champs optionnels
Object.entries(file.fields.optional).forEach(([key, value]) => {
const optionalField = xmlDoc.createElement(key);
optionalField.textContent = value;
kentryElement.appendChild(optionalField);
});
// ajout du custom dans la kentry
kentryElement.appendChild(customElement);
return kentryElement;
}
// gestion des panneaux html
function manageHtmlPanels() {
// gestion des panneaux
const tabs = document.querySelectorAll('.tab');
const tabPanes = document.querySelectorAll('.tab-pane');
// gere le click
tabs.forEach(tab => {
tab.addEventListener('click', () => {
if (tab.id != "generateKeyFile") {
const tabId = tab.getAttribute('data-tab');
// met a jour le panneau actif
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// montre le contenu correspondant
tabPanes.forEach(pane => {
pane.classList.remove('active');
if (pane.id === tabId) {
pane.classList.add('active');
}
});
}
});
});
}
// reset
function globalResets() {
inputFiles = [];
addedKeys = [];
filledFields = 0;
selectedKeys = [];
makeKeyLogMessageDiv.innerHTML = "";
plotFilesListDiv.innerHTML = `<p>... En attente</p>`;
makeKeySetLogMessagesDiv.innerHTML = "";
keyFilesListDiv.innerHTML = `<p>... En attente</p>`;
isValidFileCount = 0;
displayKeyFields = false;
canGenerateKeyFile = false;
generateKeyFileButton.hidden = true;
}

236
styles.css Normal file
View File

@@ -0,0 +1,236 @@
/* styles.css */
* {
margin: 0;
padding: 0;
/*box-sizing: border-box;*/
}
body {
font-family: Arial, sans-serif;
background-color: #1a1a1a;
color: #ddd;
height: 100vh;
overflow: hidden;
line-height: 1.6;
}
h1 {
margin: 5px;
}
h2 {
text-align: center;
border: 1px solid orange;
border-radius: 8px;
}
.tab-nav-container {
display: flex;
justify-content: space-around;
/*background-color: #ffffff;*/
/*border-bottom: 1px solid #ddd;*/
/*position: fixed;*/
top: 0;
left: 0;
right: 0;
height: 60px;
padding: 10px 0;
/*box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);*/
/*margin: 20px;*/
}
.tab {
flex: 1;
padding: 10px 0;
border: none;
font-size: 14px;
background-color: #1a1a1a;
color: #8b8b8b;
border: 1px solid orange;
cursor: pointer;
transition: color 0.3s ease;
border-radius: 8px;
max-width: 30%;
min-height: 100%;
margin: 5px;
}
.tab.active {
background-color: #000;
color: white;
font-weight: bold;
border: 2px solid red;
}
.tab:hover {
color: white;
font-weight: bold;
}
.tab#generateKeyFile {
background-color: red;
border: 2px solid orange;
font-weight: bold;
color: white;
}
.tab-content {
/*padding-top: 60px;*/
height: calc(100vh - 60px);
overflow-y: auto;
/*background-color: red;*/
}
.tab-pane {
display: none;
/*margin: 2%;*/
padding: 10px;
/*background-color: #202020f9;*/
/*min-height: 100%;*/
/*border: 1 px solid white;
border-radius:5%;*/
}
.tab-pane.active {
display: block;
}
/* old */
.container {
border: 1px solid #777;
border-radius: 5px;
margin: 5px;
padding: 10px;
}
.container ul {
margin-left: 40px;
}
.buttons-set {
display: contents;
}
.valid {
color: white;
background-color: green;
}
.invalid {
color: white;
background-color: red;
animation: blinker-one 1.4s linear infinite;
}
.no-answer {
color: black;
background-color: white;
}
.file-name {
font-weight: bold;
font-size: larger;
}
#plotLogMessages {
padding: 5px;
}
.error-message {
border: 1px solid red;
padding: 10px;
text-align: center;
font-weight: bold;
color: #000000;
background-color: red;
/* text-shadow: 4px 1px 2px #555555; */
font-size: larger;
animation: blinker-one 1.4s linear infinite;
}
@keyframes blinker-one {
100% {
opacity: 0;
}
}
/** form **/
.form-section {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
text-align: right;
}
#mandatoryFields>div,
#optionalFields>div {
display: flex;
align-items: center;
margin-bottom: 15px;
}
label {
min-width: 150px;
font-weight: bold;
margin-right: 15px;
text-align: right;
}
input[type="text"],
input[type="number"],
select {
flex: 1;
padding: 8px;
/*border: 1px solid #ccc;*/
border-radius: 4px;
/*background-color: #bbbaba;*/
color: black;
font-weight: bold;
}
input[type="number"] {
background-color: #111;
color: #aaa;
/*border: none;
font-size: 1em;*/
text-align: center;
}
#optionalFields details {
margin-top: 20px;
padding: 10px;
border: 1px solid #eee;
border-radius: 4px;
}
#optionalFields summary {
cursor: pointer;
font-weight: bold;
}
.custom-number-input::after {
content: "⌄";
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: #999;
}
table {
border-collapse: collapse;
}
table, th, td {
border:1px solid grey;
}
th {
background-color: black;
}
th, td {
padding: 5px;
}

1005
symbols.xml Normal file

File diff suppressed because it is too large Load Diff