feat: options ui search

This commit is contained in:
Michal Szczepanski 2023-05-23 16:34:04 +02:00
parent f91d170f3e
commit 39e4b4c610
3 changed files with 121 additions and 40 deletions

@ -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) {

@ -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;
}
}