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 @@
-
-
-
+
+
@@ -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