// FIXME il ne faudrait pas pouvoir échanger les rangs d’un branchement nn avec son propre ssn (supprimer les snn des rangs possibles)
import $ from 'jquery'
import { notify } from 'sesajs/error'
import { testIntervalleDecimaux } from 'sesajs/regexp'
import { addElement } from 'sesajs/dom'
import { hasProp, stringify } from 'sesajs/object'
import { j3pAddContent, j3pAddTxt, j3pElement, j3pSetProps, j3pShowError } from 'src/legacy/core/functions'
import { actualiseGestionnaireEvenements } from './scene'
import { clone, creebtnsradios, creediv, creeinput, creelisteDeroulante, creespan, findPosDiv } from './fonctionsUtiles'
import { actualiseRangsSurScene, ajouteNodeGraphe, connexionNodes, getConnexionDansGraphe, getJspConnexionFromBranchementDomId, getNodeDansGraphe, reindexeBranchementsNode } from './fonctionsNodes'
import { dispatch, getConfig, getJsPlumbInstance, getObjetGraphe, getRootElt } from './store'
import { delObjetGrapheBranchement, delObjetGrapheBranchementProp, setObjetGrapheBranchement, setObjetGrapheBranchementProp, setObjetGrapheParam } from './actions'
import getSectionParams from 'src/editGraphe/getSectionParams'
// import { addGlobalChecker, startDomDeletionTracking } from 'src/lib/utils/debug'
// pour le closeButton
import './boiteDialogue.scss'
import Modal from 'src/lib/entities/dom/Modal'
/**
* Gère les boites de dialogue dans editgraphe
* @module editGraphe/boiteDialogue
*/
/**
* Appelé au clic sur le bouton radio PE / score
* @param {boolean} isScore
* @private
*/
function afficheScoreOuPe (isScore) {
j3pElement('affichage_pe').style.display = isScore ? 'none' : 'block'
j3pElement('affichage_score').style.display = isScore ? 'block' : 'none'
}
function supprimeBranchement (branchementDomId) {
const objetGraphe = getObjetGraphe()
const instanceJsPlumb = getJsPlumbInstance()
// faut récupérer ça avant de le supprimer d’objetGraphe
const branchementScene = getJspConnexionFromBranchementDomId(branchementDomId)
// mais on le vire effectivement plus tard, pour le conserver si la suppression dans objetGraphe plante
// supprime le branchement dans objetGraphe
const branchement = getConnexionDansGraphe(branchementDomId)
// S’il y a une ppte snn il faut aussi supprimer le branchement correspondant...
if (hasProp(branchement.branchement, 'snn')) {
for (const branchementIndex in objetGraphe.nodes[branchement.sourceNodeId].branchements) {
if (!hasProp(objetGraphe.nodes[branchement.sourceNodeId].branchements[branchementIndex], 'nn')) { // c’est le branchement qui n’a pas de nn
const branchementDomIdBis = objetGraphe.nodes[branchement.sourceNodeId].branchements[branchementIndex].branchementDomId
const branchementSceneBis = getJspConnexionFromBranchementDomId(branchementDomIdBis)
instanceJsPlumb.deleteConnection(branchementSceneBis)
// delete objetGraphe.nodes[branchement.sourceNodeId].branchements[branchementIndex]
dispatch(delObjetGrapheBranchement(branchement.sourceNodeId, branchementIndex))
}
}
}
dispatch(delObjetGrapheBranchement(branchement.sourceNodeId, branchement.branchementIndex))
// ré-indexe les rangs des branchements du node duquel partait ce branchement supprimé
reindexeBranchementsNode(branchement.sourceNodeId, false)
// supprime le branchement de la scene
instanceJsPlumb.deleteConnection(branchementScene)
// ALEX : commenté car faisait buguer : au déplacement des nodes, on se retrouvait avec les flèches mal placées.
// Les _jsPlumb_endpoint n’avais plus de parent (la scène normalement) d’où bug dans la fonction
// adjustForParentOffsetAndScroll du fichier jsPlumb-1.4.0-RC1 $("._jsPlumb_endpoint").remove()
// fermela boite de dialogue du branchement qu’on vient de supprimer
$('#edgMasque, #modaleBranchement').remove()
} // supprimeBranchement
function afficheMaxParcours (isShown) {
j3pElement('affichage_max_parcours').style.display = isShown ? 'block' : 'none'
}
/**
* Affiche la boite modale
* @param {object} options
* @param {string} [options.id=edgModale]
* @param {string} [options.contenu] Le contenu html à afficher dans la modale
* @param {number} [options.width=200] Largeur
* @param {number} [options.height=150] Hauteur
* @param {boolean} [options.booltitre=false] Passer true pour ajouter un titre dans la modale
* @param {boolean} [options.croix=true] Ajoute une croix pour fermer la modale
* @param {string} [options.titre='il faudrait un titre'] Titre, devrait être fourni si booltitre, ignoré sinon
* @param {boolean} [options.autoClose=true] Passer false pour que la modale ne se ferme pas quand on clique à l’extérieur
* @return {HTMLDivElement} Le conteneur de la modale
* @private
*/
function edgModale ({
id = 'edgModale',
contenu = '',
width = 200,
height = 150,
booltitre = false,
croix = true,
titre = 'il faudrait un titre',
autoClose = true
}) {
const dropModale = () => $(`#${id}, #edgMasque`).remove()
let boite = j3pElement(id, null)
if (boite) {
// on a oublié de virer la précédente avant, bizarre, on le fait ici
console.warn(`Appel de edgModale pour l’id ${id} qui existait déjà`)
dropModale()
}
const rootElt = getRootElt()
if (!rootElt) throw new Error('Il manque l’élément html conteneur de l’application')
const larg = $(window).width()
const haut = $(window).height()
const style = {
top: `${(haut - height - 100) / 2}px`,
left: `${(larg - width - 80) / 2}px`,
minWidth: `${width}px`,
minHeight: `${height}px`,
display: 'none' // on la crée invisible et c’est le fadeIn plus bas qui l’affiche
}
boite = addElement(rootElt, 'div', { id, className: 'boiteDialogue', style })
if (booltitre) {
addElement(boite, 'div', { id: `${id}titre`, className: 'titre', content: titre })
}
// on met toujours le div ${id}contenu, même vide
addElement(boite, 'div', { id: `${id}contenu`, className: 'contenu' }).innerHTML = contenu
if (croix) {
const croixElt = addElement(boite, 'div', { className: 'croix' })
croixElt.addEventListener('click', dropModale, false)
}
const masqueElt = addElement(rootElt, 'div', { id: 'edgMasque', style: { display: 'none' } })
const $masque = $(masqueElt)
$masque.css({ filter: 'alpha(opacity=50)' }).fadeIn(500)
// on autorise uniquement à fermer la boite modale par clic sur le masque lorsqu’on n’est pas en paramétrage de noeud
if (autoClose) $masque.click(dropModale)
$(boite).fadeIn()
return boite
}
export default edgModale
/**
* Affiche un message d’erreur
* @param {string} errorMsg Le message d’erreur à afficher
* @param {object} options Les attributs à passer à edgModale (titre et contenu seront imposés)
*/
export function j3pModaleError (errorMsg = 'Une erreur est survenue', options) {
if (!options) options = {}
options.titre = 'Erreur'
options.booltitre = true
options.contenu = errorMsg
edgModale(options)
}
/**
* Ajoute #contenu_branchement pour un branchement snn non paramétrable
* @private
*/
function afficheModaleSnnNonParametrable () {
const modaleElt = edgModale({ width: '320', height: '250' })
addElement(modaleElt, 'h3', { content: 'Configuration du branchement' }) // wrapper div #branchement_entete viré
addElement(modaleElt, 'div', { id: 'contenu_branchement' }).innerHTML = 'Ce branchement n’est pas paramétrable : il est le résultat d’un maximum de passages atteint pour un nœud (dans un branchement qui boucle vers ce même nœud ou un nœud précédent du graphe).<br/>On peut modifier le message qui sera affiché à l’élève en paramétrant ce branchement parent.'
}
/**
* Ajoute #contenu_branchement pour un branchement qui existe déjà
* @private
*/
function suiteParametrageExistant () {
const modaleElt = edgModale({ width: '320', height: '250', booltitre: false })
addElement(modaleElt, 'h3', { content: 'Configuration du branchement' })
addElement(modaleElt, 'div', { id: 'contenu_branchement' }).innerHTML = 'Ce branchement existe déjà !<br>Vous pouvez en modifier les paramètres en cliquant dessus.'
}
/**
* Retourne true s’il y a des nodes non snn avant branchementIndex (non compris)
* @param {Branchement[]} branchements liste de branchements partant du node concerné
* @param {number} branchementIndex
* @return {boolean}
* @private
*/
const hasPrevious = (branchements, branchementIndex) => branchements.slice(0, branchementIndex).some(br => !br.isSnn)
/**
* Retourne true s’il y a des nodes non snn après branchementIndex (non compris)
* @param {Branchement[]} branchements liste de branchements partant du node concerné
* @param {number} branchementIndex
* @return {boolean}
* @private
*/
const hasNext = (branchements, branchementIndex) => branchements.slice(branchementIndex + 1).some(br => !br.isSnn)
/**
* Affiche la modale d’édtion du branchement
* @param {string} branchementDomId
*/
export function dialogueBranchement (branchementDomId) {
/**
* Complète le formulaire d’édition du branchement
* - si .pe contient plus d’un élément c une section quantitative (ou plutôt si contient pe_1)
* => on laisse la possibilité de jouer sur le score/pe sinon que sur le score
* Une fois terminé, à la validation, on récupère les infos, on renseigne objetGraphe.nodes[branchement.sourceNodeId].branchements[branchement.branchementIndex]
* On ne change que la conclusion et pe et/ou score (vérifier si existant déjà)
* DONE : récupérer la pe et ou le score actuel, en cas de section quantitative, on renseigne le score uniquement (même si c pe inf 0)
* DONE : si cas particulier du snn, cad si branchement.branchement a une ppte isSnn alors pas de configuration du noeud possible (msg pour renvoyer à l’autre branchement)
* DONE : en cas de branchement d’un noeud sur lui même (sourceid=targetid par ex en ligne 26, à voir si pas plus simple de faire une recherche sur branchement.branchement.nn ?) alors interface spéciale ou l’on demande de compléter aussi les snn et node Fin supplémentaire à la validation
* DONE : récupérer toutes les infos à la validation
* DONE : gérer le sans condition
* DONE : message undefined si nouvel auto branchement
* TODO : vérifier que avoir nn de type string et snn de type int ne pose pas de pb à l’execution du graphe (en testant par ex ["4","parallelesremed1",[{"nn":"7","pe":">=0.8","conclusion":"Fin"},{"nn":"4","pe":"<=0.79","conclusion":"Réessayons l\exercice précédent","sconclusion":"Parcours terminé","snn":6,"max":3},{"inclinaison":"[15;40]","nbrepetitions":3}]];)
* DONE : si branche deja existante, message pour pas possible ?
* TODO : Bouton Annuler les modifications
* @private
* @param {Connexion} connexion
* @param {string} sectionName
* @param {boolean} autobranchement
*/
function suiteParametrageBranchement (connexion, sectionName, autobranchement) {
// fonction qui permet de constuire la liste déroulante pour les embranchements de noeuds possibles
function construitListeSnn (container) {
const select = addElement(container, 'select', { id: 'deroulante_snn' })
addElement(select, 'option', { value: 'rien' })
const noeudActuel = jspConnexion.sourceId.substr(4)
for (const nodeId of Object.keys(objetGraphe.nodes)) {
if (nodeId !== noeudActuel) {
addElement(select, 'option', { content: nodeId, value: nodeId })
}
}
addElement(select, 'option', { content: 'Ajouter un nœud fin', value: 'ajouter_fin' })
}
const objetGraphe = getObjetGraphe()
const { sectionsParams } = getConfig()
const sectionParams = sectionsParams[sectionName]
const isSectionQuali = Array.isArray(sectionParams.pe) && sectionParams.pe.length
const brIndex = Number(connexion.branchementIndex)
const branchements = objetGraphe.nodes[connexion.sourceNodeId].branchements
const modaleElt = edgModale({ id: 'modaleBranchement', width: '620', height: '450', croix: false })
addElement(modaleElt, 'h3', { content: 'Configuration du branchement' })
const contenu = addElement(modaleElt, 'div', { id: 'contenu_branchement', content: 'Rang de la condition : ' })
const rang = brIndex + 1
addElement(contenu, 'span', { id: 'dialogueBranchementRang', content: String(rang) })
// console.debug('branchement', brIndex, 'parmi', branchements, 'hasNext ?', hasNext(branchements, brIndex), 'hasPrev ?', hasPrevious(branchements, brIndex))
let style = hasPrevious(branchements, brIndex) ? {} : { visibility: 'hidden' }
addElement(contenu, 'div', { className: 'flecheBas', title: 'Descendre', style })
.addEventListener('click', modifierRangCondition.bind(null, branchementDomId, false))
style = hasNext(branchements, brIndex) ? {} : { visibility: 'hidden' }
addElement(contenu, 'div', { className: 'flecheHaut', title: 'Monter', style })
.addEventListener('click', modifierRangCondition.bind(null, branchementDomId, true))
// console.debug('connexion', connexion, 'avec graphe', objetGraphe)
j3pAddTxt(contenu, '(utiliser les flèches pour modifier le rang de la condition)')
addElement(contenu, 'br')
addElement(contenu, 'br')
addElement(modaleElt, 'div', { id: 'contenu_score_ou_pe' })
const scorePeElt = addElement(modaleElt, 'div')
const affScoreElt = addElement(scorePeElt, 'div', { id: 'affichage_score' })
const affPeElt = addElement(scorePeElt, 'div', { id: 'affichage_pe' })
if (isSectionQuali) {
// on ajoute les boutons radio pour choisir la phrase d’état (pe_1, pe_2…)
const container = j3pElement('contenu_score_ou_pe')
container.style.textAlign = 'center'
let form = addElement(container, 'form')
let label = addElement(form, 'label', { content: 'Phrase d’état' })
let input = addElement(label, 'input', { type: 'radio', name: 'choix_score_pe', value: 'PE', id: 'radio_pe' })
input.addEventListener('click', () => afficheScoreOuPe(false))
label = addElement(form, 'label', { content: 'Score' })
input = addElement(label, 'input', { type: 'radio', name: 'choix_score_pe', value: 'score', id: 'radio_score' })
input.addEventListener('click', () => afficheScoreOuPe(true))
j3pAddTxt(contenu, 'Pour ce nœud, on peut choisir d’orienter suivant la Phrase d’État ou le score (réel compris entre 0 et 1) :')
addElement(contenu, 'br')
// on passe à la gestion de la pe
affPeElt.style.display = 'none'
j3pAddTxt(affPeElt, 'Choix de la Phrase d’État : (on peut en choisir plusieurs pour un même embranchement)')
addElement(affPeElt, 'br')
// form des pe + sans condition
form = addElement(affPeElt, 'form', { id: 'deroulante_pe' })
for (const peObj of sectionParams.pe) {
// à priori y’a qu’une seule clé dans chaque objet élément de params.pe
for (const [key, value] of Object.entries(peObj)) {
const label = addElement(form, 'label', { content: value })
addElement(label, 'input', { type: 'checkbox', id: `checkbox_${key}`, value: key })
}
}
label = addElement(form, 'label', { content: 'Sans condition' })
input = addElement(label, 'input', {
type: 'checkbox',
name: 'choix_sans_condition',
id: 'radio_sans_condition_pe'
})
input.addEventListener('click', () => sansCondition(sectionName))
// et on masque ça
affScoreElt.style.display = 'none'
} // isSectionQuali
// dans tous les cas on a l’affichage du score (masqué si besoin)
j3pAddTxt(affScoreElt, 'Score de l’élève (réel compris entre 0 et 1) : ')
const selectScoreElt = addElement(affScoreElt, 'select', { id: 'deroulante_score' })
addElement(selectScoreElt, 'option', { value: 'rien' })
addElement(selectScoreElt, 'option', { content: '≤', value: 'inf' })
addElement(selectScoreElt, 'option', { content: '≥', value: 'sup' })
const scoreInput = creeinput({ id: 'input_score', papa: affScoreElt, value: '' })
scoreInput.addEventListener('blur', () => {
if (scoreInput.value.includes(',')) scoreInput.value = scoreInput.value.replace(/,/, '.')
const score = Number(scoreInput.value)
if (score >= 0 && score <= 1) return
j3pShowError(`Score invalide (doit être compris entre 0 et 1) : ${scoreInput.value}`)
})
scoreInput.style.width = '25px'
const label = addElement(affScoreElt, 'label', { content: 'Sans condition', style: { marginLeft: '1em' } })
const cbSsCond = addElement(label, 'input', {
type: 'checkbox',
name: 'choix_sans_condition',
id: 'sans_condition_score'
})
cbSsCond.addEventListener('click', () => sansCondition())
// startDomDeletionTracking(affScoreElt.parentNode, '#sans_condition_score')
// avec la ligne suivante on a une fct globale c que l’on peu appeler depuis la console du navigateur ou dans notre code
// avec par ex c('avant truc')
// addGlobalChecker(cbSsCond, 'sans_condition_score', 'c', { withLog: true })
const conclusionDialogue = connexion.branchement.conclusion || ''
addElement(scorePeElt, 'br')
const labelMsg = addElement(scorePeElt, 'label', { content: 'Message affiché en fin de nœud (laisser vide pour passer directement au nœud suivant) : ' })
addElement(labelMsg, 'input', { type: 'text', size: '70', id: 'input_conclusion', value: conclusionDialogue })
addElement(scorePeElt, 'br')
addElement(scorePeElt, 'br')
// cas où les noeuds de départ et d’arrivée sont identiques, il faut les champs supplémentaires
if (autobranchement) {
j3pAddTxt(scorePeElt, 'A l’issu de ce branchement, l’élève sera orienté sur le même nœud, avec comme paramètres complémentaires :')
// une liste pour ces champs
const ul = addElement(scorePeElt, 'ul')
let li = addElement(ul, 'li')
let label = addElement(li, 'label', { content: 'Un nombre maximal de passages dans ce nœud :' })
// #input max
addElement(label, 'input', { id: 'input_max', type: 'number', min: 2, step: 1, style: { width: '35px' } })
// #input_sconclusion
li = addElement(ul, 'li')
label = addElement(li, 'label', { content: 'Un message en cas d’echec après toutes les tentatives :' })
addElement(label, 'input', { type: 'text', id: 'input_sconclusion', size: 65 })
// target
li = addElement(ul, 'li')
label = addElement(li, 'label', { content: 'Le nœud vers lequel l’élève sera alors orienté :' })
construitListeSnn(label)
} else {
// Nouveauté J3P Novembre 2016 : possibilité de boucler sur une partie du graphe, et non sur un noeud lui même
j3pAddTxt(scorePeElt, 'Ce branchement renvoie-t-il vers un nœud précédent du graphe ?')
const form = addElement(scorePeElt, 'form')
let label = addElement(form, 'label', { content: 'Oui' })
addElement(label, 'input', { type: 'radio', name: 'choix_max_parcours', value: 'max_parcours_oui', id: 'radio_max_parcours_oui' })
label.addEventListener('click', afficheMaxParcours.bind(null, true))
label = addElement(form, 'label', { content: 'Non' })
addElement(label, 'input', { type: 'radio', name: 'choix_max_parcours', value: 'max_parcours_non', id: 'radio_max_parcours_non', checked: true })
label.addEventListener('click', afficheMaxParcours.bind(null, false))
const divMaxParcours = addElement(scorePeElt, 'div', { id: 'affichage_max_parcours' })
// console.debug('#affichage_max_parcours créé')
j3pAddTxt(divMaxParcours, 'Cette portion du graphe est une boucle, il faut donc préciser :')
const ul = addElement(divMaxParcours, 'ul')
let li = addElement(ul, 'li')
label = addElement(li, 'label', { content: 'Un nombre maximal de passages dans ce nœud' })
addElement(label, 'input', { type: 'number', id: 'input_max', min: 2, step: 1, style: { width: '35px' } })
li = addElement(ul, 'li')
label = addElement(li, 'label', { content: 'Un message à l’issue de tous les passages dans ce nœud' })
addElement(label, 'input', { type: 'text', id: 'input_sconclusion', size: 65 })
li = addElement(ul, 'li')
label = addElement(li, 'label', { content: 'Le nœud vers lequel l’élève sera alors orienté :' })
construitListeSnn(label)
// et on cache par défaut
divMaxParcours.style.display = 'none'
}
// on a soit autobranchement soit un #affichage_max_parcours dans le dom
/* avec ce code
html = '<input type="button" onclick="supprimeBranchement(\'' + branchementDomId + '\',\'' + true + '\');" value="Supprimer ce branchement">'
scorePeElt.innerHTML += html
le listener sur la checkbox "sans condition" se faisait dégager (les nodes étaient retirés / ajoutés du DOM
mais perdaient leur listener au passage, pb tracé avec MutationObserver)
*/
addElement(scorePeElt, 'input', { type: 'button', value: 'Supprimer ce branchement' })
.addEventListener('click', supprimeBranchement.bind(null, branchementDomId))
const divValider = addElement(modaleElt, 'div', { id: 'btn_valider' })
const btnValider = addElement(divValider, 'input', { type: 'button', value: 'Valider ' })
btnValider.addEventListener('click', valideBranchement.bind(null, branchementDomId, sectionName, autobranchement))
$('#contenu_branchement').focus()
// si c’est un nouveau branchement, il n’a pas encore de ppte pe ou score, sinon on la récupère pour la renseigner
if (hasProp(connexion.branchement, 'pe') || hasProp(connexion.branchement, 'score')) {
if (isSectionQuali && hasProp(connexion.branchement, 'pe')) {
j3pElement('radio_pe').checked = true
affPeElt.style.display = 'block'
const tab = connexion.branchement.pe.split(',')
if (tab[0].indexOf('condition') !== -1) {
j3pElement('radio_sans_condition_pe').checked = true
sansCondition(sectionName)
} else {
for (let k = 0; k < tab.length; k++) {
const numeroPe = tab[k].split('pe_')[1] // PB : il peut y avoir "pe":"pe_1,pe_2" d’où le split
j3pElement('checkbox_pe_' + numeroPe).checked = true
}
}
} else {
// une section qualitative où on a orienté via le score ou une section quantitative (utilisant pe ou score)
let score, inegalite
if (isSectionQuali) {
j3pElement('radio_score').checked = true
affScoreElt.style.display = 'block'
}
const chaineScore = (hasProp(connexion.branchement, 'score')) ? connexion.branchement.score : connexion.branchement.pe
if (chaineScore.indexOf('<') !== -1) {
score = chaineScore.split('<')[1]
inegalite = 1
}
if (chaineScore.indexOf('>') !== -1) {
score = chaineScore.split('>')[1]
inegalite = 2
}
if (chaineScore.indexOf('<=') !== -1) {
score = chaineScore.split('<=')[1]
inegalite = 1
}
if (chaineScore.indexOf('>=') !== -1) {
score = chaineScore.split('>=')[1]
inegalite = 2
}
if (chaineScore.indexOf('condition') !== -1) {
j3pElement('sans_condition_score').checked = true
sansCondition() // inutile avec un listener change, mais important si listener click
} else {
selectScoreElt.selectedIndex = inegalite
const inputScore = j3pElement('input_score')
inputScore.value = score
}
}
}
if (autobranchement) {
// dans le cas d’un auto-branchement (node depart=node arrivee)
// il faut aussi renseigner le max/snn/sconclusion (sauf si c’est un nouveau branchement)
j3pElement('deroulante_snn').value = connexion.branchement.snn ?? ''
j3pElement('input_max').value = connexion.branchement.max ?? 2
if (connexion.branchement.sconclusion) {
j3pElement('input_sconclusion').value = connexion.branchement.sconclusion
}
}
// et si maxParcours on répercute aussi les infos
if (connexion.branchement.maxParcours) {
j3pElement('affichage_max_parcours').style.display = 'block'
j3pElement('radio_max_parcours_oui').checked = true
j3pElement('deroulante_snn').value = connexion.branchement.snn
j3pElement('input_max').value = connexion.branchement.maxParcours
if (connexion.branchement.sconclusion) {
j3pElement('input_sconclusion').value = connexion.branchement.sconclusion
}
}
} // suiteParametrageBranchement
const objetGraphe = getObjetGraphe()
let isDepartArriveeIdentique = false
const connexion = getConnexionDansGraphe(branchementDomId)
if (connexion === false) {
return console.error(Error('dialogueBranchement sans connexion correspondante dans le graphe'))
}
const jspConnexion = getJspConnexionFromBranchementDomId(branchementDomId)
if (jspConnexion.sourceId === jspConnexion.targetId) {
isDepartArriveeIdentique = true
}
// on détecte ici si le branchement est déjà existant (pour savoir s’il faut le créer)
let nbBranchement = 0
objetGraphe.nodes[connexion.sourceNodeId].branchements.forEach(branchement => {
if (
(
branchement &&
hasProp(branchement, 'nn') &&
branchement.nn === connexion.branchement.nn
) || (
hasProp(branchement, 'snn') &&
branchement.snn === connexion.branchement.nn
)
) {
nbBranchement++
}
})
if (nbBranchement === 2) {
supprimeBranchement(jspConnexion.id)
suiteParametrageExistant()
return
}
const sectionName = objetGraphe.nodes[connexion.sourceNodeId].section
// Cas particulier d’un branchement non paramétrable : le snn
if (connexion.branchementisSnn) {
afficheModaleSnnNonParametrable()
return
}
getSectionParams(sectionName).then(() => {
suiteParametrageBranchement(connexion, sectionName, isDepartArriveeIdentique)
}).catch(j3pShowError)
}
/**
* Appelée au clic sur le btn sans condition qui gèle donc le choix de la pe ou du score
* @private
* @param {string} [sectionName] si fourni on agit sur la pe et sinon sur le score
*/
function sansCondition (sectionName) {
if (sectionName) {
// on gèle/degele les cases à cocher des pe
const config = getConfig()
const section = config.sectionsParams[sectionName]
if (j3pElement('radio_sans_condition_pe').checked) {
for (let i = 0; i < section.pe.length; i++) {
j3pElement('checkbox_pe_' + (i + 1)).disabled = true
}
} else {
for (let i = 0; i < section.pe.length; i++) {
j3pElement('checkbox_pe_' + (i + 1)).disabled = false
}
}
} else {
// on gèle/degele la liste_deroulante et l’input du score
const disabled = j3pElement('sans_condition_score').checked
j3pElement('deroulante_score').disabled = disabled
j3pElement('input_score').disabled = disabled
}
}
/**
* Change le rang d’un branchement
* @param {string} branchementDomId
* @param {boolean} isUp
* @private
*/
function modifierRangCondition (branchementDomId, isUp) {
const objetGraphe = getObjetGraphe()
let echangeEffectif = false
const branchementEdite = getConnexionDansGraphe(branchementDomId)
const nodeId = branchementEdite.sourceNodeId
const branchementsNode = objetGraphe.nodes[nodeId].branchements
// console.debug('modifierRangCondition voit les branchements', branchementsNode)
let currentIndex = branchementEdite.branchementIndex
if (typeof currentIndex === 'string') {
console.warn(`branchementIndex string (${currentIndex})`, branchementEdite)
currentIndex = parseInt(currentIndex)
}
if (!Number.isFinite(currentIndex) || currentIndex < 0) throw Error(`index de branchement invalide (${branchementEdite.branchementIndex}) pour le nœud ${nodeId}`)
// si modification possible
if (!isUp && hasPrevious(branchementsNode, currentIndex)) {
// y’a un précédent non snn (normalement on devrait jamais avoir de snn en 0, mais plus simple de garder ce test générique sur le slice)
const previousIndex = currentIndex - 1
let previous = clone(branchementsNode[previousIndex])
// modification dans objetGraphe
const current = clone(branchementsNode[currentIndex])
dispatch(setObjetGrapheBranchement(nodeId, previousIndex, current))
dispatch(setObjetGrapheBranchement(nodeId, currentIndex, previous))
currentIndex--
while (previous.isSnn) {
// faut descendre encore d’un cran
previous = clone(branchementsNode[currentIndex - 1])
dispatch(setObjetGrapheBranchement(nodeId, currentIndex, previous))
dispatch(setObjetGrapheBranchement(nodeId, currentIndex - 1, current))
currentIndex--
}
echangeEffectif = true
} else if (isUp && hasNext(branchementsNode, currentIndex)) {
// le slice nous donne les nœud suivants, on en veut au moins un sans isSnn pour monter d’un cran
// modification dans objetGraphe
let next = clone(branchementsNode[currentIndex + 1])
const current = clone(branchementsNode[currentIndex])
dispatch(setObjetGrapheBranchement(nodeId, currentIndex + 1, current))
dispatch(setObjetGrapheBranchement(nodeId, currentIndex, next))
currentIndex++
while (next.isSnn) {
// faut monter d’un cran supplémentaire
next = clone(branchementsNode[currentIndex + 1])
dispatch(setObjetGrapheBranchement(nodeId, currentIndex, next))
dispatch(setObjetGrapheBranchement(nodeId, currentIndex + 1, current))
currentIndex++
}
echangeEffectif = true
}
if (echangeEffectif) {
// modification dans la boîte de dialogue
j3pAddContent('dialogueBranchementRang', currentIndex + 1, { replace: true })
// refresh des flèches
try {
// on passe par visibility et pas display car on veut garder la place occupée à l’écran (et la css fixe du inline-block mais pourrait changer d’avis)
document.querySelector('#contenu_branchement .flecheBas').style.visibility = hasPrevious(branchementsNode, currentIndex) ? 'visible' : 'hidden'
document.querySelector('#contenu_branchement .flecheHaut').style.visibility = hasNext(branchementsNode, currentIndex) ? 'visible' : 'hidden'
} catch (error) {
console.error(error)
}
// modification sur la scene
actualiseRangsSurScene(nodeId)
}
}
function estNoeudFin (nn, objetGraphe) {
return hasProp(objetGraphe, 'nodes') &&
objetGraphe.nodes[nn] &&
hasProp(objetGraphe.nodes[nn], 'section') &&
objetGraphe.nodes[nn].section.toLowerCase() === 'fin'
}
/**
* @param {string} branchementDomId
* @param {string} sectionName
* @param {boolean} autobranchement
*/
function valideBranchement (branchementDomId, sectionName, autobranchement) {
let objetGraphe = getObjetGraphe()
const config = getConfig()
const section = config.sectionsParams[sectionName]
let pe, testPeAFaire, testScoreAFaire, nodeId, posDiv, posX, posY, labelConnection
testPeAFaire = false
testScoreAFaire = false
labelConnection = ''
const noeudTemp = {}
const connexion = getConnexionDansGraphe(branchementDomId)
const branche = objetGraphe.nodes[connexion.sourceNodeId].branchements[connexion.branchementIndex]
if (j3pElement('affichage_score').style.display === 'none') {
testScoreAFaire = true
// on travaille avec LA PE
if (j3pElement('radio_sans_condition_pe').checked) {
noeudTemp.pe = 'sans+condition'
labelConnection = 'Pas de condition'
} else {
// on récupère la liste des PE
pe = ''
for (let i = 0; i < section.pe.length; i++) {
if (j3pElement('checkbox_pe_' + (i + 1)).checked) {
pe += 'pe_' + (i + 1) + ','
// TODO : pour le label, récupérer section.pe[i] pour le mettre dans une ppte de objetGraphe (ou tout simplement dans labelConnection ci dessous)
// dans definit_label, il faudra aussi récupérer les pe de config.sectionsParams pour afficher la bonne PE et non son nom
}
}
if (pe === '') {
return j3pShowError('Il faut choisir au moins une phrase d’état !')
}
pe = pe.substring(0, pe.length - 1)
noeudTemp.pe = pe
labelConnection = 'Si Phrase d`état=' + pe
}
} else {
// ici c’est le score
// cas particulier : si le noeud avait auparavant une ppte pe qui travaillait en fait sur le score (section quantitative uniquement donc) on a travaillé sur le score donc il faut maintenant dégager cette ancienne ppte pe
testPeAFaire = true
if (j3pElement('sans_condition_score').checked) {
noeudTemp.score = 'sans+condition'
labelConnection = 'Pas de condition'
} else {
// on reconstruit le score
const value = Number(j3pElement('input_score').value.replace(/,/, '.'))
if (isNaN(value) || Number(value) < 0 || Number(value) > 1) {
return j3pShowError('Le score doit être un nombre compris entre 0 et 1.')
}
switch (j3pElement('deroulante_score').selectedIndex) {
case 0:
return j3pShowError('Il faut choisir le symbole inférieur ou supérieur dans la liste déroulante !')
case 1:
noeudTemp.score = '<=' + j3pElement('input_score').value
labelConnection = 'Si Score' + noeudTemp.score
break
default :
noeudTemp.score = '>=' + j3pElement('input_score').value
labelConnection = 'Si Score' + noeudTemp.score
break
}
}
}
noeudTemp.conclusion = j3pElement('input_conclusion').value.replace(/'/g, '’')
const divMaxParcours = j3pElement('affichage_max_parcours', null)
if (!autobranchement && !divMaxParcours) {
notify(Error('Dans valideBranchement on a ni autobranchement ni #affichage_max_parcours, c’est pas normal'))
}
if (autobranchement || (divMaxParcours && divMaxParcours.style.display === 'block')) {
// recup de input_max, input_sconclusion, deroulante_snn. Différents cas, si nouveau branchement (cad quand ce branchement n’a pas encore de ppte snn) : créer un branchement supplémentaire (voir completeBranchement), si branchement existant il faut regarder si le snn a été modifié (si oui il faut supprimer l’ancien branchement et en refaire un...)
if (j3pElement('input_max').value === '') {
return j3pShowError('Il faut préciser le nombre de retour maximal dans ce nœud (pour éviter de le faire recommencer indéfiniment)')
}
const snnValue = j3pElement('deroulante_snn')?.value ?? ''
if (snnValue === 'rien' || snnValue === '') {
return j3pShowError('Il faut choisir vers quel nœud l`élève sera orienté en cas d`échec.')
}
// si on est arrivé là c’est qu’il n’y a pas de msg d’erreur
noeudTemp.sconclusion = j3pElement('input_sconclusion').value
if (autobranchement) {
noeudTemp.max = j3pElement('input_max').value
} else {
noeudTemp.maxParcours = j3pElement('input_max').value
}
nodeId = connexion.sourceNodeId
actualiseGestionnaireEvenements(false) // ceci, grace au false, permet d’éviter de reafficher la boite de dialogue de branchement a la connection de deux nodes en dessous :
if (hasProp(connexion.branchement, 'snn') && connexion.branchement.snn !== snnValue) {
// on commence par supprimer le branchement maintenant inutile de objetGraphe (car va contenir le mauvais id de branchementDomId),
// on supprime le branchement de la scène
// on récupère le bon branchementDomId
for (let i = 0; i < objetGraphe.nodes[nodeId].branchements.length; i++) {
if (hasProp(objetGraphe.nodes[nodeId].branchements[i], 'isSnn')) {
supprimeBranchement(objetGraphe.nodes[nodeId].branchements[i].branchementDomId)
}
}
}
if (!hasProp(connexion.branchement, 'snn') || (hasProp(connexion.branchement, 'snn') && connexion.branchement.snn !== snnValue)) {
// c’est donc un nouvel autobranchement, ou alors on a modifié un existant (on a déjà dégagé les anciens branchements du coup) faut créer un lien sur la scène et un autre branchement dans objetGraphe
if (snnValue === 'ajouter_fin') {
// vers un nouveau noeud qu’il faut ajouter sur ma scène
posDiv = findPosDiv(j3pElement('node' + nodeId))
posX = posDiv.x + 30
posY = posDiv.y + 30
const node = ['1', 'fin', []]
ajouteNodeGraphe(node, posX, posY, 'fin')
objetGraphe = getObjetGraphe()// obligé de le getter à nouveau car on a changé le maxNumeroNode...
connexionNodes('node' + nodeId, 'node' + objetGraphe.maxNumeroNode)
noeudTemp.snn = objetGraphe.maxNumeroNode
objetGraphe = getObjetGraphe()// obligé de le getter à nouveau après le connexionNodes sinon l’objetgraphe qui suit est pas bon...
// il reste à corriger le dernier branchement du graphe comme dans completeBranchement
for (let i = 0; i < objetGraphe.nodes[nodeId].branchements.length; i++) {
if (hasProp(objetGraphe.nodes[nodeId].branchements[i], 'nn') && Number(objetGraphe.nodes[nodeId].branchements[i].nn) === Number(objetGraphe.maxNumeroNode)) {
// objetGraphe.nodes[nodeId].branchements[i].isSnn = true
const labelSnn = noeudTemp.max ? 'Si echec aux ' + noeudTemp.max + ' tentatives ' : 'Après ' + noeudTemp.maxParcours + ' passage(s) dans la boucle '
dispatch(setObjetGrapheBranchementProp(nodeId, i, 'isSnn', true))
dispatch(setObjetGrapheBranchementProp(nodeId, i, 'label', labelSnn))
dispatch(delObjetGrapheBranchementProp(nodeId, i, 'nn'))
// delete objetGraphe.nodes[nodeId].branchements[i].nn
}
}
} else {
connexionNodes('node' + nodeId, 'node' + snnValue)
noeudTemp.snn = Number(snnValue) // je préfère convertir en Number, j’ai eu des surprises avec des string...
objetGraphe = getObjetGraphe()// obligé de le getter à nouveau après le connexionNodes sinon l’objetgraphe qui suit est pas bon...
// il reste à corriger le dernier branchement du graphe comme dans completeBranchement
// un pb s’il existait déjà un autre branchement vers ce noeud (même depart et même fin), il faut pas le modifier donc on teste l’absence de ppté conclusion
for (let i = 0; i < objetGraphe.nodes[nodeId].branchements.length; i++) {
if (hasProp(objetGraphe.nodes[nodeId].branchements[i], 'nn') && Number(objetGraphe.nodes[nodeId].branchements[i].nn) === Number(snnValue) && !hasProp(objetGraphe.nodes[nodeId].branchements[i], 'conclusion')) {
const labelSnn = noeudTemp.max ? 'Si echec aux ' + noeudTemp.max + ' tentatives ' : 'Après ' + noeudTemp.maxParcours + ' passage(s) dans la boucle '
dispatch(setObjetGrapheBranchementProp(nodeId, i, 'isSnn', true))
dispatch(setObjetGrapheBranchementProp(nodeId, i, 'label', labelSnn))
dispatch(delObjetGrapheBranchementProp(nodeId, i, 'nn'))
// objetGraphe.nodes[nodeId].branchements[i].isSnn = true
// delete objetGraphe.nodes[nodeId].branchements[i].nn
}
}
}
}
actualiseGestionnaireEvenements()// a voir si utile
}
for (const prop in noeudTemp) {
dispatch(setObjetGrapheBranchementProp(connexion.sourceNodeId, Number(connexion.branchementIndex), prop, noeudTemp[prop]))
}
// const branche = objetGraphe.nodes[connexion.sourceNodeId].branchements[connexion.branchementIndex]
if (testPeAFaire && hasProp(branche, 'pe')) {
dispatch(delObjetGrapheBranchementProp(connexion.sourceNodeId, Number(connexion.branchementIndex), 'pe'))
// delete noeud['pe']
}
if (testScoreAFaire && hasProp(branche, 'score')) {
dispatch(delObjetGrapheBranchementProp(connexion.sourceNodeId, Number(connexion.branchementIndex), 'score'))
}
dispatch(setObjetGrapheBranchementProp(connexion.sourceNodeId, Number(connexion.branchementIndex), 'label', labelConnection))
// fermer la fenetre
$('#edgMasque, #modaleBranchement').remove()
}
/**
* Valide un node
* Appelée par suite_parametrage et valideParametresGraphe
* @param numeroNode
* @param {string} sectionName
* @param {string} [prefixe='']
* @private
*/
function valideParametres (numeroNode, sectionName, prefixe = '') {
let valeurParam
// changer le css du node (changeCouleurNode et transmettre nodeDomId) et récupérer les params de la boite
// Todo : vérification syntaxique...
// console.clear()
const config = getConfig()
const { parametres } = config.sectionsParams[sectionName]
// input_parNOMPARAM et renseigner objetGraphe.nodes[numeroNode].parametres
for (const [name, defaultValue, type] of parametres) {
if (!isHiddenParam(sectionName, name)) {
if (type === 'boolean') {
// on cherche quel bouton a été checké :
if (j3pElement(prefixe + name + 'true')?.checked) {
valeurParam = true
} else if (j3pElement(prefixe + name + 'false')?.checked) {
valeurParam = false
} else {
valeurParam = defaultValue
}
} else if (type === 'liste') {
valeurParam = j3pElement(prefixe + 'select' + name)?.value ?? ''
} else {
// on récupère la value en string pour tous les autres
const strValue = j3pElement(prefixe + 'input_par' + name)?.value
if (['number', 'entier', 'reel'].includes(type)) {
valeurParam = Number(strValue.replace(',', '.'))
if (!Number.isFinite(valeurParam)) {
j3pShowError(`Valeur incorrecte pour le paramètre ${name} (${strValue}) => ${defaultValue}`)
valeurParam = defaultValue
} else if (type === 'entier' && !Number.isInteger(valeurParam)) {
j3pShowError(`Valeur incorrecte pour le paramètre ${name} (${strValue} n’est pas un entier) => ${defaultValue}`)
valeurParam = defaultValue
}
} else if (type === 'array') {
try {
valeurParam = JSON.parse(strValue)
if (!Array.isArray(valeurParam)) {
j3pShowError(`paramètre ${name} invalide, pas un array `)
valeurParam = []
}
} catch (error) {
j3pShowError(error, { message: `Le paramètre ${name} contient du json invalide` })
valeurParam = []
}
} else if (type === 'intervalle') {
if (testIntervalleDecimaux.test(strValue)) {
valeurParam = strValue
} else {
j3pShowError(`paramètre ${name} invalide, pas un intervalle => ${defaultValue}`)
valeurParam = defaultValue
}
} else {
valeurParam = strValue
}
}
}
try { // PB : ne respecte pas le typage (pour les tableau, voire pour les booléens ?)
dispatch(setObjetGrapheParam(numeroNode, name, valeurParam))
} catch (e) {
console.error('pb pour renseigner le param dans objetGraphe et erreur=', e)
}
}
if (j3pElement(prefixe + 'param_donnees_parcours3').style.display === 'block') {
switch (j3pElement(prefixe + 'deroulante_choix_noeud').value) {
case 'rien':
return j3pShowError('Il faut choisir la référence du nœud dont on veut récupérer les paramètres !')
case 'noeud_precedent':
valeurParam = 'j3p.parcours.donnees'
break
default :
valeurParam = 'j3p.parcours.donnees[' + j3pElement(prefixe + 'deroulante_choix_noeud').value + ']'
break
}
try {
const param = config.sectionsParams[sectionName].donnees_parcours_nom_variable
dispatch(setObjetGrapheParam(numeroNode, param, valeurParam))
} catch (error) {
console.error(error)
}
}
$('#node' + numeroNode).css('background-color', '#cccccc')
if (prefixe === '') {
// faut virer la modale
$('#edgMasque, #edgModale').remove()
}
}
// Fonctions utilisées dans dialogueNode
// fonction appelée lors du paramétrage des sections avec donnes_parcours
function afficheParamDP (bool, nomsection) {
j3pElement('param_donnees_parcours3').style.display = bool ? 'block' : 'none'
if (!bool) {
// on remet aussi le param à vide car si on vient d’un oui on avait j3p.parcours.donnees,à supprimer donc
const param = getConfig().sectionsParams[nomsection].donnees_parcours_nom_variable
j3pElement('input_par' + param).value = ''
}
}
// fonction qui permet de savoir si l’on a le droit de surcharger un param
// (utilise actuellement config.parametres_non_affiches renseigné dans reducerConfig)
function isHiddenParam (nomSection, param) {
const { parametresMasques } = getConfig()
if (!parametresMasques?.[nomSection]) return false
return parametresMasques[nomSection].includes(param)
}
// vidange de la boîte de dialogue
// et dépilement des actions à réaliser
function initialiseDialogue () {
// // dépile les actions à effectuer lorsque le contenu de la boîte de dialogue change
// for (var cleAction in plusieursActions) {
// if (pileActions[cleAction].evenement === 'initialiseDialogue') { // à dépiler ici donc !
// // effectue l’action
// window[pileActions[cleAction].action](pileActions[cleAction].parametres)
// // depile
// delete pileActions[cleAction]
// }
// }
// // vide la boîte de dialogue
$('#contenudivD').empty() // TODO : a checker
}
function isPremierNoeud (nodeDomId) {
const nodeNumero = Number(nodeDomId.substr(4))
// every renvoie true si la callback renvoie true pour tous les éléments
// (et renvoie false dès que la callback passée en param renvoie un false, sans boucler plus loin)
// return getObjetGraphe().nodes.every(function (node, index) { return (index < nodeNumero) })
return Object.keys(getObjetGraphe().nodes).every(numero => Number(numero) >= nodeNumero)
// @todo vérifier que ce serait pas mieux avec ça
// return getObjetGraphe().nodes.every((node, index) => index < nodeNumero)
}
// fonction qui permet de constuire la liste déroulante pour les embranchements de noeuds possibles
function construitListeNoeudDP (container, nodeDomId) {
const objetGraphe = getObjetGraphe()
const select = addElement(container, 'select', { id: 'deroulante_choix_noeud' })
addElement(select, 'option', { value: 'rien' })
const noeudActuel = nodeDomId.substr(4)
Object.keys(objetGraphe.nodes).forEach(nodeId => {
if (nodeId !== noeudActuel) {
addElement(select, 'option', { content: `Du nœud ${nodeId}`, value: nodeId })
}
})
addElement(select, 'option', { content: 'Du nœud appelant', value: 'noeud_precedent' })
addElement(container, 'br')
}
function estDansGraphe (param, node) {
const obj = {}
obj.bool = false
obj.valeur = ''
const o = node.nodeParametres || ''
for (const prop in o.parametres) {
if (prop === (param)) {
obj.bool = true
obj.valeur = o.parametres[prop]
}
}
return obj
}
function suiteParametrage (sectionName, node, prefixe = '') {
const nodeDomId = 'node' + node.nodeNumero
const { sectionsParams } = getConfig()
const section = sectionsParams[sectionName]
const divparam2 = j3pElement(prefixe + 'divparam2')
if (!divparam2) return j3pShowError(Error('Erreur à la construction de la page, impossible d’afficher les paramètres'))// bugsnag dit que ça arrive… (l’erreur est déjà signalée en console par j3pElement
if (!section) return j3pShowError(Error(`Impossible de paramétrer la section ${sectionName} car elle n’a pas été correctement chargée`), getConfig())
if (hasProp(section, 'donnees_parcours_nom_variable') && !isPremierNoeud(nodeDomId)) {
let html = 'Ce nœud est particulier, la section programmée est capable de récupérer des informations du nœud précédent'
// param_donnees_parcours
if (typeof section.donnees_parcours_description === 'string' && section.donnees_parcours_description) {
html += ', plus précisément :<br>' + section.donnees_parcours_description + '<br>'
} else {
html += '.<br>'
}
html += 'Souhaitez-vous réutiliser le paramétrage d’un nœud précédent ? (auquel cas certains paramètres suivants ne seront pas pris en compte)'
j3pElement(prefixe + 'param_donnees_parcours').innerHTML = html
const dp2Elt = j3pElement(prefixe + 'param_donnees_parcours2')
dp2Elt.style.textAlign = 'center'
const form = addElement(dp2Elt, 'form')
let label = addElement(form, 'label', { content: 'Oui' })
addElement(label, 'input', { type: 'radio', name: 'choix_param_DP', value: 'OUI', id: `${prefixe}radio_dp_true` })
.addEventListener('click', afficheParamDP.bind(null, true))
label = addElement(form, 'label', { content: 'Non (le nœud générera des données en fonction des paramètres suivants)' })
addElement(label, 'input', { type: 'radio', name: 'choix_param_DP', value: 'NON', id: `${prefixe}radio_dp_false` })
.addEventListener('click', afficheParamDP.bind(null, false))
const params3 = j3pElement(prefixe + 'param_donnees_parcours3')
j3pAddTxt(params3, 'Il est possible de choisir de quel nœud on récupèrera les paramètres, le nœud appelant ou un autre :')
construitListeNoeudDP(params3, nodeDomId)
addElement(params3, 'br')
}
// un objet dont chaque propriété sera le getter de sa valeur
const getters = {}
// idem pour le setter
const setters = {}
// tous les parametres
for (const [prop, defaultValue, type, description, option] of section.parametres) {
if (isHiddenParam(sectionName, prop)) break // si on veut pas afficher le param, typiquement le fichier chargé d’une section outil
const id = prefixe + 'input_par' + prop
const div = creediv({ papa: divparam2 })
div.style.marginTop = '10px'
const span = creespan({
id: prefixe + 'titre_param_' + prop,
innerHTML: prop + ' : ',
papa: div,
fontSize: '16px',
color: '#663500'
})
span.style.color = '#FFF'
span.style.display = 'inline-block'
span.style.width = '200px'
span.style.textAlign = 'right'
const { bool, valeur: valeurSurchargee } = estDansGraphe(prop, node)
const valeur = bool
? valeurSurchargee
: defaultValue // sinon on reprend le paramétrage de base
let input
if (typeof valeur === 'object') {
input = creeinput({
fontSize: '20px',
id,
papa: div,
value: stringify(valeur)
})
// son getter
getters[prop] = () => {
try {
return JSON.parse(input.value)
} catch (error) {
console.error(error)
return valeur
}
}
setters[prop] = (value) => { input.value = value }
} else if (type === 'boolean') {
// c’est pas un input mais un span
input = creebtnsradios({
fontSize: '16px',
color: '#FFFFFF',
id,
papa: div,
value: ['true', 'false'],
id_radios_btn: [prefixe + prop + 'true', prefixe + prop + 'false'],
check: String(valeur)
})
// aj du getter / setter
getters[prop] = () => {
const inputs = document.getElementsByName(id)
for (const input of inputs) {
if (input.checked) return input.value === 'true'
}
return valeur
}
setters[prop] = (value) => {
const inputs = document.getElementsByName(id)
if (typeof value !== 'boolean') {
console.error(Error(`Valeur non booléenne (${typeof value}) sur un type boolean`))
value = Boolean(value)
}
const strValue = value ? 'true' : 'false'
for (const input of inputs) {
if (input.value === strValue) {
input.checked = true
break
}
}
}
} else if (type === 'editor' || type === 'multiEditor') {
input = creeinput({ fontSize: '16px', id, papa: div, value: valeur })
getters[prop] = () => input.value
setters[prop] = (value) => { input.value = value }
if (typeof option === 'function') {
const editorFct = option // pour rendre le code plus compréhensible
input.hidden = true
// les deux boutons, dans un span pour que ce soit plus joli (reste calé dans la colonne de droite)
const spanBtns = addElement(div, 'span', { style: { display: 'inline-block' } })
const btnRunEditor = addElement(spanBtns, 'button', { style: { margin: '0 0.2rem' }, content: 'Éditer' })
const btnRawEditor = addElement(spanBtns, 'button', { content: 'Saisie brute' })
btnRawEditor.addEventListener('click', () => {
input.hidden = false
})
// on remet l’input après nos boutons
div.appendChild(input)
btnRunEditor.addEventListener('click', () => {
// au cas où l’input était affiché on le masque
input.hidden = true
let initalValue = getters[prop]()
if (type === 'multiEditor') {
// il faut récupérer tous les paramètres, éventuellement surchargés, pour les passer à l’éditeur (il peut avoir besoin des valeurs des autres params que celui qu’on édite
initalValue = {}
for (const [prop, getter] of Object.entries(getters)) {
initalValue[prop] = getter()
}
}
// on lance l’éditeur dans une modale (s’il est dans une iframe il sera limité à cette iframe mais avec du fullscreen ça marche plus, les input blockly ou mtg sont plus atteignables, cf http://localhost:8081/testIframe.html?edit&graphe=[1,%22blokmtg01%22,[{pe:%22sans%20condition%22,nn:%22fin%22,conclusion:%22Fin%22}]])
const title = `Édition du paramètre « ${prop} »`
const editorModal = new Modal({ title, zIndex: 100, fullScreenIframe: true, withoutClose: true }) // faut passer au-dessus des 90 de #edgModale
editorModal.show()
// addElement(iframeDoc.body, 'div', { style: { position: 'absolute', width: '100%', height: '100%', top: 0, left: 0, backgroundColor: '#eee', zIndex: 100, overflow: 'auto' } })
const divContent = editorModal.addElement('div')
editorFct(divContent, initalValue).then(result => {
// console.debug(`l’éditeur du paramètre ${prop} nous retourne`, result)
if (result != null) {
if (typeof result === 'string') {
input.value = result
} else if (typeof result === 'object') {
if (type === 'editor') {
console.error(Error(`L’éditeur du paramètre ${prop} nous retourne un objet (il devrait nous retourner une string) => JSON.stringify`), result)
input.value = stringify(result)
} else {
// faut mettre tout ça dans les différents inputs…
for (let [p, v] of Object.entries(result)) {
// console.debug('on a récupéré', v, 'pour le param', p)
if (typeof v === 'object') v = stringify(v)
if (setters[p]) setters[p](v) // màj de l’input (ou bouton radio ou select)
else console.error(Error(`Il n’y a pas de paramètre ${p}`))
}
}
} else {
throw Error(`L’éditeur de ${prop} ne retourne pas les valeurs attendues`)
}
}
// et on peut fermer notre modale d’édition de paramètre
editorModal.destroy()
}).catch(j3pShowError)
})
} else {
console.error(Error(`La section déclare le paramètre ${prop} de type ${type} mais elle ne fourni pas la fonction en 5e élément du paramètre`), option)
}
} else if (type === 'liste') {
// c’est le span qui va contenir le select
input = creelisteDeroulante({
fontSize: '16px',
nom_param: prop,
id,
papa: div,
selected: valeur,
value: option,
prefixe
})
getters[prop] = () => {
const id = prefixe + 'select' + prop
const select = j3pElement(id)
if (!select) {
console.error(Error(`aucun élément #${id}`))
return valeur
}
return select.options[select.selectedIndex].value
}
setters[prop] = (value) => {
const id = prefixe + 'select' + prop
const select = j3pElement(id)
if (!select) return console.error(Error(`aucun élément #${id}`))
for (const option of select.options) {
if (option.value === value) {
option.selected = true
return
}
}
}
} else if (['entier', 'number', 'reel'].includes(type)) {
input = creeinput({ fontSize: '16px', id, papa: div, value: String(valeur), type: 'text' })
getters[prop] = () => Number(input.value)
setters[prop] = (value) => { input.value = String(value) }
} else {
// tout le reste, chaine, intervalle, array, tableau…
input = creeinput({ fontSize: '16px', id, papa: div, value: valeur })
getters[prop] = () => input.value
setters[prop] = (value) => { input.value = value }
}
// on ajoute un peu de style sur les input texte
if (['string', 'entier', 'number', 'reel', 'intervalle', 'array', 'editor', 'multiEditor'].includes(type)) {
input.style.color = '#2D5A7D'
input.style.width = '350px'
input.style.textAlign = 'right'
input.style.marginLeft = '10px'
input.style.background = '#DDEEFB'
input.style.borderStyle = 'inset'
input.style.borderWidth = '2px'
}
// On affiche le commentaire du paramètre…
const showHelp = () => { j3pElement(prefixe + 'divinfos2').innerHTML = description.replace(/\n/g, '<br>') }
// …au clic sur le nom du paramètre :
j3pElement(prefixe + 'titre_param_' + prop).addEventListener('click', showHelp)
// …et sur l’input
input.addEventListener('focus', showHelp)
// et pour le select faut aussi l’ajouter
if (type === 'liste') {
input.querySelector('select').addEventListener('focus', showHelp)
}
// donnees_parcours_nom_variable est un paramètre d’une section qui lui permet de récupérer le paramètre d’une autre section du graphe
// c’est utilisé par ex avec les probas ou les dérivées (chercher donnees_parcours_nom_variable dans les sections)
if (hasProp(section, 'donnees_parcours_nom_variable') && !isPremierNoeud(nodeDomId) && section.donnees_parcours_nom_variable === prop) {
const chunks = /j3p.parcours.donnees\[([^\]]+)]/.exec(valeur)
let noeudInitial
if (chunks) {
noeudInitial = chunks[1]
} else if (valeur.includes('j3p.parcours.donnees')) {
noeudInitial = 'noeud_precedent'
}
if (noeudInitial) {
// console.debug('On réutilise un noeud précis', noeudInitial)
j3pElement(prefixe + 'radio_dp_true').checked = true
j3pElement(prefixe + 'deroulante_choix_noeud').value = noeudInitial
afficheParamDP('true')
}
}
}
if (prefixe === '') {
const papa = creediv({ id: 'btn_valider', papa: j3pElement('edgModale') })
const btnAnnuler = creeinput({ type: 'button', value: 'Annuler', papa })
btnAnnuler.style.margin = '1rem 3rem'
btnAnnuler.addEventListener('click', () => $('#edgModale, #edgMasque').remove())
const btnValider = creeinput({ type: 'button', value: 'Valider', papa })
btnValider.style.margin = '1rem 3rem'
btnValider.addEventListener('click', () => valideParametres(node.nodeNumero, sectionName))
}
} // suite_parametrage
function construitDivs () {
edgModale({ width: '620', height: '450', croix: false, autoClose: false })
}
// Fonction appelée pour compléter la boite de dialogue pour un node
async function completeDialogue (node, parent = 'edgModale', prefixe = '') {
try {
if (typeof parent === 'string') parent = j3pElement(parent)
const paramEntete = addElement(parent, 'div', { id: prefixe + 'param_entete' })
addElement(parent, 'div', { id: prefixe + 'param_donnees_parcours' })
addElement(parent, 'div', { id: prefixe + 'param_donnees_parcours2' })
addElement(parent, 'div', { id: prefixe + 'param_donnees_parcours3', style: { display: 'none' } })
const conteneur = addElement(parent, 'div', { id: prefixe + 'contenudivD' })
addElement(conteneur, 'div', {
id: prefixe + 'divparam2',
style: {
display: 'inline-block',
height: '360px',
width: '400px',
left: '10px',
border: 'solid 2px #EEEEEE',
overflow: 'auto',
background: '#2D5A7D'
}
})
addElement(conteneur, 'div', {
id: prefixe + 'divinfos2',
style: {
display: 'inline-block',
fontSize: '16px',
height: '360px',
width: '200px',
float: 'right',
border: 'solid 2px #EEEEEE',
overflow: 'auto',
background: '#2D5A7D',
paddingTop: '0px',
paddingLeft: '5px',
paddingRight: '5px',
color: '#FFF',
borderStyle: 'inset'
}
})
addElement(paramEntete, 'h3', { content: `Configuration du nœud n°${node.nodeNumero}` })
node.nodeParametres.style = node.nodeParametres.style || { back: '#aa0000' }
// faut charger les params de la section
const sectionName = node.nodeParametres.section
if (!sectionName) {
console.error(Error('Pas de propriété section dans nodeParametres'), node)
return j3pShowError(Error('Erreur interne, impossible de trouver le nom de la section'))
}
await getSectionParams(sectionName, node)
await suiteParametrage(sectionName, node, prefixe)
} catch (error) {
j3pShowError(error)
}
} // completeDialogue
export function dialogueNode (nodeDomId) {
// nouvelle boîte de dialogue
initialiseDialogue()
// Attention, pb de syntaxe, ici node va être un objet avec deux propriétés, nodeNumero et nodeParametres, ce dernier
const node = getNodeDansGraphe(nodeDomId)
// cas d’un node section 'classique'
if (node.nodeParametres.section !== 'fin') {
construitDivs()
completeDialogue(node)
} else {
// cas d’un node section fin
const modaleElt = edgModale({ width: '420', height: '300' })
const contenudivD = addElement(modaleElt, 'div', { id: 'contenudivD' })
addElement(contenudivD, 'h3', { content: 'Fin de l`activité' })
actualiseGestionnaireEvenements()
$('#' + nodeDomId).css('background-color', '#cccccc')
}
}
/**
* Valide le graphe complet
* @private
*/
function valideParametresGraphe () {
const objetGraphe = getObjetGraphe()
for (const index in objetGraphe.nodes) {
if (!estNoeudFin(index, objetGraphe)) {
const node = {
nodeNumero: index,
nodeParametres: objetGraphe.nodes[index]
}
const sectionName = node.nodeParametres.section || ''
valideParametres(index, sectionName, 'noeud' + index)
}
}
$('#edgMasque, #edgModale').remove()
}
export function dialogueGraphe () {
const objetGraphe = getObjetGraphe()
construitDivs()
const edgModale = j3pElement('edgModale')
const divParent = addElement(edgModale, 'div', { style: { display: 'inline-block', height: '400px', overflow: 'auto' } })
const contenuElt = j3pElement('edgModalecontenu')
if (!contenuElt) {
console.error(Error('Pas trouvé le contenu à modifier de la boite de dialogue'))
return
}
j3pAddTxt(contenuElt, 'Configuration des nœuds du graphe')
j3pSetProps(contenuElt, { style: { fontWeight: 'bold', textAlign: 'center' } })
// TODO : tester parametre liste déroulante d’un node qcq
for (const index in objetGraphe.nodes) {
if (!estNoeudFin(index, objetGraphe)) {
// On recréé un node au sens de dialogueNode (avec le nodeDomId sur lequel on 'aurait' cliqué)
const node = {
nodeNumero: index,
nodeParametres: objetGraphe.nodes[index]
}
addElement(divParent, 'div', { id: 'config_noeud' + index })
completeDialogue(node, 'config_noeud' + index, 'noeud' + index)
// TODO :
// 2. Pb : adapter valideParametres
}
}
const div = creediv({ id: 'btn_valider', papa: j3pElement('edgModale') })
const input = creeinput({ type: 'button', value: 'Valider', papa: div })
input.addEventListener('click', () => valideParametresGraphe())
}