Import WebThrottle-EX from ca33f6a

This commit is contained in:
2022-04-22 00:04:50 +02:00
parent dc85f62577
commit 4de6f83f3f
41 changed files with 7015 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
$(document).ready(function(){
locoList = getLocoList();
savedMaps = getPreparedMaps();
$("#loco-form").on("submit", function (e) {
e.preventDefault();
data = $(this).serializeArray();
mode = $("#loco-submit").attr("loco-mode");
if(mode != 'edit'){
saveLocomotive(data);
}else{
id = $("#loco-submit").attr("loco-id")
saveEditedLocomotive(data, id);
}
locoList = getLocoList();
loadLocomotives();
$("#loco-form")[0].reset();
$("#loco-form-content").css("display", "none");
});
$("#add-loco").on("click", function () {
savedMaps = getPreparedMaps();
$("#loco-form")[0].reset();
$("#loco-form-content").css("display", "inline-block");
$(".add-loco-form .add-loco-head").html("Add Locomotive");
$("#loco-submit").attr("loco-mode", "add");
});
$("#close-addloco-model").on("click", function () {
if ($("#loco-form-content").is(":visible")) {
$("#loco-form-content").css("display", "none");
}
});
$("#ex-locoid").autocomplete({
delay: 0,
minLength: 0,
source: function (request, response) {
var matcher = new RegExp(
$.ui.autocomplete.escapeRegex(request.term),
"i"
);
response(
$.grep(locoList, function (item) {
if (item != undefined) {
console.log(item);
return (
matcher.test(item.name) ||
matcher.test(item.cv) ||
matcher.test(item.type) ||
matcher.test(item.manufacturer)
);
} else {
return true;
}
})
);
},
select: function (event, ui) {
$(this).val(ui.item.cv + " | " + ui.item.name);
$(this).attr("loco-cv", ui.item.cv);
return false;
},
})
.focus(function () {
console.log("Focused");
$(this).autocomplete("search", "");
})
.data("ui-autocomplete")._renderItem = function (ul, item) {
$(ul).addClass("res-list");
return $("<li></li>")
.data("item.autocomplete", item)
.append(
"<div><p class='ac-loco-name'>" +
item.name +
"</p><small> <span class='pill'>CV:" +
item.cv +
"</span>|<span class='pill'>" +
item.type +
"</span>|<span class='pill wrap'>" +
item.brand +
"</span></small></div>"
)
.appendTo(ul);
};
$("#function-maps").autocomplete({
delay: 0,
minLength: 0,
source: function (request, response) {
var matcher = new RegExp(
$.ui.autocomplete.escapeRegex(request.term),
"i"
);
response(
$.grep(savedMaps, function (item) {
return matcher.test(item.mname);
})
);
},
select: function (event, ui) {
$(this).val(ui.item.mname);
return false;
},
})
.focus(function () {
console.log("Focused");
$(this).autocomplete("search", "");
})
.data("ui-autocomplete")._renderItem = function (ul, item) {
$(ul).addClass("res-list-sm");
return $("<li></li>")
.data("item.autocomplete", item)
.append("<div><p class='item'>" + item.mname + "</p></div>")
.appendTo(ul);
};
$("#export-locolist").on("click", function (e) {
downloadCabData();
});
$("#import-locolist").on("click", function (e) {
e.preventDefault();
$("#cabs-upload").trigger("click");
locoList = getLocoList();
});
});
function getPreparedMaps() {
const defaultMap = {
mname: "Default",
fnData: {},
}
return [defaultMap, ...getMapData()];
}
/** Reference Map Structure
maps = [
{
mname: "mkmap",
fnData: {
f0: [0, 1, "Head Light", 1],
},
},
];
locos = [
{
name: "Mikado",
cv: 3,
type: "Diesel",
manufacturer: "Kato",
},
];
*/

View File

@@ -0,0 +1,270 @@
/* This is part of the DCC++ EX Project for model railroading and more.
For licence information, please see index.html
For more information, see us at dcc-ex.com.
commandController.js
Open a serial port and create a stream to read and write data
While there is data, we read the results in loop function
*/
$(document).ready(function(){
console.log("Command Controller loaded");
uiDisable(true)
emulatorClass = new Emulator({logger: displayLog});
});
// - Request a port and open an asynchronous connection,
// which prevents the UI from blocking when waiting for
// input, and allows serial to be received by the web page
// whenever it arrives.
async function connectServer() {
// Gets values of the connection method selector
selectMethod = document.getElementById('select-method')
mode = selectMethod.value;
// Disables selector so it can't be changed whilst connected
selectMethod.disabled = true;
console.log("Set Mode: "+mode)
// Checks which method was selected
if (mode == "serial") {
try{
// - Request a port and open an asynchronous connection,
// which prevents the UI from blocking when waiting for
// input, and allows serial to be received by the web page
// whenever it arrives.
port = await navigator.serial.requestPort(); // prompt user to select device connected to a com port
// - Wait for the port to open.
await port.open({ baudRate: 115200 }); // open the port at the proper supported baud rate
// create a text encoder output stream and pipe the stream to port.writeable
const encoder = new TextEncoderStream();
outputDone = encoder.readable.pipeTo(port.writable);
outputStream = encoder.writable;
// To put the system into a known state and stop it from echoing back the characters that we send it,
// we need to send a CTRL-C and turn off the echo
writeToStream('\x03', 'echo(false);');
// Create an input stream and a reader to read the data. port.readable gets the readable stream
// DCC++ commands are text, so we will pipe it through a text decoder.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable
// .pipeThrough(new TransformStream(new LineBreakTransformer())); // added this line to pump through transformer
//.pipeThrough(new TransformStream(new JSONTransformer()));
// get a reader and start the non-blocking asynchronous read loop to read data from the stream.
reader = inputStream.getReader();
readLoop();
uiDisable(false)
displayLog("[CONNECTION] Serial connected")
return true;
} catch (err) {
console.log("User didn't select a port to connect to")
return false;
}
} else{
// If using the emulator
emulatorMode = true;
// Displays dummy hardware message
displayLog("\n[CONNECTION] Emulator connected")
displayLog("[RECEIVE] DCC++ EX COMMAND STATION FOR EMULATOR / EMULATOR MOTOR SHIELD: V-1.0.0 / Feb 30 2020 13:10:04")
uiDisable(false)
return true;
}
}
// While there is still data in the serial buffer us an asynchronous read loop
// to get the data and place it in the "value" variable. When "done" is true
// all the data has been read or the port is closed
async function readLoop() {
while (true) {
const { value, done } = await reader.read();
// if (value && value.button) { // alternate check and calling a function
// buttonPushed(value);
if (value) {
displayLog('[RECEIVE] '+value);
console.log('[RECEIVE] '+value);
}
if (done) {
console.log('[readLoop] DONE'+done.toString());
reader.releaseLock();
break;
}
}
}
function writeToStream(...lines) {
// Stops data being written to nonexistent port if using emulator
let stream = emulatorClass
if (port) {
stream = outputStream.getWriter();
}
lines.forEach((line) => {
if (line == "\x03" || line == "echo(false);") {
} else {
displayLog('[SEND]'+line.toString());
}
const packet = `<${line}>\n`;
stream.write(packet)
console.log(packet)
});
}
// Transformer for the Web Serial API. Data comes in as a stream so we
// need a container to buffer what is coming from the serial port and
// parse the data into separate lines by looking for the breaks
class LineBreakTransformer {
constructor() {
// A container for holding stream data until it sees a new line.
this.container = '';
}
transform(chunk, controller) {
// Handle incoming chunk
this.container += chunk; // add new data to the container
const lines = this.container.split('\r\n'); // look for line breaks and if it finds any
this.container = lines.pop(); // split them into an array
lines.forEach(line => controller.enqueue(line)); // iterate parsed lines and send them
}
flush(controller) {
// When the stream is closed, flush any remaining data
controller.enqueue(this.container);
}
}
// Optional transformer for use with the web serial API
// to parse a JSON file into its component commands
class JSONTransformer {
transform(chunk, controller) {
// Attempt to parse JSON content
try {
controller.enqueue(JSON.parse(chunk));
} catch (e) {
//displayLog(chunk.toString());
//displayLog(chunk);
console.log('No JSON, dumping the raw chunk', chunk);
controller.enqueue(chunk);
}
}
}
async function disconnectServer() {
if ($("#power-switch").is(':checked')) {
$("#log-box").append('<br>'+'turn off power'+'<br>');
writeToStream('0');
$("#power-switch").prop('checked', false)
$("#power-status").html('Off');
}
uiDisable(true)
if (port) {
// Close the input stream (reader).
if (reader) {
await reader.cancel(); // .cancel is asynchronous so must use await to wave for it to finish
await inputDone.catch(() => {});
reader = null;
inputDone = null;
console.log('close reader');
}
// Close the output stream.
if (outputStream) {
await outputStream.getWriter().close();
await outputDone; // have to wait for the azync calls to finish and outputDone to close
outputStream = null;
outputDone = null;
console.log('close outputStream');
}
// Close the serial port.
await port.close();
port = null;
console.log('close port');
displayLog("[CONNECTION] Serial disconnected");
} else {
// Disables emulator
emulatorMode = undefined;
displayLog("[CONNECTION] Emulator disconnected");
}
// Allows a new method to be chosen
selectMethod.disabled = false;
}
// Connect or disconnect from the command station
async function toggleServer(btn) {
// If already connected, disconnect
if (port || emulatorMode) {
await disconnectServer();
btn.attr('aria-state','Disconnected');
btn.html('<span class="con-ind"></span>Connect DCC++ EX'); //<span id="con-ind"></span>Connect DCC++ EX
return;
}
// Otherwise, call the connect() routine when the user clicks the connect button
success = await connectServer();
// Checks if the port was opened successfully
if (success) {
btn.attr('aria-state','Connected');
btn.html('<span class="con-ind connected"></span>Disconnect DCC++ EX');
} else {
selectMethod.disabled = false;
}
}
// Display log of events
function displayLog(data){
data = data.replace("<"," ");
data = data.replace(">"," ");
$("#log-box").append("<br>"+data.toString()+"<br>");
$("#log-box").scrollTop($("#log-box").prop("scrollHeight"));
}
// Function to generate commands for functions F0 to F4
function sendCommandForF0ToF4(fn, opr){
setFunCurrentVal(fn,opr);
cabval = (128+getFunCurrentVal("f1")*1 + getFunCurrentVal("f2")*2 + getFunCurrentVal("f3")*4 + getFunCurrentVal("f4")*8 + getFunCurrentVal("f0")*16);
writeToStream("f "+getCV()+" "+cabval);
console.log("Command: "+ "f "+getCV()+" "+cabval);
}
// Function to generate commands for functions F5 to F8
function sendCommandForF5ToF8(fn, opr){
setFunCurrentVal(fn,opr);
cabval = (176+getFunCurrentVal("f5")*1 + getFunCurrentVal("f6")*2 + getFunCurrentVal("f7")*4 + getFunCurrentVal("f8")*8);
writeToStream("f "+getCV()+" "+cabval);
console.log("Command: "+ "f "+getCV()+" "+cabval);
}
// Function to generate commands for functions F9 to F12
function sendCommandForF9ToF12(fn, opr){
setFunCurrentVal(fn,opr);
cabval = (160+getFunCurrentVal("f9")*1 + getFunCurrentVal("f10")*2 + getFunCurrentVal("f11")*4 + getFunCurrentVal("f12")*8);
writeToStream("f "+getCV()+" "+cabval);
console.log("Command: "+ "f "+getCV()+" "+cabval);
}
// Function to generate commands for functions F13 to F20
function sendCommandForF13ToF20(fn, opr){
setFunCurrentVal(fn,opr);
cabval = (getFunCurrentVal("f13")*1 + getFunCurrentVal("f14")*2 + getFunCurrentVal("f15")*4 + getFunCurrentVal("f16")*8 + getFunCurrentVal("f17")*16 + getFunCurrentVal("f18")*32 + getFunCurrentVal("f19")*64 + getFunCurrentVal("f20")*128);
writeToStream("f "+getCV()+" 222 "+cabval);
console.log("Command: "+ "f "+getCV()+" 222 "+cabval);
}
// Function to generate commands for functions F21 to F28
function sendCommandForF21ToF28(fn, opr){
setFunCurrentVal(fn,opr);
cabval = (getFunCurrentVal("f21")*1 + getFunCurrentVal("f22")*2 + getFunCurrentVal("f23")*4 + getFunCurrentVal("f24")*8 + getFunCurrentVal("f25")*16 + getFunCurrentVal("f26")*32 + getFunCurrentVal("f27")*64 + getFunCurrentVal("f28")*128);
writeToStream("f "+getCV()+" 223 "+cabval);
console.log("Command: "+ "f "+getCV()+" 223 "+cabval);
}

