431 lines
14 KiB
JavaScript
431 lines
14 KiB
JavaScript
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') || '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.querySelector('#background').after(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());
|
|
|
|
const mapContainer = document.querySelector('#map-container');
|
|
const mapContainerRect = mapContainer.getBoundingClientRect();
|
|
|
|
console.log('mapContainer', mapContainer);
|
|
console.log('mapContainerRect', mapContainerRect);
|
|
}
|
|
|
|
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);
|