mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-28 07:26:57 +00:00
Reorganisation + Electron fixes (#118)
* Updates package versions
* Updating versions with RCs
* Updated landing page with stock images
* Begins refactoring of websocket
Adds Help component
* Dark theme for charts
* event Event
* Adds cryptocurrency font
Updates wallet to use it
* Rejigs the location of assets
* rxjs update
wallet font correction
* renaming websocket service
* Refactors websocket use
Destroys and subscribes appropriately
Also handles when websocket is not available with intervals
* Fixes issues with electron by rebasing with Maxime GRIS electron builder
* License change
* Readme update
* Parses available and enabled currencies to create an object {Name:X, Enabled:Y}
* Adds methods to convert from string arrays to objects with enabled status for all currencies
* Uses a localstorage cache for config for 15 minutes
* Moves handling of settings to config object
* Fix typescripting
* Fixes issue with saving and loading
* Slows websocket repeats
Adds cool new dictionary style item and iterable.
Updatres currency-list.component to list all enabled currencies and exchanges (still doesn't do anything)
* Updates selected-currency.component to display all currencies ticker updates if there is no selected currency
Will display only selected currency results once it is set
Sets a new property to ensure all currency names are consistent for currency list plans
* Fixes issue where only one component could listen to the websocket at once
Allows you to select a currency in exchange grid mode
* Adds selected currency support to buy & sell components
Updates selected currency ticker to update on change faster
* Adds Online status indicator
* Removal of console.logs for working features
* Allows currency-list.component to aggregate on currency and list exchanges that match it
* Highlights selected currency in currency-list.component
Allows you to select a currency
This commit is contained in:
@@ -1,3 +1 @@
|
||||
<div *ngIf="showTicker" class="one-time-animation" >
|
||||
<p>{{message}}</p>
|
||||
</div>
|
||||
{{tickerCard.Exchange}} {{tickerCard.CurrencyPair}} Last: {{this.tickerCard.Last}}
|
||||
@@ -1,61 +1,86 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { WebsocketHandlerService } from './../../services/websocket-handler/websocket-handler.service';
|
||||
import {Component, OnInit, OnDestroy }from '@angular/core';
|
||||
import {WebsocketResponseHandlerService }from './../../services/websocket-response-handler/websocket-response-handler.service';
|
||||
import {WebSocketMessageType }from './../../shared/classes/websocket';
|
||||
|
||||
@Component({
|
||||
selector: 'app-all-updates-ticker',
|
||||
templateUrl: './all-updates-ticker.component.html',
|
||||
styleUrls: ['./all-updates-ticker.component.scss']
|
||||
@Component( {
|
||||
selector:'app-all-updates-ticker',
|
||||
templateUrl:'./all-updates-ticker.component.html',
|
||||
styleUrls:['./all-updates-ticker.component.scss'],
|
||||
})
|
||||
export class AllEnabledCurrencyTickersComponent implements OnInit {
|
||||
private ws: WebsocketHandlerService;
|
||||
allCurrencies:ExchangeCurrency[];
|
||||
tickerCard: TickerUpdate;
|
||||
showTicker:boolean;
|
||||
message:string;
|
||||
allCurrencies:ExchangeCurrency[] = < ExchangeCurrency[] > []; ;
|
||||
private ws:WebsocketResponseHandlerService;
|
||||
tickerCard:TickerUpdate = new TickerUpdate();
|
||||
showTicker:boolean = true;
|
||||
message:string;
|
||||
|
||||
constructor(private websocketHandler: WebsocketHandlerService) {
|
||||
this.ws = websocketHandler;
|
||||
this.allCurrencies = <ExchangeCurrency[]>[];
|
||||
this.ws.messages.subscribe(msg => {
|
||||
if (msg.Event === 'ticker_update') {
|
||||
this.showTicker = false;
|
||||
var modal = <ExchangeCurrency>{};
|
||||
modal.currencyPair = msg.data.CurrencyPair;
|
||||
modal.exchangeName = msg.Exchange;
|
||||
var ticker = <TickerUpdate>msg.data;
|
||||
this.tickerCard = ticker;
|
||||
this.tickerCard.Exchange = msg.Exchange;
|
||||
|
||||
if(this.tickerCard.Last > 0) {
|
||||
this.showTicker = true;
|
||||
this.message = this.tickerCard.Exchange + " " + this.tickerCard.CurrencyPair + " Last: " + this.tickerCard.Last;
|
||||
constructor(private websocketHandler: WebsocketResponseHandlerService) {
|
||||
this.tickerCard.Exchange = "Loading";
|
||||
this.tickerCard.CurrencyPair = "...";
|
||||
this.tickerCard.Last = -1;
|
||||
this.ws = websocketHandler;
|
||||
this.ws.shared.subscribe(msg => {
|
||||
if (msg.event === WebSocketMessageType.TickerUpdate) {
|
||||
if (window.localStorage["selectedExchange"] !== undefined &&
|
||||
window.localStorage["selectedCurrency"] !== undefined) {
|
||||
this.tickerCard.Exchange = window.localStorage["selectedExchange"];
|
||||
this.tickerCard.CurrencyPair = window.localStorage["selectedCurrency"];
|
||||
if (msg.exchange == window.localStorage["selectedExchange"]) {
|
||||
if (this.stripCurrencyCharacters(msg.data.CurrencyPair) == window.localStorage["selectedCurrency"]) {
|
||||
|
||||
this.updateTicker(msg)
|
||||
}
|
||||
}
|
||||
}else {
|
||||
this.updateTicker(msg)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
ngOnInit() { }
|
||||
|
||||
private updateTicker(msg:any):void {
|
||||
var modal = < ExchangeCurrency > {};
|
||||
modal.currencyPair = msg.data.CurrencyPair;
|
||||
modal.exchangeName = msg.exchange;
|
||||
var ticker = < TickerUpdate > msg.data;
|
||||
this.tickerCard = ticker;
|
||||
this.tickerCard.Exchange = msg.exchange;
|
||||
this.message = this.tickerCard.Exchange + " " + this.tickerCard.CurrencyPair + " Last: " + this.tickerCard.Last;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
}
|
||||
|
||||
private stripCurrencyCharacters(name:string):string {
|
||||
name = name.replace('_', '');
|
||||
name = name.replace('-', '');
|
||||
name = name.replace(' ', '');
|
||||
name = name.toLocaleUpperCase();
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ExchangeCurrency {
|
||||
currencyPair: string;
|
||||
exchangeName:string;
|
||||
currencyPair:string;
|
||||
exchangeName:string;
|
||||
}
|
||||
|
||||
export interface CurrencyPair {
|
||||
delimiter: string;
|
||||
first_currency: string;
|
||||
second_currency: string;
|
||||
delimiter:string;
|
||||
first_currency:string;
|
||||
second_currency:string;
|
||||
}
|
||||
|
||||
export interface TickerUpdate {
|
||||
Pair: CurrencyPair;
|
||||
CurrencyPair: string;
|
||||
Last: number;
|
||||
High: number;
|
||||
Low: number;
|
||||
Bid: number;
|
||||
Ask: number;
|
||||
Volume: number;
|
||||
PriceATH: number;
|
||||
Exchange:string;
|
||||
export class TickerUpdate {
|
||||
Pair:CurrencyPair;
|
||||
CurrencyPair:string;
|
||||
Last:number;
|
||||
High:number;
|
||||
Low:number;
|
||||
Bid:number;
|
||||
Ask:number;
|
||||
Volume:number;
|
||||
PriceATH:number;
|
||||
Exchange:string;
|
||||
}
|
||||
@@ -1,12 +1,24 @@
|
||||
<form class="form-content">
|
||||
<div *ngIf="showErrorMessage">
|
||||
<h4>{{chooseCurrencyMessage}}</h4>
|
||||
<button routerLink="/exchange-grid" mat-raised-button color="primary">Choose currency</button>
|
||||
</div>
|
||||
<div *ngIf="!showErrorMessage">
|
||||
<mat-form-field>
|
||||
<input matInput disabled="disabled" name="exchange" [ngModel]="exchangeName" placeholder="Exchange">
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput disabled="disabled" name="currency" [ngModel]="currencyName" placeholder="Currency">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-form-field>
|
||||
<input matInput name="smsUsername" placeholder="Amount">
|
||||
<input matInput name="amount" placeholder="Amount">
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput name="smsPassword" placeholder="Offer">
|
||||
<input matInput name="offer" placeholder="Offer">
|
||||
</mat-form-field>
|
||||
<div mat-line></div>
|
||||
<div class="spacer">
|
||||
<button mat-raised-button color="primary">CONFIRM BUY</button>
|
||||
<button [disabled]="showErrorMessage" mat-raised-button color="primary">CONFIRM BUY</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -6,9 +6,20 @@ import { Component, OnInit } from '@angular/core';
|
||||
styleUrls: ['./buy-form.component.scss']
|
||||
})
|
||||
export class BuyFormComponent implements OnInit {
|
||||
public exchangeName :string;
|
||||
public currencyName :string;
|
||||
public chooseCurrencyMessage :string = "Please select a currency";
|
||||
public showErrorMessage :boolean;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
if (window.localStorage["selectedExchange"] !== undefined &&
|
||||
window.localStorage["selectedCurrency"] !== undefined) {
|
||||
this.exchangeName = window.localStorage["selectedExchange"];
|
||||
this.currencyName = window.localStorage["selectedCurrency"];
|
||||
} else {
|
||||
this.showErrorMessage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
|
||||
export class Config {
|
||||
Name: string;
|
||||
EncryptConfig?: number;
|
||||
Cryptocurrencies: string;
|
||||
CurrencyExchangeProvider: string;
|
||||
CurrencyPairFormat: CurrencyPairFormat;
|
||||
PortfolioAddresses: PortfolioAddresses;
|
||||
SMSGlobal: SMSGlobal;
|
||||
Webserver: Webserver;
|
||||
Exchanges: Exchange[];
|
||||
|
||||
public isConfigCacheValid() : boolean {
|
||||
let dateStored = +new Date(window.localStorage['configDate']);
|
||||
let dateNow = +new Date();
|
||||
var dateDifference = Math.abs(dateNow - dateStored)
|
||||
var diffMins = Math.floor((dateDifference / 1000) / 60);
|
||||
(diffMins)
|
||||
|
||||
if(isNaN(new Date(dateStored).getTime()) || diffMins > 15) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public setConfig(data: any) : void {
|
||||
var configData = <Config>data;
|
||||
this.Cryptocurrencies = configData.Cryptocurrencies
|
||||
this.CurrencyExchangeProvider = configData.CurrencyExchangeProvider
|
||||
this.Exchanges = configData.Exchanges
|
||||
this.CurrencyPairFormat = configData.CurrencyPairFormat
|
||||
this.EncryptConfig = configData.EncryptConfig
|
||||
this.Exchanges = configData.Exchanges
|
||||
this.Name = configData.Name
|
||||
this.PortfolioAddresses = configData.PortfolioAddresses
|
||||
this.SMSGlobal = configData.SMSGlobal
|
||||
this.Webserver = configData.Webserver
|
||||
this.fromArrayToRedux()
|
||||
}
|
||||
|
||||
public fromArrayToRedux() : void {
|
||||
for (var i = 0; i < this.Exchanges.length; i++) {
|
||||
this.Exchanges[i].Pairs = new Array<CurrencyPairRedux>();
|
||||
var avail = this.Exchanges[i].AvailablePairs.split(',');
|
||||
var enabled = this.Exchanges[i].EnabledPairs.split(',');
|
||||
for (var j = 0; j < avail.length; j++) {
|
||||
var currencyPair = new CurrencyPairRedux();
|
||||
currencyPair.Name = avail[j]
|
||||
currencyPair.ParsedName = this.stripCurrencyCharacters(avail[j]);
|
||||
if (enabled.indexOf(avail[j]) > 0) {
|
||||
currencyPair.Enabled = true;
|
||||
} else {
|
||||
currencyPair.Enabled = false;
|
||||
}
|
||||
this.Exchanges[i].Pairs.push(currencyPair);
|
||||
}
|
||||
}
|
||||
window.localStorage['config'] = JSON.stringify(this);
|
||||
window.localStorage['configDate'] = new Date().toString();
|
||||
}
|
||||
|
||||
public parseSettings() : void {
|
||||
|
||||
}
|
||||
|
||||
private stripCurrencyCharacters(name:string) :string {
|
||||
name = name.replace('_', '');
|
||||
name = name.replace('-', '');
|
||||
name = name.replace(' ', '');
|
||||
name = name.toLocaleUpperCase();
|
||||
return name;
|
||||
}
|
||||
|
||||
public fromReduxToArray() : void {
|
||||
for (var i = 0; i < this.Exchanges.length; i++) {
|
||||
// Step 1, iterate over the Pairs
|
||||
var enabled = this.Exchanges[i].EnabledPairs.split(',');
|
||||
for (var j = 0; j < this.Exchanges[i].Pairs.length; j++) {
|
||||
if (this.Exchanges[i].Pairs[j].Enabled) {
|
||||
if (enabled.indexOf(this.Exchanges[i].Pairs[j].Name) == -1) {
|
||||
// Step 3 if its not in the enabled list, add it
|
||||
enabled.push(this.Exchanges[i].Pairs[j].Name);
|
||||
} else {
|
||||
|
||||
}
|
||||
} else {
|
||||
if (enabled.indexOf(this.Exchanges[i].Pairs[j].Name) > -1) {
|
||||
enabled.splice(enabled.indexOf(this.Exchanges[i].Pairs[j].Name), 1);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Step 4 JSONifiy the enabled list and set it to the this.settings.Exchanges[i].EnabledPairs
|
||||
this.Exchanges[i].EnabledPairs = enabled.join();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CurrencyPairRedux {
|
||||
Name: string;
|
||||
ParsedName: string;
|
||||
Enabled: boolean;
|
||||
}
|
||||
|
||||
export interface CurrencyPairFormat {
|
||||
Uppercase: boolean;
|
||||
Delimiter: string;
|
||||
}
|
||||
|
||||
export interface PortfolioAddresses {
|
||||
Addresses?: any;
|
||||
}
|
||||
|
||||
export interface Contact {
|
||||
Name: string;
|
||||
Number: string;
|
||||
Enabled: boolean;
|
||||
}
|
||||
|
||||
export interface SMSGlobal {
|
||||
Enabled: boolean;
|
||||
Username: string;
|
||||
Password: string;
|
||||
Contacts: Contact[];
|
||||
}
|
||||
|
||||
export interface Webserver {
|
||||
Enabled: boolean;
|
||||
AdminUsername: string;
|
||||
AdminPassword: string;
|
||||
ListenAddress: string;
|
||||
WebsocketConnectionLimit: number;
|
||||
WebsocketAllowInsecureOrigin: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigCurrencyPairFormat {
|
||||
Uppercase: boolean;
|
||||
Index: string;
|
||||
Delimiter: string;
|
||||
}
|
||||
|
||||
export interface RequestCurrencyPairFormat {
|
||||
Uppercase: boolean;
|
||||
Index: string;
|
||||
Delimiter: string;
|
||||
Separator: string;
|
||||
}
|
||||
|
||||
export interface Exchange {
|
||||
Name: string;
|
||||
Enabled: boolean;
|
||||
Verbose: boolean;
|
||||
Websocket: boolean;
|
||||
RESTPollingDelay: number;
|
||||
AuthenticatedAPISupport: boolean;
|
||||
APIKey: string;
|
||||
APISecret: string;
|
||||
AvailablePairs: string;
|
||||
EnabledPairs: string;
|
||||
BaseCurrencies: string;
|
||||
AssetTypes: string;
|
||||
ConfigCurrencyPairFormat: ConfigCurrencyPairFormat;
|
||||
RequestCurrencyPairFormat: RequestCurrencyPairFormat;
|
||||
ClientID: string;
|
||||
Pairs: CurrencyPairRedux[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
45
web/src/app/shared/classes/pipes.ts
Normal file
45
web/src/app/shared/classes/pipes.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Component, OnInit, OnDestroy, Pipe, PipeTransform } from '@angular/core';
|
||||
import { CurrencyPairRedux } from './../../shared/classes/config';
|
||||
|
||||
|
||||
@Pipe({
|
||||
name: 'iterateMap'
|
||||
})
|
||||
export class IterateMapPipe implements PipeTransform {
|
||||
transform(iterable: any, args: any[]): any {
|
||||
let result = [];
|
||||
|
||||
if (iterable.entries) {
|
||||
iterable.forEach((key, value) => {
|
||||
result.push({
|
||||
key,
|
||||
value
|
||||
});
|
||||
});
|
||||
} else {
|
||||
for (let key in iterable) {
|
||||
if (iterable.hasOwnProperty(key)) {
|
||||
result.push({
|
||||
key,
|
||||
value: iterable[key]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'enabledCurrencies'
|
||||
})
|
||||
export class EnabledCurrenciesPipe implements PipeTransform {
|
||||
transform(items: CurrencyPairRedux[], args: any[]): any {
|
||||
if (!items) {
|
||||
return items;
|
||||
}
|
||||
return items.filter(item => item.Enabled === true);
|
||||
}
|
||||
}
|
||||
|
||||
32
web/src/app/shared/classes/websocket.ts
Normal file
32
web/src/app/shared/classes/websocket.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export class WebSocketMessage {
|
||||
public event: string;
|
||||
public data: any;
|
||||
public exchange: string;
|
||||
public assetType: string;
|
||||
|
||||
public static CreateAuthenticationMessage(): WebSocketMessage {
|
||||
var response = new WebSocketMessage();
|
||||
|
||||
response.event = WebSocketMessageType.Auth;
|
||||
response.data = { "username": window.sessionStorage["username"], "password": window.sessionStorage["password"] };
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
public static GetSettingsMessage() : WebSocketMessage {
|
||||
var response = new WebSocketMessage();
|
||||
|
||||
response.event = WebSocketMessageType.GetConfig;
|
||||
response.data = null;
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export class WebSocketMessageType {
|
||||
public static Auth: string = "auth";
|
||||
public static GetConfig: string = "GetConfig";
|
||||
public static SaveConfig: string = "SaveConfig";
|
||||
public static GetPortfolio: string = "GetPortfolio";
|
||||
public static TickerUpdate: string = "ticker_update";
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
</a>
|
||||
<app-selected-currency></app-selected-currency>
|
||||
<div class="flex-spacer"></div>
|
||||
<app-all-updates-ticker></app-all-updates-ticker>
|
||||
<theme-picker></theme-picker>
|
||||
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export class PriceHistoryComponent implements OnInit {
|
||||
|
||||
public options = {
|
||||
"type": "serial",
|
||||
"theme": "light",
|
||||
"theme": "dark",
|
||||
"dataDateFormat": "YYYY-MM-DD",
|
||||
"zoomOutOnDataUpdate": false,
|
||||
"valueAxes": [{
|
||||
@@ -158,7 +158,6 @@ export class PriceHistoryComponent implements OnInit {
|
||||
e.chart.firstPoint = e.item;
|
||||
}
|
||||
|
||||
//console.log(e.item);
|
||||
e.chart.validateData();
|
||||
}
|
||||
}],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<button mat-button routerLink="exchange-grid" matTooltip="Change currency">POLONIEX: BTC_USD (placeholder)</button>
|
||||
|
||||
<button class="currency-button" mat-button routerLink="exchange-grid" matTooltip="Change currency"><app-all-updates-ticker></app-all-updates-ticker></button>
|
||||
<button mat-icon-button routerLink="currency-list" matTooltip="View currency list">
|
||||
<mat-icon>
|
||||
view_list
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.currency-button {
|
||||
width: 20rem;
|
||||
text-align: left;
|
||||
}
|
||||
@@ -1,12 +1,24 @@
|
||||
<form class="form-content">
|
||||
<mat-form-field>
|
||||
<input matInput name="smsUsername" placeholder="Amount">
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput name="smsPassword" placeholder="Offer">
|
||||
</mat-form-field>
|
||||
<div mat-line></div>
|
||||
<div *ngIf="showErrorMessage">
|
||||
<h4>{{chooseCurrencyMessage}}</h4>
|
||||
<button routerLink="/exchange-grid" mat-raised-button color="primary">Choose currency</button>
|
||||
</div>
|
||||
<div *ngIf="!showErrorMessage">
|
||||
<mat-form-field>
|
||||
<input matInput disabled="disabled" name="exchange" [ngModel]="exchangeName" placeholder="Exchange">
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput disabled="disabled" name="currency" [ngModel]="currencyName" placeholder="Currency">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-form-field>
|
||||
<input matInput name="amount" placeholder="Amount">
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput name="offer" placeholder="Offer">
|
||||
</mat-form-field>
|
||||
<div mat-line></div>
|
||||
<div class="spacer">
|
||||
<button mat-raised-button color="primary">CONFIRM SELL</button>
|
||||
</div>
|
||||
</form>
|
||||
<button [disabled]="showErrorMessage" mat-raised-button color="primary">CONFIRM SELL</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -6,10 +6,20 @@ import { Component, OnInit } from '@angular/core';
|
||||
styleUrls: ['./sell-form.component.scss']
|
||||
})
|
||||
export class SellFormComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
public exchangeName :string;
|
||||
public currencyName :string;
|
||||
public chooseCurrencyMessage :string = "Please select a currency";
|
||||
public showErrorMessage :boolean;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
if (window.localStorage["selectedExchange"] !== undefined &&
|
||||
window.localStorage["selectedCurrency"] !== undefined) {
|
||||
this.exchangeName = window.localStorage["selectedExchange"];
|
||||
this.currencyName = window.localStorage["selectedCurrency"];
|
||||
} else {
|
||||
this.showErrorMessage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export class ThemePickerComponent {
|
||||
if (theme.isDefault) {
|
||||
this.styleManager.removeStyle('theme');
|
||||
} else {
|
||||
this.styleManager.setStyle('theme', `assets/${theme.href}`);
|
||||
this.styleManager.setStyle('theme', `assets/themes/${theme.href}`);
|
||||
}
|
||||
|
||||
if (this.currentTheme) {
|
||||
|
||||
Reference in New Issue
Block a user