first commit
This commit is contained in:
@@ -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
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
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
+102
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user