diff --git a/client/components/ExternalLibs/BookmarkSettings/BookmarkSettings.vue b/client/components/ExternalLibs/BookmarkSettings/BookmarkSettings.vue index b41d3fc6..b43ac51c 100644 --- a/client/components/ExternalLibs/BookmarkSettings/BookmarkSettings.vue +++ b/client/components/ExternalLibs/BookmarkSettings/BookmarkSettings.vue @@ -11,7 +11,7 @@ Открыть выбранную закладку - + diff --git a/client/components/ExternalLibs/ExternalLibs.vue b/client/components/ExternalLibs/ExternalLibs.vue index f0832df0..aa2ddf7f 100644 --- a/client/components/ExternalLibs/ExternalLibs.vue +++ b/client/components/ExternalLibs/ExternalLibs.vue @@ -5,19 +5,19 @@
- -
+
+ + + +
@@ -93,8 +100,4 @@ export default vueComponent(HelpPage); diff --git a/client/components/Reader/ProgressPage/ProgressPage.vue b/client/components/Reader/ProgressPage/ProgressPage.vue index ff4bca7f..c36b5b51 100644 --- a/client/components/Reader/ProgressPage/ProgressPage.vue +++ b/client/components/Reader/ProgressPage/ProgressPage.vue @@ -1,5 +1,5 @@ @@ -99,9 +164,10 @@ import vueComponent from '../../vueComponent.js'; import path from 'path-browserify'; -//import _ from 'lodash'; +import _ from 'lodash'; import * as utils from '../../../share/utils'; +import LockQueue from '../../../share/LockQueue'; import Window from '../../share/Window.vue'; import bookManager from '../share/bookManager'; import readerApi from '../../../api/reader'; @@ -111,9 +177,15 @@ const componentOptions = { Window, }, watch: { - search: function() { + search() { this.updateTableData(); - } + }, + sortMethod() { + this.updateTableData(); + }, + settings() { + this.loadSettings(); + }, }, }; class RecentBooksPage { @@ -122,52 +194,18 @@ class RecentBooksPage { loading = false; search = ''; tableData = []; - columns = []; - pagination = {}; + sortMethod = ''; + showSameBook = false; created() { - this.firstInit = true; - this.pagination = {rowsPerPage: 0}; + this.commit = this.$store.commit; - this.columns = [ - { - name: 'num', - label: '#', - align: 'center', - sortable: true, - field: 'num', - }, - { - name: 'date', - label: 'Время
просм.', - align: 'left', - field: 'touchDateTime', - sortable: true, - sort: (a, b, rowA, rowB) => rowA.touchDateTime - rowB.touchDateTime, - }, - { - name: 'desc', - label: 'Название', - align: 'left', - field: 'descString', - sortable: true, - }, - { - name: 'links', - label: '', - align: 'left', - }, - { - name: 'close', - label: '', - align: 'left', - }, - { - name: 'last', - label: '', - align: 'left', - }, - ]; + this.lastScrollTop1 = 0; + this.lastScrollTop2 = 0; + + this.lock = new LockQueue(100); + + this.loadSettings(); } init() { @@ -176,89 +214,186 @@ class RecentBooksPage { this.$nextTick(() => { //this.$refs.input.focus();//плохо на планшетах }); - (async() => {//подгрузка списка - if (this.initing) - return; - this.initing = true; - if (this.firstInit) {//для отзывчивости - await this.updateTableData(20); - this.firstInit = false; - } - await utils.sleep(50); + this.inited = true; + + (async() => { + this.showBar(); await this.updateTableData(); - - this.initing = false; + await this.scrollToActiveBook(); })(); } - async updateTableData(limit) { - while (this.updating) await utils.sleep(100); - this.updating = true; - let result = []; + loadSettings() { + const settings = this.settings; + this.showSameBook = settings.recentShowSameBook; + this.sortMethod = settings.recentSortMethod || 'loadTimeDesc'; + } - this.loading = !!limit; - const sorted = bookManager.getSortedRecent(); + get settings() { + return this.$store.state.reader.settings; + } - let num = 0; - for (let i = 0; i < sorted.length; i++) { - const book = sorted[i]; - if (book.deleted) - continue; + async updateTableData() { + if (!this.inited) + return; - num++; - if (limit && result.length >= limit) - break; + await this.lock.get(); + try { + let result = []; - let d = new Date(); - d.setTime(book.touchTime); - const t = utils.formatDate(d).split(' '); + const sorted = bookManager.getSortedRecent(); + const activeBook = bookManager.mostRecentBook(); - let readPart = 0; - let perc = ''; - let textLen = ''; - const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0)); - if (book.textLength) { - readPart = p/book.textLength; - perc = ` [${(readPart*100).toFixed(2)}%]`; - textLen = ` ${Math.round(book.textLength/1000)}k`; + //подготовка полей + for (const book of sorted) { + if (book.deleted) + continue; + + let d = new Date(); + d.setTime(book.touchTime); + const touchTime = utils.formatDate(d); + const loadTimeRaw = (book.loadTime ? book.loadTime : 0);//book.addTime); + d.setTime(loadTimeRaw); + const loadTime = utils.formatDate(d); + + let readPart = 0; + let perc = ''; + let textLen = ''; + const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0)); + if (book.textLength) { + readPart = p/book.textLength; + perc = `${(readPart*100).toFixed(2)}%`; + textLen = `${Math.floor(readPart*book.textLength/1000)}/${Math.floor(book.textLength/1000)}`; + } + + const bt = utils.getBookTitle(book.fb2); + + let title = bt.bookTitle; + title = (title ? `"${title}"`: ''); + const author = (bt.author ? bt.author : (bt.bookTitle ? bt.bookTitle : (book.uploadFileName ? book.uploadFileName : book.url))); + + result.push({ + touchTime, + loadTime, + desc: { + author, + title, + perc, + textLen, + }, + readPart, + url: book.url, + path: book.path, + fullTitle: bt.fullTitle, + key: book.key, + sameBookKey: book.sameBookKey, + active: (activeBook.key == book.key), + activeParent: false, + inGroup: false, + + //для сортировки + loadTimeRaw, + touchTimeRaw: book.touchTime, + }); } - const bt = utils.getBookTitle(book.fb2); + //нумерация + let num = 0; - let title = bt.bookTitle; - title = (title ? `"${title}"`: ''); - const author = (bt.author ? bt.author : (bt.bookTitle ? bt.bookTitle : book.url)); + result.sort((a, b) => b.loadTimeRaw - a.loadTimeRaw); + for (const book of result) { + num++; + book.num = num; + } - result.push({ - num, - touchDateTime: book.touchTime, - touchDate: t[0], - touchTime: t[1], - desc: { - author, - title: `${title}${perc}${textLen}`, - }, - readPart, - descString: `${author}${title}${perc}${textLen}`,//для сортировки - url: book.url, - path: book.path, - fullTitle: bt.fullTitle, - key: book.key, - }); + //фильтрация + const search = this.search; + if (search) { + result = result.filter(item => { + return !search || + item.touchTime.includes(search) || + item.loadTime.includes(search) || + item.desc.title.toLowerCase().includes(search.toLowerCase()) || + item.desc.author.toLowerCase().includes(search.toLowerCase()) + }); + } + + //сортировка + switch (this.sortMethod) { + case 'loadTimeDesc': + result.sort((a, b) => b.loadTimeRaw - a.loadTimeRaw); + break; + case 'loadTimeAsc': + result.sort((a, b) => a.loadTimeRaw - b.loadTimeRaw); + break; + case 'touchTimeDesc': + result.sort((a, b) => b.touchTimeRaw - a.touchTimeRaw); + break; + case 'touchTimeAsc': + result.sort((a, b) => a.touchTimeRaw - b.touchTimeRaw); + break; + case 'authorDesc': + result.sort((a, b) => b.desc.author.localeCompare(a.desc.author)); + break; + case 'authorAsc': + result.sort((a, b) => a.desc.author.localeCompare(b.desc.author)); + break; + case 'titleDesc': + result.sort((a, b) => b.desc.title.localeCompare(a.desc.title)); + break; + case 'titleAsc': + result.sort((a, b) => a.desc.title.localeCompare(b.desc.title)); + break; + } + + //группировка + const groups = {}; + const parents = {}; + let newResult = []; + for (const book of result) { + if (book.sameBookKey !== undefined) { + if (!groups[book.sameBookKey]) { + groups[book.sameBookKey] = []; + parents[book.sameBookKey] = book; + + book.group = groups[book.sameBookKey]; + newResult.push(book); + } else { + book.inGroup = true; + if (book.active) + parents[book.sameBookKey].activeParent = true; + + groups[book.sameBookKey].push(book); + } + } else { + newResult.push(book); + } + } + result = newResult; + + //showSameBook + if (this.showSameBook) { + newResult = []; + for (const book of result) { + newResult.push(book); + if (book.group) { + for (const sameBook of book.group) { + newResult.push(sameBook); + } + } + } + + result = newResult; + } + + //другие стадии + //..... + + this.tableData = result; + } finally { + this.lock.ret(); } - - const search = this.search; - result = result.filter(item => { - return !search || - item.touchTime.includes(search) || - item.touchDate.includes(search) || - item.desc.title.toLowerCase().includes(search.toLowerCase()) || - item.desc.author.toLowerCase().includes(search.toLowerCase()) - }); - - this.tableData = result; - this.updating = false; } resetSearch() { @@ -266,19 +401,22 @@ class RecentBooksPage { this.$refs.input.focus(); } - wordEnding(num) { - const endings = ['', 'а', 'и', 'и', 'и', '', '', '', '', '']; + wordEnding(num, type = 0) { + const endings = [ + ['ов', '', 'а', 'а', 'а', 'ов', 'ов', 'ов', 'ов', 'ов'], + ['й', 'я', 'и', 'и', 'и', 'й', 'й', 'й', 'й', 'й'] + ]; const deci = num % 100; if (deci > 10 && deci < 20) { - return ''; + return endings[type][0]; } else { - return endings[num % 10]; + return endings[type][num % 10]; } } get header() { const len = (this.tableData ? this.tableData.length : 0); - return `${(this.search ? 'Найдено' : 'Всего')} ${len} книг${this.wordEnding(len)}`; + return `${(this.search ? 'Найдено' : 'Всего')} ${len} файл${this.wordEnding(len)}`; } async downloadBook(fb2path, fullTitle) { @@ -311,8 +449,8 @@ class RecentBooksPage { this.close(); } - loadBook(url) { - this.$emit('load-book', {url}); + loadBook(row) { + this.$emit('load-book', {url: row.url, path: row.path}); this.close(); } @@ -323,6 +461,111 @@ class RecentBooksPage { return false; } + showBar() { + this.lastScrollTop1 = this.$refs.vsContainer.scrollTop; + this.$refs.header.style.position = 'sticky'; + this.$refs.header.style.top = 0; + } + + onScroll() { + const curScrollTop = this.$refs.vsContainer.scrollTop; + + if (this.lockScroll) { + this.lastScrollTop1 = curScrollTop; + return; + } + + if (curScrollTop - this.lastScrollTop1 > 100) { + this.$refs.header.style.top = `-${this.$refs.header.offsetHeight}px`; + this.$refs.header.style.transition = 'top 0.2s ease 0s'; + + this.lastScrollTop1 = curScrollTop; + } else if (curScrollTop - this.lastScrollTop2 < 0) { + this.$refs.header.style.position = 'sticky'; + this.$refs.header.style.top = 0; + + this.lastScrollTop1 = curScrollTop; + } + + this.lastScrollTop2 = curScrollTop; + } + + showSameBookClick() { + this.showSameBook = !this.showSameBook; + + const newSettings = _.cloneDeep(this.settings); + newSettings.recentShowSameBook = this.showSameBook; + this.commit('reader/setSettings', newSettings); + + this.updateTableData(); + } + + sortMethodSelected() { + const newSettings = _.cloneDeep(this.settings); + newSettings.recentSortMethod = this.sortMethod; + this.commit('reader/setSettings', newSettings); + } + + async scrollToActiveBook() { + this.lockScroll = true; + try { + let activeIndex = -1; + let activeParentIndex = -1; + for (let i = 0; i < this.tableData.length; i++) { + const book = this.tableData[i]; + if (book.active) + activeIndex = i; + if (book.activeParent) + activeParentIndex = i; + + if (activeIndex >= 0 && activeParentIndex >= 0) + break; + } + + const index = (activeIndex >= 0 ? activeIndex : activeParentIndex); + if (index >= 0) { + this.$refs.virtualScroll.scrollTo(index, 'center'); + } + } finally { + await utils.sleep(100); + this.lockScroll = false; + } + } + + async scrollToBegin() { + this.lockScroll = true; + try { + this.$refs.virtualScroll.scrollTo(0, 'center'); + } finally { + await utils.sleep(100); + this.lockScroll = false; + } + } + + async scrollToEnd() { + this.lockScroll = true; + try { + this.$refs.virtualScroll.scrollTo(this.tableData.length, 'center'); + } finally { + await utils.sleep(100); + this.lockScroll = false; + } + } + + + get sortMethodOptions() { + return [ + {label: ' Время загрузки', value: 'loadTimeDesc'}, + {label: ' Время загрузки', value: 'loadTimeAsc'}, + {label: ' Время чтения', value: 'touchTimeDesc'}, + {label: ' Время чтения', value: 'touchTimeAsc'}, + {label: ' Автор', value: 'authorDesc'}, + {label: ' Автор', value: 'authorAsc'}, + {label: ' Название', value: 'titleDesc'}, + {label: ' Название', value: 'titleAsc'}, + ]; + } + close() { this.$emit('recent-books-close'); } @@ -340,29 +583,34 @@ export default vueComponent(RecentBooksPage); - - diff --git a/client/components/Reader/SearchPage/SearchPage.vue b/client/components/Reader/SearchPage/SearchPage.vue index bc6ec247..16786efe 100644 --- a/client/components/Reader/SearchPage/SearchPage.vue +++ b/client/components/Reader/SearchPage/SearchPage.vue @@ -8,12 +8,10 @@ {{ initPercentage }}%
- -
@@ -108,7 +106,7 @@ class SearchPage { this.parsed = parsed; } - this.header = 'Найти'; + this.header = 'Поиск в тексте'; await this.$nextTick(); this.$refs.input.focus(); this.$refs.input.select(); diff --git a/client/components/Reader/SettingsPage/ButtonsTab.inc b/client/components/Reader/SettingsPage/ButtonsTab.inc deleted file mode 100644 index 27a6e818..00000000 --- a/client/components/Reader/SettingsPage/ButtonsTab.inc +++ /dev/null @@ -1,9 +0,0 @@ -
Показывать кнопки панели
- -
-
-
- -
-
diff --git a/client/components/Reader/SettingsPage/SettingsPage.vue b/client/components/Reader/SettingsPage/SettingsPage.vue index 2cb0dc21..4982df37 100644 --- a/client/components/Reader/SettingsPage/SettingsPage.vue +++ b/client/components/Reader/SettingsPage/SettingsPage.vue @@ -1,5 +1,5 @@