Add TradingView widget

Fixes https://github.com/turt2live/matrix-dimension/issues/132
This commit is contained in:
Travis Ralston 2018-10-21 16:19:36 -06:00
parent 9bdba2d474
commit ea834d826a
15 changed files with 219 additions and 5 deletions

View File

@ -1,5 +1,4 @@
import { QueryInterface } from "sequelize"; import { QueryInterface } from "sequelize";
import { DataType } from "sequelize-typescript";
export default { export default {
up: (queryInterface: QueryInterface) => { up: (queryInterface: QueryInterface) => {

View File

@ -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",
}));
}
}

View File

@ -97,6 +97,8 @@ import { GitterBridgeConfigComponent } from "./configs/bridge/gitter/gitter.brid
import { GitterApiService } from "./shared/services/integrations/gitter-api.service"; import { GitterApiService } from "./shared/services/integrations/gitter-api.service";
import { GenericFullscreenWidgetWrapperComponent } from "./widget-wrappers/generic-fullscreen/generic-fullscreen.component"; import { GenericFullscreenWidgetWrapperComponent } from "./widget-wrappers/generic-fullscreen/generic-fullscreen.component";
import { GrafanaWidgetConfigComponent } from "./configs/widget/grafana/grafana.widget.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({ @NgModule({
imports: [ imports: [
@ -177,6 +179,8 @@ import { GrafanaWidgetConfigComponent } from "./configs/widget/grafana/grafana.w
GitterBridgeConfigComponent, GitterBridgeConfigComponent,
GenericFullscreenWidgetWrapperComponent, GenericFullscreenWidgetWrapperComponent,
GrafanaWidgetConfigComponent, GrafanaWidgetConfigComponent,
TradingViewWidgetConfigComponent,
TradingViewWidgetWrapperComponent,
// Vendor // Vendor
], ],

View File

@ -35,6 +35,8 @@ import { AdminGitterBridgeComponent } from "./admin/bridges/gitter/gitter.compon
import { GitterBridgeConfigComponent } from "./configs/bridge/gitter/gitter.bridge.component"; import { GitterBridgeConfigComponent } from "./configs/bridge/gitter/gitter.bridge.component";
import { GenericFullscreenWidgetWrapperComponent } from "./widget-wrappers/generic-fullscreen/generic-fullscreen.component"; import { GenericFullscreenWidgetWrapperComponent } from "./widget-wrappers/generic-fullscreen/generic-fullscreen.component";
import { GrafanaWidgetConfigComponent } from "./configs/widget/grafana/grafana.widget.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 = [ const routes: Routes = [
{path: "", component: HomeComponent}, {path: "", component: HomeComponent},
@ -167,6 +169,11 @@ const routes: Routes = [
component: GrafanaWidgetConfigComponent, component: GrafanaWidgetConfigComponent,
data: {breadcrumb: "Grafana Widgets", name: "Grafana Widgets"}, 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: "gcal", component: GCalWidgetWrapperComponent},
{path: "stickerpicker", component: StickerPickerWidgetWrapperComponent}, {path: "stickerpicker", component: StickerPickerWidgetWrapperComponent},
{path: "generic-fullscreen", component: GenericFullscreenWidgetWrapperComponent}, {path: "generic-fullscreen", component: GenericFullscreenWidgetWrapperComponent},
{path: "tradingview", component: TradingViewWidgetWrapperComponent},
] ]
}, },
]; ];

View File

@ -38,6 +38,6 @@ export class GoogleCalendarWidgetConfigComponent extends WidgetComponent {
} }
const encodedId = encodeURIComponent(widget.dimension.newData.src); 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;
} }
} }

View File

@ -0,0 +1,21 @@
<my-widget-config [widgetComponent]="this">
<ng-template #widgetParamsTemplate let-widget="widget">
<label class="label-block">
Trading Pair
<select class="form-control form-control-sm" [(ngModel)]="widget.dimension.newData.pair" [disabled]="isUpdating" name="widget-pair-{{widget.id}}">
<option *ngFor="let pair of pairs" [ngValue]="pair.value">
{{ pair.label }}
</option>
</select>
</label>
<label class="label-block">
Interval
<select class="form-control form-control-sm" [(ngModel)]="widget.dimension.newData.interval" [disabled]="isUpdating" name="widget-interval-{{widget.id}}">
<option *ngFor="let interval of intervals" [ngValue]="interval.value">
{{ interval.label }}
</option>
</select>
</label>
</ng-template>
</my-widget-config>

View File

@ -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";
}
}

View File

@ -40,8 +40,8 @@
<span>Etherpad</span> <span>Etherpad</span>
</div> </div>
<div class="integration"> <div class="integration">
<img src="/img/avatars/grafana.png"> <img src="/img/avatars/tradingview.png">
<span>Grafana</span> <span>TradingView</span>
</div> </div>
<div class="integration"> <div class="integration">
<img src="/img/avatars/youtube.png"> <img src="/img/avatars/youtube.png">
@ -51,6 +51,10 @@
<img src="/img/avatars/twitch.png"> <img src="/img/avatars/twitch.png">
<span>Twitch Livestream</span> <span>Twitch Livestream</span>
</div> </div>
<div class="integration">
<img src="/img/avatars/grafana.png">
<span>Grafana</span>
</div>
<div class="integration"> <div class="integration">
<img src="/img/avatars/googledocs.png"> <img src="/img/avatars/googledocs.png">
<span>Google Docs</span> <span>Google Docs</span>

View File

@ -9,6 +9,7 @@ export const WIDGET_YOUTUBE = ["youtube", "dimension-youtube"];
export const WIDGET_GRAFANA = ["grafana", "dimension-grafana"]; export const WIDGET_GRAFANA = ["grafana", "dimension-grafana"];
export const WIDGET_TWITCH = ["twitch", "dimension-twitch"]; export const WIDGET_TWITCH = ["twitch", "dimension-twitch"];
export const WIDGET_STICKER_PICKER = ["m.stickerpicker"]; export const WIDGET_STICKER_PICKER = ["m.stickerpicker"];
export const WIDGET_TRADINGVIEW = ["tradingview", "dimension-tradingview"];
export interface EditableWidget { export interface EditableWidget {
/** /**

View File

@ -6,6 +6,7 @@ import {
WIDGET_GOOGLE_DOCS, WIDGET_GOOGLE_DOCS,
WIDGET_GRAFANA, WIDGET_GRAFANA,
WIDGET_JITSI, WIDGET_JITSI,
WIDGET_TRADINGVIEW,
WIDGET_TWITCH, WIDGET_TWITCH,
WIDGET_YOUTUBE WIDGET_YOUTUBE
} from "../models/widget"; } from "../models/widget";
@ -51,7 +52,10 @@ export class IntegrationsRegistry {
}, },
"grafana": { "grafana": {
types: WIDGET_GRAFANA, types: WIDGET_GRAFANA,
} },
"tradingview": {
types: WIDGET_TRADINGVIEW,
},
}, },
}; };

View File

@ -0,0 +1,2 @@
<div id="tradingviewContainer">
</div>

View File

@ -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;
}

View File

@ -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",
});
});
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB