Reader.vue 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  1. <template>
  2. <el-container>
  3. <el-header v-show="toolBarActive" height='50px'>
  4. <div class="header">
  5. <el-tooltip content="Загрузить книгу" :open-delay="1000" effect="light">
  6. <el-button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')"><i class="el-icon-back"></i></el-button>
  7. </el-tooltip>
  8. <div>
  9. <el-tooltip content="Действие назад" :open-delay="1000" effect="light">
  10. <el-button ref="undoAction" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" ><i class="el-icon-arrow-left"></i></el-button>
  11. </el-tooltip>
  12. <el-tooltip content="Действие вперед" :open-delay="1000" effect="light">
  13. <el-button ref="redoAction" class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')" ><i class="el-icon-arrow-right"></i></el-button>
  14. </el-tooltip>
  15. <div class="space"></div>
  16. <el-tooltip content="На весь экран" :open-delay="1000" effect="light">
  17. <el-button ref="fullScreen" class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')"><i class="el-icon-rank"></i></el-button>
  18. </el-tooltip>
  19. <el-tooltip content="Плавный скроллинг" :open-delay="1000" effect="light">
  20. <el-button ref="scrolling" class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')"><i class="el-icon-sort"></i></el-button>
  21. </el-tooltip>
  22. <el-tooltip content="Перелистнуть" :open-delay="1000" effect="light">
  23. <el-button ref="setPosition" class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')"><i class="el-icon-d-arrow-right"></i></el-button>
  24. </el-tooltip>
  25. <el-tooltip content="Найти в тексте" :open-delay="1000" effect="light">
  26. <el-button ref="search" class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')"><i class="el-icon-search"></i></el-button>
  27. </el-tooltip>
  28. <el-tooltip content="Скопировать текст со страницы" :open-delay="1000" effect="light">
  29. <el-button ref="copyText" class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')"><i class="el-icon-edit-outline"></i></el-button>
  30. </el-tooltip>
  31. <el-tooltip content="Принудительно обновить книгу в обход кэша" :open-delay="1000" effect="light">
  32. <el-button ref="refresh" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')">
  33. <i class="el-icon-refresh" :class="{clear: !showRefreshIcon}"></i>
  34. </el-button>
  35. </el-tooltip>
  36. <div class="space"></div>
  37. <el-tooltip content="Открыть недавние" :open-delay="1000" effect="light">
  38. <el-button ref="history" class="tool-button" :class="buttonActiveClass('history')" @click="buttonClick('history')"><i class="el-icon-document"></i></el-button>
  39. </el-tooltip>
  40. </div>
  41. <el-tooltip content="Настроить" :open-delay="1000" effect="light">
  42. <el-button ref="settings" class="tool-button" :class="buttonActiveClass('settings')" @click="buttonClick('settings')"><i class="el-icon-setting"></i></el-button>
  43. </el-tooltip>
  44. </div>
  45. </el-header>
  46. <el-main>
  47. <keep-alive>
  48. <component ref="page" :is="activePage"
  49. @load-book="loadBook"
  50. @load-file="loadFile"
  51. @book-pos-changed="bookPosChanged"
  52. @tool-bar-toggle="toolBarToggle"
  53. @full-screen-toogle="fullScreenToggle"
  54. @stop-scrolling="stopScrolling"
  55. @scrolling-toggle="scrollingToggle"
  56. @help-toggle="helpToggle"
  57. @donate-toggle="donateToggle"
  58. ></component>
  59. </keep-alive>
  60. <SetPositionPage v-if="setPositionActive" ref="setPositionPage" @set-position-toggle="setPositionToggle" @book-pos-changed="bookPosChanged"></SetPositionPage>
  61. <SearchPage v-show="searchActive" ref="searchPage"
  62. @search-toggle="searchToggle"
  63. @book-pos-changed="bookPosChanged"
  64. @start-text-search="startTextSearch"
  65. @stop-text-search="stopTextSearch">
  66. </SearchPage>
  67. <CopyTextPage v-if="copyTextActive" ref="copyTextPage" @copy-text-toggle="copyTextToggle"></CopyTextPage>
  68. <HistoryPage v-show="historyActive" ref="historyPage" @load-book="loadBook" @history-toggle="historyToggle"></HistoryPage>
  69. <SettingsPage v-if="settingsActive" ref="settingsPage" @settings-toggle="settingsToggle"></SettingsPage>
  70. <HelpPage v-if="helpActive" ref="helpPage" @help-toggle="helpToggle"></HelpPage>
  71. <ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
  72. <ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
  73. </el-main>
  74. </el-container>
  75. </template>
  76. <script>
  77. //-----------------------------------------------------------------------------
  78. import Vue from 'vue';
  79. import Component from 'vue-class-component';
  80. import _ from 'lodash';
  81. import {Buffer} from 'safe-buffer';
  82. import LoaderPage from './LoaderPage/LoaderPage.vue';
  83. import TextPage from './TextPage/TextPage.vue';
  84. import ProgressPage from './ProgressPage/ProgressPage.vue';
  85. import SetPositionPage from './SetPositionPage/SetPositionPage.vue';
  86. import SearchPage from './SearchPage/SearchPage.vue';
  87. import CopyTextPage from './CopyTextPage/CopyTextPage.vue';
  88. import HistoryPage from './HistoryPage/HistoryPage.vue';
  89. import SettingsPage from './SettingsPage/SettingsPage.vue';
  90. import HelpPage from './HelpPage/HelpPage.vue';
  91. import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
  92. import ServerStorage from './ServerStorage/ServerStorage.vue';
  93. import bookManager from './share/bookManager';
  94. import readerApi from '../../api/reader';
  95. import * as utils from '../../share/utils';
  96. export default @Component({
  97. components: {
  98. LoaderPage,
  99. TextPage,
  100. ProgressPage,
  101. SetPositionPage,
  102. SearchPage,
  103. CopyTextPage,
  104. HistoryPage,
  105. SettingsPage,
  106. HelpPage,
  107. ClickMapPage,
  108. ServerStorage,
  109. },
  110. watch: {
  111. bookPos: function(newValue) {
  112. if (newValue !== undefined && this.activePage == 'TextPage') {
  113. const textPage = this.$refs.page;
  114. if (textPage.bookPos != newValue) {
  115. textPage.bookPos = newValue;
  116. }
  117. this.debouncedSetRecentBook(newValue);
  118. }
  119. },
  120. routeParamPos: function(newValue) {
  121. if (newValue !== undefined && newValue != this.bookPos) {
  122. this.bookPos = newValue;
  123. }
  124. },
  125. routeParamUrl: function(newValue) {
  126. if (newValue !== '' && newValue !== this.mostRecentBook().url) {
  127. this.loadBook({url: newValue, bookPos: this.routeParamPos});
  128. }
  129. },
  130. settings: function() {
  131. this.loadSettings();
  132. this.updateRoute();
  133. },
  134. loaderActive: function(newValue) {
  135. const recent = this.mostRecentBook();
  136. if (!newValue && !this.loading && recent && !bookManager.hasBookParsed(recent)) {
  137. this.loadBook(recent);
  138. }
  139. },
  140. },
  141. })
  142. class Reader extends Vue {
  143. loaderActive = false;
  144. progressActive = false;
  145. fullScreenActive = false;
  146. scrollingActive = false;
  147. setPositionActive = false;
  148. searchActive = false;
  149. copyTextActive = false;
  150. historyActive = false;
  151. settingsActive = false;
  152. helpActive = false;
  153. clickMapActive = false;
  154. bookPos = null;
  155. allowUrlParamBookPos = false;
  156. showRefreshIcon = true;
  157. mostRecentBookReactive = null;
  158. actionList = [];
  159. actionCur = -1;
  160. hidden = false;
  161. created() {
  162. this.loading = true;
  163. this.commit = this.$store.commit;
  164. this.dispatch = this.$store.dispatch;
  165. this.reader = this.$store.state.reader;
  166. this.config = this.$store.state.config;
  167. this.$root.addKeyHook(this.keyHook);
  168. this.lastActivePage = false;
  169. this.debouncedUpdateRoute = _.debounce(() => {
  170. this.updateRoute();
  171. }, 1000);
  172. this.debouncedSetRecentBook = _.debounce(async(newValue) => {
  173. const recent = this.mostRecentBook();
  174. if (recent && (recent.bookPos != newValue || recent.bookPosSeen !== this.bookPosSeen)) {
  175. await bookManager.setRecentBook(Object.assign({}, recent, {bookPos: newValue, bookPosSeen: this.bookPosSeen}));
  176. if (this.actionCur < 0 || (this.actionCur >= 0 && this.actionList[this.actionCur] != newValue))
  177. this.addAction(newValue);
  178. }
  179. }, 500);
  180. this.debouncedSaveRecent = _.debounce(async() => {
  181. const serverStorage = this.$refs.serverStorage;
  182. while (!serverStorage.inited) await utils.sleep(1000);
  183. await serverStorage.saveRecent();
  184. }, 1000);
  185. this.debouncedSaveRecentLast = _.debounce(async() => {
  186. const serverStorage = this.$refs.serverStorage;
  187. while (!serverStorage.inited) await utils.sleep(1000);
  188. await serverStorage.saveRecentLast();
  189. }, 1000);
  190. document.addEventListener('fullscreenchange', () => {
  191. this.fullScreenActive = (document.fullscreenElement !== null);
  192. });
  193. this.loadSettings();
  194. }
  195. mounted() {
  196. (async() => {
  197. await bookManager.init(this.settings);
  198. bookManager.addEventListener(this.bookManagerEvent);
  199. if (this.$root.rootRoute == '/reader') {
  200. if (this.routeParamUrl) {
  201. await this.loadBook({url: this.routeParamUrl, bookPos: this.routeParamPos});
  202. } else {
  203. this.loaderActive = true;
  204. }
  205. }
  206. this.checkSetStorageAccessKey();
  207. this.loading = false;
  208. })();
  209. }
  210. loadSettings() {
  211. const settings = this.settings;
  212. this.allowUrlParamBookPos = settings.allowUrlParamBookPos;
  213. this.copyFullText = settings.copyFullText;
  214. this.showClickMapPage = settings.showClickMapPage;
  215. this.clickControl = settings.clickControl;
  216. this.blinkCachedLoad = settings.blinkCachedLoad;
  217. }
  218. checkSetStorageAccessKey() {
  219. const q = this.$route.query;
  220. if (q['setStorageAccessKey']) {
  221. this.$router.replace(`/reader`);
  222. this.settingsToggle();
  223. this.$nextTick(() => {
  224. this.$refs.settingsPage.enterServerStorageKey(
  225. Buffer.from(utils.fromBase58(q['setStorageAccessKey'])).toString()
  226. );
  227. });
  228. }
  229. }
  230. get routeParamPos() {
  231. let result = undefined;
  232. const q = this.$route.query;
  233. if (q['__p']) {
  234. result = q['__p'];
  235. if (Array.isArray(result))
  236. result = result[0];
  237. }
  238. return (result ? parseInt(result, 10) || 0 : result);
  239. }
  240. updateRoute(isNewRoute) {
  241. if (this.loading)
  242. return;
  243. const recent = this.mostRecentBook();
  244. const pos = (recent && recent.bookPos && this.allowUrlParamBookPos ? `__p=${recent.bookPos}&` : '');
  245. const url = (recent ? `url=${recent.url}` : '');
  246. if (isNewRoute)
  247. this.$router.push(`/reader?${pos}${url}`);
  248. else
  249. this.$router.replace(`/reader?${pos}${url}`);
  250. }
  251. get routeParamUrl() {
  252. let result = '';
  253. const path = this.$route.fullPath;
  254. const i = path.indexOf('url=');
  255. if (i >= 0) {
  256. result = path.substr(i + 4);
  257. }
  258. return decodeURIComponent(result);
  259. }
  260. bookPosChanged(event) {
  261. if (event.bookPosSeen !== undefined)
  262. this.bookPosSeen = event.bookPosSeen;
  263. this.bookPos = event.bookPos;
  264. this.debouncedUpdateRoute();
  265. }
  266. async bookManagerEvent(eventName) {
  267. const serverStorage = this.$refs.serverStorage;
  268. if (eventName == 'load-meta-finish') {
  269. serverStorage.init();
  270. const result = await bookManager.cleanRecentBooks();
  271. if (result)
  272. this.debouncedSaveRecent();
  273. }
  274. if (eventName == 'recent-changed' || eventName == 'save-recent') {
  275. if (this.historyActive) {
  276. this.$refs.historyPage.updateTableData();
  277. }
  278. const oldBook = this.mostRecentBookReactive;
  279. const newBook = bookManager.mostRecentBook();
  280. if (oldBook && newBook) {
  281. if (oldBook.key != newBook.key) {
  282. this.loadingBook = true;
  283. try {
  284. await this.loadBook(newBook);
  285. } finally {
  286. this.loadingBook = false;
  287. }
  288. } else if (oldBook.bookPos != newBook.bookPos) {
  289. while (this.loadingBook) await utils.sleep(100);
  290. this.bookPosChanged({bookPos: newBook.bookPos});
  291. }
  292. }
  293. if (eventName == 'recent-changed') {
  294. this.debouncedSaveRecentLast();
  295. } else {
  296. this.debouncedSaveRecent();
  297. }
  298. }
  299. }
  300. get toolBarActive() {
  301. return this.reader.toolBarActive;
  302. }
  303. mostRecentBook() {
  304. const result = bookManager.mostRecentBook();
  305. this.mostRecentBookReactive = result;
  306. return result;
  307. }
  308. get settings() {
  309. return this.$store.state.reader.settings;
  310. }
  311. addAction(pos) {
  312. let a = this.actionList;
  313. if (!a.length || a[a.length - 1] != pos) {
  314. a.push(pos);
  315. if (a.length > 20)
  316. a.shift();
  317. this.actionCur = a.length - 1;
  318. }
  319. }
  320. toolBarToggle() {
  321. this.commit('reader/setToolBarActive', !this.toolBarActive);
  322. this.$root.$emit('resize');
  323. }
  324. fullScreenToggle() {
  325. this.fullScreenActive = !this.fullScreenActive;
  326. if (this.fullScreenActive) {
  327. const element = document.documentElement;
  328. if (element.requestFullscreen) {
  329. element.requestFullscreen();
  330. } else if (element.webkitrequestFullscreen) {
  331. element.webkitRequestFullscreen();
  332. } else if (element.mozRequestFullscreen) {
  333. element.mozRequestFullScreen();
  334. }
  335. } else {
  336. if (document.cancelFullScreen) {
  337. document.cancelFullScreen();
  338. } else if (document.mozCancelFullScreen) {
  339. document.mozCancelFullScreen();
  340. } else if (document.webkitCancelFullScreen) {
  341. document.webkitCancelFullScreen();
  342. }
  343. }
  344. }
  345. closeAllTextPages() {
  346. this.setPositionActive = false;
  347. this.copyTextActive = false;
  348. this.historyActive = false;
  349. this.settingsActive = false;
  350. this.stopScrolling();
  351. this.stopSearch();
  352. this.helpActive = false;
  353. }
  354. loaderToggle() {
  355. this.loaderActive = !this.loaderActive;
  356. if (this.loaderActive) {
  357. this.closeAllTextPages();
  358. }
  359. }
  360. setPositionToggle() {
  361. this.setPositionActive = !this.setPositionActive;
  362. if (this.setPositionActive && this.activePage == 'TextPage' && this.mostRecentBook()) {
  363. this.closeAllTextPages();
  364. this.setPositionActive = true;
  365. this.$nextTick(() => {
  366. const recent = this.mostRecentBook();
  367. this.$refs.setPositionPage.init(recent.bookPos, recent.textLength - 1);
  368. });
  369. } else {
  370. this.setPositionActive = false;
  371. }
  372. }
  373. stopScrolling() {
  374. if (this.scrollingActive)
  375. this.scrollingToggle();
  376. }
  377. scrollingToggle() {
  378. this.scrollingActive = !this.scrollingActive;
  379. if (this.activePage == 'TextPage') {
  380. const page = this.$refs.page;
  381. if (this.scrollingActive) {
  382. page.startTextScrolling();
  383. } else {
  384. page.stopTextScrolling();
  385. }
  386. }
  387. }
  388. stopSearch() {
  389. if (this.searchActive)
  390. this.searchToggle();
  391. }
  392. startTextSearch(opts) {
  393. if (this.activePage == 'TextPage')
  394. this.$refs.page.startSearch(opts.needle);
  395. }
  396. stopTextSearch() {
  397. if (this.activePage == 'TextPage')
  398. this.$refs.page.stopSearch();
  399. }
  400. searchToggle() {
  401. this.searchActive = !this.searchActive;
  402. const page = this.$refs.page;
  403. if (this.searchActive && this.activePage == 'TextPage' && page.parsed) {
  404. this.closeAllTextPages();
  405. this.searchActive = true;
  406. this.$nextTick(() => {
  407. this.$refs.searchPage.init(page.parsed);
  408. });
  409. } else {
  410. this.stopTextSearch();
  411. this.searchActive = false;
  412. }
  413. }
  414. copyTextToggle() {
  415. this.copyTextActive = !this.copyTextActive;
  416. const page = this.$refs.page;
  417. if (this.copyTextActive && this.activePage == 'TextPage' && page.parsed) {
  418. this.closeAllTextPages();
  419. this.copyTextActive = true;
  420. this.$nextTick(() => {
  421. this.$refs.copyTextPage.init(this.mostRecentBook().bookPos, page.parsed, this.copyFullText);
  422. });
  423. } else {
  424. this.copyTextActive = false;
  425. }
  426. }
  427. historyToggle() {
  428. this.historyActive = !this.historyActive;
  429. if (this.historyActive) {
  430. this.closeAllTextPages();
  431. this.$refs.historyPage.init();
  432. this.historyActive = true;
  433. } else {
  434. this.historyActive = false;
  435. }
  436. }
  437. settingsToggle() {
  438. this.settingsActive = !this.settingsActive;
  439. if (this.settingsActive) {
  440. this.closeAllTextPages();
  441. this.settingsActive = true;
  442. } else {
  443. this.settingsActive = false;
  444. }
  445. }
  446. helpToggle() {
  447. this.helpActive = !this.helpActive;
  448. if (this.helpActive) {
  449. this.closeAllTextPages();
  450. this.helpActive = true;
  451. }
  452. }
  453. donateToggle() {
  454. this.helpToggle();
  455. if (this.helpActive) {
  456. this.$nextTick(() => {
  457. this.$refs.helpPage.activateDonateHelpPage();
  458. });
  459. }
  460. }
  461. refreshBook() {
  462. if (this.mostRecentBook()) {
  463. this.loadBook({url: this.mostRecentBook().url, force: true});
  464. }
  465. }
  466. buttonClick(button) {
  467. const activeClass = this.buttonActiveClass(button);
  468. this.$refs[button].$el.blur();
  469. if (activeClass['tool-button-disabled'])
  470. return;
  471. switch (button) {
  472. case 'loader':
  473. this.loaderToggle();
  474. break;
  475. case 'undoAction':
  476. if (this.actionCur > 0) {
  477. this.actionCur--;
  478. this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
  479. }
  480. break;
  481. case 'redoAction':
  482. if (this.actionCur < this.actionList.length - 1) {
  483. this.actionCur++;
  484. this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
  485. }
  486. break;
  487. case 'fullScreen':
  488. this.fullScreenToggle();
  489. break;
  490. case 'setPosition':
  491. this.setPositionToggle();
  492. break;
  493. case 'scrolling':
  494. this.scrollingToggle();
  495. break;
  496. case 'search':
  497. this.searchToggle();
  498. break;
  499. case 'copyText':
  500. this.copyTextToggle();
  501. break;
  502. case 'history':
  503. this.historyToggle();
  504. break;
  505. case 'refresh':
  506. this.refreshBook();
  507. break;
  508. case 'settings':
  509. this.settingsToggle();
  510. break;
  511. }
  512. }
  513. buttonActiveClass(button) {
  514. const classActive = { 'tool-button-active': true, 'tool-button-active:hover': true };
  515. const classDisabled = { 'tool-button-disabled': true, 'tool-button-disabled:hover': true };
  516. let classResult = {};
  517. switch (button) {
  518. case 'loader':
  519. case 'fullScreen':
  520. case 'setPosition':
  521. case 'scrolling':
  522. case 'search':
  523. case 'copyText':
  524. case 'history':
  525. case 'settings':
  526. if (this[`${button}Active`])
  527. classResult = classActive;
  528. break;
  529. }
  530. switch (button) {
  531. case 'undoAction':
  532. if (this.actionCur <= 0)
  533. classResult = classDisabled;
  534. break;
  535. case 'redoAction':
  536. if (this.actionCur == this.actionList.length - 1)
  537. classResult = classDisabled;
  538. break;
  539. }
  540. if (this.activePage == 'LoaderPage' || !this.mostRecentBook()) {
  541. switch (button) {
  542. case 'undoAction':
  543. case 'redoAction':
  544. case 'setPosition':
  545. case 'scrolling':
  546. case 'search':
  547. case 'copyText':
  548. classResult = classDisabled;
  549. break;
  550. case 'history':
  551. case 'refresh':
  552. if (!this.mostRecentBook())
  553. classResult = classDisabled;
  554. break;
  555. }
  556. }
  557. return classResult;
  558. }
  559. async activateClickMapPage() {
  560. if (this.clickControl && this.showClickMapPage && !this.clickMapActive) {
  561. this.clickMapActive = true;
  562. await this.$refs.clickMapPage.slowDisappear();
  563. this.clickMapActive = false;
  564. }
  565. }
  566. get activePage() {
  567. let result = '';
  568. if (this.progressActive)
  569. result = 'ProgressPage';
  570. else if (this.loaderActive)
  571. result = 'LoaderPage';
  572. else if (this.mostRecentBookReactive)
  573. result = 'TextPage';
  574. if (!result && !this.loading) {
  575. this.loaderActive = true;
  576. result = 'LoaderPage';
  577. }
  578. if (result != 'TextPage') {
  579. this.$root.$emit('set-app-title');
  580. }
  581. // на LoaderPage всегда показываем toolBar
  582. if (result == 'LoaderPage' && !this.toolBarActive) {
  583. this.toolBarToggle();
  584. }
  585. if (this.lastActivePage != result && result == 'TextPage') {
  586. //акивируем страницу с текстом
  587. this.$nextTick(async() => {
  588. const last = this.mostRecentBookReactive;
  589. const isParsed = bookManager.hasBookParsed(last);
  590. if (!isParsed) {
  591. this.$root.$emit('set-app-title');
  592. return;
  593. }
  594. this.updateRoute();
  595. const textPage = this.$refs.page;
  596. if (textPage.showBook) {
  597. textPage.lastBook = last;
  598. textPage.bookPos = (last.bookPos !== undefined ? last.bookPos : 0);
  599. textPage.showBook();
  600. }
  601. });
  602. }
  603. this.lastActivePage = result;
  604. return result;
  605. }
  606. async loadBook(opts) {
  607. if (!opts || !opts.url) {
  608. this.mostRecentBook();
  609. return;
  610. }
  611. let url = opts.url;
  612. if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0) &&
  613. (url.indexOf('file://') != 0))
  614. url = 'http://' + url;
  615. // уже просматривается сейчас
  616. const lastBook = (this.$refs.page ? this.$refs.page.lastBook : null);
  617. if (!opts.force && lastBook && lastBook.url == url && bookManager.hasBookParsed(lastBook)) {
  618. this.loaderActive = false;
  619. return;
  620. }
  621. this.progressActive = true;
  622. await this.$nextTick()
  623. const progress = this.$refs.page;
  624. this.actionList = [];
  625. this.actionCur = -1;
  626. try {
  627. progress.show();
  628. progress.setState({state: 'parse'});
  629. // есть ли среди недавних
  630. const key = bookManager.keyFromUrl(url);
  631. let wasOpened = await bookManager.getRecentBook({key});
  632. wasOpened = (wasOpened ? wasOpened : {});
  633. const bookPos = (opts.bookPos !== undefined ? opts.bookPos : wasOpened.bookPos);
  634. const bookPosSeen = (opts.bookPos !== undefined ? opts.bookPos : wasOpened.bookPosSeen);
  635. let book = null;
  636. if (!opts.force) {
  637. // пытаемся загрузить и распарсить книгу в менеджере из локального кэша
  638. const bookParsed = await bookManager.getBook({url}, (prog) => {
  639. progress.setState({progress: prog});
  640. });
  641. // если есть в локальном кэше
  642. if (bookParsed) {
  643. await bookManager.setRecentBook(Object.assign({bookPos, bookPosSeen}, bookParsed));
  644. this.mostRecentBook();
  645. this.addAction(bookPos);
  646. this.loaderActive = false;
  647. progress.hide(); this.progressActive = false;
  648. this.blinkCachedLoadMessage();
  649. await this.activateClickMapPage();
  650. return;
  651. }
  652. // иначе идем на сервер
  653. // пытаемся загрузить готовый файл с сервера
  654. if (wasOpened.path) {
  655. try {
  656. const resp = await readerApi.loadCachedBook(wasOpened.path, (state) => {
  657. progress.setState(state);
  658. });
  659. book = Object.assign({}, wasOpened, {data: resp.data});
  660. } catch (e) {
  661. //молчим
  662. }
  663. }
  664. }
  665. progress.setState({totalSteps: 5});
  666. // не удалось, скачиваем книгу полностью с конвертацией
  667. let loadCached = true;
  668. if (!book) {
  669. book = await readerApi.loadBook(url, (state) => {
  670. progress.setState(state);
  671. });
  672. loadCached = false;
  673. }
  674. // добавляем в bookManager
  675. progress.setState({state: 'parse', step: 5});
  676. const addedBook = await bookManager.addBook(book, (prog) => {
  677. progress.setState({progress: prog});
  678. });
  679. // добавляем в историю
  680. await bookManager.setRecentBook(Object.assign({bookPos, bookPosSeen}, addedBook));
  681. this.mostRecentBook();
  682. this.addAction(bookPos);
  683. this.updateRoute(true);
  684. this.loaderActive = false;
  685. progress.hide(); this.progressActive = false;
  686. if (loadCached) {
  687. this.blinkCachedLoadMessage();
  688. } else
  689. this.stopBlink = true;
  690. await this.activateClickMapPage();
  691. } catch (e) {
  692. progress.hide(); this.progressActive = false;
  693. this.loaderActive = true;
  694. this.$alert(e.message, 'Ошибка', {type: 'error'});
  695. }
  696. }
  697. async loadFile(opts) {
  698. this.progressActive = true;
  699. await this.$nextTick();
  700. const progress = this.$refs.page;
  701. try {
  702. progress.show();
  703. progress.setState({state: 'upload'});
  704. const url = await readerApi.uploadFile(opts.file, this.config.maxUploadFileSize, (state) => {
  705. progress.setState(state);
  706. });
  707. progress.hide(); this.progressActive = false;
  708. await this.loadBook({url});
  709. } catch (e) {
  710. progress.hide(); this.progressActive = false;
  711. this.loaderActive = true;
  712. this.$alert(e.message, 'Ошибка', {type: 'error'});
  713. }
  714. }
  715. blinkCachedLoadMessage() {
  716. if (!this.blinkCachedLoad)
  717. return;
  718. this.blinkCount = 30;
  719. if (!this.inBlink) {
  720. this.inBlink = true;
  721. this.stopBlink = false;
  722. this.$nextTick(async() => {
  723. let page = this.$refs.page;
  724. while (this.blinkCount) {
  725. this.showRefreshIcon = !this.showRefreshIcon;
  726. if (page.blinkCachedLoadMessage)
  727. page.blinkCachedLoadMessage(this.showRefreshIcon);
  728. await utils.sleep(500);
  729. if (this.stopBlink)
  730. break;
  731. this.blinkCount--;
  732. page = this.$refs.page;
  733. }
  734. this.showRefreshIcon = true;
  735. this.inBlink = false;
  736. if (page.blinkCachedLoadMessage)
  737. page.blinkCachedLoadMessage('finish');
  738. });
  739. }
  740. }
  741. keyHook(event) {
  742. if (this.$root.rootRoute == '/reader') {
  743. let handled = false;
  744. if (!handled && this.helpActive)
  745. handled = this.$refs.helpPage.keyHook(event);
  746. if (!handled && this.settingsActive)
  747. handled = this.$refs.settingsPage.keyHook(event);
  748. if (!handled && this.historyActive)
  749. handled = this.$refs.historyPage.keyHook(event);
  750. if (!handled && this.setPositionActive)
  751. handled = this.$refs.setPositionPage.keyHook(event);
  752. if (!handled && this.searchActive)
  753. handled = this.$refs.searchPage.keyHook(event);
  754. if (!handled && this.copyTextActive)
  755. handled = this.$refs.copyTextPage.keyHook(event);
  756. if (!handled && this.$refs.page && this.$refs.page.keyHook)
  757. handled = this.$refs.page.keyHook(event);
  758. if (!handled && event.type == 'keydown') {
  759. if (event.code == 'Escape')
  760. this.loaderToggle();
  761. if (this.activePage == 'TextPage') {
  762. switch (event.code) {
  763. case 'KeyH':
  764. case 'F1':
  765. this.helpToggle();
  766. event.preventDefault();
  767. event.stopPropagation();
  768. break;
  769. case 'KeyZ':
  770. this.scrollingToggle();
  771. break;
  772. case 'KeyP':
  773. this.setPositionToggle();
  774. break;
  775. case 'KeyF':
  776. if (event.ctrlKey) {
  777. this.searchToggle();
  778. event.preventDefault();
  779. event.stopPropagation();
  780. }
  781. break;
  782. case 'KeyC':
  783. if (event.ctrlKey) {
  784. this.copyTextToggle();
  785. event.preventDefault();
  786. event.stopPropagation();
  787. }
  788. break;
  789. case 'KeyR':
  790. this.refreshBook();
  791. break;
  792. case 'KeyX':
  793. this.historyToggle();
  794. event.preventDefault();
  795. event.stopPropagation();
  796. break;
  797. case 'KeyS':
  798. this.settingsToggle();
  799. break;
  800. }
  801. }
  802. }
  803. }
  804. }
  805. }
  806. //-----------------------------------------------------------------------------
  807. </script>
  808. <style scoped>
  809. .el-container {
  810. padding: 0;
  811. margin: 0;
  812. height: 100%;
  813. }
  814. .el-header {
  815. padding-left: 5px;
  816. padding-right: 5px;
  817. background-color: #1B695F;
  818. color: #000;
  819. overflow-x: auto;
  820. overflow-y: hidden;
  821. }
  822. .header {
  823. display: flex;
  824. justify-content: space-between;
  825. min-width: 550px;
  826. }
  827. .el-main {
  828. position: relative;
  829. display: flex;
  830. padding: 0;
  831. margin: 0;
  832. background-color: #EBE2C9;
  833. color: #000;
  834. }
  835. .tool-button {
  836. margin: 0 2px 0 2px;
  837. padding: 0;
  838. color: #3E843E;
  839. background-color: #E6EDF4;
  840. margin-top: 5px;
  841. height: 38px;
  842. width: 38px;
  843. border: 0;
  844. box-shadow: 3px 3px 5px black;
  845. }
  846. .tool-button:hover {
  847. background-color: white;
  848. }
  849. .tool-button-active {
  850. box-shadow: 0 0 0;
  851. color: white;
  852. background-color: #8AB45F;
  853. position: relative;
  854. top: 1px;
  855. left: 1px;
  856. }
  857. .tool-button-active:hover {
  858. color: white;
  859. background-color: #81C581;
  860. }
  861. .tool-button-disabled {
  862. color: lightgray;
  863. background-color: gray;
  864. }
  865. .tool-button-disabled:hover {
  866. color: lightgray;
  867. background-color: gray;
  868. }
  869. i {
  870. font-size: 200%;
  871. }
  872. .space {
  873. width: 10px;
  874. display: inline-block;
  875. }
  876. .clear {
  877. color: rgba(0,0,0,0);
  878. }
  879. </style>