diff --git a/ts/admin.ts b/ts/admin.ts index e7fee76..b89996f 100644 --- a/ts/admin.ts +++ b/ts/admin.ts @@ -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, diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts index f20258c..41c5bc8 100644 --- a/ts/modules/accounts.ts +++ b/ts/modules/accounts.ts @@ -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(); 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 = `
- - - -
+
+ + + +
+

${window.lang.strings("activityFromJF")}

@@ -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(); 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); }; diff --git a/ts/modules/activity.ts b/ts/modules/activity.ts index 4720170..4284239 100644 --- a/ts/modules/activity.ts +++ b/ts/modules/activity.ts @@ -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); }; diff --git a/ts/modules/list.ts b/ts/modules/list.ts index 93cf1a4..ffd8a68 100644 --- a/ts/modules/list.ts +++ b/ts/modules/list.ts @@ -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); }); diff --git a/ts/modules/pages.ts b/ts/modules/pages.ts index 2af2edc..3a320bf 100644 --- a/ts/modules/pages.ts +++ b/ts/modules/pages.ts @@ -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; pageList: string[]; hideOthers: boolean; defaultName: string = ""; defaultTitle: string = ""; + private _listeners: Map 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); + } } diff --git a/ts/modules/search.ts b/ts/modules/search.ts index b4f8226..f16aac1 100644 --- a/ts/modules/search.ts +++ b/ts/modules/search.ts @@ -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; 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(); }); } } diff --git a/ts/modules/tabs.ts b/ts/modules/tabs.ts index 64b1c0a..efe2b0c 100644 --- a/ts/modules/tabs.ts +++ b/ts/modules/tabs.ts @@ -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; diff --git a/ts/typings/d.ts b/ts/typings/d.ts index 62603c3..685c155 100644 --- a/ts/typings/d.ts +++ b/ts/typings/d.ts @@ -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; + 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; + 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;