3308739619
tab content classes (e.g. settingsList, activityList) can implement "AsTab", "Navigatable" and or "PageEventBindable", the first giving them a tab name, a subpath and a reloader function, the second an "isURL" and "navigate" function for loading resources, the last giving them bind/unbindPageEvent methods. These are looped through in ts/admin.ts still crudely, maybe tabs.ts could accept "AsTab" implementers directly. "Search" class now has a ?search query param which just encodes the search box content, set when you perform a server search (hit enter or press the button). ?user queries from the accounts or activity tab will be converted to this form on loading.
113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
import { PageManager, Page } from "../modules/pages.js";
|
|
|
|
export function isPageEventBindable(object: any): object is PageEventBindable {
|
|
return "bindPageEvents" in object;
|
|
}
|
|
|
|
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 {
|
|
private _current: string = "";
|
|
private _baseOffset = -1;
|
|
tabs: Map<string, Tab>;
|
|
pages: PageManager;
|
|
|
|
constructor() {
|
|
this.tabs = new Map<string, Tab>();
|
|
this.pages = new PageManager({
|
|
hideOthersOnPageShow: true,
|
|
defaultName: "invites",
|
|
defaultTitle: document.title,
|
|
});
|
|
}
|
|
|
|
addTab = (
|
|
tabID: string,
|
|
url: string,
|
|
preFunc = () => void {},
|
|
postFunc = () => void {},
|
|
unloadFunc = () => void {},
|
|
) => {
|
|
let tab: Tab = {
|
|
page: null,
|
|
tabEl: document.getElementById("tab-" + tabID) as HTMLDivElement,
|
|
buttonEl: document.getElementById("button-tab-" + tabID) as HTMLButtonElement,
|
|
preFunc: preFunc,
|
|
postFunc: postFunc,
|
|
};
|
|
if (this._baseOffset == -1) {
|
|
this._baseOffset = tab.buttonEl.offsetLeft;
|
|
}
|
|
const order = Array.from(this.tabs.keys());
|
|
let scrollTo: () => number = (): number => tab.buttonEl.offsetLeft - this._baseOffset;
|
|
if (order.length > 0) {
|
|
scrollTo = (): number =>
|
|
tab.buttonEl.offsetLeft - (tab.buttonEl.parentElement.offsetWidth - tab.buttonEl.offsetWidth) / 2;
|
|
}
|
|
|
|
tab.page = {
|
|
name: tabID,
|
|
title: document.title /*FIXME: Get actual names from translations*/,
|
|
url: url,
|
|
show: () => {
|
|
tab.buttonEl.classList.add("active", "~urge");
|
|
tab.tabEl.classList.remove("unfocused");
|
|
tab.buttonEl.parentElement.scrollTo({
|
|
left: scrollTo(),
|
|
top: 0,
|
|
behavior: "auto",
|
|
});
|
|
document.dispatchEvent(new CustomEvent("tab-change", { detail: tabID }));
|
|
return true;
|
|
},
|
|
hide: () => {
|
|
tab.buttonEl.classList.remove("active");
|
|
tab.buttonEl.classList.remove("~urge");
|
|
tab.tabEl.classList.add("unfocused");
|
|
if (unloadFunc) unloadFunc();
|
|
return true;
|
|
},
|
|
shouldSkip: () => false,
|
|
};
|
|
this.pages.setPage(tab.page);
|
|
tab.buttonEl.onclick = () => {
|
|
this.switch(tabID);
|
|
};
|
|
this.tabs.set(tabID, tab);
|
|
};
|
|
|
|
get current(): string {
|
|
return this._current;
|
|
}
|
|
set current(tabID: string) {
|
|
this.switch(tabID);
|
|
}
|
|
|
|
switch = (tabID: string, noRun: boolean = false) => {
|
|
let t = this.tabs.get(tabID);
|
|
if (t == undefined) {
|
|
[t] = this.tabs.values();
|
|
}
|
|
|
|
this._current = t.page.name;
|
|
|
|
if (t.preFunc && !noRun) {
|
|
t.preFunc();
|
|
}
|
|
this.pages.load(tabID);
|
|
if (t.postFunc && !noRun) {
|
|
t.postFunc();
|
|
}
|
|
};
|
|
}
|