accounts: add "record count", start searchable user cache

RecordCounter class created from that in activityList, and put in
accountsList. PageCount-type route standardized and made for /users
(/users/count). Created userCache, which regularly generates the
respUser list returned by /users. Added a currently dumb POST /users for
searching/pagination, GET /users is now just for getting -all- users.
go-getted expr, an expression language that seems like it'll be useful
for evaluating local searches. We don't store this data in the badger
    DB, so we can't use the nice query form provided by badgerhold.
This commit is contained in:
Harvey Tindall
2025-05-15 19:19:51 +01:00
parent da0dc7f1c0
commit ebff016b5d
13 changed files with 213 additions and 67 deletions
+24 -6
View File
@@ -5,6 +5,7 @@ import { stripMarkdown } from "../modules/stripmd.js";
import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js";
import { HiddenInputField } from "./ui.js";
import { RecordCounter } from "./activity.js";
declare var window: GlobalWindow;
@@ -702,7 +703,11 @@ class user implements User, SearchableItem {
}
this._row.remove();
}
}
}
interface UsersDTO extends paginatedDTO {
users: User[];
}
export class accountsList {
private _table = document.getElementById("accounts-list") as HTMLTableSectionElement;
@@ -771,6 +776,8 @@ export class accountsList {
private _filterArea = document.getElementById("accounts-filter-area");
private _searchOptionsHeader = document.getElementById("accounts-search-options-header");
private _counter: RecordCounter;
// Whether the "Extend expiry" is extending or setting an expiry.
private _settingExpiry = false;
@@ -1779,6 +1786,9 @@ export class accountsList {
constructor() {
this._populateNumbers();
this._counter = new RecordCounter(document.getElementById("accounts-record-counter"));
this._users = {};
this._selectAll.checked = false;
this._selectAll.onchange = () => {
@@ -2035,12 +2045,16 @@ export class accountsList {
}
reload = (callback?: () => void) => {
this._counter.reset()
this._counter.getTotal("/users/count");
_get("/users", null, (req: XMLHttpRequest) => {
if (req.readyState == 4 && req.status == 200) {
let resp = req.response as UsersDTO;
// same method as inviteList.reload()
let accountsOnDOM: { [id: string]: boolean } = {};
for (let id in this._users) { accountsOnDOM[id] = true; }
for (let u of (req.response["users"] as User[])) {
for (let u of resp.users) {
if (u.id in this._users) {
this._users[u.id].update(u);
delete accountsOnDOM[u.id];
@@ -2055,10 +2069,10 @@ export class accountsList {
// console.log("reload, so sorting by", this._activeSortColumn);
this._ordering = this._columns[this._activeSortColumn].sort(this._users);
this._search.ordering = this._ordering;
if (!(this._search.inSearch)) {
this.setVisibility(this._ordering, true);
this._notFoundPanel.classList.add("unfocused");
} else {
this._counter.loaded = this._ordering.length;
if (this._search.inSearch) {
const results = this._search.search(this._searchBox.value);
if (results.length == 0) {
this._notFoundPanel.classList.remove("unfocused");
@@ -2066,6 +2080,10 @@ export class accountsList {
this._notFoundPanel.classList.add("unfocused");
}
this.setVisibility(results, true);
} else {
this._counter.shown = this._counter.loaded;
this.setVisibility(this._ordering, true);
this._notFoundPanel.classList.add("unfocused");
}
this._checkCheckCount();
+66 -38
View File
@@ -346,9 +346,64 @@ export class Activity implements activity, SearchableItem {
asElement = () => { return this._card; };
}
interface ActivitiesDTO {
export class RecordCounter {
private _container: HTMLElement;
private _totalRecords: HTMLElement;
private _loadedRecords: HTMLElement;
private _shownRecords: HTMLElement;
private _total: number;
private _loaded: number;
private _shown: number;
constructor(container: HTMLElement) {
this._container = container;
this._container.innerHTML = `
<span class="records-total"></span>
<span class="records-loaded"></span>
<span class="records-shown"></span>
`;
this._totalRecords = document.getElementsByClassName("records-total")[0] as HTMLElement;
this._loadedRecords = document.getElementsByClassName("records-loaded")[0] as HTMLElement;
this._shownRecords = document.getElementsByClassName("records-shown")[0] as HTMLElement;
this.total = 0;
this.loaded = 0;
this.shown = 0;
}
reset() {
this.total = 0;
this.loaded = 0;
this.shown = 0;
}
// Sets the total using a PageCountDTO-returning API endpoint.
getTotal(endpoint: string) {
_get(endpoint, null, (req: XMLHttpRequest) => {
if (req.readyState != 4 || req.status != 200) return;
this.total = req.response["count"] as number;
});
}
get total(): number { return this._total; }
set total(v: number) {
this._total = v;
this._totalRecords.textContent = window.lang.var("strings", "totalRecords", `${v}`);
}
get loaded(): number { return this._loaded; }
set loaded(v: number) {
this._loaded = v;
this._loadedRecords.textContent = window.lang.var("strings", "loadedRecords", `${v}`);
}
get shown(): number { return this._shown; }
set shown(v: number) {
this._shown = v;
this._shownRecords.textContent = window.lang.var("strings", "shownRecords", `${v}`);
}
}
interface ActivitiesDTO extends paginatedDTO {
activities: activity[];
last_page: boolean;
}
export class activityList {
@@ -368,31 +423,7 @@ export class activityList {
private _keepSearchingDescription = document.getElementById("activity-keep-searching-description");
private _keepSearchingButton = document.getElementById("activity-keep-searching");
private _totalRecords = document.getElementById("activity-total-records");
private _loadedRecords = document.getElementById("activity-loaded-records");
private _shownRecords = document.getElementById("activity-shown-records");
private _total: number;
private _loaded: number;
private _shown: number;
get total(): number { return this._total; }
set total(v: number) {
this._total = v;
this._totalRecords.textContent = window.lang.var("strings", "totalRecords", `${v}`);
}
get loaded(): number { return this._loaded; }
set loaded(v: number) {
this._loaded = v;
this._loadedRecords.textContent = window.lang.var("strings", "loadedRecords", `${v}`);
}
get shown(): number { return this._shown; }
set shown(v: number) {
this._shown = v;
this._shownRecords.textContent = window.lang.var("strings", "shownRecords", `${v}`);
}
private _counter: RecordCounter;
private _search: Search;
private _ascending: boolean;
@@ -421,9 +452,8 @@ export class activityList {
this._loadAllButton.classList.remove("unfocused");
this._loadAllButton.disabled = false;
this.total = 0;
this.loaded = 0;
this.shown = 0;
this._counter.reset();
this._counter.getTotal("/activity/count");
// this._page = 0;
let limit = 10;
@@ -438,10 +468,6 @@ export class activityList {
"ascending": this.ascending
}
_get("/activity/count", null, (req: XMLHttpRequest) => {
if (req.readyState != 4 || req.status != 200) return;
this.total = req.response["count"] as number;
});
_post("/activity", send, (req: XMLHttpRequest) => {
if (req.readyState != 4) return;
@@ -467,13 +493,13 @@ export class activityList {
this._search.items = this._activities;
this._search.ordering = this._ordering;
this.loaded = this._ordering.length;
this._counter.loaded = this._ordering.length;
if (this._search.inSearch) {
this._search.onSearchBoxChange(true);
this._loadAllButton.classList.remove("unfocused");
} else {
this.shown = this.loaded;
this._counter.shown = this._counter.loaded;
this.setVisibility(this._ordering, true);
this._loadAllButton.classList.add("unfocused");
this._notFoundPanel.classList.add("unfocused");
@@ -526,7 +552,7 @@ export class activityList {
// this._search.items = this._activities;
// this._search.ordering = this._ordering;
this.loaded = this._ordering.length;
this._counter.loaded = this._ordering.length;
if (this._search.inSearch || loadAll) {
if (this._lastPage) {
@@ -699,6 +725,8 @@ export class activityList {
this._activityList = document.getElementById("activity-card-list");
document.addEventListener("activity-reload", this.reload);
this._counter = new RecordCounter(document.getElementById("activity-record-counter"));
let conf: SearchConfiguration = {
filterArea: this._filterArea,
sortingByButton: this._sortingByButton,
@@ -711,7 +739,7 @@ export class activityList {
filterList: document.getElementById("activity-filter-list"),
// notFoundCallback: this._notFoundCallback,
onSearchCallback: (visibleCount: number, newItems: boolean, loadAll: boolean) => {
this.shown = visibleCount;
this._counter.shown = visibleCount;
if (this._search.inSearch && !this._lastPage) this._loadAllButton.classList.remove("unfocused");
else this._loadAllButton.classList.add("unfocused");
+4
View File
@@ -155,5 +155,9 @@ interface inviteList {
// submitter: HTMLInputElement;
// }
interface paginatedDTO {
last_page: boolean;
}
declare var config: Object;
declare var modifiedConfig: Object;