feat: options ui search
This commit is contained in:
parent
f91d170f3e
commit
39e4b4c610
@ -26,6 +26,7 @@ import { fnConsoleLog } from '../../common/fn/console.fn';
|
||||
|
||||
export class BoardStore {
|
||||
static objData: ObjDto[] = [];
|
||||
static keySet = new Set<number>();
|
||||
|
||||
private static loading = false;
|
||||
private static isLastValue = false;
|
||||
@ -46,10 +47,12 @@ export class BoardStore {
|
||||
}
|
||||
|
||||
static set search(value: string) {
|
||||
if (this.rangeRequest.search.length > 1) {
|
||||
if (this.rangeRequest.search.length > 0) {
|
||||
this.rangeRequest.from = -1;
|
||||
this.rangeRequest.listId = -1;
|
||||
this.objData = [];
|
||||
this.keySet.clear();
|
||||
if (this.refreshBoardCallback) this.refreshBoardCallback();
|
||||
}
|
||||
this.rangeRequest.search = value;
|
||||
this.isLastValue = false;
|
||||
@ -58,6 +61,7 @@ export class BoardStore {
|
||||
static removeObj = async (value: ObjDto): Promise<boolean> => {
|
||||
for (let i = 0; i < this.objData.length; i++) {
|
||||
if (this.objData[i].id == value.id) {
|
||||
this.keySet.delete(value.id);
|
||||
this.objData.splice(i, 1);
|
||||
if (value.type === ObjTypeDto.PageElementPin) {
|
||||
const pin = value as ObjDto<ObjPagePinDto>;
|
||||
@ -81,6 +85,7 @@ export class BoardStore {
|
||||
this.rangeRequest.from = (await BrowserStorageWrapper.get<number | undefined>(ObjectStoreKeys.OBJECT_ID)) || 1;
|
||||
this.rangeRequest.listId = -1;
|
||||
this.objData = [];
|
||||
this.keySet.clear();
|
||||
}
|
||||
|
||||
static setLoading(value: boolean): void {
|
||||
@ -103,12 +108,6 @@ export class BoardStore {
|
||||
const result = await new OptionsObjGetRangeCommand(this.rangeRequest).execute();
|
||||
if (result && result.data.length > 0) {
|
||||
const lastResultObj = result.data[result.data.length - 1];
|
||||
const firstResultObj = result.data[0];
|
||||
const lastObj = this.objData[this.objData.length - 1];
|
||||
if (lastObj?.id === firstResultObj.id) {
|
||||
result.data.shift();
|
||||
fnConsoleLog('PinBoardStore->getRange->UNSHIFT', result.data);
|
||||
}
|
||||
|
||||
if (result.data.length === 0) {
|
||||
this.isLastValue = true;
|
||||
@ -119,7 +118,14 @@ export class BoardStore {
|
||||
this.rangeRequest.listId = result.listId;
|
||||
this.rangeRequest.from = lastResultObj.id;
|
||||
|
||||
this.objData.push(...result.data);
|
||||
let added = false;
|
||||
for (const obj of result.data) {
|
||||
if (this.keySet.has(obj.id)) continue;
|
||||
added = true;
|
||||
this.objData.push(obj);
|
||||
this.keySet.add(obj.id);
|
||||
}
|
||||
if (!added) this.isLastValue = true;
|
||||
|
||||
if (this.refreshBoardCallback) this.refreshBoardCallback();
|
||||
} else {
|
||||
|
@ -20,6 +20,7 @@ import { ICommand } from '../../../common/model/shared/common.dto';
|
||||
import { ObjDto } from '../../../common/model/obj/obj.dto';
|
||||
import { ObjRangeIdCommand } from '../../../common/command/obj/id/obj-range-id.command';
|
||||
import { ObjectStoreKeys } from '../../../common/keys/object.store.keys';
|
||||
import { OptionsSearchIdsCommand } from './options-search-ids.command';
|
||||
import { fnConsoleLog } from '../../../common/fn/console.fn';
|
||||
|
||||
const emptyResult = { listId: -1, data: [] };
|
||||
@ -32,7 +33,7 @@ export class OptionsObjGetRangeCommand implements ICommand<Promise<ObjRangeRespo
|
||||
const { from, search, limit } = this.data;
|
||||
let listId = this.data.listId;
|
||||
if (search) {
|
||||
return await this.getSearch(from, search);
|
||||
return await this.getSearch(from, limit, search);
|
||||
}
|
||||
if (listId === -1) {
|
||||
listId = (await BrowserStorageWrapper.get<number | undefined>(ObjectStoreKeys.OBJECT_LIST_ID)) || 1;
|
||||
@ -43,41 +44,13 @@ export class OptionsObjGetRangeCommand implements ICommand<Promise<ObjRangeRespo
|
||||
}
|
||||
}
|
||||
|
||||
private async getSearch(from: number, search: string): Promise<ObjRangeResponse> {
|
||||
private async getSearch(from: number, limit: number, search: string): Promise<ObjRangeResponse> {
|
||||
if (search.length < 2) return emptyResult;
|
||||
const start = search.substring(0, 2);
|
||||
|
||||
const key = `${ObjectStoreKeys.SEARCH_WORD}:${start}`;
|
||||
const words = await BrowserStorageWrapper.get<string[] | undefined>(key);
|
||||
|
||||
fnConsoleLog('OptionsObjSearchCommand->words', from, words);
|
||||
if (!words) return emptyResult;
|
||||
const ids = await new OptionsSearchIdsCommand(search, from, limit).execute();
|
||||
|
||||
const data: ObjDto[] = [];
|
||||
|
||||
const idSet = new Set<number>();
|
||||
|
||||
// gather ids
|
||||
for (const word of words) {
|
||||
const wordKey = `${ObjectStoreKeys.SEARCH_INDEX}:${word}`;
|
||||
const wordIndex = await BrowserStorageWrapper.get<number[] | undefined>(wordKey);
|
||||
fnConsoleLog('wordIndex', wordIndex);
|
||||
if (!wordIndex) continue;
|
||||
|
||||
for (const objId of wordIndex) {
|
||||
idSet.add(objId);
|
||||
}
|
||||
}
|
||||
|
||||
// skip from for scrolling
|
||||
let skip = true;
|
||||
for (const objId of Array.from(idSet)) {
|
||||
// dirty but works - assume that javascript set preserves order
|
||||
if (from > -1 && skip && objId !== from) {
|
||||
continue;
|
||||
} else if (objId === from) {
|
||||
skip = false;
|
||||
}
|
||||
for (const objId of ids) {
|
||||
const objKey = `${ObjectStoreKeys.OBJECT_ID}:${objId}`;
|
||||
const obj = await BrowserStorageWrapper.get<ObjDto>(objKey);
|
||||
if (!obj) {
|
||||
|
102
src/service-worker/command/options/options-search-ids.command.ts
Normal file
102
src/service-worker/command/options/options-search-ids.command.ts
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
|
||||
* Copyright (c) 2023 Michal Szczepanski.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { BrowserStorageWrapper } from '../../../common/service/browser.storage.wrapper';
|
||||
import { ICommand } from '../../../common/model/shared/common.dto';
|
||||
import { ObjectStoreKeys } from '../../../common/keys/object.store.keys';
|
||||
import { distance } from 'fastest-levenshtein';
|
||||
|
||||
interface DistanceWord {
|
||||
word: string;
|
||||
distance: number;
|
||||
}
|
||||
|
||||
interface DistanceIds {
|
||||
ids: number[];
|
||||
distance: number;
|
||||
}
|
||||
|
||||
export class OptionsSearchIdsCommand implements ICommand<Promise<number[]>> {
|
||||
constructor(private search: string, private from: number, private limit: number) {}
|
||||
async execute(): Promise<number[]> {
|
||||
const searchWords = this.search.split(' ');
|
||||
|
||||
const wordsIds: DistanceIds[] = [];
|
||||
for (const search of searchWords) {
|
||||
const s = await this.getWordSet(search);
|
||||
if (s) wordsIds.push(...s);
|
||||
}
|
||||
wordsIds.sort((a, b) => {
|
||||
if (a.distance > b.distance) {
|
||||
return 1;
|
||||
} else if (a.distance < b.distance) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const idsSet = new Set<number>();
|
||||
for (const obj of wordsIds) {
|
||||
obj.ids.forEach((id) => idsSet.add(id));
|
||||
}
|
||||
|
||||
const out: number[] = [];
|
||||
let skip = true;
|
||||
for (const objId of Array.from(idsSet)) {
|
||||
// dirty but works - assume that javascript set preserves order
|
||||
if (this.from > -1 && skip && objId !== this.from) {
|
||||
continue;
|
||||
} else if (objId === this.from) {
|
||||
skip = false;
|
||||
}
|
||||
out.push(objId);
|
||||
if (out.length === this.limit) break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async getWordSet(search: string): Promise<DistanceIds[] | undefined> {
|
||||
const start = search.substring(0, 2);
|
||||
const key = `${ObjectStoreKeys.SEARCH_WORD}:${start}`;
|
||||
const words = await BrowserStorageWrapper.get<string[] | undefined>(key);
|
||||
if (!words) return;
|
||||
|
||||
const distanceWord: DistanceWord[] = [];
|
||||
|
||||
for (const word of words) {
|
||||
distanceWord.push({ word, distance: distance(search, word) });
|
||||
}
|
||||
distanceWord.sort((a, b) => {
|
||||
if (a.distance > b.distance) {
|
||||
return 1;
|
||||
} else if (a.distance < b.distance) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const distances: DistanceIds[] = [];
|
||||
|
||||
for (const dw of distanceWord) {
|
||||
const wordKey = `${ObjectStoreKeys.SEARCH_INDEX}:${dw.word}`;
|
||||
if (dw.distance > 3) continue;
|
||||
const ids = await BrowserStorageWrapper.get<number[] | undefined>(wordKey);
|
||||
if (!ids) continue;
|
||||
distances.push({ distance: dw.distance, ids });
|
||||
}
|
||||
return distances;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user