pinia: add layout store

Navbar to composition API
Trading.vue to composition API
This commit is contained in:
Matthias 2022-04-15 17:53:31 +02:00
parent 2d91945662
commit e1810eabbf
11 changed files with 487 additions and 556 deletions

View File

@ -38,6 +38,7 @@
"vue-property-decorator": "^9.1.2", "vue-property-decorator": "^9.1.2",
"vue-router": "^3.5.3", "vue-router": "^3.5.3",
"vue-select": "^3.18.3", "vue-select": "^3.18.3",
"vue2-helpers": "^1.1.7",
"vuex": "^3.6.2", "vuex": "^3.6.2",
"vuex-composition-helpers": "^1.1.0" "vuex-composition-helpers": "^1.1.0"
}, },

View File

@ -109,11 +109,11 @@ export default class TradeList extends Vue {
@Prop({ required: true }) trades!: Array<Trade>; @Prop({ required: true }) trades!: Array<Trade>;
@Prop({ default: 'Trades' }) title!: string; @Prop({ default: 'Trades', type: String }) title!: string;
@Prop({ required: false, default: '' }) stakeCurrency!: string; @Prop({ required: false, default: '' }) stakeCurrency!: string;
@Prop({ default: false }) activeTrades!: boolean; @Prop({ default: false, type: Boolean }) activeTrades!: boolean;
@Prop({ default: false }) showFilter!: boolean; @Prop({ default: false }) showFilter!: boolean;

View File

@ -62,7 +62,7 @@
</template> </template>
<b-dropdown-item>V: {{ getUiVersion }}</b-dropdown-item> <b-dropdown-item>V: {{ getUiVersion }}</b-dropdown-item>
<router-link class="dropdown-item" to="/settings">Settings</router-link> <router-link class="dropdown-item" to="/settings">Settings</router-link>
<b-checkbox v-model="layoutLockedLocal" class="pl-5">Lock layout</b-checkbox> <b-checkbox v-model="layoutStore.layoutLocked" class="pl-5">Lock layout</b-checkbox>
<b-dropdown-item @click="resetDynamicLayout">Reset Layout</b-dropdown-item> <b-dropdown-item @click="resetDynamicLayout">Reset Layout</b-dropdown-item>
<router-link <router-link
v-if="botCount === 1" v-if="botCount === 1"
@ -105,170 +105,163 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';
import LoginModal from '@/views/LoginModal.vue'; import LoginModal from '@/views/LoginModal.vue';
import { Action, namespace, Getter } from 'vuex-class';
import BootswatchThemeSelect from '@/components/BootswatchThemeSelect.vue'; import BootswatchThemeSelect from '@/components/BootswatchThemeSelect.vue';
import { LayoutActions, LayoutGetters } from '@/store/modules/layout';
import { BotStoreGetters } from '@/store/modules/ftbot'; import { BotStoreGetters } from '@/store/modules/ftbot';
import Favico from 'favico.js'; import Favico from 'favico.js';
import { MultiBotStoreGetters } from '@/store/modules/botStoreWrapper'; import { MultiBotStoreGetters } from '@/store/modules/botStoreWrapper';
import ReloadControl from '@/components/ftbot/ReloadControl.vue'; import ReloadControl from '@/components/ftbot/ReloadControl.vue';
import BotEntry from '@/components/BotEntry.vue'; import BotEntry from '@/components/BotEntry.vue';
import BotList from '@/components/BotList.vue'; import BotList from '@/components/BotList.vue';
import { BotDescriptor } from '@/types';
import StoreModules from '@/store/storeSubModules'; import StoreModules from '@/store/storeSubModules';
import { defineComponent, ref, onBeforeUnmount, onMounted, watch } from '@vue/composition-api';
const ftbot = namespace(StoreModules.ftbot); import {
const layoutNs = namespace(StoreModules.layout); useActions,
useGetters,
useNamespacedActions,
useNamespacedGetters,
} from 'vuex-composition-helpers';
import { useRoute } from 'vue2-helpers/vue-router';
import { OpenTradeVizOptions, useSettingsStore } from '@/stores/settings'; import { OpenTradeVizOptions, useSettingsStore } from '@/stores/settings';
import { useLayoutStore } from '@/stores/layout';
@Component({ export default defineComponent({
name: 'NavBar',
components: { LoginModal, BootswatchThemeSelect, ReloadControl, BotEntry, BotList }, components: { LoginModal, BootswatchThemeSelect, ReloadControl, BotEntry, BotList },
}) setup() {
export default class NavBar extends Vue { const { getUiVersion } = useGetters(['getUiVersion']);
pingInterval: number | null = null; const { setLoggedIn, loadUIVersion } = useActions(['setLoggedIn', 'loadUIVersion']);
const { pingAll, allGetState, logout } = useNamespacedActions(StoreModules.ftbot, [
'pingAll',
'allGetState',
'logout',
]);
const {
isBotOnline,
hasBots,
botCount,
botName,
openTradeCount,
canRunBacktest,
selectedBotObj,
} = useNamespacedGetters(StoreModules.ftbot, [
BotStoreGetters.isBotOnline,
MultiBotStoreGetters.hasBots,
MultiBotStoreGetters.botCount,
BotStoreGetters.botName,
BotStoreGetters.openTradeCount,
BotStoreGetters.canRunBacktest,
MultiBotStoreGetters.selectedBotObj,
]);
botSelectOpen = false;
@Action setLoggedIn;
@Action loadUIVersion;
@Getter getUiVersion!: string;
@ftbot.Action pingAll;
@ftbot.Action allGetState;
@ftbot.Action logout;
@ftbot.Getter [BotStoreGetters.isBotOnline]!: boolean;
@ftbot.Getter [MultiBotStoreGetters.hasBots]: boolean;
@ftbot.Getter [MultiBotStoreGetters.botCount]: number;
@ftbot.Getter [BotStoreGetters.botName]: string;
@ftbot.Getter [BotStoreGetters.openTradeCount]: number;
@ftbot.Getter [BotStoreGetters.canRunBacktest]!: boolean;
@ftbot.Getter [MultiBotStoreGetters.selectedBotObj]!: BotDescriptor;
@layoutNs.Getter [LayoutGetters.getLayoutLocked]: boolean;
@layoutNs.Action [LayoutActions.resetDashboardLayout];
@layoutNs.Action [LayoutActions.resetTradingLayout];
@layoutNs.Action [LayoutActions.setLayoutLocked];
openTradesInTitle: string = OpenTradeVizOptions.showPill;
favicon: Favico | undefined = undefined;
mounted() {
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
this.openTradesInTitle = settingsStore.openTradesInTitle; const layoutStore = useLayoutStore();
settingsStore.$subscribe((_, state) => { const route = useRoute();
const needsUpdate = this.openTradesInTitle !== state.openTradesInTitle; const favicon = ref<Favico | undefined>(undefined);
this.openTradesInTitle = state.openTradesInTitle; const pingInterval = ref<number>();
if (needsUpdate) {
this.setTitle(); const clickLogout = () => {
this.setOpenTradesAsPill(this.openTradeCount); logout();
// TODO: This should be per bot
setLoggedIn(false);
};
const setOpenTradesAsPill = (tradeCount: number) => {
if (!favicon) {
favicon.value = new Favico({
animation: 'none',
// position: 'up',
// fontStyle: 'normal',
// bgColor: '#',
// textColor: '#FFFFFF',
});
}
if (tradeCount !== 0 && settingsStore.openTradesInTitle === 'showPill') {
favicon.badge(tradeCount);
} else {
favicon.reset();
console.log('reset');
}
};
const resetDynamicLayout = (): void => {
console.log(`resetLayout called for ${route?.fullPath}`);
switch (route?.fullPath) {
case '/trade':
layoutStore.resetTradingLayout();
break;
case '/dashboard':
layoutStore.resetDashboardLayout();
break;
default:
}
};
const setTitle = () => {
let title = 'freqUI';
if (settingsStore.openTradesInTitle === OpenTradeVizOptions.asTitle) {
title = `(${openTradeCount}) ${title}`;
}
if (botName) {
title = `${title} - ${botName}`;
}
document.title = title;
};
onBeforeUnmount(() => {
if (pingInterval) {
clearInterval(pingInterval.value);
} }
}); });
this.pingAll(); onMounted(() => {
this.loadUIVersion(); pingAll();
this.pingInterval = window.setInterval(this.pingAll, 60000); loadUIVersion();
pingInterval.value = window.setInterval(pingAll, 60000);
if (hasBots) {
// Query botstate - this will enable / disable certain modes
allGetState();
}
});
if (this.hasBots) { settingsStore.$subscribe((_, state) => {
// Query botstate - this will enable / disable certain modes const needsUpdate = settingsStore.openTradesInTitle !== state.openTradesInTitle;
this.allGetState(); if (needsUpdate) {
} setTitle();
} setOpenTradesAsPill(openTradeCount.value);
}
});
beforeDestroy() { watch(botName, () => setTitle());
if (this.pingInterval) { watch(openTradeCount, () => {
clearInterval(this.pingInterval); console.log('openTradeCount changed');
} if (settingsStore.openTradesInTitle === OpenTradeVizOptions.showPill) {
} setOpenTradesAsPill(openTradeCount.value);
} else if (settingsStore.openTradesInTitle === OpenTradeVizOptions.asTitle) {
setTitle();
}
});
clickLogout(): void { return {
this.logout(); setLoggedIn,
// TODO: This should be per bot loadUIVersion,
this.setLoggedIn(false); getUiVersion,
} pingAll,
allGetState,
get layoutLockedLocal() { logout,
return this.getLayoutLocked; favicon,
} isBotOnline,
hasBots,
set layoutLockedLocal(value: boolean) { botCount,
this.setLayoutLocked(value); botName,
} openTradeCount,
canRunBacktest,
setOpenTradesAsPill(tradeCount: number) { selectedBotObj,
if (!this.favicon) { clickLogout,
this.favicon = new Favico({ resetDynamicLayout,
animation: 'none', setTitle,
// position: 'up', layoutStore,
// 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}`);
switch (route) {
case '/trade':
this.resetTradingLayout();
break;
case '/dashboard':
this.resetDashboardLayout();
break;
default:
}
}
setTitle() {
let title = 'freqUI';
if (this.openTradesInTitle === OpenTradeVizOptions.asTitle) {
title = `(${this.openTradeCount}) ${title}`;
}
if (this.botName) {
title = `${title} - ${this.botName}`;
}
document.title = title;
}
@Watch(BotStoreGetters.botName)
botnameChanged() {
this.setTitle();
}
@Watch(BotStoreGetters.openTradeCount)
openTradeCountChanged() {
console.log('openTradeCount changed');
if (this.openTradesInTitle === OpenTradeVizOptions.showPill) {
this.setOpenTradesAsPill(this.openTradeCount);
} else if (this.openTradesInTitle === OpenTradeVizOptions.asTitle) {
this.setTitle();
}
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -8,7 +8,6 @@ import { UiVersion } from '@/types';
import StoreModules from '@/store/storeSubModules'; import StoreModules from '@/store/storeSubModules';
import createBotStore, { MultiBotStoreGetters } from './modules/botStoreWrapper'; import createBotStore, { MultiBotStoreGetters } from './modules/botStoreWrapper';
import alertsModule from './modules/alerts'; import alertsModule from './modules/alerts';
import layoutModule from './modules/layout';
Vue.use(Vuex); Vue.use(Vuex);
const initCurrentTheme = getCurrentTheme(); const initCurrentTheme = getCurrentTheme();
@ -16,7 +15,6 @@ const initCurrentTheme = getCurrentTheme();
const store = new Vuex.Store({ const store = new Vuex.Store({
modules: { modules: {
[StoreModules.alerts]: alertsModule, [StoreModules.alerts]: alertsModule,
[StoreModules.layout]: layoutModule,
}, },
state: { state: {
currentTheme: initCurrentTheme, currentTheme: initCurrentTheme,

View File

@ -1,170 +0,0 @@
import { GridItemData } from 'vue-grid-layout';
export enum TradeLayout {
multiPane = 'g-multiPane',
openTrades = 'g-openTrades',
tradeHistory = 'g-tradeHistory',
tradeDetail = 'g-tradeDetail',
chartView = 'g-chartView',
}
export enum DashboardLayout {
dailyChart = 'g-dailyChart',
botComparison = 'g-botComparison',
allOpenTrades = 'g-allOpenTrades',
cumChartChart = 'g-cumChartChart',
tradesLogChart = 'g-TradesLogChart',
}
export enum LayoutGetters {
getDashboardLayoutSm = 'getDashboardLayoutSm',
getDashboardLayout = 'getDashboardLayout',
getTradingLayoutSm = 'getTradingLayoutSm',
getTradingLayout = 'getTradingLayout',
getLayoutLocked = 'getLayoutLocked',
}
export enum LayoutActions {
setDashboardLayout = 'setDashboardLayout',
setTradingLayout = 'setTradingLayout',
resetDashboardLayout = 'resetDashboardLayout',
resetTradingLayout = 'resetTradingLayout',
setLayoutLocked = 'setLayoutLocked',
}
export enum LayoutMutations {
setDashboardLayout = 'setDashboardLayout',
setTradingLayout = 'setTradingLayout',
setLayoutLocked = 'setLayoutLocked',
}
// Define default layouts
const DEFAULT_TRADING_LAYOUT: GridItemData[] = [
{ i: TradeLayout.multiPane, x: 0, y: 0, w: 3, h: 35 },
{ i: TradeLayout.chartView, x: 3, y: 0, w: 9, h: 14 },
{ i: TradeLayout.tradeDetail, x: 3, y: 19, w: 9, h: 6 },
{ i: TradeLayout.openTrades, x: 3, y: 14, w: 9, h: 5 },
{ i: TradeLayout.tradeHistory, x: 3, y: 25, w: 9, h: 10 },
];
// Currently only multiPane is visible
const DEFAULT_TRADING_LAYOUT_SM: GridItemData[] = [
{ i: TradeLayout.multiPane, x: 0, y: 0, w: 12, h: 10 },
{ i: TradeLayout.chartView, x: 0, y: 10, w: 12, h: 0 },
{ i: TradeLayout.tradeDetail, x: 0, y: 19, w: 12, h: 0 },
{ i: TradeLayout.openTrades, x: 0, y: 8, w: 12, h: 0 },
{ i: TradeLayout.tradeHistory, x: 0, y: 25, w: 12, h: 0 },
];
const DEFAULT_DASHBOARD_LAYOUT: GridItemData[] = [
{ i: DashboardLayout.botComparison, x: 0, y: 0, w: 8, h: 6 } /* Bot Comparison */,
{ i: DashboardLayout.dailyChart, x: 8, y: 0, w: 4, h: 6 },
{ i: DashboardLayout.allOpenTrades, x: 0, y: 6, w: 8, h: 6 },
{ i: DashboardLayout.cumChartChart, x: 8, y: 6, w: 4, h: 6 },
{ i: DashboardLayout.tradesLogChart, x: 0, y: 12, w: 12, h: 4 },
];
const DEFAULT_DASHBOARD_LAYOUT_SM: GridItemData[] = [
{ i: DashboardLayout.botComparison, x: 0, y: 0, w: 12, h: 6 } /* Bot Comparison */,
{ i: DashboardLayout.allOpenTrades, x: 0, y: 6, w: 12, h: 8 },
{ i: DashboardLayout.dailyChart, x: 0, y: 14, w: 12, h: 6 },
{ i: DashboardLayout.cumChartChart, x: 0, y: 20, w: 12, h: 6 },
{ i: DashboardLayout.tradesLogChart, x: 0, y: 26, w: 12, h: 4 },
];
const STORE_DASHBOARD_LAYOUT = 'ftDashboardLayout';
const STORE_TRADING_LAYOUT = 'ftTradingLayout';
const STORE_LAYOUT_LOCK = 'ftLayoutLocked';
function getLayoutLocked() {
const fromStore = localStorage.getItem(STORE_LAYOUT_LOCK);
if (fromStore) {
return JSON.parse(fromStore);
}
return true;
}
function getLayout(storageString: string, defaultLayout: GridItemData[]) {
const fromStore = localStorage.getItem(storageString);
if (fromStore) {
return JSON.parse(fromStore);
}
return JSON.parse(JSON.stringify(defaultLayout));
}
/**
* Helper function finding a layout entry
* @param gridLayout Array of grid layouts used in this layout. Must be passed to GridLayout, too.
* @param name Name within the dashboard layout to find
*/
export function findGridLayout(gridLayout: GridItemData[], name: string): GridItemData {
let layout = gridLayout.find((value) => value.i === name);
if (!layout) {
layout = { i: name, x: 0, y: 0, w: 4, h: 6 };
}
return layout;
}
export default {
namespaced: true,
state: {
dashboardLayout: getLayout(STORE_DASHBOARD_LAYOUT, DEFAULT_DASHBOARD_LAYOUT),
tradingLayout: getLayout(STORE_TRADING_LAYOUT, DEFAULT_TRADING_LAYOUT),
layoutLocked: getLayoutLocked(),
},
getters: {
[LayoutGetters.getDashboardLayoutSm]() {
return [...DEFAULT_DASHBOARD_LAYOUT_SM];
},
[LayoutGetters.getDashboardLayout](state) {
return state.dashboardLayout;
},
[LayoutGetters.getTradingLayoutSm]() {
return [...DEFAULT_TRADING_LAYOUT_SM];
},
[LayoutGetters.getTradingLayout](state) {
return state.tradingLayout;
},
[LayoutGetters.getLayoutLocked](state) {
return state.layoutLocked;
},
},
mutations: {
[LayoutMutations.setDashboardLayout](state, layout) {
state.dashboardLayout = layout;
localStorage.setItem(STORE_DASHBOARD_LAYOUT, JSON.stringify(layout));
},
[LayoutMutations.setTradingLayout](state, layout) {
state.tradingLayout = layout;
localStorage.setItem(STORE_TRADING_LAYOUT, JSON.stringify(layout));
},
[LayoutMutations.setLayoutLocked](state, locked: boolean) {
state.layoutLocked = locked;
localStorage.setItem(STORE_LAYOUT_LOCK, JSON.stringify(locked));
},
},
actions: {
[LayoutActions.setDashboardLayout]({ commit }, layout) {
commit(LayoutMutations.setDashboardLayout, layout);
},
[LayoutActions.setTradingLayout]({ commit }, layout) {
commit(LayoutMutations.setTradingLayout, layout);
},
[LayoutActions.setLayoutLocked]({ commit }, locked: boolean) {
commit(LayoutMutations.setLayoutLocked, locked);
},
[LayoutActions.resetDashboardLayout]({ commit }) {
commit(
LayoutMutations.setDashboardLayout,
JSON.parse(JSON.stringify(DEFAULT_DASHBOARD_LAYOUT)),
);
},
[LayoutActions.resetTradingLayout]({ commit }) {
commit(LayoutMutations.setTradingLayout, JSON.parse(JSON.stringify(DEFAULT_TRADING_LAYOUT)));
},
},
};

View File

@ -2,7 +2,6 @@
enum StoreModules { enum StoreModules {
ftbot = 'ftbot', ftbot = 'ftbot',
alerts = 'alerts', alerts = 'alerts',
layout = 'layout',
} }
export default StoreModules; export default StoreModules;

116
src/stores/layout.ts Normal file
View File

@ -0,0 +1,116 @@
import { defineStore } from 'pinia';
import { GridItemData } from 'vue-grid-layout';
export enum TradeLayout {
multiPane = 'g-multiPane',
openTrades = 'g-openTrades',
tradeHistory = 'g-tradeHistory',
tradeDetail = 'g-tradeDetail',
chartView = 'g-chartView',
}
export enum DashboardLayout {
dailyChart = 'g-dailyChart',
botComparison = 'g-botComparison',
allOpenTrades = 'g-allOpenTrades',
cumChartChart = 'g-cumChartChart',
tradesLogChart = 'g-TradesLogChart',
}
// Define default layouts
const DEFAULT_TRADING_LAYOUT: GridItemData[] = [
{ i: TradeLayout.multiPane, x: 0, y: 0, w: 3, h: 35 },
{ i: TradeLayout.chartView, x: 3, y: 0, w: 9, h: 14 },
{ i: TradeLayout.tradeDetail, x: 3, y: 19, w: 9, h: 6 },
{ i: TradeLayout.openTrades, x: 3, y: 14, w: 9, h: 5 },
{ i: TradeLayout.tradeHistory, x: 3, y: 25, w: 9, h: 10 },
];
// Currently only multiPane is visible
const DEFAULT_TRADING_LAYOUT_SM: GridItemData[] = [
{ i: TradeLayout.multiPane, x: 0, y: 0, w: 12, h: 10 },
{ i: TradeLayout.chartView, x: 0, y: 10, w: 12, h: 0 },
{ i: TradeLayout.tradeDetail, x: 0, y: 19, w: 12, h: 0 },
{ i: TradeLayout.openTrades, x: 0, y: 8, w: 12, h: 0 },
{ i: TradeLayout.tradeHistory, x: 0, y: 25, w: 12, h: 0 },
];
const DEFAULT_DASHBOARD_LAYOUT: GridItemData[] = [
{ i: DashboardLayout.botComparison, x: 0, y: 0, w: 8, h: 6 } /* Bot Comparison */,
{ i: DashboardLayout.dailyChart, x: 8, y: 0, w: 4, h: 6 },
{ i: DashboardLayout.allOpenTrades, x: 0, y: 6, w: 8, h: 6 },
{ i: DashboardLayout.cumChartChart, x: 8, y: 6, w: 4, h: 6 },
{ i: DashboardLayout.tradesLogChart, x: 0, y: 12, w: 12, h: 4 },
];
const DEFAULT_DASHBOARD_LAYOUT_SM: GridItemData[] = [
{ i: DashboardLayout.botComparison, x: 0, y: 0, w: 12, h: 6 } /* Bot Comparison */,
{ i: DashboardLayout.allOpenTrades, x: 0, y: 6, w: 12, h: 8 },
{ i: DashboardLayout.dailyChart, x: 0, y: 14, w: 12, h: 6 },
{ i: DashboardLayout.cumChartChart, x: 0, y: 20, w: 12, h: 6 },
{ i: DashboardLayout.tradesLogChart, x: 0, y: 26, w: 12, h: 4 },
];
const STORE_LAYOUTS = 'ftLayoutSettings';
function migrateLayoutSettings() {
const STORE_DASHBOARD_LAYOUT = 'ftDashboardLayout';
const STORE_TRADING_LAYOUT = 'ftTradingLayout';
const STORE_LAYOUT_LOCK = 'ftLayoutLocked';
// If new does not exist
if (localStorage.getItem(STORE_LAYOUTS) === null) {
console.log('Migrating dashboard settings');
const layoutLocked = localStorage.getItem(STORE_LAYOUT_LOCK);
const tradingLayout = localStorage.getItem(STORE_TRADING_LAYOUT);
const dashboardLayout = localStorage.getItem(STORE_DASHBOARD_LAYOUT);
const res = {
dashboardLayout: dashboardLayout,
tradingLayout,
layoutLocked,
};
localStorage.setItem(STORE_LAYOUTS, JSON.stringify(res));
}
localStorage.removeItem(STORE_LAYOUT_LOCK);
localStorage.removeItem(STORE_TRADING_LAYOUT);
localStorage.removeItem(STORE_DASHBOARD_LAYOUT);
}
migrateLayoutSettings();
/**
* Helper function finding a layout entry
* @param gridLayout Array of grid layouts used in this layout. Must be passed to GridLayout, too.
* @param name Name within the dashboard layout to find
*/
export function findGridLayout(gridLayout: GridItemData[], name: string): GridItemData {
let layout = gridLayout.find((value) => value.i === name);
if (!layout) {
layout = { i: name, x: 0, y: 0, w: 4, h: 6 };
}
return layout;
}
export const useLayoutStore = defineStore('layoutStore', {
state: () => {
return {
dashboardLayout: JSON.parse(JSON.stringify(DEFAULT_DASHBOARD_LAYOUT)),
tradingLayout: JSON.parse(JSON.stringify(DEFAULT_TRADING_LAYOUT)),
layoutLocked: true,
};
},
getters: {
getDashboardLayoutSm: () => [...DEFAULT_DASHBOARD_LAYOUT_SM],
getTradingLayoutSm: () => [...DEFAULT_TRADING_LAYOUT_SM],
},
actions: {
resetTradingLayout() {
this.tradingLayout = JSON.parse(JSON.stringify(DEFAULT_TRADING_LAYOUT));
},
resetDashboardLayout() {
this.dashboardLayout = JSON.parse(JSON.stringify(DEFAULT_DASHBOARD_LAYOUT));
},
},
persist: {
key: STORE_LAYOUTS,
},
});

View File

@ -2,7 +2,7 @@
<GridLayout <GridLayout
class="h-100 w-100" class="h-100 w-100"
:row-height="50" :row-height="50"
:layout.sync="gridLayout" :layout="gridLayout"
:vertical-compact="false" :vertical-compact="false"
:margin="[5, 5]" :margin="[5, 5]"
:responsive-layouts="responsiveGridLayouts" :responsive-layouts="responsiveGridLayouts"
@ -11,7 +11,7 @@
:responsive="true" :responsive="true"
:prevent-collision="true" :prevent-collision="true"
:cols="{ lg: 12, md: 12, sm: 12, xs: 4, xxs: 2 }" :cols="{ lg: 12, md: 12, sm: 12, xs: 4, xxs: 2 }"
@layout-updated="layoutUpdated" @layout-updated="layoutUpdatedEvent"
@breakpoint-changed="breakpointChanged" @breakpoint-changed="breakpointChanged"
> >
<GridItem <GridItem
@ -94,8 +94,6 @@
<script lang="ts"> <script lang="ts">
import { formatPrice } from '@/shared/formatters'; import { formatPrice } from '@/shared/formatters';
import { Component, Vue } from 'vue-property-decorator';
import { namespace } from 'vuex-class';
import { GridLayout, GridItem, GridItemData } from 'vue-grid-layout'; import { GridLayout, GridItem, GridItemData } from 'vue-grid-layout';
import DailyChart from '@/components/charts/DailyChart.vue'; import DailyChart from '@/components/charts/DailyChart.vue';
@ -105,21 +103,16 @@ import BotComparisonList from '@/components/ftbot/BotComparisonList.vue';
import TradeList from '@/components/ftbot/TradeList.vue'; import TradeList from '@/components/ftbot/TradeList.vue';
import DraggableContainer from '@/components/layout/DraggableContainer.vue'; import DraggableContainer from '@/components/layout/DraggableContainer.vue';
import {
DashboardLayout,
findGridLayout,
LayoutActions,
LayoutGetters,
} from '@/store/modules/layout';
import { Trade, DailyReturnValue, DailyPayload, ClosedTrade } from '@/types';
import { BotStoreGetters } from '@/store/modules/ftbot'; import { BotStoreGetters } from '@/store/modules/ftbot';
import { MultiBotStoreGetters } from '@/store/modules/botStoreWrapper'; import { MultiBotStoreGetters } from '@/store/modules/botStoreWrapper';
import StoreModules from '@/store/storeSubModules'; import StoreModules from '@/store/storeSubModules';
const ftbot = namespace(StoreModules.ftbot); import { defineComponent, ref, computed, onMounted } from '@vue/composition-api';
const layoutNs = namespace(StoreModules.layout); import { useNamespacedGetters, useNamespacedActions } from 'vuex-composition-helpers';
import { DashboardLayout, findGridLayout, useLayoutStore } from '@/stores/layout';
@Component({ export default defineComponent({
name: 'Dashboard',
components: { components: {
GridLayout, GridLayout,
GridItem, GridItem,
@ -130,111 +123,118 @@ const layoutNs = namespace(StoreModules.layout);
TradeList, TradeList,
DraggableContainer, DraggableContainer,
}, },
}) setup() {
export default class Dashboard extends Vue { const {
@ftbot.Getter [MultiBotStoreGetters.botCount]!: number; botCount,
allOpenTradesAllBots,
allTradesAllBots,
allDailyStatsAllBots,
performanceStats,
} = useNamespacedGetters(StoreModules.ftbot, [
MultiBotStoreGetters.botCount,
MultiBotStoreGetters.allOpenTradesAllBots,
MultiBotStoreGetters.allTradesAllBots,
MultiBotStoreGetters.allDailyStatsAllBots,
BotStoreGetters.performanceStats,
]);
@ftbot.Getter [MultiBotStoreGetters.allOpenTradesAllBots]!: Trade[]; const { getPerformance, allGetDaily, getTrades, getOpenTrades, getProfit } =
useNamespacedActions(StoreModules.ftbot, [
'getPerformance',
'allGetDaily',
'getTrades',
'getOpenTrades',
'getProfit',
]);
@ftbot.Getter [MultiBotStoreGetters.allTradesAllBots]!: ClosedTrade[]; const layoutStore = useLayoutStore();
const currentBreakpoint = ref('');
@ftbot.Getter [MultiBotStoreGetters.allDailyStatsAllBots]!: Record<string, DailyReturnValue>; const breakpointChanged = (newBreakpoint) => {
// // console.log('breakpoint:', newBreakpoint);
@ftbot.Getter [BotStoreGetters.performanceStats]!: PerformanceEntry[]; currentBreakpoint.value = newBreakpoint;
@ftbot.Action getPerformance;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@ftbot.Action allGetDaily!: (payload?: DailyPayload) => void;
@ftbot.Action getTrades;
@ftbot.Action getOpenTrades;
@ftbot.Action getProfit;
@layoutNs.Getter [LayoutGetters.getDashboardLayoutSm]!: GridItemData[];
@layoutNs.Getter [LayoutGetters.getDashboardLayout]!: GridItemData[];
@layoutNs.Action [LayoutActions.setDashboardLayout];
@layoutNs.Getter [LayoutGetters.getLayoutLocked]: boolean;
formatPrice = formatPrice;
localGridLayout: GridItemData[] = [];
currentBreakpoint = '';
get isLayoutLocked() {
return this.getLayoutLocked || !this.isResizableLayout;
}
get isResizableLayout() {
return ['', 'sm', 'md', 'lg', 'xl'].includes(this.currentBreakpoint);
}
get gridLayout() {
if (this.isResizableLayout) {
return this.getDashboardLayout;
}
return this.localGridLayout;
}
set gridLayout(newLayout) {
// Dummy setter to make gridLayout happy. Updates happen through layoutUpdated.
}
layoutUpdated(newLayout) {
// Frozen layouts for small screen sizes.
if (this.isResizableLayout) {
console.log('newlayout', newLayout);
console.log('saving dashboard');
this.setDashboardLayout(newLayout);
}
}
get gridLayoutDaily(): GridItemData {
return findGridLayout(this.gridLayout, DashboardLayout.dailyChart);
}
get gridLayoutBotComparison(): GridItemData {
return findGridLayout(this.gridLayout, DashboardLayout.botComparison);
}
get gridLayoutAllOpenTrades(): GridItemData {
return findGridLayout(this.gridLayout, DashboardLayout.allOpenTrades);
}
get gridLayoutCumChart(): GridItemData {
return findGridLayout(this.gridLayout, DashboardLayout.cumChartChart);
}
get gridLayoutTradesLogChart(): GridItemData {
return findGridLayout(this.gridLayout, DashboardLayout.tradesLogChart);
}
get responsiveGridLayouts() {
return {
sm: this.getDashboardLayoutSm,
}; };
} const isResizableLayout = computed(() =>
['', 'sm', 'md', 'lg', 'xl'].includes(currentBreakpoint.value),
);
const isLayoutLocked = computed(() => {
return layoutStore.layoutLocked || !isResizableLayout;
});
mounted() { const gridLayout = computed((): GridItemData[] => {
this.allGetDaily({ timescale: 30 }); if (isResizableLayout) {
this.getTrades(); return layoutStore.dashboardLayout;
this.getOpenTrades(); }
this.getPerformance(); return [...layoutStore.getDashboardLayoutSm];
this.getProfit(); });
this.localGridLayout = [...this.getDashboardLayoutSm];
}
breakpointChanged(newBreakpoint) { const layoutUpdatedEvent = (newLayout) => {
// console.log('breakpoint:', newBreakpoint); if (isResizableLayout) {
this.currentBreakpoint = newBreakpoint; console.log('newlayout', newLayout);
} console.log('saving dashboard');
} layoutStore.tradingLayout = newLayout;
}
};
const gridLayoutDaily = computed((): GridItemData => {
return findGridLayout(gridLayout.value, DashboardLayout.dailyChart);
});
const gridLayoutBotComparison = computed((): GridItemData => {
return findGridLayout(gridLayout.value, DashboardLayout.botComparison);
});
const gridLayoutAllOpenTrades = computed((): GridItemData => {
return findGridLayout(gridLayout.value, DashboardLayout.allOpenTrades);
});
const gridLayoutCumChart = computed((): GridItemData => {
return findGridLayout(gridLayout.value, DashboardLayout.cumChartChart);
});
const gridLayoutTradesLogChart = computed((): GridItemData => {
return findGridLayout(gridLayout.value, DashboardLayout.tradesLogChart);
});
const responsiveGridLayouts = computed(() => {
return {
sm: layoutStore.getDashboardLayoutSm,
};
});
onMounted(() => {
allGetDaily({ timescale: 30 });
getTrades();
getOpenTrades();
getPerformance();
getProfit();
});
return {
formatPrice,
isLayoutLocked,
layoutUpdatedEvent,
breakpointChanged,
gridLayout,
gridLayoutDaily,
gridLayoutBotComparison,
gridLayoutAllOpenTrades,
gridLayoutCumChart,
gridLayoutTradesLogChart,
responsiveGridLayouts,
getPerformance,
allGetDaily,
getTrades,
getOpenTrades,
getProfit,
botCount,
allOpenTradesAllBots,
allTradesAllBots,
allDailyStatsAllBots,
performanceStats,
};
},
});
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -6,7 +6,7 @@
<b-form-group <b-form-group
description="Lock dynamic layouts, so they cannot move anymore. Can also be set from the navbar at the top." description="Lock dynamic layouts, so they cannot move anymore. Can also be set from the navbar at the top."
> >
<b-checkbox v-model="layoutLockedLocal">Lock layout</b-checkbox> <b-checkbox v-model="layoutStore.layoutLocked">Lock layout</b-checkbox>
</b-form-group> </b-form-group>
<b-form-group description="Reset dynamic layouts to how they were."> <b-form-group description="Reset dynamic layouts to how they were.">
<b-button size="sm" @click="resetDynamicLayout">Reset layout</b-button> <b-button size="sm" @click="resetDynamicLayout">Reset layout</b-button>
@ -39,28 +39,20 @@
<script lang="ts"> <script lang="ts">
import { AlertActions } from '@/store/modules/alerts'; import { AlertActions } from '@/store/modules/alerts';
import { LayoutActions, LayoutGetters } from '@/store/modules/layout';
import StoreModules from '@/store/storeSubModules'; import StoreModules from '@/store/storeSubModules';
import { defineComponent, WritableComputedRef, computed } from '@vue/composition-api'; import { defineComponent } from '@vue/composition-api';
import { useGetters, useNamespacedActions, useNamespacedGetters } from 'vuex-composition-helpers'; import { useGetters, useNamespacedActions } from 'vuex-composition-helpers';
import { OpenTradeVizOptions, useSettingsStore } from '@/stores/settings'; import { OpenTradeVizOptions, useSettingsStore } from '@/stores/settings';
import { useLayoutStore } from '@/stores/layout';
export default defineComponent({ export default defineComponent({
name: 'Settings', name: 'Settings',
setup() { setup() {
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const layoutStore = useLayoutStore();
const { getUiVersion } = useGetters(['getUiVersion']); const { getUiVersion } = useGetters(['getUiVersion']);
const { setLayoutLocked, resetTradingLayout, resetDashboardLayout } = useNamespacedActions(
StoreModules.layout,
[
LayoutActions.setLayoutLocked,
LayoutActions.resetTradingLayout,
LayoutActions.resetDashboardLayout,
],
);
const { getLayoutLocked } = useNamespacedGetters(StoreModules.layout, [
LayoutGetters.getLayoutLocked,
]);
const { addAlert } = useNamespacedActions(StoreModules.alerts, [AlertActions.addAlert]); const { addAlert } = useNamespacedActions(StoreModules.alerts, [AlertActions.addAlert]);
const timezoneOptions = ['UTC', Intl.DateTimeFormat().resolvedOptions().timeZone]; const timezoneOptions = ['UTC', Intl.DateTimeFormat().resolvedOptions().timeZone];
@ -69,27 +61,20 @@ export default defineComponent({
{ value: OpenTradeVizOptions.asTitle, text: 'Show in title' }, { value: OpenTradeVizOptions.asTitle, text: 'Show in title' },
{ value: OpenTradeVizOptions.noOpenTrades, text: "Don't show open trades in header" }, { value: OpenTradeVizOptions.noOpenTrades, text: "Don't show open trades in header" },
]; ];
const layoutLockedLocal: WritableComputedRef<boolean> = computed({
get(): boolean {
return getLayoutLocked.value;
},
set(value: boolean): void {
setLayoutLocked(value);
},
});
// //
const resetDynamicLayout = () => { const resetDynamicLayout = () => {
resetTradingLayout(); layoutStore.resetTradingLayout();
resetDashboardLayout(); layoutStore.resetDashboardLayout();
addAlert({ message: 'Layouts have been reset.' }); addAlert({ message: 'Layouts have been reset.' });
}; };
return { return {
getUiVersion, getUiVersion,
resetDynamicLayout, resetDynamicLayout,
settingsStore, settingsStore,
layoutStore,
timezoneOptions, timezoneOptions,
openTradesOptions, openTradesOptions,
layoutLockedLocal,
}; };
}, },
}); });

View File

@ -128,8 +128,6 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { namespace } from 'vuex-class';
import { GridLayout, GridItem, GridItemData } from 'vue-grid-layout'; import { GridLayout, GridItem, GridItemData } from 'vue-grid-layout';
import Balance from '@/components/ftbot/Balance.vue'; import Balance from '@/components/ftbot/Balance.vue';
@ -145,15 +143,14 @@ import Performance from '@/components/ftbot/Performance.vue';
import TradeDetail from '@/components/ftbot/TradeDetail.vue'; import TradeDetail from '@/components/ftbot/TradeDetail.vue';
import TradeList from '@/components/ftbot/TradeList.vue'; import TradeList from '@/components/ftbot/TradeList.vue';
import { Lock, Trade } from '@/types';
import { BotStoreGetters } from '@/store/modules/ftbot'; import { BotStoreGetters } from '@/store/modules/ftbot';
import { TradeLayout, findGridLayout, LayoutGetters, LayoutActions } from '@/store/modules/layout';
import StoreModules from '@/store/storeSubModules'; import StoreModules from '@/store/storeSubModules';
import { defineComponent, ref, computed } from '@vue/composition-api';
import { useNamespacedGetters } from 'vuex-composition-helpers';
import { useLayoutStore, findGridLayout, TradeLayout } from '@/stores/layout';
const ftbot = namespace(StoreModules.ftbot); export default defineComponent({
const layoutNs = namespace(StoreModules.layout); name: 'Trading',
@Component({
components: { components: {
Balance, Balance,
BotControls, BotControls,
@ -170,98 +167,105 @@ const layoutNs = namespace(StoreModules.layout);
TradeDetail, TradeDetail,
TradeList, TradeList,
}, },
}) setup() {
export default class Trading extends Vue { const {
@ftbot.Getter [BotStoreGetters.detailTradeId]!: number; detailTradeId,
openTrades,
closedTrades,
allTrades,
tradeDetail,
timeframe,
currentLocks,
whitelist,
stakeCurrency,
} = useNamespacedGetters(StoreModules.ftbot, [
BotStoreGetters.detailTradeId,
BotStoreGetters.openTrades,
BotStoreGetters.closedTrades,
BotStoreGetters.allTrades,
BotStoreGetters.tradeDetail,
BotStoreGetters.timeframe,
BotStoreGetters.currentLocks,
BotStoreGetters.whitelist,
BotStoreGetters.stakeCurrency,
]);
const layoutStore = useLayoutStore();
const currentBreakpoint = ref('');
@ftbot.Getter [BotStoreGetters.openTrades]!: Trade[]; const breakpointChanged = (newBreakpoint) => {
// console.log('breakpoint:', newBreakpoint);
@ftbot.Getter [BotStoreGetters.closedTrades]!: Trade[]; currentBreakpoint.value = newBreakpoint;
@ftbot.Getter [BotStoreGetters.allTrades]!: Trade[];
@ftbot.Getter [BotStoreGetters.tradeDetail]!: Trade;
@ftbot.Getter [BotStoreGetters.timeframe]!: string;
@ftbot.Getter [BotStoreGetters.currentLocks]!: Lock[];
@ftbot.Getter [BotStoreGetters.whitelist]!: string[];
@ftbot.Getter [BotStoreGetters.stakeCurrency]!: string;
@layoutNs.Getter [LayoutGetters.getTradingLayout]!: GridItemData[];
@layoutNs.Getter [LayoutGetters.getTradingLayoutSm]!: GridItemData[];
@layoutNs.Action [LayoutActions.setTradingLayout];
@layoutNs.Getter [LayoutGetters.getLayoutLocked]: boolean;
currentBreakpoint = '';
localGridLayout: GridItemData[] = [];
get isLayoutLocked() {
return this.getLayoutLocked || !this.isResizableLayout;
}
get isResizableLayout() {
return ['', 'sm', 'md', 'lg', 'xl'].includes(this.currentBreakpoint);
}
get gridLayout(): GridItemData[] {
if (this.isResizableLayout) {
return this.getTradingLayout;
}
return this.localGridLayout;
}
set gridLayout(newLayout) {
// Dummy setter to make gridLayout happy. Updates happen through layoutUpdated.
}
get gridLayoutMultiPane(): GridItemData {
return findGridLayout(this.gridLayout, TradeLayout.multiPane);
}
get gridLayoutOpenTrades(): GridItemData {
return findGridLayout(this.gridLayout, TradeLayout.openTrades);
}
get gridLayoutTradeHistory(): GridItemData {
return findGridLayout(this.gridLayout, TradeLayout.tradeHistory);
}
get gridLayoutTradeDetail(): GridItemData {
return findGridLayout(this.gridLayout, TradeLayout.tradeDetail);
}
get gridLayoutChartView(): GridItemData {
return findGridLayout(this.gridLayout, TradeLayout.chartView);
}
mounted() {
this.localGridLayout = [...this.getTradingLayoutSm];
}
layoutUpdatedEvent(newLayout) {
if (this.isResizableLayout) {
this.setTradingLayout(newLayout);
}
}
get responsiveGridLayouts() {
return {
sm: this[LayoutGetters.getTradingLayoutSm],
}; };
} const isResizableLayout = computed(() =>
['', 'sm', 'md', 'lg', 'xl'].includes(currentBreakpoint.value),
);
const isLayoutLocked = computed(() => {
return layoutStore.layoutLocked || !isResizableLayout;
});
const gridLayout = computed((): GridItemData[] => {
if (isResizableLayout) {
return layoutStore.tradingLayout;
}
return [...layoutStore.getTradingLayoutSm];
});
breakpointChanged(newBreakpoint) { const gridLayoutMultiPane = computed(() => {
console.log('breakpoint:', newBreakpoint); return findGridLayout(gridLayout.value, TradeLayout.multiPane);
this.currentBreakpoint = newBreakpoint; });
}
} const gridLayoutOpenTrades = computed(() => {
return findGridLayout(gridLayout.value, TradeLayout.openTrades);
});
const gridLayoutTradeHistory = computed(() => {
return findGridLayout(gridLayout.value, TradeLayout.tradeHistory);
});
const gridLayoutTradeDetail = computed(() => {
return findGridLayout(gridLayout.value, TradeLayout.tradeDetail);
});
const gridLayoutChartView = computed(() => {
return findGridLayout(gridLayout.value, TradeLayout.chartView);
});
const responsiveGridLayouts = computed(() => {
return {
sm: layoutStore.getTradingLayoutSm,
};
});
const layoutUpdatedEvent = (newLayout) => {
if (isResizableLayout) {
layoutStore.tradingLayout = newLayout;
}
};
return {
detailTradeId,
openTrades,
closedTrades,
allTrades,
tradeDetail,
timeframe,
currentLocks,
whitelist,
stakeCurrency,
layoutStore,
breakpointChanged,
layoutUpdatedEvent,
isLayoutLocked,
gridLayout,
gridLayoutMultiPane,
gridLayoutOpenTrades,
gridLayoutTradeHistory,
gridLayoutTradeDetail,
gridLayoutChartView,
responsiveGridLayouts,
isResizableLayout,
};
},
});
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -5319,6 +5319,11 @@ vue-template-es2015-compiler@^1.9.0, vue-template-es2015-compiler@^1.9.1:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue2-helpers@^1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/vue2-helpers/-/vue2-helpers-1.1.7.tgz#f105313979af0260ef446c583fd2fa75b067afd1"
integrity sha512-NLF7bYFPyoKMvn/Bkxr7+7Ure/kZpWmd6pQpG613dT0Sn6EwI+2+LwVUQyDkDk4P0UaAwvn/QEYzhBRzDzuGLw==
vue@^2.6.14: vue@^2.6.14:
version "2.6.14" version "2.6.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235" resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"