mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2025-03-14 12:10:18 +03:00
perf(search): Rework search data to save in store (#991)
This commit is contained in:
parent
b547d9d217
commit
470ae36cb5
12 changed files with 230 additions and 225 deletions
|
@ -1,168 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-card flat>
|
||||
<v-list-item class="searchCriterias">
|
||||
<v-row class="my-2">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="searchPattern"
|
||||
dense
|
||||
hide-details
|
||||
clearable
|
||||
:rules="[v => !!v || 'Search term is required']"
|
||||
label="Search pattern"
|
||||
@keydown.enter.prevent="runNewSearch"
|
||||
autofocus
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="5" md="2">
|
||||
<v-select v-model="searchCategory" height="1" flat dense hide-details outlined :items="categories" label="Search category" />
|
||||
</v-col>
|
||||
<v-col cols="6" sm="5" md="2">
|
||||
<v-select v-model="searchPlugin" height="1" flat dense hide-details outlined :items="plugins" label="Search plugins" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="2" class="d-flex align-center justify-center">
|
||||
<v-btn v-if="queryId === 0" class="mx-auto accent white--text elevation-0 px-4" @click="runNewSearch">
|
||||
{{ $t('search.runNewSearch') }}
|
||||
</v-btn>
|
||||
<v-btn v-else class="mx-auto warning white--text elevation-0 px-4" @click="stopSearch">
|
||||
{{ $t('search.stopSearch') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-list-item>
|
||||
</v-card>
|
||||
|
||||
<v-card flat class="mt-5">
|
||||
<v-list-item class="searchResults">
|
||||
<v-data-table style="width: 100%" id="searchResultsTable" :headers="headers" :items="filteredResults" :search="resultFilter" :custom-filter="customFilter">
|
||||
<template v-slot:top>
|
||||
<v-row class="mt-2">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field v-model="resultFilter" dense hide-details label="Filter" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<template v-slot:item.fileSize="{ item }">
|
||||
{{ item.fileSize | formatSize }}
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<span class="d-flex flex-row">
|
||||
<v-icon @click="downloadTorrent(item)">{{ mdiDownload }}</v-icon>
|
||||
</span>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-list-item>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { mdiDownload } from '@mdi/js'
|
||||
import { SearchPlugin, SearchResult } from '@/types/qbit/models'
|
||||
import { General } from '@/mixins'
|
||||
import qbit from '@/services/qbit'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SearchTab',
|
||||
mixins: [General],
|
||||
data() {
|
||||
return {
|
||||
headers: [
|
||||
{ text: 'Filename', value: 'fileName' },
|
||||
{ text: 'File Size', value: 'fileSize' },
|
||||
{ text: 'Seeders', value: 'nbSeeders' },
|
||||
{ text: 'Leechers', value: 'nbLeechers' },
|
||||
{ text: 'Site URL', value: 'siteUrl' },
|
||||
{ text: '', value: 'actions', sortable: false }
|
||||
],
|
||||
searchPattern: '',
|
||||
searchCategory: 'all',
|
||||
searchPlugin: 'enabled',
|
||||
queryId: 0,
|
||||
queryTimer: NaN as NodeJS.Timer,
|
||||
queryResults: [] as SearchResult[],
|
||||
resultFilter: '',
|
||||
mdiDownload
|
||||
}
|
||||
},
|
||||
async beforeDestroy() {
|
||||
await this.stopSearch()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getSearchPlugins']),
|
||||
categories() {
|
||||
const cats = [
|
||||
{ text: 'Movies', value: 'movies' },
|
||||
{ text: 'TV shows', value: 'tv' },
|
||||
{ text: 'Music', value: 'music' },
|
||||
{ text: 'Games', value: 'games' },
|
||||
{ text: 'Anime', value: 'anime' },
|
||||
{ text: 'Software', value: 'software' },
|
||||
{ text: 'Pictures', value: 'pictures' },
|
||||
{ text: 'Books', value: 'books' }
|
||||
]
|
||||
cats.sort((a, b) => a.text.localeCompare(b.text))
|
||||
|
||||
return [{ text: 'All categories', value: 'all' }, ...cats]
|
||||
},
|
||||
plugins() {
|
||||
const plugins = [
|
||||
{ text: 'All plugins', value: 'all' },
|
||||
{ text: 'Only enabled', value: 'enabled' }
|
||||
]
|
||||
|
||||
this.getSearchPlugins()
|
||||
.filter((plugin: SearchPlugin) => plugin.enabled)
|
||||
.forEach((plugin: SearchPlugin) => plugins.push({ text: plugin.fullName, value: plugin.name }))
|
||||
|
||||
return plugins
|
||||
},
|
||||
filteredResults() {
|
||||
return this.queryResults
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
downloadTorrent(item: SearchResult) {
|
||||
this.createModal('AddModal', { initialMagnet: item.fileUrl })
|
||||
},
|
||||
async runNewSearch() {
|
||||
const searchJob = await qbit.startSearch(this.searchPattern, this.searchCategory, [this.searchPlugin])
|
||||
this.queryId = searchJob.id
|
||||
this.queryResults = []
|
||||
this.queryTimer = setInterval(() => this.refreshResults(), 1000)
|
||||
},
|
||||
async stopSearch() {
|
||||
if (this.queryId !== 0) await qbit.stopSearch(this.queryId)
|
||||
|
||||
this.queryId = 0
|
||||
clearInterval(this.queryTimer)
|
||||
},
|
||||
customFilter(value: any, search: string | null) {
|
||||
return (
|
||||
value != null &&
|
||||
search != null &&
|
||||
typeof value === 'string' &&
|
||||
search
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split(' ')
|
||||
.every(i => value.toString().toLowerCase().indexOf(i) !== -1)
|
||||
)
|
||||
},
|
||||
async refreshResults() {
|
||||
const response = await qbit.getSearchResults(this.queryId, this.queryResults.length)
|
||||
this.queryResults.push(...response.results)
|
||||
|
||||
if (response.status === 'Stopped') {
|
||||
this.queryId = 0
|
||||
await this.stopSearch()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
|
@ -101,7 +101,7 @@
|
|||
},
|
||||
"search": {
|
||||
"title": "Search torrents",
|
||||
"tabHeaderTemplate": "Tab #$0",
|
||||
"tabHeaderEmpty": "(Empty Query)",
|
||||
"runNewSearch": "Search",
|
||||
"stopSearch": "Stop"
|
||||
},
|
||||
|
|
|
@ -101,7 +101,6 @@
|
|||
},
|
||||
"search": {
|
||||
"title": "搜尋種子",
|
||||
"tabHeaderTemplate": "分頁 #$0",
|
||||
"runNewSearch": "搜尋",
|
||||
"stopSearch": "停止"
|
||||
},
|
||||
|
|
|
@ -98,6 +98,7 @@ export default new Vuex.Store<StoreState>({
|
|||
feeds: [],
|
||||
rules: []
|
||||
},
|
||||
searchData: [],
|
||||
searchPlugins: [],
|
||||
selectMode: false,
|
||||
selected_torrents: [],
|
||||
|
|
|
@ -8,6 +8,7 @@ import type ModalTemplate from './ModalTemplate'
|
|||
import type { Status } from '@/models'
|
||||
import type WebUISettings from './WebUISettings'
|
||||
import type { SearchPlugin } from '@/types/qbit/models'
|
||||
import { SearchData } from "@/types/vuetorrent/search";
|
||||
|
||||
export interface PersistentStoreState {
|
||||
authenticated: boolean
|
||||
|
@ -33,6 +34,7 @@ export default interface StoreState extends PersistentStoreState {
|
|||
feeds: Feed[]
|
||||
rules: FeedRule[]
|
||||
}
|
||||
searchData: SearchData[]
|
||||
searchPlugins: SearchPlugin[]
|
||||
selectMode: boolean
|
||||
selected_torrents: string[]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type Category from './Category'
|
||||
import type { Feed, FeedArticle, FeedRule } from './rss'
|
||||
import type SearchStatus from './search/SearchStatus'
|
||||
import type SearchResult from './search/SearchResult'
|
||||
import type { SearchData, SearchFilters } from './search'
|
||||
import type ModalTemplate from './ModalTemplate'
|
||||
import type SortOptions from './SortOptions'
|
||||
import type StoreState from './StoreState'
|
||||
|
@ -15,8 +14,8 @@ export {
|
|||
Feed,
|
||||
FeedArticle,
|
||||
FeedRule,
|
||||
SearchStatus,
|
||||
SearchResult,
|
||||
SearchData,
|
||||
SearchFilters,
|
||||
ModalTemplate,
|
||||
SortOptions,
|
||||
PersistentStoreState,
|
||||
|
|
12
src/types/vuetorrent/search/SearchData.ts
Normal file
12
src/types/vuetorrent/search/SearchData.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import SearchFilters from './SearchFilters'
|
||||
import { SearchResult } from '@/types/qbit/models'
|
||||
|
||||
export default interface SearchData {
|
||||
uniqueId: string
|
||||
id: number
|
||||
timer: NodeJS.Timer | null
|
||||
query: string
|
||||
itemsPerPage: number
|
||||
filters: SearchFilters
|
||||
results: SearchResult[]
|
||||
}
|
5
src/types/vuetorrent/search/SearchFilters.ts
Normal file
5
src/types/vuetorrent/search/SearchFilters.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default interface SearchFilters {
|
||||
title: string
|
||||
category: string
|
||||
plugin: string
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
export default interface SearchResult {
|
||||
descrLink: string
|
||||
fileName: string
|
||||
fileSize: number
|
||||
fileUrl: string
|
||||
nbLeechers: number
|
||||
nbSeeders: number
|
||||
siteUrl: string
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import type { SearchResult } from '..'
|
||||
import type { Optional } from '@/global'
|
||||
|
||||
export default interface SearchStatus {
|
||||
id: number
|
||||
status: 'Running' | 'Stopped'
|
||||
interval: Optional<NodeJS.Timer>
|
||||
results: SearchResult[]
|
||||
}
|
7
src/types/vuetorrent/search/index.ts
Normal file
7
src/types/vuetorrent/search/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import SearchData from './SearchData'
|
||||
import SearchFilters from './SearchFilters'
|
||||
|
||||
export {
|
||||
SearchData,
|
||||
SearchFilters
|
||||
}
|
|
@ -8,6 +8,9 @@
|
|||
</v-col>
|
||||
<v-col class="align-center justify-center">
|
||||
<v-card-actions class="justify-end">
|
||||
<v-btn small elevation="0" color="error" @click="stopAllSearch">
|
||||
<v-icon>{{ mdiStop }}</v-icon>
|
||||
</v-btn>
|
||||
<v-btn small elevation="0" color="primary" @click="openPluginManager">
|
||||
<v-icon>{{ mdiToyBrick }}</v-icon>
|
||||
</v-btn>
|
||||
|
@ -20,60 +23,172 @@
|
|||
|
||||
<v-row class="ma-0 pa-0">
|
||||
<v-container class="d-flex align-center justify-center ma-0 pa-0 primary" fluid>
|
||||
<v-tabs v-model="tab" align-with-title show-arrows background-color="primary" slider-color="white" class="overflow-auto">
|
||||
<v-tab v-for="t in tabs" :href="`#${t.value}`" class="white--text">
|
||||
<h4>{{ $t('search.tabHeaderTemplate').replace('$0', t.id) }}</h4>
|
||||
<v-tabs v-model="tabIndex" ref="tabs" align-with-title show-arrows background-color="primary" slider-color="white" class="overflow-auto">
|
||||
<v-tab v-for="t in tabs" :key="t.uniqueId" class="white--text">
|
||||
<h4>{{ !t.query || t.query.length === 0 ? $t('search.tabHeaderEmpty') : t.query }}</h4>
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-spacer />
|
||||
<v-btn icon @click="createNewTab" class="mr-1">
|
||||
<v-icon color="accent">{{ mdiPlusCircleOutline }}</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon @click="deleteTab" :disabled="tabs.length === 0" class="mx-1">
|
||||
<v-btn icon @click="deleteTab" :disabled="tabs.length === 1" class="mx-1">
|
||||
<v-icon color="error">{{ mdiMinusCircleOutline }}</v-icon>
|
||||
</v-btn>
|
||||
</v-container>
|
||||
|
||||
<v-tabs-items v-model="tab" touchless class="full-width">
|
||||
<v-tab-item v-for="t in tabs" :key="t.id" eager :value="t.value">
|
||||
<SearchTab />
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
</v-row>
|
||||
|
||||
<div>
|
||||
<v-card flat>
|
||||
<v-list-item class="searchCriterias">
|
||||
<v-row class="my-2">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="selectedTab.query"
|
||||
ref="queryInput"
|
||||
autofocus
|
||||
dense
|
||||
hide-details
|
||||
clearable
|
||||
label="Search pattern"
|
||||
@keydown.enter.prevent="runNewSearch"
|
||||
@input="updateTabWidth"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="5" md="2">
|
||||
<v-select v-model="selectedTab.filters.category" height="1" flat dense hide-details outlined :items="categories" label="Search category" />
|
||||
</v-col>
|
||||
<v-col cols="6" sm="5" md="2">
|
||||
<v-select v-model="selectedTab.filters.plugin" height="1" flat dense hide-details outlined :items="plugins" label="Search plugins" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="2" class="d-flex align-center justify-center">
|
||||
<v-btn v-if="selectedTab.id === 0" class="mx-auto accent white--text elevation-0 px-4" @click="runNewSearch">
|
||||
{{ $t('search.runNewSearch') }}
|
||||
</v-btn>
|
||||
<v-btn v-else class="mx-auto warning white--text elevation-0 px-4" @click="stopSearch(selectedTab)">
|
||||
{{ $t('search.stopSearch') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-list-item>
|
||||
</v-card>
|
||||
|
||||
<v-card flat class="mt-5">
|
||||
<v-list-item class="searchResults">
|
||||
<v-data-table style="width: 100%" id="searchResultsTable" :headers="headers" :items="filteredResults" :footer-props="{ itemsPerPageOptions: [10, 25, 50, 100, -1] }" :items-per-page.sync="selectedTab.itemsPerPage" :search="selectedTab.filters.title" :custom-filter="customFilter">
|
||||
<template v-slot:top>
|
||||
<v-row class="mt-2">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field v-model="selectedTab.filters.title" dense hide-details label="Filter" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<template v-slot:item.fileSize="{ item }">
|
||||
{{ item.fileSize | formatData(shouldUseBinaryData()) }}
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<span class="d-flex flex-row">
|
||||
<v-icon @click="downloadTorrent(item)">{{ mdiDownload }}</v-icon>
|
||||
</span>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-list-item>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { mdiClose, mdiToyBrick, mdiPlusCircleOutline, mdiMinusCircleOutline } from '@mdi/js'
|
||||
import { FullScreenModal, General } from '@/mixins'
|
||||
import SearchTab from '@/components/SearchEngine/SearchTab.vue'
|
||||
import { mdiClose, mdiToyBrick, mdiPlusCircleOutline, mdiMinusCircleOutline, mdiDownload, mdiStop } from '@mdi/js'
|
||||
import { General } from '@/mixins'
|
||||
import { SearchPlugin, SearchResult } from '@/types/qbit/models'
|
||||
import qbit from '@/services/qbit'
|
||||
import { SearchData } from '@/types/vuetorrent'
|
||||
import { Optional } from '@/global'
|
||||
import { v1 as genUuid } from 'uuid'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SearchEngine',
|
||||
components: { SearchTab },
|
||||
mixins: [General, FullScreenModal],
|
||||
mixins: [General],
|
||||
data() {
|
||||
return {
|
||||
tabs: [] as { id: number; value: string }[],
|
||||
tabCount: 0,
|
||||
tabIndex: 0,
|
||||
tabs: [] as SearchData[],
|
||||
headers: [
|
||||
{ text: 'Filename', value: 'fileName' },
|
||||
{ text: 'File Size', value: 'fileSize' },
|
||||
{ text: 'Seeders', value: 'nbSeeders' },
|
||||
{ text: 'Leechers', value: 'nbLeechers' },
|
||||
{ text: 'Site URL', value: 'siteUrl' },
|
||||
{ text: '', value: 'actions', sortable: false }
|
||||
],
|
||||
mdiDownload,
|
||||
mdiClose,
|
||||
mdiToyBrick,
|
||||
mdiPlusCircleOutline,
|
||||
mdiMinusCircleOutline
|
||||
mdiMinusCircleOutline,
|
||||
mdiStop
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
computed: {
|
||||
...mapGetters(['getModals', 'getSearchPlugins', 'shouldUseBinaryData']),
|
||||
categories() {
|
||||
const cats = [
|
||||
{ text: 'Movies', value: 'movies' },
|
||||
{ text: 'TV shows', value: 'tv' },
|
||||
{ text: 'Music', value: 'music' },
|
||||
{ text: 'Games', value: 'games' },
|
||||
{ text: 'Anime', value: 'anime' },
|
||||
{ text: 'Software', value: 'software' },
|
||||
{ text: 'Pictures', value: 'pictures' },
|
||||
{ text: 'Books', value: 'books' }
|
||||
]
|
||||
cats.sort((a, b) => a.text.localeCompare(b.text))
|
||||
|
||||
return [{ text: 'All categories', value: 'all' }, ...cats]
|
||||
},
|
||||
plugins() {
|
||||
const plugins = [
|
||||
{ text: 'All plugins', value: 'all' },
|
||||
{ text: 'Only enabled', value: 'enabled' }
|
||||
]
|
||||
|
||||
this.getSearchPlugins()
|
||||
.filter((plugin: SearchPlugin) => plugin.enabled)
|
||||
.forEach((plugin: SearchPlugin) => plugins.push({ text: plugin.fullName, value: plugin.name }))
|
||||
|
||||
return plugins
|
||||
},
|
||||
selectedTab() {
|
||||
return this.tabs[this.tabIndex]
|
||||
},
|
||||
filteredResults() {
|
||||
return this.selectedTab.results
|
||||
}
|
||||
},
|
||||
async beforeMount() {
|
||||
this.tabs = this.$store.state.searchData
|
||||
if (this.tabs.length === 0) this.createNewTab()
|
||||
else {
|
||||
for (const tab of this.tabs) {
|
||||
if (tab.id && tab.id !== 0) {
|
||||
tab.timer = setInterval(() => this.refreshResults(tab), 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.$store.dispatch('FETCH_SEARCH_PLUGINS')
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.handleKeyboardShortcut)
|
||||
this.createNewTab()
|
||||
},
|
||||
async beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.handleKeyboardShortcut)
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getModals'])
|
||||
|
||||
for (const tab of this.tabs) {
|
||||
!!tab.timer && clearInterval(tab.timer)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openPluginManager() {
|
||||
|
@ -81,18 +196,65 @@ export default defineComponent({
|
|||
},
|
||||
createNewTab() {
|
||||
this.tabs.push({
|
||||
id: this.tabCount++,
|
||||
value: `tab-${this.tabCount}`
|
||||
uniqueId: genUuid(),
|
||||
id: 0,
|
||||
query: '',
|
||||
itemsPerPage: 10,
|
||||
filters: {
|
||||
title: '',
|
||||
category: 'all',
|
||||
plugin: 'enabled'
|
||||
},
|
||||
results: [],
|
||||
timer: null
|
||||
})
|
||||
this.tabIndex = this.tabs.length - 1;
|
||||
const input = this.$refs.queryInput as Optional<HTMLInputElement>
|
||||
input && input.focus()
|
||||
},
|
||||
deleteTab() {
|
||||
this.tabs.find((tab, index) => {
|
||||
if (tab.value === this.tab) {
|
||||
this.tabs.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
async deleteTab() {
|
||||
await this.stopSearch(this.selectedTab)
|
||||
this.tabs.splice(this.tabIndex, 1)
|
||||
this.tabIndex = Math.min(this.tabIndex, this.tabs.length - 1)
|
||||
},
|
||||
downloadTorrent(item: SearchResult) {
|
||||
this.createModal('AddModal', { initialMagnet: item.fileUrl })
|
||||
},
|
||||
async runNewSearch() {
|
||||
const searchJob = await qbit.startSearch(this.selectedTab.query, this.selectedTab.filters.category, [this.selectedTab.filters.plugin])
|
||||
this.selectedTab.id = searchJob.id
|
||||
this.selectedTab.results = []
|
||||
this.selectedTab.timer = setInterval(() => this.refreshResults(this.selectedTab), 1000)
|
||||
},
|
||||
async stopSearch(tab: SearchData) {
|
||||
if (tab.id && tab.id !== 0) await qbit.stopSearch(tab.id)
|
||||
tab.id = 0
|
||||
!!tab.timer && clearInterval(tab.timer)
|
||||
},
|
||||
async stopAllSearch() {
|
||||
for (const tab of this.tabs) {
|
||||
await this.stopSearch(tab)
|
||||
}
|
||||
},
|
||||
customFilter(value: any, search: string | null) {
|
||||
return (
|
||||
value != null &&
|
||||
search != null &&
|
||||
typeof value === 'string' &&
|
||||
search
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split(' ')
|
||||
.every(i => value.toString().toLowerCase().indexOf(i) !== -1)
|
||||
)
|
||||
},
|
||||
async refreshResults(tab: SearchData) {
|
||||
const response = await qbit.getSearchResults(tab.id, tab.results.length)
|
||||
tab.results.push(...response.results)
|
||||
|
||||
if (response.status === 'Stopped') {
|
||||
await this.stopSearch(tab)
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$router.back()
|
||||
|
@ -101,6 +263,10 @@ export default defineComponent({
|
|||
if (e.key === 'Escape' && this.getModals().length === 0) {
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
updateTabWidth() {
|
||||
//@ts-expect-error: TS2339: Property 'onResize' does not exist on type '...'
|
||||
this.$refs.tabs && this.$refs.tabs.onResize()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue