From 7caa19ab889464d00d58630f93a84e98fbb82eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Marseault?= <22910497+Larsluph@users.noreply.github.com> Date: Mon, 27 Feb 2023 15:46:57 +0100 Subject: [PATCH] feat: Add import / export settings button (#659) @Larsluph --- package-lock.json | 9 + package.json | 1 + .../Settings/Tabs/VueTorrent/VGeneral.vue | 51 +++++- src/lang/en.json | 8 +- src/lang/fr.json | 7 +- src/schemas/StoreState.json | 170 ++++++++++++++++++ src/schemas/index.ts | 3 + src/services/qbit.ts | 1 - src/store/index.ts | 4 +- src/store/mutations.ts | 3 +- src/types/vuetorrent/SortOptions.ts | 1 - src/types/vuetorrent/StoreState.ts | 3 +- tests/unit/__snapshots__/General.spec.ts.snap | 9 +- 13 files changed, 255 insertions(+), 15 deletions(-) create mode 100644 src/schemas/StoreState.json create mode 100644 src/schemas/index.ts diff --git a/package-lock.json b/package-lock.json index ef634c0b..7f080ea9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "apexcharts": "^3.35.0", "axios": "^0.26.1", "dayjs": "^1.10.4", + "jsonschema": "^1.4.1", "lodash": "^4.17.21", "quick-score": "^0.2.0", "typeface-roboto": "^1.1.13", @@ -5382,6 +5383,14 @@ "node": ">=0.10.0" } }, + "node_modules/jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "engines": { + "node": "*" + } + }, "node_modules/kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", diff --git a/package.json b/package.json index 2dd24fbc..1fd9fb5d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "apexcharts": "^3.35.0", "axios": "^0.26.1", "dayjs": "^1.10.4", + "jsonschema": "^1.4.1", "lodash": "^4.17.21", "quick-score": "^0.2.0", "typeface-roboto": "^1.1.13", diff --git a/src/components/Settings/Tabs/VueTorrent/VGeneral.vue b/src/components/Settings/Tabs/VueTorrent/VGeneral.vue index 8e28d42e..fa9a7c9a 100644 --- a/src/components/Settings/Tabs/VueTorrent/VGeneral.vue +++ b/src/components/Settings/Tabs/VueTorrent/VGeneral.vue @@ -207,8 +207,15 @@ + + + + + {{ $t('modals.settings.pageVueTorrent.pageGeneral.importSettings') }} + {{ $t('modals.settings.pageVueTorrent.pageGeneral.exportSettings') }} + - {{ $t('modals.settings.pageVueTorrent.pageGeneral.resetSettings') }} + {{ $t('modals.settings.pageVueTorrent.pageGeneral.resetSettings') }} @@ -217,16 +224,21 @@ import { mapState, mapGetters } from 'vuex' import { Qbit } from '@/services/qbit' import { LOCALES } from '@/lang/locales' -import {TitleOptions} from "@/enums/vuetorrent"; +import { General } from '@/mixins' +import { TitleOptions } from '@/enums/vuetorrent' +import { validate } from 'jsonschema' +import { StoreState } from '@/schemas' export default { name: 'VueTorrent-General', + mixins: [General], data() { return { languages: LOCALES, paginationSizes: [5, 15, 30, 50], titleOptions: [TitleOptions.DEFAULT, TitleOptions.GLOBAL_SPEED, TitleOptions.FIRST_TORRENT_STATUS], - Qbitversion: 0 + Qbitversion: 0, + settingsField: '' } }, computed: { @@ -243,6 +255,32 @@ export default { async fetchQbitVersion() { this.Qbitversion = await Qbit.getAppVersion() }, + importSettings() { + let isValidJson = true + let userState + try { + userState = JSON.parse(this.settingsField) + let validatorResult = validate(userState, StoreState) + console.log(userState) + console.log(validatorResult) + if (!validatorResult.valid) + isValidJson = false + } catch (e) { + console.log(e) + isValidJson = false + } + + if (!isValidJson) { + this.$toast.error(this.$t('toast.invalidJson').toString()) + return + } + + window.localStorage.setItem('vuetorrent', this.settingsField) + location.reload() + }, + exportSettings() { + this.settingsField = window.localStorage.getItem('vuetorrent') ?? '' + }, resetSettings() { window.localStorage.clear() location.reload() @@ -261,4 +299,11 @@ export default { @import 'src/styles/styles.scss'; @include reverse-switch; } + +.justify-content-evenly { + justify-content: space-evenly; +} +.remove-after::after { + content: unset; +} diff --git a/src/lang/en.json b/src/lang/en.json index 9a008177..0ea56dc1 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -24,7 +24,7 @@ "inactive": "Inactive", "stalled": "Stalled", "errored": "Errored", - "login": "Login in", + "login": "Log in", "logout": "Log out", "shutdownApp": "Shutdown Application", "download": "Download", @@ -50,6 +50,7 @@ "yes": "yes", "no": "no", "filter": "Filter", + "close": "Close", "dashboard": { "tooltips": { "toggleSearch": "Toggle Search Filter", @@ -190,6 +191,8 @@ "openSideBarOnStart": "Open Side Bar on launch", "currentVersion": "Current Version:", "qbittorrentVersion": "QBittorrent Version:", + "importSettings": "Import Settings", + "exportSettings": "Export Settings", "resetSettings": "Reset Settings" }, "pageDashboard": { @@ -505,7 +508,8 @@ "pasteSuccess": "Text pasted!", "pasteNotSupported": "Unable to paste, context isn't secured", "shutdownSuccess": "qBittorrent was shutdown successfully!", - "shutdownError": "Unable to shutdown app. Make sure qBittorrent is running!" + "shutdownError": "Unable to shutdown app. Make sure qBittorrent is running!", + "invalidJson": "Invalid JSON!" }, "rightClick": { "resume": "resume", diff --git a/src/lang/fr.json b/src/lang/fr.json index 228d9659..2d84ac7d 100644 --- a/src/lang/fr.json +++ b/src/lang/fr.json @@ -47,6 +47,7 @@ "yes": "oui", "no": "non", "filter": "Filtre", + "close": "Fermer", "dashboard": { "tooltips": { "toggleSearch": "Rechercher un torrent", @@ -185,7 +186,10 @@ "dateFormat": "Format de date", "openSideBarOnStart": "Ouvrir la barre latérale au lancement", "currentVersion": "Version actuelle:", - "qbittorrentVersion": "Version de QBittorrent:" + "qbittorrentVersion": "Version de QBittorrent:", + "importSettings": "Importer la configuration", + "exportSettings": "Exporter la configuration", + "resetSettings": "Réinitialiser la configuration" }, "pageDashboard": { "busyTorrentTip": "Propriétés à afficher pour les torrents occupés", @@ -491,6 +495,7 @@ "copySuccess": "Texte copié!", "copyNotSupported": "Impossible de copier, le contexte n'est pas sécurisé", "pasteSuccess": "Texte collé!", + "invalidJson": "JSON invalide!" "pasteNotSupported": "Impossible de coller, le contexte n'est pas sécurisé" }, "rightClick": { diff --git a/src/schemas/StoreState.json b/src/schemas/StoreState.json new file mode 100644 index 00000000..befa5c41 --- /dev/null +++ b/src/schemas/StoreState.json @@ -0,0 +1,170 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "sort_options": { + "type": "object", + "properties": { + "isCustomSortEnabled": { + "type": "boolean" + }, + "sort": { + "type": "string" + }, + "reverse": { + "type": "boolean" + }, + "filter": { + "type": ["string", "null"] + }, + "category": { + "type": ["string", "null"] + }, + "tag": { + "type": ["string", "null"] + }, + "tracker": { + "type": ["string", "null"] + } + }, + "required": [ + "isCustomSortEnabled", + "sort", + "reverse", + "filter", + "category", + "tag", + "tracker" + ] + }, + "webuiSettings": { + "type": "object", + "properties": { + "lang": { + "type": "string" + }, + "darkTheme": { + "type": "boolean" + }, + "showFreeSpace": { + "type": "boolean" + }, + "showSpeedGraph": { + "type": "boolean" + }, + "showSessionStat": { + "type": "boolean" + }, + "showAlltimeStat": { + "type": "boolean" + }, + "showCurrentSpeed": { + "type": "boolean" + }, + "showTrackerFilter": { + "type": "boolean" + }, + "showSpeedInTitle": { + "type": "boolean" + }, + "deleteWithFiles": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "rightDrawer": { + "type": "boolean" + }, + "topPagination": { + "type": "boolean" + }, + "paginationSize": { + "type": "integer" + }, + "dateFormat": { + "type": "string" + }, + "openSideBarOnStart": { + "type": "boolean" + }, + "busyTorrentProperties": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "label": { + "type": "string" + } + }, + "required": [ + "name", + "active", + "label" + ] + } + ] + }, + "doneTorrentProperties": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "label": { + "type": "string" + } + }, + "required": [ + "name", + "active", + "label" + ] + } + ] + } + }, + "required": [ + "lang", + "darkTheme", + "showFreeSpace", + "showSpeedGraph", + "showSessionStat", + "showAlltimeStat", + "showCurrentSpeed", + "showTrackerFilter", + "showSpeedInTitle", + "deleteWithFiles", + "title", + "rightDrawer", + "topPagination", + "paginationSize", + "dateFormat", + "openSideBarOnStart", + "busyTorrentProperties", + "doneTorrentProperties" + ] + }, + "authenticated": { + "type": "boolean" + } + }, + "required": [ + "sort_options", + "webuiSettings", + "authenticated" + ] +} diff --git a/src/schemas/index.ts b/src/schemas/index.ts new file mode 100644 index 00000000..db323bbe --- /dev/null +++ b/src/schemas/index.ts @@ -0,0 +1,3 @@ +import StoreState from './StoreState.json' + +export { StoreState } diff --git a/src/services/qbit.ts b/src/services/qbit.ts index 7363f573..5ad79d73 100644 --- a/src/services/qbit.ts +++ b/src/services/qbit.ts @@ -92,7 +92,6 @@ export class QBitApi { const params: Parameters = { sort: !payload.isCustomSortEnabled ? payload.sort : null, reverse: !payload.isCustomSortEnabled ? payload.reverse : null, - hashes: payload.hashes.length > 0 ? payload.hashes.join('|') : null, filter: payload.filter ? payload.filter : null, category: payload.category !== null ? payload.category : null, tag: payload.tag !== null ? payload.tag : null diff --git a/src/store/index.ts b/src/store/index.ts index c553801b..463b1bef 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -7,6 +7,7 @@ import mutations from './mutations' import type { StoreState } from '@/types/vuetorrent' import { Status } from '@/models' import { TitleOptions } from '@/enums/vuetorrent' +import {AppPreferences} from "@/types/qbit/models"; const vuexPersist = new VuexPersist({ key: 'vuetorrent', @@ -67,12 +68,11 @@ export default new Vuex.Store({ searchPlugins: [], selectMode: false, selected_torrents: [], - settings: null, + settings: {} as AppPreferences, sort_options: { isCustomSortEnabled: false, sort: 'priority', reverse: false, - hashes: [], filter: null, category: null, tag: null, diff --git a/src/store/mutations.ts b/src/store/mutations.ts index d04a3670..1dbeddcb 100644 --- a/src/store/mutations.ts +++ b/src/store/mutations.ts @@ -70,8 +70,7 @@ export default { FETCH_SETTINGS: async (state: StoreState, settings: AppPreferences) => { state.settings = settings }, - UPDATE_SORT_OPTIONS: (state: StoreState, { hashes = [], filter = null, category = null, tag = null, tracker = null }) => { - state.sort_options.hashes = hashes + UPDATE_SORT_OPTIONS: (state: StoreState, { filter = null, category = null, tag = null, tracker = null }) => { state.sort_options.filter = filter state.sort_options.category = category state.sort_options.tag = tag diff --git a/src/types/vuetorrent/SortOptions.ts b/src/types/vuetorrent/SortOptions.ts index c53a963c..8c7f76b1 100644 --- a/src/types/vuetorrent/SortOptions.ts +++ b/src/types/vuetorrent/SortOptions.ts @@ -5,7 +5,6 @@ export default interface SortOptions { isCustomSortEnabled: boolean sort: string reverse: boolean - hashes: string[] filter: Optional category: Optional tag: Optional diff --git a/src/types/vuetorrent/StoreState.ts b/src/types/vuetorrent/StoreState.ts index 82871c69..725fe3dc 100644 --- a/src/types/vuetorrent/StoreState.ts +++ b/src/types/vuetorrent/StoreState.ts @@ -4,7 +4,6 @@ import type Category from '../qbit/models/Category' import type Torrent from '@/models/Torrent' import type SortOptions from './SortOptions' import type { AppPreferences } from '../qbit/models' -import type { Optional } from '@/global' import type ModalTemplate from './ModalTemplate' import type { Status } from '@/models' import type WebUISettings from '@/types/vuetorrent/WebUISettings' @@ -30,7 +29,7 @@ export default interface StoreState { searchPlugins: SearchPlugin[] selectMode: boolean selected_torrents: string[] - settings: Optional + settings: AppPreferences sort_options: SortOptions status: Status tags: string[] diff --git a/tests/unit/__snapshots__/General.spec.ts.snap b/tests/unit/__snapshots__/General.spec.ts.snap index 8758b00e..451abded 100644 --- a/tests/unit/__snapshots__/General.spec.ts.snap +++ b/tests/unit/__snapshots__/General.spec.ts.snap @@ -126,8 +126,15 @@ exports[`General > render correctly 1`] = ` + + + + + + + - + " `;