Merge branch 'main' into pr/Tako88/1265

This commit is contained in:
Matthias 2023-06-04 08:36:35 +02:00
commit af05f9fc88
11 changed files with 129 additions and 51 deletions

View File

@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" data-theme="dark"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
@ -8,11 +9,14 @@
<title>FreqUI</title> <title>FreqUI</title>
</head> </head>
<body>
<body data-bs-theme="dark">
<noscript> <noscript>
<strong>We're sorry but FreqUI doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> <strong>We're sorry but FreqUI doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript> </noscript>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

View File

@ -6,8 +6,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useColorMode } from 'bootstrap-vue-next';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
const mode = useColorMode();
const activeTheme = ref(''); const activeTheme = ref('');
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
@ -18,17 +21,14 @@ const setTheme = (themeName: string) => {
} }
if (themeName.toLowerCase() === 'bootstrap' || themeName.toLowerCase() === 'bootstrap_dark') { if (themeName.toLowerCase() === 'bootstrap' || themeName.toLowerCase() === 'bootstrap_dark') {
// const styles = document.getElementsByTagName('style'); // const styles = document.getElementsByTagName('style');
document.documentElement.setAttribute(
'data-theme',
themeName.toLowerCase() === 'bootstrap' ? 'light' : 'dark',
);
if (activeTheme.value) { if (activeTheme.value) {
// Only transition if simple mode is active // Only transition if simple mode is active
document.documentElement.classList.add('ft-theme-transition'); document.body.classList.add('ft-theme-transition');
window.setTimeout(() => { window.setTimeout(() => {
document.documentElement.classList.remove('ft-theme-transition'); document.body.classList.remove('ft-theme-transition');
}, 1000); }, 1000);
} }
mode.value = themeName.toLowerCase() === 'bootstrap' ? 'light' : 'dark';
} }
// Save the theme as localstorage // Save the theme as localstorage
settingsStore.currentTheme = themeName; settingsStore.currentTheme = themeName;

View File

@ -3,10 +3,12 @@
v-model="plotStore.plotConfigName" v-model="plotStore.plotConfigName"
:allow-edit="allowEdit" :allow-edit="allowEdit"
:allow-add="allowEdit" :allow-add="allowEdit"
:allow-duplicate="allowEdit"
editable-name="plot configuration" editable-name="plot configuration"
@rename="plotStore.renamePlotConfig" @rename="plotStore.renamePlotConfig"
@delete="plotStore.deletePlotConfig" @delete="plotStore.deletePlotConfig"
@new="plotStore.newPlotConfig" @new="plotStore.newPlotConfig"
@duplicate="plotStore.duplicatePlotConfig"
> >
<b-form-select <b-form-select
v-model="plotStore.plotConfigName" v-model="plotStore.plotConfigName"

View File

