mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-12 21:09:24 +00:00
Fix broken sas ui in webkit based browsers
This commit is contained in:
152
core/static/webpack/core/components/ajax-select-index.ts
Normal file
152
core/static/webpack/core/components/ajax-select-index.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import "tom-select/dist/css/tom-select.css";
|
||||
import { inheritHtmlElement, registerComponent } from "#core:utils/web-components";
|
||||
import TomSelect from "tom-select";
|
||||
import type {
|
||||
RecursivePartial,
|
||||
TomItem,
|
||||
TomLoadCallback,
|
||||
TomOption,
|
||||
TomSettings,
|
||||
} from "tom-select/dist/types/types";
|
||||
import type { escape_html } from "tom-select/dist/types/utils";
|
||||
import { type UserProfileSchema, userSearchUsers } from "#openapi";
|
||||
|
||||
abstract class AjaxSelectBase extends inheritHtmlElement("select") {
|
||||
static observedAttributes = ["delay", "placeholder", "max"];
|
||||
public widget: TomSelect;
|
||||
|
||||
private delay: number | null = null;
|
||||
private placeholder = "";
|
||||
private max: number | null = null;
|
||||
|
||||
protected attributeChangedCallback(
|
||||
name: string,
|
||||
_oldValue?: string,
|
||||
newValue?: string,
|
||||
) {
|
||||
switch (name) {
|
||||
case "delay": {
|
||||
this.delay = Number.parseInt(newValue) ?? null;
|
||||
break;
|
||||
}
|
||||
case "placeholder": {
|
||||
this.placeholder = newValue ?? "";
|
||||
break;
|
||||
}
|
||||
case "max": {
|
||||
this.max = Number.parseInt(newValue) ?? null;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
this.configureTomSelect(this.defaultTomSelectSettings());
|
||||
this.setDefaultTomSelectBehaviors();
|
||||
});
|
||||
}
|
||||
|
||||
private defaultTomSelectSettings(): RecursivePartial<TomSettings> {
|
||||
return {
|
||||
maxItems: this.max,
|
||||
loadThrottle: this.delay,
|
||||
placeholder: this.placeholder,
|
||||
};
|
||||
}
|
||||
|
||||
private setDefaultTomSelectBehaviors() {
|
||||
// Allow removing selected items by clicking on them
|
||||
this.widget.on("item_select", (item: TomItem) => {
|
||||
this.widget.removeItem(item);
|
||||
});
|
||||
// Remove typed text once an item has been selected
|
||||
this.widget.on("item_add", () => {
|
||||
this.widget.setTextboxValue("");
|
||||
});
|
||||
}
|
||||
|
||||
abstract configureTomSelect(defaultSettings: RecursivePartial<TomSettings>): void;
|
||||
}
|
||||
|
||||
@registerComponent("user-ajax-select")
|
||||
export class UserAjaxSelect extends AjaxSelectBase {
|
||||
public filter?: <T>(items: T[]) => T[];
|
||||
static observedAttributes = [
|
||||
"min-characters-for-search",
|
||||
...AjaxSelectBase.observedAttributes,
|
||||
];
|
||||
|
||||
private minCharNumberForSearch = 2;
|
||||
|
||||
protected attributeChangedCallback(
|
||||
name: string,
|
||||
_oldValue?: string,
|
||||
newValue?: string,
|
||||
) {
|
||||
super.attributeChangedCallback(name, _oldValue, newValue);
|
||||
if (name === "min-characters-for-search") {
|
||||
this.minCharNumberForSearch = Number.parseInt(newValue) ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
configureTomSelect(defaultSettings: RecursivePartial<TomSettings>) {
|
||||
this.widget = new TomSelect(this.node, {
|
||||
...defaultSettings,
|
||||
hideSelected: true,
|
||||
diacritics: true,
|
||||
duplicates: false,
|
||||
valueField: "id",
|
||||
labelField: "display_name",
|
||||
searchField: [], // Disable local search filter and rely on tested backend
|
||||
shouldLoad: (query: string) => {
|
||||
return query.length >= this.minCharNumberForSearch; // Avoid launching search with less than 2 characters
|
||||
},
|
||||
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([], []);
|
||||
});
|
||||
},
|
||||
render: {
|
||||
option: (item: UserProfileSchema, sanitize: typeof escape_html) => {
|
||||
return `<div class="select-item">
|
||||
<img
|
||||
src="${sanitize(item.profile_pict)}"
|
||||
alt="${sanitize(item.display_name)}"
|
||||
onerror="this.src = '/static/core/img/unknown.jpg'"
|
||||
/>
|
||||
<span class="select-item-text">${sanitize(item.display_name)}</span>
|
||||
</div>`;
|
||||
},
|
||||
item: (item: UserProfileSchema, sanitize: typeof escape_html) => {
|
||||
return `<span><i class="fa fa-times"></i>${sanitize(item.display_name)}</span>`;
|
||||
},
|
||||
// biome-ignore lint/style/useNamingConvention: that's how it's defined
|
||||
not_loading: (data: TomOption, _sanitize: typeof escape_html) => {
|
||||
return `<div class="no-results">${interpolate(gettext("You need to type %(number)s more characters"), { number: this.minCharNumberForSearch - data.input.length }, true)}</div>`;
|
||||
},
|
||||
// biome-ignore lint/style/useNamingConvention: that's how it's defined
|
||||
no_results: (_data: TomOption, _sanitize: typeof escape_html) => {
|
||||
return `<div class="no-results">${gettext("No results found")}</div>`;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user