Работа над приложением
This commit is contained in:
@@ -24,6 +24,20 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
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) {
|
async request(params) {
|
||||||
@@ -40,7 +54,7 @@ class Api {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async config() {
|
async getConfig() {
|
||||||
const response = await this.request({action: 'get-config'});
|
const response = await this.request({action: 'get-config'});
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
|
|||||||
@@ -91,17 +91,6 @@ class App {
|
|||||||
this.$root.stdDialog = this.$refs.stdDialog;
|
this.$root.stdDialog = this.$refs.stdDialog;
|
||||||
|
|
||||||
this.setAppTitle();
|
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() {
|
get config() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="root column fit">
|
<div class="root column fit">
|
||||||
<div class="tool-panel bg-green-11">
|
<div class="tool-panel column bg-green-11">
|
||||||
<div class="header q-mx-md q-mt-xs row justify-between items-center">
|
<div class="header q-mx-md q-mt-sm row justify-between items-center">
|
||||||
<div class="row items-center">
|
<div class="row items-center">
|
||||||
<div class="q-mr-xs">
|
<div class="q-mr-xs">
|
||||||
Коллекция:
|
Коллекция:
|
||||||
@@ -15,22 +15,55 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mx-md q-my-sm items-center">
|
<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" />
|
<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" />
|
<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" />
|
<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" />
|
<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" />
|
<div class="q-mx-xs" />
|
||||||
<q-btn round dense style="height: 20px" color="info" icon="la la-question" @click="showSearchHelp" />
|
<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>
|
</div>
|
||||||
|
|
||||||
<div class="col fit" style="overflow: auto">
|
<div class="col fit column no-wrap" style="overflow: auto">
|
||||||
{{ config }}
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -55,12 +88,32 @@ class Search {
|
|||||||
collection = '';
|
collection = '';
|
||||||
projectName = '';
|
projectName = '';
|
||||||
|
|
||||||
|
//input field consts
|
||||||
|
inputMaxLength = 1000;
|
||||||
|
inputDebounce = 400;
|
||||||
|
|
||||||
//search fields
|
//search fields
|
||||||
author = '';
|
author = '';
|
||||||
series = '';
|
series = '';
|
||||||
title = '';
|
title = '';
|
||||||
genre = '';
|
genre = '';
|
||||||
lang = '';
|
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() {
|
created() {
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
@@ -101,7 +154,23 @@ class Search {
|
|||||||
`, 'Статистика по коллекции', {iconName: 'la la-info-circle'});
|
`, 'Статистика по коллекции', {iconName: 'la la-info-circle'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectGenre() {
|
||||||
|
this.$root.stdDialog.alert('Выбор жанра');
|
||||||
|
}
|
||||||
|
|
||||||
|
selectLang() {
|
||||||
|
this.$root.stdDialog.alert('Выбор языка');
|
||||||
|
}
|
||||||
|
|
||||||
async updateTableData() {
|
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() {
|
async refresh() {
|
||||||
@@ -111,6 +180,7 @@ class Search {
|
|||||||
title: this.title,
|
title: this.title,
|
||||||
genre: this.genre,
|
genre: this.genre,
|
||||||
lang: this.lang,
|
lang: this.lang,
|
||||||
|
limit: this.limit,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.queryExecute = newQuery;
|
this.queryExecute = newQuery;
|
||||||
@@ -125,13 +195,17 @@ class Search {
|
|||||||
this.queryExecute = null;
|
this.queryExecute = null;
|
||||||
|
|
||||||
try {
|
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) {
|
} catch (e) {
|
||||||
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateTableData();//no await
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
@@ -153,7 +227,7 @@ export default vueComponent(Search);
|
|||||||
|
|
||||||
.header {
|
.header {
|
||||||
font-size: 150%;
|
font-size: 150%;
|
||||||
height: 30px;
|
min-height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clickable {
|
.clickable {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {QIcon} from 'quasar/src/components/icon';
|
|||||||
//import {QSpinner} from 'quasar/src/components/spinner';
|
//import {QSpinner} from 'quasar/src/components/spinner';
|
||||||
//import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
|
//import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
|
||||||
//import {QCheckbox} from 'quasar/src/components/checkbox';
|
//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 {QColor} from 'quasar/src/components/color';
|
||||||
//import {QPopupProxy} from 'quasar/src/components/popup-proxy';
|
//import {QPopupProxy} from 'quasar/src/components/popup-proxy';
|
||||||
import {QDialog} from 'quasar/src/components/dialog';
|
import {QDialog} from 'quasar/src/components/dialog';
|
||||||
@@ -56,7 +56,7 @@ const components = {
|
|||||||
//QSpinner,
|
//QSpinner,
|
||||||
//QTable, QTh, QTr, QTd,
|
//QTable, QTh, QTr, QTd,
|
||||||
//QCheckbox,
|
//QCheckbox,
|
||||||
//QSelect,
|
QSelect,
|
||||||
//QColor,
|
//QColor,
|
||||||
//QPopupProxy,
|
//QPopupProxy,
|
||||||
QDialog,
|
QDialog,
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ class WebSocketController {
|
|||||||
await this.getConfig(req, ws); break;
|
await this.getConfig(req, ws); break;
|
||||||
case 'get-worker-state':
|
case 'get-worker-state':
|
||||||
await this.getWorkerState(req, ws); break;
|
await this.getWorkerState(req, ws); break;
|
||||||
|
case 'search':
|
||||||
|
await this.search(req, ws); break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Action not found: ${req.action}`);
|
throw new Error(`Action not found: ${req.action}`);
|
||||||
@@ -112,6 +114,14 @@ class WebSocketController {
|
|||||||
this.send((state ? state : {}), req, ws);
|
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;
|
module.exports = WebSocketController;
|
||||||
|
|||||||
@@ -302,6 +302,9 @@ class DbCreator {
|
|||||||
await db.close({table: 'lang'});
|
await db.close({table: 'lang'});
|
||||||
utils.freeMemory();
|
utils.freeMemory();
|
||||||
|
|
||||||
|
//кэш-таблицы
|
||||||
|
|
||||||
|
|
||||||
callback({job: 'done', jobMessage: ''});
|
callback({job: 'done', jobMessage: ''});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,82 @@
|
|||||||
|
//const _ = require('lodash');
|
||||||
|
|
||||||
|
const utils = require('./utils');
|
||||||
|
|
||||||
class DbSearcher {
|
class DbSearcher {
|
||||||
constructor(db) {
|
constructor(db) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(query) {
|
async selectAuthorIds(query) {
|
||||||
const db = this.db;
|
const db = this.db;
|
||||||
|
|
||||||
let result = [];
|
let authorRows;
|
||||||
|
//сначала выберем все id авторов по фильтру
|
||||||
|
//порядок id соответсвует ASC-сортировке по author
|
||||||
if (query.author) {
|
if (query.author) {
|
||||||
//
|
//
|
||||||
} else {
|
} else {
|
||||||
result = await db.select({
|
authorRows = await db.select({
|
||||||
table: 'author',
|
table: 'author',
|
||||||
map: `(r) => ({id: r.id, author: r.author})`
|
map: `(r) => ({id: r.id})`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.limit) {
|
let authorIds = new Set();
|
||||||
result = result.slice(0, query.limit);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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, totalFound};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,19 @@ class WebWorker {
|
|||||||
return db.wwCache.config;
|
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() {
|
async logServerStats() {
|
||||||
while (1) {// eslint-disable-line
|
while (1) {// eslint-disable-line
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -59,6 +59,38 @@ function getBufHash(buf, hashName, enc) {
|
|||||||
return hash.digest(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 = {
|
module.exports = {
|
||||||
sleep,
|
sleep,
|
||||||
versionText,
|
versionText,
|
||||||
@@ -68,4 +100,5 @@ module.exports = {
|
|||||||
freeMemory,
|
freeMemory,
|
||||||
getFileHash,
|
getFileHash,
|
||||||
getBufHash,
|
getBufHash,
|
||||||
|
intersectSet,
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user