'use strict';
import * as Utils from './js/utillib.js';
import * as Config from './config.js';
import { Color } from './js/colorlib.js';
import * as MapData from './js/mapdata.js';
const settingsVersion = 2;
let settingEntries = [
{
label: 'Reset All',
id: 'button_resetSettings',
func: () =>{
for(let entry of settingEntries){
resetSettingEntry(entry);
}
}
},
{
label: 'Catenary Network',
id: 'toggle_catenary',
saveState: true,
state: true,
func: state =>{
MapData.layers.catenary = state;
MapData.view.dirty = true;
},
children: [
{
label: 'Catenary Color Mode',
id: 'dropdown_catenaryColor',
saveState: true,
options: [
'Voltage',
'Substation',
'Wire Type',
],
state: 'Voltage',
func: state =>{
MapData.setCatenaryColorMode(state);
}
},
{
label: 'Contact Wires',
id: 'toggle_catenaryWires',
saveState: true,
state: true,
func: state =>{
MapData.layers.catenaryWires = state;
MapData.view.dirty = true;
}
},
{
label: 'Traction Substations',
id: 'toggle_catenarySubstations',
saveState: true,
state: true,
func: state =>{
MapData.layers.catenarySubstations = state;
MapData.view.dirty = true;
}
},
{
label: 'Neutral Sections (Токоразделы)',
id: 'toggle_catenaryNeutrals',
saveState: true,
state: true,
func: state =>{
MapData.layers.catenaryNeutrals = state;
MapData.view.dirty = true;
}
},
{
label: 'Interruption Signs',
id: 'toggle_catenaryInterruptions',
saveState: true,
state: true,
func: state =>{
MapData.layers.catenaryInterruptions = state;
MapData.view.dirty = true;
}
}
]
},
{
label: 'Color Mode',
id: 'dropdown_trackColor',
saveState: true,
options: [
'None',
'Grade',
'Altitude',
'Speed',
'Track Type',
'Track Random',
'Curve Random',
],
state: 'Grade',
func: state =>{
MapData.setTrackColorMode(state);
}
},
{
label: 'Signage',
id: 'toggle_signage',
saveState: true,
state: true,
func: state =>{
MapData.layers.signage = state;
MapData.view.dirty = true;
},
children: [
{
label: 'Speed Signage',
id: 'toggle_speedSigns',
saveState: true,
state: true,
func: state =>{
MapData.layers.speedSigns = state;
MapData.view.dirty = true;
},
},
{
label: 'Grade Arrows',
id: 'toggle_gradeSigns',
saveState: true,
state: true,
func: state =>{
MapData.layers.gradeArrows = state;
MapData.view.dirty = true;
},
},
{
label: 'Yard Signage',
id: 'toggle_yardSigns',
saveState: true,
state: true,
func: state =>{
MapData.layers.yardSigns = state;
MapData.view.dirty = true;
},
},
{
label: 'Junctions',
id: 'toggle_junctions',
saveState: true,
state: true,
func: state =>{
MapData.layers.junctions = state;
MapData.view.dirty = true;
},
}
],
},
{
label: 'Points of Interest',
id: 'toggle_poi',
saveState: true,
state: true,
func: state => {
MapData.layers.poi = state;
MapData.view.dirty = true;
},
children: [
{
label: 'Stations',
id: 'toggle_stations',
saveState: true,
state: true,
func: state =>{
MapData.layers.stations = state;
MapData.view.dirty = true;
}
},
{
label: 'Services',
id: 'toggle_services',
saveState: true,
state: true,
func: state =>{
MapData.layers.services = state;
MapData.view.dirty = true;
},
},
{
label: 'Landmarks',
id: 'toggle_landmarks',
saveState: true,
state: true,
func: state =>{
MapData.layers.landmarks = state;
MapData.view.dirty = true;
},
},
{
label: 'Demonstrator Spawns',
id: 'dropdown_demonstrators',
saveState: true,
options: [
'None',
'Hint',
'Exact',
],
state: 'None',
func: state =>{
MapData.layers.demonstratorExact = state == 'Exact';
MapData.layers.demonstratorHint = state == 'Hint';
MapData.view.dirty = true;
},
}
]
},
{
label: 'Grade Arrow Direction',
id: 'dropdown_gradeArrowDirection',
saveState: true,
options: [
'Uphill',
'Downhill',
],
state: 'Uphill',
func: state =>{
MapData.layers.gradeDirection = state == 'Downhill' ? 'Down' : '';
MapData.view.dirty = true;
}
},
{
label: 'Terrain',
id: 'dropdown_terrain',
saveState: true,
options: [
'None',
'Simple',
'DVRT',
],
state: 'None',
func: state =>{
console.log(state);
let mapTerrain = document.getElementById('mapTerrain');
if(state == 'None'){
MapData.layers.terrain = false;
MapData.view.dirty = true;
}else{
mapTerrain.onload = () => {
MapData.layers.terrain = true;
MapData.view.dirty = true;
}
if(state == 'Simple'){
document.getElementById('mapTerrain').src = 'terrains/simple.jpg';
}else if(state == 'DVRT'){
document.getElementById('mapTerrain').src = 'terrains/dvrt.jpg';
}
}
}
},
{
label: '
Community Map Location Mod
',
id: 'header_cmlm',
},
{
label: 'Location Host',
id: 'field_locationHost',
state: 'localhost:9090',
placeholder: 'localhost:9090',
saveState: true,
button: 'Connect',
func: state =>{
if(state == '') state = 'localhost:9090';
document.getElementById('field_locationHost').value = state;
let button = document.getElementById('field_locationHost').nextSibling;
if(button.value.startsWith('Connect')){
MapData.connectPlayerLocation(state, button);
}else if(button.value.startsWith('Disconnect')){
button.value = 'Disconnect';
MapData.disconnectPlayerLocation();
}else if(!button.value.startsWith('Fetch')){
button.value = 'Connect';
MapData.disconnectPlayerLocation();
}else{
MapData.disconnectPlayerLocation();
}
}
},
{
label: 'Location Update Rate (seconds)',
id: 'dropdown_locationFrequency',
tooltip: 'Lower update intervals may cause poor performance',
saveState: true,
state: '0.2',
options: [
'0.05',
'0.1',
'0.2',
'0.25',
'0.5',
'1',
'2',
'5',
'10',
'30',
],
func: state =>{
MapData.setLocationUpdateRate(Number(state) * 1000);
}
},
{
label: 'Dynamic Objects',
id: 'toggle_dynamic',
saveState: true,
state: true,
func: state => {
MapData.layers.dynamic = state;
MapData.view.dynDirty = true;
},
children: [
{
label: 'Player',
id: 'toggle_player',
saveState: true,
state: true,
func: state =>{
MapData.layers.player = state;
MapData.view.dynDirty = true;
},
children: [
{
label: 'Follow Player',
tooltip: 'Keep View centered on player',
id: 'toggle_gps',
saveState: true,
state: true,
func: state =>{
MapData.layers.gps = state;
MapData.view.dynDirty = true;
}
}
]
},
{
label: 'Train Cars',
id: 'toggle_cars',
saveState: true,
state: true,
func: state =>{
MapData.layers.cars = state;
MapData.view.dynDirty = true;
},
children:[
{
label: 'Non-Player Trains',
tooltip: 'Other trains with active locomotives',
id: 'toggle_otherCars',
saveState: true,
state: true,
func: state =>{
MapData.layers.otherCars = state;
MapData.view.dynDirty = true;
},
}
]
},
]
},
];
const legendKey = document.getElementById('legendKey');
export function initialize(){
const settingsPanel = document.getElementById('settingsPanel');
const oldSettingVersion = loadSetting('version');
for(let thisSetting of settingEntries){
if(oldSettingVersion != settingsVersion){
resetSetting(thisSetting.id);
addSettingEntry(thisSetting, settingsPanel);
resetSettingEntry(thisSetting);
}else{
addSettingEntry(thisSetting, settingsPanel);
}
}
saveSetting('version', settingsVersion);
const legend = document.getElementById('legend');
const legendContents = document.getElementById('legendContents');
const legendButton = document.getElementById('legendButton');
populateKey();
setLastUpdateData();
initTabs();
document.getElementById('sidepanel').style.visibility = '';
};
function addSettingEntry(thisSetting, parent, indent=0){
thisSetting.divContainer = document.createElement('div');
thisSetting.labelElement = document.createElement('label');
thisSetting.divContainer.appendChild(thisSetting.labelElement);
thisSetting.settingType = thisSetting.id.split('_')[0];
thisSetting.default = thisSetting.state;
if(thisSetting.saveState) thisSetting.state = loadSetting(thisSetting.id, thisSetting.state);
switch(thisSetting.settingType){
case 'toggle':
thisSetting.inputElement = document.createElement('input');
thisSetting.inputElement.type = 'checkbox';
thisSetting.inputElement.checked = thisSetting.state;
if(thisSetting.func){
thisSetting.inputElement.addEventListener('input', e =>{
thisSetting.func(e.target.checked);
if(thisSetting.saveState) saveSetting(thisSetting.id, e.target.checked);
if(thisSetting.children){
for(const child of thisSetting.children){
document.getElementById(child.id).parentElement.style.display = e.target.checked ? '' : 'none';
}
}
});
thisSetting.func(thisSetting.state);
}
break;
case 'dropdown':
thisSetting.inputElement = document.createElement('select');
for(let inputEntry of thisSetting.options){
let optionElement = document.createElement('option');
if(typeof(inputEntry) == 'object'){
optionElement.value = inputEntry[0];
optionElement.innerHTML = inputEntry[1];
}else{
optionElement.value = inputEntry;
optionElement.innerHTML = inputEntry;
}
if(inputEntry == thisSetting.state) optionElement.selected = true;
thisSetting.inputElement.appendChild(optionElement);
}
if(thisSetting.state) thisSetting.inputElement.value = thisSetting.state;
if(thisSetting.func){
thisSetting.inputElement.addEventListener('change', e =>{
thisSetting.func(e.target.value);
if(thisSetting.saveState) saveSetting(thisSetting.id, e.target.value);
});
thisSetting.func(thisSetting.state);
}
break;
case 'slider':
thisSetting.inputElement = document.createElement('input');
thisSetting.inputElement.type = 'range';
thisSetting.inputElement.min = thisSetting.min ?? 0;
thisSetting.inputElement.max = thisSetting.max ?? 100;
thisSetting.inputElement.step = thisSetting.step ?? 1;
thisSetting.inputElement.value = thisSetting.state ?? 0;
if(thisSetting.func){
thisSetting.inputElement.addEventListener('input', e => {
thisSetting.func(e.target.value);
if(thisSetting.saveState) saveSetting(thisSetting.id, e.target.value);
});
thisSetting.func(thisSetting.state);
}
break;
case 'field':
thisSetting.inputElement = document.createElement('input');
thisSetting.inputElement.type = 'text';
thisSetting.inputElement.value = thisSetting.state;
if(thisSetting.placeholder) thisSetting.inputElement.placeholder = thisSetting.placeholder;
if(thisSetting.button){
thisSetting.buttonElement = document.createElement('input');
thisSetting.buttonElement.type = 'button';
thisSetting.buttonElement.value = thisSetting.button;
if(thisSetting.func){
thisSetting.buttonElement.addEventListener('click', e => {
thisSetting.func(thisSetting.inputElement.value);
if(thisSetting.saveState) saveSetting(thisSetting.id, thisSetting.inputElement.value);
});
}
}
if(thisSetting.saveState){
thisSetting.inputElement.addEventListener('input', e => {
saveSetting(thisSetting.id, e.target.value);
});
}
break;
case 'button':
thisSetting.inputElement = document.createElement('input');
thisSetting.inputElement.type = 'button';
thisSetting.inputElement.value = thisSetting.label;
if(thisSetting.func){
thisSetting.inputElement.addEventListener('click', e => {
thisSetting.func(0);
});
}
break;
case 'header':
break;
default:
thisSetting.divContainer.remove();
return;
}
parent.appendChild(thisSetting.divContainer);
if(thisSetting.settingType != 'button'){
thisSetting.labelElement.innerHTML = thisSetting.label;
thisSetting.labelElement.htmlFor = thisSetting.id;
if(thisSetting.tooltip) thisSetting.labelElement.title = thisSetting.tooltip;
}
if(thisSetting.settingType == 'field'){
thisSetting.buttonElement.style.margin = `0 0.5em 0 ${indent}em`;
thisSetting.buttonElement.style.padding = `0 0.2em`;
thisSetting.divContainer.prepend(thisSetting.buttonElement);
}
if(thisSetting.inputElement){
thisSetting.inputElement.id = thisSetting.id;
thisSetting.inputElement.style.margin = `0 0.5em 0 ${indent}em`;
thisSetting.divContainer.prepend(thisSetting.inputElement);
}
if(thisSetting.children){
for(let child of thisSetting.children){
addSettingEntry(child, parent, indent+1);
}
}
}
function resetSettingEntry(entry){
resetSetting(entry.id);
entry.state = entry.default;
switch(entry.settingType){
case 'toggle':
entry.inputElement.checked = entry.state;
if(entry.children){
for(const child of entry.children){
document.getElementById(child.id).parentElement.style.display = entry.state ? '' : 'none';
}
}
break;
case 'dropdown':
case 'slider':
case 'field':
entry.inputElement.value = entry.state;
break;
}
switch(entry.settingType){
case 'toggle':
case 'dropdown':
case 'slider':
if(entry.func) entry.func(entry.state);
}
if(entry.children){
for(const child of entry.children){
resetSettingEntry(child);
}
}
}
function saveSetting(name, value){
//console.trace(`saving 'setting_${name}=${encodeURIComponent(value)};'`);
if(value == null){
resetSetting(name);
}else{
document.cookie = `setting_${name}=${encodeURIComponent(value)};max-age=5000000`;
}
}
function loadSetting(name, defaultValue){
const cookies = document.cookie.split(/\s*;\s*/);
for(let cookie of cookies){
if(cookie.startsWith('setting_'+name+'=')){
let result = cookie.split('=');
//console.log(`Loading setting_${name}=${decodeURIComponent(result[1])}`);
if(result[1] == '') return defaultValue;
return autocast(decodeURIComponent(result[1]));
}
}
//console.log(`Loading default setting_${name}=${defaultValue}`);
return defaultValue;
}
function resetSetting(name){
document.cookie = `setting_${name}=;expires=${new Date(0).toUTCString()};`;
}
function autocast(value){
if(value == 'true' || value == 'True'){
return true;
}else if(value == 'false' || value == 'False'){
return false;
}else if(!isNaN(value)){
return Number(value);
}else{
return value;
}
}
let pageLoadUpdateDate = null;
async function setLastUpdateData(){
if(!window.location.host.includes('github')) return;
let commits = await (await fetch(new Request('https://api.github.com/repos/PyroNicampt/DV-Community-Map/commits'))).json();
let lastUpdateElement = document.getElementById('lastUpdate');
if(!commits){
lastUpdateElement.innerHTML = 'Fetch Error';
return;
};
let updateDate = new Date(commits[0].commit.author.date);
lastUpdateElement.innerHTML = `${Utils.formattedTimeBetweenDates(new Date(), updateDate)} ${new Date() >= updateDate ? 'ago' : 'from now'}`;
document.getElementById('changelog').innerHTML = commits[0].commit.message.replaceAll(/\n+/g, '\n').replaceAll('\n','
');
if(pageLoadUpdateDate == null){
pageLoadUpdateDate = updateDate;
}else{
if(updateDate.valueOf() != pageLoadUpdateDate.valueOf()){
lastUpdateElement.innerHTML += ' Reload page to see changes!';
}
}
setTimeout(() => {setLastUpdateData()}, 600000);
}
async function initTabs(){
const tabContents = document.getElementById('tabContents');
let tabs = [];
let totalOffset = 5;
let maxButtonWidth = 0;
const updateTabPosition = tabData => {
//tabData.element.style.zIndex = tabData.visible ? 10 : '';
tabData.element.style.transform = `translateX(${tabData.visible ? maxButtonWidth : -(tabData.content.clientWidth + 10)}px)`;
};
for(let tabButton of document.querySelectorAll('#tabContents .tabButton')){
maxButtonWidth = Math.max(maxButtonWidth, tabButton.clientWidth);
}
for(let tabElement of tabContents.children){
let newTab = {
element: tabElement,
button: tabElement.getElementsByClassName('tabButton')[0],
content: tabElement.getElementsByClassName('tabContent')[0],
visible: tabElement.dataset.tab,
};
newTab.button.style.transform = `translateY(${totalOffset}px)`;
totalOffset += newTab.button.clientHeight + 6;
updateTabPosition(newTab);
tabs.push(newTab);
newTab.button.addEventListener('click', () => {
for(let tab of tabs){
if(tab == newTab)
tab.visible = !tab.visible;
else
tab.visible = false;
if(tab.visible)
tab.element.dataset.tab = 'open';
else
delete tab.element.dataset.tab;
updateTabPosition(tab);
}
});
}
await new Promise(requestAnimationFrame);
tabContents.classList.add('active');
}
function populateKey(){
addKeyEntry('gradeArrow', 'Grade Indication')
addKeyEntry('speed_5', 'Speed Limit');
addKeyEntry('junction', 'Junction/Switch');
addKeyEntry('office', 'Station Office');
addKeyEntry('shop', 'Shop');
addKeyEntry('service_repair', 'Repair Service');
addKeyEntry('service_diesel', 'Diesel Refuel');
addKeyEntry('service_charger', 'Electric Charger');
addKeyEntry('coal', 'Coal Tower');
addKeyEntry('water', 'Water Tower');
addKeyEntry('landmark', 'Landmark');
addKeyEntry('garage', 'Garage');
addKeyEntry('demonstrator', 'Demonstrator Spawn');
addKeyEntry('player', 'Player');
populateCatenaryKey();
}
function populateCatenaryKey(){
const catKey = document.getElementById('legendCatenaryKey');
if(!catKey) return;
catKey.innerHTML = `
⚡ Traction Substation
⛔ Neutral Section (0V Токораздел)
⤓ Lower Pantographs Warning
`;
MapData.updateCatenaryLegend();
}
/**
* Add an entry to the map key.
* @param {string} keyImageHTML
* @param {string} keyLabel
*/
function addKeyEntry(keySprite, keyLabel){
if(!keyLabel) keyLabel = ' ';
let keyDiv = document.createElement('div');
if(keySprite && Config.spriteBounds[keySprite]){
let keyImage = document.createElement('span');
let spriteBounds = Config.spriteBounds[keySprite];
keyImage.classList.add('inlineSvg');
keyImage.style.background = `url('mapSprites.svg') -${spriteBounds.x/spriteBounds.width}em -${spriteBounds.y/spriteBounds.height}em`;
keyImage.style.backgroundSize = '1000%';
keyDiv.appendChild(keyImage);
keyLabel = ' '+keyLabel;
}
keyDiv.innerHTML += keyLabel;
legendKey.appendChild(keyDiv);
}