Sfoglia il codice sorgente

Работа над приложением

Book Pauk 2 anni fa
parent
commit
adb1d55141

+ 15 - 1
client/components/Api/Api.vue

@@ -24,6 +24,20 @@ class Api {
     }
 
     mounted() {
+        this.updateConfig();//no await
+    }
+
+    async updateConfig() {
+        try {
+            const config = await this.getConfig();
+            this.commit('setConfig', config);
+        } catch (e) {
+            this.$root.stdDialog.alert(e.message, 'Ошибка');
+        }
+    }
+
+    get config() {
+        return this.$store.state.config;
     }
 
     async request(params) {
@@ -40,7 +54,7 @@ class Api {
         return response;
     }
 
-    async config() {
+    async getConfig() {
         const response = await this.request({action: 'get-config'});
 
         if (response.error) {

+ 0 - 11
client/components/App.vue

@@ -91,17 +91,6 @@ class App {
         this.$root.stdDialog = this.$refs.stdDialog;
 
         this.setAppTitle();
-
-        (async() => {
-            try {
-                const api = this.$root.api;
-                const config = await api.config();
-                this.commit('setConfig', config);
-            } catch (e) {
-                this.$root.stdDialog.alert(e.message, 'Ошибка');
-            }
-        })();
-
     }
 
     get config() {

+ 87 - 13
client/components/Search/Search.vue

@@ -1,7 +1,7 @@
 <template>
     <div class="root column fit">
-        <div class="tool-panel bg-green-11">
-            <div class="header q-mx-md q-mt-xs row justify-between items-center">
+        <div class="tool-panel column bg-green-11">
+            <div class="header q-mx-md q-mt-sm row justify-between items-center">
                 <div class="row items-center">
                     <div class="q-mr-xs">
                         Коллекция:
@@ -15,22 +15,55 @@
                 </div>
             </div>
             <div class="row q-mx-md q-my-sm items-center">
-                <q-input ref="authorInput" v-model="author" maxlength="1000" class="bg-white" style="width: 300px;" label="Автор" stack-label outlined dense clearable />
+                <q-input
+                    ref="authorInput" v-model="author" :maxlength="inputMaxLength" :debounce="inputDebounce"
+                    class="bg-white q-mt-xs" style="width: 300px;" label="Автор" stack-label outlined dense clearable
+                />
                 <div class="q-mx-xs" />
-                <q-input v-model="series" maxlength="1000" class="bg-white" style="width: 200px;" label="Серия" stack-label outlined dense clearable />
+                <q-input
+                    v-model="series" :maxlength="inputMaxLength" :debounce="inputDebounce"
+                    class="bg-white q-mt-xs" style="width: 200px;" label="Серия" stack-label outlined dense clearable
+                />
                 <div class="q-mx-xs" />
-                <q-input v-model="title" maxlength="1000" class="bg-white" style="width: 200px;" label="Название" stack-label outlined dense clearable />
+                <q-input
+                    v-model="title" :maxlength="inputMaxLength" :debounce="inputDebounce"
+                    class="bg-white q-mt-xs" style="width: 200px;" label="Название" stack-label outlined dense clearable
+                />
                 <div class="q-mx-xs" />
-                <q-input v-model="genre" maxlength="1000" class="bg-white" style="width: 200px;" label="Жанр" stack-label outlined dense clearable readonly />
+                <q-input
+                    v-model="genre" :maxlength="inputMaxLength" :debounce="inputDebounce"
+                    class="bg-white q-mt-xs" style="width: 200px;" label="Жанр" stack-label outlined dense clearable readonly
+                    @click="selectGenre"
+                />
                 <div class="q-mx-xs" />
-                <q-input v-model="lang" maxlength="1000" class="bg-white" style="width: 80px;" label="Язык" stack-label outlined dense clearable readonly />
+                <q-input
+                    v-model="lang" :maxlength="inputMaxLength" :debounce="inputDebounce"
+                    class="bg-white q-mt-xs" style="width: 80px;" label="Язык" stack-label outlined dense clearable readonly
+                    @click="selectLang"
+                />
                 <div class="q-mx-xs" />                
                 <q-btn round dense style="height: 20px" color="info" icon="la la-question" @click="showSearchHelp" />
+
+                <div class="q-mx-xs" />
+                <div class="row items-center q-mt-xs" style="font-size: 120%">
+                    Показаны {{ queryFound }} из {{ totalFound }}
+                </div>
+
+                <div class="q-mx-xs" />
+                <div class="col row justify-end q-mt-xs">
+                    <q-select
+                        v-model="limit" :options="limitOptions" class="bg-white"
+                        dropdown-icon="la la-angle-down la-sm"
+                        outlined dense emit-value map-options
+                    />
+                </div>
             </div>
         </div>
 
-        <div class="col fit" style="overflow: auto">
-            {{ config }}
+        <div class="col fit column no-wrap" style="overflow: auto">
+            <div v-for="item in tableData" :key="item.key" style="border-bottom: 1px solid #aaaaaa">
+                <div class="q-my-sm q-ml-md" style="font-size: 120%">{{ item.value }}</div>
+            </div>
         </div>
     </div>
 </template>
@@ -55,12 +88,32 @@ class Search {
     collection = '';
     projectName = '';
 
+    //input field consts 
+    inputMaxLength = 1000;
+    inputDebounce = 400;
+
     //search fields
     author = '';
     series = '';
     title = '';
     genre = '';
     lang = '';
+    limit = 100;
+
+    //stuff
+    queryFound = 0;
+    totalFound = 0;
+
+    limitOptions = [
+        {label: '10', value: 10},
+        {label: '20', value: 20},
+        {label: '50', value: 50},
+        {label: '100', value: 100},
+        {label: '1000', value: 1000},
+    ];
+
+    searchResult = {};
+    tableData = [];
 
     created() {
         this.commit = this.$store.commit;
@@ -101,7 +154,23 @@ class Search {
             `, 'Статистика по коллекции', {iconName: 'la la-info-circle'});
     }
 
+    selectGenre() {
+        this.$root.stdDialog.alert('Выбор жанра');
+    }    
+
+    selectLang() {
+        this.$root.stdDialog.alert('Выбор языка');
+    }
+    
     async updateTableData() {
+        let result = [];
+
+        let id = 0;
+        for (const rec of this.searchResult.author) {
+            result.push({key: id++, value: rec.author});
+        }
+
+        this.tableData = result;
     }
 
     async refresh() {
@@ -111,6 +180,7 @@ class Search {
             title: this.title,
             genre: this.genre,
             lang:  this.lang,
+            limit: this.limit,
         };
 
         this.queryExecute = newQuery;
@@ -125,13 +195,17 @@ class Search {
                 this.queryExecute = null;
 
                 try {
-                    this.searchResult = await this.api.search(query);
+                    const result = await this.api.search(query);
+
+                    this.queryFound = result.author.length;
+                    this.totalFound = result.totalFound;
+
+                    this.searchResult = result;
+                    this.updateTableData();//no await
                 } catch (e) {
                     this.$root.stdDialog.alert(e.message, 'Ошибка');
                     return;
                 }
-
-                this.updateTableData();//no await
             }
         } finally {
             this.refreshing = false;
@@ -153,7 +227,7 @@ export default vueComponent(Search);
 
 .header {
     font-size: 150%;
-    height: 30px;
+    min-height: 30px;
 }
 
 .clickable {

+ 2 - 2
client/quasar.js

@@ -25,7 +25,7 @@ import {QIcon} from 'quasar/src/components/icon';
 //import {QSpinner} from 'quasar/src/components/spinner';
 //import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
 //import {QCheckbox} from 'quasar/src/components/checkbox';
-//import {QSelect} from 'quasar/src/components/select';
+import {QSelect} from 'quasar/src/components/select';
 //import {QColor} from 'quasar/src/components/color';
 //import {QPopupProxy} from 'quasar/src/components/popup-proxy';
 import {QDialog} from 'quasar/src/components/dialog';
@@ -56,7 +56,7 @@ const components = {
     //QSpinner,
     //QTable, QTh, QTr, QTd,
     //QCheckbox,
-    //QSelect,
+    QSelect,
     //QColor,
     //QPopupProxy,
     QDialog,

+ 10 - 0
server/controllers/WebSocketController.js

@@ -66,6 +66,8 @@ class WebSocketController {
                     await this.getConfig(req, ws); break;
                 case 'get-worker-state':
                     await this.getWorkerState(req, ws); break;
+                case 'search':
+                    await this.search(req, ws); break;
 
                 default:
                     throw new Error(`Action not found: ${req.action}`);
@@ -112,6 +114,14 @@ class WebSocketController {
         this.send((state ? state : {}), req, ws);
     }
 
+    async search(req, ws) {
+        if (!req.query)
+            throw new Error(`query is empty`);
+
+        const result = await this.webWorker.search(req.query);
+
+        this.send(result, req, ws);
+    }
 }
 
 module.exports = WebSocketController;

+ 3 - 0
server/core/DbCreator.js

@@ -302,6 +302,9 @@ class DbCreator {
         await db.close({table: 'lang'});
         utils.freeMemory();
 
+        //кэш-таблицы
+        
+
         callback({job: 'done', jobMessage: ''});
     }
 }

+ 64 - 9
server/core/DbSearcher.js

@@ -1,27 +1,82 @@
+//const _ = require('lodash');
+
+const utils = require('./utils');
+
 class DbSearcher {
     constructor(db) {
         this.db = db;
     }
 
-    async search(query) {
+    async selectAuthorIds(query) {
         const db = this.db;
 
-        let result = [];
-
+        let authorRows;
+        //сначала выберем все id авторов по фильтру
+        //порядок id соответсвует ASC-сортировке по author
         if (query.author) {
             //
         } else {
-            result = await db.select({
+            authorRows = await db.select({
                 table: 'author',
-                map: `(r) => ({id: r.id, author: r.author})`
+                map: `(r) => ({id: r.id})`,
             });
         }
 
-        if (query.limit) {
-            result = result.slice(0, query.limit);
-        }
+        let authorIds = new Set();
+        for (const row of authorRows)
+            authorIds.add(row.id);
+
+        const idsArr = [];
+        idsArr.push(authorIds);
+
+        //серии
+        //названия
+        //жанры
+        //языки
+
+        if (idsArr.length > 1)
+            authorIds = utils.intersectSet(idsArr);
+
+        //сортировка
+        authorIds = Array.from(authorIds);
+        authorIds.sort();
+
+        return authorIds;
+    }
+
+    async getAuthorIds(query) {
+        const db = this.db;
+
+        if (!db.searchCache)
+            db.searchCache = {};
+
+        /*const q = query;
+        const key = JSON.stringify([q.author, ]);
+        query);
+        delete q.limit;
+
+        q = */
+        return await this.selectAuthorIds(query);
+    }
+
+    async search(query) {
+        const db = this.db;
+
+        const authorIds = await this.getAuthorIds(query);
+
+        const totalFound = authorIds.length;
+        const limit = (query.limit ? query.limit : 1000);
+
+        //выборка найденных авторов
+        let result = await db.select({
+            table: 'author',
+            map: `(r) => ({id: r.id, author: r.author})`,
+            where: `@@id(${db.esc(authorIds)})`
+        });
+
+        result = result.slice(0, limit);
 
-        return result;
+        return {result, totalFound};
     }
 }
 

+ 13 - 0
server/core/WebWorker.js

@@ -176,6 +176,19 @@ class WebWorker {
         return db.wwCache.config;
     }
 
+    async search(query) {
+        this.checkMyState();
+
+        const config = await this.dbConfig();
+        const result = await this.dbSearcher.search(query);
+
+        return {
+            author: result.result,
+            totalFound: result.totalFound,
+            inpxHash: (config.inpxHash ? config.inpxHash : ''),
+        };
+    }    
+
     async logServerStats() {
         while (1) {// eslint-disable-line
             try {

+ 33 - 0
server/core/utils.js

@@ -59,6 +59,38 @@ function getBufHash(buf, hashName, enc) {
     return hash.digest(enc);
 }
 
+function intersectSet(arrSet) {
+    if (!arrSet.length)
+        return new Set();
+
+    let min = 0;
+    let size = arrSet[0].size;
+    for (let i = 1; i < arrSet.length; i++) {
+        if (arrSet[i].size < size) {
+            min = i;
+            size = arrSet[i].size;
+        }
+    }
+
+    const result = new Set();
+    for (const elem of arrSet[min]) {
+        let inAll = true;
+        for (let i = 0; i < arrSet.length; i++) {
+            if (i === min)
+                continue;
+            if (!arrSet[i].has(elem)) {
+                inAll = false;
+                break;
+            }
+        }
+
+        if (inAll)
+            result.add(elem);
+    }
+
+    return result;
+}
+
 module.exports = {
     sleep,
     versionText,
@@ -68,4 +100,5 @@ module.exports = {
     freeMemory,
     getFileHash,
     getBufHash,
+    intersectSet,
 };