convert local autocompleter to typescript

This commit is contained in:
SeinopSys 2022-03-25 23:55:47 +01:00 committed by Luna D
parent 6d85535abd
commit cccbcd041f
No known key found for this signature in database
GPG key ID: 4B1C63448394F688
2 changed files with 26 additions and 53 deletions

View file

@ -12,7 +12,7 @@ export function leftClick<E extends MouseEvent, Target extends EventTarget>(func
return (event: E, target: Target) => { if (event.button === 0) return func(event, target); }; return (event: E, target: Target) => { if (event.button === 0) return func(event, target); };
} }
export function delegate<K extends keyof GlobalEventHandlersEventMap>(node: GlobalEventHandlers, event: K, selectors: { [k: string]: ((e: GlobalEventHandlersEventMap[K], target: Element) => boolean) }) { export function delegate<K extends keyof GlobalEventHandlersEventMap>(node: GlobalEventHandlers, event: K, selectors: Record<string, ((e: GlobalEventHandlersEventMap[K], target: Element) => boolean)>) {
node.addEventListener(event, e => { node.addEventListener(event, e => {
for (const selector in selectors) { for (const selector in selectors) {
const evtTarget = e.target as EventTarget | Element | null; const evtTarget = e.target as EventTarget | Element | null;

View file

@ -1,31 +1,23 @@
// Client-side tag completion. // Client-side tag completion.
import store from './store'; import store from './store';
/** interface Result {
* @typedef {object} Result name: string;
* @property {string} name imageCount: number;
* @property {number} imageCount associations: number[];
* @property {number[]} associations }
*/
/** /**
* Compare two strings, C-style. * Compare two strings, C-style.
*
* @param {string} a
* @param {string} b
* @returns {number}
*/ */
function strcmp(a, b) { function strcmp(a: string, b: string):number {
return a < b ? -1 : Number(a > b); return a < b ? -1 : Number(a > b);
} }
/** /**
* Returns the name of a tag without any namespace component. * Returns the name of a tag without any namespace component.
*
* @param {string} s
* @returns {string}
*/ */
function nameInNamespace(s) { function nameInNamespace(s: string): string {
const v = s.split(':', 2); const v = s.split(':', 2);
if (v.length === 2) return v[1]; if (v.length === 2) return v[1];
@ -39,25 +31,24 @@ function nameInNamespace(s) {
* the JS heap and speed up the execution of the search. * the JS heap and speed up the execution of the search.
*/ */
export class LocalAutocompleter { export class LocalAutocompleter {
private data: Uint8Array;
private view: DataView;
private decoder: TextDecoder;
private numTags: number;
private referenceStart: number;
private secondaryStart: number;
private formatVersion: number;
/** /**
* Build a new local autocompleter. * Build a new local autocompleter.
*
* @param {ArrayBuffer} backingStore
*/ */
constructor(backingStore) { constructor(backingStore: ArrayBuffer) {
/** @type {Uint8Array} */
this.data = new Uint8Array(backingStore); this.data = new Uint8Array(backingStore);
/** @type {DataView} */
this.view = new DataView(backingStore); this.view = new DataView(backingStore);
/** @type {TextDecoder} */
this.decoder = new TextDecoder(); this.decoder = new TextDecoder();
/** @type {number} */
this.numTags = this.view.getUint32(backingStore.byteLength - 4, true); this.numTags = this.view.getUint32(backingStore.byteLength - 4, true);
/** @type {number} */
this.referenceStart = this.view.getUint32(backingStore.byteLength - 8, true); this.referenceStart = this.view.getUint32(backingStore.byteLength - 8, true);
/** @type {number} */
this.secondaryStart = this.referenceStart + 8 * this.numTags; this.secondaryStart = this.referenceStart + 8 * this.numTags;
/** @type {number} */
this.formatVersion = this.view.getUint32(backingStore.byteLength - 12, true); this.formatVersion = this.view.getUint32(backingStore.byteLength - 12, true);
if (this.formatVersion !== 2) { if (this.formatVersion !== 2) {
@ -67,11 +58,8 @@ export class LocalAutocompleter {
/** /**
* Get a tag's name and its associations given a byte location inside the file. * Get a tag's name and its associations given a byte location inside the file.
*
* @param {number} location
* @returns {[string, number[]]}
*/ */
getTagFromLocation(location) { getTagFromLocation(location: number): [string, number[]] {
const nameLength = this.view.getUint8(location); const nameLength = this.view.getUint8(location);
const assnLength = this.view.getUint8(location + 1 + nameLength); const assnLength = this.view.getUint8(location + 1 + nameLength);
@ -88,11 +76,8 @@ export class LocalAutocompleter {
/** /**
* Get a Result object as the ith tag inside the file. * Get a Result object as the ith tag inside the file.
*
* @param {number} i
* @returns {[string, Result]}
*/ */
getResultAt(i) { getResultAt(i: number): [string, Result] {
const nameLocation = this.view.getUint32(this.referenceStart + i * 8, true); const nameLocation = this.view.getUint32(this.referenceStart + i * 8, true);
const imageCount = this.view.getInt32(this.referenceStart + i * 8 + 4, true); const imageCount = this.view.getInt32(this.referenceStart + i * 8 + 4, true);
const [ name, associations ] = this.getTagFromLocation(nameLocation); const [ name, associations ] = this.getTagFromLocation(nameLocation);
@ -107,23 +92,16 @@ export class LocalAutocompleter {
/** /**
* Get a Result object as the ith tag inside the file, secondary ordering. * Get a Result object as the ith tag inside the file, secondary ordering.
*
* @param {number} i
* @returns {[string, Result]}
*/ */
getSecondaryResultAt(i) { getSecondaryResultAt(i: number): [string, Result] {
const referenceIndex = this.view.getUint32(this.secondaryStart + i * 4, true); const referenceIndex = this.view.getUint32(this.secondaryStart + i * 4, true);
return this.getResultAt(referenceIndex); return this.getResultAt(referenceIndex);
} }
/** /**
* Perform a binary search to fetch all results matching a condition. * Perform a binary search to fetch all results matching a condition.
*
* @param {(i: number) => [string, Result]} getResult
* @param {(name: string) => number} compare
* @param {{[key: string]: Result}} results
*/ */
scanResults(getResult, compare, results) { scanResults(getResult: (i: number) => [string, Result], compare: (name: string) => number, results: Record<string, Result>) {
const unfilter = store.get('unfilter_tag_suggestions'); const unfilter = store.get('unfilter_tag_suggestions');
let min = 0; let min = 0;
@ -132,7 +110,7 @@ export class LocalAutocompleter {
const hiddenTags = window.booru.hiddenTagList; const hiddenTags = window.booru.hiddenTagList;
while (min < max - 1) { while (min < max - 1) {
const med = (min + (max - min) / 2) | 0; const med = min + (max - min) / 2 | 0;
const sortKey = getResult(med)[0]; const sortKey = getResult(med)[0];
if (compare(sortKey) >= 0) { if (compare(sortKey) >= 0) {
@ -161,25 +139,20 @@ export class LocalAutocompleter {
/** /**
* Find the top k results by image count which match the given string prefix. * Find the top k results by image count which match the given string prefix.
*
* @param {string} prefix
* @param {number} k
* @returns {Result[]}
*/ */
topK(prefix, k) { topK(prefix: string, k:number): Result[] {
/** @type {{[key: string]: Result}} */ const results: Record<string, Result> = {};
const results = {};
if (prefix === '') { if (prefix === '') {
return []; return [];
} }
// Find normally, in full name-sorted order // Find normally, in full name-sorted order
const prefixMatch = (/** @type {string} */ name) => strcmp(name.slice(0, prefix.length), prefix); const prefixMatch = (name: string) => strcmp(name.slice(0, prefix.length), prefix);
this.scanResults(this.getResultAt.bind(this), prefixMatch, results); this.scanResults(this.getResultAt.bind(this), prefixMatch, results);
// Find in secondary order // Find in secondary order
const namespaceMatch = (/** @type {string} */ name) => strcmp(nameInNamespace(name).slice(0, prefix.length), prefix); const namespaceMatch = (name: string) => strcmp(nameInNamespace(name).slice(0, prefix.length), prefix);
this.scanResults(this.getSecondaryResultAt.bind(this), namespaceMatch, results); this.scanResults(this.getSecondaryResultAt.bind(this), namespaceMatch, results);
// Sort results by image count // Sort results by image count