diff --git a/src/db/migrations/20181021144645-AddGrafanaWidget.ts b/src/db/migrations/20181021144645-AddGrafanaWidget.ts
index a2caa60..6ebfc73 100644
--- a/src/db/migrations/20181021144645-AddGrafanaWidget.ts
+++ b/src/db/migrations/20181021144645-AddGrafanaWidget.ts
@@ -1,5 +1,4 @@
import { QueryInterface } from "sequelize";
-import { DataType } from "sequelize-typescript";
export default {
up: (queryInterface: QueryInterface) => {
diff --git a/src/db/migrations/20181021152145-AddTradingViewWidget.ts b/src/db/migrations/20181021152145-AddTradingViewWidget.ts
new file mode 100644
index 0000000..328893b
--- /dev/null
+++ b/src/db/migrations/20181021152145-AddTradingViewWidget.ts
@@ -0,0 +1,23 @@
+import { QueryInterface } from "sequelize";
+
+export default {
+ up: (queryInterface: QueryInterface) => {
+ return Promise.resolve()
+ .then(() => queryInterface.bulkInsert("dimension_widgets", [
+ {
+ type: "tradingview",
+ name: "TradingView",
+ avatarUrl: "/img/avatars/tradingview.png",
+ isEnabled: true,
+ isPublic: true,
+ description: "Monitor your favourite cryptocurrencies",
+ }
+ ]));
+ },
+ down: (queryInterface: QueryInterface) => {
+ return Promise.resolve()
+ .then(() => queryInterface.bulkDelete("dimension_widgets", {
+ type: "tradingview",
+ }));
+ }
+}
\ No newline at end of file
diff --git a/web/app/app.module.ts b/web/app/app.module.ts
index cc683d4..c341a25 100644
--- a/web/app/app.module.ts
+++ b/web/app/app.module.ts
@@ -97,6 +97,8 @@ import { GitterBridgeConfigComponent } from "./configs/bridge/gitter/gitter.brid
import { GitterApiService } from "./shared/services/integrations/gitter-api.service";
import { GenericFullscreenWidgetWrapperComponent } from "./widget-wrappers/generic-fullscreen/generic-fullscreen.component";
import { GrafanaWidgetConfigComponent } from "./configs/widget/grafana/grafana.widget.component";
+import { TradingViewWidgetConfigComponent } from "./configs/widget/tradingview/tradingview.widget.component";
+import { TradingViewWidgetWrapperComponent } from "./widget-wrappers/tradingview/tradingview.component";
@NgModule({
imports: [
@@ -177,6 +179,8 @@ import { GrafanaWidgetConfigComponent } from "./configs/widget/grafana/grafana.w
GitterBridgeConfigComponent,
GenericFullscreenWidgetWrapperComponent,
GrafanaWidgetConfigComponent,
+ TradingViewWidgetConfigComponent,
+ TradingViewWidgetWrapperComponent,
// Vendor
],
diff --git a/web/app/app.routing.ts b/web/app/app.routing.ts
index 1215f2a..e0c926b 100644
--- a/web/app/app.routing.ts
+++ b/web/app/app.routing.ts
@@ -35,6 +35,8 @@ import { AdminGitterBridgeComponent } from "./admin/bridges/gitter/gitter.compon
import { GitterBridgeConfigComponent } from "./configs/bridge/gitter/gitter.bridge.component";
import { GenericFullscreenWidgetWrapperComponent } from "./widget-wrappers/generic-fullscreen/generic-fullscreen.component";
import { GrafanaWidgetConfigComponent } from "./configs/widget/grafana/grafana.widget.component";
+import { TradingViewWidgetConfigComponent } from "./configs/widget/tradingview/tradingview.widget.component";
+import { TradingViewWidgetWrapperComponent } from "./widget-wrappers/tradingview/tradingview.component";
const routes: Routes = [
{path: "", component: HomeComponent},
@@ -167,6 +169,11 @@ const routes: Routes = [
component: GrafanaWidgetConfigComponent,
data: {breadcrumb: "Grafana Widgets", name: "Grafana Widgets"},
},
+ {
+ path: "tradingview",
+ component: TradingViewWidgetConfigComponent,
+ data: {breadcrumb: "TradingView Widgets", name: "TradingView Widgets"},
+ },
],
},
{
@@ -225,6 +232,7 @@ const routes: Routes = [
{path: "gcal", component: GCalWidgetWrapperComponent},
{path: "stickerpicker", component: StickerPickerWidgetWrapperComponent},
{path: "generic-fullscreen", component: GenericFullscreenWidgetWrapperComponent},
+ {path: "tradingview", component: TradingViewWidgetWrapperComponent},
]
},
];
diff --git a/web/app/configs/widget/google-calendar/gcal.widget.component.ts b/web/app/configs/widget/google-calendar/gcal.widget.component.ts
index 3b1e370..7d88dda 100644
--- a/web/app/configs/widget/google-calendar/gcal.widget.component.ts
+++ b/web/app/configs/widget/google-calendar/gcal.widget.component.ts
@@ -38,6 +38,6 @@ export class GoogleCalendarWidgetConfigComponent extends WidgetComponent {
}
const encodedId = encodeURIComponent(widget.dimension.newData.src);
- widget.dimension.newUrl = window.location.origin + "/widget/gcal?calendarId=" + encodedId;
+ widget.dimension.newUrl = window.location.origin + "/widgets/gcal?calendarId=" + encodedId;
}
}
\ No newline at end of file
diff --git a/web/app/configs/widget/tradingview/tradingview.widget.component.html b/web/app/configs/widget/tradingview/tradingview.widget.component.html
new file mode 100644
index 0000000..c72e682
--- /dev/null
+++ b/web/app/configs/widget/tradingview/tradingview.widget.component.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/app/configs/widget/tradingview/tradingview.widget.component.scss b/web/app/configs/widget/tradingview/tradingview.widget.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/web/app/configs/widget/tradingview/tradingview.widget.component.ts b/web/app/configs/widget/tradingview/tradingview.widget.component.ts
new file mode 100644
index 0000000..397d57d
--- /dev/null
+++ b/web/app/configs/widget/tradingview/tradingview.widget.component.ts
@@ -0,0 +1,98 @@
+import { DISABLE_AUTOMATIC_WRAPPING, WidgetComponent } from "../widget.component";
+import { EditableWidget, WIDGET_TRADINGVIEW } from "../../../shared/models/widget";
+import { Component } from "@angular/core";
+
+@Component({
+ templateUrl: "tradingview.widget.component.html",
+ styleUrls: ["tradingview.widget.component.scss"],
+})
+export class TradingViewWidgetConfigComponent extends WidgetComponent {
+
+ public readonly intervals = [
+ {value: '1', label: '1 Minute'},
+ {value: '3', label: '3 Minutes'},
+ {value: '5', label: '5 Minutes'},
+ {value: '15', label: '15 Minutes'},
+ {value: '30', label: '30 Minutes'},
+ {value: '60', label: '1 Hour'},
+ {value: '120', label: '2 Hours'},
+ {value: '180', label: '3 Hours'},
+ {value: '240', label: '4 Hours'},
+ {value: 'D', label: '1 Day'},
+ {value: 'W', label: '1 Week'},
+ ];
+
+ public readonly pairs = [
+ // USD
+ {value: 'COINBASE:BTCUSD', label: 'Bitcoin / US Dollar'},
+ {value: 'COINBASE:ETHUSD', label: 'Ethereum / US Dollar'},
+ {value: 'COINBASE:LTCUSD', label: 'Litecoin / US Dollar'},
+ {value: 'BITTREX:SNTUSD', label: 'Status Network Token / US Dollar'},
+ {value: 'BITTREX:ETCUSD', label: 'Ethereum Classic / US Dollar'},
+ {value: 'BITFINEX:BTGUSD', label: 'BTG / US Dollar'},
+ {value: 'BITTREX:DASHUSD', label: 'Dash / US Dollar'},
+ {value: 'BITFINEX:EOSUSD', label: 'EOS / US Dollar'},
+ {value: 'BITFINEX:IOTUSD', label: 'IOTA / US Dollar'},
+ {value: 'BITTREX:LSKUSD', label: 'Lisk / US Dollar'},
+ {value: 'BITTREX:OMGUSD', label: 'OmiseGo / US Dollar'},
+ {value: 'BITTREX:NEOUSD', label: 'NEO / US Dollar'},
+ {value: 'BITTREX:XRPUSD', label: 'Ripple / US Dollar'},
+ {value: 'BITFINEX:ZECUSD', label: 'Zcash / US Dollar'},
+ {value: 'BITFINEX:XMRUSD', label: 'Monero / US Dollar'},
+
+ // Euro / GBP
+ {value: 'COINBASE:BTCEUR', label: 'Bitcoin / Euro'},
+ {value: 'COINBASE:ETHEUR', label: 'Ethereum / Euro'},
+ {value: 'COINBASE:LTCEUR', label: 'Litecoin / Euro'},
+ {value: 'COINBASE:BTCGBP', label: 'Bitcoin / GBP'},
+
+ // Bitcoin
+ {value: 'COINBASE:ETHBTC', label: 'Ethereum / Bitcoin'},
+ {value: 'COINBASE:LTCBTC', label: 'Litecoin / Bitcoin'},
+ {value: 'BITTREX:SNTBTC', label: 'Status Network Token / Bitcoin'},
+ {value: 'BITTREX:BCCBTC', label: 'Bitcoin Cash / Bitcoin'},
+ {value: 'BITTREX:ADABTC', label: 'Ada / Bitcoin'},
+ {value: 'BITTREX:ARKBTC', label: 'Ark / Bitcoin'},
+ {value: 'BITTREX:EMC2BTC', label: 'Einsteinium / Bitcoin'},
+ {value: 'BITFINEX:IOTBTC', label: 'IOTA / Bitcoin'},
+ {value: 'BITTREX:LSKBTC', label: 'Lisk / Bitcoin'},
+ {value: 'BITTREX:NEOBTC', label: 'Neo / Bitcoin'},
+ {value: 'BITTREX:OMGBTC', label: 'OmiseGO / Bitcoin'},
+ {value: 'BITTREX:POWRBTC', label: 'PowerLedger / Bitcoin'},
+ {value: 'BITTREX:STRATBTC', label: 'Stratis / Bitcoin'},
+ {value: 'BITTREX:TRIGBTC', label: 'TRIG Token / Bitcoin'},
+ {value: 'BITTREX:VTCBTC', label: 'Vertcoin / Bitcoin'},
+ {value: 'BITTREX:XLMBTC', label: 'Lumen / Bitcoin'},
+ {value: 'BITTREX:XRPBTC', label: 'Ripple / Bitcoin'},
+
+ // Misc
+ {value: 'BITTREX:BTCUSDT', label: 'Bitcoin / Tether USD'},
+ {value: 'BITTREX:ETHUSDT', label: 'Ethereum / Tether USD'},
+ {value: 'BITTREX:SNTETH', label: 'Status Network Token / Ethereum'},
+ {value: 'BITTREX:BCCUSDT', label: 'Bitcoin Cash / Tether USD'},
+ {value: 'BITTREX:NEOUSDT', label: 'Neo / Tether'},
+ ];
+
+ constructor() {
+ super(WIDGET_TRADINGVIEW, "TradingView Chart", DISABLE_AUTOMATIC_WRAPPING, "tradingView");
+ }
+
+ protected OnNewWidgetPrepared(widget: EditableWidget): void {
+ widget.dimension.newData.interval = "D"; // 1 day
+ widget.dimension.newData.pair = this.pairs[0].value;
+ }
+
+ protected OnWidgetBeforeAdd(widget: EditableWidget): void {
+ this.setViewUrl(widget);
+ }
+
+ protected OnWidgetBeforeEdit(widget: EditableWidget) {
+ this.setViewUrl(widget);
+ }
+
+ private setViewUrl(widget: EditableWidget) {
+ const pair = this.pairs.find(p => p.value === widget.dimension.newData.pair);
+ widget.dimension.newTitle = pair ? pair.label : null;
+ widget.dimension.newUrl = window.location.origin + "/widgets/tradingview?pair=$pair&interval=$interval";
+ }
+}
\ No newline at end of file
diff --git a/web/app/home/home.component.html b/web/app/home/home.component.html
index dfd1185..9f42ff9 100644
--- a/web/app/home/home.component.html
+++ b/web/app/home/home.component.html
@@ -40,8 +40,8 @@
Etherpad
-
![](/img/avatars/grafana.png)
-
Grafana
+
![](/img/avatars/tradingview.png)
+
TradingView
![](/img/avatars/youtube.png)
@@ -51,6 +51,10 @@
Twitch Livestream
+
+
![](/img/avatars/grafana.png)
+
Grafana
+
Google Docs
diff --git a/web/app/shared/models/widget.ts b/web/app/shared/models/widget.ts
index 65f7ec8..828834f 100644
--- a/web/app/shared/models/widget.ts
+++ b/web/app/shared/models/widget.ts
@@ -9,6 +9,7 @@ export const WIDGET_YOUTUBE = ["youtube", "dimension-youtube"];
export const WIDGET_GRAFANA = ["grafana", "dimension-grafana"];
export const WIDGET_TWITCH = ["twitch", "dimension-twitch"];
export const WIDGET_STICKER_PICKER = ["m.stickerpicker"];
+export const WIDGET_TRADINGVIEW = ["tradingview", "dimension-tradingview"];
export interface EditableWidget {
/**
diff --git a/web/app/shared/registry/integrations.registry.ts b/web/app/shared/registry/integrations.registry.ts
index 09de201..27b12ba 100644
--- a/web/app/shared/registry/integrations.registry.ts
+++ b/web/app/shared/registry/integrations.registry.ts
@@ -6,6 +6,7 @@ import {
WIDGET_GOOGLE_DOCS,
WIDGET_GRAFANA,
WIDGET_JITSI,
+ WIDGET_TRADINGVIEW,
WIDGET_TWITCH,
WIDGET_YOUTUBE
} from "../models/widget";
@@ -51,7 +52,10 @@ export class IntegrationsRegistry {
},
"grafana": {
types: WIDGET_GRAFANA,
- }
+ },
+ "tradingview": {
+ types: WIDGET_TRADINGVIEW,
+ },
},
};
diff --git a/web/app/widget-wrappers/tradingview/tradingview.component.html b/web/app/widget-wrappers/tradingview/tradingview.component.html
new file mode 100644
index 0000000..4a8002a
--- /dev/null
+++ b/web/app/widget-wrappers/tradingview/tradingview.component.html
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/web/app/widget-wrappers/tradingview/tradingview.component.scss b/web/app/widget-wrappers/tradingview/tradingview.component.scss
new file mode 100644
index 0000000..4e9c7e5
--- /dev/null
+++ b/web/app/widget-wrappers/tradingview/tradingview.component.scss
@@ -0,0 +1,8 @@
+// component styles are encapsulated and only applied to their components
+#tradingviewContainer {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
\ No newline at end of file
diff --git a/web/app/widget-wrappers/tradingview/tradingview.component.ts b/web/app/widget-wrappers/tradingview/tradingview.component.ts
new file mode 100644
index 0000000..c1335fd
--- /dev/null
+++ b/web/app/widget-wrappers/tradingview/tradingview.component.ts
@@ -0,0 +1,42 @@
+import { Component, OnInit } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+import * as $ from "jquery";
+
+declare var TradingView: any;
+
+@Component({
+ selector: "my-tradingview-widget-wrapper",
+ templateUrl: "tradingview.component.html",
+ styleUrls: ["tradingview.component.scss"],
+})
+export class TradingViewWidgetWrapperComponent implements OnInit {
+
+ private symbol: string;
+ private interval: string;
+
+ constructor(activatedRoute: ActivatedRoute) {
+ let params: any = activatedRoute.snapshot.queryParams;
+
+ this.symbol = params.pair;
+ this.interval = params.interval;
+ }
+
+ public ngOnInit() {
+ $.getScript("https://s3.tradingview.com/tv.js", () => {
+ new TradingView.widget({
+ "autosize": true,
+ "symbol": this.symbol,
+ "interval": this.interval,
+ "timezone": "Etc/UTC",
+ "theme": "Light",
+ "style": "1",
+ "locale": "en",
+ "toolbar_bg": "#f1f3f6",
+ "enable_publishing": false,
+ "hide_top_toolbar": true,
+ "hide_legend": true,
+ "container_id": "tradingviewContainer",
+ });
+ });
+ }
+}
diff --git a/web/public/img/avatars/tradingview.png b/web/public/img/avatars/tradingview.png
new file mode 100644
index 0000000..517de82
Binary files /dev/null and b/web/public/img/avatars/tradingview.png differ