<template>
    <div class="column no-wrap">
        <div v-show="toolBarActive" ref="header" class="header">
            <div ref="buttons" class="row" :class="{'no-wrap': !toolBarMultiLine}">
                <button ref="loader" v-ripple class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')">
                    <q-icon name="la la-arrow-left" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">
                        {{ rstore.readerActions['loader'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['loadFile']" ref="loadFile" v-ripple class="tool-button" :class="buttonActiveClass('loadFile')" @click="buttonClick('loadFile')">
                    <q-icon name="la la-caret-square-up" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">
                        {{ rstore.readerActions['loadFile'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['loadBuffer']" ref="loadBuffer" v-ripple class="tool-button" :class="buttonActiveClass('loadBuffer')" @click="buttonClick('loadBuffer')">
                    <q-icon name="la la-comment" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">
                        {{ rstore.readerActions['loadBuffer'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['help']" ref="help" v-ripple class="tool-button" :class="buttonActiveClass('help')" @click="buttonClick('help')">
                    <q-icon name="la la-question" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">
                        {{ rstore.readerActions['help'] }}
                    </q-tooltip>
                </button>

                <div class="col"></div>

                <div class="space"></div>
                <button v-show="showToolButton['undoAction']" ref="undoAction" v-ripple class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')">
                    <q-icon name="la la-angle-left" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['undoAction'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['redoAction']" ref="redoAction" v-ripple class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')">
                    <q-icon name="la la-angle-right" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['redoAction'] }}
                    </q-tooltip>
                </button>
                <div class="space"></div>
                <button v-show="showToolButton['fullScreen']" ref="fullScreen" v-ripple class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')">
                    <q-icon :name="(fullScreenActive ? 'la la-compress-arrows-alt': 'la la-expand-arrows-alt')" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['fullScreen'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['scrolling']" ref="scrolling" v-ripple class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')">
                    <q-icon name="la la-film" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['scrolling'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['setPosition']" ref="setPosition" v-ripple class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')">
                    <q-icon name="la la-angle-double-right" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['setPosition'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['search']" ref="search" v-ripple class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')">
                    <q-icon name="la la-search" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['search'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['copyText']" ref="copyText" v-ripple class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')">
                    <q-icon name="la la-copy" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['copyText'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['convOptions']" ref="convOptions" v-ripple class="tool-button" :class="buttonActiveClass('convOptions')" @click="buttonClick('convOptions')">
                    <q-icon name="la la-magic" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['convOptions'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['refresh']" ref="refresh" v-ripple class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')">
                    <q-icon name="la la-sync" size="32px" :class="{clear: !showRefreshIcon}" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['refresh'] }}
                    </q-tooltip>
                </button>
                <div v-show="showToolButton['libs']" class="space"></div>
                <button v-show="showToolButton['libs']" ref="libs" v-ripple class="tool-button" :class="buttonActiveClass('libs')" @click="buttonClick('libs')">
                    <q-icon name="la la-sitemap" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['libs'] }}
                    </q-tooltip>
                </button>
                <div class="space"></div>
                <button v-show="showToolButton['contents']" ref="contents" v-ripple class="tool-button" :class="buttonActiveClass('contents')" @click="buttonClick('contents')">
                    <q-icon name="la la-list" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['contents'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['recentBooks']" ref="recentBooks" v-ripple class="tool-button" :class="buttonActiveClass('recentBooks')" @click="buttonClick('recentBooks')">
                    <div v-show="bothBucEnabled && needBookUpdateCount > 0" style="position: absolute">
                        <div class="need-book-update-count">
                            {{ needBookUpdateCount }}
                        </div>
                    </div>

                    <q-icon name="la la-book-open" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['recentBooks'] }}
                    </q-tooltip>
                </button>
                <div class="space"></div>

                <div class="col"></div>

                <button v-show="showToolButton['nightMode']" ref="nightMode" v-ripple class="tool-button" :class="buttonActiveClass('nightMode')" @click="buttonClick('nightMode')">
                    <q-icon name="la la-moon" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['nightMode'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['clickControl']" ref="clickControl" v-ripple class="tool-button" :class="buttonActiveClass('clickControl')" @click="buttonClick('clickControl')">
                    <q-icon name="la la-mouse" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['clickControl'] }}
                    </q-tooltip>
                </button>
                <button v-show="showToolButton['offlineMode']" ref="offlineMode" v-ripple class="tool-button" :class="buttonActiveClass('offlineMode')" @click="buttonClick('offlineMode')">
                    <q-icon name="la la-unlink" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
                        {{ rstore.readerActions['offlineMode'] }}
                    </q-tooltip>
                </button>
                <button ref="settings" v-ripple class="tool-button" :class="buttonActiveClass('settings')" @click="buttonClick('settings')">
                    <q-icon name="la la-cog" size="32px" />
                    <q-tooltip :delay="1500" anchor="bottom left" content-style="font-size: 80%">
                        {{ rstore.readerActions['settings'] }}
                    </q-tooltip>
                </button>
            </div>
        </div>

        <div class="col row relative-position main">
            <keep-alive>
                <component 
                    :is="activePage"
                    ref="page"
                    class="col"
                    @load-book="loadBook"
                    @load-file="loadFile"
                    @book-pos-changed="bookPosChanged"
                    @do-action="doAction"
                    @hide-tool-bar="hideToolBar"
                ></component>
            </keep-alive>

            <SetPositionPage v-if="setPositionActive" ref="setPositionPage" @set-position-toggle="setPositionToggle" @book-pos-changed="bookPosChanged"></SetPositionPage>
            <SearchPage 
                v-show="searchActive"
                ref="searchPage" 
                @do-action="doAction"
                @book-pos-changed="bookPosChanged"
                @start-text-search="startTextSearch"
                @stop-text-search="stopTextSearch"
            ></SearchPage>
            <CopyTextPage v-if="copyTextActive" ref="copyTextPage" @do-action="doAction"></CopyTextPage>
            <LibsPage v-show="hidden" ref="libsPage" @load-book="loadBook" @libs-close="libsClose" @do-action="doAction"></LibsPage>
            <RecentBooksPage v-show="recentBooksActive" ref="recentBooksPage" @load-book="loadBook" @recent-books-close="recentBooksClose" @update-count-changed="updateCountChanged"></RecentBooksPage>
            <SettingsPage v-show="settingsActive" ref="settingsPage" @do-action="doAction"></SettingsPage>
            <HelpPage v-if="helpActive" ref="helpPage" @do-action="doAction"></HelpPage>
            <ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
            <ContentsPage v-show="contentsActive" ref="contentsPage" :book-pos="bookPos" :is-visible="contentsActive" @do-action="doAction" @book-pos-changed="bookPosChanged"></ContentsPage>

            <ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
            <ReaderDialogs ref="dialogs" @donate-toggle="donateToggle" @version-history-toggle="versionHistoryToggle" @load-buffer-toggle="loadBufferToggle"></ReaderDialogs>
        </div>
    </div>
</template>

<script>
//-----------------------------------------------------------------------------
import vueComponent from '../vueComponent.js';

import _ from 'lodash';
import {Buffer} from 'safe-buffer';

import LoaderPage from './LoaderPage/LoaderPage.vue';
import TextPage from './TextPage/TextPage.vue';
import ProgressPage from './ProgressPage/ProgressPage.vue';

import SetPositionPage from './SetPositionPage/SetPositionPage.vue';
import SearchPage from './SearchPage/SearchPage.vue';
import CopyTextPage from './CopyTextPage/CopyTextPage.vue';
import LibsPage from './LibsPage/LibsPage.vue';
import RecentBooksPage from './RecentBooksPage/RecentBooksPage.vue';
import SettingsPage from './SettingsPage/SettingsPage.vue';
import HelpPage from './HelpPage/HelpPage.vue';
import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
import ContentsPage from './ContentsPage/ContentsPage.vue';

import ServerStorage from './ServerStorage/ServerStorage.vue';
import ReaderDialogs from './ReaderDialogs/ReaderDialogs.vue';

import bookManager from './share/bookManager';
import wallpaperStorage from './share/wallpaperStorage';
import coversStorage from './share/coversStorage';
import dynamicCss from '../../share/dynamicCss';

import rstore from '../../store/modules/reader';
import readerApi from '../../api/reader';
import miscApi from '../../api/misc';

import {versionHistory} from './versionHistory';
import * as utils from '../../share/utils';
import LockQueue from '../../share/LockQueue';

const componentOptions = {
    components: {
        LoaderPage,
        TextPage,
        ProgressPage,

        SetPositionPage,
        SearchPage,
        CopyTextPage,
        LibsPage,
        RecentBooksPage,
        SettingsPage,
        HelpPage,
        ClickMapPage,
        ContentsPage,

        ServerStorage,
        ReaderDialogs,
    },
    watch: {
        bookPos: function(newValue) {
            if (newValue !== undefined && this.activePage == 'TextPage') {
                const textPage = this.$refs.page;

                if (textPage.bookPos != newValue) {
                    textPage.bookPos = newValue;
                }

                if (!this.scrollingActive)
                    this.debouncedSetRecentBook(newValue);
                else
                    this.scrollingSetRecentBook(newValue);
            }
        },
        routeParamPos: function(newValue) {
            if (!this.paramPosIgnore && newValue !== undefined && newValue != this.bookPos) {
                this.bookPos = newValue;
            }
        },
        routeParamUrl: function(newValue) {
            if (newValue !== '' && newValue !== this.mostRecentBook().url) {
                this.loadBook({url: newValue, bookPos: this.routeParamPos});
            }
        },
        settings: function() {
            this.loadSettings();
            this.updateRoute();
        },
        loaderActive: function(newValue) {
            (async() => {
                const recent = this.mostRecentBook();
                if (!newValue && !this.loading && recent && !await bookManager.hasBookParsed(recent)) {
                    this.loadBook(recent);
                }
            })();
        },
        dualPageMode(newValue) {
            if (newValue)
                this.stopScrolling();
        },
    },
};

class Reader {
    _options = componentOptions;

    rstore = {};

    loaderActive = false;
    loadFileActive = false;
    loadBufferActive = false;
    fullScreenActive = false;
    setPositionActive = false;
    searchActive = false;
    copyTextActive = false;
    convOptionsActive = false;
    refreshActive = false;
    contentsActive = false;    
    libsActive = false;
    recentBooksActive = false;
    
    nightModeActive = false;
    clickControlActive = false;
    settingsActive = false;

    clickMapActive = false;
    helpActive = false;
    scrollingActive = false;
    progressActive = false;

    bookPos = null;
    allowUrlParamBookPos = false;
    showRefreshIcon = true;
    mostRecentBookReactive = null;
    showToolButton = {};
    toolBarHideOnScroll = false;
    toolBarMultiLine = false;

    actionList = [];
    actionCur = -1;
    hidden = false;

    whatsNewVisible = false;
    whatsNewContent = '';
    donationVisible = false;
    dualPageMode = false;

    bucEnabled = false;
    bucSetOnNew = false;
    needBookUpdateCount = 0;

    created() {
        this.rstore = rstore;
        this.loading = true;
        this.commit = this.$store.commit;
        this.reader = this.$store.state.reader;
        this.config = this.$store.state.config;

        this.lock = new LockQueue(100);

        this.$root.addEventHook('key', this.keyHook);

        this.lastActivePage = false;

        this.$watch(
            () => this.$route.path,
            (newValue) => {
                if (newValue == '/reader') {
                    this.updateRoute();
                }
            }
        );

        this.debouncedSetRecentBook = _.debounce(async(newValue) => {
            const recent = this.mostRecentBook();
            if (recent && (recent.bookPos != newValue || recent.bookPosSeen !== this.bookPosSeen)) {
                await bookManager.setRecentBook(Object.assign({}, recent, {bookPos: newValue, bookPosSeen: this.bookPosSeen}));

                if (this.actionCur < 0 || (this.actionCur >= 0 && this.actionList[this.actionCur] != newValue))
                    this.addAction(newValue);

                this.paramPosIgnore = true;
                this.updateRoute();
                await this.$nextTick();
                this.paramPosIgnore = false;
            }
        }, 250, {maxWait: 5000});

        this.scrollingSetRecentBook = _.debounce((newValue) => {
            this.debouncedSetRecentBook(newValue);
        }, 15000, {maxWait: 20000});

        this.debouncedHideToolBar = _.debounce((event) => {
            if (this.toolBarHideOnScroll && this.toolBarActive !== !!event.show) {
                this.commit('reader/setToolBarActive', !!event.show);
                this.$root.eventHook('resize');
            }
        }, 200);

        this.debouncedRecentBooksPageUpdate = _.debounce(async() => {
            if (this.recentBooksActive) {
                await this.$refs.recentBooksPage.updateTableData();
            }
        }, 100);

        this.recentItemKeys = [];
        this.debouncedSaveRecent = _.debounce(async() => {
            let timer = setTimeout(() => {
                if (!this.offlineModeActive)
                    this.$root.notify.error('Таймаут соединения');
            }, 10000);

            try {
                const itemKeys = this.recentItemKeys;
                this.recentItemKeys = [];
                //сохранение в удаленном хранилище
                await this.$refs.serverStorage.saveRecent(itemKeys);
            } catch (e) {
                if (!this.offlineModeActive)
                    this.$root.notify.error(e.message);
            } finally {
                clearTimeout(timer);
            }
        }, 500, {maxWait: 1000});

        document.addEventListener('fullscreenchange', () => {
            this.fullScreenActive = (document.fullscreenElement !== null);
        });

        this.loadSettings();
    }

    mounted() {
        (async() => {
            await wallpaperStorage.init();
            await coversStorage.init();
            
            await bookManager.init(this.settings);
            bookManager.addEventListener(this.bookManagerEvent);

            if (this.$root.getRootRoute() == '/reader') {
                if (this.routeParamUrl) {
                    await this.loadBook({url: this.routeParamUrl, bookPos: this.routeParamPos, force: this.routeParamRefresh});
                } else {
                    this.loaderActive = true;
                }
            }

            await this.$refs.serverStorage.init();
            this.checkSetStorageAccessKey();
            this.checkActivateDonateHelpPage();
            this.loading = false;

            //проверим состояние Settings, и обновим, если надо
            const newSettings = rstore.addDefaultsToSettings(this.settings);
            if (newSettings) {
                this.commit('reader/setSettings', newSettings);
            }

            this.updateRoute();

            await this.$refs.dialogs.init();

            this.$refs.recentBooksPage.init();
        })();

        //проверки обновлений читалки
        (async() => {
            this.isFirstNeedUpdateNotify = true;
            //вечный цикл, запрашиваем периодически конфиг для проверки выхода новой версии читалки
            while (1) {// eslint-disable-line no-constant-condition
                await this.checkNewVersionAvailable();
                await utils.sleep(60*60*1000); //каждый час
            }
            //дальше хода нет
        })();

        //проверки обновлений книг
        (async() => {
            await utils.sleep(15*1000); //подождем неск. секунд перед первым запросом
            //вечный цикл, запрашиваем периодически обновления
            while (1) {// eslint-disable-line no-constant-condition
                await this.checkBuc();
                await utils.sleep(70*60*1000); //каждые 70 минут
            }
            //дальше хода нет
        })();
    }

    loadSettings() {
        const settings = this.settings;
        this.allowUrlParamBookPos = settings.allowUrlParamBookPos;
        this.copyFullText = settings.copyFullText;
        this.showClickMapPage = settings.showClickMapPage;
        this.nightModeActive = settings.nightMode;
        this.clickControlActive = settings.clickControl;
        this.blinkCachedLoad = settings.blinkCachedLoad;
        this.showToolButton = settings.showToolButton;
        this.toolBarHideOnScroll = settings.toolBarHideOnScroll;
        this.toolBarMultiLine = settings.toolBarMultiLine;
        this.enableSitesFilter = settings.enableSitesFilter;
        this.showNeedUpdateNotify = settings.showNeedUpdateNotify;
        this.splitToPara = settings.splitToPara;
        this.djvuQuality = settings.djvuQuality;
        this.pdfAsText = settings.pdfAsText;
        this.pdfQuality = settings.pdfQuality;
        this.dualPageMode = settings.dualPageMode;
        this.userWallpapers = settings.userWallpapers;
        this.bucEnabled = settings.bucEnabled;
        this.bucSizeDiff = settings.bucSizeDiff;
        this.bucSetOnNew = settings.bucSetOnNew;
        this.bucCancelEnabled = settings.bucCancelEnabled;
        this.bucCancelDays = settings.bucCancelDays;

        this.readerActionByKeyCode = utils.userHotKeysObjectSwap(settings.userHotKeys);
        this.$root.readerActionByKeyEvent = (event) => {
            return this.readerActionByKeyCode[utils.keyEventToCode(event)];
        }

        this.loadWallpapers();//no await
    }

    showHelpOnErrorIfNeeded(errorMessage) {
        //небольшая эвристика
        let i = errorMessage.indexOf('http://');
        if (i < 0)
            i = errorMessage.indexOf('https://');

        errorMessage = errorMessage.substring(i + 7);
        const perCount = errorMessage.split('%').length - 1;

        if (perCount > errorMessage.length/3.2) {
            this.$refs.dialogs.showUrlHelp();
            return true;
        }

        return false;
    }

    //wallpaper css
    async loadWallpapers() {
        if (!_.isEqual(this.userWallpapers, this.prevUserWallpapers)) {//оптимизация
            this.prevUserWallpapers = _.cloneDeep(this.userWallpapers);

            let newCss = '';
            let updated = false;
            const wallpaperExists = new Set();
            for (const wp of this.userWallpapers) {
                wallpaperExists.add(wp.cssClass);

                let data = await wallpaperStorage.getData(wp.cssClass);
                if (!data) {
                    //здесь будем восстанавливать данные с сервера
                    const url = `disk://${wp.cssClass.replace('user-paper', '')}`;
                    try {
                        data = await readerApi.getUploadedFileBuf(url);
                        await wallpaperStorage.setData(wp.cssClass, data);
                        updated = true;
                    } catch (e) {
                        console.error(e);
                    }
                }

                if (data) {
                    newCss += `.${wp.cssClass} {background: url(${data}) center; background-size: 100% 100%;}`;                
                }
            }

            //почистим wallpaperStorage
            for (const key of await wallpaperStorage.getKeys()) {
                if (!wallpaperExists.has(key)) {
                    await wallpaperStorage.removeData(key);
                }
            }

            //обновим settings, если загружали обои из /upload/
            if (updated) {
                this.commit('reader/setSettings', {});
            }

            dynamicCss.replace('wallpapers', newCss);
        }
    }

    async checkNewVersionAvailable() {
        if (!this.checkingNewVersion && this.showNeedUpdateNotify) {
            this.checkingNewVersion = true;
            try {
                await utils.sleep(15*1000); //подождем 15 секунд, чтобы прогрузился ServiceWorker при выходе новой версии
                const config = await miscApi.loadConfig();
                this.commit('config/setConfig', config);

                let againMes = '';
                if (this.isFirstNeedUpdateNotify) {
                    againMes = ' еще один раз';
                }

                if (this.version != this.clientVersion)
                    this.$root.notify.info(`Вышла новая версия (v${this.version}) читалки.<br>Пожалуйста, обновите страницу${againMes}.`, 'Обновление');
            } catch(e) {
                console.error(e);
            } finally {
                this.checkingNewVersion = false;
            }        
            this.isFirstNeedUpdateNotify = false;
        }
    }

    async checkBuc() {
        if (!this.bothBucEnabled)
            return;

        try {
            const sorted = bookManager.getSortedRecent();

            //выберем все кандидиаты на обновление
            const updateUrls = new Set();
            for (const book of sorted) {
                if (!book.deleted && book.checkBuc && book.url && book.url.indexOf('disk://') !== 0)
                    updateUrls.add(book.url);
            }

            //теперь по кусочкам запросим сервер
            const arr = Array.from(updateUrls);
            const bucSize = {};
            const chunkSize = 100;
            for (let i = 0; i < arr.length; i += chunkSize) {
                const chunk = arr.slice(i, i + chunkSize);

                const data = await readerApi.checkBuc(chunk);

                for (const item of data) {
                    bucSize[item.id] = item.size;
                }

                await utils.sleep(1000);//чтобы не ддосить сервер
            }

            const checkSetTime = {};
            //проставим новые размеры у книг
            for (const book of sorted) {
                if (book.deleted)
                    continue;
                
                //размер 0 считаем отсутствующим
                if (book.url && bucSize[book.url] && bucSize[book.url] !== book.bucSize) {
                    book.bucSize = bucSize[book.url];
                    await bookManager.recentSetItem(book);
                }

                //подготовка к следующему шагу, ищем книгу по url с максимальной датой установки checkBucTime/loadTime
                //от этой даты будем потом отсчитывать bucCancelDays
                if (updateUrls.has(book.url)) {
                    let rec = checkSetTime[book.url] || {time: 0, loadTime: 0};

                    const time = (book.checkBucTime ? book.checkBucTime : (rec.loadTime || 0));
                    if (time > rec.time || (time == rec.time && (book.loadTime > rec.loadTime)))
                        rec = {time, loadTime: book.loadTime, key: book.key};

                    checkSetTime[book.url] = rec;
                }
            }

            //bucCancelEnabled и bucCancelDays
            //снимем флаг checkBuc у необновлявшихся bucCancelDays
            if (this.bucCancelEnabled) {
                for (const rec of Object.values(checkSetTime)) {
                    if (rec.time && Date.now() - rec.time > this.bucCancelDays*24*3600*1000) {
                        const book = await bookManager.getRecentBook({key: rec.key});
                        const needBookUpdate = 
                            book.checkBuc
                            && book.bucSize
                            && utils.hasProp(book, 'downloadSize')
                            && book.bucSize !== book.downloadSize
                            && (book.bucSize - book.downloadSize >= this.bucSizeDiff)
                        ;

                        if (book && !needBookUpdate) {
                            await bookManager.setCheckBuc(book, undefined);//!!!
                        }
                    }
                }
            }

            await this.$refs.recentBooksPage.updateTableData();
        } catch (e) {
            console.error(e);
        }
    }

    updateCountChanged(event) {
        this.needBookUpdateCount = event.needBookUpdateCount;
    }

    checkSetStorageAccessKey() {
        const q = this.$route.query;

        if (q['setStorageAccessKey']) {
            this.$router.replace(`/reader`);
            this.settingsToggle();
            this.$nextTick(() => {
                this.$refs.settingsPage.enterServerStorageKey(
                    Buffer.from(utils.fromBase58(q['setStorageAccessKey'])).toString()
                );
            });
        }
    }

    checkActivateDonateHelpPage() {
        const q = this.$route.query;

        if (q['donate']) {
            this.$router.replace(`/reader`);
            this.helpToggle();
            this.$nextTick(() => {
                this.$refs.helpPage.activateDonateHelpPage();
            });
        }
    }

    checkBookPosPercent() {
        const q = this.$route.query;
        if (q['__pp']) {
            let pp = q['__pp'];
            if (pp) {
                pp = parseFloat(pp) || 0;
                const recent = this.mostRecentBook();
                (async() => {
                    await utils.sleep(100);
                    this.bookPos = Math.floor(recent.textLength*pp/100);
                })();
            }
        }
    }

    get routeParamPos() {
        let result = undefined;
        const q = this.$route.query;
        if (q['__p']) {
            result = q['__p'];
            if (Array.isArray(result))
                result = result[0];
        }
        
        return (result ? parseInt(result, 10) || 0 : result);
    }

    updateRoute(isNewRoute) {
        if (this.loading)
            return;
        const recent = this.mostRecentBook();
        const pos = (recent && recent.bookPos && this.allowUrlParamBookPos ? `__p=${recent.bookPos}&` : '');
        const url = (recent ? `url=${encodeURIComponent(recent.url)}` : '');
        if (isNewRoute)
            this.$router.push(`/reader?${pos}${url}`).catch(() => {});
        else
            this.$router.replace(`/reader?${pos}${url}`).catch(() => {});

    }

    get mode() {
        return this.$store.state.config.mode;
    }

    get version() {
        return this.$store.state.config.version;
    }

    get clientVersion() {
        return versionHistory[0].version;
    }

    get bothBucEnabled() {
        return this.$store.state.config.bucEnabled && this.bucEnabled;
    }

    get routeParamUrl() {
        let result = '';
        const path = this.$route.fullPath;
        const i = path.indexOf('url=');
        if (i >= 0) {
            result = path.substr(i + 4);
        }
        
        return decodeURIComponent(result);
    }

    get routeParamRefresh() {
        const q = this.$route.query;
        return !!q['__refresh'];
    }

    bookPosChanged(event) {
        if (event.bookPosSeen !== undefined)
            this.bookPosSeen = event.bookPosSeen;
        this.bookPos = event.bookPos;
    }

    async bookManagerEvent(eventName, value) {
        if (eventName == 'set-recent' || eventName == 'recent-deleted') {
            const oldBook = (this.textPage ? this.textPage.lastBook : null);
            const oldPos = (this.textPage ? this.textPage.bookPos : null);
            const newBook = bookManager.mostRecentBook();

            if (!(oldBook && newBook && oldBook.key == newBook.key)) {
                this.mostRecentBook();
            }

            if (oldBook && newBook) {
                if (oldBook.key != newBook.key || oldBook.path != newBook.path) {
                    this.loadingBook = true;
                    try {
                        await this.loadBook(newBook);
                    } finally {
                        this.loadingBook = false;
                    }
                } else if (oldPos != newBook.bookPos) {
                    while (this.loadingBook) await utils.sleep(100);
                    this.bookPosChanged({bookPos: newBook.bookPos});
                }
            }
        }

        if (eventName == 'recent-changed') {
            this.debouncedRecentBooksPageUpdate();

            //сохранение в serverStorage
            if (value && this.recentItemKeys.indexOf(value) < 0) {
                this.recentItemKeys.push(value);
                this.debouncedSaveRecent();
            }
        }
    }

    get toolBarActive() {
        return this.reader.toolBarActive;
    }

    get offlineModeActive() {
        return this.reader.offlineModeActive;
    }

    mostRecentBook() {
        const result = bookManager.mostRecentBook();
        this.mostRecentBookReactive = result;
        return result;
    }

    get settings() {
        return this.$store.state.reader.settings;
    }

    addAction(pos) {
        let a = this.actionList;
        if (!a.length || a[a.length - 1] != pos) {
            a.push(pos);
            if (a.length > 20)
                a.shift();
            this.actionCur = a.length - 1;
        }
    }

    toolBarToggle() {
        this.commit('reader/setToolBarActive', !this.toolBarActive);
        this.$root.eventHook('resize');
    }

    hideToolBar(event) {
        this.debouncedHideToolBar(event);
    }

    fullScreenToggle() {
        if (!this.$q.fullscreen.isActive) {
            this.$q.fullscreen.request();
        } else {
            this.$q.fullscreen.exit();
        }
    }

    closeAllWindows() {
        this.setPositionActive = false;
        this.copyTextActive = false;
        this.recentBooksActive = false;
        this.settingsActive = false;
        this.stopScrolling();
        this.stopSearch();
        this.helpActive = false;
        this.contentsActive = false;
    }

    loaderToggle() {
        this.loaderActive = !this.loaderActive;
        if (this.loaderActive) {
            this.closeAllWindows();
        }
    }

    loadFileToggle() {
        if (!this.loaderActive)
            this.loaderToggle();
        this.$nextTick(() => {
            const page = this.$refs.page;
            if (this.activePage == 'LoaderPage' && page.loadFileClick) {
                page.loadFileClick();
            }
        });        
    }

    loadBufferToggle() {
        if (!this.loaderActive)
            this.loaderToggle();
        this.$nextTick(() => {
            const page = this.$refs.page;
            if (this.activePage == 'LoaderPage' && page.showPasteText) {
                page.showPasteText();
            }
        });        
    }

    setPositionToggle() {
        this.setPositionActive = !this.setPositionActive;
        const page = this.$refs.page;
        if (this.setPositionActive && this.activePage == 'TextPage' && page.parsed) {
            this.closeAllWindows();
            this.setPositionActive = true;

            this.$nextTick(() => {
                const recent = this.mostRecentBook();
                this.$refs.setPositionPage.init(recent.bookPos, recent.textLength - 1);
            });
        } else {
            this.setPositionActive = false;
        }
    }

    stopScrolling() {
        if (this.scrollingActive)
            this.scrollingToggle();
    }

    scrollingToggle() {
        this.scrollingActive = !this.scrollingActive;
        if (this.activePage == 'TextPage') {
            const page = this.$refs.page;
            if (this.scrollingActive) {
                page.startTextScrolling();
            } else {
                page.stopTextScrolling();
            }
        }

        if (!this.scrollingActive) {
            this.scrollingSetRecentBook.flush();
        }
    }

    stopSearch() {
        if (this.searchActive)
            this.searchToggle();
    }

    startTextSearch(opts) {
        if (this.activePage == 'TextPage')
            this.$refs.page.startSearch(opts.needle);
    }

    stopTextSearch() {
        if (this.activePage == 'TextPage')
            this.$refs.page.stopSearch();
    }

    searchToggle() {
        this.searchActive = !this.searchActive;
        const page = this.$refs.page;
        if (this.searchActive && this.activePage == 'TextPage' && page.parsed) {
            this.closeAllWindows();
            this.searchActive = true;

            this.$nextTick(() => {
                this.$refs.searchPage.init(page.parsed);
            });
        } else {
            this.stopTextSearch();
            this.searchActive = false;
        }
    }

    copyTextToggle() {
        this.copyTextActive = !this.copyTextActive;
        const page = this.$refs.page;
        if (this.copyTextActive && this.activePage == 'TextPage' && page.parsed) {
            this.closeAllWindows();
            this.copyTextActive = true;

            this.$nextTick(() => {
                this.$refs.copyTextPage.init(this.mostRecentBook().bookPos, page.parsed, this.copyFullText);
            });
        } else {
            this.copyTextActive = false;
        }
    }

    recentBooksClose() {
        this.recentBooksActive = false;
    }

    recentBooksToggle() {
        this.recentBooksActive = !this.recentBooksActive;
        if (this.recentBooksActive) {
            this.closeAllWindows();
            this.$refs.recentBooksPage.init();
            this.recentBooksActive = true;
        } else {
            this.recentBooksActive = false;
        }
    }

    contentsPageToggle() {
        this.contentsActive = !this.contentsActive;
        const page = this.$refs.page;
        if (this.contentsActive && this.activePage == 'TextPage' && page.parsed) {
            this.closeAllWindows();
            this.contentsActive = true;

            this.$nextTick(() => {
                this.$refs.contentsPage.init(this.mostRecentBook(), page.parsed);
            });
        } else {
            this.contentsActive = false;
        }
    }

    libsClose() {
        if (this.libsActive)
            this.libsToogle();
    }

    libsToogle() {
        this.libsActive = !this.libsActive;
        if (this.libsActive) {
            this.$refs.libsPage.init();//no await
        } else {
            this.$refs.libsPage.done();
        }
    }

    nightModeToggle() {
        if (!this.nightModeActive && !utils.hasProp(this.settings.nightColorSets, 'textColor')) {
            this.$root.notify.warning(`Ночной режим активирован впервые. Цвета заданы по умолчанию.`);
        }

        this.commit('reader/nightModeToggle');
    }

    clickControlToggle() {
        this.commit('reader/setSettings', {clickControl: !this.clickControlActive});
    }

    offlineModeToggle() {
        this.commit('reader/setOfflineModeActive', !this.offlineModeActive);
    }

    settingsToggle() {
        this.settingsActive = !this.settingsActive;
        if (this.settingsActive) {
            this.closeAllWindows();
            this.settingsActive = true;

            this.$nextTick(() => {
                this.$refs.settingsPage.init();
            });
        } else {
            this.settingsActive = false;
        }
    }

    convOptionsToggle() {
        this.settingsToggle();
        if (this.settingsActive)
            this.$refs.settingsPage.selectedTab = 'convert';
    }

    helpToggle() {
        this.helpActive = !this.helpActive;
        if (this.helpActive) {
            this.closeAllWindows();
            this.helpActive = true;
        }
    }

    donateToggle() {
        this.helpToggle();
        if (this.helpActive) {
            this.$nextTick(() => {
                this.$refs.helpPage.activateDonateHelpPage();
            });
        }
    }

    versionHistoryToggle() {
        this.helpToggle();
        if (this.helpActive) {
            this.$nextTick(() => {
                this.$refs.helpPage.activateVersionHistoryHelpPage();
            });
        }
    }

    refreshBook() {
        const mrb = this.mostRecentBook();
        this.loadBook(Object.assign({}, mrb, {force: true}));
    }

    undoAction() {
        if (this.actionCur > 0) {
            this.actionCur--;
            this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
        }
    }

    redoAction() {
        if (this.actionCur < this.actionList.length - 1) {
            this.actionCur++;
            this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
        }
    }

    buttonClick(action) {
        const activeClass = this.buttonActiveClass(action);

        this.$refs[action].blur();

        if (activeClass['tool-button-disabled'])
            return;
        
        this.doAction({action});
    }

    buttonActiveClass(action) {
        const classActive = { 'tool-button-active': true, 'tool-button-active:hover': true };
        const classDisabled = { 'tool-button-disabled': true, 'tool-button-disabled:hover': true };
        let classResult = {};

        switch (action) {
            case 'loader':
            case 'loadFile':
            case 'loadBuffer':
            case 'help':
            case 'fullScreen':
            case 'setPosition':
            case 'search':
            case 'copyText':
            case 'convOptions':
            case 'refresh':
            case 'contents':
            case 'libs':
            case 'recentBooks':
            case 'nightMode':
            case 'clickControl':
            case 'offlineMode':
            case 'settings':
                if (this.progressActive) {
                    classResult = classDisabled;
                } else if (this[`${action}Active`]) {
                    classResult = classActive;
                }
                break;
            case 'scrolling':
                if (this.progressActive || this.dualPageMode) {
                    classResult = classDisabled;
                } else if (this[`${action}Active`]) {
                    classResult = classActive;
                }
                break;
            case 'undoAction':
                if (this.actionCur <= 0)
                    classResult = classDisabled;
                break;
            case 'redoAction':
                if (this.actionCur == this.actionList.length - 1)
                    classResult = classDisabled;
                break;
        }

        if (this.activePage == 'LoaderPage' || !this.mostRecentBookReactive) {
            switch (action) {
                case 'undoAction':
                case 'redoAction':
                case 'setPosition':
                case 'scrolling':
                case 'search':
                case 'copyText':
                case 'contents':
                    classResult = classDisabled;
                    break;
                case 'refresh':
                    if (!this.mostRecentBookReactive)
                        classResult = classDisabled;
                    break;
            }
        }

        return classResult;
    }

    async activateClickMapPage() {
        if (this.clickControlActive && this.showClickMapPage && !this.clickMapActive) {
            this.clickMapActive = true;
            await this.$refs.clickMapPage.slowDisappear();
            this.clickMapActive = false;
        }
    }

    get activePage() {
        let result = undefined;

        if (this.progressActive)
            result = 'ProgressPage';
        else if (this.loaderActive)
            result = 'LoaderPage';
        else if (this.mostRecentBookReactive)
            result = 'TextPage';

        if (!result && !this.loading) {
            this.loaderActive = true;
            result = 'LoaderPage';
        }

        if (result != 'TextPage') {
            this.$root.setAppTitle();
        }

        // на LoaderPage всегда показываем toolBar
        if (result == 'LoaderPage' && !this.toolBarActive) {
            this.toolBarToggle();
        }

        if (this.lastActivePage != result && result == 'TextPage') {
            //акивируем страницу с текстом
            this.$nextTick(async() => {
                const last = this.mostRecentBookReactive;
                const isParsed = await bookManager.hasBookParsed(last);

                if (!isParsed) {
                    this.$root.setAppTitle();
                    return;
                }

                this.updateRoute();
                const textPage = this.$refs.page;
                if (textPage.showBook) {
                    this.textPage = textPage;
                    textPage.lastBook = last;
                    textPage.bookPos = (last.bookPos !== undefined ? last.bookPos : 0);

                    textPage.showBook();
                }
            });
        }

        this.lastActivePage = result;
        return result;
    }

    async _loadBook(opts) {
        if (!opts || !opts.url) {
            this.mostRecentBook();
            return;
        }

        this.closeAllWindows();

        let url = encodeURI(decodeURI(opts.url));

        if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0) &&
            (url.indexOf('disk://') != 0))
            url = 'http://' + url;

        // уже просматривается сейчас
        const lastBook = (this.textPage ? this.textPage.lastBook : null);
        if (!opts.force && lastBook && lastBook.url == url && 
                (!opts.path || opts.path == lastBook.path) && 
                await bookManager.hasBookParsed(lastBook)) {
            this.loaderActive = false;
            return;
        }

        this.progressActive = true;

        await this.$nextTick();

        const progress = this.$refs.page;

        this.actionList = [];
        this.actionCur = -1;

        try {
            progress.show();
            progress.setState({state: 'parse'});

            // есть ли среди загруженных
            let wasOpened = bookManager.findRecentByUrlAndPath(url, opts.path);
            wasOpened = (wasOpened ? _.cloneDeep(wasOpened) : {});

            wasOpened = Object.assign(wasOpened, {
                url: (opts.url !== undefined ? opts.url : wasOpened.url),
                path: (opts.path !== undefined ? opts.path : wasOpened.path),
                bookPos: (opts.bookPos !== undefined ? opts.bookPos : wasOpened.bookPos),
                bookPosSeen: (opts.bookPos !== undefined ? opts.bookPos : wasOpened.bookPosSeen),
                uploadFileName: (opts.uploadFileName ? opts.uploadFileName : wasOpened.uploadFileName),
            });

            let book = null;

            if (!opts.force) {
                // пытаемся загрузить и распарсить книгу в менеджере из локального кэша
                const bookParsed = await bookManager.getBook(wasOpened, (prog) => {
                    progress.setState({progress: prog});
                });

                // если есть в локальном кэше
                if (bookParsed) {
                    await bookManager.setRecentBook(Object.assign(wasOpened, bookParsed));
                    this.mostRecentBook();
                    this.addAction(wasOpened.bookPos);
                    this.loaderActive = false;
                    progress.hide(); this.progressActive = false;
                    this.blinkCachedLoadMessage();

                    this.checkBookPosPercent();
                    this.activateClickMapPage();//no await
                    this.$refs.recentBooksPage.updateTableData();//no await
                    return;
                }

                // иначе идем на сервер
                // пытаемся загрузить готовый файл с сервера
                if (wasOpened.path) {
                    progress.setState({totalSteps: 5});
                    try {
                        const resp = await readerApi.loadCachedBook(wasOpened.path, (state) => {
                            progress.setState(state);
                        });
                        book = Object.assign({}, wasOpened, {data: resp.data});
                    } catch (e) {
                        this.$root.notify.error('Конвертированный файл не найден на сервере.<br>Пробуем загрузить оригинал.', 'Ошибка загрузки');
                    }
                }
            }

            progress.setState({totalSteps: 5});
            // не удалось, скачиваем книгу полностью с конвертацией
            let loadCached = true;
            if (!book) {
                book = await readerApi.loadBook({
                        url,
                        uploadFileName: wasOpened.uploadFileName,
                        enableSitesFilter: this.enableSitesFilter,
                        skipHtmlCheck: (this.splitToPara ? true : false),
                        isText: (this.splitToPara ? true : false),
                        djvuQuality: this.djvuQuality,
                        pdfAsText: this.pdfAsText,
                        pdfQuality: this.pdfQuality,
                    },
                    (state) => {
                        progress.setState(state);
                    }
                );
                loadCached = false;
            }

            // добавляем в bookManager
            progress.setState({state: 'parse', step: 5});

            const addedBook = await bookManager.addBook(book, (prog) => {
                progress.setState({progress: prog});
            });

            // sameBookKey
            if (url.indexOf('disk://') == 0) {
                //ищем такой файл в загруженных
                let found = bookManager.findRecentBySameBookKey(wasOpened.uploadFileName);
                found = (found ? _.cloneDeep(found) : found);

                if (found) {
                    if (wasOpened.sameBookKey != found.sameBookKey) {
                        //спрашиваем, надо ли объединить файлы
                        const askResult = bookManager.keysEqual(found.path, addedBook.path) || 
                            await this.$root.stdDialog.askYesNo(`
    Файл с именем "${wasOpened.uploadFileName}" уже есть в загруженных.
    <br>Объединить позицию?`, 'Найдена похожая книга');
                        if (askResult) {
                            wasOpened.bookPos = found.bookPos;
                            wasOpened.bookPosSeen = found.bookPosSeen;
                            wasOpened.sameBookKey = found.sameBookKey;
                        }
                    }
                } else {
                    wasOpened.sameBookKey = wasOpened.uploadFileName;
                }
            } else {
                wasOpened.sameBookKey = addedBook.url;
            }

            if (!bookManager.keysEqual(wasOpened.path, addedBook.path))
                delete wasOpened.loadTime;

            // добавляем в историю
            const recentBook = await bookManager.setRecentBook(Object.assign(wasOpened, addedBook));
            if (this.bucSetOnNew) {
                await bookManager.setCheckBuc(recentBook, true);
            }

            this.mostRecentBook();
            this.addAction(recentBook.bookPos);
            this.updateRoute(true);

            this.loaderActive = false;
            progress.hide(); this.progressActive = false;
            if (loadCached) {
                this.blinkCachedLoadMessage();
            } else
                this.stopBlink = true;

            this.checkBookPosPercent();
            this.activateClickMapPage();//no await
            this.$refs.recentBooksPage.updateTableData();//no await
        } catch (e) {
            progress.hide(); this.progressActive = false;
            this.loaderActive = true;
            if (!this.showHelpOnErrorIfNeeded(url)) {
                this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
            }
        } finally {
            this.checkNewVersionAvailable();
        }
    }

    async loadBook(opts) {
        await this.lock.get();
        try {
            await this._loadBook(opts);
        } finally {
            this.lock.ret();
        }
    }

    async _loadFile(opts) {
        this.progressActive = true;

        await this.$nextTick();

        const progress = this.$refs.page;
        try {
            progress.show();
            progress.setState({state: 'upload'});

            const url = await readerApi.uploadFile(opts.file, this.config.maxUploadFileSize, (state) => {
                progress.setState(state);
            });

            progress.hide(); this.progressActive = false;

            await this._loadBook({url, uploadFileName: opts.file.name, force: true});
        } catch (e) {
            progress.hide(); this.progressActive = false;
            this.loaderActive = true;
            this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
        }
    }

    async loadFile(opts) {
        await this.lock.get();
        try {
            await this._loadFile(opts);
        } finally {
            this.lock.ret();
        }
    }

    blinkCachedLoadMessage() {
        if (!this.blinkCachedLoad)
            return;

        this.blinkCount = 30;
        if (!this.inBlink) {
            this.inBlink = true;
            this.stopBlink = false;
            this.$nextTick(async() => {
                let page = this.$refs.page;
                while (this.blinkCount) {
                    this.showRefreshIcon = !this.showRefreshIcon;
                    if (page && page.blinkCachedLoadMessage)
                        page.blinkCachedLoadMessage(this.showRefreshIcon);
                    await utils.sleep(500);
                    if (this.stopBlink)
                        break;
                    this.blinkCount--;
                    page = this.$refs.page;
                }
                this.showRefreshIcon = true;
                this.inBlink = false;
                if (page && page.blinkCachedLoadMessage)
                    page.blinkCachedLoadMessage('finish');
            });
        }
    }

    doAction(opts) {
        let result = true;
        let {action = '', event = false} = opts;

        switch (action) {
            case 'loader':
                this.loaderToggle();
                break;
            case 'loadFile':
                this.loadFileToggle();
                break;
            case 'loadBuffer':
                this.loadBufferToggle();
                break;
            case 'help':
                this.helpToggle();
                break;
            case 'undoAction':
                this.undoAction();
                break;
            case 'redoAction':
                this.redoAction();
                break;
            case 'fullScreen':
                this.fullScreenToggle();
                break;
            case 'scrolling':
                this.scrollingToggle();
                break;
            case 'stopScrolling':
                this.stopScrolling();
                break;
            case 'setPosition':
                this.setPositionToggle();
                break;
            case 'search':
                this.searchToggle();
                break;
            case 'copyText':
                this.copyTextToggle();
                break;
            case 'convOptions':
                this.convOptionsToggle();
                break;
            case 'refresh':
                this.refreshBook();
                break;
            case 'contents':
                this.contentsPageToggle();
                break;
            case 'libs':
                this.libsToogle();
                break;
            case 'recentBooks':
                this.recentBooksToggle();
                break;
            case 'nightMode':
                this.nightModeToggle();
                break;
            case 'clickControl':
                this.clickControlToggle();
                break;
            case 'offlineMode':
                this.offlineModeToggle();
                break;
            case 'settings':
                this.settingsToggle();
                break;
            case 'switchToolbar':
                this.toolBarToggle();
                break;
            case 'donate':
                this.donateToggle();
                break;
            default:
                result = false;
                break;
        }

        if (!result && this.activePage == 'TextPage' && this.$refs.page) {
            result = true;
            const textPage = this.$refs.page;

            switch (action) {
                case 'bookBegin':
                    textPage.doHome();
                    break;
                case 'bookEnd':
                    textPage.doEnd();
                    break;
                case 'pageBack':
                    textPage.doPageUp();
                    break;
                case 'pageForward':
                    textPage.doPageDown();
                    break;
                case 'lineBack':
                    textPage.doUp();
                    break;
                case 'lineForward':
                    textPage.doDown();
                    break;
                case 'incFontSize':
                    textPage.doFontSizeInc();
                    break;
                case 'decFontSize':
                    textPage.doFontSizeDec();
                    break;
                case 'scrollingSpeedUp':
                    textPage.doScrollingSpeedUp();
                    break;
                case 'scrollingSpeedDown':
                    textPage.doScrollingSpeedDown();
                    break;
                default:
                    result = false;
                    break;
            }
        }

        if (result && event) {
            event.preventDefault();
            event.stopPropagation();
        }

        return result;
    }

    keyHook(event) {
        let result = false;
        if (this.$root.getRootRoute() == '/reader') {
            if (this.$root.stdDialog.active)
                return result;

            if (!result)
                result = this.$refs.dialogs.keyHook(event);

            if (!result && this.helpActive)
                result = this.$refs.helpPage.keyHook(event);

            if (!result && this.settingsActive)
                result = this.$refs.settingsPage.keyHook(event);

            if (!result && this.recentBooksActive)
                result = this.$refs.recentBooksPage.keyHook(event);

            if (!result && this.setPositionActive)
                result = this.$refs.setPositionPage.keyHook(event);

            if (!result && this.searchActive)
                result = this.$refs.searchPage.keyHook(event);

            if (!result && this.copyTextActive)
                result = this.$refs.copyTextPage.keyHook(event);

            if (!result && this.contentsActive)
                result = this.$refs.contentsPage.keyHook(event);

            if (!result && this.$refs.page && this.$refs.page.keyHook)
                result = this.$refs.page.keyHook(event);

            if (!result && event.type == 'keydown') {
                const action = this.$root.readerActionByKeyEvent(event);

                /*if (action == 'loader') {
                    result = this.doAction({action, event});
                }

                if (!result && this.activePage == 'TextPage') {
                    result = this.doAction({action, event});
                }*/
                result = this.doAction({action, event});
            }
        }
        return result;
    }
}

export default vueComponent(Reader);
//-----------------------------------------------------------------------------
</script>

<style scoped>
.header {
    padding: 5px 5px 0px 5px;
    background-color: #1B695F;
    color: #000;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-color: #c4aa60 #e4e4e4;
}

.header::-webkit-scrollbar {
    height: 5px;
}
 
.header::-webkit-scrollbar-track {
    background-color: #1B695F;
    border-radius: 1px;
}
 
.header::-webkit-scrollbar-thumb {
    background-color: #c4aa60;
    border-radius: 1px;
    border: 1px solid #1B695F;
}

.main {
    background-color: var(--bg-loader-color);
    color: var(--text-app-color);
}

.tool-button {
    margin: 0px 2px 7px 2px;
    padding: 0;
    color: var(--text-tb-normal);
    background-color: var(--bg-tb-normal);
    min-height: 38px;
    min-width: 38px;
    height: 38px;
    width: 38px;
    border: 0;
    border-radius: 6px;
    box-shadow: 3px 3px 5px black;
    outline: 0;
}

.tool-button:hover {
    background-color: var(--bg-tb-hover);
    cursor: pointer;
}

.tool-button-active {
    box-shadow: 0 0 0;
    color: var(--text-tb-active);
    background-color: var(--bg-tb-active);
    position: relative;
    top: 1px;
    left: 1px;
}

.tool-button-active:hover {
    background-color: var(--bg-tb-active-hover);
    cursor: pointer;
}

.tool-button-disabled {
    color: var(--text-tb-disabled);
    background-color: var(--bg-tb-disabled);
    cursor: default;
}

.tool-button-disabled:hover {
    color: var(--text-tb-disabled);
    background-color: var(--bg-tb-disabled);
    cursor: default;
}

.space {
    width: 10px;
    display: inline-block;
}

.clear {
    color: rgba(0,0,0,0);
}

.need-book-update-count {
    position: relative;
    padding: 2px 6px 2px 6px;
    left: 27px;
    top: 22px;
    background-color: blue;
    border-radius: 10px;
    color: white;
    z-index: 10;
    font-size: 80%;
}
</style>
