mirror of
https://github.com/CzBiX/qb-web.git
synced 2026-03-24 22:10:14 +08:00
Add search plugin manager foundation (#58)
This commit is contained in:
@@ -307,6 +307,15 @@ class Api {
|
||||
return this.axios.get(`/search/results?id=${id}`).then(Api.handleResponse);
|
||||
}
|
||||
|
||||
public enablePlugin(plugin: SearchPlugin, enable: boolean) {
|
||||
const body = new URLSearchParams({
|
||||
names: plugin.name,
|
||||
enable: JSON.stringify(enable)
|
||||
});
|
||||
|
||||
return this.axios.post('/search/enablePlugin', body).then(Api.handleResponse);
|
||||
}
|
||||
|
||||
private actionTorrents(action: string, hashes: string[], extra?: any) {
|
||||
const params: any = {
|
||||
hashes: hashes.join('|'),
|
||||
|
||||
49
src/components/dialogs/searchDialog/PluginsManager.vue
Normal file
49
src/components/dialogs/searchDialog/PluginsManager.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="searchEngineState.isPluginManagerOpen"
|
||||
max-width="20rem"
|
||||
scrollable
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title> <v-icon>mdi-toy-brick</v-icon> Plugins manager </v-card-title>
|
||||
<v-card-text>
|
||||
<v-switch
|
||||
v-for="(plugin, key) in searchEngineState.searchPlugins"
|
||||
:key="key"
|
||||
:input-value="plugin.enabled"
|
||||
:label="plugin.fullName"
|
||||
@change="togglePluginAvailability(plugin)"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { SearchEnginePage } from "@/store/types";
|
||||
import { SearchPlugin } from '@/types';
|
||||
import Vue from "vue";
|
||||
import Component from "vue-class-component";
|
||||
import { mapActions, mapState } from "vuex";
|
||||
|
||||
@Component({
|
||||
computed: {
|
||||
...mapState({
|
||||
searchEngineState: "searchEngine"
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
togglePluginAvailabilityAction: "togglePluginAvailability"
|
||||
})
|
||||
}
|
||||
})
|
||||
export default class PluginsManager extends Vue {
|
||||
searchEngineState!: SearchEnginePage;
|
||||
togglePluginAvailabilityAction!: (_: any) => void;
|
||||
|
||||
togglePluginAvailability(plugin: SearchPlugin) {
|
||||
this.togglePluginAvailabilityAction(plugin);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -48,9 +48,14 @@
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
<v-card-actions />
|
||||
<v-card-actions>
|
||||
<v-btn @click="openPluginManager">
|
||||
<v-icon>mdi-cog</v-icon> Plugins manager
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<PluginManager />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -59,9 +64,10 @@ import api from "@/Api";
|
||||
import HasTask from "@/mixins/hasTask";
|
||||
import { Component, Prop, Emit } from "vue-property-decorator";
|
||||
import { SearchTaskTorrent } from "@/types";
|
||||
import { mapGetters, mapMutations } from "vuex";
|
||||
import { mapActions, mapGetters, mapMutations } from "vuex";
|
||||
import { tr } from "@/locale";
|
||||
import SearchDialogForm from "./SearchDialogForm.vue";
|
||||
import PluginManager from "./PluginsManager.vue";
|
||||
|
||||
interface GridConfig {
|
||||
searchItems: SearchTaskTorrent[];
|
||||
@@ -71,7 +77,8 @@ interface GridConfig {
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
SearchDialogForm
|
||||
SearchDialogForm,
|
||||
PluginManager
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@@ -80,7 +87,10 @@ interface GridConfig {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["openAddForm", "setPasteUrl", "addFormDownloadItem"])
|
||||
...mapMutations(["openAddForm", "setPasteUrl", "addFormDownloadItem", "openPluginManager"]),
|
||||
...mapActions({
|
||||
loadSearchPlugins: 'fetchSearchPlugins'
|
||||
})
|
||||
}
|
||||
})
|
||||
export default class SearchDialog extends HasTask {
|
||||
@@ -115,6 +125,12 @@ export default class SearchDialog extends HasTask {
|
||||
setPasteUrl!: (_: any) => void;
|
||||
openAddForm!: () => void;
|
||||
addFormDownloadItem!: (_: any) => void;
|
||||
loadSearchPlugins!: () => void;
|
||||
openPluginManager!: () => void;
|
||||
|
||||
mounted() {
|
||||
this.loadSearchPlugins(); // load the plugins so they are available in the entire module
|
||||
}
|
||||
|
||||
async downloadTorrent(item: SearchTaskTorrent) {
|
||||
this.addFormDownloadItem({
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
</v-col>
|
||||
<v-col class="col__plugins">
|
||||
<v-btn
|
||||
:loading="searchPlugins === undefined"
|
||||
:disabled="searchPlugins === null"
|
||||
type="button"
|
||||
class="btn"
|
||||
@click="plugginSelectorOpen = true"
|
||||
@@ -113,7 +115,8 @@ import { Component, Emit, Prop, Watch } from "vue-property-decorator";
|
||||
import { SearchPlugin } from "@/types";
|
||||
import { tr } from "@/locale";
|
||||
import { intersection } from "lodash";
|
||||
import api from "@/Api";
|
||||
import { mapGetters } from "vuex";
|
||||
import { SearchEnginePage } from '@/store/types';
|
||||
|
||||
const ALL_KEY = "all";
|
||||
|
||||
@@ -134,8 +137,17 @@ export interface SearchForm {
|
||||
plugins: SearchPlugin[];
|
||||
}
|
||||
|
||||
@Component({})
|
||||
@Component({
|
||||
computed: {
|
||||
...mapGetters({
|
||||
searchPlugins: "allSearchPlugins"
|
||||
})
|
||||
},
|
||||
})
|
||||
export default class SearchDialogForm extends Vue {
|
||||
searchEngineState!: SearchEnginePage;
|
||||
searchPlugins!: SearchPlugin[];
|
||||
|
||||
@Prop(Boolean)
|
||||
readonly loading: boolean = false;
|
||||
|
||||
@@ -151,7 +163,7 @@ export default class SearchDialogForm extends Vue {
|
||||
};
|
||||
|
||||
get hasSelectAllPlugins() {
|
||||
return this.searchForm.plugins.length === this.availablePlugins.length;
|
||||
return this.searchForm.plugins.length === this.availablePlugins?.length;
|
||||
}
|
||||
|
||||
get availableCategories() {
|
||||
@@ -169,36 +181,27 @@ export default class SearchDialogForm extends Vue {
|
||||
return result;
|
||||
}
|
||||
|
||||
get allPluginIcon() {
|
||||
if (this.hasSelectAllPlugins) return "mdi-checkbox-marked";
|
||||
if (this.searchForm.plugins.length) return "mdi-minus-box";
|
||||
return "mdi-checkbox-blank-outline";
|
||||
}
|
||||
|
||||
toggleSelectAll() {
|
||||
this.searchForm.plugins = this.hasSelectAllPlugins ? [] : this.availablePlugins.slice();
|
||||
}
|
||||
|
||||
async mounted() {
|
||||
this.availablePlugins = await this.getAvailablePlugins();
|
||||
this.toggleSelectAll();
|
||||
@Watch("searchPlugins")
|
||||
searchPluginsUpdated(plugins: SearchPlugin[] | undefined | null) {
|
||||
if (!plugins) {
|
||||
this.availablePlugins = [];
|
||||
} else {
|
||||
this.availablePlugins = this.searchPlugins.filter(x => x.enabled);
|
||||
this.toggleSelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
async getAvailablePlugins(): Promise<SearchPlugin[]> {
|
||||
const availablePlugins = await api.getSearchPlugins();
|
||||
|
||||
return availablePlugins
|
||||
.filter(plugin => plugin.enabled === true)
|
||||
.sort((p1, p2) => p1.fullName.localeCompare(p2.fullName));
|
||||
}
|
||||
|
||||
@Emit('triggerSearch')
|
||||
@Emit("triggerSearch")
|
||||
triggerSearch(): SearchForm | void {
|
||||
if (!this.searchForm.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const plugins = this.hasSelectAllPlugins
|
||||
const plugins = this.hasSelectAllPlugins
|
||||
? ALL_KEY
|
||||
: this.searchForm.plugins.map(p => p.name).join("|");
|
||||
|
||||
@@ -209,7 +212,7 @@ export default class SearchDialogForm extends Vue {
|
||||
return searchForm;
|
||||
}
|
||||
|
||||
@Emit('stopSearch')
|
||||
@Emit("stopSearch")
|
||||
stopSearch() {
|
||||
//
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { snackBarStore } from './snackBar';
|
||||
import { addFormStore } from './addForm';
|
||||
import { AllStateTypes } from '../consts';
|
||||
import { torrentIsState } from '../utils';
|
||||
import searchEngineStore from './searchEngine';
|
||||
import { RootState } from './types';
|
||||
|
||||
Vue.use(Vuex);
|
||||
@@ -18,7 +19,8 @@ const store = new Vuex.Store<RootState>({
|
||||
config: configStore,
|
||||
dialog: dialogStore,
|
||||
snackBar: snackBarStore,
|
||||
addForm: addFormStore
|
||||
addForm: addFormStore,
|
||||
searchEngine: searchEngineStore
|
||||
},
|
||||
state: {
|
||||
rid: 0,
|
||||
@@ -117,7 +119,7 @@ const store = new Vuex.Store<RootState>({
|
||||
|
||||
return result;
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
export default store;
|
||||
|
||||
65
src/store/searchEngine.ts
Normal file
65
src/store/searchEngine.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Module } from "vuex";
|
||||
import { SearchPlugin } from "@/types";
|
||||
import { SearchEnginePage } from "./types";
|
||||
import api from "@/Api";
|
||||
|
||||
export default {
|
||||
state: {
|
||||
searchPlugins: [],
|
||||
isPluginManagerOpen: false
|
||||
},
|
||||
mutations: {
|
||||
setSearchPlugins(state, plugins: SearchPlugin[] | undefined | null) {
|
||||
state.searchPlugins = plugins;
|
||||
},
|
||||
openPluginManager(state) {
|
||||
state.isPluginManagerOpen = true;
|
||||
},
|
||||
closePluginManager(state) {
|
||||
state.isPluginManagerOpen = false;
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
allSearchPlugins(state): SearchPlugin[] | undefined | null {
|
||||
return state.searchPlugins;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchSearchPlugins({ dispatch }) {
|
||||
// semantic helper
|
||||
dispatch("getSearchPluginsRequest");
|
||||
},
|
||||
async getSearchPluginsRequest({ dispatch }) {
|
||||
try {
|
||||
const searchPlugins = await api.getSearchPlugins();
|
||||
|
||||
dispatch("getSearchPluginRequestSuccess", searchPlugins);
|
||||
} catch {
|
||||
dispatch("getSearchPluginsRequestFailure");
|
||||
}
|
||||
},
|
||||
getSearchPluginRequestSuccess({ commit }, searchPlugins) {
|
||||
commit("setSearchPlugins", undefined);
|
||||
|
||||
commit("setSearchPlugins", searchPlugins);
|
||||
},
|
||||
getSearchPluginRequestFailure({ commit }) {
|
||||
commit("setSearchPlugins", null);
|
||||
},
|
||||
togglePluginAvailability({ dispatch }, plugin) {
|
||||
dispatch("togglePluginEnableRequest", plugin);
|
||||
},
|
||||
async togglePluginEnableRequest({ dispatch }, plugin: SearchPlugin) {
|
||||
try {
|
||||
await api.enablePlugin(plugin, !plugin.enabled); // switch plugin enable state
|
||||
|
||||
dispatch("enablePluginRequestSuccess", plugin);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
},
|
||||
enablePluginRequestSuccess({ dispatch }) {
|
||||
dispatch('fetchSearchPlugins'); // refresh the plugins
|
||||
}
|
||||
}
|
||||
} as Module<SearchEnginePage, any>;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MainData } from '@/types';
|
||||
import { MainData, SearchPlugin } from '@/types';
|
||||
import { Config } from './config';
|
||||
|
||||
export interface RootState {
|
||||
@@ -8,6 +8,11 @@ export interface RootState {
|
||||
pasteUrl: string | null;
|
||||
}
|
||||
|
||||
export interface SearchEnginePage {
|
||||
searchPlugins: SearchPlugin[] | null | undefined;
|
||||
isPluginManagerOpen: boolean;
|
||||
}
|
||||
|
||||
export interface AddFormState {
|
||||
isOpen: boolean;
|
||||
downloadItem: {
|
||||
|
||||
Reference in New Issue
Block a user