Add search plugin manager foundation (#58)

This commit is contained in:
Bogdan Bogdanov
2020-09-24 09:42:57 +03:00
committed by GitHub
parent b2438035fb
commit 89938adafa
7 changed files with 179 additions and 30 deletions

View File

@@ -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('|'),

View 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>

View File

@@ -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({

View File

@@ -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() {
//
}

View File

@@ -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
View 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>;

View File

@@ -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: {