View File

@@ -0,0 +1,187 @@
/* This is part of the DCC++ EX Project for model railroading and more.
For licence information, please see index.html.
For more information, see us at dcc-ex.com.
emulator.js
Allows the software to operate without a Command Station attached. This
file manages the correct response that a Command Station would provide.
*/
/**
* Utility function can be moved when we can import files
* @description Removes control characters "<", ">" and "\n"
* @param {string} packet
* @return {string}
*/
function removeControlCharacters(packet) {
return [...packet].filter(char => !["<", ">", "\n"].includes(char)).join("")
}
/**
* Utility function can be moved when we can import files
* @description Finds the first non-blank character after control characters are removed
* @param {string} packet
* @return {string}
*/
function extractPacketKey(packet) {
const cleanedPacket = [...removeControlCharacters(packet)]
return cleanedPacket.find(char => char !== " ");
}
class Emulator {
constructor({logger}) {
this.turnoutEmulator = new TurnoutEmulator()
this.logger = logger
}
/**
* @param {string} packet
*/
write(packet) {
if (packet === "<credits>") {
console.log("Credits")
return credits()
}
let packetKey = extractPacketKey(packet);
switch (packetKey) {
// Cab control
case ("t"):
this.logger('[RECEIVE] '+ this.#cabControlCommand(packet))
break;
// Track power off
case ('0') :
this.logger('[RECEIVE] '+ this.#powerOffCommand(packet));
break;
// Track power on
case ('1'):
this.logger('[RECEIVE] '+ this.#powerOnCommand(packet));
break;
// New cab functions
case ('F'):
this.logger('[RECEIVE] '+ this.#cabFunctionCommand(packet));
break;
// Legacy cab functions
case ('f'):
this.logger('[RECEIVE] '+ this.#cabFunctionCommand(packet, true));
break;
// Turnouts
case ('T'): //Not fully finished
this.logger('[RECEIVE] '+ this.#turnoutCommand(packet, this.turnoutEmulator))
break
default:
break;
}
}
/**
* @param {string} packet
* @return {string}
*/
#cabControlCommand(packet) {
const splitPacket = packet.split(" ");
return 'T 1 ' + splitPacket[3] + ' ' + splitPacket[4].substring(0, splitPacket[4].length - 2);
}
/**
* @param {string} packet
* @return {string}
*/
#powerOffCommand(packet) {
return 'p0';
}
/**
* @param {string} packet
* @return {string}
*/
#powerOnCommand(packet) {
return 'p1';
}
/**
* @param {string} packet
* @param {boolean} legacy
* @return number
*/
#cabFunctionCommand(packet, legacy = false) {
return NaN;
}
/**
* @param {string} packet
* @param {TurnoutEmulator} turnoutEmulator
* @return {string}
*/
#turnoutCommand(packet, turnoutEmulator = new TurnoutEmulator()) {
const splitPacket = removeControlCharacters(packet).split(" ");
if (splitPacket.length === 4) {
// Adds a Turnout
return turnoutEmulator.addTurnout(new Turnout(packet))
} else if (splitPacket.length === 2) {
// Removes a Turnout
return turnoutEmulator.removeTurnout(splitPacket[1])
} else if (splitPacket.length === 1) {
// Reads Turnouts
return turnoutEmulator.turnouts
}
}
}
class TurnoutEmulator {
/**
* @type {[]}
*/
#turnouts = []
/**
* @return {string|string[]}
*/
get turnouts() {
if (!this.#turnouts.length) {
return 'X'
}
return this.#turnouts.map(turnout => `H ${turnout.id} ${turnout.address} ${turnout.subaddress} ${turnout.thrown}`)
}
/**
* @param {*} turnout
* @return {string}
*/
addTurnout(turnout) {
this.#turnouts = [...this.#turnouts, turnout]
console.log(this.#turnouts);
return '<O>';
}
/**
* @param {string} turnoutId
* @return {string}
*/
removeTurnout(turnoutId) {
this.#turnouts = this.#turnouts.filter(turnout => turnout.id !== turnoutId);
console.log(this.#turnouts);
return 'O';
}
}
class Turnout {
constructor(packet) {
let [_key, id, address, subaddress] = removeControlCharacters(packet).split(" ");
this.id = id
this.address = address
this.subaddress = subaddress
this.thrown = 0
}
}

View File

@@ -0,0 +1,971 @@
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or$("#log-box").append("<br>"+data+"<br>");
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Authors: Fred Decker
Mani Kumar
Matt
This is part of the DCC++ EX Project for model railroading and more.
For more information, see us at dcc-ex.com.
*/
window.cv=0;
window.speed=0;
window.direction=1;
window.server="";
window.port=4444;
window.speedStep = 1;
window.functions = {
"f0": 0,
"f1": 0,
"f2": 0,
"f3": 0,
"f4": 0,
"f5": 0,
"f6": 0,
"f7": 0,
"f8": 0,
"f9": 0,
"f10": 0,
"f11": 0,
"f12": 0,
"f13": 0,
"f14": 0,
"f15": 0,
"f16": 0,
"f17": 0,
"f18": 0,
"f19": 0,
"f20": 0,
"f21": 0,
"f22": 0,
"f23": 0,
"f24": 0,
"f25": 0,
"f26": 0,
"f27": 0,
"f28": 0
};
window.isStopped = true;
let port;
let emulatorMode;
let reader;
let inputDone;
let outputDone;
let inputStream;
let outputStream;
let pressed = false;
// Enables and disables ui elements
function uiDisable (status) {
/*document.getElementById('ex-locoid').disabled = status
document.getElementById('power-switch').disabled = status
document.getElementById('button-sendCmd').disabled = status
document.getElementById('dir-f').disabled = status
document.getElementById('dir-S').disabled = status*/
//document.getElementById('dir-b').disabled = status
$("#ex-locoid").prop('disabled', status)
$("#power-switch").prop('disabled', status)
$("#button-sendCmd").prop('disabled', status)
$("#dir-f").prop('disabled', status)
$("#dir-S").prop('disabled', status)
$("#dir-b").prop('disabled', status)
if (status){
//$("#throttle").roundSlider("disable");
//toggleThrottleState(false)
$("#button-getloco").trigger("click");
} else {
//$("#throttle").roundSlider("enable");
//toggleThrottleState(true)
//$("#button-getloco").trigger("click");
}
}
// Returns given function current value (0-disable/1-enable)
function getFunCurrentVal(fun){
return window.functions[fun];
}
// Set given function current value with given value (0/1)
function setFunCurrentVal(fun, val){
window.functions[fun]=val;
}
// Set given CV value
function setCV(val){
window.cv = val;
console.log("Set Cab Address: "+val);
}
// Get stored CV value
function getCV(){
return window.cv
}
// Set Speed value
function setSpeed(sp){
window.speed=sp;
}
// Get Speed value
function getSpeed(){
return parseInt(window.speed);
}
// Set Direction
function setDirection(dir){
window.direction=dir;
}
// Get Direction value
function getDirection(dir){
return window.direction;
}
// Handling Disabling functionality for Knob Type throttle (it will not Out of the box)
function toggleKnobState(ele, state) {
if(!state){
ele.addClass("disabled");
}else{
ele.removeClass("disabled");
}
}
function loadMapData(map){
data = [];
if (map == "Default") {
data = { mname: "Default", fnData: fnMasterData };
} else {
data = getStoredMapData(map);
}
$("#mapping-panel").empty();
$("#mapping-panel").append(
'<div class="settings-subheading row">' +
'<div class="column-7 pl0"><div class="map-name-heading">' +
map +
"</div></div>" +
'<div class="column-3 pr0">' +
'<input type="hidden" id="cur-map-val" cur-map="'+map+'"/>' +
'<a href="#" class="option-btn waste-bin" id="delete-map"> &#128465;</a>' +
'<a href="#" class="option-btn" id="import-map"> &#8595;</a>' +
'<a href="#" class="option-btn" id="export-cur-map"> &#8593;</a>' +
'<a href="#" class="option-btn" id="edit-cur-map">&#9998;</a>' +
"</div>" +
"</div>"
);
$("#mapping-panel").append(
'<div class="row settings-group fnhead">' +
'<div class="column-2">Function</div>'+
'<div class="column-4">Name</div>'+
'<div class="column-2">Type</div>' +
'<div class="column-2">Visible</div>' +
'</div>'
);
container = $('<div class="maps-content"></div>').appendTo("#mapping-panel");
$.each(data.fnData, function (key, value) {
container.append(
'<div class="row settings-group" id="'+key+'">' +
'<div class="column-2 caplitalize">'+key+'</div>'+
'<div class="column-4">'+value[2]+'</div>'+
'<div class="column-2">'+ (value[1] == 1 ? '<span class="pill red">Momentary</span>' : '<span class="pill green">Latching</span>') +'</div>' +
'<div class="column-2">'+ (value[3] == 1 ? '<span class="pill green">Visible</span>' : '<span class="pill">Hidden</span>') +'</div>' +
'</div>'
);
});
}
function loadLocomotives(){
locos = getLocoList();
$("#locomotives-panel").empty();
$.each(locos, function (key, value) {
$("#locomotives-panel").append(
'<div class="row settings-group" id="'+key+'">'+
'<div class="column-1 sno"><p>' + (key + 1) + "</p></div>" +
'<div class="column-7 loco-details">' +
'<div class="row">' +
'<div class="column-7"><p class="ac-loco-name column-10">' +
value.name +
"</p></div>" +
'<div class="column-2 cv-num"><p><small>CV </small>' +
value.cv +
"</p></div>" +
"</div>" +
'<div class="row sub-text">' +
'<div class="column-3"><p>' + value.type + '</p></div>' +
'<div class="column-3">' +
(value.decoder == "" ? '<p class="nd">Undefined</p>' : "<p>" + value.decoder + "</p>") +
"</div>" +
'<div class="column-3">' +
(value.brand == "" ? '<p class="nd">Undefined</p>' : "<p>" + value.brand + "</p>") +
"</div></div></div>" +
'<div class="column-2 asst-map"><div class="row">' +
'<div class="column-7"><p class="muted">Map</p><p>' +
value.map +
"</p></div>" +
'<div class="column-3 prh"><a href="#" loco-id="'+key+'" data-loco="'+
value.name+'" class="edit-cur-loco"> &#9998; </a></div></div>' +
"</div></br>"
);
});
}
//Initialization routine for Throttle screen
function setThrottleScreenUI() {
loadmaps();
loadButtons({ mname: "default", fnData: fnMasterData });
controller = getPreference("scontroller");
$("#throttle-selector").val(controller).trigger("change");
setspeedControllerType(controller);
// Show and hide debug console based on preference set in earlier session
if (getPreference("dbugConsole") == null) {
setPreference("dbugConsole", true);
}
if(getPreference("dbugConsole")) {
$("#debug-console").show()
$("#console-toggle").prop("checked", true);
}else{
$("#debug-console").hide();
$("#console-toggle").prop("checked", false);
}
$(".dir-toggle").addClass("forward");
// Set theme
if (getPreference("theme") == null) {
setPreference("theme", "simple");
}else{
theme = getPreference("theme");
$("#theme-selector").val(theme).trigger("change");
if (theme != "simple") {
$("link[title*='theme']").remove();
$("head").append(
'<link rel="stylesheet" type="text/css" title="theme" href="css/themes/'+theme+'.css">'
);
}
}
}
// Change the Speed controller type
function setspeedControllerType(pref){
// Set saved throttle or use circular throttle as default
$(".speedController").hide();
switch (pref) {
case "vertical":
console.log("Vertical Speed Controller");
$("#vertical-throttle").show();
break;
case "knob":
console.log("Knob Speed Controller");
$("#knobthrottle").show();
break;
case "circular":
console.log("Circular Speed Controller");
$("#circular-throttle").show();
break;
case "default":
case null:
case undefined:
console.log("Fallback Speed Controller");
$("#vertical-throttle").show();
setPreference("scontroller", "vertical");
$("#throttle-selector").val("vertical").trigger("change");
}
}
// Enabling/disabling Speed Controllers
function toggleThrottleState(state){
if(state){
$("#circular-throttle").roundSlider("enable");
$("#v-throttle").slider("enable");
toggleKnobState($("#knobthrottle"), true);
}else{
$("#circular-throttle").roundSlider("disable");
$("#v-throttle").slider("disable");
toggleKnobState($("#knobthrottle"), false);
}
}
/**********************************************************
* This function will
1. Send Speed command
2. Set speed value of all the available sliders/controllers
3. Set respctive speed number
************************************************************/
function setSpeedofControllers(){
spd = getSpeed();
if(!isStopped){
writeToStream("t 01 " + getCV() + " " + spd + " " + getDirection());
}
// Circular
$("#circular-throttle").roundSlider("setValue", spd);
// Vertical
$("#v-throttle").val(spd).change();
$("#v-throttle").slider("option", "value", spd);
$("#speed-indicator").html(spd);
// Knob
$("#knob-value").html(spd);
knob.val(spd).change();
}
// This function will generate commands for each type of function
function generateFnCommand(clickedBtn){
func = clickedBtn.attr('name'); // Gives function name (F1, F2, .... F28)
eventType = clickedBtn.data("type"); // Gives type of button (Press/Hold or Toggle)
btnPressed = clickedBtn.attr("aria-pressed");
//console.log("Function Name=>"+func+" , Button Type=>"+eventType+" , Button Pressed=>"+btnStatus);
switch(func){
case "f0":
case "f1":
case "f2":
case "f3":
case "f4":
{
if(btnPressed=="true"){
sendCommandForF0ToF4(func,1);
}else{
sendCommandForF0ToF4(func,0);
}
break;
}
case "f5":
case "f6":
case "f7":
case "f8":
{
if(btnPressed=="true"){
sendCommandForF5ToF8(func,1);
}else{
sendCommandForF5ToF8(func,0);
}
break;
}
case "f9":
case "f10":
case "f11":
case "f12":
{
if(btnPressed=="true"){
sendCommandForF9ToF12(func,1);
}else{
sendCommandForF9ToF12(func,0);
}
break;
}
case "f13":
case "f14":
case "f15":
case "f16":
case "f17":
case "f18":
case "f19":
case "f20":
{
if(btnPressed=="true"){
sendCommandForF13ToF20(func,1);
}else{
sendCommandForF13ToF20(func,0);
}
break;
}
case "f21":
case "f22":
case "f23":
case "f24":
case "f25":
case "f26":
case "f27":
case "f28":
{
if(btnPressed=="true"){
sendCommandForF21ToF28(func,1);
}else{
sendCommandForF21ToF28(func,0);
}
break;
}
default:
{
alert("Invalid Function");
}
}
}
$(document).ready(function(){
var mode = 0;
// Left Menu
$("#nav-open").on("click", function () {
$("#side-menu").show().animate({ left: 0 });
});
$("#nav-close").on("click", function () {
$("#side-menu").animate({ left: -260 }, function(){
$("#side-menu").hide();
});
});
$("#info-tooltip").tooltip({
content:
"<p>DCC++ EX Web Throttle<br>(WebThrottle-EX)</p><hr><p>Version: "+version+"</p><p><b>Credits</b><br> Fred Decker <br> Mani Kumar <br> Matt H</p>",
show: {
effect: "slideDown",
delay: 100,
},
classes: {
"ui-tooltip": "credits-tooltip",
},
position: {
my: "left top",
at: "left bottom",
},
});
// Load function map, buttons throttle etc
setThrottleScreenUI();
$("#throttle-selector").on("change", function (e) {
selectedval = $(this).val();
console.log(selectedval);
setPreference("scontroller", selectedval);
setspeedControllerType(selectedval);
});
$("#theme-selector").on("change", function (e) {
selectedval = $(this).val();
console.log(selectedval);
setPreference("theme", selectedval);
$("link[title*='theme']").remove();
if (selectedval != "simple") {
$("head").append(
'<link rel="stylesheet" type="text/css" title="theme" href="css/themes/' +
selectedval +
'.css">'
);
}
});
// Connect command station
$("#button-connect").on("click", function () {
toggleServer($(this));
});
// Disconnect command station
$("#button-disconnect").on("click", function () {
disconnectServer();
});
// Aquire loco of given CV
$("#button-getloco").on("click", function () {
acButton = $(this);
isAcquired = $(this).data("acquired");
// Parse int only returns number if the string is starting with Number
locoid_input = parseInt($("#ex-locoid").val());
if (locoid_input != 0) {
if (isAcquired == false && getCV() == 0) {
setCV(locoid_input);
$("#loco-info").html("Acquired Locomotive: " + locoid_input);
acButton.data("acquired", true);
acButton.html('<span class="icon-cross"></span>');
toggleThrottleState(true);
} else {
currentCV = getCV();
$("#ex-locoid").val(0);
setCV(0);
$("#loco-info").html("Released Locomotive: " + currentCV);
acButton.data("acquired", false);
acButton.html('<span class="icon-circle-right"></span>');
toggleThrottleState(false);
}
}
});
// Switch ON/OFF power of the Command station
$("#power-switch").on("click", function () {
pb = $(this).is(":checked");
if (pb == true) {
writeToStream("1");
$("#power-status").html("On");
} else {
writeToStream("0");
$("#power-status").html("Off");
}
});
////////////////////////////////////
$("#v-throttle").slider({
orientation: "vertical",
min: 0,
max: 126,
disabled: true,
range: "max",
slide: function (event, ui) {
$("#speed-indicator").html(ui.value);
setSpeed(ui.value);
setSpeedofControllers();
},
});
/////////////////////////////////////////*/
knob = $(".rotarySwitch").rotaryswitch({
minimum: 0,
maximum: 126,
step: 2,
beginDeg: 210,
lengthDeg: 295,
minimumOverMaximum: true,
showMarks: true,
themeClass: "big light",
});
toggleKnobState($("#knobthrottle"), false);
knob.on("change", function () {
oldValue = getSpeed();
kval = knob.val();
$("#knob-value").html(kval);
setSpeed(kval);
// Below condition is to avoid infinite loop
// that triggers change() event indifinitely
if (oldValue != kval) {
setSpeedofControllers();
} else {
writeToStream(
"t 01 " + getCV() + " " + getSpeed() + " " + getDirection()
);
}
//console.log( "t 01 " + getCV() + " " + getSpeed() + " " + getDirection());
});
/////////////////////////////////////////////
// Speed (round) Slider allows user to change the speed of the locomotive
Tht = $("#circular-throttle").roundSlider({
width: 20,
radius: 116,
value: speed,
circleShape: "pie",
handleShape: "dot",
startAngle: 315,
lineCap: "round",
sliderType: "min-range",
showTooltip: true,
editableTooltip: false,
handleSize: "+18",
max: "126",
disabled: true,
update: function (slider) {
setSpeed(slider.value);
setSpeedofControllers();
//console.log("t 01 "+getCV()+" "+getSpeed()+" "+getDirection());
},
valueChange: function (slider) {
//setSpeed(slider.value);
//writeToStream("t 01 "+getCV()+" "+getSpeed()+" "+getDirection());
// console.log("This event is similar to 'update' event, in addition it will trigger even the value was changed through programmatically also.");
},
});
// Allows user to change the direction of the loco and STOP.
$(".dir-btn").on("click", function () {
if (getCV() != 0){
current = $(this);
dir = current.attr("aria-label");
$(".dir-btn").removeClass("selected");
current.addClass("selected", 200);
console.log(dir);
$(".dir-toggle").removeClass("forward backward stop");
$(".dir-toggle").addClass(dir);
// Do direction stuff here
switch (dir) {
case "forward": {
isStopped = false;
setDirection(1);
setSpeedofControllers();
writeToStream("t 01 " + getCV() + " " + getSpeed() + " 1");
break;
}
case "backward": {
isStopped = false;
setDirection(0);
setSpeedofControllers();
writeToStream("t 01 " + getCV() + " " + getSpeed() + " 0");
break;
}
case "stop": {
isStopped = true;
dir = getDirection();
setSpeed(0);
setSpeedofControllers();
writeToStream("t 01 " + getCV() + " 0 " + dir);
break;
}
}
}else{
console.log("No loco acquired");
}
});
$("#emergency-stop").on("click", function () {
if (getCV() != 0){
isStopped = true;
dir = getDirection();
setSpeed(0);
setSpeedofControllers();
writeToStream("t 01 " + getCV() + " -1 " + dir);
}
else{
console.log("No loco acquired");
}
});
// Hide/Show the Loco, Connect server fields (on top)
$("#button-hide").on("click", function () {
if ($(".details-panel").is(":visible")) {
$(".details-panel").hide();
$(this).css("top", 0);
$(this).html('<span class="icon-circle-down"></span>');
} else {
$(".details-panel").show();
$(this).html('<span class="icon-circle-up"></span>');
$(this).css("top", "-9px");
}
});
// PLUS button. Increases speed on Hold / Tap
var tId = 0;
$("#button-right")
.on("mousedown", function () {
event.stopImmediatePropagation();
tId = setInterval(function () {
var sp = getSpeed();
if (sp <= 125 && getDirection() != -1 && getCV() != 0) {
setSpeed(sp + speedStep);
setSpeedofControllers();
writeToStream(
"t 01 " + getCV() + " " + getSpeed() + " " + getDirection()
);
sp = 0;
}
}, 100);
})
.on("mouseup mouseleave", function () {
clearInterval(tId);
})
.on("click", function () {
event.stopImmediatePropagation();
var sp = getSpeed();
if (sp <= 125 && getDirection() != -1 && getCV() != 0) {
setSpeed(sp + speedStep);
setSpeedofControllers();
writeToStream(
"t 01 " + getCV() + " " + getSpeed() + " " + getDirection()
);
sp = 0;
}
});
// MINUS button. Decreases speed on Hold / Tap
var tId = 0;
$("#button-left")
.on("mousedown", function () {
event.stopImmediatePropagation();
tId = setInterval(function () {
var sp = getSpeed(sp);
if (sp >= 1 && getDirection() != -1 && getCV() != 0) {
setSpeed(sp - speedStep);
setSpeedofControllers();
writeToStream(
"t 01 " + getCV() + " " + getSpeed() + " " + getDirection()
);
sp = 0;
}
}, 100);
})
.on("mouseup mouseleave", function () {
clearInterval(tId);
})
.on("click", function () {
event.stopImmediatePropagation();
var sp = getSpeed(sp);
if (sp >= 1 && getDirection() != -1 && getCV() != 0) {
setSpeed(sp - speedStep);
setSpeedofControllers();
writeToStream(
"t 01 " + getCV() + " " + getSpeed() + " " + getDirection()
);
sp = 0;
}
});
// Functions buttons
// Send Instructions to generate command depends the type of Button (press/toggle)
var timer = 0;
$(document)
.on("mousedown", ".fn-btn", function () {
console.log($(this).val);
clickedBtn = $(this);
btnType = clickedBtn.data("type");
if (btnType == "press") {
timer = setInterval(function () {
// MOMENTARY HOLD ON
clickedBtn.attr("aria-pressed", "true");
generateFnCommand(clickedBtn);
console.log("PRESSED HOLD ==> " + clickedBtn.attr("name"));
}, 100);
}
})
.on("mouseup mouserelease", ".fn-btn", function () {
clearInterval(timer);
clickedBtn = $(this);
btnType = clickedBtn.data("type");
btnState = clickedBtn.attr("aria-pressed");
if (btnType == "press") {
// MOMENTARY HOLD OFF
clickedBtn.attr("aria-pressed", "false");
generateFnCommand(clickedBtn);
console.log("RELEASED HOLD ==> " + clickedBtn.attr("name"));
} else {
if (btnState == "false") {
// TOGGLE ON
clickedBtn.attr("aria-pressed", "true");
generateFnCommand(clickedBtn);
console.log("TOGGLE ON ==> " + clickedBtn.attr("name"));
} else {
// TOGGLE OFF
clickedBtn.attr("aria-pressed", "false");
generateFnCommand(clickedBtn);
console.log("TOGGLE OFF ==> " + clickedBtn.attr("name"));
}
}
});
// Hide/Show the Debug console
$("#console-toggle").on("click", function () {
pb = $(this).is(":checked");
if (pb == true) {
$("#debug-console").show();
setPreference("dbugConsole", true);
} else {
$("#debug-console").hide();
setPreference("dbugConsole", false);
}
});
// Send command written in console
$("#button-sendCmd").on("click", function () {
cmd = $("#cmd-direct").val();
writeToStream(cmd);
document.getElementById("cmd-direct").value = "";
});
// Clear the console log window
$("#button-clearLog").on("click", function () {
$("#log-box").html("");
});
// Function to toggle fullScreen viceversa
$("#fs-toggle").on("click", function () {
st = $(this).attr("state");
var elem = document.documentElement;
if (st == "ws") {
$(this).attr("state", "fs");
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.mozRequestFullScreen) {
/* Firefox */
elem.mozRequestFullScreen();
} else if (elem.webkitRequestFullscreen) {
/* Chrome, Safari and Opera */
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) {
/* IE/Edge */
elem.msRequestFullscreen();
}
} else {
$(this).attr("state", "ws");
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
/* Firefox */
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
/* Chrome, Safari and Opera */
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
/* IE/Edge */
document.msExitFullscreen();
}
}
});
//Handles navigation clicks
$("#throttle-nav").on("click", function () {
hideWindows();
$("#throttle-window").show();
$("#nav-close").trigger("click");
});
$("#loco-nav").on("click", function () {
hideWindows();
$("#loco-window").show();
$("#nav-close").trigger("click");
loadLocomotives();
});
$("#fn-map-nav").on("click", function () {
hideWindows();
$("#fn-map-window").show();
$("#nav-close").trigger("click");
setFunctionMaps();
//loadMapData("Default");
});
$("#settings-nav").on("click", function () {
hideWindows();
$("#settings-window").show();
$("#nav-close").trigger("click");
});
eventListeners();
/*
$("#settings-general").on('click', function(){
hideSettings();
$("#general-section").show();
});
$("#settings-storage").on('click', function(){
hideSettings();
$("#storage-section").show();
});*/
$("#settings-general").on("click", function () {
/*var target = $('#general-section');
if (target.length) {
$('#settings-panel').animate({
scrollTop: target.offset().top
}, 1000);
}*/
$("#general-section")[0].scrollIntoView(true);
});
$("#settings-storage").on("click", function () {
/*var target = $('#storage-section');
if (target.length) {
$('#settings-panel').animate({
scrollTop: target.offset().top
}, 1000);
}*/
$("#storage-section")[0].scrollIntoView(true);
});
$('#settings-app').on('click', function() {
$("#app-section")[0].scrollIntoView(true);
})
$(document).on("click", ".map-name", function () {
loadMapData($(this).attr("map-val"));
$("li.map-name").removeClass("active");
$(this).addClass("active");
});
// This allows user to delete currently selected Map
$(document).on("click", "#delete-map", function () {
selectedval = $("#cur-map-val").attr("cur-map");
if (selectedval != "Default") {
deleteFuncData(selectedval);
loadmaps();
setFunctionMaps();
loadMapData("Default");
$("#select-map").val("default").trigger("change");
$("li.map-name").removeClass("active");
$("li.map-name[map-val= 'Default']").addClass("active");
}
});
$(document).on("click", ".edit-cur-loco", function () {
cabdata = getStoredLocoData($(this).attr("data-loco"));
$("#loco-form")[0].reset();
$("#loco-form-content").css("display", "inline-block");
$(".add-loco-form .add-loco-head").html("Edit Locomotive");
$("#loco-submit").attr("loco-mode", "edit");
$("#loco-submit").attr("loco-id", $(this).attr("loco-id"));
$.each(cabdata, function (key, value) {
$("#loco-form").children().find("#"+key).val(value);
if(key=="map"){
$("#function-maps").autocomplete("search", value);
var menu = $("#function-maps").autocomplete("widget");
$(menu[0].children[0]).click();
}
});
});
});
function setFunctionMaps() {
const defaultMap = {
mname: "Default",
fnData: {},
}
const maps = [defaultMap, ...getMapData()];
$("#function-mappings").empty();
maps.forEach(map => {
const name = map.mname
$("#function-mappings").append(`<li class='map-name' map-val=${name}>${name}</li>`);
})
}
function hideWindows(){
$("#throttle-window").hide();
$("#loco-window").hide();
$("#fn-map-window").hide();
$("#settings-window").hide();
}
function hideSettings(){
$("#general-section").hide();
$("#storage-section").hide();
}
function credits() {
authors = ["Fred Decker","Mani Kumar","Matt"]
displayLog("Credits:")
console.log("Credits:")
for (i=0; i<authors.length; i++) {
displayLog(authors[i])
console.log(authors[i])
}
}
function eventListeners(){
var cmdDirect = document.getElementById("cmd-direct");
var exLocoID = document.getElementById("ex-locoid");
cmdDirect.addEventListener("keyup", function(event) {
if (event.key === "Enter") {
event.preventDefault();
// Trigger the button element with a click
$('#button-sendCmd').click();
}
});
exLocoID.addEventListener("keyup", function(event) {
if (event.key === "Enter") {
event.preventDefault();
// Trigger the button element with a click
$('#button-getloco').click();
}
})
}

View File

@@ -0,0 +1,34 @@
// This is default function definition
// New functions are based on this
// If no Map is loaded this will load ad default
var fnMasterData = {
"f0": [0, 0, "Head Light", 1], // Index => 0=state, 1=type (0-Toggle, 1-press),
"f1": [0, 1, "Bell" , 1], // 2=Label, 3= Available
"f2": [0, 1, "Horn", 1],
"f3": [0, 0, "F3" , 1],
"f4": [0, 0, "F4", 1],
"f5": [0, 0, "F5", 1],
"f6": [0, 0, "F6", 1],
"f7": [0, 0, "F7", 1],
"f8": [0, 0, "F8", 1],
"f9": [0, 0, "F9", 1],
"f10": [0, 0, "F10", 1],
"f11": [0, 0, "F11", 1],
"f12": [0, 0, "F12", 1],
"f13": [0, 0, "F13", 1],
"f14": [0, 0, "F14", 1],
"f15": [0, 0, "F15", 1],
"f16": [0, 0, "F16", 1],
"f17": [0, 0, "F17", 1],
"f18": [0, 0, "F18", 1],
"f19": [0, 0, "F19", 1],
"f20": [0, 0, "F20", 1],
"f21": [0, 0, "F21", 1],
"f22": [0, 0, "F22", 1],
"f23": [0, 0, "F23", 1],
"f24": [0, 0, "F24", 1],
"f25": [0, 0, "F25", 1],
"f26": [0, 0, "F26", 1],
"f27": [0, 0, "F27", 1],
"f28": [0, 0, "F28", 1],
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,420 @@
/*
Version 1.0.1
Copyright 2014 Red White Silver GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function($, window, document, undefined) {
var pluginName = 'rotaryswitch',
defaults = {
minimum: 0, // Minimal value
maximum: 12, // Maximum value
step: 1, // Step size
snapInMotion: true, // Snap to steps in motion
beginDeg: 0, // Start point in deg
lengthDeg: 360, // Length in deg
minimumOverMaximum: true, // Which value will used, if the the start and the end point at the same deg.
showInput: false, // Show input element
showMarks: false, // Show deg marks
themeClass: 'defaultTheme' // Theme class
};
function Plugin(element, options) {
this.element = $(element);
this.domElements = {};
this.htmlStructure = {
wrap: '<div class="rotaryswitchPlugin"></div>',
switchButton: '<div class="switch"></div>',
overlay: '<div class="overlay"></div>',
marks: '<div class="marks"></div>',
mark: '<div class="mark"></div>'
};
this.mousePosition = {x: -1, y: -1};
this.switchDeg = 0;
this.valueInPercent = 0;
this.value = 0;
this.steps = 0;
this.totalDeg = 0;
this.degPerStep = 0;
this.lastTriggeredValue = -1;
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.initialize();
}
Plugin.prototype = {
/**
* Initialze this plugin
* See inline comments for more details
*
* @param none
* @return none
*/
initialize: function() {
// Save the needed jquery DOM elements
this.domElements = {
// Wrap the input element and save the parent as main element and add theme class:
main: this.element.wrap(this.htmlStructure.wrap).parent().addClass(this.options.themeClass),
switchButton: $(this.htmlStructure.switchButton),
overlay: $(this.htmlStructure.overlay)
};
// Append addional dom elements:
this.domElements.main.append([this.domElements.switchButton, this.domElements.overlay]);
// Calculate the length (Maximum - minimum)
this.steps = Math.abs(this.options.maximum - this.options.minimum);
// Calculate the total deg length
this.totalDeg = this.options.lengthDeg;
// Calculate deg per step
this.degPerStep = this.totalDeg / this.steps;
// Listen to some necessary events
this.domElements.main.on('mousedown', $.proxy(this.onMouseDown, this));
this.domElements.main.on('touchstart', $.proxy(this.onTouchStart, this));
this.element.on('change', $.proxy(this.onChangeElementValue, this));
this.readValueFromInput(); // Get the value from the input element
this.rotateSwitch(); // Rotate the switch
// Show marks if wished
if (this.options.showMarks === true) {
this.renderMarks();
}
// Show the input element if wished
if (this.options.showInput === false) {
this.element.hide();
}
},
/**
* Adds marks for each step
*
* @param none
* @return none
*/
renderMarks: function() {
var i=0,
len = this.steps / this.options.step,
deg = this.options.beginDeg,
degPerStep = this.degPerStep * this.options.step,
marks = $(this.htmlStructure.marks);
for (; i < len; i += 1) {
deg += degPerStep;
var mark = $(this.htmlStructure.mark).css({'transform': 'rotate('+deg+'deg) translate(0, -'+ (this.domElements.main.width()/2 + (this.domElements.main.width()*0.1)) +'px)'});
marks.append(mark);
}
this.domElements.main.append(marks);
},
/**
* On mouse down event handler
* Save the mouse position (x, y) to the object this.mousePosition
* call the method this.startHandling
*
* @param event jquery mouse event
* @return none
*/
onMouseDown: function(event) {
event.preventDefault();
this.mousePosition.x = event.pageX;
this.mousePosition.y = event.pageY;
this.startHandling();
},
/**
* On mouse up event handler
* call the method this.stopHandling
*
* @param event jquery mouse event
* @return none
*/
onMouseUp: function(event) {
this.stopHandling();
},
/**
* On mouse move event handler
* Save the mouse position (x, y) to the object this.mousePosition
* call the method this.calculateSwitchDeg
* call the method this.calculateValueByDeg
* call the method this.rotateSwitch
* call the method this.setValueToInput
*
* @param event jquery mouse event
* @return none
*/
onMousemove: function(event) {
event.preventDefault();
this.mousePosition.x = event.pageX;
this.mousePosition.y = event.pageY;
this.calculateSwitchDeg();
this.calculateValueByDeg();
this.rotateSwitch();
this.setValueToInput();
},
/**
* On touch start event handler
* Identical width this.onMouseDown
*
* @param event jquery touch event
* @return none
* @see this.onMouseDown
*/
onTouchStart: function(event) {
event.preventDefault();
this.mousePosition.x = event.originalEvent.targetTouches[0].pageX;
this.mousePosition.y = event.originalEvent.targetTouches[0].pageY;
this.startHandling();
},
/**
* On touch end event handler
* Identical width this.onMouseUp
*
* @param event jquery touch event
* @return none
* @see this.onMouseUp
*/
onTouchEnd: function(event) {
this.stopHandling();
},
/**
* On touch move event handler
* Identical width this.onMousemove
*
* @param event jquery touch event
* @return none
* @see this.onMousemove
*/
onTouchMove: function(event) {
event.preventDefault();
this.mousePosition.x = event.originalEvent.targetTouches[0].pageX;
this.mousePosition.y = event.originalEvent.targetTouches[0].pageY;
this.calculateSwitchDeg();
this.calculateValueByDeg();
this.rotateSwitch();
this.setValueToInput();
},
/**
* On change input element event handler
* If event not triggered by plugin:
* Call the method this.readValueFromInput
* Call the method this.rotateSwitch
*
* @param event jquery event
* @return none
*/
onChangeElementValue: function(event) {
if (!event.plugin || event.plugin !== this) {
this.readValueFromInput();
this.rotateSwitch();
}
},
/**
* Triggered by mouse or touch begin event
* Calls some methods
* Listen to some events
* Add class 'active' to the main DOM element (this.domElements.main)
*
* @param none
* @return none
*/
startHandling: function() {
this.calculateSwitchDeg();
this.calculateValueByDeg();
this.rotateSwitch();
this.setValueToInput();
$(document).on('mouseup', $.proxy(this.onMouseUp, this));
$(document).on('mousemove', $.proxy(this.onMousemove, this));
$(document).on('touchend', $.proxy(this.onTouchEnd, this));
$(document).on('touchmove', $.proxy(this.onTouchMove, this));
this.domElements.main.addClass('active');
},
/**
* Triggered by mouseup or touchend event
* Stop listen to some events
* Call method this.rotateSwitch
* Remove class 'active' from the main DOM element (this.domElements.main)
*
* @param none
* @return none
*/
stopHandling: function() {
$(document).off('mouseup', $.proxy(this.onMouseUp, this))
.off('mousemove', $.proxy(this.onMousemove, this))
.off('touchend', $.proxy(this.onTouchEnd, this))
.off('touchmove', $.proxy(this.onTouchMove, this));
this.rotateSwitch(true);
this.domElements.main.removeClass('active');
},
/**
* Calculate the switch deg by the element position and the mouse position
* Stores the switch deg in this.switchDeg
* @param none
* @return none
*/
calculateSwitchDeg: function() {
var offset = this.domElements.main.offset(),
radians = Math.atan2(this.mousePosition.x - (offset.left + (this.domElements.main.width()/2)), this.mousePosition.y - (offset.top + (this.domElements.main.height()/2)));
if (this.mousePosition.x !== -1) {
this.switchDeg = (radians * (180 / Math.PI) * -1) + 180;
}
},
/**
* Calculate the value by deg
* Stores the in percent in this.valueInPercent
* Stores the value in this.value
* @param none
* @return none
*/
calculateValueByDeg: function() {
var range = this.options.maximum - this.options.minimum;
if (this.switchDeg - this.options.beginDeg > 0) {
this.valueInPercent = (this.switchDeg - this.options.beginDeg) / this.totalDeg;
} else {
this.valueInPercent = (this.switchDeg - this.options.beginDeg + 360) / this.totalDeg;
}
if (this.valueInPercent > 1) {
if (this.valueInPercent > (((360 / this.totalDeg)-1) / 2)+1 ) {
this.valueInPercent = 0;
} else {
this.valueInPercent = 1;
}
}
this.value = ~~ (((((range * this.valueInPercent) < 0) ? -0.5 : 0.5) + ((range * this.valueInPercent) / this.options.step))) * this.options.step;
this.value += this.options.minimum;
if (this.options.lengthDeg === 360 && (this.value === this.options.minimum || this.value === this.options.maximum)) {
if (this.options.minimumOverMaximum === true) {
this.value = this.options.minimum;
} else {
this.value = this.options.maximum;
}
}
},
/**
* Rotate the switch with css transform
* snap to the the next rounded value if the parameter snap is true
* @param snap boolean
* @return none
*/
rotateSwitch: function(snap) {
var deg = 0,
exactDeg = (this.valueInPercent * this.totalDeg),
roundedDeg = ((this.value / this.steps) * this.totalDeg) - (this.options.minimum * this.degPerStep),
difference = Math.abs(Math.abs(exactDeg) - Math.abs(roundedDeg)),
rotateString = '';
if (snap === true || (this.options.snapInMotion === true && difference < this.degPerStep / 6)) {
if (roundedDeg + this.options.beginDeg < 360) {
deg = (roundedDeg + this.options.beginDeg);
} else {
deg = roundedDeg + this.options.beginDeg - 360;
}
} else {
if (exactDeg + this.options.beginDeg < 360) {
deg = (exactDeg + this.options.beginDeg);
} else {
deg = exactDeg + this.options.beginDeg - 360;
}
}
rotateString = ['rotate(', deg, 'deg)'].join('');
this.domElements.switchButton.css({
'transform': rotateString,
'-webkit-transform': rotateString,
'-moz-transform': rotateString,
'-o-transform': rotateString,
'-ms-transform': rotateString
});
},
/**
* Read the valur from the input element
* If no value available, this.options.minimum is used
* @param snap boolean
* @return none
*/
readValueFromInput: function() {
var elementValue = parseInt(this.element.val(), 10);
if (isNaN(elementValue) === true) {
this.value = this.options.minimum;
} else {
this.value = Math.max(this.options.minimum, elementValue);
this.value = Math.min(this.options.maximum, this.value);
}
this.value -= this.options.minimum;
this.valueInPercent = this.value / (this.options.maximum - this.options.minimum);
},
/**
* Set the value to the input element
* and trigger the change event on the input element
* @param none
* @return none
*/
setValueToInput: function() {
if (this.value !== this.lastTriggeredValue) {
this.lastTriggeredValue = this.value;
this.element.val(this.value).trigger({type: 'change', plugin: this});
}
}
};
$.fn[pluginName] = function(options) {
return this.each(function() {
if (!$.data(this, 'plugin_' + pluginName)){
$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
}
});
};
}(jQuery, window, document));

