Web Dev Solutions

Catalin Mititiuc

import * as panzoom from './modules/pan-zoom.js'; import * as gameboard from './modules/gameboard.js'; import * as recordSheet from './modules/record_sheet.js'; import * as mapSelectDialog from './modules/map_select_dialog.js'; import { Observable } from './modules/observable.js'; import { requestResource, build } from './modules/scenario.js'; import { scenarios } from './modules/scenarios.js'; globalThis.svgns = 'http://www.w3.org/2000/svg'; if (window.IS_DEV) { const source = new EventSource('/esbuild'); source.addEventListener('change', () => location.reload()); // source.addEventListener('message', (e) => console.log(e)); } const mapPlaceholder = document.querySelector('.map-placeholder'), distanceOutput = document.getElementById('status'), proneToggle = document.getElementById('toggle-prone-counter'), contentVisToggleEl = document.querySelector('#content input[type="checkbox"].visible'), // fileName = localStorage.getItem('map') || 'scenario-side_show', fileName = localStorage.getItem('map') || 'radial', map = scenarios[fileName]?.hashed || `assets/images/${fileName}.svg`, fileInputEl = document.querySelector('input[type="file"]'), dice = document.querySelectorAll('.die'), mapResourceEl = document.querySelector('object'), scenarioRequest = requestResource(map), d6 = { 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six' }, toggleContentVis = (function () { document.querySelector('#content').style.minWidth = this.checked ? '' : 0; document.querySelectorAll('#content > div').forEach(div => { if (this.checked) { div.style.display = div.id == 'record-sheet' ? 'flex' : 'block'; } else { div.style.display = 'none'; } }); localStorage.setItem('content-visibility', this.checked); }).bind(contentVisToggleEl); let scenarioTemplate; async function buildScenario(req) { gameboard.stop(); recordSheet.stop(); const svg = scenarioTemplate.querySelector('svg').cloneNode(true); document.querySelector('object').contentDocument.querySelector('svg').replaceWith(svg); await build(svg, req); mapResourceEl.style.opacity = 1; mapPlaceholder.style.opacity = 0; panzoom.start(svg); gameboard.start(svg); recordSheet.start(svg.querySelector('.start-locations'), gameboard.getUnits()); } function updateTurnCounter() { const turnCounter = document.getElementById('turn-count'); if (turnCounter.dataset.update === '1') { turnCounter.children.namedItem('count').textContent++; turnCounter.dataset.update = '0'; } else { turnCounter.dataset.update = '1'; } } function enableEndTurnButton(allegiance) { document .querySelector(`button.end-turn:not([data-allegiance="${allegiance}"])`) .removeAttribute('disabled'); } function clearMoveEndedIndicators(records) { records.forEach(el => el.classList.remove('movement-ended')); } function distance(count = '-') { distanceOutput.querySelector('#hex-count').textContent = count; distanceOutput.style.display = count === '-' ? 'none' : 'block'; } // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#getting_a_random_integer_between_two_values_inclusive function getRandomIntInclusive(min, max) { const minCeiled = Math.ceil(min); const maxFloored = Math.floor(max); return Math.floor(Math.random() * (maxFloored - minCeiled + 1) + minCeiled); // The maximum is inclusive and the minimum is inclusive } // ⚀ ⚁ ⚂ ⚃ ⚄ ⚅ function roll(die) { const numsAsWords = Object.values(die); return numsAsWords[getRandomIntInclusive(0, numsAsWords.length - 1)]; } async function load() { const svg = this.contentDocument.querySelector('svg'), startLocs = svg.querySelector('.start-locations') // , scriptEl = this.contentDocument.querySelector('script') ; scenarioTemplate = this.contentDocument.cloneNode(svg); await buildScenario(scenarioRequest); this.style.opacity = 1; mapPlaceholder.style.opacity = 0; } document.querySelectorAll('.end-turn').forEach(el => el.addEventListener('click', ({ target: { dataset: { allegiance: opponent }}}) => { const dataSelector = `[data-allegiance="${opponent}"]`, opponentRecords = Array.from(document.querySelectorAll(`.soldier-record${dataSelector}`)), firstOpponentRecord = opponentRecords.sort((el1, el2) => el1.dataset.number > el2.dataset.number).at(0); el.setAttribute('disabled', ''); updateTurnCounter(); enableEndTurnButton(opponent); clearMoveEndedIndicators(opponentRecords); gameboard.clearFiringArcs(opponent); Observable.notify('select', firstOpponentRecord); }) ); document.querySelectorAll('.set-firing-arc').forEach(el => el.addEventListener('click', gameboard.setFiringArc) ); document.querySelector('.set-grenade').addEventListener('click', gameboard.setGrenade); document.querySelectorAll('#toggle-firing-arc-vis input').forEach(el => el.addEventListener('input', gameboard.toggleFiringArcVisibility) ); document.getElementById('toggle-prone-counter').addEventListener('input', function () { const selected = recordSheet.getSelected(); selected && gameboard.toggleProne(); }); document.querySelectorAll('.end-move').forEach(el => el.addEventListener('click', () => Observable.notify('endmove')) ); document.querySelector('#fullscreen').addEventListener('click', () => { if (!document.fullscreenElement) { document.documentElement.requestFullscreen(); } else if (document.exitFullscreen) { document.exitFullscreen(); } }); document.querySelector('#download-save').addEventListener('click', e => { const data = document.querySelector('object').contentDocument.documentElement.outerHTML; const element = document.createElement('a'); element.setAttribute('download', 'save.svg'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data)); // element.style.display = 'none'; // document.body.appendChild(element); element.click(); // document.body.removeChild(element); }); document.querySelector('#upload-save').addEventListener('click', () => { fileInputEl.click(); }); document.querySelector('input[type="file"]').addEventListener('change', e => { const [file] = fileInputEl.files; const reader = new FileReader(); reader.onload = function () { const parser = new DOMParser(); const doc = parser.parseFromString(reader.result, "image/svg+xml"); mapResourceEl.addEventListener( 'transitionend', () => buildScenario(doc), { once: true } ); }; mapPlaceholder.style.opacity = 1; mapResourceEl.style.opacity = 0; reader.readAsText(file); }); document.querySelector('#roll-dice').addEventListener('click', () => { dice.forEach(el => { el.classList.remove('roll-in'); el.classList.add('roll-out'); }); }); // document.querySelectorAll('[name="select-elevation"]').forEach(el => { // const gameboard = document.querySelector('.gameboard'); // el.addEventListener('change', function (e) { // gameboard.dataset.viewElevation = this.value // }); // }); contentVisToggleEl.addEventListener('input', toggleContentVis); contentVisToggleEl.checked = (localStorage.getItem('content-visibility') !== 'false'); toggleContentVis(); mapSelectDialog .init() .selectCurrentOptionOnPageLoad() .showOnClick() .updateValueOnSelection() .changeMapOnConfirm(data => { const scenarioRequest = requestResource(data); mapResourceEl.addEventListener( 'transitionend', () => buildScenario(scenarioRequest), { once: true } ); mapPlaceholder.style.opacity = 1; mapResourceEl.style.opacity = 0; }); mapResourceEl.addEventListener('load', load); dice.forEach(el => { el.classList.add(roll(d6)); el.addEventListener('animationend', e => { if (e.animationName === 'roll-out') { el.classList.remove('roll-out'); el.classList.replace(el.classList.item(1), roll(d6)); el.classList.add('roll-in'); } }); }); Observable.subscribe('distance', distance); Observable.subscribe('proneflag', checked => proneToggle.checked = checked);