ws4kp/server/scripts/modules/navigation.js
2020-09-08 10:05:46 -05:00

296 lines
9 KiB
JavaScript

'use strict';
// navigation handles progress, next/previous and initial load messages from the parent frame
/* globals utils, _StationInfo, STATUS, draw */
/* globals CurrentWeather, LatestObservations, TravelForecast, RegionalForecast, LocalForecast, ExtendedForecast, Almanac, Radar */
document.addEventListener('DOMContentLoaded', () => {
navigation.init();
});
const UNITS = {
english: Symbol('english'),
metric: Symbol('metric'),
};
const navigation = (() => {
let weatherParameters = {};
let displays = [];
let initialLoadDone = false;
let currentUnits = UNITS.english;
let playing = false;
let backgroundImage;
const init = async () => {
// set up message receive and dispatch accordingly
window.addEventListener('message', (event) => {
// test for trust
if (!event.isTrusted) return;
// get the data
const data = JSON.parse(event.data);
// dispatch event
if (!data.type) return;
switch (data.type) {
case 'latLon':
getWeather(data.message);
break;
case 'units':
setUnits(data.message);
break;
case 'navButton':
handleNavButton(data.message);
break;
default:
console.error(`Unknown event ${data.type}`);
}
}, false);
backgroundImage = await utils.image.load('images/BackGround1_1.png');
drawProgressCanvas();
};
const postMessage = (type, message = {}) => {
const parent = window.parent;
parent.postMessage(JSON.stringify({type, message}, window.location.origin));
};
const getWeather = async (latLon) => {
// reset statuses
initialLoadDone = false;
// get initial weather data
const point = await utils.weather.getPoint(latLon.lat, latLon.lon);
// get stations
const stations = await $.ajax({
type: 'GET',
url: point.properties.observationStations,
dataType: 'json',
crossDomain: true,
});
const StationId = stations.features[0].properties.stationIdentifier;
let city = point.properties.relativeLocation.properties.city;
if (StationId in _StationInfo) {
city = _StationInfo[StationId].City;
city = city.split('/')[0];
}
// populate the weather parameters
weatherParameters.latitude = latLon.lat;
weatherParameters.longitude = latLon.lon;
weatherParameters.zoneId = point.properties.forecastZone.substr(-6);
weatherParameters.radarId = point.properties.radarStation.substr(-3);
weatherParameters.stationId = StationId;
weatherParameters.weatherOffice = point.properties.cwa;
weatherParameters.city = city;
weatherParameters.state = point.properties.relativeLocation.properties.state;
weatherParameters.timeZone = point.properties.relativeLocation.properties.timeZone;
weatherParameters.forecast = point.properties.forecast;
weatherParameters.stations = stations.features;
// update the main process for display purposes
postMessage('weatherParameters', weatherParameters);
// start loading canvases if necessary
if (displays.length === 0) {
displays = [
new CurrentWeather(0,'currentWeather', weatherParameters),
new LatestObservations(1, 'latestObservations', weatherParameters),
new TravelForecast(2, 'travelForecast', weatherParameters),
// Regional Forecast: 0 = regional conditions, 1 = today, 2 = tomorrow
new RegionalForecast(3, 'regionalForecast0', weatherParameters, 0),
new RegionalForecast(4, 'regionalForecast1', weatherParameters, 1),
new RegionalForecast(5, 'regionalForecast2', weatherParameters, 2),
new LocalForecast(6, 'localForecast', weatherParameters),
new ExtendedForecast(7, 'extendedForecast', weatherParameters),
new Almanac(8, 'almanac', weatherParameters),
new Radar(8, 'radar', weatherParameters),
];
} else {
// or just call for new data if the canvases already exist
displays.forEach(display => display.getData(weatherParameters));
}
// GetMonthPrecipitation(this.weatherParameters);
// GetAirQuality3(this.weatherParameters);
// ShowDopplerMap(this.weatherParameters);
// GetWeatherHazards3(this.weatherParameters);
};
// receive a status update from a module {id, value}
const updateStatus = (value) => {
// skip if initial load
if (initialLoadDone) return;
// test for loaded status
if (value.status !== STATUS.loaded) return;
drawProgressCanvas();
// send loaded messaged to parent
if (countLoadedCanvases < displays.length) return;
postMessage('loaded');
// store the display number
};
const countLoadedCanvases = () => displays.reduce((acc, display) => {
if (display.status !== STATUS.loading) return acc+1;
return acc;
},0);
const hideAllCanvases = () => {
displays.forEach(display => display.hideCanvas());
};
const units = () => currentUnits;
const setUnits = (_unit) => {
const unit = _unit.toLowerCase();
if (unit === 'english') {
currentUnits = UNITS.english;
} else {
currentUnits = UNITS.metric;
}
// TODO: refresh current screen
};
// is playing interface
const isPlaying = () => playing;
// navigation message constants
const msg = {
response: { // display to navigation
previous: Symbol('previous'), // already at first frame, calling function should switch to previous canvas
inProgress: Symbol('inProgress'), // have data to display, calling function should do nothing
next: Symbol('next'), // end of frames reached, calling function should switch to next canvas
},
command: { // navigation to display
firstFrame: Symbol('firstFrame'),
previousFrame: Symbol('previousFrame'),
nextFrame: Symbol('nextFrame'),
lastFrame: Symbol('lastFrame'), // used when navigating backwards from the begining of the next canvas
},
};
// receive navigation messages from displays
const displayNavMessage = (message) => {
if (message.type === msg.response.previous) loadDisplay(-1);
if (message.type === msg.response.next) loadDisplay(1);
};
// navigate to next or previous
const navTo = (direction) => {
if (direction === msg.command.nextFrame) currentDisplay().navNext();
if (direction === msg.command.previousFrame) currentDisplay().navPrev();
};
// find the next or previous available display
const loadDisplay = (direction) => {
const totalDisplays = displays.length;
const curIdx = currentDisplayIndex();
let idx;
for (let i = 0; i < totalDisplays; i++) {
// convert form simple 0-10 to start at current display index +/-1 and wrap
idx = utils.calc.wrap(curIdx+(i+1)*direction,totalDisplays);
if (displays[idx].status === STATUS.loaded) break;
}
const newDisplay = displays[idx];
// hide all displays
hideAllCanvases();
// show the new display and navigate to an appropriate display
if (direction < 0) newDisplay.showCanvas(msg.command.lastFrame);
if (direction > 0) newDisplay.showCanvas(msg.command.firstFrame);
};
// get the current display index or value
const currentDisplayIndex = () => {
let index = displays.findIndex(display=>display.isActive());
// if there is no active display, default to the first one
if (index === -1) index = displays.length-1;
return index;
};
const currentDisplay = () => {
return displays[currentDisplayIndex()];
};
const setPlaying = (newValue) => {
playing = newValue;
postMessage('isPlaying', playing);
};
// handle all navigation buttons
const handleNavButton = (button) => {
switch (button) {
case 'playToggle':
setPlaying(!playing);
break;
case 'next':
setPlaying(false);
navTo(msg.command.nextFrame);
break;
case 'previous':
setPlaying(false);
navTo(msg.command.previousFrame);
break;
default:
console.error(`Unknown navButton ${button}`);
}
};
const drawProgressCanvas = () => {
const canvas = document.getElementById('progressCanvas');
const context = canvas.getContext('2d');
context.drawImage(backgroundImage, 0, 100, 640, 300, 0, 100, 640, 300);
draw.horizontalGradientSingle(context, 0, 90, 52, 399, draw.sideColor1, draw.sideColor2);
draw.horizontalGradientSingle(context, 584, 90, 640, 399, draw.sideColor1, draw.sideColor2);
displays.forEach((display, idx) => {
const y = 120 + idx*29;
const dots = Array(120 - Math.floor(display.name.length * 2.5)).join('.');
draw.text(context, 'Star4000 Extended', '19pt', '#ffffff', 70, y, display.name + dots, 2);
// Erase any dots that spill into the status text.
context.drawImage(backgroundImage, 475, y - 20, 165, 30, 475, y - 20, 165, 30);
draw.horizontalGradientSingle(context, 584, 90, 640, 399, draw.sideColor1, draw.sideColor2);
let statusText;
let statusColor;
switch (display.status) {
case STATUS.loading:
statusText = 'Loading';
statusColor = '#ffff00';
break;
case STATUS.loaded:
statusText = 'Press Here';
statusColor = '#00ff00';
break;
case STATUS.failed:
statusText = 'Failed';
statusColor = '#ff0000';
break;
case STATUS.noData:
statusText = 'No Data';
statusColor = '#C0C0C0';
draw.box(context, 'rgb(33, 40, 90)', 475, y - 15, 75, 15);
break;
default:
}
draw.text(context, 'Star4000 Extended', '19pt', statusColor, 565, y, statusText, 2, 'end');
});
};
return {
init,
updateStatus,
units,
isPlaying,
displayNavMessage,
msg,
};
})();