mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-03-18 21:50:33 +01:00
list/search/accounts: fully(?) page-based search
searches are triggered by setting the search QP, and ran by a listener set on PageManager. same with details. Works surprisingly well, but i'm sure theres bugs.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { ThemeManager } from "./modules/theme.js";
|
import { ThemeManager } from "./modules/theme.js";
|
||||||
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
||||||
import { Modal } from "./modules/modal.js";
|
import { Modal } from "./modules/modal.js";
|
||||||
import { Tabs, Tab, isPageEventBindable, isNavigatable } from "./modules/tabs.js";
|
import { TabManager, isPageEventBindable, isNavigatable } from "./modules/tabs.js";
|
||||||
import { DOMInviteList, createInvite } from "./modules/invites.js";
|
import { DOMInviteList, createInvite } from "./modules/invites.js";
|
||||||
import { accountsList } from "./modules/accounts.js";
|
import { accountsList } from "./modules/accounts.js";
|
||||||
import { settingsList } from "./modules/settings.js";
|
import { settingsList } from "./modules/settings.js";
|
||||||
@@ -129,6 +129,9 @@ window.availableProfiles = window.availableProfiles || [];
|
|||||||
});
|
});
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
|
// tab content objects will register with this independently, so initialise now
|
||||||
|
window.tabs = new TabManager();
|
||||||
|
|
||||||
var inviteCreator = new createInvite();
|
var inviteCreator = new createInvite();
|
||||||
|
|
||||||
var accounts = new accountsList();
|
var accounts = new accountsList();
|
||||||
@@ -148,7 +151,6 @@ let navigated = false;
|
|||||||
|
|
||||||
// load tabs
|
// load tabs
|
||||||
const tabs: { id: string; url: string; reloader: () => void; unloader?: () => void }[] = [];
|
const tabs: { id: string; url: string; reloader: () => void; unloader?: () => void }[] = [];
|
||||||
window.tabs = new Tabs();
|
|
||||||
[window.invites, accounts, activity, settings].forEach((p: AsTab) => {
|
[window.invites, accounts, activity, settings].forEach((p: AsTab) => {
|
||||||
let t: { id: string; url: string; reloader: () => void; unloader?: () => void } = {
|
let t: { id: string; url: string; reloader: () => void; unloader?: () => void } = {
|
||||||
id: p.tabName,
|
id: p.tabName,
|
||||||
|
|||||||
@@ -940,7 +940,7 @@ class User extends TableRow implements UserDTO, SearchableItem {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UsersDTO extends paginatedDTO {
|
interface UsersDTO extends PaginatedDTO {
|
||||||
users: UserDTO[];
|
users: UserDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,9 +1054,8 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _inDetails: boolean = false;
|
private _inDetails: boolean = false;
|
||||||
details(username: string, jfId: string) {
|
details(jfId?: string) {
|
||||||
this.unbindPageEvents();
|
if (!jfId) jfId = this.users.keys().next().value;
|
||||||
console.debug("Loading details for ", username, jfId);
|
|
||||||
this._details.load(
|
this._details.load(
|
||||||
this.users.get(jfId),
|
this.users.get(jfId),
|
||||||
() => {
|
() => {
|
||||||
@@ -1066,17 +1065,28 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
this.processSelectedAccounts();
|
this.processSelectedAccounts();
|
||||||
this._table.classList.add("unfocused");
|
this._table.classList.add("unfocused");
|
||||||
this._details.hidden = false;
|
this._details.hidden = false;
|
||||||
|
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set("details", jfId);
|
||||||
|
window.history.pushState(null, "", url.toString());
|
||||||
},
|
},
|
||||||
() => {
|
this.closeDetails,
|
||||||
this._inDetails = false;
|
|
||||||
this.processSelectedAccounts();
|
|
||||||
this._details.hidden = true;
|
|
||||||
this._table.classList.remove("unfocused");
|
|
||||||
this.bindPageEvents();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeDetails = () => {
|
||||||
|
if (!this._inDetails) return;
|
||||||
|
this._inDetails = false;
|
||||||
|
this.processSelectedAccounts();
|
||||||
|
this._details.hidden = true;
|
||||||
|
this._table.classList.remove("unfocused");
|
||||||
|
this.bindPageEvents();
|
||||||
|
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete("details");
|
||||||
|
window.history.pushState(null, "", url.toString());
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
loader: document.getElementById("accounts-loader"),
|
loader: document.getElementById("accounts-loader"),
|
||||||
@@ -1095,7 +1105,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
getPageEndpoint: "/users",
|
getPageEndpoint: "/users",
|
||||||
itemsPerPage: 40,
|
itemsPerPage: 40,
|
||||||
maxItemsLoadedForSearch: 200,
|
maxItemsLoadedForSearch: 200,
|
||||||
appendNewItems: (resp: paginatedDTO) => {
|
appendNewItems: (resp: PaginatedDTO) => {
|
||||||
for (let u of (resp as UsersDTO).users || []) {
|
for (let u of (resp as UsersDTO).users || []) {
|
||||||
if (this.users.has(u.id)) {
|
if (this.users.has(u.id)) {
|
||||||
this.users.get(u.id).update(u);
|
this.users.get(u.id).update(u);
|
||||||
@@ -1110,7 +1120,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
this._search.ascending,
|
this._search.ascending,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
replaceWithNewItems: (resp: paginatedDTO) => {
|
replaceWithNewItems: (resp: PaginatedDTO) => {
|
||||||
let accountsOnDOM = new Map<string, boolean>();
|
let accountsOnDOM = new Map<string, boolean>();
|
||||||
|
|
||||||
for (let id of this.users.keys()) {
|
for (let id of this.users.keys()) {
|
||||||
@@ -1161,6 +1171,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
clearSearchButtonSelector: ".accounts-search-clear",
|
clearSearchButtonSelector: ".accounts-search-clear",
|
||||||
serverSearchButtonSelector: ".accounts-search-server",
|
serverSearchButtonSelector: ".accounts-search-server",
|
||||||
onSearchCallback: (_0: boolean, _1: boolean) => {
|
onSearchCallback: (_0: boolean, _1: boolean) => {
|
||||||
|
this.closeDetails();
|
||||||
this.processSelectedAccounts();
|
this.processSelectedAccounts();
|
||||||
},
|
},
|
||||||
searchServer: null,
|
searchServer: null,
|
||||||
@@ -1418,10 +1429,14 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
this._search.showHideSearchOptionsHeader();
|
this._search.showHideSearchOptionsHeader();
|
||||||
|
|
||||||
this.registerURLListener();
|
this.registerURLListener();
|
||||||
|
// FIXME: registerParamListener once PageManager is global
|
||||||
|
//
|
||||||
this._details = new UserInfo(document.getElementsByClassName("accounts-details")[0] as HTMLElement);
|
this._details = new UserInfo(document.getElementsByClassName("accounts-details")[0] as HTMLElement);
|
||||||
document.addEventListener("accounts-show-details", (ev: ShowDetailsEvent) => {
|
document.addEventListener("accounts-show-details", (ev: ShowDetailsEvent) => {
|
||||||
this.details(ev.detail.username, ev.detail.id);
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set("details", ev.detail.id);
|
||||||
|
window.history.pushState(null, "", url.toString());
|
||||||
|
// this.details(ev.detail.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get rid of nasty CSS
|
// Get rid of nasty CSS
|
||||||
@@ -1431,16 +1446,16 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
reload = (callback?: (resp: paginatedDTO) => void) => {
|
reload = (callback?: (resp: PaginatedDTO) => void) => {
|
||||||
this._reload(callback);
|
this._reload(callback);
|
||||||
this.loadTemplates();
|
this.loadTemplates();
|
||||||
};
|
};
|
||||||
|
|
||||||
loadMore = (loadAll: boolean = false, callback?: (resp?: paginatedDTO) => void) => {
|
loadMore = (loadAll: boolean = false, callback?: (resp?: PaginatedDTO) => void) => {
|
||||||
this._loadMore(loadAll, callback);
|
this._loadMore(loadAll, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
loadAll = (callback?: (resp?: PaginatedDTO) => void) => {
|
||||||
this._loadAll(callback);
|
this._loadAll(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1498,7 +1513,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (next == SelectAllState.All) {
|
if (next == SelectAllState.All) {
|
||||||
this.loadAll((_: paginatedDTO) => {
|
this.loadAll((_: PaginatedDTO) => {
|
||||||
if (!this.lastPage) {
|
if (!this.lastPage) {
|
||||||
// Pretend to live-select elements as they load.
|
// Pretend to live-select elements as they load.
|
||||||
this._counter.selected = this._counter.shown;
|
this._counter.selected = this._counter.shown;
|
||||||
@@ -2457,35 +2472,47 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
|
|
||||||
focusAccount = (userID: string) => {
|
focusAccount = (userID: string) => {
|
||||||
console.debug("focusing user", userID);
|
console.debug("focusing user", userID);
|
||||||
this._c.searchBox.value = `id:"${userID}"`;
|
this._search.setQueryParam(`id:"${userID}"`);
|
||||||
this._search.onSearchBoxChange();
|
|
||||||
this._search.onServerSearch();
|
|
||||||
if (userID in this.users) this.users.get(userID).focus();
|
if (userID in this.users) this.users.get(userID).focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly _accountURLEvent = "account-url";
|
public static readonly _accountURLEvent = "account-url";
|
||||||
registerURLListener = () =>
|
registerURLListener = () => {
|
||||||
document.addEventListener(accountsList._accountURLEvent, (event: CustomEvent) => {
|
document.addEventListener(accountsList._accountURLEvent, (event: CustomEvent) => {
|
||||||
this.focusAccount(event.detail);
|
this.focusAccount(event.detail);
|
||||||
});
|
});
|
||||||
|
window.tabs.pages.registerParamListener(
|
||||||
|
this.tabName,
|
||||||
|
(_: URLSearchParams) => {
|
||||||
|
this.navigate();
|
||||||
|
},
|
||||||
|
"user",
|
||||||
|
"details",
|
||||||
|
"search",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
isURL = (url?: string) => {
|
isURL = (url?: string) => {
|
||||||
const urlParams = new URLSearchParams(url || window.location.search);
|
const urlParams = new URLSearchParams(url || window.location.search);
|
||||||
const userID = urlParams.get("user");
|
const userID = urlParams.get("user");
|
||||||
return Boolean(userID) || this._search.isURL(url);
|
const details = urlParams.get("details");
|
||||||
|
return Boolean(userID) || Boolean(details) || this._search.isURL(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
navigate = (url?: string) => {
|
navigate = (url?: string) => {
|
||||||
const urlParams = new URLSearchParams(url || window.location.search);
|
const urlParams = new URLSearchParams(url || window.location.search);
|
||||||
const userID = urlParams.get("user");
|
const details = urlParams.get("details");
|
||||||
|
const userID = details || urlParams.get("user");
|
||||||
let search = urlParams.get("search") || "";
|
let search = urlParams.get("search") || "";
|
||||||
if (userID) {
|
if (userID) {
|
||||||
search = `id:${userID}" ` + search;
|
search = `id:"${userID}"`;
|
||||||
urlParams.set("search", search);
|
urlParams.set("search", search);
|
||||||
// Get rid of it, as it'll now be included in the "search" param anyway
|
// Get rid of it, as it'll now be included in the "search" param anyway
|
||||||
urlParams.delete("user");
|
urlParams.delete("user");
|
||||||
}
|
}
|
||||||
this._search.navigate(urlParams.toString());
|
this._search.navigate(urlParams.toString(), () => {
|
||||||
|
if (details) this.details(details);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2856,10 +2883,12 @@ class UserInfo extends PaginatedList {
|
|||||||
card.classList.add("unfocused");
|
card.classList.add("unfocused");
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<table class="table text-base leading-5">
|
<div class="overflow-x-scroll">
|
||||||
<thead class="user-info-table-header"></thead>
|
<table class="table text-base leading-5">
|
||||||
<tbody class="user-info-row"></tbody>
|
<thead class="user-info-table-header"></thead>
|
||||||
</table>
|
<tbody class="user-info-row"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="jf-activity-source flex flex-row justify-between gap-2">
|
<div class="jf-activity-source flex flex-row justify-between gap-2">
|
||||||
<h3 class="heading text-lg">${window.lang.strings("activityFromJF")}</h3>
|
<h3 class="heading text-lg">${window.lang.strings("activityFromJF")}</h3>
|
||||||
@@ -2907,7 +2936,7 @@ class UserInfo extends PaginatedList {
|
|||||||
maxItemsLoadedForSearch: 200,
|
maxItemsLoadedForSearch: 200,
|
||||||
disableSearch: true,
|
disableSearch: true,
|
||||||
hideButtonsOnLastPage: true,
|
hideButtonsOnLastPage: true,
|
||||||
appendNewItems: (resp: paginatedDTO) => {
|
appendNewItems: (resp: PaginatedDTO) => {
|
||||||
for (let entry of (resp as PaginatedActivityLogEntriesDTO).entries || []) {
|
for (let entry of (resp as PaginatedActivityLogEntriesDTO).entries || []) {
|
||||||
if (this.entries.has("" + entry.Id)) {
|
if (this.entries.has("" + entry.Id)) {
|
||||||
this.entries.get("" + entry.Id).update(null, entry);
|
this.entries.get("" + entry.Id).update(null, entry);
|
||||||
@@ -2918,7 +2947,7 @@ class UserInfo extends PaginatedList {
|
|||||||
|
|
||||||
this._search.setOrdering(Array.from(this.entries.keys()), "Date", true);
|
this._search.setOrdering(Array.from(this.entries.keys()), "Date", true);
|
||||||
},
|
},
|
||||||
replaceWithNewItems: (resp: paginatedDTO) => {
|
replaceWithNewItems: (resp: PaginatedDTO) => {
|
||||||
let entriesOnDOM = new Map<string, boolean>();
|
let entriesOnDOM = new Map<string, boolean>();
|
||||||
|
|
||||||
for (let id of this.entries.keys()) {
|
for (let id of this.entries.keys()) {
|
||||||
@@ -2973,15 +3002,15 @@ class UserInfo extends PaginatedList {
|
|||||||
this.entries.set("" + entry.Id, new ActivityLogEntry(this.username, entry));
|
this.entries.set("" + entry.Id, new ActivityLogEntry(this.username, entry));
|
||||||
};
|
};
|
||||||
|
|
||||||
loadMore = (loadAll: boolean = false, callback?: (resp?: paginatedDTO) => void) => {
|
loadMore = (loadAll: boolean = false, callback?: (resp?: PaginatedDTO) => void) => {
|
||||||
this._loadMore(loadAll, callback);
|
this._loadMore(loadAll, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
loadAll = (callback?: (resp?: PaginatedDTO) => void) => {
|
||||||
this._loadAll(callback);
|
this._loadAll(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
reload = (callback?: (resp: paginatedDTO) => void) => {
|
reload = (callback?: (resp: PaginatedDTO) => void) => {
|
||||||
this._reload(callback);
|
this._reload(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -554,7 +554,7 @@ interface ActivitiesReqDTO extends PaginatedReqDTO {
|
|||||||
type: string[];
|
type: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActivitiesDTO extends paginatedDTO {
|
interface ActivitiesDTO extends PaginatedDTO {
|
||||||
activities: activity[];
|
activities: activity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,7 +589,7 @@ export class activityList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
getPageEndpoint: "/activity",
|
getPageEndpoint: "/activity",
|
||||||
itemsPerPage: 20,
|
itemsPerPage: 20,
|
||||||
maxItemsLoadedForSearch: 200,
|
maxItemsLoadedForSearch: 200,
|
||||||
appendNewItems: (resp: paginatedDTO) => {
|
appendNewItems: (resp: PaginatedDTO) => {
|
||||||
let ordering: string[] = this._search.ordering;
|
let ordering: string[] = this._search.ordering;
|
||||||
for (let act of (resp as ActivitiesDTO).activities || []) {
|
for (let act of (resp as ActivitiesDTO).activities || []) {
|
||||||
this.activities.set(act.id, new Activity(act));
|
this.activities.set(act.id, new Activity(act));
|
||||||
@@ -597,7 +597,7 @@ export class activityList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
}
|
}
|
||||||
this._search.setOrdering(ordering, this._c.defaultSortField, this.ascending);
|
this._search.setOrdering(ordering, this._c.defaultSortField, this.ascending);
|
||||||
},
|
},
|
||||||
replaceWithNewItems: (resp: paginatedDTO) => {
|
replaceWithNewItems: (resp: PaginatedDTO) => {
|
||||||
// FIXME: Implement updates to existing elements, rather than just wiping each time.
|
// FIXME: Implement updates to existing elements, rather than just wiping each time.
|
||||||
|
|
||||||
// Remove existing items
|
// Remove existing items
|
||||||
@@ -645,7 +645,7 @@ export class activityList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
this._sortDirection.addEventListener("click", () => (this.ascending = !this.ascending));
|
this._sortDirection.addEventListener("click", () => (this.ascending = !this.ascending));
|
||||||
}
|
}
|
||||||
|
|
||||||
reload = (callback?: (resp: paginatedDTO) => void) => {
|
reload = (callback?: (resp: PaginatedDTO) => void) => {
|
||||||
this._reload(callback);
|
this._reload(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -653,7 +653,7 @@ export class activityList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
this._loadMore(loadAll, callback);
|
this._loadMore(loadAll, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
loadAll = (callback?: (resp?: PaginatedDTO) => void) => {
|
||||||
this._loadAll(callback);
|
this._loadAll(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ export interface PaginatedListConfig {
|
|||||||
getPageEndpoint: string | (() => string);
|
getPageEndpoint: string | (() => string);
|
||||||
itemsPerPage: number;
|
itemsPerPage: number;
|
||||||
maxItemsLoadedForSearch: number;
|
maxItemsLoadedForSearch: number;
|
||||||
appendNewItems: (resp: paginatedDTO) => void;
|
appendNewItems: (resp: PaginatedDTO) => void;
|
||||||
replaceWithNewItems: (resp: paginatedDTO) => void;
|
replaceWithNewItems: (resp: PaginatedDTO) => void;
|
||||||
defaultSortField?: string;
|
defaultSortField?: string;
|
||||||
defaultSortAscending?: boolean;
|
defaultSortAscending?: boolean;
|
||||||
pageLoadCallback?: (req: XMLHttpRequest) => void;
|
pageLoadCallback?: (req: XMLHttpRequest) => void;
|
||||||
@@ -231,7 +231,7 @@ export abstract class PaginatedList implements PageEventBindable {
|
|||||||
searchConfig.onSearchCallback = (
|
searchConfig.onSearchCallback = (
|
||||||
newItems: boolean,
|
newItems: boolean,
|
||||||
loadAll: boolean,
|
loadAll: boolean,
|
||||||
callback?: (resp: paginatedDTO) => void,
|
callback?: (resp: PaginatedDTO) => void,
|
||||||
) => {
|
) => {
|
||||||
// if (this._search.inSearch && !this.lastPage) this._c.loadAllButton.classList.remove("unfocused");
|
// if (this._search.inSearch && !this.lastPage) this._c.loadAllButton.classList.remove("unfocused");
|
||||||
// else this._c.loadAllButton.classList.add("unfocused");
|
// else this._c.loadAllButton.classList.add("unfocused");
|
||||||
@@ -258,12 +258,12 @@ export abstract class PaginatedList implements PageEventBindable {
|
|||||||
if (previousCallback) previousCallback(newItems, loadAll);
|
if (previousCallback) previousCallback(newItems, loadAll);
|
||||||
};
|
};
|
||||||
const previousServerSearch = searchConfig.searchServer;
|
const previousServerSearch = searchConfig.searchServer;
|
||||||
searchConfig.searchServer = (params: PaginatedReqDTO, newSearch: boolean) => {
|
searchConfig.searchServer = (params: PaginatedReqDTO, newSearch: boolean, then?: () => void) => {
|
||||||
this._searchParams = params;
|
this._searchParams = params;
|
||||||
if (newSearch) this.reload();
|
if (newSearch) this.reload(then);
|
||||||
else this.loadMore(false);
|
else this.loadMore(false, then);
|
||||||
|
|
||||||
if (previousServerSearch) previousServerSearch(params, newSearch);
|
if (previousServerSearch) previousServerSearch(params, newSearch, then);
|
||||||
};
|
};
|
||||||
searchConfig.clearServerSearch = () => {
|
searchConfig.clearServerSearch = () => {
|
||||||
console.trace("Clearing server search");
|
console.trace("Clearing server search");
|
||||||
@@ -362,9 +362,9 @@ export abstract class PaginatedList implements PageEventBindable {
|
|||||||
private _load = (
|
private _load = (
|
||||||
itemLimit: number,
|
itemLimit: number,
|
||||||
page: number,
|
page: number,
|
||||||
appendFunc: (resp: paginatedDTO) => void, // Function to append/put items in storage.
|
appendFunc: (resp: PaginatedDTO) => void, // Function to append/put items in storage.
|
||||||
pre?: (resp: paginatedDTO) => void,
|
pre?: (resp: PaginatedDTO) => void,
|
||||||
post?: (resp: paginatedDTO) => void,
|
post?: (resp: PaginatedDTO) => void,
|
||||||
failCallback?: (req: XMLHttpRequest) => void,
|
failCallback?: (req: XMLHttpRequest) => void,
|
||||||
) => {
|
) => {
|
||||||
this._lastLoad = Date.now();
|
this._lastLoad = Date.now();
|
||||||
@@ -388,7 +388,7 @@ export abstract class PaginatedList implements PageEventBindable {
|
|||||||
}
|
}
|
||||||
this._hasLoaded = true;
|
this._hasLoaded = true;
|
||||||
|
|
||||||
let resp = req.response as paginatedDTO;
|
let resp = req.response as PaginatedDTO;
|
||||||
if (pre) pre(resp);
|
if (pre) pre(resp);
|
||||||
|
|
||||||
this.lastPage = resp.last_page;
|
this.lastPage = resp.last_page;
|
||||||
@@ -406,8 +406,8 @@ export abstract class PaginatedList implements PageEventBindable {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Removes all elements, and reloads the first page.
|
// Removes all elements, and reloads the first page.
|
||||||
public abstract reload: (callback?: (resp: paginatedDTO) => void) => void;
|
public abstract reload: (callback?: (resp: PaginatedDTO) => void) => void;
|
||||||
protected _reload = (callback?: (resp: paginatedDTO) => void) => {
|
protected _reload = (callback?: (resp: PaginatedDTO) => void) => {
|
||||||
this.lastPage = false;
|
this.lastPage = false;
|
||||||
this._counter.reset();
|
this._counter.reset();
|
||||||
this._counter.getTotal(
|
this._counter.getTotal(
|
||||||
@@ -422,14 +422,14 @@ export abstract class PaginatedList implements PageEventBindable {
|
|||||||
limit,
|
limit,
|
||||||
0,
|
0,
|
||||||
this._c.replaceWithNewItems,
|
this._c.replaceWithNewItems,
|
||||||
(_0: paginatedDTO) => {
|
(_0: PaginatedDTO) => {
|
||||||
// Allow refreshes every 15s
|
// Allow refreshes every 15s
|
||||||
if (this._c.refreshButton) {
|
if (this._c.refreshButton) {
|
||||||
this._c.refreshButton.disabled = true;
|
this._c.refreshButton.disabled = true;
|
||||||
setTimeout(() => (this._c.refreshButton.disabled = false), 15000);
|
setTimeout(() => (this._c.refreshButton.disabled = false), 15000);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(resp: paginatedDTO) => {
|
(resp: PaginatedDTO) => {
|
||||||
this._search.onSearchBoxChange(true, false, false);
|
this._search.onSearchBoxChange(true, false, false);
|
||||||
if (this._search.inSearch) {
|
if (this._search.inSearch) {
|
||||||
// this._c.loadAllButton.classList.remove("unfocused");
|
// this._c.loadAllButton.classList.remove("unfocused");
|
||||||
@@ -444,8 +444,8 @@ export abstract class PaginatedList implements PageEventBindable {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Loads the next page. If "loadAll", all pages will be loaded until the last is reached.
|
// Loads the next page. If "loadAll", all pages will be loaded until the last is reached.
|
||||||
public abstract loadMore: (loadAll?: boolean, callback?: (resp?: paginatedDTO) => void) => void;
|
public abstract loadMore: (loadAll?: boolean, callback?: (resp?: PaginatedDTO) => void) => void;
|
||||||
protected _loadMore = (loadAll: boolean = false, callback?: (resp: paginatedDTO) => void) => {
|
protected _loadMore = (loadAll: boolean = false, callback?: (resp: PaginatedDTO) => void) => {
|
||||||
this._c.loadMoreButtons.forEach((v) => (v.disabled = true));
|
this._c.loadMoreButtons.forEach((v) => (v.disabled = true));
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
this._c.loadMoreButtons.forEach((v) => (v.disabled = false));
|
this._c.loadMoreButtons.forEach((v) => (v.disabled = false));
|
||||||
@@ -456,14 +456,14 @@ export abstract class PaginatedList implements PageEventBindable {
|
|||||||
this._c.itemsPerPage,
|
this._c.itemsPerPage,
|
||||||
this._page,
|
this._page,
|
||||||
this._c.appendNewItems,
|
this._c.appendNewItems,
|
||||||
(resp: paginatedDTO) => {
|
(resp: PaginatedDTO) => {
|
||||||
// Check before setting this.lastPage so we have a chance to cancel the timeout.
|
// Check before setting this.lastPage so we have a chance to cancel the timeout.
|
||||||
if (resp.last_page) {
|
if (resp.last_page) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
this._c.loadAllButtons.forEach((v) => removeLoader(v));
|
this._c.loadAllButtons.forEach((v) => removeLoader(v));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(resp: paginatedDTO) => {
|
(resp: PaginatedDTO) => {
|
||||||
if (this._search.inSearch || loadAll) {
|
if (this._search.inSearch || loadAll) {
|
||||||
if (this.lastPage) {
|
if (this.lastPage) {
|
||||||
loadAll = false;
|
loadAll = false;
|
||||||
@@ -481,8 +481,8 @@ export abstract class PaginatedList implements PageEventBindable {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
public abstract loadAll: (callback?: (resp?: paginatedDTO) => void) => void;
|
public abstract loadAll: (callback?: (resp?: PaginatedDTO) => void) => void;
|
||||||
protected _loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
protected _loadAll = (callback?: (resp?: PaginatedDTO) => void) => {
|
||||||
this._c.loadAllButtons.forEach((v) => {
|
this._c.loadAllButtons.forEach((v) => {
|
||||||
addLoader(v, true);
|
addLoader(v, true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,26 +1,19 @@
|
|||||||
export interface Page {
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
url: string;
|
|
||||||
show: () => boolean;
|
|
||||||
hide: () => boolean;
|
|
||||||
shouldSkip: () => boolean;
|
|
||||||
index?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PageConfig {
|
export interface PageConfig {
|
||||||
hideOthersOnPageShow: boolean;
|
hideOthersOnPageShow: boolean;
|
||||||
defaultName: string;
|
defaultName: string;
|
||||||
defaultTitle: string;
|
defaultTitle: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PageManager {
|
export class PageManager implements Pages {
|
||||||
pages: Map<string, Page>;
|
pages: Map<string, Page>;
|
||||||
pageList: string[];
|
pageList: string[];
|
||||||
hideOthers: boolean;
|
hideOthers: boolean;
|
||||||
defaultName: string = "";
|
defaultName: string = "";
|
||||||
defaultTitle: string = "";
|
defaultTitle: string = "";
|
||||||
|
|
||||||
|
private _listeners: Map<string, { params: string[]; func: (qp: URLSearchParams) => void }> = new Map();
|
||||||
|
private _previousParams = new URLSearchParams();
|
||||||
|
|
||||||
private _overridePushState = () => {
|
private _overridePushState = () => {
|
||||||
const pushState = window.history.pushState;
|
const pushState = window.history.pushState;
|
||||||
window.history.pushState = function (data: any, __: string, _: string | URL) {
|
window.history.pushState = function (data: any, __: string, _: string | URL) {
|
||||||
@@ -32,6 +25,8 @@ export class PageManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _onpopstate = (event: PopStateEvent) => {
|
private _onpopstate = (event: PopStateEvent) => {
|
||||||
|
const prevParams = this._previousParams;
|
||||||
|
this._previousParams = new URLSearchParams(window.location.search);
|
||||||
let name = event.state;
|
let name = event.state;
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
// Attempt to use hash from URL, if it isn't there, try the last part of the URL.
|
// Attempt to use hash from URL, if it isn't there, try the last part of the URL.
|
||||||
@@ -48,6 +43,14 @@ export class PageManager {
|
|||||||
if (!success) {
|
if (!success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this._listeners.has(name)) {
|
||||||
|
for (let qp of this._listeners.get(name).params) {
|
||||||
|
if (prevParams.get(qp) != this._previousParams.get(qp)) {
|
||||||
|
this._listeners.get(name).func(this._previousParams);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!this.hideOthers) {
|
if (!this.hideOthers) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -115,4 +118,17 @@ export class PageManager {
|
|||||||
}
|
}
|
||||||
this.loadPage(p);
|
this.loadPage(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Make PageManager global.
|
||||||
|
|
||||||
|
// registerParamListener allows registering a listener which will be called when one or many of the given query param names are changed. It will only be called once per navigation.
|
||||||
|
registerParamListener(pageName: string, func: (qp: URLSearchParams) => void, ...qps: string[]) {
|
||||||
|
const p: { params: string[]; func: (qp: URLSearchParams) => void } = this._listeners.get(pageName) || {
|
||||||
|
params: [],
|
||||||
|
func: null,
|
||||||
|
};
|
||||||
|
if (func) p.func = func;
|
||||||
|
p.params.push(...qps);
|
||||||
|
this._listeners.set(pageName, p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ export interface SearchConfiguration {
|
|||||||
search?: HTMLInputElement;
|
search?: HTMLInputElement;
|
||||||
queries: { [field: string]: QueryType };
|
queries: { [field: string]: QueryType };
|
||||||
setVisibility: (items: string[], visible: boolean, appendedItems: boolean) => void;
|
setVisibility: (items: string[], visible: boolean, appendedItems: boolean) => void;
|
||||||
onSearchCallback: (newItems: boolean, loadAll: boolean, callback?: (resp: paginatedDTO) => void) => void;
|
onSearchCallback: (newItems: boolean, loadAll: boolean, callback?: (resp: PaginatedDTO) => void) => void;
|
||||||
searchServer: (params: PaginatedReqDTO, newSearch: boolean) => void;
|
searchServer: (params: PaginatedReqDTO, newSearch: boolean, then?: () => void) => void;
|
||||||
clearServerSearch: () => void;
|
clearServerSearch: () => void;
|
||||||
loadMore?: () => void;
|
loadMore?: () => void;
|
||||||
}
|
}
|
||||||
@@ -556,7 +556,7 @@ export class Search implements Navigatable {
|
|||||||
newItems: boolean = false,
|
newItems: boolean = false,
|
||||||
appendedItems: boolean = false,
|
appendedItems: boolean = false,
|
||||||
loadAll: boolean = false,
|
loadAll: boolean = false,
|
||||||
callback?: (resp: paginatedDTO) => void,
|
callback?: (resp: PaginatedDTO) => void,
|
||||||
) => {
|
) => {
|
||||||
const query = this._c.search.value;
|
const query = this._c.search.value;
|
||||||
if (!query) {
|
if (!query) {
|
||||||
@@ -694,15 +694,15 @@ export class Search implements Navigatable {
|
|||||||
this._c.filterList.appendChild(filterListContainer);
|
this._c.filterList.appendChild(filterListContainer);
|
||||||
};
|
};
|
||||||
|
|
||||||
onServerSearch = () => {
|
onServerSearch = (then?: () => void) => {
|
||||||
const newServerSearch = !this.inServerSearch;
|
const newServerSearch = !this.inServerSearch;
|
||||||
this.inServerSearch = true;
|
this.inServerSearch = true;
|
||||||
this.setQueryParam();
|
// this.setQueryParam();
|
||||||
this.searchServer(newServerSearch);
|
this.searchServer(newServerSearch, then);
|
||||||
};
|
};
|
||||||
|
|
||||||
searchServer = (newServerSearch: boolean) => {
|
searchServer = (newServerSearch: boolean, then?: () => void) => {
|
||||||
this._c.searchServer(this.serverSearchParams(this._searchTerms, this._queries), newServerSearch);
|
this._c.searchServer(this.serverSearchParams(this._searchTerms, this._queries), newServerSearch, then);
|
||||||
};
|
};
|
||||||
|
|
||||||
serverSearchParams = (searchTerms: string[], queries: Query[]): PaginatedReqDTO => {
|
serverSearchParams = (searchTerms: string[], queries: Query[]): PaginatedReqDTO => {
|
||||||
@@ -721,13 +721,25 @@ export class Search implements Navigatable {
|
|||||||
return req;
|
return req;
|
||||||
};
|
};
|
||||||
|
|
||||||
// setQueryParam sets the ?search query param to the current searchbox content.
|
// setQueryParam sets the ?search query param to the current searchbox content,
|
||||||
setQueryParam = () => {
|
// or value if given. If everything is set up correctly, this should trigger a search when it is
|
||||||
|
// set to a new value.
|
||||||
|
setQueryParam = (value?: string) => {
|
||||||
|
let triggerManually = false;
|
||||||
|
if (value === undefined || value == null) value = this._c.search.value;
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
// FIXME: do better and make someone else clear this
|
// FIXME: do better and make someone else clear this
|
||||||
url.searchParams.delete("user");
|
if (value.trim()) {
|
||||||
url.searchParams.set("search", this._c.search.value);
|
url.searchParams.delete("user");
|
||||||
|
url.searchParams.set("search", value);
|
||||||
|
} else {
|
||||||
|
// If the query param is already blank, no change will mean no call to navigate()
|
||||||
|
triggerManually = !url.searchParams.has("search");
|
||||||
|
url.searchParams.delete("search");
|
||||||
|
}
|
||||||
|
console.log("pushing", url.toString());
|
||||||
window.history.pushState(null, "", url.toString());
|
window.history.pushState(null, "", url.toString());
|
||||||
|
if (triggerManually) this.navigate();
|
||||||
};
|
};
|
||||||
|
|
||||||
setServerSearchButtonsDisabled = (disabled: boolean) => {
|
setServerSearchButtonsDisabled = (disabled: boolean) => {
|
||||||
@@ -740,12 +752,15 @@ export class Search implements Navigatable {
|
|||||||
return Boolean(searchContent);
|
return Boolean(searchContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
navigate = (url?: string) => {
|
// navigate pulls the current "search" query param, puts it in the search box and searches it.
|
||||||
|
navigate = (url?: string, then?: () => void) => {
|
||||||
|
(window as any).s = this;
|
||||||
const urlParams = new URLSearchParams(url || window.location.search);
|
const urlParams = new URLSearchParams(url || window.location.search);
|
||||||
const searchContent = urlParams.get("search");
|
const searchContent = urlParams.get("search") || "";
|
||||||
|
console.log("navigate!, setting search box to ", searchContent);
|
||||||
this._c.search.value = searchContent;
|
this._c.search.value = searchContent;
|
||||||
this.onSearchBoxChange();
|
this.onSearchBoxChange();
|
||||||
this.onServerSearch();
|
this.onServerSearch(then);
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(c: SearchConfiguration) {
|
constructor(c: SearchConfiguration) {
|
||||||
@@ -761,7 +776,7 @@ export class Search implements Navigatable {
|
|||||||
};
|
};
|
||||||
this._c.search.addEventListener("keyup", (ev: KeyboardEvent) => {
|
this._c.search.addEventListener("keyup", (ev: KeyboardEvent) => {
|
||||||
if (ev.key == "Enter") {
|
if (ev.key == "Enter") {
|
||||||
this.onServerSearch();
|
this.setQueryParam();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -771,9 +786,8 @@ export class Search implements Navigatable {
|
|||||||
) as Array<HTMLSpanElement>;
|
) as Array<HTMLSpanElement>;
|
||||||
for (let b of clearSearchButtons) {
|
for (let b of clearSearchButtons) {
|
||||||
b.addEventListener("click", () => {
|
b.addEventListener("click", () => {
|
||||||
this._c.search.value = "";
|
|
||||||
this.inServerSearch = false;
|
this.inServerSearch = false;
|
||||||
this.onSearchBoxChange();
|
this.setQueryParam("");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -783,7 +797,7 @@ export class Search implements Navigatable {
|
|||||||
: [];
|
: [];
|
||||||
for (let b of this._serverSearchButtons) {
|
for (let b of this._serverSearchButtons) {
|
||||||
b.addEventListener("click", () => {
|
b.addEventListener("click", () => {
|
||||||
this.onServerSearch();
|
this.setQueryParam();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PageManager, Page } from "../modules/pages.js";
|
import { PageManager } from "../modules/pages.js";
|
||||||
|
|
||||||
export function isPageEventBindable(object: any): object is PageEventBindable {
|
export function isPageEventBindable(object: any): object is PageEventBindable {
|
||||||
return "bindPageEvents" in object;
|
return "bindPageEvents" in object;
|
||||||
@@ -8,15 +8,7 @@ export function isNavigatable(object: any): object is Navigatable {
|
|||||||
return "isURL" in object && "navigate" in object;
|
return "isURL" in object && "navigate" in object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Tab {
|
export class TabManager implements TabManager {
|
||||||
page: Page;
|
|
||||||
tabEl: HTMLDivElement;
|
|
||||||
buttonEl: HTMLSpanElement;
|
|
||||||
preFunc?: () => void;
|
|
||||||
postFunc?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Tabs implements Tabs {
|
|
||||||
private _current: string = "";
|
private _current: string = "";
|
||||||
private _baseOffset = -1;
|
private _baseOffset = -1;
|
||||||
tabs: Map<string, Tab>;
|
tabs: Map<string, Tab>;
|
||||||
|
|||||||
@@ -150,12 +150,6 @@ declare interface NotificationBox {
|
|||||||
customSuccess: (type: string, message: string) => void;
|
customSuccess: (type: string, message: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface Tabs {
|
|
||||||
current: string;
|
|
||||||
addTab: (tabID: string, url: string, preFunc?: () => void, postFunc?: () => void, unloadFunc?: () => void) => void;
|
|
||||||
switch: (tabID: string, noRun?: boolean, keepURL?: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface Modals {
|
declare interface Modals {
|
||||||
about: Modal;
|
about: Modal;
|
||||||
login: Modal;
|
login: Modal;
|
||||||
@@ -188,18 +182,58 @@ declare interface Modals {
|
|||||||
backups?: Modal;
|
backups?: Modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface paginatedDTO {
|
declare interface Page {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
show: () => boolean;
|
||||||
|
hide: () => boolean;
|
||||||
|
shouldSkip: () => boolean;
|
||||||
|
index?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface Tab {
|
||||||
|
page: Page;
|
||||||
|
tabEl: HTMLDivElement;
|
||||||
|
buttonEl: HTMLSpanElement;
|
||||||
|
preFunc?: () => void;
|
||||||
|
postFunc?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface Tabs {
|
||||||
|
tabs: Map<string, Tab>;
|
||||||
|
pages: Pages;
|
||||||
|
addTab(tabID: string, url: string, preFunc: () => void, postFunc: () => void, unloadFunc: () => void): void;
|
||||||
|
current: string;
|
||||||
|
switch(tabID: string, noRun?: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface Pages {
|
||||||
|
pages: Map<string, Page>;
|
||||||
|
pageList: string[];
|
||||||
|
hideOthers: boolean;
|
||||||
|
defaultName: string;
|
||||||
|
defaultTitle: string;
|
||||||
|
setPage(p: Page): void;
|
||||||
|
load(name?: string): void;
|
||||||
|
loadPage(p: Page): void;
|
||||||
|
prev(name?: string): void;
|
||||||
|
next(name?: string): void;
|
||||||
|
registerParamListener(pageName: string, func: (qp: URLSearchParams) => void, ...qps: string[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface PaginatedDTO {
|
||||||
last_page: boolean;
|
last_page: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PaginatedReqDTO {
|
declare interface PaginatedReqDTO {
|
||||||
limit: number;
|
limit: number;
|
||||||
page: number;
|
page: number;
|
||||||
sortByField: string;
|
sortByField: string;
|
||||||
ascending: boolean;
|
ascending: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DateAttempt {
|
declare interface DateAttempt {
|
||||||
year?: number;
|
year?: number;
|
||||||
month?: number;
|
month?: number;
|
||||||
day?: number;
|
day?: number;
|
||||||
@@ -208,7 +242,7 @@ interface DateAttempt {
|
|||||||
offsetMinutesFromUTC?: number;
|
offsetMinutesFromUTC?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParsedDate {
|
declare interface ParsedDate {
|
||||||
attempt: DateAttempt;
|
attempt: DateAttempt;
|
||||||
date: Date;
|
date: Date;
|
||||||
text: string;
|
text: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user