@ -1,22 +1,31 @@
<template> <template>
<div class="d-flex flex-row"> <div class="d-flex flex-row">
<div class="flex-grow-1"> <div class="flex-grow-1">
<slot v-if="!editing"> </slot> <slot v-if="mode === EditState.None"> </slot>
<b-form-input v-else v-model="localName" size="sm"> </b-form-input> <b-form-input v-else v-model="localName" size="sm"> </b-form-input>
</div> </div>
<div <div
class="flex-grow-2 mt-auto d-flex gap-1 ms-1" class="flex-grow-2 mt-auto d-flex gap-1 ms-1"
:class="alignVertical ? 'flex-column' : 'flex-row'" :class="alignVertical ? 'flex-column' : 'flex-row'"
> >
<template v-if="allowEdit && !(addNew || editing)"> <template v-if="allowEdit && mode === EditState.None">
<b-button <b-button
size="sm" size="sm"
variant="secondary" variant="secondary"
:title="`Edit this ${editableName}.`" :title="`Edit this ${editableName}.`"
@click="editing = true" @click="mode = EditState.Editing"
> >
<i-mdi-pencil /> <i-mdi-pencil />
</b-button> </b-button>
<b-button
v-if="allowDuplicate"
size="sm"
variant="secondary"
:title="`Duplicate ${editableName}.`"
@click="duplicate"
>
<i-mdi-content-copy />
</b-button>
<b-button <b-button
size="sm" size="sm"
variant="secondary" variant="secondary"
@ -27,14 +36,14 @@
</b-button> </b-button>
</template> </template>
<b-button <b-button
v-if="allowAdd && !(addNew || editing)" v-if="allowAdd && mode === EditState.None"
size="sm" size="sm"
:title="`Add new ${editableName}.`" :title="`Add new ${editableName}.`"
variant="primary" variant="primary"
@click="addNewClick" @click="addNewClick"
><i-mdi-plus-box-outline /> ><i-mdi-plus-box-outline />
</b-button> </b-button>
<template v-if="addNew || editing"> <template v-if="mode !== EditState.None">
<b-button <b-button
size="sm" size="sm"
:title="`Add new '${editableName}`" :title="`Add new '${editableName}`"
@ -67,6 +76,10 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
allowDuplicate: {
type: Boolean,
default: false,
},
editableName: { editableName: {
type: String, type: String,
required: true, required: true,
@ -78,26 +91,37 @@ const props = defineProps({
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'delete', value: string): void; delete: [value: string];
(e: 'new', value: string): void; new: [value: string];
(e: 'rename', oldName: string, newName: string): void; duplicate: [oldName: string, newName: string];
rename: [oldName: string, newName: string];
}>(); }>();
const addNew = ref(false); enum EditState {
None,
Editing,
Adding,
Duplicating,
}
const localName = ref<string>(props.modelValue); const localName = ref<string>(props.modelValue);
const editing = ref<boolean>(false); const mode = ref<EditState>(EditState.None);
function abort() { function abort() {
editing.value = false; mode.value = EditState.None;
addNew.value = false;
localName.value = props.modelValue; localName.value = props.modelValue;
} }
function duplicate() {
localName.value = localName.value + ' (copy)';
mode.value = EditState.Duplicating;
}
function addNewClick() { function addNewClick() {
localName.value = ''; localName.value = '';
addNew.value = true; mode.value = EditState.Adding;
editing.value = true;
} }
watch( watch(
() => props.modelValue, () => props.modelValue,
() => { () => {
@ -106,13 +130,14 @@ watch(
); );
function saveNewName() { function saveNewName() {
editing.value = false; if (mode.value === EditState.Adding) {
if (addNew.value) {
addNew.value = false;
emit('new', localName.value); emit('new', localName.value);
} else if (mode.value === EditState.Duplicating) {
emit('duplicate', props.modelValue, localName.value);
} else { } else {
// Editing // Editing
emit('rename', props.modelValue, localName.value); emit('rename', props.modelValue, localName.value);
} }
mode.value = EditState.None;
} }
</script> </script>

View File

@ -37,6 +37,12 @@ import {
BotDescriptor, BotDescriptor,
BgTaskStarted, BgTaskStarted,
BackgroundTaskStatus, BackgroundTaskStatus,
Exchange,
ExchangeListResult,
FreqAIModelListResult,
PairlistEvalResponse,
PairlistsPayload,
PairlistsResponse,
} from '@/types'; } from '@/types';
import axios, { AxiosResponse } from 'axios'; import axios, { AxiosResponse } from 'axios';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
@ -44,12 +50,6 @@ import { showAlert } from './alerts';
import { useWebSocket } from '@vueuse/core'; import { useWebSocket } from '@vueuse/core';
import { FTWsMessage, FtWsMessageTypes } from '@/types/wsMessageTypes'; import { FTWsMessage, FtWsMessageTypes } from '@/types/wsMessageTypes';
import { showNotification } from '@/shared/notifications'; import { showNotification } from '@/shared/notifications';
import {
FreqAIModelListResult,
PairlistEvalResponse,
PairlistsPayload,
PairlistsResponse,
} from '../types';
export function createBotSubStore(botId: string, botName: string) { export function createBotSubStore(botId: string, botName: string) {
const userService = useUserService(botId); const userService = useUserService(botId);
@ -91,6 +91,7 @@ export function createBotSubStore(botId: string, botName: string) {
strategyPlotConfig: undefined as PlotConfig | undefined, strategyPlotConfig: undefined as PlotConfig | undefined,
strategyList: [] as string[], strategyList: [] as string[],
freqaiModelList: [] as string[], freqaiModelList: [] as string[],
exchangeList: [] as Exchange[],
strategy: {} as StrategyResult, strategy: {} as StrategyResult,
pairlist: [] as string[], pairlist: [] as string[],
currentLocks: undefined as LockResponse | undefined, currentLocks: undefined as LockResponse | undefined,
@ -448,6 +449,16 @@ export function createBotSubStore(botId: string, botName: string) {
return Promise.reject(error); return Promise.reject(error);
} }
}, },
async getExchangeList() {
try {
const { data } = await api.get<ExchangeListResult>('/exchanges');
this.exchangeList = data.exchanges;
return Promise.resolve(data.exchanges);
} catch (error) {
console.error(error);
return Promise.reject(error);
}
},
async getAvailablePairs(payload: AvailablePairPayload) { async getAvailablePairs(payload: AvailablePairPayload) {
try { try {
const { data } = await api.get<AvailablePairResult>('/available_pairs', { const { data } = await api.get<AvailablePairResult>('/available_pairs', {

View File

@ -72,6 +72,11 @@ export const usePlotConfigStore = defineStore('plotConfig', {
this.editablePlotConfig = deepClone(this.customPlotConfigs[this.plotConfigName]); this.editablePlotConfig = deepClone(this.customPlotConfigs[this.plotConfigName]);
} }
}, },
duplicatePlotConfig(oldName: string, newName: string) {
console.log(oldName, newName);
this.customPlotConfigs[newName] = deepClone(this.customPlotConfigs[oldName]);
this.plotConfigChanged(newName);
},
}, },
persist: { persist: {
key: FT_PLOT_CONFIG_KEY, key: FT_PLOT_CONFIG_KEY,

View File

@ -2,3 +2,6 @@
// $body-bg: rgb(42, 42, 49); // $body-bg: rgb(42, 42, 49);
$font-size-base: 0.9rem; $font-size-base: 0.9rem;
$primary: #0089a1; $primary: #0089a1;
$body-bg-dark: #121212;
$body-color-dark: #dedede;

View File

@ -41,7 +41,7 @@
font-size: 0.8rem; font-size: 0.8rem;
} }
[data-theme="dark"] { [data-bs-theme="dark"] {
$bg-dark: rgb(18, 18, 18); $bg-dark: rgb(18, 18, 18);
$bg-darker: darken($bg-dark, 5%); $bg-darker: darken($bg-dark, 5%);
@ -235,11 +235,14 @@
background-color: unset !important; background-color: unset !important;
} }
html.ft-theme-transition, body.ft-theme-transition,
html.ft-theme-transition *, body.ft-theme-transition *,
html.ft-theme-transition *:before, body.ft-theme-transition *:before,
html.ft-theme-transition *:after { body.ft-theme-transition *:after {
transition: background 750ms ease-in-out, transition:
border-color 750ms ease-in-out; background 750ms ease-in-out,
border-color 750ms ease-in-out,
background-color 750ms ease-in-out,
;
transition-delay: 0 !important; transition-delay: 0 !important;
} }

18
src/types/exchange.ts Normal file
View File

@ -0,0 +1,18 @@
import { MarginMode, TradingMode } from './types';
export interface TradeMode {
trading_mode: TradingMode;
margin_mode: MarginMode;
}
export interface Exchange {
name: string;
valid: boolean;
supported: boolean;
comment: string;
trade_modes: TradeMode[];
}
export interface ExchangeListResult {
exchanges: Exchange[];
}

View File

@ -5,6 +5,7 @@ export * from './balance';
export * from './blacklist'; export * from './blacklist';
export * from './botComparison'; export * from './botComparison';
export * from './chart'; export * from './chart';
export * from './exchange';
export * from './daily'; export * from './daily';
export * from './gridLayout'; export * from './gridLayout';
export * from './locks'; export * from './locks';

View File

@ -80,6 +80,12 @@ export enum TradingMode {
FUTURES = 'futures', FUTURES = 'futures',
} }
export enum MarginMode {
NONE = 'none',
ISOLATED = 'isolated',
// CROSS = 'cross',
}
export interface UnfilledTimeout { export interface UnfilledTimeout {
/** @deprecated replaced by entry in 2.x */ /** @deprecated replaced by entry in 2.x */
buy?: number; buy?: number;