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 { scenarios } from './modules/scenarios.js';
globalThis.svgns = 'http://www.w3.org/2000/svg';
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') || (env === 'test' ? 'test_map' : 'map1'),
fileName = localStorage.getItem('map') || 'scenario-side_show',
map = scenarios[fileName]?.hashed || `assets/images/${fileName}.svg`,
fileInputEl = document.querySelector('input[type="file"]'),
dice = document.querySelectorAll('.die'),
d6 = {
1: 'one',
2: 'two',
3: 'three',
4: 'four',
5: 'five',
6: 'six'
},
toggleContentVis = (function () {
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 mapResourceEl = document.querySelector('object');
async function requestScenario(url) {
return new Promise((res, rej) => {
const request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'document';
request.onload = function() {
if (request.status === 200) {
res(request.response);
} else {
rej(Error('Image didn\'t load successfully; error code:' + request.statusText));
}
};
request.onerror = function() {
rej(Error('There was a network error.'));
};
request.send();
});
}
const scenarioRequest = requestScenario(map);
function loadScenario(data) {
// const current = document.querySelector('object');
// const next = document.createElement('object');
// next.setAttribute('type', 'image/svg+xml');
// next.style.opacity = 0;
// next.addEventListener('load', load);
// mapPlaceholder.style.opacity = 1;
// next.data = data;
// mapPlaceholder.after(next);
// current.remove();
buildScenario(requestScenario(data));
}
async function buildScenario(req) {
console.log('req', req);
console.log('fresh template', scenarioTemplate.querySelector('svg'));
const svg = scenarioTemplate.querySelector('svg').cloneNode(true);
// console.log('document', document.querySelector('object').contentDocument);
document.querySelector('object').contentDocument.querySelector('svg').remove();
document.querySelector('object').contentDocument.append(svg);
gameboard.stop();
recordSheet.stop();
const scenario = await req;
const startLocs = scenario.querySelector('.start-locations');
console.log(scenario);
const gb = svg.querySelector('.gameboard');
const grid = svg.querySelector('.grid');
const externalResourceEls = Array.from(scenario.querySelectorAll('use[href*=".svg"'));
const refs = externalResourceEls.reduce((acc, el) => {
const href = el.getAttributeNS(null, 'href');
const [filename] = href.match(/.+\.svg/);
const fragmentIdentifier = href.split('.svg').pop();
(acc[filename] ??= new Set()).add(fragmentIdentifier);
el.setAttributeNS(null, 'href', fragmentIdentifier);
return acc;
}, {});
await Promise.all(
Object.keys(refs).map(filename => requestScenario(`assets/images/${filename}`))
).then(result => {
const defs = svg.querySelector('defs');
Object.keys(refs).forEach((filename, index) => {
const external = result[index];
refs[filename].forEach(fragmentIdentifier => {
external
.querySelectorAll(`${fragmentIdentifier} use`)
.forEach(el => refs[filename].add(el.getAttributeNS(null, 'href')));
});
const refsQuery = [...refs[filename]].join(', ');
external.querySelectorAll(refsQuery).forEach(node => defs.append(svg.ownerDocument.importNode(node, true)));
});
});
scenario.querySelectorAll('use.mapsheet').forEach(el => gb.prepend(svg.ownerDocument.importNode(el, true)));
if (startLocs) grid.before(svg.ownerDocument.importNode(startLocs, true));
const scenarioGrid = scenario.querySelector('.grid');
if (scenarioGrid) {
grid.replaceWith(svg.ownerDocument.importNode(scenarioGrid, true));
}
async function loadScript() {
return new Promise((resolve, reject) => {
const scriptEl = document.createElementNS("http://www.w3.org/2000/svg", 'script');
// const scriptEl = svg.ownerDocument.importNode(scenario.querySelector('script'));
scriptEl.onload = () => {
console.log('map.js loaded');
resolve();
};
scriptEl.onerror = () => {
reject(Error('Script failed to load.'));
};
const oldScript = scenario.querySelector('script');
if ('cols' in oldScript.dataset && 'rows' in oldScript.dataset) {
scriptEl.dataset.rows = oldScript.dataset.rows;
scriptEl.dataset.cols = oldScript.dataset.cols;
}
scriptEl.setAttributeNS(null, 'href', '../../map.js');
svg.append(scriptEl);
});
}
await loadScript();
// this.style.opacity = 1;
// mapPlaceholder.style.opacity = 0;
panzoom.start(svg);
gameboard.start(svg);
recordSheet.start(startLocs, 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)];
}
let scenarioTemplate;
async function load() {
const svg = this.contentDocument.querySelector('svg'),
startLocs = svg.querySelector('.start-locations')
// , scriptEl = this.contentDocument.querySelector('script')
;
scenarioTemplate = this.contentDocument.cloneNode(svg);
buildScenario(scenarioRequest);
// const scenario = await scenarioRequest;
// const gb = svg.querySelector('.gameboard');
// const grid = svg.querySelector('.grid');
// const externalResourceEls = Array.from(scenario.querySelectorAll('use[href*=".svg"'));
// const refs = externalResourceEls.reduce((acc, el) => {
// const href = el.getAttributeNS(null, 'href');
// const [filename] = href.match(/.+\.svg/);
// const fragmentIdentifier = href.split('.svg').pop();
// (acc[filename] ??= new Set()).add(fragmentIdentifier);
// el.setAttributeNS(null, 'href', fragmentIdentifier);
// return acc;
// }, {});
// await Promise.all(
// Object.keys(refs).map(filename => requestScenario(`assets/images/${filename}`))
// ).then(result => {
// const defs = svg.querySelector('defs');
// Object.keys(refs).forEach((filename, index) => {
// const external = result[index];
// refs[filename].forEach(fragmentIdentifier => {
// external
// .querySelectorAll(`${fragmentIdentifier} use`)
// .forEach(el => refs[filename].add(el.getAttributeNS(null, 'href')));
// });
// const refsQuery = [...refs[filename]].join(', ');
// external.querySelectorAll(refsQuery).forEach(node => defs.append(node));
// });
// });
// scenario.querySelectorAll('use.mapsheet').forEach(el => gb.prepend(el));
// grid.before(scenario.querySelector('.start-locations'));
// async function loadScript() {
// return new Promise((resolve, reject) => {
// const scriptEl = document.createElementNS("http://www.w3.org/2000/svg", 'script');
// // const scriptEl = svg.ownerDocument.importNode(scenario.querySelector('script'));
// scriptEl.onload = () => {
// console.log('map.js loaded');
// resolve();
// };
// scriptEl.onerror = () => {
// reject(Error('Script failed to load.'));
// };
// scriptEl.dataset.rows = scenario.querySelector('script').dataset.rows;
// scriptEl.dataset.cols = scenario.querySelector('script').dataset.cols;
// scriptEl.setAttributeNS(null, 'href', '../../map.js');
// svg.append(scriptEl);
// });
// }
// await loadScript();
this.style.opacity = 1;
mapPlaceholder.style.opacity = 0;
// URL.revokeObjectURL(this.data);
// const linkEl = document.createElement('link');
// linkEl.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
// linkEl.setAttribute('rel', 'stylesheet');
// linkEl.setAttribute('href', '../../assets/css/map.css');
// linkEl.setAttribute('type', 'text/css');
// linkEl.onload = function (e) {
// console.log('map.css loaded');
// };
// svg.prepend(linkEl);
// panzoom.start(svg);
// gameboard.start(svg);
// recordSheet.start(startLocs, gameboard.getUnits());
}
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;
let reader = new FileReader();
reader.onload = function () {
const parser = new DOMParser();
const doc = parser.parseFromString(reader.result, "image/svg+xml");
buildScenario(doc);
};
reader.readAsText(file);
// loadScenario(URL.createObjectURL(file));
});
document.querySelector('#roll-dice').addEventListener('click', () => {
dice.forEach(el => {
el.classList.remove('roll-in');
el.classList.add('roll-out');
});
});
contentVisToggleEl.addEventListener('input', toggleContentVis);
contentVisToggleEl.checked = (localStorage.getItem('content-visibility') !== 'false');
toggleContentVis();
mapSelectDialog
.init()
.selectCurrentOptionOnPageLoad()
.showOnClick()
.updateValueOnSelection()
.changeMapOnConfirm(loadScenario);
mapResourceEl.addEventListener('load', load);
// mapResourceEl.data = map;
// mapResourceEl = null;
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);