From 9d3a1e45da2baa78ead2faf5233e406aa7e4b969 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Thu, 15 Sep 2022 20:53:28 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=20+=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=84=D0=BE=D1=80=D0=BC=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B8=20=D0=BF=D0=B0?= =?UTF-8?q?=D1=80=D1=81=D0=B8=D0=BD=D0=B3=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D1=80=D0=BE=D0=B2=20=D0=B2=20URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/components/Search/Search.vue | 166 +++++++++++------- .../SelectGenreDialog/SelectGenreDialog.vue | 1 + .../SelectLangDialog/SelectLangDialog.vue | 1 + client/components/share/NumInput.vue | 27 ++- client/share/diffUtils.js | 143 +++++++++++++++ 5 files changed, 269 insertions(+), 69 deletions(-) create mode 100644 client/share/diffUtils.js diff --git a/client/components/Search/Search.vue b/client/components/Search/Search.vue index ff6ad12..bf4bc38 100644 --- a/client/components/Search/Search.vue +++ b/client/components/Search/Search.vue @@ -42,17 +42,17 @@
@@ -67,12 +67,12 @@
- - {{ lang }} + + {{ search.lang }} @@ -101,10 +101,10 @@
-
- +
+
-
+
@@ -150,10 +150,10 @@
-
- +
+
-
+
@@ -188,8 +188,8 @@ - - + +
@@ -207,6 +207,7 @@ import DivBtn from '../share/DivBtn.vue'; import Dialog from '../share/Dialog.vue'; import * as utils from '../../share/utils'; +import diffUtils from '../../share/diffUtils'; import _ from 'lodash'; @@ -225,29 +226,17 @@ const componentOptions = { settings() { this.loadSettings(); }, - author() { - this.refresh(); - }, - series() { - this.refresh(); - }, - title() { - this.refresh(); - }, - genre() { - this.refresh(); - }, - lang() { - this.refresh(); - }, - page() { - this.refresh(); + search: { + handler(newValue) { + this.limit = newValue.limit; + this.refresh(); + }, + deep: true, }, limit(newValue) { this.setSetting('limit', newValue); this.updatePageCount(); - this.refresh(); }, showCounts(newValue) { this.setSetting('showCounts', newValue); @@ -261,6 +250,12 @@ const componentOptions = { totalFound() { this.updatePageCount(); }, + $route(to) { + this.updateQueryFromRoute(to); + }, + langDefault() { + this.updateQueryFromRoute(this.$route); + }, }, }; class Search { @@ -274,7 +269,6 @@ class Search { selectGenreDialogVisible = false; selectLangDialogVisible = false; - page = 1; pageCount = 1; //input field consts @@ -282,12 +276,15 @@ class Search { inputDebounce = 200; //search fields - author = ''; - series = ''; - title = ''; - genre = ''; - lang = ''; - limit = 50;//settings + search = { + author: '', + series: '', + title: '', + genre: '', + lang: '', + page: 1, + limit: 50, + }; //settings expanded = []; @@ -295,6 +292,7 @@ class Search { showDeleted = false; abCacheEnabled = true; langDefault = ''; + limit = 50; //stuff queryFound = -1; @@ -334,8 +332,10 @@ class Search { this.$refs.authorInput.focus(); this.setDefaults(); + this.updateQueryFromRoute(this.$route); - //query from url parse + //чтоб не вызывался лишний refresh + await utils.sleep(100); this.ready = true; this.refresh();//no await @@ -345,7 +345,7 @@ class Search { loadSettings() { const settings = this.settings; - this.limit = settings.limit; + this.search.limit = settings.limit; this.expanded = _.cloneDeep(settings.expanded); this.showCounts = settings.showCounts; this.showDeleted = settings.showDeleted; @@ -363,7 +363,7 @@ class Search { get genreNames() { let result = []; - const genre = new Set(this.genre.split(',')); + const genre = new Set(this.search.genre.split(',')); for (const section of this.genreTree) { for (const g of section.value) @@ -444,19 +444,28 @@ class Search { } updatePageCount() { + const prevPageCount = this.pageCount; + this.pageCount = Math.ceil(this.totalFound/this.limit); this.pageCount = (this.pageCount < 1 ? 1 : this.pageCount); - if (this.page > this.pageCount) - this.page = 1; + + if (this.prevPage && prevPageCount == 1 && this.pageCount > 1 && this.prevPage <= this.pageCount) { + this.search.page = this.prevPage; + } + + if (this.search.page > this.pageCount) { + this.prevPage = this.search.page; + this.search.page = 1; + } } selectAuthor(author) { - this.author = `=${author}`; + this.search.author = `=${author}`; this.scrollToTop(); } selectTitle(title) { - this.title = `=${title}`; + this.search.title = `=${title}`; } isExpanded(item) { @@ -636,28 +645,63 @@ class Search { } setDefaults() { - this.author = ''; - this.series = ''; - this.title = ''; - this.genre = ''; - this.lang = this.langDefault; + this.search = Object.assign({}, this.search, { + author: '', + series: '', + title: '', + genre: '', + lang: this.langDefault, + }); + } + + async updateQueryFromRoute(to) { + if (this.routeUpdating) + return; + + const query = to.query; + + this.search = Object.assign({}, this.search, { + author: query.author || '', + series: query.series || '', + title: query.title || '', + genre: query.genre || '', + lang: (query.lang == 'default' ? this.langDefault : query.lang || ''), + page: parseInt(query.page, 10) || 1, + }); + } + + updateRouteQuery() { + this.routeUpdating = true; + try { + const oldQuery = this.$route.query; + const query = _.pickBy(this.search); + delete query.limit; + if (this.search.lang == this.langDefault) + query.lang = 'default' + + const diff = diffUtils.getObjDiff(oldQuery, query); + if (!diffUtils.isEmptyObjDiff(diff)) { + this.$router.replace({query}); + } + } finally { + (async() => { + await utils.sleep(100); + this.routeUpdating = false; + })(); + } } async refresh() { if (!this.ready) return; - const offset = (this.page - 1)*this.limit; + this.updateRouteQuery(); - const newQuery = { - author: this.author, - series: this.series, - title: this.title, - genre: this.genre, - lang: this.lang, - limit: this.limit, - offset, - }; + const offset = (this.search.page - 1)*this.limit; + + const newQuery = _.cloneDeep(this.search); + newQuery.limit = this.limit; + newQuery.offset = offset; this.queryExecute = newQuery; diff --git a/client/components/Search/SelectGenreDialog/SelectGenreDialog.vue b/client/components/Search/SelectGenreDialog/SelectGenreDialog.vue index 6f81ce9..66037d1 100644 --- a/client/components/Search/SelectGenreDialog/SelectGenreDialog.vue +++ b/client/components/Search/SelectGenreDialog/SelectGenreDialog.vue @@ -86,6 +86,7 @@ class GenreSelectDialog { } mounted() { + this.updateTicked(); } async init() { diff --git a/client/components/Search/SelectLangDialog/SelectLangDialog.vue b/client/components/Search/SelectLangDialog/SelectLangDialog.vue index 87a1e38..270fc94 100644 --- a/client/components/Search/SelectLangDialog/SelectLangDialog.vue +++ b/client/components/Search/SelectLangDialog/SelectLangDialog.vue @@ -96,6 +96,7 @@ class SelectLangDialog { } mounted() { + this.updateTicked(); } async init() { diff --git a/client/components/share/NumInput.vue b/client/components/share/NumInput.vue index 57d309a..bff685f 100644 --- a/client/components/share/NumInput.vue +++ b/client/components/share/NumInput.vue @@ -49,17 +49,18 @@ import * as utils from '../../share/utils'; const componentOptions = { watch: { - filteredValue: function(newValue) { - if (this.validate(newValue)) { - this.error = false; - this.$emit('update:modelValue', this.string2number(newValue)); - } else { - this.error = true; - } + filteredValue() { + this.checkErrorAndEmit(true); }, - modelValue: function(newValue) { + modelValue(newValue) { this.filteredValue = newValue; }, + min() { + this.checkErrorAndEmit(); + }, + max() { + this.checkErrorAndEmit(); + } } }; class NumInput { @@ -97,6 +98,16 @@ class NumInput { return true; } + checkErrorAndEmit(emit = false) { + if (this.validate(this.filteredValue)) { + this.error = false; + if (emit) + this.$emit('update:modelValue', this.string2number(this.filteredValue)); + } else { + this.error = true; + } + } + plus() { const newValue = this.modelValue + this.step; if (this.validate(newValue)) diff --git a/client/share/diffUtils.js b/client/share/diffUtils.js new file mode 100644 index 0000000..02aaa38 --- /dev/null +++ b/client/share/diffUtils.js @@ -0,0 +1,143 @@ +const _ = require('lodash'); + +function getObjDiff(oldObj, newObj, opts = {}) { + const { + exclude = [], + excludeAdd = [], + excludeDel = [], + } = opts; + + const ex = new Set(exclude); + const exAdd = new Set(excludeAdd); + const exDel = new Set(excludeDel); + + const makeObjDiff = (oldObj, newObj, keyPath) => { + const result = {__isDiff: true, change: {}, add: {}, del: []}; + + keyPath = `${keyPath}${keyPath ? '/' : ''}`; + + for (const key of Object.keys(oldObj)) { + const kp = `${keyPath}${key}`; + + if (newObj.hasOwnProperty(key)) { + if (ex.has(kp)) + continue; + + if (!_.isEqual(oldObj[key], newObj[key])) { + if (_.isObject(oldObj[key]) && _.isObject(newObj[key])) { + result.change[key] = makeObjDiff(oldObj[key], newObj[key], kp); + } else { + result.change[key] = _.cloneDeep(newObj[key]); + } + } + } else { + if (exDel.has(kp)) + continue; + result.del.push(key); + } + } + + for (const key of Object.keys(newObj)) { + const kp = `${keyPath}${key}`; + if (exAdd.has(kp)) + continue; + + if (!oldObj.hasOwnProperty(key)) { + result.add[key] = _.cloneDeep(newObj[key]); + } + } + + return result; + } + + return makeObjDiff(oldObj, newObj, ''); +} + +function isObjDiff(diff) { + return (_.isObject(diff) && diff.__isDiff && diff.change && diff.add && diff.del); +} + +function isEmptyObjDiff(diff) { + return (!isObjDiff(diff) || + !(Object.keys(diff.change).length || + Object.keys(diff.add).length || + diff.del.length + ) + ); +} + +function isEmptyObjDiffDeep(diff, opts = {}) { + if (!isObjDiff(diff)) + return true; + + const { + isApplyChange = true, + isApplyAdd = true, + isApplyDel = true, + } = opts; + + let notEmptyDeep = false; + const change = diff.change; + for (const key of Object.keys(change)) { + if (_.isObject(change[key])) + notEmptyDeep |= !isEmptyObjDiffDeep(change[key], opts); + else if (isApplyChange) + notEmptyDeep = true; + } + + return !( + notEmptyDeep || + (isApplyAdd && Object.keys(diff.add).length) || + (isApplyDel && diff.del.length) + ); +} + +function applyObjDiff(obj, diff, opts = {}) { + const { + isAddChanged = false, + isApplyChange = true, + isApplyAdd = true, + isApplyDel = true, + } = opts; + + let result = _.cloneDeep(obj); + if (!diff.__isDiff) + return result; + + const change = diff.change; + for (const key of Object.keys(change)) { + if (result.hasOwnProperty(key)) { + if (_.isObject(change[key])) { + result[key] = applyObjDiff(result[key], change[key], opts); + } else { + if (isApplyChange) + result[key] = _.cloneDeep(change[key]); + } + } else if (isAddChanged) { + result[key] = _.cloneDeep(change[key]); + } + } + + if (isApplyAdd) { + for (const key of Object.keys(diff.add)) { + result[key] = _.cloneDeep(diff.add[key]); + } + } + + if (isApplyDel && diff.del.length) { + for (const key of diff.del) { + delete result[key]; + } + if (_.isArray(result)) + result = result.filter(v => v); + } + + return result; +} + +module.exports = { + getObjDiff, + isObjDiff, + isEmptyObjDiff, + applyObjDiff, +} \ No newline at end of file