cahier-de-bridge
Version:
Gestion d'un éditeur de mains de bridge
1,583 lines (1,477 loc) • 55.5 kB
HTML
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cahier de Bridge</title>
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png" />
<link rel="manifest" href="/images/site.webmanifest" />
<link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="/css/status.css" />
<link rel="stylesheet" href="/css/tree.css" />
<link rel="stylesheet" href="/css/menu.css" />
<link rel="stylesheet" href="/css/modal.css" />
<link rel="stylesheet" href="/css/hamburger.css" />
<style type="text/css">
body {
box-sizing: border-box;
margin: 0;
padding: 0;
}
#container {
position: absolute;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
box-sizing: border-box;
}
#volets {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
justify-content: flex-start;
box-sizing: border-box;
}
#volet_centre {
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: column;
background-color: lightgray;
background: linear-gradient(rgba(211, 211, 211, 0.5), rgba(211, 211, 211, 0.5)), url("/images/Cards.png");
}
.donne {
display: flex;
flex-direction: row;
background-color: white;
}
#jtxt {
min-height: 50px;
width: 100%;
padding: 0.5em;
background-color: white;
border: 1px solid black;
border-top: none;
}
.donne > .cartes {
flex-grow: 2;
display: grid;
grid-template-areas:
". nord . "
"ouest centre est"
". sud . ";
}
.donne > .encheres {
display: flex;
flex-direction: column;
padding: 0.5em;
align-items: center;
}
.NS {
display: flex;
flex-direction: column;
align-items: center;
}
.nord {
justify-content: flex-end;
grid-area: nord;
margin-bottom: 8px;
}
.sud {
margin-top: 8px;
grid-area: sud;
}
.centre {
grid-area: centre;
min-width: 60px;
min-height: 60px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.OE {
display: flex;
flex-direction: column;
align-self: center;
}
.est {
grid-area: est;
align-items: flex-start;
}
.ouest {
grid-area: ouest;
align-items: flex-end;
}
.donne > .encheres table {
border: 1px solid black;
table-layout: fixed;
width: 300px;
text-align: center;
}
.donne > .encheres table th {
border-bottom: 1px solid black;
}
.donne > .encheres table tr {
height: 1.2em;
}
.donne > .encheres table td {
vertical-align: middle;
}
.donne > .encheres table td img {
vertical-align: middle;
}
.donne > .encheres #etxt {
width: 100%;
height: 100%;
border: 1px black solid;
padding: 0.5em;
}
.main {
display: flex;
flex-direction: row;
align-items: center;
font-size: 1.5rem;
}
.main > img {
margin-right: 8px;
}
.vulnerable {
border: green 10px solid;
width: 50px;
height: 50px;
}
.vulnerable.NSred {
border-top-color: red;
border-bottom-color: red;
}
.vulnerable.EWred {
border-left-color: red;
border-right-color: red;
}
#sw_edit {
appearance: none;
}
#sw_edit + label {
border: 1px darkgray solid;
border-radius: 5px;
margin-right: 8px;
padding-top: 4px;
}
#sw_edit + label:hover {
cursor: pointer;
background-color: rgb(190, 190, 190);
}
#sw_edit:not(:checked) + label img.si_chk {
display: none;
}
#sw_edit:checked + label img.si_unchk {
display: none;
}
[contenteditable="false"] {
cursor: default;
}
[contenteditable="true"] {
cursor: text;
border: lightgray 1px solid;
}
h3 {
text-align: center;
}
.d_nord::before {
position: relative;
content: "▲";
top: -100%;
left: 30%;
}
.d_ouest::before {
position: relative;
content: "◀";
top: 4px;
left: -100%;
}
.d_est::after {
position: relative;
content: "▶";
top: 4px;
left: 140%;
}
.d_sud::after {
position: relative;
content: "▼";
top: 130%;
left: 30%;
}
.titre {
align-self: center;
padding: 0px 8px;
background-color: white;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border: 1px solid black;
border-bottom: none;
}
.bas {
flex-grow: 2;
border-bottom: 1px solid black;
height: 100%;
}
#volet_tree {
display: flex;
flex-direction: column;
position: relative;
box-sizing: border-box;
min-width: 220px;
background-color: whitesmoke;
z-index: 2;
}
#volet_tree.hide_me {
display: none;
}
#volet_tree .find {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
}
#volet_tree .find > input {
width: 100%;
height: 100%;
}
#volet_tree .find > img {
padding: 4px;
}
#del_rch {
cursor: pointer;
}
#volet_tree .icones {
margin-top: 0.5em;
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
align-items: center;
}
#volet_tree #arbre {
position: relative;
margin-top: 1em;
box-sizing: border-box;
overflow-y: scroll;
height: 100%;
}
.ed_div {
width: 100%;
position: relative;
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-around;
}
.cp_btn {
margin: 4px;
font-weight: bold;
font-size: 1.2em;
min-width: 24px;
}
.rch_txt {
font-weight: bolder;
color: blue;
background-color: whitesmoke;
border: blue 1px dashed;
}
#bkp_where div {
display: flex;
flex-direction: column;
}
#bkp_where > div > div span {
margin: 8px;
}
#print_this {
display: none;
}
.icn_wrap {
position: absolute;
display: none;
justify-self: flex-start;
align-self: flex-start;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.icn_wrap img {
margin: 4px;
cursor: pointer;
}
.icn_wrap img:hover {
background-color: lightgray;
}
.cartes:hover .icn_wrap,
.encheres:hover .icn_wrap {
display: flex;
}
@media (width <= 1300px) {
.main {
font-size: 1.25rem;
}
}
@media (width <= 1100px) {
.main {
font-size: 1rem;
}
}
@media (width <= 890px) {
.donne {
flex-direction: column-reverse;
}
.main {
font-size: 1rem;
}
}
@media print {
body {
position: relative;
padding: 0;
margin: 0;
float: none;
background-color: white;
background-image: none;
overflow: visible;
}
#container {
display: none;
}
#print_this {
display: block;
}
h1 {
padding: 0.2em;
border-radius: 10px;
border: 3px solid black;
text-align: center;
width: fit-content;
align-self: center;
margin-bottom: 0.5em;
}
h3 {
border-bottom: 2px dashed blue;
text-align: center;
width: fit-content;
align-self: center;
margin-bottom: 0.5em;
}
.pagebreak {
display: flex;
flex-direction: column;
position: relative;
z-index: 2;
box-sizing: inherit;
clear: both;
page-break-after: always;
}
.donne {
flex-direction: column;
}
}
</style>
</head>
<body>
<span id="print_this"></span>
<div class="modalBackground" id="sel_donne">
<div class="modalWnd">
<span class="puclose">×</span>
<!-- Modal Content -->
<h1>Sélectionner une donne</h1>
<div>
<label for="nom_jeu">Nom du modèle: </label>
<select id="nom_jeu"></select>
</div>
<button onclick="CloseModalWnd(); OpenID(nom_jeu.value)">Ouvrir</button>
</div>
</div>
<div class="modalBackground" id="bkp_where">
<div class="modalWnd">
<span class="puclose">×</span>
<!-- Modal Content -->
<h1>Quelle type de restauration ?</h1>
<div>
<span hlp="ATTENTION ! Tous les utilisateurs seront impactés"> <input type="radio" name="rbt_bkp" value="0" /> Écraser la base existante </span><br />
<span hlp="Préserve la base actuelle"> <input type="radio" name="rbt_bkp" value="1" /> Créer une copie et l'ouvrir </span>
<br />
<span id="nom_base" hlp="Saisir le nom de la copie">Nom de la copie: <input type="text" id="ed_nom_bkp" /></span>
</div>
<button onclick="CloseModalWnd(); OpenBKP()">Restaurer</button>
</div>
</div>
<div class="modalBackground" id="sel_db">
<div class="modalWnd">
<span class="puclose">×</span>
<!-- Modal Content -->
<h1>Sélectionnez la base de donnée à ouvrir</h1>
<select id="cbx_db"></select>
<button onclick="CloseModalWnd(); openDB()">Ouvrir</button>
</div>
</div>
<div id="container" onclick="closeMenu()">
<div id="volets">
<div id="volet_tree" class="hide_me">
<div class="find">
<img src="/images/Search.png" />
<input type="search" id="ed_find" hlp="Rechercher une donne, un mot dans les textes" />
<img src="/images/cancel_16px.png" id="del_rch" hlp="Effacer le champ de recherche" />
</div>
<div class="ed_div">
<input type="checkbox" id="chk_find" /><label for="chk_find">Rechercher seulement<br />dans les noms des jeux</label>
</div>
<div class="icones si_edit">
<img src="/images/subtree.png" alt="Déplacer cette icone sur l'arbre pour créer des nouveaux dossiers ou sous-dossiers" ondragstart="onSubnodeDrag(event)" />
<img id="node_trash" src="/images/trash_40px.png" draggable="false" alt="Faire glisser ici les dossiers ou jeux à effacer" />
</div>
<div id="arbre"></div>
</div>
<div id="volet_centre">
<div class="menu">
<input type="checkbox" id="tree_sw" />
<label for="tree_sw">
<img src="/images/arrow_d.png" alt="Afficher/Cacher l'arbre de situation" />
</label>
<button class="hamburger" type="button" aria-label="Toggle navigation" aria-expanded="false" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</button>
<div class="menu2">
<div class="level0" style="background-image: url('/images/dossier.png')" hlp="Actions génériques sur une donne (ouvrir, sauver, copier...)">
<div class="level1">
<button class="itmh" id="itm_new"><img src="/images/create_30px.png" alt="Créer une nouvelle donne" />Créer une donne</button>
<button class="itmh si_un" alt="Sélectionner une donne existante" id="itm_open"><img src="/images/dossier.png" />Ouvrir une donne</button>
<button class="itmh si_dirty" onclick="onSave()" alt="Enregistrer les modifications"><img src="/images/save_30px.png" />Sauver les changements</button>
<details hlp="Gestion des données: sauver, restaurer, ajouter, choisir base...">
<summary><img src="/images/database_30px.png" />Base de donnée</summary>
<input type="file" style="display: none" id="btn_bkp" />
<a class="hide_me" id="bkp_lnk" href="/public/bridge.db" download>Télécharger</a>
<button class="itmh si_admin" onclick="onBkp()" hlp="Sauvegarder la base de donnée dans un backup"><img src="/images/database_export_24px.png" />Sauvegarde</button>
<input type="file" class="hide_me" id="get_bkp" />
<button class="itmh si_admin" onclick="onRestore()" hlp="Restaurer la base de donnée depuis une sauvegarde"><img src="/images/database_restore_24px.png" />Restauration</button>
<button class="itmh si_admin si_dbs" onclick="OpenModalWnd('sel_db')" hlp="Choisir la base de donnée à ouvrir"><img src="/images/database_view_24px.png" />Sélectionner une base de donnée</button>
<input type="file" class="hide_me" id="get_export" />
<button class="itmh" hlp="Exporter donne(s) au format SQL. ctrl-clic pour sélectionner plusieurs" id="itm_export"><img src="/images/export_24px.png" />Exporter donne(s)</button>
<input type="file" class="hide_me" id="get_import" />
<label for="get_import" class="itmh si_admin" hlp="Importer des donnes depuis un fichier SQL"> <img src="/images/import_24px.png" />Importer donne(s) </label>
</details>
<button class="itmh si_open" onclick="PrintThis()" alt="Imprimer"><img src="/images/print_30px.png" />Imprimer cette donne</button>
</div>
</div>
<div class="level0" style="background-image: url('/images/link_30px.png')" hlp="Liens en relations avec ce logiciel (aide, mise à jour..)">
<div class="level1">
<a href="https://github.com/cledou/Cahier-de-Bridge/blob/main/interface.md">Aide à la saisie</a>
<a href="https://github.com/cledou/Cahier-de-Bridge/blob/main/install.md">Installation</a>
<a href="https://github.com/cledou/Cahier-de-Bridge/discussions">Forum</a>
<a href="https://github.com/cledou/Cahier-de-Bridge/issues">Rapport de bugs</a>
<a href="https://github.com/cledou/Cahier-de-Bridge/blob/main/assistance.md">Assistance</a>
</div>
</div>
</div>
<div style="width: 90%"></div>
<!-- repousser menuD à droite-->
<div class="flexV" style="margin-right: 10px">
<input id="ed_masquer" type="range" min="1" max="4" hlp="Montrer/cacher commentaires" value="4" style="width: 100px" />
<label style="font-size: 0.7em">Masquer affichage</label>
</div>
<input type="checkbox" id="sw_edit" /><label for="sw_edit">
<img src="/images/Lock.png" class="si_unchk" hlp="Autoriser les modifications" />
<img src="/images/Unlock.png" class="si_chk" hlp="Interdir les modifications"
/></label>
<button onclick="PrintThis()"><img src="/images/print_30px.png" alt="Imprimer la donne" /></button>
<button class="to_do undo" onclick="Undo()" disabled><img src="/images/undo_30px.png" alt="Revenir en arrière (ctrl-Z)" /></button>
<button class="to_do redo" onclick="Redo()" disabled><img src="/images/redo_30px.png" alt="Annuler retour en arrière (shift-ctrl-Z)" /></button>
<button class="si_dirty" onclick="onSave()"><img src="/images/save_30px.png" alt="Sauver les modifications" /></button>
<button class="si_login hide_me" onclick="window.location.href='/user'">
<img src="/images/add_user_30px.png" alt="Créer un compte utilisateur" />
</button>
<button class="si_login hide_me" onclick="window.location.href='/login'">
<img src="/images/close_window_30px.png" hlp="Revenir à l'écran de connexion" />
</button>
</div>
<h1 class="titre" id="enr_nom" contenteditable="true" style="min-width: 3em; min-height: 1em"></h1>
<div class="donne">
<div class="cartes">
<div class="icn_wrap"><img src="images/clipboard_18px.png" hlp="Copier la donne comme image dans le presse-papier" onclick="makeImage('.cartes')" /></div>
<div class="NS nord"></div>
<div class="OE ouest"></div>
<div class="centre">
<div class="vulnerable"></div>
</div>
<div class="OE est"></div>
<div class="NS sud"></div>
</div>
<div class="encheres">
<div class="icn_wrap"><img src="images/clipboard_18px.png" hlp="Copier l'enchère' comme image dans le presse-papier" onclick="makeImage('.encheres')" /></div>
<h3>Enchères</h3>
<div class="flexV">
<table id="tbe"></table>
<div class="si_edit ed_div">
<div><b>-</b>:Passe</div>
<div><b>X</b>:Contre</div>
<div><b>XX</b>:Surcontre</div>
</div>
</div>
<div class="si_mask2" id="etxt" contenteditable="true"></div>
<div class="ed_div si_mask2">
<div class="flexH">
Entame:
<label class="flexH" id="entame" contenteditable="true" style="min-width: 3em; min-height: 1em"></label>
</div>
<div class="flexH">
Résultat:
<label class="flexH" id="score" contenteditable="true" style="min-width: 10em; min-height: 1em"></label>
</div>
</div>
<div class="si_edit ed_div si_mask2">
<div class="flexH">
Vulnérable:
<select id="ed_vul">
<option value="-">Aucun</option>
<option value="NSEW">Tous</option>
<option value="NS">Nord Sud</option>
<option value="EW">Est Ouest</option>
</select>
</div>
<div class="flexH">
Copier
<button class="cp_btn" onclick="navigator.clipboard.writeText('♠')"><img src="/images/Spades.png" /></button>
<button class="cp_btn" onclick="navigator.clipboard.writeText('♥')"><img src="/images/coeur.png" /></button>
<button class="cp_btn" onclick="navigator.clipboard.writeText('♦')"><img src="/images/Diamonds.png" /></button>
<button class="cp_btn" onclick="navigator.clipboard.writeText('♣')"><img src="/images/Clubs.png" /></button>
<button class="cp_btn" onclick="navigator.clipboard.writeText('♠♥♦♣')"><img src="/images/Spades.png" /><img src="/images/coeur.png" /><img src="/images/Diamonds.png" /><img src="/images/Clubs.png" /></button>
</div>
<div class="flexH">
Donneur:
<select id="ed_donneur">
<option value="N">Nord</option>
<option value="E">Est</option>
<option value="S">Sud</option>
<option value="W">Ouest</option>
</select>
</div>
</div>
</div>
</div>
<br />
<div class="flexH si_mask3">
<div class="bas"></div>
<h3 class="titre">Jeu de la carte</h3>
<div class="bas"></div>
</div>
<div class="si_mask3" id="jtxt" contenteditable="true"></div>
</div>
</div>
<div id="status"></div>
</div>
</body>
</html>
<script src="/socket.io/socket.io.min.js"></script>
<script src="/node/html2canvas/dist/html2canvas.min.js"></script>
<script src="/js/status.js"></script>
<script>
/*
TODO: voir traduction avec gettext https://github.com/guillaumepotier/gettext.js/
TODO: Undo/Redo
*/
//*********************
// Variables globales
//*********************
var io = io.connect(location.host);
var session = { choix: {} };
var mesChoix;
const volet_tree = document.getElementById("volet_tree"); // lié à mesChoix.show_tree
const arbre = document.getElementById("arbre");
const node_trash = document.getElementById("node_trash");
const tree_sw = document.getElementById("tree_sw");
const sw_edit = document.getElementById("sw_edit");
const etxt = document.getElementById("etxt");
const jtxt = document.getElementById("jtxt");
const entame = document.getElementById("entame");
const score = document.getElementById("score");
const itm_new = document.getElementById("itm_new");
const enr_nom = document.getElementById("enr_nom");
const print_this = document.getElementById("print_this");
const cbx_db = document.getElementById("cbx_db");
const ed_masquer = document.getElementById("ed_masquer");
const ShowHide = (id, b) => {
document.getElementById(id).style.opacity = b ? "1.0" : "0";
};
const BlockIf = (id, b) => {
document.getElementById(id).style.display = b ? "block" : "none";
};
const ShowSelector = (sel, b) => {
document.querySelectorAll(sel).forEach((el) => (el.style.opacity = b ? "1" : "0"));
};
const DisableSelector = (classe, val) => {
document.querySelectorAll(classe).forEach((el) => (el.disabled = val));
};
const SetSelectorDisplay = (classe, val) => {
document.querySelectorAll(classe).forEach((el) => (el.style.display = val));
};
const SetRbtValue = (name, idx) => {
document.querySelectorAll('input[name="' + name + '"]')[idx].checked = true;
};
let enr = {};
//*********************
// SESSION socket I/O
//*********************
function SetSessionFlag(f, b) {
const foo = b ? mesChoix.flags | f : mesChoix.flags & ~f;
if (foo != mesChoix.flags) SetChoix("flags", foo);
}
// connect -> session -> get_dims -> liste_donnes -> loadDonne OU nouvelle donne
io.on("connect", function () {
io.emit("session", (data) => {
session = data;
if (data.erreur != undefined) {
Erreur(data.erreur);
}
if (data.info != undefined) {
Info(data.info);
}
if (!session.user.admin) for (let el of document.getElementsByClassName("si_admin")) el.remove();
mesChoix = session.user.choix;
if (mesChoix.flags == undefined) mesChoix.flags = 0;
Object.defineProperties(mesChoix, {
show_tree: {
get: function () {
return Boolean(this.flags & 1);
},
set: (b) => SetSessionFlag(1, b),
},
edit: {
get: function () {
return Boolean(this.flags & 2);
},
set: (b) => SetSessionFlag(2, b),
},
chk_find: {
get: function () {
return Boolean(this.flags & 4);
},
set: (b) => SetSessionFlag(4, b),
},
rbt_bkp: {
get: () => {
return Boolean(this.flags & 8);
},
set: (b) => SetSessionFlag(8, b),
},
});
if (mesChoix.carets == undefined) mesChoix.carets = "XXXXXXXXXXXXX";
document.querySelectorAll(".level1 summary").forEach((caret, idx) => {
while (mesChoix.carets.length <= idx) mesChoix.carets += "X";
console.assert(idx < mesChoix.carets.length);
caret.addEventListener("click", function () {
// En js, string est inmutable
let ar = mesChoix.carets.split("");
ar[idx] = ar[idx] == "X" ? "-" : "X";
SetChoix("carets", ar.join(""));
});
if (mesChoix.carets[idx] == "X") caret.parentElement.setAttribute("open", true);
else caret.parentElement.removeAttribute("open");
});
tree_sw.checked = mesChoix.show_tree;
HideVoletGauche(mesChoix.show_tree);
chk_find.checked = mesChoix.chk_find;
ed_find.value = mesChoix.find || "";
ed_masquer.value = mesChoix.masquer || 4;
for (let el of document.getElementsByClassName("si_login")) {
if (session.need_login) el.classList.remove("hide_me");
else el.classList.add("hide_me");
}
OpenOrNew(); // asynchrone..
});
io.on("info", function (msg) {
Info(msg);
});
io.on("alert", function (msg) {
Erreur(msg);
});
io.on("warning", function (msg) {
Warning(msg);
});
io.onAny((event, p1, p2, p3, p4, p5) => {
return;
if (p5 != undefined) console.log("IO WEB->", event, p1, p2, p3, p4, p5);
else if (p4 != undefined) console.log("IO WEB->", event, p1, p2, p3, p4);
else if (p3 != undefined) console.log("IO WEB->", event, p1, p2, p3.toString().substring(0, 20));
else if (p2 != undefined) console.log("IO WEB->", event, p1, p2);
else if (p1 != undefined) console.log("IO WEB->", event, p1);
else console.log("IO WEB->", event);
});
io.on("db_list", (ar) => {
SetSelectorDisplay(".si_dbs", ar.length > 1 ? "block" : "none");
let foo = "";
ar.forEach((db) => (foo += "<option>" + db + "</option>"));
cbx_db.innerHTML = foo;
//cbx_db.value = mesChoix.db
});
io.on("reload", () => {
window.location.reload();
});
}); // connect
//*********************
// Let's GO
//*********************
async function OpenOrNew() {
if (!(await OpenID(mesChoix.id_donne)) && !(await OpenOneDonne())) itm_new.click();
}
async function OpenOneDonne() {
try {
const r = await SQL_get("SELECT id FROM donnes LIMIT 1");
DisableSelector(".si_un", r == undefined);
return r && OpenID(r.id);
} catch (e) {
Erreur(e);
return false;
}
}
async function OpenID(id) {
if (id == undefined) return false;
try {
const row = await SQL_get("SELECT * FROM donnes WHERE id=" + id);
if (row == undefined) return false;
const foo = JSON.parse(row.data);
VerifierDonne(foo.donne);
enr.jeu = foo;
enr.nom = row.nom || "Donne n°" + row.id;
enr.id = row.id;
enr_nom.innerText = enr.nom;
AfficheDonne();
SetDirty(false);
if (mesChoix.id_donne != row.id) SetChoix("id_donne", row.id);
MakeTree();
DisableSelector(".si_un", false);
return true;
} catch (e) {
Erreur(e);
return false;
}
}
/****************************/
/* STUFF */
/****************************/
function getEnchereSt(v) {
let st = "";
if (v != "") {
if (v == "-") st += "Passe";
else if (v == "X") st += "Contre";
else if (v == "XX") st += "Surcontre";
else if (v[0] >= "1" && v[0] <= "7") {
st += v[0];
if (v[1] == "P" || v[1] == "♠") st += '<img src="/images/Spades.png" />';
else if (v[1] == "C" || v[1] == "♥") st += '<img src="/images/coeur.png" />';
else if (v[1] == "K" || v[1] == "♦") st += '<img src="/images/Diamonds.png" />';
else if (v[1] == "T" || v[1] == "♣") st += '<img src="/images/Clubs.png" />';
else st += v.substr(1);
} else st += v;
}
return st;
}
function MakeEncheres(enchere) {
let st = "<tr><th>Sud</th><th>Ouest</th><th>Nord</th><th>Est</th></tr>";
for (let i = 0; i < enchere.length; i++) {
if (i % 4 == 0) st += "</tr><tr>";
const v = enchere[i];
st += '<td id="enc_' + i + '" onfocus="this.innerText =\'' + v + '\'" contenteditable="' + (mesChoix.edit ? "true" : "false") + '" onblur="blurEnchere(this.innerText,' + i + ',event.relatedTarget.id)" >' + getEnchereSt(v) + "</td>";
}
st += "</tr>";
return st;
}
function blurEnchere(val, idx, next_id) {
const foo = val.toUpperCase().trim();
if (foo != enr.jeu.enchere[idx]) {
enr.jeu.enchere[idx] = foo;
SetDirty(true);
}
document.getElementById("enc_" + idx).innerHTML = getEnchereSt(foo);
if (next_id == "etxt" && val.length > 0) {
enr.jeu.enchere.push(" ");
document.getElementById("tbe").innerHTML = MakeEncheres(enr.jeu.enchere);
document.getElementById("enc_" + (idx + 1)).focus();
}
}
function GetHTMLmain(img, ar, idx) {
return '<div class="main" ><img src="/images/' + img + '.png" /><span style="min-width: 6em" contenteditable="true" oninput="Info(\'\')" onblur="blurMain(' + idx + ',this)">' + AjouteBlancs(ar[idx]) + "</span></div>";
}
function MakeDonne(ar, idx) {
return GetHTMLmain("Spades", ar, idx++) + GetHTMLmain("coeur", ar, idx++) + GetHTMLmain("Diamonds", ar, idx++) + GetHTMLmain("Clubs", ar, idx++);
}
function ARDV2N(v) {
if (v == "A" || v == "1") return 12;
else if (v == "R") return 11;
else if (v == "D") return 10;
else if (v == "V") return 9;
else return Number(v) - 2;
}
function N2ARDV(n) {
if (n == 12) return "A";
if (n == 11) return "R";
if (n == 10) return "D";
if (n == 9) return "V";
return (n + 2).toString();
}
var not_done;
function VerifierDonne(donne) {
// retourne -1 si erreur de distribution, ou nbr cartes manquantes si distribution ok
let compte = new Array(52).fill(0);
let cnt_NOES = new Array(4).fill(0);
not_done = 52;
for (let i = 0; i < donne.length; i++) {
const NOES = Math.floor(i / 4);
const ar = AjouteBlancs(donne[i]).split(" ");
for (let j = 0; j < ar.length; j++)
if (ar[j].trim().length > 0) {
const idx = 13 * (i % 4) + ARDV2N(ar[j]);
if (compte[idx] != 0) {
Erreur("Doublon: " + ar[j] + "♠♥♦♣"[i % 4]);
return false;
}
compte[idx]++;
if (cnt_NOES[NOES] == 13) {
const foo = ["Nord", "Ouest", "Est", "Sud"];
Erreur(foo[NOES] + " comporte déjà 13 cartes");
return false;
}
cnt_NOES[NOES]++;
not_done--;
}
}
// distri OK. Complétable ?
if (not_done == 13)
for (let NOES = 0; NOES < 4; NOES++)
if (cnt_NOES[NOES] == 0) {
for (let i = 0; i < 4; i++) {
let foo = "";
const base = 13 * i;
for (let j = 0; j < 13; j++) if (compte[base + j] == 0) foo = N2ARDV(j) + foo;
donne[(NOES << 2) + i] = foo;
}
return true;
}
return true;
}
function SetChoix(nom, val) {
mesChoix[nom] = val;
io.emit("upducfg", nom, val);
}
function SQL(id, stm, p) {
return new Promise((resolve, reject) => {
io.emit(id, stm, p, (r) => {
// ATTENTION: S'assurer que le serveur retourne bien un objet err si erreur
//console.log("SQL", r);
if (r != undefined && r.err != undefined) {
reject(r.err);
} else resolve(r);
});
});
}
function SQL_all(stm) {
return SQL("cb_all", stm);
}
function SQL_get(stm) {
return SQL("cb_get", stm);
}
function SQL_run(stm, p) {
return SQL("cb_run", stm, p);
}
/****************************/
/* MENUS */
/****************************/
const menu2 = document.querySelector(".menu2");
const level0 = document.querySelectorAll(".level0");
function closeMenu() {
level0.forEach((itm) => {
itm.classList.remove("open");
});
}
level0.forEach((el, idx) => {
el.addEventListener("click", (e) => {
e.stopPropagation();
// différencier le bouton du menu des sous-menus
if (e.target.classList.contains("level0")) {
let foo = e.target.classList.contains("open");
level0.forEach((el1) => {
el1.classList.remove("open");
});
if (!foo) e.target.classList.add("open");
}
});
});
function SetDirty(b) {
DisableSelector(".si_dirty", !b);
}
function HideVoletGauche(b) {
if (b) volet_tree.classList.remove("hide_me");
else volet_tree.classList.add("hide_me");
}
tree_sw.addEventListener("change", function () {
HideVoletGauche(this.checked);
mesChoix.show_tree = this.checked;
});
function SetElvisArbre(b) {
// tree: On ne peut déplacer les noeuds et jeux qu'en mode édition
if (!b) removeSelection();
// seuls les <span> jeux ont l'attribut 'draggable'. Les img sont draggable par nature
arbre.querySelectorAll("[draggable]").forEach((el) => {
el.setAttribute("draggable", b);
el.style.cursor = b ? "grab" : "pointer";
});
arbre.querySelectorAll("[contenteditable]").forEach((el) => {
el.setAttribute("contenteditable", b);
});
SetSelectorDisplay("#arbre .si_edit", b ? "flex" : "none");
}
function SetElvisEdit(b) {
if (mesChoix.edit != b) SetChoix("edit", b);
if (b != sw_edit.checked) sw_edit.checked = b;
document.querySelectorAll('[contenteditable="' + (b ? "false" : "true") + '"]').forEach((el) => {
el.setAttribute("contenteditable", b);
});
SetSelectorDisplay(".si_edit", b ? "flex" : "none");
document.getElementById("tbe").innerHTML = MakeEncheres(enr.jeu.enchere);
SetElvisArbre(b);
}
sw_edit.addEventListener("change", function () {
SetElvisEdit(this.checked);
});
const itm_open = document.getElementById("itm_open");
nom_jeu = document.getElementById("nom_jeu");
itm_open.addEventListener("click", () => {
closeMenu();
GetCbxDonnes()
.then((opt) => {
nom_jeu.innerHTML = opt;
OpenModalWnd("sel_donne");
})
.catch((e) => Erreur(e));
});
function GetSelJeu() {
let ar = [enr.id];
if (mesChoix.show_tree)
for (let el of all_sel) {
const id = Number(el.id.substr(2));
if (id != enr.id && ar.indexOf(id) == -1) ar.push(id); // pour éviter les doublons
}
return ar;
}
const bkp_lnk = document.getElementById("bkp_lnk");
const itm_export = document.getElementById("itm_export");
itm_export.addEventListener("click", () => {
closeMenu();
io.emit("export", GetSelJeu(), (r) => {
if (r.err) Erreur(r.err);
else {
bkp_lnk.setAttribute("href", r.fn);
bkp_lnk.click();
}
});
});
const get_import = document.getElementById("get_import");
get_import.addEventListener("change", () => {
closeMenu();
Info("Importation de " + get_import.files[0].name + " en cours...");
SendFile(get_import.files[0], "upload", get_import.files[0].name)
.then((r) => {
io.emit("import", r.fn, (foo) => {
if (foo == "OK") {
MakeTree();
OK("L'importation s'est bien passée");
} else Erreur(foo);
});
})
.catch((e) => Erreur(e));
});
function GetCbxDonnes() {
return new Promise((resolve, reject) => {
SQL_all("SELECT id,nom FROM donnes ORDER BY nom")
.then((rows) => {
let opt = "";
rows.forEach((el) => {
opt += '<option value="' + el.id + '"';
if (mesChoix.id_donne === el.id) opt += " selected";
opt += ">" + el.nom + "</option>";
});
resolve(opt);
})
.catch((e) => reject(e));
});
}
nom_jeu.addEventListener("change", function () {
CloseModalWnd();
OpenID(this.value);
});
function onSave() {
io.emit("save_donne", enr, (r) => {
if (r.err) Erreur(r);
else SetDirty(false);
});
}
itm_new.addEventListener("click", () => {
closeMenu();
io.emit(
"save_donne",
{
jeu: {
donneur: "",
vul: "",
txt1: "",
txt2: "",
donne: ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
enchere: [" ", " ", " ", " "],
entame: " ",
score: " ",
},
},
(r) => {
if (r.err) Erreur(r);
else if (r.changes == 1 && OpenID(r.lastInsertRowid)) {
SetElvisEdit(true);
if ("showPicker" in HTMLSelectElement.prototype) ed_donneur.showPicker();
else ed_donneur.focus();
}
}
);
});
/****************************/
/* EDITION DE LA DONNE */
/****************************/
const vulnerable = document.querySelector(".cartes .centre .vulnerable");
const classIf = (el, c, b) => {
if (b) el.classList.add(c);
else el.classList.remove(c);
};
function SetVulnerable(val) {
classIf(vulnerable, "NSred", val.startsWith("NS"));
classIf(vulnerable, "EWred", val.endsWith("EW"));
}
const ed_vul = document.getElementById("ed_vul");
ed_vul.addEventListener("change", function () {
enr.jeu.vul = this.value;
SetVulnerable(this.value);
SetDirty(true);
});
function SetElvisDonneur(val) {
classIf(vulnerable, "d_nord", val == "N");
classIf(vulnerable, "d_sud", val == "S");
classIf(vulnerable, "d_est", val == "E");
classIf(vulnerable, "d_ouest", val == "W");
}
const ed_donneur = document.getElementById("ed_donneur");
ed_donneur.addEventListener("change", function () {
enr.jeu.donneur = this.value;
SetElvisDonneur(this.value);
SetDirty(true);
});
enr_nom.addEventListener("input", function () {
enr.nom = this.innerText;
SetDirty(true);
});
function img2PCKT(txt) {
return txt.replace(/<img src="\/images\/Diamonds.png" \/>/g, "♦").replace(/<img src="\/images\/Clubs.png" \/>/g, "♣");
}
etxt.addEventListener("focus", function () {
this.innerText = enr.jeu.txt1;
});
etxt.addEventListener("blur", function () {
if (enr.jeu.txt1 != this.innerText) {
enr.jeu.txt1 = this.innerText.trim();
SetDirty(true);
}
etxt.innerHTML = PCKT2img(enr.jeu.txt1);
});
jtxt.addEventListener("focus", function () {
this.innerText = enr.jeu.txt2;
});
jtxt.addEventListener("blur", function () {
if (enr.jeu.txt2 != this.innerText) {
enr.jeu.txt2 = this.innerText.trim();
SetDirty(true);
}
jtxt.innerHTML = PCKT2img(enr.jeu.txt2);
});
entame.addEventListener("focus", function () {
this.innerText = enr.jeu.entame || "";
});
entame.addEventListener("blur", function () {
if (enr.jeu.entame != this.innerText) {
enr.jeu.entame = this.innerText.trim();
SetDirty(true);
}
entame.innerHTML = PCKT2img(enr.jeu.entame);
});
score.addEventListener("focus", function () {
this.innerText = enr.jeu.score || "";
});
score.addEventListener("blur", function () {
if (enr.jeu.score != this.innerText) {
enr.jeu.score = this.innerText.trim();
SetDirty(true);
}
score.innerHTML = PCKT2img(enr.jeu.score);
});
function AjouteBlancs(val) {
// ajouter les espaces
let st = "";
if (val != undefined && val.length) {
let idx = 0;
while (idx < val.length) {
const c = val[idx++];
if (c != " ") {
if (st != "") st += " ";
if (c == "1" && idx < val.length && val[idx] == "0") {
st += "10";
idx++;
} else st += c;
}
}
}
return st;
}
function RetireBlancs(val) {
// retirer les espaces
let st = "";
let idx = 0;
while (idx < val.length) {
const c = val[idx++];
if (c != " ") st += c;
}
return st;
}
function blurMain(idx, el) {
const st = RetireBlancs(el.innerText.toUpperCase());
if (enr.jeu.donne[idx] != st) {
enr.jeu.donne[idx] = st;
VerifierDonne(enr.jeu.donne);
SetDirty(true);
if (not_done == 13) {
AfficheDonne();
document.getElementById("enc_0").focus();
return;
}
}
el.innerText = AjouteBlancs(st);
}
function makeImage(sel) {
const foo = document.querySelector(sel);
html2canvas(foo, { scrollX: 0, scrollY: -window.scrollY }).then((canvas) => {
canvas.toBlob((blob) => {
navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]).then(() => OK("Image copiée dans le presse-papier"));
});
});
}
/****************************/
/* FENETRES POP-UP */
/****************************/
function OpenModalWnd(id) {
document.getElementById(id).style.display = "flex";
}
function CloseModalWnd() {
document.querySelectorAll(".modalBackground").forEach((el) => {
el.style.display = "none";
});
}
document.querySelectorAll(".modalBackground, .modalWnd > .puclose").forEach((el) => {
el.addEventListener("click", (e) => CloseModalWnd());
});
document.querySelectorAll(".modalWnd").forEach((el) => {
el.addEventListener("click", (e) => e.stopPropagation());
});
/****************************/
/* ARBRE DE SELECTION */
/****************************/
/* Crédit: https://iamkate.com/code/tree-views/
structure de base d'un noeud simple: <li>noeud</li>
structure de base d'un noeud avec enfants:
<li>
<details>
<summary>Nom du noeud</summary>
<ul>
<li>Enfant 1</li>
<li>Enfant 2</li>
</ul>
</details>
</li>
les enfants peuvent aussi imbriquer cette structure
// ATTENTION: ontoggle se déclenche si 'open' dans le HTML de départ
*/
function AddJeux(ar, id_node) {
let st = "";
ar.forEach((el) => {
st += '<li id="j_' + el.id + '" node="' + id_node + '" onclick="onJeuClick(event)" draggable="true" ondragstart="onJeuDrag(event)" hlp="' + (mesChoix.edit ? "Drag: Déplacer. Shift-drag: dupliquer le jeu dans un autre dossier. " : "") + (el.id == enr.id ? "ID:" + el.id + '" class="tree_sel' : "Clic: Ouvrir (ID: " + el.id + ")") + '" >' + (el.nom || "Donne n°" + el.id);
//st += '<img class="si_edit" src="/images/Hand_25px.png" id="jeux' + el.id + '" hlp="Sert à faire glisser le jeu vers un autre dossier ou la corbeille"/>';
st += "</li>";
});
return st;
}
async function renumerote(id, pos) {
try {
await SQL_run("UPDATE arbre SET pos=? WHERE id=?", [pos, id]);
} catch (e) {
Erreur(e);
}
}
function AddChilds(ar) {
if (ar == undefined) return "";
let st = "";
const ar_max = ar.length - 1;
ar.forEach((el, idx) => {
if (idx != el.pos) renumerote(el.id, idx);
st += '<li><details open><summary ondragstart="onNodeDrag(event)" ondragenter="onDragEnter(this)" ondragleave="onDragLeave(this)" onDrop="DropOnNode(event)" >';
st += '<span contenteditable node="' + el.id + '" onblur="onBlurNode(' + el.id + ',this.innerText)">' + el.itm + "</span>";
st += '<img class="si_edit" src="/images/Hand_25px.png" id="node' + el.id + '" hlp="Sert à faire glisser le dossier vers un autre dossier ou la corbeille (n\'efface pas les jeux)"/>';
if (idx > 0) st += '<img class="si_edit" src="/images/up_25px.png" onclick="UpNode(event,' + el.id + "," + el.pos + ' )" hlp="Remonter ce dossier dans la liste"/>';
if (idx < ar_max) st += '<img class="si_edit" src="/images/down_25px.png" onclick="DownNode(event,' + el.id + "," + el.pos + ')" hlp="Descendre ce dossier dans la liste"/>';
st += "</summary><ul>" + AddChilds(el.childs) + AddJeux(el.jeux, el.id) + "</ul></details>";
});
return st;
}
async function UpNode(ev, id, pos) {
ev.preventDefault();
ev.stopPropagation();
try {
await SQL_run("UPDATE arbre SET pos=? WHERE pos=?", [pos, pos - 1]);
await SQL_run("UPDATE arbre SET pos=? WHERE id=?", [pos - 1, id]);
MakeTree();
} catch (e) {
Erreur(e);
}
}
async function DownNode(ev, id, pos) {
ev.preventDefault();
ev.stopPropagation();
try {
await SQL_run("UPDATE arbre SET pos=? WHERE pos=?", [pos, pos + 1]);
await SQL_run("UPDATE arbre SET pos=? WHERE id=?", [pos + 1, id]);
MakeTree();
} catch (e) {
Erreur(e);
}
}
function SetEventTree() {
SetElvisArbre(mesChoix.edit);
document.querySelectorAll(".tree [hlp],.tree [alt]").forEach((itm) => {
itm.addEventListener("mouseover", (e) => {
e.stopPropagation();
Info(itm.getAttribute("hlp") || itm.getAttribute("alt"));
});
itm.addEventListener("mouseout", (e) => {
Info("");
});
});
}
function MakeTree(selector) {
if (ed_find.value.trim() != "") MakeRecherche();
else
io.emit("get_tree", (r) => {
arbre.innerHTML = '<ul class="tree">' + AddChilds(r.childs) + "<li><details open><summary>Non classés</summary><ul>" + AddJeux(r.jeux, 0) + "</ul></details></li></ul>";
SetEventTree();
if (selector != undefined) {
const el = arbre.querySelector(selector);
el.focus();
window.getSelection().selectAllChildren(el);
}
});
}
const all_sel = arbre.getElementsByClassName("tree_sel"); // en temps réel
const removeSelection = () => {
for (const el of all_sel) el.classList.remove("tree_sel");
arbre.querySelectorAll("#j_" + enr.id).forEach((el) => el.classList.add("tree_sel"));
};
arbre.addEventListener("dragover", (e) => {
if (mesChoix.edit) e.preventDefault();
});
node_trash.addEventListener("dragover", (ev) => {
ev.preventDefault();
let id = ev.dataTransfer.getData("application/internal");
if (id.startsWith("j_")) Warning("Attention: Vous allez effacer une donne");
});
node_trash.addEventListener("drop", (ev) => {
Info("");
ev.preventDefault();
let id = ev.dataTransfer.getData("application/internal");
if (id.startsWith("node")) {
SQL_run("DELETE FROM arbre WHERE id=" + id.substr(4))
.then((r) => {
// les triggers effacent automatiquement les data2tree et les enfants
MakeTree();
})
.catch((e) => Erreur(e));
} else if (id.startsWith("j_")) {
SQL_run("DELETE FROM donnes WHERE id=" + id.substr(2))
.then((r) => {
// les triggers effacent automatiquement les data2tree et les enfants
if (id.substr(2) != enr.id || OpenOrNew()) MakeTree();
})
.catch((e) => Erreur(e));
}
});
function onDragEnter(el) {
el.style.color = "blue";
el.style.fontWeight = "bold";
}
function onDragLeave(el) {
el.style.color = "inherit";
el.style.fontWeight = "inherit";
}
function onJeuClick(e) {
e.stopPropagation();
e.preventDefault();
if (e.ctrlKey == false) {
removeSelection();
OpenID(Number(e.target.id.substr(2)));
} else e.target.classList.add("tree_sel");
//SelectObjet(e.target.id, e.shiftKey, e.ctrlKey);
}
function onJeuDrag(ev) {
ev.dataTransfer.setData("application/internal", ev.target.id);
ev.dataTransfer.setData("text/plain", ev.target.id);
ev.dataTransfer.dropEffect = "move";
}
// Rappel: node_org=0 si non-classé (jeu sans entrée dans la table data2tree)
async function onJeuDrop(id, node_dest, make_copy) {
const node_org = Number(document.getElementById(id).getAttribute("node"));
if (node_dest == null) node_dest = 0;
try {
if (node_dest != 0 && (node_org == 0 || make_copy == true)) await SQL_run("INSERT INTO data2tree (id_donne,id_arbre) VALUES (" + id.substr(2) + "," + node_dest + ")");
else if (node_dest == 0) await SQL_run("DELETE FROM data2tree WHERE id_donne=" + id.substr(2) + " AND id_arbre=" + node_org);
else await SQL_run("UPDATE data2tree SET id_arbre=" + node_dest + " WHERE id_donne=" + id.substr(2) + " AND id_arbre=" + node_org);
MakeTree();
} catch (e) {
Erreur(e);
}
}
function onNodeDrag(ev) {
ev.dataTransfer.setData("application/internal", ev.target.id);
ev.dataTransfer.setData("text/plain", ev.target.id);
ev.dataTransfer.dropEffect = "move";
}
arbre.addEventListener("drop", (ev) => {
Info("");
ev.preventDefault();
let id = ev.dataTransfer.getData("application/internal");
if (id.startsWith("j_")) onJeuDrop(id, ev.target.getAttribute("node"), ev.shiftKey);
else if (id.startsWith("node")) unLinkNode(id.substr(4));
else if (id.startsWith("subnode")) AddNode();
});
async function unLinkNode(id) {
try {
await SQL_run("DELETE FROM arbre WHERE id=" + id);
MakeTree();
} catch (e) {
Erreur(e);
}
}
async function AddNode() {
try {
let pos = await SQL_get("SELECT MAX(pos)+1 as n FROM arbre");
let r = await SQL_run("INSERT INTO arbre (itm,pos) VALUES ('Nouveau dossier',?)", [pos.n || 0]);
MakeTree('details [node="' + r.lastInsertRowid + '"]');
} catch (e) {
Erreur(e);
}
}
async function DropOnNode(ev) {
// id est toujours sous la forme 'nodexxx'
const node_dest = ev.target.getAttribute("node");
ev.preventDefault();
ev.stopPropagation();
onDragLeave(ev.target.parentElement);
const dt = ev.dataTransfer;
let foo = dt.getData("application/internal");
try {
if (foo.startsWith("j_")) onJeuDrop(foo, node_dest, ev.shiftKey);
else if (foo == "subnode") {
await SQL_run("INSERT INTO arbre (id_parent,itm) VALUES (" + node_dest + ",'Nouveau dossier')");
MakeTree();
} else if (foo.startsWith("node")) {
await SQL_run("UPDATE arbre SET id_parent=" + node_dest + " WHERE id=" + foo.substr(4));
MakeTree();
}
} catch (e) {
Erreur(e);
}
}
function onSubnodeDrag(ev) {
ev.dataTransfer.setData("application/internal", "subnode");
ev.dataTransfer.setData("text/plain", "subnode");
ev.dataTransfer.dropEffect = "copy";
}
async function onBlurNode(id_node, txt) {
try {
// récupérer le nom
const id = " WHERE id=" + id_node;
r = await SQL_get("SELECT itm FROM arbre" + id);
if (r.itm != txt) {
await SQL_run("UPDATE arbre SET itm=?" + id, [txt]);
}
} catch (e) {
Erreur(e);
}
}
function PCKT2img(txt) {
if (txt == undefined) return "";
const rch = ed_find.value.trim();
if (rch != "") txt = txt.replace(new RegExp(rch, "ig"), '<span class="rch_txt">' + rch + "</span>");
return txt.replace(/♦/g, '<img src="/images/Diamonds.png" />').replace(/♣/g, '<img src="/images/Clubs.png" />').replace(/♠/g, '<img src="/images/Spades.png" />').replace(/♥/g, '<img src="/images/coeur.png" />').replace(/\n/g, "<br />");
}
function Masquer(val) {
ShowSelector(".encheres", val > 1);
ShowSelector(".si_mask2", val > 2);
ShowSelector(".si_mask3", val > 3);
}
ed_masquer.addEventListener("input", function () {
Masquer(this.value);
SetChoix("masquer", this.value);
});
/****************************/
/* RECHERCHER */
/****************************/
const ed_find = document.getElementById("ed_find");
const chk_find = document.getElementById("chk_find");
chk_find.addEventListener("change", function () {
mesChoix.chk_find = this.checked;
MakeTree();
});
const onRchChg = (val) => {
SetChoix("find", val);
MakeTree();
};
ed_find.addEventListener("input", function () {
onRchChg(this.value);
});
document.getElementById("del_rch").addEventListener("click", function () {
ed_find.value = "";
onRchChg("");
AfficheTextes();
});
function MakeRecherche() {
const like = " LIKE '%" + ed_find.value.replace(/'/g, "''") + "%'";
let stm = "SELECT id,nom FROM donnes WHERE nom" + like;
if (!chk_find.checked) stm += " OR data" + like;
SQL_all(stm)
.then((r) => {
arbre.innerHTML = '<ul class="tree">' + "<li><details open><summary>Résultat</summary><ul>" + AddJeux(r, 0) + "</ul></details></li></ul>";
SetEventTree();
if (r.length == 0) Warning("Pas de résultats");
else if (r.findIndex((el) => el.id == mesChoix.id_donne) == -1) OpenID(r[0].id);
else AfficheTextes();
})
.catch((e) => Erreur(e));
}
/****************************/
/* START */
/****************************/
function AfficheTextes() {
entame.innerHTML = PCKT2img(enr.jeu.entame);
score.innerHTML = PCKT2img(enr