diff --git a/package.json b/package.json index 6b69c889..836035a9 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "date-fns": "^2.22.1", "date-fns-tz": "^1.1.4", "echarts": "^5.1.0", + "favico.js": "^0.3.10", "humanize-duration": "^3.27.0", "vue": "^2.6.14", "vue-class-component": "^7.2.5", diff --git a/src/components/layout/NavBar.vue b/src/components/layout/NavBar.vue index a9621f00..57d5ee56 100644 --- a/src/components/layout/NavBar.vue +++ b/src/components/layout/NavBar.vue @@ -54,9 +54,12 @@ import userService from '@/shared/userService'; import BootswatchThemeSelect from '@/components/BootswatchThemeSelect.vue'; import { LayoutActions, LayoutGetters } from '@/store/modules/layout'; import { BotStoreGetters } from '@/store/modules/ftbot'; +import Favico from 'favico.js'; +import { SettingsGetters } from '@/store/modules/settings'; const ftbot = namespace('ftbot'); const layoutNs = namespace('layout'); +const uiSettingsNs = namespace('uiSettings'); @Component({ components: { LoginModal, BootswatchThemeSelect }, @@ -74,6 +77,8 @@ export default class NavBar extends Vue { @ftbot.Getter [BotStoreGetters.botName]: string; + @ftbot.Getter [BotStoreGetters.openTradeCount]: number; + @layoutNs.Getter [LayoutGetters.getLayoutLocked]: boolean; @layoutNs.Action [LayoutActions.resetDashboardLayout]; @@ -82,6 +87,10 @@ export default class NavBar extends Vue { @layoutNs.Action [LayoutActions.setLayoutLocked]; + @uiSettingsNs.Getter [SettingsGetters.openTradesInTitle]: string; + + favicon: Favico | undefined = undefined; + mounted() { this.ping(); this.pingInterval = window.setInterval(this.ping, 60000); @@ -103,10 +112,28 @@ export default class NavBar extends Vue { } set layoutLockedLocal(value: boolean) { - console.log(value); this.setLayoutLocked(value); } + setOpenTradesAsPill(tradeCount: number) { + console.log('setPill', tradeCount); + if (!this.favicon) { + this.favicon = new Favico({ + animation: 'none', + // position: 'up', + // fontStyle: 'normal', + // bgColor: '#', + // textColor: '#FFFFFF', + }); + } + if (tradeCount !== 0 && this.openTradesInTitle === 'showPill') { + this.favicon.badge(tradeCount); + } else { + this.favicon.reset(); + console.log('reset'); + } + } + resetDynamicLayout(): void { const route = this.$router.currentRoute.path; console.log(`resetLayout called for ${route}`); @@ -121,14 +148,37 @@ export default class NavBar extends Vue { } } + setTitle() { + let title = 'freqUI'; + if (this.openTradesInTitle === 'asTitle') { + title = `(${this.openTradeCount}) ${title}`; + } + if (this.botName) { + title = `${title} - ${this.botName}`; + } + document.title = title; + } + @Watch(BotStoreGetters.botName) botnameChanged() { - if (this.botName) { - document.title = `freqUI - ${this.botName}`; - } else { - document.title = 'freqUI'; + this.setTitle(); + } + + @Watch(BotStoreGetters.openTradeCount) + openTradeCountChanged() { + console.log('openTradeCount changed'); + if (this.openTradesInTitle === 'showPill') { + this.setOpenTradesAsPill(this.openTradeCount); + } else if (this.openTradesInTitle === 'asTitle') { + this.setTitle(); } } + + @Watch(SettingsGetters.openTradesInTitle) + openTradesSettingChanged() { + this.setTitle(); + this.setOpenTradesAsPill(this.openTradeCount); + } } diff --git a/src/router/index.ts b/src/router/index.ts index 69bf3efd..36cf0598 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -31,6 +31,11 @@ const routes: Array = [ name: 'Freqtrade Dashboard', component: () => import(/* webpackChunkName: "dashboard" */ '@/views/Dashboard.vue'), }, + { + path: '/settings', + name: 'Freqtrade Settings', + component: () => import(/* webpackChunkName: "dashboard" */ '@/views/Settings.vue'), + }, { path: '/login', name: 'Login', diff --git a/src/store/index.ts b/src/store/index.ts index cc7736c2..76aeb502 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -7,6 +7,7 @@ import { AxiosInstance } from 'axios'; import ftbotModule, { BotStoreGetters } from './modules/ftbot'; import alertsModule from './modules/alerts'; import layoutModule from './modules/layout'; +import settingsModule from './modules/settings'; const AUTO_REFRESH = 'ft_auto_refresh'; @@ -38,6 +39,7 @@ export default new Vuex.Store({ ftbot: ftbotModule, alerts: alertsModule, layout: layoutModule, + uiSettings: settingsModule, }, mutations: { setPing(state, ping) { diff --git a/src/store/modules/ftbot/index.ts b/src/store/modules/ftbot/index.ts index f98a7187..ec6b13b4 100644 --- a/src/store/modules/ftbot/index.ts +++ b/src/store/modules/ftbot/index.ts @@ -35,6 +35,7 @@ import { showAlert } from '../alerts'; export enum BotStoreGetters { botName = 'botName', openTrades = 'openTrades', + openTradeCount = 'openTradeCount', tradeDetail = 'tradeDetail', selectedPair = 'selectedPair', closedTrades = 'closedTrades', @@ -64,6 +65,9 @@ export default { [BotStoreGetters.openTrades](state: FtbotStateType): Trade[] { return state.openTrades; }, + [BotStoreGetters.openTradeCount](state: FtbotStateType): number { + return state.openTrades.length; + }, [BotStoreGetters.allTrades](state: FtbotStateType): Trade[] { return [...state.openTrades, ...state.trades]; }, @@ -115,6 +119,7 @@ export default { state.tradeCount = tradesCount; }, updateOpenTrades(state: FtbotStateType, trades) { + console.log(`Update open trade length ${trades.length}`); state.openTrades = trades; }, updateLocks(state: FtbotStateType, locks: LockResponse) { diff --git a/src/store/modules/settings.ts b/src/store/modules/settings.ts new file mode 100644 index 00000000..ea35775a --- /dev/null +++ b/src/store/modules/settings.ts @@ -0,0 +1,51 @@ +const STORE_UI_SETTINGS = 'ftUISettings'; + +export enum SettingsGetters { + openTradesInTitle = 'openTradesInTitle', +} + +export enum SettingsActions { + setOpenTradesInTitle = 'setOpenTradesInTitle', +} + +export enum SettingsMutations { + setOpenTrades = 'setOpenTrades', +} + +function getSettings() { + const fromStore = localStorage.getItem(STORE_UI_SETTINGS); + if (fromStore) { + return JSON.parse(fromStore); + } + return {}; +} +const storedSettings = getSettings(); + +function updateSetting(key: string, value: string) { + const settings = getSettings() || {}; + settings[key] = value; + localStorage.setItem(STORE_UI_SETTINGS, JSON.stringify(settings)); +} + +export default { + namespaced: true, + state: { + openTradesInTitle: storedSettings?.openTradesInTitle || 'showPill', + }, + getters: { + [SettingsGetters.openTradesInTitle](state) { + return state.openTradesInTitle; + }, + }, + mutations: { + [SettingsMutations.setOpenTrades](state, value: string) { + state.openTradesInTitle = value; + updateSetting('openTradesInTitle', value); + }, + }, + actions: { + [SettingsActions.setOpenTradesInTitle]({ commit }, locked: boolean) { + commit(SettingsMutations.setOpenTrades, locked); + }, + }, +}; diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 7888a57c..d4acaec4 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -7,6 +7,8 @@ :margin="[5, 5]" :is-resizable="!getLayoutLocked" :is-draggable="!getLayoutLocked" + responsive + prevent-collision @layout-updated="layoutUpdatedEvent" > +
+ +
+ + Lock layout + + + + +
+
+
+ + + + + diff --git a/yarn.lock b/yarn.lock index 5e154fc7..94df2fca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4980,6 +4980,11 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +favico.js@^0.3.10: + version "0.3.10" + resolved "https://registry.yarnpkg.com/favico.js/-/favico.js-0.3.10.tgz#80586e27a117f24a8d51c18a99bdc714d4339301" + integrity sha1-gFhuJ6EX8kqNUcGKmb3HFNQzkwE= + faye-websocket@^0.11.3: version "0.11.3" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"