mirror of
https://github.com/CzBiX/qb-web.git
synced 2026-04-13 18:01:17 +08:00
Add confirm dialog for reannounce and recheck
This commit is contained in:
6466
package-lock.json
generated
6466
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
104
src/components/GlobalDialog.vue
Normal file
104
src/components/GlobalDialog.vue
Normal 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>
|
||||
54
src/components/GlobalSnackBar.vue
Normal file
54
src/components/GlobalSnackBar.vue
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
4
src/plugins/composition-api.ts
Normal file
4
src/plugins/composition-api.ts
Normal 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
27
src/store/dialog.ts
Normal 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;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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
25
src/store/snackBar.ts
Normal 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;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user