View File

@@ -0,0 +1,34 @@
$(document).ready(function(){
let deferredPrompt;
const addBtn = document.querySelector('.add-button');
const removeBtn = document.querySelector('.installed-label');
addBtn.style.display = 'none';
removeBtn.style.display = 'block';
window.addEventListener('beforeinstallprompt', (e) => {
removeBtn.style.display = 'none';
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// Update UI to notify the user they can add to home screen
addBtn.style.display = 'block';
addBtn.addEventListener('click', (e) => {
// hide our user interface that shows our A2HS button
//addBtn.style.display = 'none';
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the prompt, app installed');
addBtn.style.display = 'none';
removeBtn.style.display = 'block';
} else {
console.log('User dismissed the prompt');
}
deferredPrompt = null;
});
});
});
});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,586 @@
/* This is part of the DCC++ EX Project for model railroading and more.
For licence information, please see index.html.
For more information, see us at dcc-ex.com.
storageController.js
Manages the setting storage capabilities
*/
$(document).ready(function(){
// This is displays message about Local storage Support of the Local browser
if (typeof Storage !== "undefined") {
console.log("Your browser supports Local Storage");
} else {
console.log("Sorry! Your browser does not supporting Local Storage");
}
// Opens NEW MAP window with all fields empty
$("#add-map").on("click", function () {
$("#save-fn-map").attr("mode", "new");
$(".fn-heading").html("New Mapping");
showBtnConfig({ mname: "", fnData: fnMasterData });
});
// This will Load buttons on selecting a map from the select box
$("#select-map").change(function () {
selectedval = $(this).val();
if (selectedval != "default") {
data = getStoredMapData(selectedval);
loadButtons(data);
} else {
loadButtons({ mname: "default", fnData: fnMasterData });
}
});
// Opens MAP window with all fields filled from the selected map that allows editing Map
$(document).on("click", "#edit-cur-map", function () {
$("#save-fn-map").attr("mode", "edit");
$(".fn-heading").html("Edit Mapping");
selectedval = $("#cur-map-val").attr("cur-map");
if (selectedval != "Default") {
data = getStoredMapData(selectedval);
showBtnConfig(data);
} else {
alert("Cannot edit Default mapping!");
}
//showBtnConfig();
});
// Closes MAP window on clicking X icon
$("#close-model").on("click", function () {
$("#fnModal").hide();
});
//This will check for Save mode (NEW MAP / EDIT MAP) and delegate the functionality
$("#save-fn-map").on("click", function () {
mode = $(this).attr("mode");
// alert(mode); // debug line
if (mode == "new") {
addNewMap();
} else {
editMap();
}
});
//Allows user to download the selected map in .JSON format
$(document).on("click", "#export-cur-map", function () {
map = $("#cur-map-val").attr("cur-map");
if (map != "default") {
downloadMapData(map);
} else {
alert("Please select Custom map");
}
});
// This remove whole exthrottle app data but with confirmation
$("#wipe-map").on("click", function () {
var r = confirm("Are you sure on deletion?");
if (r == true) {
window.localStorage.removeItem("mapData");
console.log("!!!!!!MAPS WIPED!!!!!!");
loadmaps();
}
});
// This allows user to download whole exthrottle app data
$("#export-all-maps").on("click", function () {
exportMapData();
});
// This allows user to upload previously downloaded Map (JSON format must adhere)
$(document).on("click", "#import-map", function (e) {
e.preventDefault();
$("#map-upload").trigger("click");
});
// This part with above which is responsible for actual file upload for MAP
$("#map-upload").on("change", function (e) {
var file = e.target.files[0];
var field = $(this);
var freader = new FileReader();
freader.onload = function (evt) {
data = JSON.parse(evt.target.result);
setMapData(data);
field.val("");
};
freader.readAsText(file);
});
// This is responsible for Cab upload
$("#cabs-upload").on("change", function (e) {
var file = e.target.files[0];
var field = $(this);
var freader = new FileReader();
freader.onload = function (evt) {
data = JSON.parse(evt.target.result);
importLocoData(data);
field.val("");
};
freader.readAsText(file);
});
// This allows user to upload previously downloaded MAP DATA (JSON format must adhere)
$(document).on("click", "#import-all-maps", function (e) {
e.preventDefault();
//$("#appdata-upload").attr("mode", "mapData");
$("#maps-upload").trigger("click");
});
// This part of above which is responsible for actual file upload for MAP DATA
$("#maps-upload").on("change", function (e) {
var file = e.target.files[0];
var field = $(this);
var freader = new FileReader();
freader.onload = function (evt) {
data = JSON.parse(evt.target.result);
importMapdata(data);
field.val("");
};
freader.readAsText(file);
});
// Set height of throttle container according to functions panel
$(".throttle-container").height($(".functionKeys").first().height());
//Temparory function Shows APP DATA in console
$("#loco-info").on("click", function () {
console.log(getMapData());
});
//Functions for the storage page in settings
$("#backup-app-settings").on("click", function () {
exportAppData();
});
// This allows user to upload previously downloaded APP DATA (JSON format must adhere)
$("#restore-app-settings").on("click", function (e) {
e.preventDefault();
$("#app-upload").trigger("click");
});
// This part of above which is responsible for actual file upload for APP DATA
$("#app-upload").on("change", function (e) {
var file = e.target.files[0];
var field = $(this);
var freader = new FileReader();
freader.onload = function (evt) {
data = JSON.parse(evt.target.result);
importAppdata(data);
field.val("");
};
freader.readAsText(file);
});
$("#wipe-app-settings").on("click", function () {
var r = confirm("Are you sure on deletion?");
if (r == true) {
window.localStorage.removeItem("mapData");
window.localStorage.removeItem("cabList");
window.localStorage.removeItem("userpref");
console.log("!!!!!DATA IS WIPED!!!!!!");
loadmaps();
}
});
});
// Load all maps to select box
function loadmaps(){
$("#select-map").empty();
$("#select-map").append($("<option />").val("default").text("Default"));
getMapData().forEach(map => {
$("#select-map").append($("<option />").val(map.mname).text(map.mname));
})
}
// Load button layout of selected Map
function loadButtons(data){
$("#fn-wrapper").empty();
$.each(data.fnData, function(key, value){
isPressed = value[0] != 0 ? true : false;
btnType = value[1] != 0 ? "press" : "toggle";
if(value[3]==1){
$("#fn-wrapper").append(
"<div class='fn-button form-group field-button-fn'> <button class='btn-default btn fn-btn "+btnType+"' data-type='"+
btnType+"' aria-pressed='"+isPressed+"' name='"+key+"' id='"+key+"'>"+
value[2]+"</button>"
+"</div>");
}
});
}
// Show the Custom Map fields inside Custom map window while adding and editing a Map.
function showBtnConfig(data){
$("#fnModal").show();
$('#fnModal').css({"top":"7%", "left": "18%"});
$('#fnModal').draggable();
$("#fnModal .fn-modal-content").empty();
$("#fnModal .fn-modal-content").append('<div class="row header-row"><div class="column-2 header-col func-title">Map Name</div> <div class="column-5 header-col"><input type="text" class="fn-input" id="map-name" value="'+data.mname+'"/></div> <div class="column-3 header-col"></div></div>');
$("#fnModal .fn-modal-content").append('<div class="row header-row"><div class="column-1 header-col">Function</div> <div class="column-4 header-col">Label</div> <div class="column-3 header-col">Button Type</div><div class="column-2 header-col">Visibility</div></div>');
$.each(data.fnData, function(key, value){
isPressed = value[0] != 0 ? true : false;
btnType = value[1] != 0 ? "press" : "toggle";
btnpress = value[1] == 1 ? "checked" : "";
btnToggle = value[1] == 0 ? "checked" : "";
fvisible = value[3] == 1 ? "checked" : "";
$("#fnModal .fn-modal-content").append('<div class="row edit-row" id="'+key+'">'+
'<div class="column-1 func-title">'+key +'</div>'+
'<div class="column-4"> <input class="fn-input" name="'+key+'" id="'+key+'" value="'+value[2]+'"/>'+
'<span class="focus-border"><i></i></span>'+
'</div>'+
'<div class="fn-radio column-3" name="'+key+'Type" id="'+key+'Type">'+
'<input type="radio" id="'+key+'press" name="btn'+key+'Type" value="press" '+btnpress+'/>'+
'<label for="'+key+'press">Momentary</label> &nbsp;'+
'<input type="radio" id="'+key+'toggle" name="btn'+key+'Type" value="toggle" '+btnToggle+'/>'+
'<label for="'+key+'toggle">Latching</label>'+
'</div>'+
'<div class="fn-chkbox column-2" name="'+key+'Visible" id="'+key+'Type">'+
'<input type="checkbox" id="'+key+'Visible" name="'+key+'Visible" '+fvisible+'/>'+
'<label for="'+key+'Visible">Show</label> &nbsp;'+
'</div>'+
'</div>');
});
}
// Saves New Map data to Local storage
function addNewMap(){
customFnData = {};
$(".edit-row").each(function (val) {
key = $(this).find(".func-title").text();
btnType = $(this).children().find("input[type='radio']:checked").val() == "press" ? 1 : 0;
fnvisible = $(this).children().find("input[type='checkbox']").prop("checked") ? 1 : 0;
arr = [0, btnType, $(this).children().find(".fn-input").val(), fnvisible];
customFnData[key] = arr;
});
mapName = $.trim($("#map-name").val());
if (!ifExists(mapName)) {
if (mapName) {
// Send data to store in Local storage
setMapData({ mname: mapName, fnData: customFnData });
$("#fnModal").hide();
setFunctionMaps();
alert("Map Saved Sucessfully");
} else {
alert("Name is missing!!");
}
} else {
alert("Map with the Name already exists!! Please change the Map name..");
}
}
// Saves Edited Map data to local storage
function editMap(){
customFnData = {};
$(".edit-row").each(function(val){
key = $(this).find(".func-title").text();
btnType = $(this).children().find("input[type='radio']:checked").val() == "press" ? 1 : 0;
fnvisible = $(this).children().find("input[type='checkbox']").prop('checked') ? 1 : 0;
arr = [ 0, btnType, $(this).children().find(".fn-input").val(), fnvisible ];
customFnData[key] = arr;
});
mapName = $.trim($("#map-name").val());
if (mapName) {
setMapData({ mname: mapName, fnData: customFnData });
$("#fnModal").hide();
setFunctionMaps();
loadMapData(mapName);
alert("Map Saved Sucessfully");
} else {
alert("Name is missing!!");
}
}
//*** Saves given data into Local storage**/
// Create new data object if one does not exist
// Verify if the given Map data already exists and replace it
// Or Create new map data and inserts it into local storage object
// Finally Saves the Data into Local storage */
function setMapData(mapdata) {
if (typeof Storage === "undefined") {
return;
}
const smapdata = getMapData()
if (ifExists(mapdata.mname)) {
smapdata.find(function (item, i) {
if (item.mname == mapdata.mname) {
item.fnData = mapdata.fnData;
}
});
} else {
smapdata.push(mapdata);
}
window.localStorage.setItem('mapData', JSON.stringify(smapdata));
loadmaps();
setFunctionMaps();
loadMapData(mapdata.mname);
$("#select-map").val(mapdata.mname).trigger("change");
}
//Returns the Map data of given Map name
function getStoredMapData(name){
const data = getMapData();
if(data !=null){
return data.find(function(item, i){
if(item.mname == name){
return item.fnData;
}
});
}else{
return null;
}
}
//Download the Map data of given Map name
function downloadMapData(mapName){
data = JSON.stringify(getStoredMapData(mapName));
const a = document.createElement("a");
const file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = mapName+".json";
a.click();
}
//Delete the Map data of given Map name
function deleteFuncData(name){
var r = confirm("Are you sure on deletion?");
if (r == true) {
if (typeof(Storage) !== "undefined") {
curmapdata = [];
const data = getMapData()
if(!data){
alert("No Data stored");
}else{
data.find(function(item, i){
if(item.mname != name){
curmapdata.push(item);
}
});
window.localStorage.setItem('mapData', JSON.stringify(curmapdata));
}
}
}
}
/**
* Returns the Map data of ExWebThrottle
* @return {[]}
*/
function getMapData(){
if (typeof Storage === "undefined") {
return [];
}
const localMapData = JSON.parse(window.localStorage.getItem("mapData"));
return localMapData || []
}
// Returns boolen if the given Map exists in local storage
function ifExists(name) {
const data = getMapData()
const existingItem = data.find((item) => item.mname === name);
return !!existingItem;
}
//Download all Maps of EXthrottle
function exportMapData() {
const data = getMapData()
const a = document.createElement("a");
const file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = "ListofMaps.json";
a.click();
}
function importMapdata(data){
if(data){
window.localStorage.setItem('mapData', JSON.stringify(data));
loadmaps();
$("#select-map").val('default').trigger("change");
setFunctionMaps();
}
}
//Import the Locomotives List data
function importLocoData(data) {
if (data) {
window.localStorage.setItem("cabList", JSON.stringify(data));
loadLocomotives();
locoList = getLocoList();
}
}
/*************************************************/
/********** Locomotives Data functions ***********/
/*************************************************/
function saveLocomotive(data){
locodata = $(data).arrayToJSON();
if (typeof Storage !== "undefined") {
curCabList = [];
cabData = JSON.parse(window.localStorage.getItem("cabList"));
if (!cabData) {
curCabList.push(locodata);
window.localStorage.setItem("cabList", JSON.stringify(curCabList));
return true;
} else {
cabData.push(locodata);
window.localStorage.setItem("cabList", JSON.stringify(cabData));
return true;
}
}
return false;
}
function saveEditedLocomotive(data, id){
locodata = $(data).arrayToJSON();
if (typeof Storage !== "undefined") {
cabData = JSON.parse(window.localStorage.getItem("cabList"));
cabData.find(function (item, i) {
if (i == id) {
cabData[i] = locodata;
}
});
window.localStorage.setItem("cabList", JSON.stringify(cabData));
}
}
function ifLocoExists(name) {
data = JSON.parse(window.localStorage.getItem("cabList"));
found = false;
if (data != null) {
data.find(function (item, i) {
if (item.name == name) {
found = true;
}
});
return found;
}
return found;
}
// Returns the AppData of ExWebThrottle
function getLocoList(){
if (typeof Storage !== "undefined") {
return JSON.parse(window.localStorage.getItem("cabList"));
}else{
return [];
}
}
//Download the Locomotives List data
function downloadCabData(){
data = JSON.stringify(getLocoList());
const a = document.createElement("a");
const file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = "CabList.json";
a.click();
}
// Returns the LocoData of ExWebThrottle
function getStoredLocoData(name) {
console.log(name);
data = JSON.parse(window.localStorage.getItem("cabList"));
if (data != null) {
return data.find(function (item, i) {
if (item.name == name) {
return item;
}
});
} else {
return null;
}
}
/********************************************/
/************** Preferences ***************/
/********************************************/
// Get a given user preference
function getPreference(pref){
if (window.localStorage.getItem("userpref") != null) {
curpref = JSON.parse(window.localStorage.getItem("userpref"));
return curpref[pref];
} else {
return null;
}
}
// Set a given user preference
function setPreference(pref, val){
if (window.localStorage.getItem("userpref") != null){
curpref = JSON.parse(window.localStorage.getItem("userpref"));
}else{
curpref = {};
}
curpref[pref] = val;
setUserPreferences(curpref);
}
// Store user preferences in local storage
function setUserPreferences(pref){
if (typeof(Storage) !== "undefined") {
window.localStorage.setItem("userpref", JSON.stringify(pref));
}
}
function getUserPreferences() {
if (typeof Storage !== "undefined") {
return JSON.parse(window.localStorage.getItem("userpref"));
}else{
return [];
}
}
function importPrefData(data) {
if (data) {
window.localStorage.setItem("userpref", JSON.stringify(data));
}
}
function exportAppData(){
const jsonObj = [
{maps: getMapData()},
{locos: getLocoList()},
{preferences: getUserPreferences()}
]
const data = JSON.stringify(jsonObj);
const a = document.createElement("a");
const file = new Blob([data], { type: "application/json" });
a.href = URL.createObjectURL(file);
a.download = "AppData.json";
a.click();
}
function importAppdata(data){
importMapdata(data[0]["maps"]);
importLocoData(data[1]["locos"]);
importPrefData(data[2]["preferences"]);
setThrottleScreenUI();
}
(function ($) {
$.fn.arrayToJSON = function () {
var o = {};
$.each($(this), function () {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || "");
} else {
o[this.name] = this.value || "";
}
});
return o;
};
}
)(jQuery);