mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-01-18 16:47:42 +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 { lang, LangFile, loadLangSelector } from "./modules/lang.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 { accountsList } from "./modules/accounts.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 accounts = new accountsList();
|
||||
@@ -148,7 +151,6 @@ let navigated = false;
|
||||
|
||||
// load tabs
|
||||
const tabs: { id: string; url: string; reloader: () => void; unloader?: () => void }[] = [];
|
||||
window.tabs = new Tabs();
|
||||
[window.invites, accounts, activity, settings].forEach((p: AsTab) => {
|
||||
let t: { id: string; url: string; reloader: () => void; unloader?: () => void } = {
|
||||
id: p.tabName,
|
||||
|
||||
@@ -940,7 +940,7 @@ class User extends TableRow implements UserDTO, SearchableItem {
|
||||
};
|
||||
}
|
||||
|
||||
interface UsersDTO extends paginatedDTO {
|
||||
interface UsersDTO extends PaginatedDTO {
|
||||
users: UserDTO[];
|
||||
}
|
||||
|
||||
@@ -1054,9 +1054,8 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
||||
};
|
||||
|
||||
private _inDetails: boolean = false;
|
||||
details(username: string, jfId: string) {
|
||||
this.unbindPageEvents();
|
||||
console.debug("Loading details for ", username, jfId);
|
||||
details(jfId?: string) {
|
||||
if (!jfId) jfId = this.users.keys().next().value;
|
||||
this._details.load(
|
||||
this.users.get(jfId),
|
||||
() => {
|
||||
@@ -1066,17 +1065,28 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
||||
this.processSelectedAccounts();
|
||||
this._table.classList.add("unfocused");
|
||||
this._details.hidden = false;
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("details", jfId);
|
||||
window.history.pushState(null, "", url.toString());
|
||||
},
|
||||
() => {
|
||||
this._inDetails = false;
|
||||
this.processSelectedAccounts();
|
||||
this._details.hidden = true;
|
||||
this._table.classList.remove("unfocused");
|
||||
this.bindPageEvents();
|
||||
},
|
||||
this.closeDetails,
|
||||
);
|
||||
}
|
||||
|
||||
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() {
|
||||
super({
|
||||
loader: document.getElementById("accounts-loader"),
|
||||
@@ -1095,7 +1105,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
||||
getPageEndpoint: "/users",
|
||||
itemsPerPage: 40,
|
||||
maxItemsLoadedForSearch: 200,
|
||||
appendNewItems: (resp: paginatedDTO) => {
|
||||
appendNewItems: (resp: PaginatedDTO) => {
|
||||
for (let u of (resp as UsersDTO).users || []) {
|
||||
if (this.users.has(u.id)) {
|
||||
this.users.get(u.id).update(u);
|
||||
@@ -1110,7 +1120,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
||||
this._search.ascending,
|
||||
);
|
||||
},
|
||||
replaceWithNewItems: (resp: paginatedDTO) => {
|
||||
replaceWithNewItems: (resp: PaginatedDTO) => {
|
||||
let accountsOnDOM = new Map<string, boolean>();
|
||||
|
||||
for (let id of this.users.keys()) {
|
||||
@@ -1161,6 +1171,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
||||
clearSearchButtonSelector: ".accounts-search-clear",
|
||||
serverSearchButtonSelector: ".accounts-search-server",
|
||||
onSearchCallback: (_0: boolean, _1: boolean) => {
|
||||
this.closeDetails();
|
||||
this.processSelectedAccounts();
|
||||
},
|
||||
searchServer: null,
|
||||
@@ -1418,10 +1429,14 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
||||
this._search.showHideSearchOptionsHeader();
|
||||
|
||||
this.registerURLListener();
|
||||
|
||||
// FIXME: registerParamListener once PageManager is global
|
||||
//
|
||||
this._details = new UserInfo(document.getElementsByClassName("accounts-details")[0] as HTMLElement);
|
||||
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
|
||||
@@ -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.loadTemplates();
|
||||
};
|
||||
|
||||
loadMore = (loadAll: boolean = false, callback?: (resp?: paginatedDTO) => void) => {
|
||||
loadMore = (loadAll: boolean = false, callback?: (resp?: PaginatedDTO) => void) => {
|
||||
this._loadMore(loadAll, callback);
|
||||
};
|
||||
|
||||
loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
||||
loadAll = (callback?: (resp?: PaginatedDTO) => void) => {
|
||||
this._loadAll(callback);
|
||||
};
|
||||
|
||||
@@ -1498,7 +1513,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
||||
}
|
||||
|
||||
if (next == SelectAllState.All) {
|
||||
this.loadAll((_: paginatedDTO) => {
|
||||
this.loadAll((_: PaginatedDTO) => {
|
||||
if (!this.lastPage) {
|
||||
// Pretend to live-select elements as they load.
|
||||
this._counter.selected = this._counter.shown;
|
||||
@@ -2457,35 +2472,47 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
||||
|
||||
focusAccount = (userID: string) => {
|
||||
console.debug("focusing user", userID);
|
||||
this._c.searchBox.value = `id:"${userID}"`;
|
||||
this._search.onSearchBoxChange();
|
||||
this._search.onServerSearch();
|
||||
this._search.setQueryParam(`id:"${userID}"`);
|
||||
if (userID in this.users) this.users.get(userID).focus();
|
||||
};
|
||||
|
||||
public static readonly _accountURLEvent = "account-url";
|
||||
registerURLListener = () =>
|
||||
registerURLListener = () => {
|
||||
document.addEventListener(accountsList._accountURLEvent, (event: CustomEvent) => {
|
||||
this.focusAccount(event.detail);
|
||||
});
|
||||
window.tabs.pages.registerParamListener(
|
||||
this.tabName,
|
||||
(_: URLSearchParams) => {
|
||||
this.navigate();
|
||||
},
|
||||
"user",
|
||||
"details",
|
||||
"search",
|
||||
);
|
||||
};
|
||||
|
||||
isURL = (url?: string) => {
|
||||
const urlParams = new URLSearchParams(url || window.location.search);
|
||||
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) => {
|
||||
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") || "";
|
||||
if (userID) {
|
||||
search = `id:${userID}" ` + search;
|
||||
search = `id:"${userID}"`;
|
||||
urlParams.set("search", search);
|
||||
// Get rid of it, as it'll now be included in the "search" param anyway
|
||||
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.innerHTML = `
|
||||
<div class="flex flex-col gap-2">
|
||||
<table class="table text-base leading-5">
|
||||
<thead class="user-info-table-header"></thead>
|
||||
<tbody class="user-info-row"></tbody>
|
||||
</table>
|
||||
<div class="overflow-x-scroll">
|
||||
<table class="table text-base leading-5">
|
||||
<thead class="user-info-table-header"></thead>
|
||||
<tbody class="user-info-row"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="jf-activity-source flex flex-row justify-between gap-2">
|
||||
<h3 class="heading text-lg">${window.lang.strings("activityFromJF")}</h3>
|
||||
@@ -2907,7 +2936,7 @@ class UserInfo extends PaginatedList {
|
||||
maxItemsLoadedForSearch: 200,
|
||||
disableSearch: true,
|
||||
hideButtonsOnLastPage: true,
|
||||
appendNewItems: (resp: paginatedDTO) => {
|
||||
appendNewItems: (resp: PaginatedDTO) => {
|
||||
for (let entry of (resp as PaginatedActivityLogEntriesDTO).entries || []) {
|
||||
if (this.entries.has("" + entry.Id)) {
|
||||
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);
|
||||
},
|
||||
replaceWithNewItems: (resp: paginatedDTO) => {
|
||||
replaceWithNewItems: (resp: PaginatedDTO) => {
|
||||
let entriesOnDOM = new Map<string, boolean>();
|
||||
|
||||
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));
|
||||
};
|
||||
|
||||
loadMore = (loadAll: boolean = false, callback?: (resp?: paginatedDTO) => void) => {
|
||||
loadMore = (loadAll: boolean = false, callback?: (resp?: PaginatedDTO) => void) => {
|
||||
this._loadMore(loadAll, callback);
|
||||
};
|
||||
|
||||
loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
||||
loadAll = (callback?: (resp?: PaginatedDTO) => void) => {
|
||||
this._loadAll(callback);
|
||||
};
|
||||
|
||||
reload = (callback?: (resp: paginatedDTO) => void) => {
|
||||
reload = (callback?: (resp: PaginatedDTO) => void) => {
|
||||
this._reload(callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -554,7 +554,7 @@ interface ActivitiesReqDTO extends PaginatedReqDTO {
|
||||
type: string[];
|
||||
}
|
||||
|
||||
interface ActivitiesDTO extends paginatedDTO {
|
||||
interface ActivitiesDTO extends PaginatedDTO {
|
||||
activities: activity[];
|
||||
}
|
||||
|
||||
@@ -589,7 +589,7 @@ export class activityList extends PaginatedList implements Navigatable, AsTab {
|
||||
getPageEndpoint: "/activity",
|
||||
itemsPerPage: 20,
|
||||
maxItemsLoadedForSearch: 200,
|
||||
appendNewItems: (resp: paginatedDTO) => {
|
||||
appendNewItems: (resp: PaginatedDTO) => {
|
||||
let ordering: string[] = this._search.ordering;
|
||||
for (let act of (resp as ActivitiesDTO).activities || []) {
|
||||
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);
|
||||
},
|
||||
replaceWithNewItems: (resp: paginatedDTO) => {
|
||||
replaceWithNewItems: (resp: PaginatedDTO) => {
|
||||
// FIXME: Implement updates to existing elements, rather than just wiping each time.
|
||||
|
||||
// Remove existing items
|
||||
@@ -645,7 +645,7 @@ export class activityList extends PaginatedList implements Navigatable, AsTab {
|
||||
this._sortDirection.addEventListener("click", () => (this.ascending = !this.ascending));
|
||||
}
|
||||
|
||||
reload = (callback?: (resp: paginatedDTO) => void) => {
|
||||
reload = (callback?: (resp: PaginatedDTO) => void) => {
|
||||
this._reload(callback);
|
||||
};
|
||||
|
||||
@@ -653,7 +653,7 @@ export class activityList extends PaginatedList implements Navigatable, AsTab {
|
||||
this._loadMore(loadAll, callback);
|
||||
};
|
||||
|
||||
loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
||||
loadAll = (callback?: (resp?: PaginatedDTO) => void) => {
|
||||
this._loadAll(callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -101,8 +101,8 @@ export interface PaginatedListConfig {
|
||||
getPageEndpoint: string | (() => string);
|
||||
itemsPerPage: number;
|
||||
maxItemsLoadedForSearch: number;
|
||||
appendNewItems: (resp: paginatedDTO) => void;
|
||||
replaceWithNewItems: (resp: paginatedDTO) => void;
|
||||
appendNewItems: (resp: PaginatedDTO) => void;
|
||||
replaceWithNewItems: (resp: PaginatedDTO) => void;
|
||||
defaultSortField?: string;
|
||||
defaultSortAscending?: boolean;
|
||||
pageLoadCallback?: (req: XMLHttpRequest) => void;
|
||||
@@ -231,7 +231,7 @@ export abstract class PaginatedList implements PageEventBindable {
|
||||
searchConfig.onSearchCallback = (
|
||||
newItems: boolean,
|
||||
loadAll: boolean,
|
||||
callback?: (resp: paginatedDTO) => void,
|
||||
callback?: (resp: PaginatedDTO) => void,
|
||||
) => {
|
||||
// if (this._search.inSearch && !this.lastPage) this._c.loadAllButton.classList.remove("unfocused");
|
||||
// else this._c.loadAllButton.classList.add("unfocused");
|
||||
@@ -258,12 +258,12 @@ export abstract class PaginatedList implements PageEventBindable {
|
||||
if (previousCallback) previousCallback(newItems, loadAll);
|
||||
};
|
||||
const previousServerSearch = searchConfig.searchServer;
|
||||
searchConfig.searchServer = (params: PaginatedReqDTO, newSearch: boolean) => {
|
||||
searchConfig.searchServer = (params: PaginatedReqDTO, newSearch: boolean, then?: () => void) => {
|
||||
this._searchParams = params;
|
||||
if (newSearch) this.reload();
|
||||
else this.loadMore(false);
|
||||
if (newSearch) this.reload(then);
|
||||
else this.loadMore(false, then);
|
||||
|
||||
if (previousServerSearch) previousServerSearch(params, newSearch);
|
||||
if (previousServerSearch) previousServerSearch(params, newSearch, then);
|
||||
};
|
||||
searchConfig.clearServerSearch = () => {
|
||||
console.trace("Clearing server search");
|
||||
@@ -362,9 +362,9 @@ export abstract class PaginatedList implements PageEventBindable {
|
||||
private _load = (
|
||||
itemLimit: number,
|
||||
page: number,
|
||||
appendFunc: (resp: paginatedDTO) => void, // Function to append/put items in storage.
|
||||
pre?: (resp: paginatedDTO) => void,
|
||||
post?: (resp: paginatedDTO) => void,
|
||||
appendFunc: (resp: PaginatedDTO) => void, // Function to append/put items in storage.
|
||||
pre?: (resp: PaginatedDTO) => void,
|
||||
post?: (resp: PaginatedDTO) => void,
|
||||
failCallback?: (req: XMLHttpRequest) => void,
|
||||
) => {
|
||||
this._lastLoad = Date.now();
|
||||
@@ -388,7 +388,7 @@ export abstract class PaginatedList implements PageEventBindable {
|
||||
}
|
||||
this._hasLoaded = true;
|
||||
|
||||
let resp = req.response as paginatedDTO;
|
||||
let resp = req.response as PaginatedDTO;
|
||||
if (pre) pre(resp);
|
||||
|
||||
this.lastPage = resp.last_page;
|
||||
@@ -406,8 +406,8 @@ export abstract class PaginatedList implements PageEventBindable {
|
||||
};
|
||||
|
||||
// Removes all elements, and reloads the first page.
|
||||
public abstract reload: (callback?: (resp: paginatedDTO) => void) => void;
|
||||
protected _reload = (callback?: (resp: paginatedDTO) => void) => {
|
||||
public abstract reload: (callback?: (resp: PaginatedDTO) => void) => void;
|
||||
protected _reload = (callback?: (resp: PaginatedDTO) => void) => {
|
||||
this.lastPage = false;
|
||||
this._counter.reset();
|
||||
this._counter.getTotal(
|
||||
@@ -422,14 +422,14 @@ export abstract class PaginatedList implements PageEventBindable {
|
||||
limit,
|
||||
0,
|
||||
this._c.replaceWithNewItems,
|
||||
(_0: paginatedDTO) => {
|
||||
(_0: PaginatedDTO) => {
|
||||
// Allow refreshes every 15s
|
||||
if (this._c.refreshButton) {
|
||||
this._c.refreshButton.disabled = true;
|
||||
setTimeout(() => (this._c.refreshButton.disabled = false), 15000);
|
||||
}
|
||||
},
|
||||
(resp: paginatedDTO) => {
|
||||
(resp: PaginatedDTO) => {
|
||||
this._search.onSearchBoxChange(true, false, false);
|
||||
if (this._search.inSearch) {
|
||||
// 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.
|
||||
public abstract loadMore: (loadAll?: boolean, callback?: (resp?: paginatedDTO) => void) => void;
|
||||
protected _loadMore = (loadAll: boolean = false, callback?: (resp: paginatedDTO) => void) => {
|
||||
public abstract loadMore: (loadAll?: boolean, callback?: (resp?: PaginatedDTO) => void) => void;
|
||||
protected _loadMore = (loadAll: boolean = false, callback?: (resp: PaginatedDTO) => void) => {
|
||||
this._c.loadMoreButtons.forEach((v) => (v.disabled = true));
|
||||
const timeout = setTimeout(() => {
|
||||
this._c.loadMoreButtons.forEach((v) => (v.disabled = false));
|
||||
@@ -456,14 +456,14 @@ export abstract class PaginatedList implements PageEventBindable {
|
||||
this._c.itemsPerPage,
|
||||
this._page,
|
||||
this._c.appendNewItems,
|
||||
(resp: paginatedDTO) => {
|
||||
(resp: PaginatedDTO) => {
|
||||
// Check before setting this.lastPage so we have a chance to cancel the timeout.
|
||||
if (resp.last_page) {
|
||||
clearTimeout(timeout);
|
||||
this._c.loadAllButtons.forEach((v) => removeLoader(v));
|
||||
}
|
||||
},
|
||||
(resp: paginatedDTO) => {
|
||||
(resp: PaginatedDTO) => {
|
||||
if (this._search.inSearch || loadAll) {
|
||||
if (this.lastPage) {
|
||||
loadAll = false;
|
||||
@@ -481,8 +481,8 @@ export abstract class PaginatedList implements PageEventBindable {
|
||||
);
|
||||
};
|
||||
|
||||
public abstract loadAll: (callback?: (resp?: paginatedDTO) => void) => void;
|
||||
protected _loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
||||
public abstract loadAll: (callback?: (resp?: PaginatedDTO) => void) => void;
|
||||
protected _loadAll = (callback?: (resp?: PaginatedDTO) => void) => {
|
||||
this._c.loadAllButtons.forEach((v) => {
|
||||
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 {
|
||||
hideOthersOnPageShow: boolean;
|
||||
defaultName: string;
|
||||
defaultTitle: string;
|
||||
}
|
||||
|
||||
export class PageManager {
|
||||
export class PageManager implements Pages {
|
||||
pages: Map<string, Page>;
|
||||
pageList: string[];
|
||||
hideOthers: boolean;
|
||||
defaultName: string = "";
|
||||
defaultTitle: string = "";
|
||||
|
||||
private _listeners: Map<string, { params: string[]; func: (qp: URLSearchParams) => void }> = new Map();
|
||||
private _previousParams = new URLSearchParams();
|
||||
|
||||
private _overridePushState = () => {
|
||||
const pushState = window.history.pushState;
|
||||
window.history.pushState = function (data: any, __: string, _: string | URL) {
|
||||
@@ -32,6 +25,8 @@ export class PageManager {
|
||||
};
|
||||
|
||||
private _onpopstate = (event: PopStateEvent) => {
|
||||
const prevParams = this._previousParams;
|
||||
this._previousParams = new URLSearchParams(window.location.search);
|
||||
let name = event.state;
|
||||
if (name == null) {
|
||||
// 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) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
@@ -115,4 +118,17 @@ export class PageManager {
|
||||
}
|
||||
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;
|
||||
queries: { [field: string]: QueryType };
|
||||
setVisibility: (items: string[], visible: boolean, appendedItems: boolean) => void;
|
||||
onSearchCallback: (newItems: boolean, loadAll: boolean, callback?: (resp: paginatedDTO) => void) => void;
|
||||
searchServer: (params: PaginatedReqDTO, newSearch: boolean) => void;
|
||||
onSearchCallback: (newItems: boolean, loadAll: boolean, callback?: (resp: PaginatedDTO) => void) => void;
|
||||
searchServer: (params: PaginatedReqDTO, newSearch: boolean, then?: () => void) => void;
|
||||
clearServerSearch: () => void;
|
||||
loadMore?: () => void;
|
||||
}
|
||||
@@ -556,7 +556,7 @@ export class Search implements Navigatable {
|
||||
newItems: boolean = false,
|
||||
appendedItems: boolean = false,
|
||||
loadAll: boolean = false,
|
||||
callback?: (resp: paginatedDTO) => void,
|
||||
callback?: (resp: PaginatedDTO) => void,
|
||||
) => {
|
||||
const query = this._c.search.value;
|
||||
if (!query) {
|
||||
@@ -694,15 +694,15 @@ export class Search implements Navigatable {
|
||||
this._c.filterList.appendChild(filterListContainer);
|
||||
};
|
||||
|
||||
onServerSearch = () => {
|
||||
onServerSearch = (then?: () => void) => {
|
||||
const newServerSearch = !this.inServerSearch;
|
||||
this.inServerSearch = true;
|
||||
this.setQueryParam();
|
||||
this.searchServer(newServerSearch);
|
||||
// this.setQueryParam();
|
||||
this.searchServer(newServerSearch, then);
|
||||
};
|
||||
|
||||
searchServer = (newServerSearch: boolean) => {
|
||||
this._c.searchServer(this.serverSearchParams(this._searchTerms, this._queries), newServerSearch);
|
||||
searchServer = (newServerSearch: boolean, then?: () => void) => {
|
||||
this._c.searchServer(this.serverSearchParams(this._searchTerms, this._queries), newServerSearch, then);
|
||||
};
|
||||
|
||||
serverSearchParams = (searchTerms: string[], queries: Query[]): PaginatedReqDTO => {
|
||||
@@ -721,13 +721,25 @@ export class Search implements Navigatable {
|
||||
return req;
|
||||
};
|
||||
|
||||
// setQueryParam sets the ?search query param to the current searchbox content.
|
||||
setQueryParam = () => {
|
||||
// setQueryParam sets the ?search query param to the current searchbox content,
|
||||
// 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);
|
||||
// FIXME: do better and make someone else clear this
|
||||
url.searchParams.delete("user");
|
||||
url.searchParams.set("search", this._c.search.value);
|
||||
if (value.trim()) {
|
||||
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());
|
||||
if (triggerManually) this.navigate();
|
||||
};
|
||||
|
||||
setServerSearchButtonsDisabled = (disabled: boolean) => {
|
||||
@@ -740,12 +752,15 @@ export class Search implements Navigatable {
|
||||
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 searchContent = urlParams.get("search");
|
||||
const searchContent = urlParams.get("search") || "";
|
||||
console.log("navigate!, setting search box to ", searchContent);
|
||||
this._c.search.value = searchContent;
|
||||
this.onSearchBoxChange();
|
||||
this.onServerSearch();
|
||||
this.onServerSearch(then);
|
||||
};
|
||||
|
||||
constructor(c: SearchConfiguration) {
|
||||
@@ -761,7 +776,7 @@ export class Search implements Navigatable {
|
||||
};
|
||||
this._c.search.addEventListener("keyup", (ev: KeyboardEvent) => {
|
||||
if (ev.key == "Enter") {
|
||||
this.onServerSearch();
|
||||
this.setQueryParam();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -771,9 +786,8 @@ export class Search implements Navigatable {
|
||||
) as Array<HTMLSpanElement>;
|
||||
for (let b of clearSearchButtons) {
|
||||
b.addEventListener("click", () => {
|
||||
this._c.search.value = "";
|
||||
this.inServerSearch = false;
|
||||
this.onSearchBoxChange();
|
||||
this.setQueryParam("");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -783,7 +797,7 @@ export class Search implements Navigatable {
|
||||
: [];
|
||||
for (let b of this._serverSearchButtons) {
|
||||
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 {
|
||||
return "bindPageEvents" in object;
|
||||
@@ -8,15 +8,7 @@ export function isNavigatable(object: any): object is Navigatable {
|
||||
return "isURL" in object && "navigate" in object;
|
||||
}
|
||||
|
||||
export interface Tab {
|
||||
page: Page;
|
||||
tabEl: HTMLDivElement;
|
||||
buttonEl: HTMLSpanElement;
|
||||
preFunc?: () => void;
|
||||
postFunc?: () => void;
|
||||
}
|
||||
|
||||
export class Tabs implements Tabs {
|
||||
export class TabManager implements TabManager {
|
||||
private _current: string = "";
|
||||
private _baseOffset = -1;
|
||||
tabs: Map<string, Tab>;
|
||||
|
||||
@@ -150,12 +150,6 @@ declare interface NotificationBox {
|
||||
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 {
|
||||
about: Modal;
|
||||
login: Modal;
|
||||
@@ -188,18 +182,58 @@ declare interface Modals {
|
||||
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;
|
||||
}
|
||||
|
||||
interface PaginatedReqDTO {
|
||||
declare interface PaginatedReqDTO {
|
||||
limit: number;
|
||||
page: number;
|
||||
sortByField: string;
|
||||
ascending: boolean;
|
||||
}
|
||||
|
||||
interface DateAttempt {
|
||||
declare interface DateAttempt {
|
||||
year?: number;
|
||||
month?: number;
|
||||
day?: number;
|
||||
@@ -208,7 +242,7 @@ interface DateAttempt {
|
||||
offsetMinutesFromUTC?: number;
|
||||
}
|
||||
|
||||
interface ParsedDate {
|
||||
declare interface ParsedDate {
|
||||
attempt: DateAttempt;
|
||||
date: Date;
|
||||
text: string;
|
||||
|
||||
Reference in New Issue
Block a user