"use strict"; import Luci from './luci.js'; import Antifurto from './antifurto.js'; import { scenariToggleListener } from "./toggles.js"; import { showAlert, makeElementBounce } from "./alerts.js"; // JSON templeate for scenario export interface scenarioTemplate { "nome": string, "data": string, "id": string, }; class Scenari { /** * the div where everything related to the Scenari is stored */ static scenariContainer = document.getElementById('scenariContainer')!; /** * this is the select element where to add all the scenarios */ static addNameHere = document.getElementById('p')! as HTMLSelectElement; /** * contains the toggle that is currently active */ static scenarioAttivoToggle: HTMLElement | null = null; /** * counts how many scenarios have been added */ static index = 0; /** * the array where all of the user's scenari are stored */ static scenariBucket: scenarioTemplate[] = []; constructor() { Scenari.init(); Scenari.antifurtoModalLauncherListener(); Scenari.anyModalListener(); } /** * sets up the listener for the button in the top of the page * i'm also leaving here what I haven't yet implemented */ static init(): void { Scenari.fillTable(); const scenariBtn = document.getElementById('mainButtonContainer')!.children[1]; scenariBtn.addEventListener('click', () => { Scenari.toggleContainer(true); Luci.toggleContainer(false); Antifurto.toggleContainer(false); }, false); const registerBtn = document.getElementById('scenari-registra')!; registerBtn.addEventListener('click', event => { event.preventDefault(); // TODO choose how to register the scenario }, false); } /** * used for the antitheft button (ON/OFF): before opening the connected modal, change its text then open it * @see Scenari.antifurtoModalSubmitListener */ static antifurtoModalLauncherListener(): void { const antifurtoBtn = document.getElementById('scenari-stato-antifurto')!; const okBtn = document.getElementById('attiva-antifurto-btn')!; const modalBody = document.querySelector('#antifurto-modal .modal-body')!; const modalLauncher = document.getElementById('lancia-antifurto-modal')!; const registerBtn = document.getElementById('scenari-registra')!; antifurtoBtn.addEventListener('click', () => { if (registerBtn.innerText !== "Registra") {//sto registrando showAlert("Attenzione", "Stai registrando uno scenario, termina la registrazione per attivare l'antifurto.", false); makeElementBounce(registerBtn); return; } if (Scenari.scenariBucket.length === 0) { showAlert('Nessun scenario disponibile', 'Registrare uno o più scenari per poter attivarne uno', false); // make the registerBtn bounce for 3 seconds const registerBtn = document.getElementById('scenari-registra'); makeElementBounce(registerBtn); return; } const children = modalBody.children as HTMLCollectionOf<HTMLElement>; // p, p /* <p>Stai per abilitare l'antifurto.</p> <p>Vuoi continuare?</p> */ if (antifurtoBtn.innerText === 'ON') { // the antitheft is on children[0].innerText = "Stai per disabilitare l'antifurto."; okBtn.innerText = 'Disattiva'; } else { // the antitheft is off children[0].innerText = "Stai per abilitare l'antifurto."; okBtn.innerText = 'Attiva'; } // launch the modal modalLauncher.click(); // related: Scenari.antifurtoModalSubmitListener(); }, false); // TODO server get antifurto status and set it correctly // toggleAntifurto(response); } /** * sets up various listeners for the modals * @see Scenari.attivaScenarioModal * @see Scenari.registraModal * @see Scenari.antifurtoModalSubmitListener */ static anyModalListener(): void { Scenari.anteprimaModal(); Scenari.attivaScenarioModal(); Scenari.registraModal(); Scenari.antifurtoModalSubmitListener(); } /** * listens to the submit button in the modal for the antitheft and activates/deactivates the antitheft * this modal is shown when the user clicks on the antitheft button * @see Scenari.antifurtoModalLauncherListener */ static antifurtoModalSubmitListener(): void { const antifurtoOkBtn = document.getElementById('attiva-antifurto-btn')!; const closeBtn = document.querySelector('#antifurto-modal .btn.btn-danger')! as HTMLButtonElement; const antifurtoBtn = document.getElementById('scenari-stato-antifurto')!; antifurtoOkBtn.addEventListener('click', () => { // close modal closeBtn.click(); // switch the antitheft status button const isActivatingAntitheft = antifurtoBtn.innerText === 'OFF'; // Scenari.toggleAntifurto(isActivatingAntitheft); if (isActivatingAntitheft) Scenari.activateRandomScenario(); else Scenari.deactivateActiveScenario();//cannot use toggleScenario because I don't have the scenarioID // Antifurto.activate(isActivatingAntitheft) already taken care of }, false); } /** * listens for the submit of the anteprima modal */ static anteprimaModal(): void { const anterprimaModal = document.getElementById('anteprima-modal')!; const okBtn = anterprimaModal.querySelector('#anteprima-scenari-btn')! as HTMLButtonElement; const modalBody = anterprimaModal.querySelector('.modal-body')!; const closeBtn = anterprimaModal.querySelector('.btn.btn-danger')! as HTMLButtonElement; okBtn.addEventListener('click', () => {//activate the scenario // close the modal closeBtn.click(); // get the scenario from the modal body const scenarioID = (modalBody.querySelector('.invisible')! as HTMLElement).innerText; // find the scenario's row on the page // const scenarioRow = scenarioTable.querySelector(`#${scenarioName}`); // const toggle = scenarioTable.querySelector(`#${scenarioName} .toggle-button`); Scenari.toggleScenario(scenarioID, okBtn.innerText === 'Attiva'); }, false); } /** * listenes for the submit button in the modal for activating/deactivating a scenario when chosen from the toggles * @see scenariToggleListener */ static attivaScenarioModal(): void { const modalBtn = document.getElementById('attiva-scenari-btn')!; const closeBtn = document.querySelector('#attiva-scenari-modal .btn.btn-danger')! as HTMLButtonElement; modalBtn.addEventListener('click', () => { // close the modal by clicking on the close button closeBtn.click(); // I have to get to the toggle and activate it from the value of the select const scenarioID = Scenari.addNameHere.value; const toggle = document.querySelector(`#${scenarioID} .toggle-button`); // has the user chosen to activate the scenario? // const isActivatingNewScenario = ; // Scenari.toggleToggle(toggle,isActivatingNewScenario); // Scenari.toggleAntifurto(isActivatingNewScenario); Scenari.toggleScenario(scenarioID, toggle !== Scenari.scenarioAttivoToggle); }, false); } /** * this is for the button "registra"/"termina" and the related modals * @see Scenari.registraBtnListener * @see Scenari.submitRecordModalListener * @see Scenari.endRecordingListener */ static registraModal(): void { const scenariModal = document.getElementById('scenari-modal')!; const registraBtn = document.getElementById('scenari-registra') as HTMLButtonElement; const okBtn = scenariModal.querySelector('.btn.btn-info') as HTMLButtonElement; const modalBody = scenariModal.querySelector('.modal-body') as HTMLElement; const htmlForModalBody = `<p>Stai per attivare la registrazione dei comandi.</p> <p>Dal momento in cui premerai sul bottone azzurro, inizierai a registrare tutte le interazioni con le tue luci.</p> <p>Inoltre disabiliterai eventuali scenari attivi.</p> <p>Una volta che sarai soddisfatto del risultato, torna su questa pagina per terminare la registrazione.</p> <p>Inizare la registrazione?</p>` const modalLauncher = document.getElementById('registra-modal-launcer') as HTMLButtonElement; // const okBtn = scenariModal.querySelector('.btn.btn-info'); // const registraBtn = document.getElementById('scenari-registra'); const closeScenariModalBtn = scenariModal.querySelector('.btn.btn-danger') as HTMLButtonElement; const endRecordingModalLauncher = document.getElementById('termina-registrazione-launcher') as HTMLButtonElement; const endRecordingForm = document.getElementById('termina-registrazione-form') as HTMLFormElement; // const modalBody = scenariModal.querySelector('.modal-body'); // htmlForModalBody = ... const closeRecordingBtn = document.getElementById('termina-registrazione-modal')!.querySelector('.btn.btn-danger') as HTMLButtonElement; const campoNome = document.getElementById('nome-nuovo-scenario') as HTMLInputElement; Scenari.registraBtnListener(registraBtn, okBtn, modalBody, htmlForModalBody, modalLauncher); Scenari.submitRecordModalListener(okBtn, registraBtn, closeScenariModalBtn, endRecordingModalLauncher); Scenari.endRecordingListener(endRecordingForm, modalBody, htmlForModalBody, closeRecordingBtn, campoNome); } /** * listens for the actual "registra"/"termina" button on the page and edits the related modal accordingly * @param {HTMLButtonElement} registraBtn the button on the page * @param {HTMLButtonElement} okBtn the submit button in the modal * @param {HTMLElement} modalBody the body of the modal which is to be changed * @param {string} htmlForModalBody a string containing the html to be inserted in the modal body * @param {HTMLButtonElement} modalLauncher the button which launches the modal * @see Scenari.registraModal */ static registraBtnListener(registraBtn: HTMLButtonElement, okBtn: HTMLButtonElement, modalBody: HTMLElement, htmlForModalBody: string, modalLauncher: HTMLButtonElement): void { registraBtn.addEventListener('click', () => { const isAboutToRecord = registraBtn.innerText === 'Registra'; // change text of okBtn okBtn.innerText = isAboutToRecord ? 'Registra' : 'Termina'; // change inner text of the modal if (isAboutToRecord) { modalBody.innerHTML = htmlForModalBody; } else { modalBody.innerHTML = ` <p>Stai per terminare la registrazione dei comandi.</p> <p>Terminare la registrazione?</p>`; } // launch the modal modalLauncher.click(); // related: Scenari.submitRecordModalListener }, false); } /** * listens for the submit of the modal which is launched when the user clicks on the "registra"/"termina" button * @param {HTMLButtonElement} okBtn the submit button in the modal * @param {HTMLButtonElement} registraBtn the button on the page (to understand if the user is about to record or not) * @param {HTMLButtonElement} closeScenariModalBtn the button used to close the modal * @param {HTMLButtonElement} endRecordingModalLauncher the button which launches the next modal (used if the user has finished recording) * @see Scenari.registraModal */ static submitRecordModalListener(okBtn: HTMLButtonElement, registraBtn: HTMLButtonElement, closeScenariModalBtn: HTMLButtonElement, endRecordingModalLauncher: HTMLButtonElement): void { okBtn.addEventListener('click', () => { const isAboutToRecord = registraBtn.innerText === 'Registra'; // close modal closeScenariModalBtn.click(); // get the current time // const timeBegin = new Date(); // if the user ended the recording, show another modal to ask for the name of the scenario if (isAboutToRecord) { registraBtn.innerText = 'Termina'; // TODO server send a note to start recording Scenari.deactivateActiveScenario(); } else { registraBtn.innerText = 'Registra'; // TODO server send a note to stop recording endRecordingModalLauncher.click(); // related: Scenari.endRecordingListener } }, false); } /** * listens for the submit of the modal which is launched when the user is done recording a scenario * @param {HTMLFormElement} endRecordingForm the form used to submit the name of the scenario * @param {HTMLElement} modalBody the body of the modal which is to be changed * @param {string} htmlForModalBody a string containing the html to be inserted in the modal body * @param {HTMLButtonElement} closeRecordingBtn the button used to close the modal * @param {HTMLInputElement} campoNome the input field used to insert the name of the scenario by the user * @see Scenari.registraModal * @see Scenari.mostraNuovoScenario */ static endRecordingListener(endRecordingForm: HTMLFormElement, modalBody: HTMLElement, htmlForModalBody: string, closeRecordingBtn: HTMLButtonElement, campoNome: HTMLInputElement): void { endRecordingForm.addEventListener('submit', (event: { preventDefault: () => void; }) => { event.preventDefault(); // reset the other modal and button modalBody.innerHTML = htmlForModalBody; // close the modal closeRecordingBtn.click(); const name = campoNome.value; const scenario = Scenari.createScenarioFromName(name) Scenari.mostraNuovoScenario(scenario); // clear the field for the next use campoNome.value = ''; // TODO server send a note to save the scenario }, false); } /** * takes care of launching a modal to preview the actions of the chosen scenario; it also listens for the submit of the launched modal * @param {HTMLButtonElement} anteprimaBtn the button used to launch the modal * @param {scenarioTemplate} scenario the JSON scenario to be previewed * @see Scenari.correctlySetToggle */ static anteprimaListener(anteprimaBtn: HTMLButtonElement, scenario: scenarioTemplate): void { const scenarioID = scenario.id; const anterprimaModal = document.getElementById('anteprima-modal')!; const modalBody = anterprimaModal.querySelector('.modal-body')!; const modalLauncher = document.getElementById('anteprima-modal-launcher')!; const okBtn = anterprimaModal.querySelector('#anteprima-scenari-btn')! as HTMLButtonElement; const scenarioTable = document.getElementById('table-row-scenari')!; const registerBtn = document.getElementById('scenari-registra')!; anteprimaBtn.addEventListener('click', () => { if (registerBtn.innerText !== "Registra") {//sto registrando showAlert("Attenzione", "Stai registrando uno scenario, termina la registrazione per mostrare l'anteprima.", false); makeElementBounce(registerBtn); return; } // TODO server get the scenario's data // show the scenario in the modal (choose how to display it) modalBody.innerHTML = ` <p>${scenario.nome}</p> <p>${scenario.data}</p> <span class='invisible'>${scenarioID}</span>`;//FIXME remove this test (be sure to keep the last span as is) // if the scenario is already active, change the okBtn text // to find if it's active, find the relative toggle // and check if it's active // const scenarioRow = scenarioTable.querySelector(`#${name}`); const toggle = scenarioTable.querySelector(`#${scenarioID} .toggle-button`)!; okBtn.innerText = toggle.classList.contains('active') ? 'Disattiva' : 'Attiva'; // launch the modal modalLauncher.click(); }, false); } // utilility functions /** * displays the given scenario on the screen (below every other one) and hooks up the listeners; then it increments the index * @param {scenarioTemplate} scenario the JSON scenario to be displayed * @see Scenari.createScenarioRow * @see Scenari.anteprimaListener * @see scenariToggleListener */ static mostraNuovoScenario(scenario: scenarioTemplate): void { const row = Scenari.createScenarioRow(scenario); const scenarioID: string = scenario.id; const tableBody = document.getElementById('table-row-scenari')!; tableBody.appendChild(row); const anteprimaBtn = row.querySelector('.badge.rounded-pill') as HTMLButtonElement; Scenari.anteprimaListener(anteprimaBtn, scenario); // add the name of the scenario as option in the select of the modal const option = document.createElement('option'); option.value = scenarioID; option.innerText = scenario.nome; Scenari.addNameHere.appendChild(option); const toggle = row.querySelector('.toggle-button') as HTMLElement; scenariToggleListener(toggle, scenarioID); // Scenari.index++; } /** * creates a table entry for the given scenario and returns it * @param {scenarioTemplate} scenario the JSON scenario to be displayed * @returns {HTMLTableRowElement} the row containing the given scenario */ static createScenarioRow(scenario: scenarioTemplate): HTMLTableRowElement { const row = document.createElement('tr'); row.id = scenario.id; row.innerHTML = ` <th scope="row">${scenario.nome.trim()}</th> <td> <span>${scenario.data}</span> </td> <td> <h5> <span class="badge rounded-pill bg-secondary">anteprima</span> </h5> </td> <td> <div class="switch-container no-box-sizing"> <div id="toggle-${scenario.id}" class="toggle-button no-box-sizing"> <div class="inner-circle no-box-sizing"></div> </div> </div> <span id="launch-modal-${scenario.id}" data-bs-toggle="modal" data-bs-target="#attiva-scenari-modal"></span> </td>`; // the span is used by the toggle to launch the modal return row; } /** * changes the antifurto button to the given state * @param {Boolean} activating true if the antitheft is being activated, false otherwise * @param {Boolean} fromAntifurto usually false or undefined; true only if the function is called from the scenari */ static correctlySetAntifurto(activating: Boolean, fromAntifurto: Boolean = false): void { if (fromAntifurto !== true) Antifurto.activate(activating, true); const antifurtoBtn = document.getElementById('scenari-stato-antifurto')!; if (activating) { if (antifurtoBtn.innerText === 'ON') return; antifurtoBtn.innerText = 'ON'; antifurtoBtn.classList.remove('btn-danger'); antifurtoBtn.classList.add('btn-success'); } else { if (antifurtoBtn.innerText === 'OFF') return; antifurtoBtn.innerText = 'OFF'; antifurtoBtn.classList.remove('btn-success'); antifurtoBtn.classList.add('btn-danger'); } } /** * turns off the active scenario if there is one; useful if you don't have the scenarioID */ static deactivateActiveScenario(): void { if (Scenari.scenarioAttivoToggle == null) return; // Scenari.scenarioAttivoToggle.classList.remove('active'); // Scenari.scenarioAttivoToggle = null; // // TODO server deactivate the scenario const toggleID = Scenari.scenarioAttivoToggle.id; const scenarioID = toggleID.substring(toggleID.indexOf('-') + 1); Scenari.toggleScenario(scenarioID, false); } /** * activates/deactivates the scenario with the given name * @param {string} scenarioID the ID of the scenario to be activated/deactivated * @param {Boolean} activating true if the scenario should be activated, false otherwise */ static toggleScenario(scenarioID: string, activating: Boolean) { // find the scenario's row on the page const scenarioRow = document.getElementById(`${scenarioID}`)!; const toggle = scenarioRow.querySelector('.toggle-button') as HTMLElement; Scenari.correctlySetToggle(toggle); Scenari.correctlySetAntifurto(activating); // if (activating) { // // TODO server activate the scenario // } else { // // TODO server deactivate the scenario // } } /** * takes care of activating or deactivating the scenario's toggle based on the given boolean * @param {HTMLElement} toggle the toggle to be activated or deactivated * @see Scenari.correctlySetAntifurto */ static correctlySetToggle(toggle: HTMLElement): void { const isActivatingNewScenario = toggle !== Scenari.scenarioAttivoToggle; Scenari.scenarioAttivoToggle?.classList.remove('active'); if (isActivatingNewScenario) { toggle.classList.add('active'); Scenari.scenarioAttivoToggle = toggle; } else { toggle.classList.remove('active'); Scenari.scenarioAttivoToggle = null; } } /** * activates a random scenario from the ones available */ static activateRandomScenario(): void { if (Scenari.scenariBucket.length === 0) { showAlert('Nessun scenario disponibile', 'Registrare uno o più scenari per poter attivarne uno', false); // make the registerBtn bounce for 3 seconds const registerBtn = document.getElementById('scenari-registra'); makeElementBounce(registerBtn); return; } const randomIndex = Math.floor(Math.random() * Scenari.index); const scenarioID = `scenario-${randomIndex}`; // const table = document.getElementById('table-row-scenari'); // // enable a random scenario // const randomScenario = table.children[Math.floor(Math.random() * table.children.length)]; // const scenarioID = randomScenario.id; Scenari.toggleScenario(scenarioID, true); } /** * creates the scenario and updates index * @param {string} scenarioName the string of the scenario to create * @returns {scenarioTemplate} the JSON scenario */ static createScenarioFromName(scenarioName: string): scenarioTemplate { const scenario: scenarioTemplate = { nome: scenarioName, data: new Date().toLocaleString(), id: `scenario-${Scenari.index}`, }; Scenari.index++; Scenari.scenariBucket.push(scenario); return scenario; } /** * fills the table with the scenarios available for the user in the server * @see Scenari.mostraNuovoScenario */ static fillTable(): void { // TODO server get all scenarios const scenari = []; for (const scenario of scenari) { Scenari.mostraNuovoScenario(scenario) // TODO server if scenario is active, change the toggle to active // also set Scenari.scenarioAttivoToggle to its toggle } } /** * toggle the visibility of the scenari container * @param {Boolean} visible true if the container should be shown, false otherwise */ static toggleContainer(visible: Boolean): void { if (visible) { Scenari.scenariContainer.classList.remove('invisible'); if (Scenari.scenariBucket.length === 0) { // make the register button bounce const registerBtn = document.getElementById('scenari-registra'); makeElementBounce(registerBtn); } } else { Scenari.scenariContainer.classList.add('invisible'); } } } export default Scenari;