/******/ (() => { // webpackBootstrap /******/ "use strict"; // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // UNUSED EXPORTS: default ;// ./static/js/lib/validation.js class Validation { /* isNullOrWhitespace(v) { let txt = JSON.stringify(v).replace('/\s\g', ''); return (txt == '' || 'null'); } */ static isEmpty(object) { let isEmpty = true; if (object !== null && object !== "null" && object !== undefined && object !== "undefined") { if (object.length == undefined) { isEmpty = false; // object exists but isn't a collection } else if (typeof object === "function") { isEmpty = false; // object is reference } else { // string or collection let isString = typeof object == "string"; if (isString) object = object.trim(); if (object.length > 0) { if (isString) { isEmpty = false; // String greater than length 0 } else { if (typeof object[0] != "string") { isEmpty = false; } else { for (let i = 0; i < object.length; i++) { if (object[i] != "") { isEmpty = false; break; } } } } } } } return isEmpty; } static isValidNumber(value, positiveOnly) { return !Validation.isEmpty(value) && !isNaN(value) && (!positiveOnly || parseFloat(value) > 0); } static getDataContentType(params) { var data = null; var contentType = ''; if (!Validation.isEmpty(params)) { if (typeof params === "string") { data = params; contentType = "application/x-www-form-urlencoded; charset=UTF-8"; } else { data = JSON.stringify(params); contentType = "application/json; charset=UTF-8"; } } return { Data: data, ContentType: contentType }; } static arrayContainsItem(array, itemValue) { var hasItem = false; if (!Validation.isEmpty(array) && !Validation.isEmpty(itemValue)) { var isJQueryElementArray = array[0] instanceof jQuery; if (isJQueryElementArray) { for (let i = 0; i < array.length; i++) { if (document.querySelectorAll(array[i]).is(itemValue)) { hasItem = true; break; } } } else { var isDate = array[0] instanceof Date; if (isDate) { for (let i = 0; i < array.length; i++) { if (array[i].getTime() === itemValue.getTime()) { hasItem = true; break; } } } else { for (let i = 0; i < array.length; i++) { if (array[i] == itemValue) { hasItem = true; break; } } } } } return hasItem; } static dictHasKey(d, k) { return k in d; } static areEqualDicts(dict1, dict2) { const keys1 = Object.keys(dict1); const keys2 = Object.keys(dict2); if (keys1.length !== keys2.length) { return false; } for (let key of keys1) { if (dict1[key] !== dict2[key]) { return false; } } return true; } static imageExists(url, callback) { var img = new Image(); img.onload = function () { callback(true); }; img.onerror = function () { callback(false); }; img.src = url; } static toFixedOrDefault(value, decimalPlaces, defaultValue = null) { return Validation.isValidNumber(value) ? parseFloat(value).toFixed(decimalPlaces) : defaultValue; } } ;// ./static/js/dom.js class DOM { static setElementAttributesValuesCurrentAndPrevious(element, data) { DOM.setElementAttributeValueCurrent(element, data); DOM.setElementAttributeValuePrevious(element, data); } static setElementAttributeValueCurrent(element, data) { element.setAttribute(attrValueCurrent, data); } static setElementAttributeValuePrevious(element, data) { element.setAttribute(attrValuePrevious, data); } static setElementValuesCurrentAndPrevious(element, data) { DOM.setElementValueCurrent(element, data); DOM.setElementAttributeValuePrevious(element, data); } static setElementValueCurrent(element, data) { DOM.setElementAttributeValueCurrent(element, data); let tagName = element.tagName.toUpperCase(); if (element.type === "checkbox") { element.checked = data; } else if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') { element.value = data; } else { element.textContent = data; } } static setElementValueCurrentIfEmpty(element, data) { if (Validation.isEmpty(DOM.getElementValueCurrent(element))) { DOM.setElementValueCurrent(element, data); } } static getCellFromElement(element) { return element.closest('td'); } static getRowFromElement(element, flagRow) { let selector = Validation.isEmpty(flagRow) ? 'tr' : 'tr.' + flagRow; return element.closest(selector); } static getClosestParent(element, parentSelector) { let parent = element.parentElement; while (parent) { if (parent.matches(parentSelector)) { return parent; } parent = parent.parentElement; } return null; } static convertForm2JSON(elementForm) { let dataForm = {}; if (Validation.isEmpty(elementForm)) { return dataForm; } let containersFilter = elementForm.querySelectorAll('.' + flagContainerInput + '.' + flagFilter); let containerFilter, labelFilter, keyFilter, filter; for (let indexFilter = 0; indexFilter < containersFilter.length; indexFilter++) { containerFilter = containersFilter[indexFilter]; labelFilter = containerFilter.querySelector('label'); keyFilter = labelFilter.getAttribute('for'); filter = containerFilter.querySelector(`#${keyFilter}`); dataForm[keyFilter] = DOM.getElementValueCurrent(filter); } return dataForm; } static loadPageBody(contentNew) { let pageBody = document.querySelector(idPageBody); pageBody.innerHTML = contentNew; } static getHashPageCurrent() { const hashPageCurrent = document.body.dataset.page; return hashPageCurrent; } static updateAndCheckIsElementDirty(element) { element.setAttribute(attrValueCurrent, DOM.getElementValueCurrent(element)); return DOM.isElementDirty(element); } static isElementDirty(element) { let isDirty = element.getAttribute(attrValuePrevious) != element.getAttribute(attrValueCurrent); DOM.handleDirtyElement(element, isDirty); return isDirty; } static handleDirtyElement(element, isDirty) { DOM.toggleElementHasClassnameFlag(element, isDirty, flagDirty); } static toggleElementHasClassnameFlag(element, elementHasFlag, flag) { let elementAlreadyHasFlag = element.classList.contains(flag); if (elementHasFlag == elementAlreadyHasFlag) return; if (elementHasFlag) { element.classList.add(flag); } else { element.classList.remove(flag); } } static hasDirtyChildrenContainer(container) { if (container == null) return false; return container.querySelector('.' + flagDirty) != null; } static hasDirtyChildrenNotDeletedContainer(container) { if (container == null || container.classList.contains(flagDelete)) return false; return container.querySelector('.' + flagDirty + ':not(.' + flagDelete + ', .' + flagDelete + ' *)') != null; } static getElementValueCurrent(element) { let returnVal = ''; if (!Validation.isEmpty(element)) { let tagName = element.tagName.toUpperCase(); if (element.type === "checkbox") { returnVal = element.checked; } /* else if (element.classList.contains(flagIsDatePicker)) { returnVal = getDatePickerDate(element, adjust4DayLightSavings); } */else if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') { returnVal = element.value; } else if (element.classList.contains(flagButton) && element.classList.contains(flagActive)) { // tagName === 'BUTTON' returnVal = element.classList.contains(flagDelete); } else if (tagName === 'TD') { returnVal = DOM.getElementAttributeValueCurrent(element); } else if (tagName == 'SVG' && element.classList.contains(flagCheckbox)) { returnVal = element.classList.contains(flagIsChecked); } else { returnVal = element.textContent; } } if (Validation.isEmpty(returnVal)) returnVal = ''; return returnVal; } static getElementAttributeValueCurrent(element) { // debugger; if (Validation.isEmpty(element)) return null; return element.getAttribute(attrValueCurrent); } static getElementAttributeValuePrevious(element) { if (Validation.isEmpty(element)) return null; return element.getAttribute(attrValuePrevious); } /* base_table.handleChangeElementCellTable static updateAndCheckIsTableElementDirty(element) { let wasDirty = DOM.isElementDirty(element); let row = DOM.getRowFromElement(element); let wasDirtyRow = DOM.hasDirtyChildrenNotDeletedContainer(row); let isDirty = DOM.updateAndCheckIsElementDirty(element); let cell = DOM.getCellFromElement(element); Utils.consoleLogIfNotProductionEnvironment({element, row, cell, isDirty, wasDirty}); if (isDirty != wasDirty) { DOM.handleDirtyElement(cell, isDirty); let isDirtyRow = DOM.hasDirtyChildrenNotDeletedContainer(row); Utils.consoleLogIfNotProductionEnvironment({isDirtyRow, wasDirtyRow}); if (isDirtyRow != wasDirtyRow) { DOM.handleDirtyElement(row, isDirtyRow); } } } */ static scrollToElement(parent, element) { // REQUIRED: parent has scroll-bar parent.scrollTop(parent.scrollTop() + (element.offset().top - parent.offset().top)); } static isElementInContainer(container, element) { if (typeof jQuery === 'function') { if (container instanceof jQuery) container = container[0]; if (element instanceof jQuery) element = element[0]; } var containerBounds = container.getBoundingClientRect(); var elementBounds = element.getBoundingClientRect(); return containerBounds.top <= elementBounds.top && containerBounds.left <= elementBounds.left && elementBounds.top + elementBounds.height <= containerBounds.top + containerBounds.height && elementBounds.left + elementBounds.width <= containerBounds.left + containerBounds.width; } static alertError(errorType, errorText) { alert(errorType + '\n' + errorText); } static createOptionUnselectedProductVariation() { return { [flagProductVariationType]: { [flagNameAttrOptionText]: [flagName], [flagNameAttrOptionValue]: [attrIdProductVariationType], [flagName]: 'Select Variation Type', [attrIdProductVariationType]: 0 }, [flagProductVariation]: { [flagNameAttrOptionText]: [flagName], [flagNameAttrOptionValue]: [attrIdProductVariation], [flagName]: 'Select Variation', [attrIdProductVariation]: 0 } }; } static createOption(optionJson) { if (Validation.isEmpty(optionJson)) optionJson = { text: 'Select', value: 0 }; let option = document.createElement('option'); option.value = optionJson.value; option.textContent = optionJson.text; option.selected = optionJson.selected; return option; } static escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } static unescapeHtml(html) { const div = document.createElement('div'); div.innerHTML = html; return div.textContent || div.innerText || ''; } } ;// ./static/js/api.js class API { static getCsrfToken() { return document.querySelector(idCSRFToken).getAttribute('content'); } static async request(hashEndpoint, method = 'GET', data = null, params = null) { const url = API.getUrlFromHash(hashEndpoint, params); const csrfToken = API.getCsrfToken(); const options = { method, headers: { 'Content-Type': 'application/json', [flagCsrfToken]: csrfToken } }; if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) { data = { ...data, [flagCsrfToken]: csrfToken }; options.body = JSON.stringify(data); } try { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('API request failed:', error); throw error; } } static getUrlFromHash(hash, params = null) { if (hash == null) hash = hashPageHome; let url = API.parameteriseUrl(_pathHost + hash, params); return url; } static parameteriseUrl(url, params) { if (params) { url += '?' + new URLSearchParams(params).toString(); } return url; } static goToUrl(url) { window.location.href = url; } static goToHash(hash, params = null) { const url = API.getUrlFromHash(hash, params); API.goToUrl(url); } // specific api calls /* Example: getUsers: () => request('/users'), getUserById: (id) => request(`/users/${id}`), createUser: (userData) => request('/users', 'POST', userData), updateUser: (id, userData) => request(`/users/${id}`, 'PUT', userData), deleteUser: (id) => request(`/users/${id}`, 'DELETE'), */ // User // user static async loginUser() { let callback = {}; callback[flagCallback] = DOM.getHashPageCurrent(); return await API.request(hashPageUserLogin, 'POST', callback); } static async saveUsers(users, formFilters, comment) { let dataRequest = {}; dataRequest[flagFormFilters] = DOM.convertForm2JSON(formFilters); dataRequest[flagUser] = users; dataRequest[flagComment] = comment; return await API.request(hashSaveUserUser, 'POST', dataRequest); } // MTG Game API methods static async saveGame(game, formFilters, comment) { let dataRequest = {}; dataRequest[flagFormFilters] = DOM.convertForm2JSON(formFilters); dataRequest[flagGame] = game; dataRequest[flagComment] = comment; return await API.request(hashSaveGame, 'POST', dataRequest); } static async getGamePlayers(gameId) { const url = `/mtg/api/game/${gameId}/players`; return await API.request(url, 'GET'); } static async saveGamePlayers(players, formFilters, comment) { let dataRequest = {}; dataRequest[flagFormFilters] = DOM.convertForm2JSON(formFilters); dataRequest[flagPlayer] = players; dataRequest[flagComment] = comment; return await API.request(hashSaveGamePlayer, 'POST', dataRequest); } static async getGameRounds(gameId) { const url = `/mtg/api/game/${gameId}/rounds`; return await API.request(url, 'GET'); } static async getGameDamageRecords(gameId) { const url = `/mtg/api/game/${gameId}/damage-records`; return await API.request(url, 'GET'); } static async saveGameRoundPlayerDamages(rounds, damages, formFilters, comment) { let dataRequest = {}; dataRequest[flagFormFilters] = DOM.convertForm2JSON(formFilters); dataRequest[flagDamage] = damages; dataRequest[flagRound] = rounds; dataRequest[flagComment] = comment; return await API.request(hashSaveGameRoundPlayerDamage, 'POST', dataRequest); } } ;// ./static/js/lib/business_objects/business_objects.js class BusinessObjects { static getOptionJsonFromObjectJsonAndKeys(objectJson, keyText, keyValue, valueSelected = null) { return { text: objectJson[keyText], value: objectJson[keyValue], selected: objectJson[keyValue] == valueSelected }; } static getOptionJsonFromObjectJson(objectJson, valueSelected = null) { let keyText = objectJson[flagNameAttrOptionText]; let keyValue = objectJson[flagNameAttrOptionValue]; // Utils.consoleLogIfNotProductionEnvironment({objectJson, keyText, keyValue}); return BusinessObjects.getOptionJsonFromObjectJsonAndKeys(objectJson, keyText, keyValue, valueSelected); } static getObjectText(objectJson) { return objectJson == null ? '' : objectJson[objectJson[flagNameAttrOptionText]]; } static getListObjectsFromIdDictAndCsv(idDict, idCsv) { let listObjects = []; let ids = idCsv.split(','); for (let id of ids) { listObjects.push(idDict[id]); } return listObjects; } } ;// ./static/js/lib/events.js class Events { static initialiseEventHandler(selectorElement, classInitialised, eventHandler) { document.querySelectorAll(selectorElement).forEach(function (element) { if (element.classList.contains(classInitialised)) return; eventHandler(element); element.classList.add(classInitialised); }); } static hookupEventHandler(eventType, selector, callback) { Events.initialiseEventHandler(selector, flagInitialised, element => { element.addEventListener(eventType, event => { event.stopPropagation(); callback(event, element); }); }); } } ;// ./static/js/lib/local_storage.js class LocalStorage { /* function getPageLocalStorage(pageHash) { let ls; try { ls = JSON.parse(localStorage.getItem(pageHash)); } catch { } if (Validation.isEmpty(ls)) return {} return ls; } function getPageLocalStorageCurrent() { return JSON.parse(localStorage.getItem(hashPageCurrent)); } function setPageLocalStorage(pageHash, newLS) { localStorage.setItem(pageHash, JSON.stringify(newLS)); } function clearPageLocalStorage(pageHash) { localStorage.removeItem(pageHash); } function setupPageLocalStorage(pageHash) { let ls = getPageLocalStorage(pageHash); if (Validation.isEmpty(ls)) ls = {}; setPageLocalStorage(pageHash, ls); } */ static getLocalStorage(key) { return JSON.parse(localStorage.getItem(key)); } static setLocalStorage(key, newLS) { localStorage.setItem(key, JSON.stringify(newLS)); } /* function setupPageLocalStorageNext(pageHashNext) { let lsOld = getPageLocalStorage(hashPageCurrent); hashPageCurrent = pageHashNext; clearPageLocalStorage(hashPageCurrent); setupPageLocalStorage(hashPageCurrent); let lsNew = getPageLocalStorage(hashPageCurrent); lsNew[keyBasket] = (keyBasket in lsOld) ? lsOld[keyBasket] : {'items': []}; setPageLocalStorage(hashPageCurrent, lsNew); } */ } ;// ./static/js/lib/utils.js // Utility functions /* function $(selector) { return document.querySelector(selector); } function $$(selector) { return document.querySelectorAll(selector); } */ class utils_Utils { static getListFromDict(dict) { let list = []; for (let key in dict) { list.push(dict[key]); } return list; } static consoleLogIfNotProductionEnvironment(message) { if (environment.is_production != "true") { console.log(message); } } } ;// ./static/js/components/common/temporary/overlay_confirm.js class OverlayConfirm { static hookup(callbackSuccess) { Events.initialiseEventHandler(idOverlayConfirm + ' button.' + flagCancel, flagInitialised, buttonCancel => { buttonCancel.addEventListener('click', () => { let overlay = document.querySelector(idOverlayConfirm); overlay.style.visibility = 'hidden'; }); }); Events.initialiseEventHandler(idOverlayConfirm + ' button.' + flagSubmit, flagInitialised, buttonConfirm => { buttonConfirm.addEventListener('click', () => { let overlay = document.querySelector(idOverlayConfirm); let textarea = overlay.querySelector('textarea'); overlay.style.visibility = 'hidden'; callbackSuccess(textarea.value); }); }); } static show() { let overlay = document.querySelector(idOverlayConfirm); overlay.classList.remove(flagIsCollapsed); overlay.style.visibility = 'visible'; } } ;// ./static/js/pages/base.js class BasePage { constructor(router) { if (!router) { throw new Error("Router is required"); } else { utils_Utils.consoleLogIfNotProductionEnvironment("initialising with router: ", router); } this.router = router; this.title = titlePageCurrent; if (this.constructor === BasePage) { throw new Error("Cannot instantiate abstract class"); } if (!this.constructor.hash) { throw new Error(`Class ${this.constructor.name} must have a static hash attribute.`); } } initialize() { throw new Error("Method 'initialize()' must be implemented."); } sharedInitialize() { this.logInitialisation(); this.hookupCommonElements(); } logInitialisation() { utils_Utils.consoleLogIfNotProductionEnvironment('Initialising ' + this.title + ' page'); } hookupCommonElements() { // hookupVideos(); this.hookupLogos(); this.hookupNavigation(); this.hookupOverlays(); } hookupLogos() { Events.hookupEventHandler("click", "." + flagImageLogo + "," + "." + flagLogo, (event, element) => { utils_Utils.consoleLogIfNotProductionEnvironment('clicking logo'); this.router.navigateToHash(hashPageHome); }); } /* hookupEventHandler(eventType, selector, callback) { Events.initialiseEventHandler(selector, flagInitialised, (element) => { element.addEventListener(eventType, (event) => { event.stopPropagation(); callback(event, element); }); }); } */ hookupNavigation() { Events.hookupEventHandler("click", idButtonHamburger, (event, element) => { let overlayHamburger = document.querySelector(idOverlayHamburger); if (overlayHamburger.classList.contains(flagIsCollapsed)) { overlayHamburger.classList.remove(flagIsCollapsed); overlayHamburger.classList.add(flagExpanded); } else { overlayHamburger.classList.remove(flagExpanded); overlayHamburger.classList.add(flagIsCollapsed); } }); this.hookupButtonsNavUserAccount(); this.hookupButtonsNavUserLogout(); this.hookupButtonsNavUserLogin(); } hookupButtonsNav(buttonSelector) { Events.hookupEventHandler("click", buttonSelector, (event, button) => { let pageHash = buttonSelector.getAttribute('href'); this.router.navigateToHash(pageHash); }); } hookupButtonsNavUserAccount() { // this.hookupButtonsNav('.' + flagNavUserAccount); } hookupButtonsNavUserLogout() { // this.hookupButtonsNav('.' + flagNavUserLogout); } hookupButtonsNavUserLogin() { Events.hookupEventHandler("click", '.' + flagNavUserLogin, (event, navigator) => { event.preventDefault(); event.stopPropagation(); this.leave(); API.loginUser().then(response => { if (response.Success) { window.location.href = response[flagCallback]; } else { DOM.alertError("Error", response.Message); } }); }); } hookupOverlays() { this.hookupOverlayFromId(idOverlayConfirm); this.hookupOverlayFromId(idOverlayError); } hookupOverlayFromId(idOverlay) { Events.initialiseEventHandler(idOverlay, flagInitialised, overlay => { overlay.querySelector('button.' + flagCancel).addEventListener("click", event => { event.stopPropagation(); overlay.style.display = 'none'; }); }); } hookupButtonSave() { Events.initialiseEventHandler('.' + flagContainer + '.' + flagSave + '.' + flagCancel + ' button.' + flagSave, flagInitialised, button => { button.addEventListener("click", event => { event.stopPropagation(); button = event.target; if (button.classList.contains(flagIsCollapsed)) return; utils_Utils.consoleLogIfNotProductionEnvironment('saving page: ', this.title); OverlayConfirm.show(); }); }); } leave() { utils_Utils.consoleLogIfNotProductionEnvironment('Leaving ' + this.title + ' page'); if (this.constructor === BasePage) { throw new Error("Must implement leave() method."); } } setLocalStoragePage(dataPage) { LocalStorage.setLocalStorage(this.hash, dataPage); } getLocalStoragePage() { return LocalStorage.getLocalStorage(this.hash); } toggleShowButtonsSaveCancel(show, buttonContainerSelector = null) { // , buttonSave = null, buttonCancel = null if (Validation.isEmpty(buttonContainerSelector)) buttonContainerSelector = '.' + flagContainer + '.' + flagSave + '.' + flagCancel; let buttonSave = document.querySelector(buttonContainerSelector + ' ' + idButtonSave); let buttonCancel = document.querySelector(buttonContainerSelector + ' ' + idButtonCancel); utils_Utils.consoleLogIfNotProductionEnvironment({ show, buttonContainerSelector, buttonCancel, buttonSave }); if (show) { buttonCancel.classList.remove(flagIsCollapsed); buttonSave.classList.remove(flagIsCollapsed); utils_Utils.consoleLogIfNotProductionEnvironment('showing buttons'); } else { buttonCancel.classList.add(flagIsCollapsed); buttonSave.classList.add(flagIsCollapsed); utils_Utils.consoleLogIfNotProductionEnvironment('hiding buttons'); } } static isDirtyFilter(filter) { let isDirty = DOM.updateAndCheckIsElementDirty(filter); if (isDirty) document.querySelectorAll(idTableMain + ' tbody tr').remove(); return isDirty; } } ;// ./static/js/components/common/temporary/overlay_error.js class OverlayError { static hookup() { Events.initialiseEventHandler(idOverlayError + ' button.' + flagCancel, flagInitialised, buttonCancel => { buttonCancel.addEventListener('click', () => { let overlay = document.querySelector(idOverlayError); overlay.style.visibility = 'hidden'; }); }); } static show(msgError) { let overlay = document.querySelector(idOverlayError); let labelError = overlay.querySelector(idLabelError); labelError.innerText = msgError; overlay.style.visibility = 'visible'; } } ;// ./static/js/pages/base_table.js class TableBasePage extends BasePage { // static hash // static attrIdRowObject // callSaveTableContent constructor(router) { super(router); this.cursorYInitial = null; this.rowInitial = null; this.placeholder = null; this.dragSrcEl = null; this.dragSrcRow = null; this.hookupTableCellDdls = this.hookupTableCellDdls.bind(this); } initialize(isPopState = false) { throw new Error("Must implement initialize() method."); } sharedInitialize(isPopState = false, isSinglePageApp = false) { if (!isPopState) { super.sharedInitialize(); this.hookupFilters(); this.hookupButtonsSaveCancel(); this.hookupTableMain(); OverlayConfirm.hookup(() => { if (isSinglePageApp) { this.saveRecordsTableDirtySinglePageApp(); } else { this.saveRecordsTableDirty(); } }); } else { let dataPage = this.getLocalStoragePage(); let filters = dataPage[flagFormFilters]; let formFilters = TableBasePage.getFormFilters(); let filtersDefault = DOM.convertForm2JSON(formFilters); if (!Validation.areEqualDicts(filters, filtersDefault)) { this.callFilterTableContent(); } } } hookupFilters() { if (this.constructor === TableBasePage) { throw new Error("Subclass of TableBasePage must implement method hookupFilters()."); } } sharedHookupFilters() { this.hookupButtonApplyFilters(); this.hookupSearchTextFilter(); } hookupFilterActive() { let filterSelector = idFormFilters + ' #' + flagActiveOnly; let filterActiveOld = document.querySelector(filterSelector); filterActiveOld.removeAttribute('id'); let parentDiv = filterActiveOld.parentElement; let isChecked = DOM.getElementAttributeValuePrevious(parentDiv) == "True"; let filterActiveNew = document.querySelector(idFormFilters + ' div.' + flagActiveOnly + '.' + flagContainerInput + ' svg.' + flagActiveOnly); filterActiveNew.setAttribute('id', flagActiveOnly); if (isChecked) filterActiveNew.classList.add(flagIsChecked); Events.hookupEventHandler("click", filterSelector, (event, filterActive) => { utils_Utils.consoleLogIfNotProductionEnvironment({ filterActive }); utils_Utils.consoleLogIfNotProductionEnvironment({ [filterActive.tagName]: filterActive.tagName }); let svgElement = filterActive.tagName.toUpperCase() == 'SVG' ? filterActive : filterActive.parentElement; let wasChecked = svgElement.classList.contains(flagIsChecked); if (wasChecked) { svgElement.classList.remove(flagIsChecked); } else { svgElement.classList.add(flagIsChecked); } return this.handleChangeFilter(event, filterActive); }); let filter = document.querySelector(filterSelector); let filterValuePrevious = DOM.getElementValueCurrent(filter); filter.setAttribute(attrValueCurrent, filterValuePrevious); filter.setAttribute(attrValuePrevious, filterValuePrevious); } hookupFilter(filterFlag, handler = (event, filter) => { return this.handleChangeFilter(event, filter); }) { let filterSelector = idFormFilters + ' #' + filterFlag; Events.hookupEventHandler("change", filterSelector, handler); let filter = document.querySelector(filterSelector); let filterValuePrevious = DOM.getElementValueCurrent(filter); filter.setAttribute(attrValueCurrent, filterValuePrevious); filter.setAttribute(attrValuePrevious, filterValuePrevious); } handleChangeFilter(event, filter) { let isDirtyFilter = DOM.updateAndCheckIsElementDirty(filter); let formFilters = TableBasePage.getFormFilters(); let areDirtyFilters = isDirtyFilter || DOM.hasDirtyChildrenContainer(formFilters); let tbody = document.querySelector(idTableMain + ' tbody'); let rows = tbody.querySelectorAll(':scope > tr'); rows.forEach(row => { if (areDirtyFilters && !row.classList.contains(flagIsCollapsed)) row.classList.add(flagIsCollapsed); if (!areDirtyFilters && row.classList.contains(flagIsCollapsed)) { row.classList.remove(flagIsCollapsed); let dirtyInputs = row.querySelectorAll('input.' + flagDirty); dirtyInputs.forEach(dirtyInput => { dirtyInput.value = DOM.getElementAttributeValueCurrent(dirtyInput); }); } }); if (areDirtyFilters) { /* tbody.querySelectorAll('tr').forEach((tr) => { if (!DOM.hasDirtyChildrenContainer(tr)) tr.remove(); }); */ tbody.innerHTML = '
Press "Apply Filters" to refresh the table.
' + tbody.innerHTML; if (!tbody.classList.contains(flagIsCollapsed)) tbody.classList.add(flagIsCollapsed); } else { let isDirtyLabel = tbody.querySelector(":scope > div"); if (isDirtyLabel != null) isDirtyLabel.remove(); if (tbody.classList.contains(flagIsCollapsed)) tbody.classList.remove(flagIsCollapsed); let initialisedElements = tbody.querySelectorAll('.' + flagInitialised); initialisedElements.forEach(initialisedElement => { initialisedElement.classList.remove(flagInitialised); }); this.hookupTableMain(); } this.updateAndToggleShowButtonsSaveCancel(); } hookupFilterIsNotEmpty() { this.hookupFilter(flagIsNotEmpty); } hookupButtonApplyFilters() { Events.hookupEventHandler("click", idButtonApplyFilters, (event, button) => { event.stopPropagation(); this.callFilterTableContent(); }); } hookupSearchTextFilter() { this.hookupFilter(flagSearch); } hookupFilterCommandCategory() { this.hookupFilter(attrIdCommandCategory, (event, filterCommandCategory) => { this.handleChangeFilter(); let isDirtyFilter = filterCommandCategory.classList.contains(flagDirty); let idCommandCategory = DOM.getElementValueCurrent(filterCommandCategory); console.log("filter commands unsorted"); console.log(utils_Utils.getListFromDict(filterCommands)); let commandsInCategory = utils_Utils.getListFromDict(filterCommands).filter(command => command[attrIdCommandCategory] == idCommandCategory); let sortedCommands = commandsInCategory.sort((a, b) => a[flagName].localeCompare(b[flagName])); let filterCommand = document.querySelector(idFormFilters + ' .' + flagCommand); let idCommandPrevious = DOM.getElementAttributeValuePrevious(filterCommand); filterCommand.innerHTML = ''; let optionJson, option; option = DOM.createOption(null); filterCommand.appendChild(option); sortedCommands.forEach(command => { optionJson = BusinessObjects.getOptionJsonFromObjectJson(command, idCommandPrevious); option = DOM.createOption(optionJson); filterCommand.appendChild(option); }); filterCommand.dispatchEvent(new Event('change')); return isDirtyFilter; }); } hookupFilterCommand() { this.hookupFilter(attrIdCommand); } hookupFilterLocation() { this.hookupFilter(attrIdLocation); } /* getAndLoadFilteredTableContent = () => { this.callFilterTableContent() .catch(error => console.error('Error:', error)); } */ static getFormFilters() { return document.querySelector(idFormFilters); } callFilterTableContent() { let formFilters = TableBasePage.getFormFilters(); let filtersJson = DOM.convertForm2JSON(formFilters); utils_Utils.consoleLogIfNotProductionEnvironment("callFilterTableContent"); utils_Utils.consoleLogIfNotProductionEnvironment("formFilters"); utils_Utils.consoleLogIfNotProductionEnvironment(formFilters); utils_Utils.consoleLogIfNotProductionEnvironment("filtersJson"); utils_Utils.consoleLogIfNotProductionEnvironment(filtersJson); this.leave(); API.goToHash(this.constructor.hash, filtersJson); } callbackLoadTableContent(response) { let table = TableBasePage.getTableMain(); let bodyTable = table.querySelector('tbody'); bodyTable.querySelectorAll('tr').forEach(function (row) { row.remove(); }); let rowsJson = response.data[flagRows]; if (!Validation.isEmpty(rowsJson) && rowsJson.every(row => row.hasOwnProperty('display_order'))) { rowsJson = rowsJson.sort((a, b) => a.display_order - b.display_order); } rowsJson.forEach(this.loadRowTable.bind(this)); this.hookupTableMain(); } static getTableMain() { return document.querySelector(idTableMain); } loadRowTable(rowJson) { throw new Error("Subclass of TableBasePage must implement method loadRowTable()."); } getAndLoadFilteredTableContentSinglePageApp() { this.callFilterTableContent().then(data => { utils_Utils.consoleLogIfNotProductionEnvironment('Table data received:', data); this.callbackLoadTableContent(data); }).catch(error => console.error('Error:', error)); } hookupButtonsSaveCancel() { this.hookupButtonSave(); this.hookupButtonCancel(); this.toggleShowButtonsSaveCancel(false); } saveRecordsTableDirty() { let records = this.getTableRecords(true); if (records.length == 0) { OverlayError.show('No records to save'); return; } let formElement = TableBasePage.getFormFilters(); let comment = DOM.getElementValueCurrent(document.querySelector(idTextareaConfirm)); /* Utils.consoleLogIfNotProductionEnvironment({ formElement, comment, records }); Utils.consoleLogIfNotProductionEnvironment('records'); Utils.consoleLogIfNotProductionEnvironment(records); debugger; */ this.callSaveTableContent(records, formElement, comment).then(data => { if (data[flagStatus] == flagSuccess) { if (_verbose) { utils_Utils.consoleLogIfNotProductionEnvironment('Records saved!'); utils_Utils.consoleLogIfNotProductionEnvironment('Data received:', data); } this.callFilterTableContent(); } else { utils_Utils.consoleLogIfNotProductionEnvironment("error: ", data[flagMessage]); OverlayError.show(data[flagMessage]); } }).catch(error => console.error('Error:', error)); } getTableRecords(dirtyOnly = false) { let records = []; let record; document.querySelectorAll(idTableMain + ' > tbody > tr').forEach(row => { if (dirtyOnly && !DOM.hasDirtyChildrenContainer(row)) return; record = this.getJsonRow(row); records.push(record); }); return records; } getJsonRow(row) { throw new Error("Subclass of TableBasePage must implement method getJsonRow()."); } saveRecordsTableDirtySinglePageApp() { let records = this.getTableRecords(true); if (records.length == 0) { OverlayError.show('No records to save'); return; } let formElement = TableBasePage.getFormFilters(); let comment = DOM.getElementValueCurrent(document.querySelector(idTextareaConfirm)); this.callSaveTableContent(records, formElement, comment).then(data => { if (data[flagStatus] == flagSuccess) { if (_verbose) { utils_Utils.consoleLogIfNotProductionEnvironment('Records saved!'); utils_Utils.consoleLogIfNotProductionEnvironment('Data received:', data); } this.callbackLoadTableContent(data); } else { utils_Utils.consoleLogIfNotProductionEnvironment("error: ", data[flagMessage]); OverlayError.show(data[flagMessage]); } }).catch(error => console.error('Error:', error)); } hookupButtonCancel() { Events.initialiseEventHandler('.' + flagContainer + '.' + flagSave + '.' + flagCancel + ' button.' + flagCancel, flagInitialised, button => { button.addEventListener("click", event => { event.stopPropagation(); button = event.target; if (button.classList.contains(flagIsCollapsed)) return; this.callFilterTableContent(); }); button.classList.add(flagIsCollapsed); }); } handleClickAddRowTable(event, button) { event.stopPropagation(); _rowBlank.setAttribute(this.constructor.attrIdRowObject, -1 - _rowBlank.getAttribute(this.constructor.attrIdRowObject)); let tbody = document.querySelector(idTableMain + ' tbody'); if (tbody.classList.contains(flagIsCollapsed)) return; let row = _rowBlank.cloneNode(true); row.classList.remove(flagInitialised); row.querySelectorAll('.' + flagInitialised).forEach(function (element) { element.classList.remove(flagInitialised); }); let countRows = document.querySelectorAll(idTableMain + ' > tbody > tr').length; row.setAttribute(this.constructor.attrIdRowObject, -1 - countRows); this.initialiseRowNew(tbody, row); tbody.prepend(row); tbody.scrollTop = 0; this.hookupTableMain(); this.postInitialiseRowNewCallback(tbody); } initialiseRowNew(tbody, row) { if (this.constructor === TableBasePage) { throw new Error("Subclass of TableBasePage must implement method initialiseRowNew()."); } // row.classList.remove(flagRowNew); } hookupTableMain() { if (this.constructor === TableBasePage) { throw new Error("Must implement hookupTableMain() method."); } Events.initialiseEventHandler(idTableMain, flagInitialised, table => { this.cacheRowBlank(); }); } cacheRowBlank() { let selectorRowNew = idTableMain + ' tbody tr.' + flagRowNew; let rowBlankTemp = document.querySelector(selectorRowNew); utils_Utils.consoleLogIfNotProductionEnvironment("row blank temp: ", rowBlankTemp); let countRows = document.querySelectorAll(idTableMain + ' > tbody > tr').length; _rowBlank = rowBlankTemp.cloneNode(true); document.querySelectorAll(selectorRowNew).forEach(function (row) { row.remove(); }); _rowBlank.setAttribute(this.constructor.attrIdRowObject, -1 - countRows); } postInitialiseRowNewCallback(tbody) { if (this.constructor === TableBasePage) { throw new Error("Subclass of TableBasePage must implement method postInitialiseRowNewCallback(tbody)."); } } initialiseSliderDisplayOrderRowNew(tbody, row) { // let tdSelector = ':scope > tr > td.' + flagDisplayOrder; // let tbody = document.querySelector('table' + (Validation.isEmpty(flagTable) ? '' : '.' + flagTable) + ' > tbody'); let slidersDisplayOrder = tbody.querySelectorAll(':scope > tr > td.' + flagDisplayOrder + ' input.' + flagSlider); let maxDisplayOrder = 0; slidersDisplayOrder.forEach(slider => { maxDisplayOrder = Math.max(maxDisplayOrder, parseFloat(DOM.getElementValueCurrent(slider))); }); let sliderDisplayOrder = row.querySelector('td.' + flagDisplayOrder + ' .' + flagSlider); DOM.setElementValuesCurrentAndPrevious(sliderDisplayOrder, maxDisplayOrder + 1); } hookupSlidersDisplayOrderTable() { let selectorDisplayOrder = idTableMain + ' tbody tr td.' + flagDisplayOrder + ' input.' + flagSlider + '.' + flagDisplayOrder; this.hookupChangeHandlerTableCells(selectorDisplayOrder); } hookupChangeHandlerTableCells(inputSelector, handler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) { Events.initialiseEventHandler(inputSelector, flagInitialised, input => { input.addEventListener("change", event => { handler(event, input); }); handler(null, input); }); } handleChangeNestedElementCellTable(event, element) { let wasDirtyParentRows = this.getAllIsDirtyRowsInParentTree(element); let wasDirtyElement = element.classList.contains(flagDirty); let isDirtyElement = DOM.updateAndCheckIsElementDirty(element); // Utils.consoleLogIfNotProductionEnvironment({isDirtyElement, wasDirtyElement, wasDirtyParentRows}); // let td = DOM.getCellFromElement(element); // DOM.setElementAttributeValueCurrent(td, DOM.getElementAttributeValueCurrent(element)); if (isDirtyElement != wasDirtyElement) { // DOM.handleDirtyElement(td, isDirtyElement); this.updateAndToggleShowButtonsSaveCancel(); this.cascadeChangedIsDirtyNestedElementCellTable(element, isDirtyElement, wasDirtyParentRows); } } getAllIsDirtyRowsInParentTree(element) { let rows = []; let parent = element; let isDirty; while (parent) { if (parent.tagName.toUpperCase() == 'TR') { isDirty = parent.classList.contains(flagDirty); rows.push(isDirty); } parent = parent.parentElement; } return rows; } cascadeChangedIsDirtyNestedElementCellTable(element, isDirtyElement, wasDirtyParentRows) { if (Validation.isEmpty(wasDirtyParentRows)) return; let tr = DOM.getRowFromElement(element); let isDirtyRow = isDirtyElement || DOM.hasDirtyChildrenContainer(tr); let wasDirtyRow = wasDirtyParentRows.shift(); utils_Utils.consoleLogIfNotProductionEnvironment({ isDirtyRow, wasDirtyRow }); if (isDirtyRow != wasDirtyRow) { DOM.handleDirtyElement(tr, isDirtyRow); this.updateAndToggleShowButtonsSaveCancel(); this.cascadeChangedIsDirtyNestedElementCellTable(tr.parentElement, isDirtyRow, wasDirtyParentRows); } } hookupChangeHandlerTableCellsWhenNotCollapsed(inputSelector, handler = (event, element) => { if (!element.classList.contains(flagIsCollapsed)) this.handleChangeNestedElementCellTable(event, element); }) { Events.hookupEventHandler("change", inputSelector, handler); } hookupFieldsCodeTable() { this.hookupChangeHandlerTableCells(idTableMain + ' > tbody > tr > td.' + flagCode + ' > .' + flagCode); } hookupFieldsNameTable() { this.hookupChangeHandlerTableCells(idTableMain + ' > tbody > tr > td.' + flagName + ' > .' + flagName); } hookupFieldsDescriptionTable() { this.hookupChangeHandlerTableCells(idTableMain + ' > tbody > tr > td.' + flagDescription + ' > .' + flagDescription); } hookupFieldsNotesTable() { this.hookupChangeHandlerTableCells(idTableMain + ' > tbody > tr > td.' + flagNotes + ' > .' + flagNotes); } hookupFieldsActive(flagTable = '', handleClickRowNew = (event, element) => { this.handleClickAddRowTable(event, element); }) { let selectorButton = 'table.table-main' + (Validation.isEmpty(flagTable) ? '' : '.' + flagTable) + ' > tbody > tr > td.' + flagActive + ' .' + flagButton + '.' + flagActive; let selectorButtonDelete = selectorButton + '.' + flagDelete; let selectorButtonUndelete = selectorButton + ':not(.' + flagDelete + ')'; utils_Utils.consoleLogIfNotProductionEnvironment("hookupFieldsActive: ", selectorButtonDelete, selectorButtonUndelete); this.hookupButtonsRowDelete(selectorButtonDelete, selectorButtonUndelete); this.hookupButtonsRowUndelete(selectorButtonDelete, selectorButtonUndelete); Events.hookupEventHandler("click", 'table.table-main' + (Validation.isEmpty(flagTable) ? '' : '.' + flagTable) + ' > thead > tr > th.' + flagActive + ' .' + flagButton + '.' + flagActive, (event, button) => { handleClickRowNew(event, button); }); } hookupButtonsRowDelete(selectorButtonDelete, selectorButtonUndelete, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) { Events.hookupEventHandler("click", selectorButtonDelete, (event, element) => { this.handleClickButtonRowDelete(event, element, selectorButtonDelete, selectorButtonUndelete, (changeEvent, changeElement) => { changeHandler(changeEvent, changeElement); }); }); } handleClickButtonRowDelete(event, element, selectorButtonDelete, selectorButtonUndelete, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) { if (element.tagName.toUpperCase() != 'SVG') element = element.parentElement; let valuePrevious = DOM.getElementAttributeValuePrevious(element); let wasDirty = element.classList.contains(flagDirty); let row = DOM.getRowFromElement(element); if (row.classList.contains(flagRowNew) && !DOM.hasDirtyChildrenContainer(row)) { row.parentNode.removeChild(row); } else { let buttonAddTemplate = document.querySelector(idContainerTemplateElements + ' .' + flagButton + '.' + flagActive + '.' + flagAdd); let buttonAdd = buttonAddTemplate.cloneNode(true); DOM.setElementAttributeValuePrevious(buttonAdd, valuePrevious); DOM.setElementAttributeValueCurrent(buttonAdd, false); if (wasDirty) buttonAdd.classList.add(flagDirty); element.replaceWith(buttonAdd); changeHandler(null, buttonAdd); this.hookupButtonsRowUndelete(selectorButtonDelete, selectorButtonUndelete, (changeEvent, changeElement) => { changeHandler(changeEvent, changeElement); }); } this.updateAndToggleShowButtonsSaveCancel(); } hookupButtonsRowUndelete(selectorButtonDelete, selectorButtonUndelete, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) { Events.hookupEventHandler("click", selectorButtonUndelete, (event, element) => { this.handleClickButtonRowUndelete(event, element, selectorButtonDelete, selectorButtonUndelete, (changeEvent, changeElement) => { changeHandler(changeEvent, changeElement); }); }); } handleClickButtonRowUndelete(event, element, selectorButtonDelete, selectorButtonUndelete, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) { if (element.tagName.toUpperCase() != 'SVG') element = element.parentElement; let valuePrevious = DOM.getElementAttributeValuePrevious(element); let wasDirty = DOM.isElementDirty(element); let buttonDeleteTemplate = document.querySelector(idContainerTemplateElements + ' .' + flagButton + '.' + flagActive + '.' + flagDelete); let buttonDelete = buttonDeleteTemplate.cloneNode(true); DOM.setElementAttributeValuePrevious(buttonDelete, valuePrevious); DOM.setElementAttributeValueCurrent(buttonDelete, true); if (wasDirty) buttonDelete.classList.add(flagDirty); element.replaceWith(buttonDelete); changeHandler(null, buttonDelete); this.hookupButtonsRowDelete(selectorButtonDelete, selectorButtonUndelete, (changeEvent, changeElement) => { changeHandler(changeEvent, changeElement); }); this.updateAndToggleShowButtonsSaveCancel(); } hookupTdsAccessLevel() { this.hookupTableCellDdlPreviews(flagAccessLevel, utils_Utils.getListFromDict(accessLevels)); } hookupTableCellDdlPreviews(fieldFlag, optionList, cellSelector = null, ddlHookup = ddlSelector => { this.hookupTableCellDdls(ddlSelector); }, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) { if (cellSelector == null) cellSelector = idTableMain + ' > tbody > tr > td.' + fieldFlag; Events.hookupEventHandler("click", cellSelector + ' div.' + fieldFlag, (event, div) => { this.handleClickTableCellDdlPreview(event, div, fieldFlag, optionList, cellSelector, ddlSelector => { ddlHookup(ddlSelector, (event, element) => { changeHandler(event, element); }); }); }); ddlHookup(cellSelector + ' select.' + fieldFlag); } hookupTableCellDdls(ddlSelector, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) { this.hookupChangeHandlerTableCells(ddlSelector, (event, element) => { changeHandler(event, element); }); } handleClickTableCellDdlPreview(event, div, fieldFlag, optionObjectList, cellSelector = null, ddlHookup = cellSelector => { this.hookupTableCellDdls(cellSelector); }) { if (Validation.isEmpty(cellSelector)) cellSelector = idTableMain + ' > tbody > tr > td.' + fieldFlag; let idSelected = DOM.getElementAttributeValueCurrent(div); let td = DOM.getCellFromElement(div); td.innerHTML = ''; let ddl = document.createElement('select'); ddl.classList.add(fieldFlag); DOM.setElementValuesCurrentAndPrevious(ddl, idSelected); let optionJson, option; if (_verbose) { utils_Utils.consoleLogIfNotProductionEnvironment("click table cell ddl preview"); utils_Utils.consoleLogIfNotProductionEnvironment({ optionObjectList, cellSelector }); } option = DOM.createOption(null); ddl.appendChild(option); optionObjectList.forEach(optionObjectJson => { optionJson = BusinessObjects.getOptionJsonFromObjectJson(optionObjectJson, idSelected); option = DOM.createOption(optionJson); ddl.appendChild(option); }); td.appendChild(ddl); let ddlSelector = cellSelector + ' select.' + fieldFlag; ddlHookup(ddlSelector); } /* hookupTableCellDDlPreviewsWhenNotCollapsed(cellSelector, optionList, ddlHookup = (event, element) => { this.hookupTableCellDdls(event, element); }) { Events.hookupEventHandler("click", cellSelector + ' div', (event, div) => { this.handleClickTableCellDdlPreview(event, div, optionList, cellSelector, (event, element) => { ddlHookup(event, element); }); }); } */ toggleColumnCollapsed(flagColumn, isCollapsed) { this.toggleColumnHasClassnameFlag(flagColumn, isCollapsed, flagIsCollapsed); } toggleColumnHeaderCollapsed(flagColumn, isCollapsed) { this.toggleColumnHasClassnameFlag(flagColumn, isCollapsed, flagIsCollapsed); } hookupFieldsCommandCategory(idTable = null) { if (idTable == null) idTable = idTableMain; this.hookupTableCellDdlPreviews(flagCommandCategory, utils_Utils.getListFromDict(filterCommandCategories).sort((a, b) => a[flagName].localeCompare(b[flagName])), idTable + ' > tbody > tr > td.' + flagCommandCategory // + ' .' + flagCommandCategory , cellSelector => { this.hookupCommandCategoryDdls(cellSelector); }); } hookupCommandCategoryDdls(ddlSelector) { this.hookupChangeHandlerTableCells(ddlSelector, (event, element) => { this.handleChangeCommandCategoryDdl(event, element); }); } handleChangeCommandCategoryDdl(event, ddlCategory) { let row = DOM.getRowFromElement(ddlCategory); let idCommandCategoryRowOld = this.getIdCommandCategoryRow(row); // DOM.getElementAttributeValueCurrent(ddlCategory); this.handleChangeNestedElementCellTable(event, ddlCategory); let idCommandCategoryRowNew = this.getIdCommandCategoryRow(row); // DOM.getElementAttributeValueCurrent(ddlCategory); if (idCommandCategoryRowOld == idCommandCategoryRowNew || idCommandCategoryRowNew == 0) return; console.log({ idCommandCategoryRowNew, idCommandCategoryRowOld }); let idCommandCategoryFilter = this.getIdCommandCategoryFilter(); let tdCommand = row.querySelector('td.' + flagCommand); tdCommand.dispatchEvent(new Event('click')); let ddlCommand = row.querySelector('td.' + flagCommand + ' select.' + flagCommand); ddlCommand.innerHTML = ''; ddlCommand.appendChild(DOM.createOption(null)); let optionJson, option; let commandsInCategory = utils_Utils.getListFromDict(filterCommands).filter(command => (command[attrIdCommandCategory] == idCommandCategoryRowNew || idCommandCategoryRowNew == 0) && (command[attrIdCommandCategory] == idCommandCategoryFilter || idCommandCategoryFilter == 0)); let sortedCommands = commandsInCategory.sort((a, b) => a[flagName].localeCompare(b[flagName])); sortedCommands.forEach(command => { optionJson = BusinessObjects.getOptionJsonFromObjectJson(command); option = DOM.createOption(optionJson); ddlCommand.appendChild(option); }); this.handleChangeNestedElementCellTable(event, ddlCommand); } hookupFieldsCommand(idTable = null) { if (idTable == null) idTable = idTableMain; Events.hookupEventHandler("click", idTable + ' > tbody > tr > td.' + flagCommand + ' div.' + flagCommand, (event, div) => { utils_Utils.consoleLogIfNotProductionEnvironment(div); let parentTr = DOM.getRowFromElement(div); utils_Utils.consoleLogIfNotProductionEnvironment({ div, parentTr }); let tdCommandCategory = parentTr.querySelector('td.' + flagCommandCategory); let idCommandCategoryRow = this.getIdCommandCategoryRow(parentTr); // DOM.getElementAttributeValueCurrent(tdCommandCategory); let idCommandCategoryFilter = this.getIdCommandCategoryFilter(); let filterCommandList = utils_Utils.getListFromDict(filterCommands); let commandsInCategory = filterCommandList.filter(command => (command[attrIdCommandCategory] == idCommandCategoryRow || idCommandCategoryRow == 0) && (command[attrIdCommandCategory] == idCommandCategoryFilter || idCommandCategoryFilter == 0)); let sortedCommands = commandsInCategory.sort((a, b) => a[flagName].localeCompare(b[flagName])); utils_Utils.consoleLogIfNotProductionEnvironment({ tdCommandCategory, idCommandCategoryRow, idCommandCategoryFilter, filterCommandList, commandsInCategory }); utils_Utils.consoleLogIfNotProductionEnvironment(filterCommandList); this.handleClickTableCellDdlPreview(event, div, flagCommand // fieldFlag , sortedCommands // optionList , idTable + ' > tbody > tr > td.' + flagCommand // cellSelector , cellSelector => { this.hookupTableCellDdls(cellSelector, (event, element) => { this.handleChangeCommandDdl(event, element); }); }); }); this.hookupTableCellDdls(idTable + ' > tbody > tr > td.' + flagCommand + ' select.' + flagCommand, (event, element) => { this.handleChangeCommandDdl(event, element); }); } handleChangeCommandDdl(event, ddlCommand) { // console.log("handle change command ddl"); let row = DOM.getRowFromElement(ddlCommand); this.handleChangeNestedElementCellTable(event, ddlCommand); let idCommandCategoryRowOld = this.getIdCommandCategoryRow(row); let idCommandNew = this.getIdCommandRow(row); let commandNew = filterCommands[idCommandNew]; // console.log({ idCommandCategoryRowOld, commandNew }); if (commandNew == null || idCommandCategoryRowOld == commandNew[attrIdCommandCategory]) return; let divCommandCategory = row.querySelector('td.' + flagCommandCategory + ' div'); if (divCommandCategory) divCommandCategory.dispatchEvent(new Event('click')); let ddlCommandCategory = row.querySelector('td.' + flagCommandCategory + ' select.' + flagCommandCategory); DOM.setElementValueCurrent(ddlCommandCategory, commandNew[attrIdCommandCategory]); // console.log({ ddlCommandCategory, commandNew }); this.handleChangeNestedElementCellTable(event, ddlCommandCategory); } getIdCommandCategoryRow(tr) { let elementCommandCategory = tr.querySelector('td.' + flagCommandCategory + ' .' + flagCommandCategory); return DOM.getElementAttributeValueCurrent(elementCommandCategory); } getIdCommandCategoryFilter() { let formFilters = TableBasePage.getFormFilters(); let idCommandCategory = 0; if (formFilters == null) return idCommandCategory; let commandCategoryFilter = formFilters.querySelector('#' + attrIdCommandCategory); let commandFilter = formFilters.querySelector('#' + attrIdCommand); let valueCurrentCommandCategoryFilter = DOM.getElementAttributeValueCurrent(commandCategoryFilter); utils_Utils.consoleLogIfNotProductionEnvironment({ valueCurrentCommandCategoryFilter }); if (valueCurrentCommandCategoryFilter == "") { let valueCurrentCommandFilter = DOM.getElementAttributeValueCurrent(commandFilter); utils_Utils.consoleLogIfNotProductionEnvironment({ valueCurrentCommandFilter }); if (valueCurrentCommandFilter != "") { let command = filterCommands[valueCurrentCommandFilter]; idCommandCategory = command[attrIdCommandCategory]; } } else { idCommandCategory = Number(valueCurrentCommandCategoryFilter); } return idCommandCategory; } getHasCommandCategoryFilter() { let idCommandCategoryFilter = this.getIdCommandCategoryFilter(); return !(Validation.isEmpty(idCommandCategoryFilter) || idCommandCategoryFilter == 0); } getIdCommandRow(tr) { let elementCommand = tr.querySelector('td.' + flagCommand + ' .' + flagCommand); return DOM.getElementAttributeValueCurrent(elementCommand); } getIdCommandFilter() { let formFilters = TableBasePage.getFormFilters(); let commandFilter = formFilters.querySelector('#' + attrIdCommand); let valueCurrentCommandFilter = DOM.getElementAttributeValueCurrent(commandFilter); let idCommand = Number(valueCurrentCommandFilter); return idCommand; } getHasCommandFilter() { let idCommandFilter = this.getIdCommandFilter(); return !(Validation.isEmpty(idCommandFilter) || idCommandFilter == 0); } /* createTdActive(isActive) { let tdActive = document.createElement("td"); tdActive.classList.add(flagActive); let buttonActive = document.createElement("button"); buttonActive.classList.add(flagActive); buttonActive.classList.add(isActive ? flagDelete : flagAdd); buttonActive.textContent = isActive ? 'x' : '+'; DOM.setElementAttributesValuesCurrentAndPrevious(buttonActive, isActive); tdActive.appendChild(buttonActive); return tdActive; } */ leave() { if (this.constructor === TableBasePage) { throw new Error("Must implement leave() method."); } super.leave(); let formFilters = TableBasePage.getFormFilters(); let dataPage = {}; dataPage[flagFormFilters] = DOM.convertForm2JSON(formFilters); this.setLocalStoragePage(dataPage); } toggleColumnHasClassnameFlag(columnFlag, isRequiredFlag, classnameFlag) { let table = TableBasePage.getTableMain(); let columnTh = table.querySelector('th.' + columnFlag); let columnThHasFlag = columnTh.classList.contains(classnameFlag); if (isRequiredFlag == columnThHasFlag) return; DOM.toggleElementHasClassnameFlag(columnTh, isRequiredFlag, classnameFlag); } toggleColumnHeaderHasClassnameFlag(columnFlag, isRequiredFlag, classnameFlag) { let table = TableBasePage.getTableMain(); let columnTh = table.querySelector('th.' + columnFlag); DOM.toggleElementHasClassnameFlag(columnTh, isRequiredFlag, classnameFlag); } updateAndToggleShowButtonsSaveCancel() { let pageBody = document.querySelector(idPageBody); let isDirty = DOM.hasDirtyChildrenContainer(pageBody); console.log({ pageBody, isDirty }); this.toggleShowButtonsSaveCancel(isDirty); } } ;// ./static/js/pages/tcg/mtg_game.js class PageMtgGame extends TableBasePage { static hash = hashPageMtgGame; static attrIdRowObject = attrGameId; callSaveTableContent = API.saveGame; constructor(router) { super(router); } initialize() { this.sharedInitialize(); this.hookupTcgGame(); } hookupFilters() { // this.sharedHookupFilters(); } loadRowTable(rowJson) { return; } getJsonRow(row) { return; } initialiseRowNew(tbody, row) {} postInitialiseRowNewCallback(tbody) {} hookupTableMain() { super.hookupTableMain(); } hookupTcgGame() { this.initGamePage(); let pageHeading = document.querySelector('.container.company-name .tcg-title.company-name'); pageHeading.innerText = `MTG Game #${gameId}`; } initGamePage() { // Load existing game state from API or show setup PageMtgGame.updatePlayerSetup(); if (typeof gameId !== 'undefined' && gameId) { this.loadGameFromServer(); } /* else { PageMtgGame.updatePlayerSetup(); } */ PageMtgGame.hookupResetButton(); PageMtgGame.hookupPlayerCountInput(); this.hookupStartGameButton(); /* this.hookupCommanderDeathIncrementButtons(); this.hookupEliminateCommanderButtons(); this.hookupPlayerLifeIncrementButtons(); this.hookupCommanderDamageIncrementButtons(); */ } static hookupResetButton() { const resetGameButton = document.querySelector('header.game-header .header-right .btn-tcg.btn-tcg-secondary'); if (resetGameButton) { resetGameButton.addEventListener('click', PageMtgGame.resetGame); } } static hookupPlayerCountInput() { const playerCountInput = document.getElementById('playerCount'); if (playerCountInput) { playerCountInput.addEventListener('change', PageMtgGame.updatePlayerSetup); } } hookupStartGameButton() { const startGameButton = document.querySelector('.setup-section .setup-actions .btn-tcg'); if (startGameButton) { startGameButton.addEventListener('click', () => { this.startGame(); }); } } /* hookupCommanderDeathIncrementButtons() { const commanderDeathIncremementButtons = document.querySelectorAll('#players-grid .player-card .commander-deaths .death-btn'); if (commanderDeathIncremementButtons) { commanderDeathIncremementButtons.forEach((button) => { button.addEventListener('click', PageMtgGame.changeCommanderDeaths); }); } } hookupEliminateCommanderButtons() { const eliminateCommanderButtons = document.querySelector('#players-grid .player-card .eliminate-btn'); if (eliminateCommanderButtons) { eliminateCommanderButtons.forEach((button) => { button.addEventListener('click', PageMtgGame.toggleEliminate); }); } } hookupPlayerLifeIncrementButtons() { const playerLifeIncrementButtons = document.querySelector('#players-grid .player-card .eliminate-btn'); if (playerLifeIncrementButtons) { playerLifeIncrementButtons.forEach((button) => { button.addEventListener('click', PageMtgGame.changeLife); }); } } hookupCommanderDamageIncrementButtons() { const commanderDamageIncrementButtons = document.querySelector('#players-grid .player-card .eliminate-btn'); if (commanderDamageIncrementButtons) { commanderDamageIncrementButtons.forEach((button) => { button.addEventListener('click', PageMtgGame.changeCommanderDamage); }); } } */ async loadGameFromServer() { console.log("loading game from server"); try { // Fetch players, rounds, and damage records from API const [playersResponse, roundsResponse, damageResponse] = await Promise.all([API.getGamePlayers(gameId), API.getGameRounds(gameId), API.getGameDamageRecords(gameId)]); console.log({ playersResponse, damageResponse }); let setupSection = document.getElementById('setupSection'); let gameSection = document.getElementById('gameSection'); setupSection.classList.remove('hidden'); gameSection.classList.add('hidden'); if (playersResponse.status !== 'success') { console.error('Failed to load players:', playersResponse.message); return; } const savedPlayers = playersResponse.data || []; const savedRounds = roundsResponse.status === 'success' ? roundsResponse.data || [] : []; const savedDamageRecords = damageResponse.status === 'success' ? damageResponse.data || [] : []; players = savedPlayers; rounds = savedRounds; damageRecords = savedDamageRecords; if (savedPlayers.length === 0) { // No players yet, show setup section return; } // Hide setup, show game setupSection.classList.add('hidden'); gameSection.classList.remove('hidden'); console.log({ savedPlayers, damageRecords }); // Render players to DOM this.renderPlayers(); } catch (error) { console.error('Error loading game from server:', error); } } renderPlayers() { const grid = document.getElementById('playersGrid'); grid.innerHTML = ''; // Build a damage lookup: { playerId: { fromPlayerId: damageAmount } } /* const damageLookup = {}; damageRecords.forEach(damage => { if (!damageLookup[damage.player_id]) { damageLookup[damage.player_id] = {}; } if (damage.received_from_commander_player_id) { damageLookup[damage.player_id][damage.received_from_commander_player_id] = damage.health_change || 0; } }); */ const latestRoundId = PageMtgGame.getLatestRoundId(); players.forEach((player, index) => { // Build display name: prefer user_name + deck_name, fallback to player name const playerId = player[attrPlayerId]; let displayName = PageMtgGame.makePlayerDisplayName(playerId, index); let damagePlayerPairs = [...players, { [attrPlayerId]: null }]; let maxCommanderDamageReceived = 0; damagePlayerPairs.forEach(damagePlayerPair => { const sourceId = damagePlayerPair[attrPlayerId]; const filteredPlayerDamages = damageRecords.filter(damage => damage[attrRoundId] == latestRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId); //[playerId] || {}; if (filteredPlayerDamages.length == 0) { damageRecords.push(PageMtgGame.makeDefaultGameRoundPlayerDamage(playerId, sourceId)); } maxCommanderDamageReceived = Math.max(maxCommanderDamageReceived, damageRecords.filter(damage => damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId).map(damage => damage[flagHealthChange]).reduce((acc, curr) => acc + curr, 0)); }); const totalDamage = damageRecords.filter(damage => damage[attrPlayerId] == playerId).map(damage => damage[flagHealthChange]).reduce((acc, curr) => acc + curr, 0); let life = startingLife + totalDamage; let isEliminatedByForce = damageRecords.filter(damage => damage[attrPlayerId] == playerId).map(damage => damage[flagIsEliminated]).some(Boolean); const isEliminated = isEliminatedByForce || !player[flagActive] || life < 1 || maxCommanderDamageReceived >= 21; const playerOwnDamage = damageRecords.filter(damage => damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null && damage[attrRoundId] == latestRoundId)[0]; const card = document.createElement('div'); card.className = `player-card ${isEliminated ? 'eliminated' : ''}`; card.style.animationDelay = `${index * 0.1}s`; card.dataset.playerId = playerId; card.dataset.userName = player.user_name || ''; card.dataset.deckName = player.deck_name || ''; card.innerHTML = `
${displayName}
Commander Deaths:
${playerOwnDamage[flagCommanderDeaths]}
${life}
Commander Damage Taken
${PageMtgGame.renderCommanderDamageRows(playerId // playerId , player[attrDeckId] // deckId )}
`; grid.appendChild(card); }); // Hookup all event handlers this.hookupPlayerCardEvents(); } static makeDefaultGameRoundPlayerDamage(playerId, receivedFromCommanderPlayerId) { let roundId = PageMtgGame.getLatestRoundId(); return { [attrDamageId]: -1 - damageRecords.length, [attrRoundId]: roundId, [attrPlayerId]: playerId, [attrReceivedFromCommanderPlayerId]: receivedFromCommanderPlayerId, [flagHealthChange]: 0, [flagCommanderDeaths]: 0, [flagActive]: true }; } static getLatestRoundId() { let roundId = -1; if (rounds.length > 0) { let highestRoundDisplayOrder = Math.max(rounds.map(round => { return round[flagDisplayOrder]; })); roundId = rounds.filter(round => round[flagDisplayOrder] == highestRoundDisplayOrder)[0][attrRoundId]; console.log({ "method": "getLatestRoundId", highestRoundDisplayOrder, roundId }); } return roundId; } static makePlayerDisplayName(playerId, index) { if (playerId == null) { return `Player ${index + 1}`; } const player = players.filter(player => player[attrPlayerId] == playerId)[0]; const deckId = player[attrDeckId]; const deck = deckId == null ? null : decks.filter(deck => deck[attrDeckId] == deckId)[0]; const user = playerId == null ? null : users[player[attrUserId]]; return player[flagName] || `${user == null ? 'Error' : user[flagName]} - ${deck == null ? 'Error' : deck[flagName]}`; } static renderCommanderDamageRows(playerId) { // const roundId = PageMtgGame.getLatestRoundId(); return players.filter(otherPlayer => otherPlayer[attrPlayerId] !== playerId).map(otherPlayer => { const sourceId = otherPlayer[attrPlayerId]; let otherPlayerDisplayName = PageMtgGame.makePlayerDisplayName(sourceId); const totalDamage = damageRecords.filter(damage => damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId).map(damage => -damage[flagHealthChange]).reduce((acc, curr) => acc + curr, 0); const isLethal = totalDamage >= 21; return `
from ${otherPlayerDisplayName}
${totalDamage}
`; }).join(''); } hookupPlayerCardEvents() { // Life buttons let lifeButtonSelector = '.life-btn'; Events.hookupEventHandler("click", lifeButtonSelector, (event, button) => { const playerId = button.dataset.playerId; const amount = parseInt(button.dataset.amount); const latestRoundId = PageMtgGame.getLatestRoundId(); const damageIndex = damageRecords.findIndex(damage => damage[attrRoundId] == latestRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null); this.changeLife(playerId // playerId , amount // amount , true // updateDamage , damageIndex // damageIndex ); }); // Commander death buttons let commanderDeathButtonSelector = '.death-btn'; Events.hookupEventHandler("click", commanderDeathButtonSelector, (event, button) => { const playerId = button.dataset.playerId; const isMinusButton = button.classList.contains('death-minus'); const amount = isMinusButton ? -1 : 1; this.changeCommanderDeaths(playerId, amount); }); // Commander damage buttons let commmanderDamageButtonSelector = '.damage-btn'; Events.hookupEventHandler("click", commmanderDamageButtonSelector, (event, button) => { const playerId = button.dataset.playerId; const sourceId = button.dataset.sourceId; const isMinusButton = button.classList.contains('damage-minus'); const amount = isMinusButton ? -1 : 1; this.changeCommanderDamage(playerId, sourceId, amount); }); // Eliminate buttons let eliminatePlayerButtonSelector = '.eliminate-btn'; Events.hookupEventHandler("click", eliminatePlayerButtonSelector, (event, button) => { const playerId = button.dataset.playerId; this.toggleEliminate(playerId); }); } changeLife(playerId, amount, updateDamage = false, damageIndex = null) { const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`); if (!card || card.classList.contains('eliminated')) return; const lifeInput = card.querySelector(`.life-value[data-player-id="${playerId}"]`); const lifeDisplay = card.querySelector(`.life-display[data-player-id="${playerId}"]`); const currentLife = parseInt(lifeInput.value) || 0; const newLife = Math.max(0, currentLife + amount); DOM.setElementAttributeValueCurrent(lifeDisplay, newLife); DOM.isElementDirty(lifeDisplay); lifeInput.value = newLife; lifeDisplay.textContent = newLife; if (updateDamage) { damageRecords[damageIndex][flagHealthChange] += amount; } // PageMtgGame.debouncedSave(); this.updateAndToggleShowButtonsSaveCancel(); } changeCommanderDamage(playerId, sourceId, amount) { const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`); if (!card || card.classList.contains('eliminated')) return; const damageInput = card.querySelector(`.damage-value[data-player-id="${playerId}"][data-source-id="${sourceId}"]`); const damageDisplay = card.querySelector(`.damage-display[data-player-id="${playerId}"][data-source-id="${sourceId}"]`); const currentDamage = parseInt(damageInput.value) || 0; const newDamage = Math.max(0, currentDamage + amount); amount = newDamage - currentDamage; DOM.setElementAttributeValueCurrent(damageDisplay, newDamage); DOM.isElementDirty(damageDisplay); damageInput.value = newDamage; damageDisplay.textContent = newDamage; // Update lethal class if (newDamage >= 21) { damageDisplay.classList.add('lethal'); } else { damageDisplay.classList.remove('lethal'); } const latestRoundId = PageMtgGame.getLatestRoundId(); const damageIndex = damageRecords.findIndex(damageRecord => damage[attrRoundId] == latestRoundId && damageRecord[attrPlayerId] == playerId && damageRecord[attrReceivedFromCommanderPlayerId] == sourceId); damageRecords[damageIndex][flagHealthChange] -= amount; this.changeLife(playerId // playerId , -amount // amount , false // updateDamage , damageIndex // damageIndex ); // PageMtgGame.debouncedSave(); } changeCommanderDeaths(playerId, amount) { const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`); if (!card || card.classList.contains('eliminated')) return; const deathDisplay = card.querySelector(`.death-display[data-player-id="${playerId}"]`); const currentDeaths = parseInt(deathDisplay.textContent) || 0; const newDeaths = Math.max(0, currentDeaths + amount); deathDisplay.textContent = newDeaths; DOM.setElementAttributeValueCurrent(deathDisplay, newDeaths); DOM.isElementDirty(deathDisplay); const latestRoundId = PageMtgGame.getLatestRoundId(); const damageIndex = damageRecords.findIndex(damage => damage[attrRoundId] == latestRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null); damageRecords[damageIndex][flagCommanderDeaths] = newDeaths; // PageMtgGame.debouncedSave(); this.updateAndToggleShowButtonsSaveCancel(); } toggleEliminate(playerId) { const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`); if (!card) return; const eliminateBtn = card.querySelector(`.eliminate-btn[data-player-id="${playerId}"]`); const wasEliminated = card.classList.contains('eliminated'); if (wasEliminated) { card.classList.remove('eliminated'); eliminateBtn.textContent = 'Eliminate'; } else { card.classList.add('eliminated'); eliminateBtn.textContent = 'Revive'; } const isEliminated = card.classList.contains('eliminated'); const latestRoundId = PageMtgGame.getLatestRoundId(); const damageIndex = damageRecords.findIndex(damage => damage[attrRoundId] == latestRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null); damageRecords[damageIndex][flagIsEliminated] = isEliminated; DOM.setElementAttributeValueCurrent(eliminateBtn, isEliminated); DOM.isElementDirty(eliminateBtn); // PageMtgGame.debouncedSave(); this.updateAndToggleShowButtonsSaveCancel(); } static updatePlayerSetup() { const playerCountInput = document.getElementById('playerCount'); if (!playerCountInput) return; const playerCount = parseInt(playerCountInput.value); const grid = document.getElementById('playerSetupGrid'); if (!grid) return; grid.innerHTML = ''; const wrapperTemplate = document.getElementById(playerSetupWrapperTemplateId); let player, wrapper, wrapperHeading, userDdl, deckDdl, nameInput; for (let i = 0; i < playerCount; i++) { if (i < players.length) { player = players[i]; } else { player = PageMtgGame.makeDefaultGamePlayer(); players.push(player); } wrapper = wrapperTemplate.cloneNode(true); wrapper.removeAttribute("id"); wrapper.setAttribute(flagDisplayOrder, i + 1); wrapper.classList.remove(flagIsCollapsed); wrapperHeading = wrapper.querySelector('label'); wrapperHeading.innerText = 'Player ' + (i + 1); userDdl = wrapper.querySelector('.playerUser select'); DOM.setElementValuesCurrentAndPrevious(userDdl, player[attrUserId]); deckDdl = wrapper.querySelector('.playerDeck select'); DOM.setElementValuesCurrentAndPrevious(deckDdl, player[attrDeckId]); nameInput = wrapper.querySelector('.playerName input'); DOM.setElementValuesCurrentAndPrevious(nameInput, player[flagName]); console.log('player: ', player); grid.appendChild(wrapper); } } static makeDefaultGamePlayer() { return { [attrPlayerId]: -players.length, [attrGameId]: gameId, [attrUserId]: user[attrUserId], [attrDeckId]: 0, [flagName]: "", [flagNotes]: null, [flagDisplayOrder]: players.length, [flagActive]: true }; } async startGame() { const playerCountInput = document.getElementById('playerCount'); if (!playerCountInput) return; const playerCount = parseInt(playerCountInput.value); const playersToSave = []; let playerSetupWrapper, playerId, player, userDdl, userId, deckDdl, deckId, nameInput, name; for (let i = 0; i < playerCount; i++) { playerSetupWrapper = document.querySelector('.player-name-input-wrapper[' + flagDisplayOrder + '="' + (i + 1) + '"]'); userDdl = playerSetupWrapper.querySelector('.playerUser select'); deckDdl = playerSetupWrapper.querySelector('.playerDeck select'); nameInput = playerSetupWrapper.querySelector('.playerName input'); userId = DOM.getElementValueCurrent(userDdl); deckId = DOM.getElementValueCurrent(deckDdl); name = nameInput ? nameInput.value.trim() || `Player ${i + 1}` : `Player ${i + 1}`; playerId = playerSetupWrapper.getAttribute(attrPlayerId); player = players.filter(p => p[attrPlayerId] == playerId)[0]; playersToSave.push({ ...player, [attrGameId]: gameId, [attrUserId]: userId, [attrDeckId]: deckId, [flagName]: name, [flagDisplayOrder]: i + 1, [flagActive]: true }); } // Save players to server const comment = 'Save players'; const self = this; API.saveGamePlayers(playersToSave, null, comment).then(data => { if (data[flagStatus] == flagSuccess) { self.leave(); window.location.reload(); } else { console.error('Failed to save players:', data[flagMessage]); PageMtgGame.showError('An error occurred while creating the game'); } }).catch(error => { console.error('Error creating game:', error); PageMtgGame.showError('An error occurred while creating the game'); }).finally(() => {}); } static resetGame() { if (confirm('Are you sure you want to start a new game? Current game will be lost.')) { localStorage.removeItem(`mtgGame_${gameId}`); window.location.href = hashPageGames; } } async saveGame() { const gameState = { [flagPlayer]: players, [flagRound]: rounds, [flagDamage]: damageRecords }; /* if (gameState[flagPlayer].length > 0) { localStorage.setItem(`mtgGame_${gameId}`, JSON.stringify(gameState)); PageMtgGame.showSaveIndicator(); } */ const comment = 'Save players'; const self = this; API.saveGameRoundPlayerDamages(rounds, damageRecords, null, comment).then(data => { if (data[flagStatus] == flagSuccess) { self.leave(); window.location.reload(); } else { console.error('Failed to save players:', data[flagMessage]); PageMtgGame.showError('An error occurred while creating the game'); } }).catch(error => { console.error('Error creating game:', error); PageMtgGame.showError('An error occurred while creating the game'); }).finally(() => {}); } /* static debouncedSave() { clearTimeout(PageMtgGame._saveTimeout); PageMtgGame._saveTimeout = setTimeout(() => PageMtgGame.saveGame(), 500); } static showSaveIndicator() { const indicator = document.getElementById('saveIndicator'); if (indicator) { indicator.classList.add('show'); setTimeout(() => { indicator.classList.remove('show'); }, 2000); } } */ saveRecordsTableDirty() { this.saveGame(); } static showError(message) { // Check if there's an overlay error element const errorOverlay = document.getElementById('overlayError'); if (errorOverlay) { const errorLabel = errorOverlay.querySelector('.error-message, #labelError'); if (errorLabel) { errorLabel.textContent = message; } errorOverlay.classList.remove('hidden'); errorOverlay.style.display = 'flex'; } else { // Fallback to alert alert(message); } } leave() { super.leave(); } } // Static timeout reference for debouncing PageMtgGame._saveTimeout = null; ;// ./static/js/pages/tcg/mtg_games.js class PageMtgGames extends TableBasePage { static hash = hashPageMtgGames; static attrIdRowObject = attrGameId; callSaveTableContent = API.saveGame; constructor(router) { super(router); } initialize() { this.sharedInitialize(); this.hookupTcgGames(); } hookupTcgGames() { PageMtgGames.initGamesPage(); } static initGamesPage() { // Initialize form submission const newGameForm = document.getElementById('newGameForm'); if (newGameForm) { newGameForm.addEventListener('submit', PageMtgGames.handleNewGameSubmit); } // Initialize filter form const filterForm = document.getElementById('formFilters'); if (filterForm) { filterForm.addEventListener('submit', PageMtgGames.handleFilterSubmit); } // Close modal on escape key document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { PageMtgGames.hideNewGameForm(); } }); // Close modal on backdrop click const modal = document.getElementById('newGameModal'); if (modal) { modal.addEventListener('click', function (e) { if (e.target === modal) { PageMtgGames.hideNewGameForm(); } }); } // Button onclicks const newGameButton = document.getElementById('btnNewGame'); if (newGameButton) { newGameButton.addEventListener('click', PageMtgGames.showNewGameForm); } const cancelNewGameButtons = document.querySelectorAll('#newGameForm .form-actions .btn-tcg.btn-tcg-secondary' + ',' + '#newGameModal .modal-content .modal-header .modal-close'); if (cancelNewGameButtons.length > 0) { cancelNewGameButtons.forEach(button => { button.addEventListener('click', PageMtgGames.hideNewGameForm); }); } } static showNewGameForm() { const modal = document.getElementById('newGameModal'); if (modal) { modal.classList.remove('hidden'); document.body.style.overflow = 'hidden'; // Focus on first input const firstInput = modal.querySelector('input, select'); if (firstInput) { firstInput.focus(); } } } static hideNewGameForm() { const modal = document.getElementById('newGameModal'); if (modal) { modal.classList.add('hidden'); document.body.style.overflow = ''; // Reset form const form = document.getElementById('newGameForm'); if (form) { form.reset(); } } } static async handleNewGameSubmit(e) { e.preventDefault(); const form = e.target; const formData = new FormData(form); const gameType = formData.get('game_type'); const gameData = { [attrGameId]: -1, [flagIsCommander]: gameType === 'commander', [flagIsDraft]: gameType === 'draft', [flagIsSealed]: gameType === 'sealed', [flagLocationName]: formData.get(flagLocationName) || null, [flagNotes]: formData.get(flagNotes) || null, [flagStartOn]: new Date().toISOString(), [flagStartingLife]: formData.get(flagStartingLife) || 40, [flagActive]: true }; const submitBtn = form.querySelector('button[type="submit"]'); const originalText = submitBtn.textContent; submitBtn.textContent = 'Creating...'; submitBtn.disabled = true; const games = [gameData]; const comment = 'Create new game'; debugger; API.saveGame(games, form, comment).then(data => { if (data[flagStatus] == flagSuccess) { if (_verbose) { utils_Utils.consoleLogIfNotProductionEnvironment('Records saved!'); utils_Utils.consoleLogIfNotProductionEnvironment('Data received:', data); } this.callFilterTableContent(gameData.game_id); } else { utils_Utils.consoleLogIfNotProductionEnvironment("error: " + data[flagMessage]); // OverlayError.show(data[flagMessage]); window.location.reload(); } }).catch(error => { console.error('Error creating game:', error); PageMtgGames.showError('An error occurred while creating the game'); }).finally(() => { submitBtn.textContent = originalText; submitBtn.disabled = false; }); } callFilterTableContent(gameId) { const gamePageHash = `${hashPageGame}/${gameId}`; let filtersJson = {}; utils_Utils.consoleLogIfNotProductionEnvironment("callFilterTableContent"); this.leave(); API.goToHash(gamePageHash, filtersJson); } static handleFilterSubmit(e) { // Let the form submit normally - it will reload with query params // You can add client-side filtering here if needed } static getCSRFToken() { // Try meta tag first const metaTag = document.querySelector('meta[name="csrf-token"]'); if (metaTag) { return metaTag.getAttribute('content'); } // Try hidden input const hiddenInput = document.querySelector('input[name="csrf_token"]'); if (hiddenInput) { return hiddenInput.value; } // Try cookie const cookies = document.cookie.split(';'); for (let cookie of cookies) { const [name, value] = cookie.trim().split('='); if (name === 'csrf_token') { return value; } } return ''; } static showError(message) { // Check if there's an overlay error element const errorOverlay = document.getElementById('overlayError'); if (errorOverlay) { const errorLabel = errorOverlay.querySelector('.error-message, #labelError'); if (errorLabel) { errorLabel.textContent = message; } errorOverlay.classList.remove('hidden'); errorOverlay.style.display = 'flex'; } else { // Fallback to alert alert(message); } } static showSuccess(message) { // Could implement a toast notification here console.log('Success:', message); } static joinGame(gameId) { window.location.href = `${hashPageGame}/${gameId}`; } static async deleteGame(gameId) { if (!confirm('Are you sure you want to delete this game? This action cannot be undone.')) { return; } try { const gameData = { 'game_id': gameId, 'active': false }; const response = await fetch(hashSaveGame, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': PageMtgGames.getCSRFToken() }, body: JSON.stringify({ [flagGame]: [gameData], 'form-filters': {}, 'comment': 'Game deleted' }) }); const result = await response.json(); if (result.status === 'success') { // Remove the row from the table const row = document.querySelector(`tr[data-game-id="${gameId}"]`); if (row) { row.style.animation = 'tcg-fadeOut 0.3s ease-out forwards'; setTimeout(() => row.remove(), 300); } } else { PageMtgGames.showError(result.message || 'Failed to delete game'); } } catch (error) { console.error('Error deleting game:', error); PageMtgGames.showError('An error occurred while deleting the game'); } } toggleShowButtonsSaveCancel() {} leave() { super.leave(); } } ;// ./static/js/pages/tcg/mtg_home.js class PageMtgHome extends BasePage { static hash = hashPageMtgHome; constructor(router) { super(router); } initialize() { this.sharedInitialize(); this.hookupTcgHome(); } hookupTcgHome() {} leave() { super.leave(); } } ;// ./static/js/pages/legal/accessibility_report.js class PageAccessibilityReport extends BasePage { static hash = hashPageAccessibilityReport; constructor(router) { super(router); } initialize() { this.sharedInitialize(); } leave() { super.leave(); } } ;// ./static/js/pages/legal/accessibility_statement.js class PageAccessibilityStatement extends BasePage { static hash = hashPageAccessibilityStatement; constructor(router) { super(router); } initialize() { this.sharedInitialize(); } leave() { super.leave(); } } ;// ./static/js/pages/legal/license.js class PageLicense extends BasePage { static hash = hashPageLicense; constructor(router) { super(router); } initialize() { this.sharedInitialize(); } leave() { super.leave(); } } ;// ./static/js/pages/legal/privacy_policy.js class PagePrivacyPolicy extends BasePage { static hash = hashPagePrivacyPolicy; constructor(router) { super(router); } initialize() { this.sharedInitialize(); } leave() { super.leave(); } } ;// ./static/js/pages/legal/retention_schedule.js class PageRetentionSchedule extends BasePage { static hash = hashPageDataRetentionSchedule; constructor(router) { super(router); } initialize() { this.sharedInitialize(); } leave() { super.leave(); } } ;// ./static/js/pages/mixin.js class MixinPage { constructor(pageCurrent) { this.page = pageCurrent; } initialize() { Utils.consoleLogIfNotProductionEnvironment('hookup start for ', this.page.hash); this.hookupFilters(); this.hookupLocalStorage(); } hookupFilters() {} hookupLocalStorage() {} leave() {} } ;// ./static/js/pages/mixin_table.js class TableMixinPage extends MixinPage { constructor(pageCurrent) { super(pageCurrent); } initialize() { super.initialize(); this.hookupFilters(); this.hookupTable(); } hookupFilters() { // Implement filter-specific functionality here } hookupTable() { // Implement table-specific functionality here } } ;// ./static/js/pages/user/user.js class PageUser extends TableBasePage { static hash = hashPageUserAccount; static attrIdRowObject = attrUserId; callSaveTableContent = API.saveUsers; constructor(router) { super(router); this.mixin = new TableMixinPage(this); } initialize() { this.sharedInitialize(); this.hookupTableMain(); } hookupFilters() {} loadRowTable(rowJson) { if (rowJson == null) return; if (_verbose) { Utils.consoleLogIfNotProductionEnvironment("applying data row: ", rowJson); } } getTableRecords(dirtyOnly = false) { dirtyOnly = true; let container = document.querySelector('.' + flagCard + '.' + flagUser); return [this.getJsonRow(container)]; } getJsonRow(container) { console.log("getJsonRow: ", container); if (container == null) return; let inputFirstname = container.querySelector(' #' + flagFirstname); let inputSurname = container.querySelector(' #' + flagSurname); let inputEmail = container.querySelector(' #' + flagEmail); let idUser = container.getAttribute(attrUserId); let jsonRow = { [attrUserAuth0Id]: null, [flagEmail]: null, [flagIsEmailVerified]: null, [flagIsSuperUser]: null, [flagCanAdminUser]: null }; jsonRow[attrUserId] = idUser; jsonRow[flagFirstname] = DOM.getElementAttributeValueCurrent(inputFirstname); jsonRow[flagSurname] = DOM.getElementAttributeValueCurrent(inputSurname); jsonRow[flagEmail] = DOM.getElementAttributeValueCurrent(inputEmail); return jsonRow; } initialiseRowNew(tbody, row) {} postInitialiseRowNewCallback(tbody) {} hookupTableMain() { super.hookupTableMain(); this.hookupFieldsFirstname(); this.hookupFieldsSurname(); this.hookupFieldsEmail(); } hookupFieldsFirstname() { this.hookupChangeHandlerTableCells('.' + flagCard + '.' + flagUser + ' #' + flagFirstname); } hookupFieldsSurname() { this.hookupChangeHandlerTableCells('.' + flagCard + '.' + flagUser + ' #' + flagSurname); } hookupFieldsEmail() { this.hookupChangeHandlerTableCells('.' + flagCard + '.' + flagUser + ' #' + flagEmail); } leave() { super.leave(); } } ;// ./static/js/pages/user/users.js class PageUsers extends TableBasePage { static hash = hashPageUserAccounts; static attrIdRowObject = attrUserId; callSaveTableContent = API.saveUsers; constructor(router) { super(router); this.mixin = new TableMixinPage(this); } initialize() { this.sharedInitialize(); } hookupFilters() { this.sharedHookupFilters(); this.hookupFilterActive(); } loadRowTable(rowJson) { if (rowJson == null) return; if (_verbose) { utils_Utils.consoleLogIfNotProductionEnvironment("applying data row: ", rowJson); } } getJsonRow(row) { if (row == null) return; let inputFirstname = row.querySelector('td.' + flagFirstname + ' .' + flagFirstname); let inputSurname = row.querySelector('td.' + flagSurname + ' .' + flagSurname); let inputNotes = row.querySelector('td.' + flagNotes + ' .' + flagNotes); let buttonActive = row.querySelector('td.' + flagActive + ' .' + flagActive); let jsonRow = { [attrUserAuth0Id]: null, [flagEmail]: null, [flagIsEmailVerified]: null, [flagIsSuperUser]: null, [flagCanAdminUser]: null }; jsonRow[attrUserId] = row.getAttribute(attrUserId); jsonRow[flagFirstname] = DOM.getElementAttributeValueCurrent(inputFirstname); jsonRow[flagSurname] = DOM.getElementAttributeValueCurrent(inputSurname); jsonRow[flagNotes] = DOM.getElementAttributeValueCurrent(inputNotes); jsonRow[flagActive] = buttonActive.classList.contains(flagDelete); console.log("jsonRow"); console.log(jsonRow); return jsonRow; } initialiseRowNew(tbody, row) {} postInitialiseRowNewCallback(tbody) { let newRows = tbody.querySelectorAll('tr.' + flagRowNew); let newestRow = newRows[0]; let clickableElementsSelector = [].join(''); newestRow.querySelectorAll(clickableElementsSelector).forEach(clickableElement => { clickableElement.click(); }); } hookupTableMain() { super.hookupTableMain(); this.hookupFieldsFirstname(); this.hookupFieldsSurname(); this.hookupFieldsNotesTable(); this.hookupFieldsActive(); } hookupFieldsFirstname() { this.hookupChangeHandlerTableCells(flagFirstname); } hookupFieldsSurname() { this.hookupChangeHandlerTableCells(flagSurname); } leave() { super.leave(); } } ;// ./static/js/router.js // Pages // Core // TCG // Legal // User // import PageUserLogin from './pages/user/login.js'; // import PageUserLogout from './pages/user/logout.js'; class Router { constructor() { // Pages this.pages = {}; // Core // TCG this.pages[hashPageMtgGame] = { name: 'PageMtgGame', module: PageMtgGame }; this.pages[hashPageMtgGames] = { name: 'PageMtgGames', module: PageMtgGames }; this.pages[hashPageMtgHome] = { name: 'PageMtgGame', module: PageMtgHome }; // Legal this.pages[hashPageAccessibilityStatement] = { name: 'PageAccessibilityStatement', module: PageAccessibilityStatement }; this.pages[hashPageDataRetentionSchedule] = { name: 'PageDataRetentionSchedule', module: PageRetentionSchedule }; this.pages[hashPageLicense] = { name: 'PageLicense', module: PageLicense }; this.pages[hashPagePrivacyPolicy] = { name: 'PagePrivacyPolicy', module: PagePrivacyPolicy }; // User // this.pages[hashPageUserLogin] = { name: 'PageUserLogin', module: PageUserLogin }; // pathModule: './pages/user/login.js' }; // this.pages[hashPageUserLogout] = { name: 'PageUserLogout', module: PageUserLogout }; // pathModule: './pages/user/logout.js' }; this.pages[hashPageUserAccount] = { name: 'PageUser', module: PageUser }; this.pages[hashPageUserAccounts] = { name: 'PageUsers', module: PageUsers }; // Routes this.routes = {}; // Core // TCG this.routes[hashPageMtgGame] = (isPopState = false) => this.navigateToHash(hashPageMtgGame, isPopState); this.routes[hashPageMtgGames] = (isPopState = false) => this.navigateToHash(hashPageMtgGames, isPopState); this.routes[hashPageMtgHome] = (isPopState = false) => this.navigateToHash(hashPageMtgHome, isPopState); // Legal this.routes[hashPageAccessibilityStatement] = (isPopState = false) => this.navigateToHash(hashPageAccessibilityStatement, isPopState); this.routes[hashPageDataRetentionSchedule] = (isPopState = false) => this.navigateToHash(hashPageDataRetentionSchedule, isPopState); this.routes[hashPageLicense] = (isPopState = false) => this.navigateToHash(hashPageLicense, isPopState); this.routes[hashPagePrivacyPolicy] = (isPopState = false) => this.navigateToHash(hashPagePrivacyPolicy, isPopState); // User // this.routes[hashPageUserLogin] = (isPopState = false) => this.navigateToHash(hashPageUserLogin, isPopState); // this.routes[hashPageUserLogout] = (isPopState = false) => this.navigateToHash(hashPageUserLogout, isPopState); this.routes[hashPageUserAccount] = (isPopState = false) => this.navigateToHash(hashPageUserAccount, isPopState); this.routes[hashPageUserAccounts] = (isPopState = false) => this.navigateToHash(hashPageUserAccounts, isPopState); this.initialize(); } loadPage(hashPage, isPopState = false) { const PageClass = this.getClassPageFromHash(hashPage); this.currentPage = new PageClass(this); this.currentPage.initialize(isPopState); window.addEventListener('beforeunload', () => this.currentPage.leave()); } getClassPageFromHash(hashPage) { let pageJson = this.pages[hashPage]; try { const module = pageJson.module; return module; } catch (error) { utils_Utils.consoleLogIfNotProductionEnvironment("this.pages: ", this.pages); console.error('Page not found:', hashPage); throw error; } } initialize() { window.addEventListener('popstate', this.handlePopState.bind(this)); } handlePopState(event) { this.loadPageCurrent(); } loadPageCurrent() { const hashPageCurrent = DOM.getHashPageCurrent(); this.loadPage(hashPageCurrent); } navigateToHash(hash, data = null, params = null, isPopState = false) { let url = API.getUrlFromHash(hash, params); history.pushState({ data: data, params: params }, '', hash); API.goToUrl(url, data); } navigateToUrl(url, data = null, appendHistory = true) { // this.beforeLeave(); if (appendHistory) history.pushState(data, '', url); url = API.parameteriseUrl(url, data); API.goToUrl(url); } static loadPageBodyFromResponse(response) { DOM.loadPageBody(response.data); } } const router = new Router(); ;// ./static/js/app.js class App { constructor() { this.dom = new DOM(); this.router = new Router(); } initialize() { this.setupEventListeners(); this.start(); } setupEventListeners() { // document.addEventListener('click', this.handleGlobalClick.bind(this)); } handleGlobalClick(event) {} start() { this.initPageCurrent(); } initPageCurrent() { this.router.loadPageCurrent(); } } const app = new App(); function domReady(fn) { if (document.readyState !== 'loading') { fn(); } else { document.addEventListener('DOMContentLoaded', fn); } } domReady(() => { app.initialize(); }); window.app = app; /* harmony default export */ const js_app = ((/* unused pure expression or super */ null && (app))); })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); // This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. (() => { // extracted by mini-css-extract-plugin })(); /******/ })() ; //# sourceMappingURL=main.bundle.js.map