diff --git a/JastUtils.js b/JastUtils.js
new file mode 100644
index 0000000..a80e9cc
--- /dev/null
+++ b/JastUtils.js
@@ -0,0 +1,82 @@
+class JastUtils {
+ static getCurrentValue(stock, exchangeData) {
+ let currentValue = "-";
+ if (stock.current) {
+ currentValue = stock.current;
+ if (
+ exchangeData &&
+ stock.tradeCurrency &&
+ stock.displayCurrency &&
+ stock.tradeCurrency !== stock.displayCurrency
+ ) {
+ const exchange = exchangeData.find(
+ (exchange) =>
+ exchange.from === stock.tradeCurrency &&
+ exchange.to === stock.displayCurrency
+ );
+ if (exchange) {
+ currentValue = currentValue * exchange.rate;
+ }
+ }
+ currentValue = currentValue.toFixed(2);
+ }
+ return currentValue;
+ }
+
+ static getCurrency(stock, exchangeData, config) {
+ let currency = config.defaultCurrency;
+ if (stock.displayCurrency) {
+ const exchange = exchangeData.find(
+ (exchange) =>
+ exchange.from === stock.tradeCurrency &&
+ exchange.to === stock.displayCurrency
+ );
+ if (exchange) {
+ currency = stock.displayCurrency;
+ } else if (stock.tradeCurrency) {
+ currency = stock.tradeCurrency;
+ }
+ }
+ return currency;
+ }
+
+ static getStockChange(stock) {
+ if (stock.current && stock.last) {
+ return (((stock.current - stock.last) / stock.last) * 100).toFixed(1);
+ } else {
+ return 0;
+ }
+ }
+
+ static getDepotGrowth(config, exchangeData) {
+ let growth = 0;
+ let errors = false;
+ config.stocks.forEach((stock) => {
+ if (stock.current && stock.last) {
+ let change =
+ stock.current * stock.quantity - stock.last * stock.quantity;
+
+ if (
+ stock.tradeCurrency &&
+ stock.tradeCurrency !== config.defaultCurrency
+ ) {
+ const exchange = exchangeData.find(
+ (exchange) =>
+ exchange.from === stock.tradeCurrency &&
+ exchange.to === stock.displayCurrency
+ );
+ if (exchange) {
+ change = change * exchange.rate;
+ } else {
+ errors = true;
+ }
+ } else {
+ errors = true;
+ }
+ growth = growth + change;
+ }
+ });
+
+ return { value: growth.toFixed(2), errors };
+ }
+}
diff --git a/MMM-Jast.css b/MMM-Jast.css
index d925f13..b2aa27e 100644
--- a/MMM-Jast.css
+++ b/MMM-Jast.css
@@ -1,53 +1,56 @@
@keyframes tickerh {
0% {
- transform: translate3d(0, 0, 0);
+ transform: translate(0, 0);
}
100% {
- transform: translate3d(-100%, 0, 0);
+ transform: translate(-100%, 0);
}
}
-.ticker-wrap {
+.jast-wrapper {
overflow: hidden;
}
-.ticker-wrap.vertical {
- height: 26px;
-}
-.ticker-wrap.horizontal {
- padding-left: 100%;
+.jast-wrapper.horizontal {
+ margin: 0 auto;
+ white-space: nowrap;
}
-.ticker-wrap ul {
+.jast-wrapper ul {
padding: 0;
margin: 0;
list-style-type: none;
+}
+.jast-wrapper.vertical {
+ height: 26px;
+}
+.jast-wrapper.vertical > ul {
+ animation-name: tickerv;
animation-iteration-count: infinite;
animation-timing-function: cubic-bezier(1, 0, 0.5, 0);
}
-.ticker-wrap.vertical ul {
- animation-name: tickerv;
+.jast-wrapper.horizontal .jast-hticker {
+ margin: 0 auto;
+ white-space: nowrap;
+ overflow: hidden;
+ position: absolute;
}
-.ticker-wrap.horizontal ul {
- padding-right: 100%;
+
+.jast-wrapper.horizontal span.jast-tickerframe {
display: inline-block;
- white-space: nowrap;
- animation-iteration-count: infinite;
- animation-timing-function: linear;
- animation-name: tickerh;
+ padding-left: 100%;
}
-.ticker-wrap.horizontal ul li:before {
+
+.jast-wrapper.horizontal span.jast-stock:before {
content: "\2022";
margin-right: 0.4em;
+ margin-left: 0.4em;
}
-.ticker-wrap ul li {
+.jast-wrapper .jast-stock {
font-size: 18px;
line-height: 26px;
}
-.ticker-wrap.horizontal ul li {
- display: inline-block;
- padding: 0 0.3rem;
-}
-.ticker-wrap ul li span.high {
+
+.jast-wrapper .jast-stock span.high {
color: green;
}
-.ticker-wrap ul li span.low {
+.jast-wrapper .jast-stock span.low {
color: red;
}
diff --git a/MMM-Jast.js b/MMM-Jast.js
index 6ec45a8..4a01aef 100644
--- a/MMM-Jast.js
+++ b/MMM-Jast.js
@@ -1,91 +1,72 @@
"use strict";
Module.register("MMM-Jast", {
- result: {},
defaults: {
debug: false,
+ header: null,
updateIntervalInSeconds: 1800,
requestIntervalInSeconds: 62,
- fadeSpeedInSeconds: 3.5,
+ fadeSpeedInSeconds: 3.5, // Higher value: vertical -> faster // horizontal -> slower
stocks: [
{ name: "BASF", symbol: "BAS.DE" },
{ name: "SAP", symbol: "SAP.DE" },
{ name: "Henkel", symbol: "HEN3.DE" },
{ name: "AbbVie", symbol: "4AB.DE" },
- { name: "Alibaba", symbol: "BABA", tradeCurrency: "USD", displayCurrency: "EUR", quantity: 10 },
+ {
+ name: "Alibaba",
+ symbol: "BABA",
+ tradeCurrency: "USD",
+ displayCurrency: "EUR",
+ quantity: 10
+ }
],
defaultCurrency: "EUR",
baseURL: "https://www.alphavantage.co/",
apiKey: "",
scroll: "vertical",
- maxWidth: "300px",
- showDepotGrowth: false,
+ //maxWidth: "300px",
+ showDepotGrowth: false
},
- getStyles: function () {
+ getStyles() {
return ["MMM-Jast.css"];
},
- getTranslations: function () {
+ getScripts() {
+ return [this.file("JastUtils.js")];
+ },
+
+ getTranslations() {
return {
en: "translations/en.json",
- de: "translations/de.json",
+ de: "translations/de.json"
+ };
+ },
+
+ getTemplate() {
+ return "templates/MMM-Jast.njk";
+ },
+
+ getTemplateData() {
+ return {
+ config: this.config,
+ exchangeData: this.exchangeData,
+ utils: JastUtils
};
},
- start: function () {
- this.exchangeData = {};
+ getHeader() {
+ return this.config.header;
+ },
+
+ start() {
+ this.exchangeData = [];
this.getExchangeRate();
this.getStocks();
this.scheduleUpdate();
},
- getDom: function () {
- this.setVerticalScrollingKeyframes();
- let app = document.createElement("div");
- let depotChange = 0;
- let entryCount = this.config.showDepotGrowth ? this.config.stocks.length + 1 : this.config.stocks.length;
- let ticker = `
`;
- app.innerHTML = ticker;
- return app;
- },
-
- scheduleUpdate: function () {
+ scheduleUpdate() {
const self = this;
setInterval(function () {
self.getExchangeRate();
@@ -93,38 +74,33 @@ Module.register("MMM-Jast", {
}, this.config.requestIntervalInSeconds * 1000);
},
- getStocks: function () {
+ getStocks() {
this.sendSocketNotification("GET_STOCKS", this.config);
},
- getExchangeRate: function () {
- this.sendSocketNotification("GET_EXCHANGE", { config: this.config, rates: this.exchangeData });
- },
-
- getColorClass: function (depotChange) {
- if (depotChange > 0) {
- return "high";
- } else if (depotChange < 0) {
- return "low";
- } else {
- return "neutral";
- }
+ getExchangeRate() {
+ this.sendSocketNotification("GET_EXCHANGE", {
+ config: this.config,
+ rates: this.exchangeData
+ });
},
- socketNotificationReceived: function (notification, payload) {
+ socketNotificationReceived(notification, payload) {
if (notification === "STOCK_RESULT") {
- let { symbol, current, last } = payload;
- let stockIndex = this.config.stocks.findIndex((stock) => stock.symbol === symbol);
- if (stockIndex >= 0) {
- this.config.stocks[stockIndex].current = current;
- this.config.stocks[stockIndex].last = last;
- this.config.stocks[stockIndex].lastUpdate = Date.now();
+ const { symbol, current, last } = payload;
+ const currentStock = this.config.stocks.find(
+ (stock) => stock.symbol === symbol
+ );
+ if (currentStock) {
+ currentStock.current = current;
+ currentStock.last = last;
+ currentStock.lastUpdate = Date.now();
this.updateDom();
}
} else if (notification === "EXCHANGE_RESULT") {
let { from, to, rate } = payload;
- this.exchangeData[from + to] = { from, to, rate };
- this.exchangeData[from + to].lastUpdate = Date.now();
+ this.exchangeData.push({ from, to, rate, lastUpdate: Date.now() });
+
this.updateDom();
}
},
@@ -142,12 +118,15 @@ Module.register("MMM-Jast", {
document.head.appendChild(vkf);
}
let innerText = `@keyframes tickerv {`;
- const itemCount = this.config.stocks.length > 0 ? this.config.stocks.length + offset : 1; // avoid divition by zero
+ const itemCount =
+ this.config.stocks.length > 0 ? this.config.stocks.length + offset : 1; // avoid divition by zero
const percentPerItem = 100 / itemCount;
for (let i = 0; i <= itemCount; i++) {
- innerText += ` ${i * percentPerItem}% { margin-top: ${i == 0 || i == itemCount ? "0" : i * -26 + "px"}; }`;
+ innerText += ` ${i * percentPerItem}% { margin-top: ${
+ i === 0 || i === itemCount ? "0" : i * -26 + "px"
+ }; }`;
}
innerText += `}`;
vkf.innerText = innerText;
- },
+ }
});
diff --git a/node_helper.js b/node_helper.js
index 5a3e16a..528e99a 100644
--- a/node_helper.js
+++ b/node_helper.js
@@ -1,30 +1,44 @@
-var NodeHelper = require("node_helper");
-var request = require("request");
+const NodeHelper = require("node_helper");
+const request = require("request");
module.exports = NodeHelper.create({
- start: function () {
+ start() {
console.log(`${this.name} helper method started...`);
},
- getRandomApiKey: function (config) {
- const key = config.apiKeys[Math.floor(Math.random() * config.apiKeys.length)];
- console.log("Using key", key);
- return key;
- },
-
- sendStocksRequest: function (config) {
+ sendStocksRequest(config) {
const self = this;
if (config.debug) {
- self.sendSocketNotification("STOCK_RESULT", { symbol: "BAS.DE", current: 50.2, last: 44.9 });
- self.sendSocketNotification("STOCK_RESULT", { symbol: "SAP.DE", current: 100.2, last: 100.9 });
- self.sendSocketNotification("STOCK_RESULT", { symbol: "HEN3.DE", current: 66.2, last: 70.9 });
- self.sendSocketNotification("STOCK_RESULT", { symbol: "BABA", current: 180.2, last: 188.9 });
+ self.sendSocketNotification("STOCK_RESULT", {
+ symbol: "BAS.DE",
+ current: 50.2,
+ last: 44.9
+ });
+ self.sendSocketNotification("STOCK_RESULT", {
+ symbol: "SAP.DE",
+ current: 100.2,
+ last: 100.9
+ });
+ self.sendSocketNotification("STOCK_RESULT", {
+ symbol: "HEN3.DE",
+ current: 66.2,
+ last: 70.9
+ });
+ self.sendSocketNotification("STOCK_RESULT", {
+ symbol: "BABA",
+ current: 180.2,
+ last: 188.9
+ });
return;
}
+
config.stocks.forEach((stock) => {
- if (!stock.lastUpdate || Date.now() - stock.lastUpdate >= config.updateIntervalInSeconds * 1000) {
+ if (
+ !stock.lastUpdate ||
+ Date.now() - stock.lastUpdate >= config.updateIntervalInSeconds * 1000
+ ) {
const url = `${config.baseURL}query?function=TIME_SERIES_DAILY&outputsize=compact&apikey=${config.apiKey}&symbol=${stock.symbol}`;
- request(url, { json: true }, (err, res, body) => {
+ request(url, { json: true }, (err, _res, body) => {
if (err) {
console.error(`Error requesting Stock data`);
}
@@ -35,7 +49,11 @@ module.exports = NodeHelper.create({
const last = parseFloat(values[1]["4. close"]);
console.log("Sending Stock result:", { symbol, current, last });
- self.sendSocketNotification("STOCK_RESULT", { symbol, current, last });
+ self.sendSocketNotification("STOCK_RESULT", {
+ symbol,
+ current,
+ last
+ });
} catch (err) {
console.error(`Error processing Stock response`, body);
}
@@ -44,20 +62,34 @@ module.exports = NodeHelper.create({
});
},
- sendExchangeRequest: function (payload) {
+ sendExchangeRequest(payload) {
const self = this;
const { config, rates } = payload;
if (config.debug) {
- self.sendSocketNotification("EXCHANGE_RESULT", { from: "USD", to: "EUR", rate: 0.923 });
+ self.sendSocketNotification("EXCHANGE_RESULT", {
+ from: "USD",
+ to: "EUR",
+ rate: 0.923
+ });
return;
}
config.stocks.forEach((stock) => {
- if (stock.tradeCurrency && stock.displayCurrency && stock.tradeCurrency != stock.displayCurrency) {
- const currentChange = rates ? rates[stock.tradeCurrency + stock.displayCurrency] : null;
+ if (
+ stock.tradeCurrency &&
+ stock.displayCurrency &&
+ stock.tradeCurrency !== stock.displayCurrency
+ ) {
+ const currentChange = rates.find(
+ (rate) =>
+ rate.from === stock.tradeCurrency &&
+ rate.to === stock.displayCurrency
+ );
+
if (
!currentChange ||
!currentChange.lastUpdate ||
- Date.now() - currentChange.lastUpdate >= config.updateIntervalInSeconds * 1000
+ Date.now() - currentChange.lastUpdate >=
+ config.updateIntervalInSeconds * 1000
) {
const url = `${config.baseURL}query?function=CURRENCY_EXCHANGE_RATE&from_currency=${stock.tradeCurrency}&to_currency=${stock.displayCurrency}&apikey=${config.apiKey}`;
request(url, { json: true }, (err, res, body) => {
@@ -65,12 +97,22 @@ module.exports = NodeHelper.create({
console.error(`Error requesting Exchange rate`);
}
try {
- const from = body["Realtime Currency Exchange Rate"]["1. From_Currency Code"];
- const to = body["Realtime Currency Exchange Rate"]["3. To_Currency Code"];
- const rate = parseFloat(body["Realtime Currency Exchange Rate"]["5. Exchange Rate"]);
+ const from =
+ body["Realtime Currency Exchange Rate"][
+ "1. From_Currency Code"
+ ];
+ const to =
+ body["Realtime Currency Exchange Rate"]["3. To_Currency Code"];
+ const rate = parseFloat(
+ body["Realtime Currency Exchange Rate"]["5. Exchange Rate"]
+ );
console.log("Sending Exchange result:", { from, to, rate });
- self.sendSocketNotification("EXCHANGE_RESULT", { from, to, rate });
+ self.sendSocketNotification("EXCHANGE_RESULT", {
+ from,
+ to,
+ rate
+ });
} catch (err) {
console.error(`Error processing Exchange response`, body);
}
@@ -80,7 +122,7 @@ module.exports = NodeHelper.create({
});
},
- socketNotificationReceived: function (notification, payload) {
+ socketNotificationReceived(notification, payload) {
if (notification === "GET_STOCKS") {
this.sendStocksRequest(payload);
} else if (notification === "GET_EXCHANGE") {
@@ -88,5 +130,5 @@ module.exports = NodeHelper.create({
} else {
console.warn(`${notification} is invalid notification`);
}
- },
+ }
});
diff --git a/templates/HorizontalScrollStyle.njk b/templates/HorizontalScrollStyle.njk
new file mode 100755
index 0000000..e2b12bb
--- /dev/null
+++ b/templates/HorizontalScrollStyle.njk
@@ -0,0 +1,11 @@
+{% macro render(config) %}
+
+{% endmacro %}
diff --git a/templates/HorizontalStockList.njk b/templates/HorizontalStockList.njk
new file mode 100755
index 0000000..7f75282
--- /dev/null
+++ b/templates/HorizontalStockList.njk
@@ -0,0 +1,39 @@
+
+
+{% macro render(config, exchangeData, utils, num="1") %}
+
+
+ {% for stock in config.stocks %}
+ {{ stock.name }}:
+ {% if utils.getStockChange(stock) > 0 %}
+ {% set colorClass = "high" %}
+ {% elif utils.getStockChange(stock) < 0 %}
+ {% set colorClass = "low " %}
+ {% else %}
+ {% set colorClass = "" %}
+ {% endif %}
+
+ {{ utils.getCurrentValue(stock, exchangeData) }}
+ {{ utils.getCurrency(stock, exchangeData, config) }}
+ {% if colorClass %}
+ ({{ utils.getStockChange(stock) }}%)
+ {% endif %}
+
+
+ {% endfor %}
+ {% if config.showDepotGrowth %}
+ {% set depotGrowth = utils.getDepotGrowth(config, exchangeData) %}
+ {% if depotGrowth.value > 0 %}
+ {% set colorClass = "high" %}
+ {% elif depotGrowth.value < 0 %}
+ {% set colorClass = "low " %}
+ {% else %}
+ {% set colorClass = "" %}
+ {% endif %}
+ {{ "depotGrowth" | translate | safe }}
+ {% if depotGrowth.errors%}≈ {% endif %}{{ depotGrowth.value }} {{ config.defaultCurrency }}
+
+ {% endif %}
+
+
+{% endmacro %}
diff --git a/templates/MMM-Jast.njk b/templates/MMM-Jast.njk
new file mode 100755
index 0000000..c209c45
--- /dev/null
+++ b/templates/MMM-Jast.njk
@@ -0,0 +1,23 @@
+{% import "templates/VerticalStockList.njk" as verticalStockList %}
+{% import "templates/HorizontalScrollStyle.njk" as hss %}
+{% import "templates/HorizontalStockList.njk" as horizontalStockList %}
+
+{% if config.stocks %}
+{% if config.scroll == "vertical" %}
+
+{% elif config.scroll == "horizontal" %}
+{% endif %}
+
+ {% if config.scroll == "horizontal" %}
+ {{ hss.render(config) }}
+ {{ horizontalStockList.render(config, exchangeData, utils) }}
+ {{ horizontalStockList.render(config, exchangeData, utils, "2") }}
+ {% else %}
+ {{ verticalStockList.render(config, exchangeData, utils) }}
+ {% endif %}
+
+{% else %}
+
+ {{ "LOADING" | translate | safe }}
+
+{% endif %}
diff --git a/templates/VerticalScrollStyle.njk b/templates/VerticalScrollStyle.njk
new file mode 100755
index 0000000..b5b5efb
--- /dev/null
+++ b/templates/VerticalScrollStyle.njk
@@ -0,0 +1,17 @@
+{% macro render(config) %}
+ {% if config.showDepotGrowth %}
+ {% set itemCount = config.stocks.length + 1 %}
+ {% else %}
+ {% set itemCount = config.stocks.length %}
+ {% endif %}
+ {% set percentPerItem = 100 / itemCount %}
+
+{% endmacro %}
diff --git a/templates/VerticalStockList.njk b/templates/VerticalStockList.njk
new file mode 100755
index 0000000..52439fe
--- /dev/null
+++ b/templates/VerticalStockList.njk
@@ -0,0 +1,46 @@
+{% import "templates/VerticalScrollStyle.njk" as vss %}
+
+{% macro render(config, exchangeData, utils)%}
+ {% if config.scroll == "vertical" %}
+ {{ vss.render(config) }}
+ {% if config.showDepotGrowth %}
+ {% set fadeSpeed = config.stocks.length + 1 %}
+ {% else %}
+ {% set fadeSpeed = config.stocks.length %}
+ {% endif %}
+ {% set animationStyle = ' style="animation-duration: ' + fadeSpeed * config.fadeSpeedInSeconds + 's"' %}
+ {% endif %}
+
+ {% for stock in config.stocks %}
+ - {{ stock.name }}:
+ {% if utils.getStockChange(stock) > 0 %}
+ {% set colorClass = "high" %}
+ {% elif utils.getStockChange(stock) < 0 %}
+ {% set colorClass = "low " %}
+ {% else %}
+ {% set colorClass = "" %}
+ {% endif %}
+
+ {{ utils.getCurrentValue(stock, exchangeData) }}
+ {{ utils.getCurrency(stock, exchangeData, config) }}
+ {% if colorClass %}
+ ({{ utils.getStockChange(stock) }}%)
+ {% endif %}
+
+
+ {% endfor %}
+ {% if config.showDepotGrowth %}
+ {% set depotGrowth = utils.getDepotGrowth(config, exchangeData) %}
+ {% if depotGrowth.value > 0 %}
+ {% set colorClass = "high" %}
+ {% elif depotGrowth.value < 0 %}
+ {% set colorClass = "low " %}
+ {% else %}
+ {% set colorClass = "" %}
+ {% endif %}
+ - {{ "depotGrowth" | translate | safe }}
+ {% if depotGrowth.errors%}≈ {% endif %}{{ depotGrowth.value }} {{ config.defaultCurrency }}
+
+ {% endif %}
+
+{% endmacro %}