1
0
Fork 0
mirror of https://github.com/VueTorrent/VueTorrent.git synced 2025-04-02 23:05:12 +03:00
VueTorrent/src/components/Modals/Rss/RuleForm.vue
2023-06-04 22:03:12 +02:00

241 lines
9.4 KiB
Vue

<template>
<v-dialog v-model="dialog" max-width="1000px">
<v-card flat :loading="loading">
<v-container class="pa-0 project done">
<v-card-title class="justify-center">
<v-toolbar flat dense class="transparent">
<v-toolbar-title class="mx-auto">
<h2>{{ this.lastSavedName !== '' ? $t('modals.newRule.titleEdit') : $t('modals.newRule.titleCreate') }}</h2>
</v-toolbar-title>
<v-btn fab small class="transparent elevation-0" @click="close">
<v-icon>{{ mdiClose }}</v-icon>
</v-btn>
</v-toolbar>
</v-card-title>
<v-card-text class="pb-0">
<v-row>
<v-col cols="12" sm="6">
<v-form ref="form">
<v-container>
<v-text-field v-model="rule.name" :label="$t('modals.newRule.name')" required />
<v-divider />
<v-checkbox hide-details v-model="rule.useRegex" :label="$t('modals.newRule.def.useRegex')" />
<v-text-field hide-details v-model="rule.mustContain" :label="$t('modals.newRule.def.mustContain')" />
<v-text-field hide-details v-model="rule.mustNotContain" :label="$t('modals.newRule.def.mustNotContain')" />
<v-checkbox hide-details v-model="rule.smartFilter" :label="$t('modals.newRule.def.smartFilter')" />
<v-text-field v-model="rule.episodeFilter" :label="$t('modals.newRule.def.episodeFilter')" />
<v-divider />
<v-row>
<p class="subtitle-1 mb-1">{{ $t('modals.newRule.def.assignedCategory') }}</p>
<v-select flat solo dense hide-details background-color="background" class="rounded-xl" v-model="rule.assignedCategory" :items="availableCategories" />
</v-row>
<v-text-field hide-details v-model="rule.savePath" :label="$t('modals.newRule.def.savePath')" />
<v-text-field hide-details v-model="rule.ignoreDays" :label="$t('modals.newRule.def.ignoreDays')" required type="number" min="0" />
<v-text-field disabled :value="lastMatch" :label="$t('modals.newRule.def.lastMatch.title')" />
<v-divider />
<v-row class="my-2 flex-gap">
<v-col>
<div class="d-flex flex-column align-center">
<p class="subtitle-1 mb-1">{{ $t('modals.newRule.def.addPaused.title') }}</p>
<v-select v-model="rule.addPaused" flat solo dense hide-details background-color="background" class="rounded-xl" :items="addPausedOptions" />
</div>
</v-col>
<v-col>
<div class="d-flex flex-column align-center">
<p class="subtitle-1 mb-1">{{ $t('enums.contentLayout.title') }}</p>
<v-select v-model="rule.torrentContentLayout" flat solo dense hide-details background-color="background" class="rounded-xl" :items="contentLayoutOptions" />
</div>
</v-col>
</v-row>
<v-subheader class="pa-0">
{{ $t('modals.newRule.def.affectedFeeds') }}
</v-subheader>
<v-row>
<v-col cols="6" class="d-flex align-center justify-center">
<v-btn color="accent" @click="selectAll">{{ $t('selectAll') }}</v-btn>
</v-col>
<v-col cols="6" class="d-flex align-center justify-center">
<v-btn color="primary" @click="selectNone">{{ $t('selectNone') }}</v-btn>
</v-col>
</v-row>
<v-checkbox v-for="(item, index) in availableFeeds" :key="index" v-model="rule.affectedFeeds" hide-details :label="item.name" :value="item.url" />
</v-container>
</v-form>
</v-col>
<v-col v-if="$vuetify.breakpoint.smAndUp" cols="6">
<h2>{{ $t('modals.matchingArticles.title') }}</h2>
<v-list subheader>
<template v-for="item in matchingArticles">
<v-divider :key="item.value" v-if="item.type === 'divider'" />
<v-subheader inset v-else-if="item.type === 'subheader'">{{ item.value }}</v-subheader>
<v-list-item v-else class="mb-3">{{ item.value }}</v-list-item>
</template>
</v-list>
</v-col>
</v-row>
<v-btn fab color="accent" fixed right bottom @click="setRule">
<v-icon>{{ mdiContentSave }}</v-icon>
</v-btn>
</v-card-text>
<v-divider />
<v-card-actions class="justify-end">
<v-btn class="accent white--text elevation-0 px-4" @click="setRule">
{{ $t('save') }}
</v-btn>
<v-btn color="primary" class="white--text elevation-0 px-4" @click="close">
{{ $t('close') }}
</v-btn>
</v-card-actions>
</v-container>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { mapGetters } from 'vuex'
import qbit from '@/services/qbit'
import { Modal } from '@/mixins'
import { mdiClose, mdiContentSave } from '@mdi/js'
import i18n from '@/plugins/i18n'
import { AppPreferences } from '@/enums/qbit'
import {Category, Feed} from '@/types/vuetorrent'
type FormattedArticle = { type: string; value?: string }
export default defineComponent({
name: 'RuleForm',
mixins: [Modal],
props: ['initialRule'],
data: () => ({
rule: {
addPaused: null,
affectedFeeds: [],
assignedCategory: '',
enabled: true,
episodeFilter: '',
ignoreDays: 0,
lastMatch: '',
mustContain: '',
mustNotContain: '',
name: '',
savePath: '',
smartFilter: false,
torrentContentLayout: null,
useRegex: false
},
addPausedOptions: [
{ text: i18n.t('useGlobalSettings'), value: null },
{ text: i18n.t('modals.newRule.def.addPaused.always'), value: true },
{ text: i18n.t('modals.newRule.def.addPaused.never'), value: false }
],
contentLayoutOptions: [
{ text: i18n.t('useGlobalSettings'), value: null },
{ text: i18n.t('enums.contentLayout.original'), value: AppPreferences.ContentLayout.ORIGINAL },
{ text: i18n.t('enums.contentLayout.subfolder'), value: AppPreferences.ContentLayout.SUBFOLDER },
{ text: i18n.t('enums.contentLayout.nosubfolder'), value: AppPreferences.ContentLayout.NO_SUBFOLDER }
],
loading: false,
matchingArticles: [] as FormattedArticle[],
lastSavedName: '',
mdiClose,
mdiContentSave
}),
computed: {
...mapGetters(['getFeeds', 'getCategories']),
lastMatch() {
if (this.rule.lastMatch === '') return i18n.t('modals.newRule.def.lastMatch.unknownValue').toString()
const delta = new Date().getTime() - new Date(this.rule.lastMatch).getTime()
return i18n
.t('modals.newRule.def.lastMatch.knownValue')
.toString()
.replace('%1', Math.floor(delta / (1000 * 60 * 60 * 24)).toString())
},
availableFeeds() {
// @ts-expect-error: TS2349: This expression is not callable. Type 'never' has no call signatures.
return this.getFeeds()
},
availableCategories() {
// @ts-expect-error: TS2349: This expression is not callable. Type 'never' has no call signatures.
return this.getCategories().map((c: Category) => c.name)
},
hasInitialRule() {
return !!(this.initialRule && this.initialRule.name)
}
},
created() {
this.$store.commit('FETCH_RULES')
if (this.hasInitialRule) {
this.rule = { ...this.initialRule }
this.lastSavedName = this.initialRule.name
}
},
mounted() {
document.addEventListener('keydown', this.handleKeyboardShortcut)
this.updateArticles()
},
beforeDestroy() {
document.removeEventListener('keydown', this.handleKeyboardShortcut)
},
methods: {
async setRule() {
if ((this.hasInitialRule || this.lastSavedName !== '') && this.lastSavedName !== this.rule.name) {
await qbit.renameRule(this.lastSavedName, this.rule.name)
}
await qbit.setRule(this.rule)
this.lastSavedName = this.rule.name
this.$store.commit('FETCH_RULES')
await this.updateArticles()
},
async updateArticles() {
if (this.lastSavedName === '') return
this.loading = true
const formattedArticles = []
const articles = await qbit.getMatchingArticles(this.lastSavedName)
for (const feedName in articles) {
const feedArticles = articles[feedName]
if (formattedArticles.length > 0) formattedArticles.push({ type: 'divider' })
formattedArticles.push({ type: 'subheader', value: feedName })
for (const i in feedArticles) {
const article = feedArticles[i]
formattedArticles.push({ type: 'item', value: article })
}
}
this.matchingArticles = formattedArticles
this.loading = false
},
selectNone() {
this.rule.affectedFeeds = []
},
selectAll() {
this.rule.affectedFeeds = this.availableFeeds.map((feed: Feed) => feed.url)
},
close() {
this.dialog = false
},
handleKeyboardShortcut(e: KeyboardEvent) {
if (e.key === 'Escape') {
this.close()
} else if (e.key === 'Enter') {
this.setRule()
}
}
}
})
</script>