Add confirm dialog for reannounce and recheck

This commit is contained in:
CzBiX
2020-03-25 13:52:53 +08:00
parent 5bbd9a4fd7
commit a78a5663f5
12 changed files with 3576 additions and 3222 deletions

6466
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@
},
"dependencies": {
"@mdi/font": "^3.6.95",
"@vue/composition-api": "^0.5.0",
"axios": "^0.19.1",
"core-js": "^3.6.3",
"dayjs": "^1.8.19",

View File

@@ -47,6 +47,9 @@
>
<app-footer />
</v-footer>
<GlobalDialog />
<GlobalSnackBar />
</v-app>
</template>
@@ -56,6 +59,9 @@ import {
mapActions, mapGetters, mapState, mapMutations,
} from 'vuex';
import Axios, { AxiosError } from 'axios';
import GlobalDialog from './components/GlobalDialog.vue';
import GlobalSnackBar from './components/GlobalSnackBar.vue';
import AddForm from './components/AddForm.vue';
import Drawer from './components/Drawer.vue';
import LoginForm from './components/LoginForm.vue';
@@ -63,6 +69,7 @@ import MainToolbar from './components/MainToolbar.vue';
import Torrents from './components/Torrents.vue';
import AppFooter from './components/Footer.vue';
import LogsDialog from './components/dialogs/LogsDialog.vue';
import api from './Api';
import { sleep } from './utils';
@@ -78,6 +85,8 @@ export default Vue.extend({
AppFooter,
LogsDialog,
MainToolbar,
GlobalDialog,
GlobalSnackBar,
},
data() {
return {

View File

@@ -0,0 +1,104 @@
<template>
<v-dialog
v-bind="config"
v-model="value"
>
<v-card v-if="!!config">
<v-card-title v-text="content.title" />
<v-card-text v-text="content.text" />
<v-card-actions>
<v-spacer />
<v-btn
v-for="(btn, index) in btns"
:key="index"
color="info"
text
@click="clickBtn(btn[1])"
>
{{ btn[0] }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import Vue from 'vue';
import {
computed, ref, watch, Ref,
} from '@vue/composition-api';
import { DialogType, DialogConfig } from '@/store/types';
import { useMutations, useState } from '@/store';
import { timeout } from '@/utils';
const BUTTONS = {
[DialogType.Alert]: [
['Close', false],
],
[DialogType.YesNo]: [
['No', false],
['Yes', true],
],
[DialogType.OkCancel]: [
['Cancel', false],
['OK', true],
],
};
const DefaultConfig = {
width: '25%',
};
export default {
setup() {
const mutations = useMutations(['closeDialog']);
const { config: userConfig } = useState(['config'], 'dialog');
const config = computed(() => {
if (!userConfig.value) {
return null;
}
return Object.assign(DefaultConfig, userConfig.value) as DialogConfig;
});
const content = computed(() => (config.value ? config.value.content : null));
const value = ref<boolean>();
async function clickBtn(btnValue: any) {
const cb = content.value!.callback;
if (cb) {
cb(btnValue);
}
mutations.closeDialog();
}
watch(config, (v) => {
value.value = !!v;
});
watch(value, async (v) => {
if (!v && !!config.value) {
clickBtn(null);
}
}, { lazy: true });
const btns = computed(() => {
const c = content.value;
const dialogType = (c && c.type) ? c.type : DialogType.Alert;
if (dialogType === DialogType.Custom) {
return c!.buttons;
}
return BUTTONS[dialogType];
});
return {
config,
content,
value,
btns,
clickBtn,
};
},
};
</script>

View File

@@ -0,0 +1,54 @@
<template>
<v-snackbar
v-if="!!config"
v-bind="config"
:value="true"
@input="changed"
>
{{ config.text }}
<v-btn
text
color="info"
@click="clickBtn"
>
{{ config.btnText ? config.btnText : 'Close' }}
</v-btn>
</v-snackbar>
</template>
<script>
import { useMutations, useState } from '@/store';
import { timeout } from '@/utils';
export default {
setup() {
const mutations = useMutations(['closeSnackBar']);
const { config } = useState(['config'], 'snackBar');
async function changed(v) {
if (v) {
return;
}
await timeout(150);
mutations.closeSnackBar();
}
function clickBtn() {
const cb = config.value.callback;
changed(false);
if (cb) {
cb();
}
}
return {
config,
changed,
clickBtn,
};
},
};
</script>

View File

@@ -167,6 +167,7 @@ import api from '../Api';
import { formatSize, formatDuration } from '../filters';
import { torrentIsState } from '../utils';
import { StateType } from '../consts';
import { DialogType } from '../store/types';
function getStateInfo(state: string) {
let icon;
@@ -365,6 +366,8 @@ export default Vue.extend({
methods: {
...mapMutations([
'updateConfig',
'showDialog',
'showSnackBar',
]),
confirmDelete() {
this.toDelete = this.selectedRows;
@@ -382,10 +385,43 @@ export default Vue.extend({
if (!this.hasSelected) {
this.selectedRows = this.allTorrents;
}
const v = await new Promise((resolve) => {
this.showDialog({
content: {
title: 'Reannounce Torrents',
text: 'Are you sure want to reannounce torrents?',
type: DialogType.OkCancel,
callback: resolve,
},
});
});
if (!v) {
return;
}
await api.reannounceTorrents(this.selectedHashes);
this.showSnackBar('Reannounced');
},
async recheckTorrents() {
const v = await new Promise((resolve) => {
this.showDialog({
content: {
title: 'Recheck Torrents',
text: 'Are you sure want to recheck torrents?',
type: DialogType.OkCancel,
callback: resolve,
},
});
});
if (!v) {
return;
}
await api.recheckTorrents(this.selectedHashes);
this.showSnackBar('Rechecking');
},
setTorrentsCategory(category: string) {
this.categoryToSet = category;

View File

@@ -1,5 +1,6 @@
import Vue from 'vue';
import vuetify from './plugins/vuetify';
import './plugins/composition-api';
import store from './store';
// import router from './router';
import './filters';

View File

@@ -0,0 +1,4 @@
import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';
Vue.use(VueCompositionApi);

27
src/store/dialog.ts Normal file
View File

@@ -0,0 +1,27 @@
import _ from 'lodash';
import { Module } from 'vuex';
import { DialogState } from './types';
export const dialogStore : Module<DialogState, any> = {
state() {
return {
config: null,
};
},
mutations: {
showDialog(state, payload) {
if (_.isString(payload)) {
state.config = {
content: {
text: payload,
},
};
} else {
state.config = _.cloneDeep(payload);
}
},
closeDialog(state) {
state.config = null;
},
},
};

View File

@@ -1,16 +1,22 @@
import _ from 'lodash';
import Vue from 'vue';
import Vuex from 'vuex';
import _ from 'lodash';
import { computed, Ref } from '@vue/composition-api';
import { configStore } from './config';
import { dialogStore } from './dialog';
import { snackBarStore } from './snackBar';
import { AllStateTypes } from '../consts';
import { torrentIsState } from '../utils';
import { RootState } from './types';
Vue.use(Vuex);
export default new Vuex.Store<RootState>({
const store = new Vuex.Store<RootState>({
modules: {
config: configStore,
dialog: dialogStore,
snackBar: snackBarStore,
},
state: {
rid: 0,
@@ -109,3 +115,32 @@ export default new Vuex.Store<RootState>({
},
},
});
export default store;
export function useStore() {
return store;
}
export function useMutations(mutations: [string], namespace?: string) {
const result: {[key: string]: () => any} = {};
mutations.forEach((m) => {
const method = namespace ? `${namespace}/${m}` : m;
result[m] = (..._args) => store.commit(method, ..._args);
});
return result;
}
export function useState(states: [string], namespace?: string) {
const state = namespace ? (store.state as any)[namespace] : store.state;
const result: {[key: string]: Readonly<Ref<Readonly<any>>>} = {};
states.forEach((s) => {
result[s] = computed(() => state[s]);
});
return result;
}

25
src/store/snackBar.ts Normal file
View File

@@ -0,0 +1,25 @@
import _ from 'lodash';
import { Module } from 'vuex';
import { SnackBarState } from './types';
export const snackBarStore : Module<SnackBarState, any> = {
state() {
return {
config: null,
};
},
mutations: {
showSnackBar(state, payload) {
if (_.isString(payload)) {
state.config = {
text: payload,
};
} else {
state.config = _.cloneDeep(payload);
}
},
closeSnackBar(state) {
state.config = null;
},
},
};

View File

@@ -10,3 +10,35 @@ export interface RootState {
export interface ConfigState {
userConfig: any;
}
export enum DialogType {
Alert,
YesNo,
OkCancel,
Custom,
}
export interface DialogConfig {
content: {
title?: String,
text: String,
callback?: CallableFunction,
type?: DialogType,
buttons?: any,
},
width?: string,
}
export interface DialogState {
config: DialogConfig | null,
}
export interface SnackBarConfig {
text: string,
btnText?: string,
callback?: CallableFunction,
}
export interface SnackBarState {
config: SnackBarConfig | null,
}