From 0af3505c2ad8664cdc0db7e47c7f87a9e3b29252 Mon Sep 17 00:00:00 2001 From: Sli Date: Sun, 20 Oct 2024 02:26:32 +0200 Subject: [PATCH] Make a generic AjaxSelect abstract class --- .../core/components/ajax-select-index.ts | 90 ++++++++++++------- core/static/webpack/utils/api.ts | 4 +- sas/static/webpack/sas/viewer-index.ts | 4 +- 3 files changed, 63 insertions(+), 35 deletions(-) diff --git a/core/static/webpack/core/components/ajax-select-index.ts b/core/static/webpack/core/components/ajax-select-index.ts index 1db8f8b8..32888cf2 100644 --- a/core/static/webpack/core/components/ajax-select-index.ts +++ b/core/static/webpack/core/components/ajax-select-index.ts @@ -112,42 +112,72 @@ class AutocompleteSelect extends inheritHtmlElement("select") { } } -@registerComponent("user-ajax-select") -export class UserAjaxSelect extends AutocompleteSelect { - public filter?: (items: T[]) => T[]; - +abstract class AjaxSelect extends AutocompleteSelect { + protected filter?: (items: TomOption[]) => TomOption[] = null; protected minCharNumberForSearch = 2; + protected abstract valueField: string; + protected abstract labelField: string; + protected abstract renderOption( + item: TomOption, + sanitize: typeof escape_html, + ): string; + protected abstract renderItem(item: TomOption, sanitize: typeof escape_html): string; + protected abstract search(query: string): Promise; + + public setFilter(filter?: (items: TomOption[]) => TomOption[]) { + this.filter = filter; + } + + protected getLoadFunction() { + // this will be replaced by TomSelect if we don't wrap it that way + return async (query: string, callback: TomLoadCallback) => { + const resp = await this.search(query); + if (this.filter) { + callback(this.filter(resp), []); + } else { + callback(resp, []); + } + }; + } + protected tomSelectSettings(): RecursivePartial { return { ...super.tomSelectSettings(), hideSelected: true, diacritics: true, duplicates: false, - valueField: "id", - labelField: "display_name", + valueField: this.valueField, + labelField: this.labelField, searchField: [], // Disable local search filter and rely on tested backend - load: (query: string, callback: TomLoadCallback) => { - userSearchUsers({ - query: { - search: query, - }, - }).then((response) => { - if (response.data) { - if (this.filter) { - callback(this.filter(response.data.results), []); - } else { - callback(response.data.results, []); - } - return; - } - callback([], []); - }); - }, + load: this.getLoadFunction(), render: { ...super.tomSelectSettings().render, - option: (item: UserProfileSchema, sanitize: typeof escape_html) => { - return `
+ option: this.renderOption, + item: this.renderItem, + }, + }; + } +} + +@registerComponent("user-ajax-select") +export class UserAjaxSelect extends AjaxSelect { + protected valueField = "id"; + protected labelField = "display_name"; + + protected async search(query: string): Promise { + const resp = await userSearchUsers({ query: { search: query } }); + if (resp.data) { + return resp.data.results; + } + return []; + } + protected tomSelectSettings(): RecursivePartial { + return super.tomSelectSettings(); + } + + protected renderOption(item: UserProfileSchema, sanitize: typeof escape_html) { + return `
${sanitize(item.display_name)} ${sanitize(item.display_name)}
`; - }, - item: (item: UserProfileSchema, sanitize: typeof escape_html) => { - return `${sanitize(item.display_name)}`; - }, - }, - }; + } + + protected renderItem(item: UserProfileSchema, sanitize: typeof escape_html) { + return `${sanitize(item.display_name)}`; } } diff --git a/core/static/webpack/utils/api.ts b/core/static/webpack/utils/api.ts index 72df568b..ac647cd7 100644 --- a/core/static/webpack/utils/api.ts +++ b/core/static/webpack/utils/api.ts @@ -1,14 +1,14 @@ import type { Client, Options, RequestResult } from "@hey-api/client-fetch"; import { client } from "#openapi"; -interface PaginatedResponse { +export interface PaginatedResponse { count: number; next: string | null; previous: string | null; results: T[]; } -interface PaginatedRequest { +export interface PaginatedRequest { query?: { page?: number; // biome-ignore lint/style/useNamingConvention: api is in snake_case diff --git a/sas/static/webpack/sas/viewer-index.ts b/sas/static/webpack/sas/viewer-index.ts index b084810c..faa9505a 100644 --- a/sas/static/webpack/sas/viewer-index.ts +++ b/sas/static/webpack/sas/viewer-index.ts @@ -177,7 +177,7 @@ exportToHtml("loadViewer", (config: ViewerConfig) => { } as PicturesFetchPicturesData) ).map(PictureWithIdentifications.fromPicture); this.selector = this.$refs.search; - this.selector.filter = (users: UserProfileSchema[]) => { + this.selector.setFilter((users: UserProfileSchema[]) => { const resp: UserProfileSchema[] = []; const ids = [ ...(this.currentPicture.identifications || []).map( @@ -190,7 +190,7 @@ exportToHtml("loadViewer", (config: ViewerConfig) => { } } return resp; - }; + }); this.currentPicture = this.pictures.find( (i: PictureSchema) => i.id === config.firstPictureId, );