ts: use pages modules in admin (kinda), change pseudo-links
pseudo-links are now just links, because i'm lazy and it's easier than fixing an issue. They now take the form `/?invite=code` and `/accounts/?user=userid`. ts/modules.tabs.ts is now a wrapper for ts/modules/pages.ts. Also, fixed no section appearing when visiting the settings tab.
This commit is contained in:
+37
-65
@@ -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 } from "./modules/tabs.js";
|
||||
import { Tabs, Tab } from "./modules/tabs.js";
|
||||
import { inviteList, createInvite } from "./modules/invites.js";
|
||||
import { accountsList } from "./modules/accounts.js";
|
||||
import { settingsList } from "./modules/settings.js";
|
||||
@@ -120,21 +120,41 @@ window.notifications = new notificationBox(document.getElementById('notification
|
||||
userSelect.classList.toggle('unfocused');
|
||||
}*/
|
||||
|
||||
// Determine if url references an invite or account
|
||||
let isInviteURL = window.invites.isInviteURL();
|
||||
let isAccountURL = accounts.isAccountURL();
|
||||
|
||||
// load tabs
|
||||
const tabs: { url: string, reloader: () => void }[] = [
|
||||
const tabs: { id: string, url: string, reloader: () => void }[] = [
|
||||
{
|
||||
url: "invites",
|
||||
reloader: window.invites.reload
|
||||
id: "invites",
|
||||
url: "",
|
||||
reloader: () => window.invites.reload(() => {
|
||||
if (isInviteURL) {
|
||||
window.invites.loadInviteURL();
|
||||
// Don't keep loading the same item on every tab refresh
|
||||
isInviteURL = false;
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "accounts",
|
||||
url: "accounts",
|
||||
reloader: accounts.reload
|
||||
reloader: () => accounts.reload(() => {
|
||||
if (isAccountURL) {
|
||||
accounts.loadAccountURL();
|
||||
// Don't keep loading the same item on every tab refresh
|
||||
isAccountURL = false;
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "activity",
|
||||
url: "activity",
|
||||
reloader: activity.reload
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
url: "settings",
|
||||
reloader: settings.reload
|
||||
}
|
||||
@@ -145,41 +165,20 @@ const defaultTab = tabs[0];
|
||||
window.tabs = new Tabs();
|
||||
|
||||
for (let tab of tabs) {
|
||||
window.tabs.addTab(tab.url, null, tab.reloader);
|
||||
if (window.location.pathname == window.URLBase + "/" + tab.url) {
|
||||
window.tabs.addTab(tab.id, tab.url, null, tab.reloader);
|
||||
}
|
||||
|
||||
let matchedTab = false
|
||||
for (let tab of tabs) {
|
||||
if (window.location.pathname.startsWith(window.URLBase + "/" + tab.url)) {
|
||||
window.tabs.switch(tab.url, true);
|
||||
matchedTab = true;
|
||||
}
|
||||
}
|
||||
|
||||
let isInviteURL = window.invites.isInviteURL();
|
||||
let isAccountURL = accounts.isAccountURL();
|
||||
|
||||
// Default tab
|
||||
if ((window.URLBase + "/").includes(window.location.pathname)) {
|
||||
window.tabs.switch(defaultTab.url, true);
|
||||
}
|
||||
|
||||
document.addEventListener("tab-change", (event: CustomEvent) => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const lang = urlParams.get('lang');
|
||||
let tab = window.URLBase + "/" + event.detail;
|
||||
if (event.detail == "") {
|
||||
tab = window.location.pathname;
|
||||
} else if (tab == window.URLBase + "/invites") {
|
||||
if (window.location.pathname == window.URLBase + "/") {
|
||||
tab = window.URLBase + "/";
|
||||
} else if (window.URLBase) { tab = window.URLBase; }
|
||||
else { tab = "../"; }
|
||||
}
|
||||
if (lang) {
|
||||
tab += "?lang=" + lang
|
||||
}
|
||||
window.history.pushState(event.detail, "Admin - jfa-go", tab);
|
||||
});
|
||||
|
||||
window.onpopstate = (event: PopStateEvent) => {
|
||||
console.log(event.state);
|
||||
window.tabs.switch(event.state);
|
||||
// if ((window.URLBase + "/").includes(window.location.pathname)) {
|
||||
if (!matchedTab) {
|
||||
window.tabs.switch("", true);
|
||||
}
|
||||
|
||||
const login = new Login(window.modals.login as Modal, "/", window.loginAppearance);
|
||||
@@ -189,35 +188,8 @@ login.onLogin = () => {
|
||||
// FIXME: Decide whether to autoload activity or not
|
||||
reloadProfileNames();
|
||||
setInterval(() => { window.invites.reload(); accounts.reload(); }, 30*1000);
|
||||
const currentTab = window.tabs.current;
|
||||
switch (currentTab) {
|
||||
case "invites":
|
||||
window.invites.reload();
|
||||
break;
|
||||
case "accounts":
|
||||
accounts.reload();
|
||||
break;
|
||||
case "settings":
|
||||
settings.reload();
|
||||
break;
|
||||
case "activity": // FIXME: fix URL clash with route
|
||||
activity.reload();
|
||||
break;
|
||||
default:
|
||||
console.log(isAccountURL, isInviteURL);
|
||||
if (isInviteURL) {
|
||||
window.invites.reload(() => {
|
||||
window.invites.loadInviteURL();
|
||||
window.tabs.switch("invites", false, true);
|
||||
});
|
||||
} else if (isAccountURL) {
|
||||
accounts.reload(() => {
|
||||
accounts.loadAccountURL();
|
||||
window.tabs.switch("accounts", false, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Triggers pre and post funcs, even though we're already on that page
|
||||
window.tabs.switch(window.tabs.current);
|
||||
}
|
||||
|
||||
bindManualDropdowns();
|
||||
|
||||
@@ -1804,10 +1804,16 @@ export class accountsList {
|
||||
this.focusAccount(event.detail);
|
||||
});
|
||||
|
||||
isAccountURL = () => { return window.location.pathname.startsWith(window.URLBase + "/accounts/user/"); }
|
||||
// FIXME: Use Query Param! so it doesn't get cleared by pages.ts.
|
||||
isAccountURL = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const userID = urlParams.get("user");
|
||||
return Boolean(userID);
|
||||
}
|
||||
|
||||
loadAccountURL = () => {
|
||||
let userID = window.location.pathname.split(window.URLBase + "/accounts/user/")[1].split("?lang")[0];
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const userID = urlParams.get("user");
|
||||
this.focusAccount(userID);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,17 +64,17 @@ export class Activity implements activity, SearchableItem {
|
||||
}
|
||||
|
||||
_genUserLink = (): string => {
|
||||
return `<span role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" data-href="${this._urlBase}accounts/user/${this._act.user_id}">${this._genUserText()}</span>`;
|
||||
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${this._urlBase}accounts?user=${this._act.user_id}">${this._genUserText()}</a>`;
|
||||
}
|
||||
|
||||
_genSrcUserLink = (): string => {
|
||||
return `<span role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" data-href="${this._urlBase}accounts/user/${this._act.source}">${this._genSrcUserText()}</span>`;
|
||||
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${this._urlBase}accounts?user=${this._act.source}">${this._genSrcUserText()}</a>`;
|
||||
}
|
||||
|
||||
private _renderInvText = (): string => { return `<span class="font-medium font-mono">${this.value || this.invite_code || "???"}</span>`; }
|
||||
|
||||
private _genInvLink = (): string => {
|
||||
return `<span role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-invite" data-id="${this.invite_code}" data-href="${this._urlBase}invites/${this.invite_code}">${this._renderInvText()}</span>`;
|
||||
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-invite" data-id="${this.invite_code}" href="${this._urlBase}?invite=${this.invite_code}">${this._renderInvText()}</a>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -307,17 +307,17 @@ export class Activity implements activity, SearchableItem {
|
||||
const pseudoInvites = this._card.getElementsByClassName("activity-pseudo-link-invite") as HTMLCollectionOf<HTMLAnchorElement>;
|
||||
|
||||
for (let i = 0; i < pseudoUsers.length; i++) {
|
||||
const navigate = (event: Event) => {
|
||||
/*const navigate = (event: Event) => {
|
||||
event.preventDefault()
|
||||
window.tabs.switch("accounts");
|
||||
document.dispatchEvent(accountURLEvent(pseudoUsers[i].getAttribute("data-id")));
|
||||
window.history.pushState(null, document.title, pseudoUsers[i].getAttribute("data-href"));
|
||||
}
|
||||
pseudoUsers[i].onclick = navigate;
|
||||
pseudoUsers[i].onkeydown = navigate;
|
||||
pseudoUsers[i].onkeydown = navigate;*/
|
||||
}
|
||||
for (let i = 0; i < pseudoInvites.length; i++) {
|
||||
const navigate = (event: Event) => {
|
||||
/*const navigate = (event: Event) => {
|
||||
event.preventDefault();
|
||||
window.invites.reload(() => {
|
||||
window.tabs.switch("invites");
|
||||
@@ -326,7 +326,7 @@ export class Activity implements activity, SearchableItem {
|
||||
});
|
||||
}
|
||||
pseudoInvites[i].onclick = navigate;
|
||||
pseudoInvites[i].onkeydown = navigate;
|
||||
pseudoInvites[i].onkeydown = navigate;*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -448,10 +448,15 @@ export class inviteList implements inviteList {
|
||||
this.focusInvite(event.detail);
|
||||
})
|
||||
|
||||
isInviteURL = () => { return window.location.pathname.startsWith(window.URLBase + "/invites/"); }
|
||||
isInviteURL = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const inviteCode = urlParams.get("invite");
|
||||
return Boolean(inviteCode);
|
||||
}
|
||||
|
||||
loadInviteURL = () => {
|
||||
let inviteCode = window.location.pathname.split(window.URLBase + "/invites/")[1].split("?lang")[0];
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const inviteCode = urlParams.get("invite");
|
||||
this.focusInvite(inviteCode, window.lang.notif("errorInviteNotFound"));
|
||||
}
|
||||
|
||||
|
||||
+13
-19
@@ -32,24 +32,21 @@ export class PageManager {
|
||||
|
||||
private _onpopstate = (event: PopStateEvent) => {
|
||||
let name = event.state;
|
||||
if (!(event.state in this.pages)) {
|
||||
if (!this.pages.has(event.state)) {
|
||||
name = this.pageList[0]
|
||||
}
|
||||
let success = this.pages[name].show();
|
||||
let success = this.pages.get(name).show();
|
||||
if (!success) {
|
||||
console.log("failed");
|
||||
return;
|
||||
}
|
||||
if (!(this.hideOthers)) {
|
||||
console.log("shoudln't hide others");
|
||||
return;
|
||||
}
|
||||
for (let k of this.pageList) {
|
||||
if (name != k) {
|
||||
this.pages[k].hide();
|
||||
this.pages.get(k).hide();
|
||||
}
|
||||
}
|
||||
console.log("loop ended", this);
|
||||
}
|
||||
|
||||
constructor(c: PageConfig) {
|
||||
@@ -65,42 +62,39 @@ export class PageManager {
|
||||
|
||||
setPage(p: Page) {
|
||||
p.index = this.pageList.length;
|
||||
this.pages[p.name] = p;
|
||||
this.pages.set(p.name, p);
|
||||
this.pageList.push(p.name);
|
||||
}
|
||||
|
||||
load(name: string = "") {
|
||||
if (!(name in this.pages)) return window.history.pushState(name || this.defaultName, this.defaultTitle, "")
|
||||
const p = this.pages[name];
|
||||
if (!this.pages.has(name)) return window.history.pushState(name || this.defaultName, this.defaultTitle, "")
|
||||
const p = this.pages.get(name);
|
||||
this.loadPage(p);
|
||||
}
|
||||
|
||||
loadPage (p: Page) {
|
||||
window.history.pushState(p.name || this.defaultName, p.title, p.url);
|
||||
window.history.pushState(p.name || this.defaultName, p.title, p.url + window.location.search);
|
||||
}
|
||||
|
||||
prev(name: string = "") {
|
||||
if (!(name in this.pages)) return console.error(`previous page ${name} not found`);
|
||||
let p = this.pages[name];
|
||||
if (!this.pages.has(name)) return console.error(`previous page ${name} not found`);
|
||||
let p = this.pages.get(name);
|
||||
let shouldSkip = true;
|
||||
while (shouldSkip && p.index > 0) {
|
||||
p = this.pages[this.pageList[p.index-1]];
|
||||
p = this.pages.get(this.pageList[p.index-1]);
|
||||
shouldSkip = p.shouldSkip();
|
||||
}
|
||||
this.loadPage(p);
|
||||
}
|
||||
|
||||
next(name: string = "") {
|
||||
if (!(name in this.pages)) return console.error(`previous page ${name} not found`);
|
||||
let p = this.pages[name];
|
||||
console.log("next", name, p);
|
||||
console.log("pages", this.pages, this.pageList);
|
||||
if (!this.pages.has(name)) return console.error(`previous page ${name} not found`);
|
||||
let p = this.pages.get(name);
|
||||
let shouldSkip = true;
|
||||
while (shouldSkip && p.index < this.pageList.length) {
|
||||
p = this.pages[this.pageList[p.index+1]];
|
||||
p = this.pages.get(this.pageList[p.index+1]);
|
||||
shouldSkip = p.shouldSkip();
|
||||
}
|
||||
console.log("next ended with", p);
|
||||
this.loadPage(p);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -636,6 +636,7 @@ export class settingsList {
|
||||
}
|
||||
|
||||
private _showPanel = (name: string) => {
|
||||
console.log("showing", name);
|
||||
for (let n in this._sections) {
|
||||
if (n == name) {
|
||||
this._sections[name].visible = true;
|
||||
@@ -919,7 +920,11 @@ export class settingsList {
|
||||
for (let i = 0; i < this._loader.children.length; i++) {
|
||||
this._loader.children[i].classList.remove("invisible");
|
||||
}
|
||||
this._showPanel(this._settings.sections[0].section);
|
||||
for (let s of this._settings.sections) {
|
||||
if (s.meta.disabled) continue;
|
||||
this._showPanel(s.section);
|
||||
break;
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent("settings-loaded"));
|
||||
document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false }));
|
||||
this._saveButton.classList.add("unfocused");
|
||||
|
||||
+62
-29
@@ -1,44 +1,77 @@
|
||||
import { PageManager, Page } from "../modules/pages.js";
|
||||
|
||||
export interface Tab {
|
||||
page: Page;
|
||||
tabEl: HTMLDivElement;
|
||||
buttonEl: HTMLSpanElement;
|
||||
preFunc?: () => void;
|
||||
postFunc?: () => void;
|
||||
}
|
||||
|
||||
|
||||
export class Tabs implements Tabs {
|
||||
private _current: string = "";
|
||||
tabs: Array<Tab>;
|
||||
private _baseOffset = -1;
|
||||
tabs: Map<string, Tab>;
|
||||
pages: PageManager;
|
||||
|
||||
constructor() {
|
||||
this.tabs = [];
|
||||
this.tabs = new Map<string, Tab>;
|
||||
this.pages = new PageManager({
|
||||
hideOthersOnPageShow: true,
|
||||
defaultName: "invites",
|
||||
defaultTitle: document.title,
|
||||
});
|
||||
}
|
||||
|
||||
addTab = (tabID: string, preFunc = () => void {}, postFunc = () => void {}) => {
|
||||
let tab = {} as Tab;
|
||||
tab.tabID = tabID;
|
||||
tab.tabEl = document.getElementById("tab-" + tabID) as HTMLDivElement;
|
||||
tab.buttonEl = document.getElementById("button-tab-" + tabID) as HTMLSpanElement;
|
||||
addTab = (tabID: string, url: string, preFunc = () => void {}, postFunc = () => void {},) => {
|
||||
let tab: Tab = {
|
||||
page: null,
|
||||
tabEl: document.getElementById("tab-" + tabID) as HTMLDivElement,
|
||||
buttonEl: document.getElementById("button-tab-" + tabID) as HTMLSpanElement,
|
||||
preFunc: preFunc,
|
||||
postFunc: postFunc,
|
||||
};
|
||||
if (this._baseOffset == -1) {
|
||||
this._baseOffset = tab.buttonEl.offsetLeft;
|
||||
}
|
||||
tab.page = {
|
||||
name: tabID,
|
||||
title: document.title, /*FIXME: Get actual names from translations*/
|
||||
url: window.URLBase + "/" + url,
|
||||
show: () => {
|
||||
tab.buttonEl.classList.add("active", "~urge");
|
||||
tab.tabEl.classList.remove("unfocused");
|
||||
tab.buttonEl.parentElement.scrollTo(tab.buttonEl.offsetLeft-this._baseOffset, 0);
|
||||
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");
|
||||
return true;
|
||||
},
|
||||
shouldSkip: () => false,
|
||||
};
|
||||
this.pages.setPage(tab.page);
|
||||
tab.buttonEl.onclick = () => { this.switch(tabID); };
|
||||
tab.preFunc = preFunc;
|
||||
tab.postFunc = postFunc;
|
||||
this.tabs.push(tab);
|
||||
this.tabs.set(tabID, tab);
|
||||
}
|
||||
|
||||
get current(): string { return this._current; }
|
||||
set current(tabID: string) { this.switch(tabID); }
|
||||
|
||||
switch = (tabID: string, noRun: boolean = false, keepURL: boolean = false) => {
|
||||
this._current = tabID;
|
||||
let baseOffset = -1;
|
||||
for (let t of this.tabs) {
|
||||
if (baseOffset == -1) baseOffset = t.buttonEl.offsetLeft;
|
||||
if (t.tabID == tabID) {
|
||||
t.buttonEl.classList.add("active", "~urge");
|
||||
if (t.preFunc && !noRun) { t.preFunc(); }
|
||||
t.tabEl.classList.remove("unfocused");
|
||||
if (t.postFunc && !noRun) { t.postFunc(); }
|
||||
document.dispatchEvent(new CustomEvent("tab-change", { detail: keepURL ? "" : tabID }));
|
||||
// t.buttonEl.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' })
|
||||
|
||||
t.buttonEl.parentElement.scrollTo(t.buttonEl.offsetLeft-baseOffset, 0);
|
||||
} else {
|
||||
t.buttonEl.classList.remove("active");
|
||||
t.buttonEl.classList.remove("~urge");
|
||||
t.tabEl.classList.add("unfocused");
|
||||
}
|
||||
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(); }
|
||||
}
|
||||
}
|
||||
|
||||
+1
-11
@@ -79,20 +79,10 @@ declare interface NotificationBox {
|
||||
|
||||
declare interface Tabs {
|
||||
current: string;
|
||||
tabs: Array<Tab>;
|
||||
addTab: (tabID: string, preFunc?: () => void, postFunc?: () => void) => void;
|
||||
addTab: (tabID: string, url: string, preFunc?: () => void, postFunc?: () => void) => void;
|
||||
switch: (tabID: string, noRun?: boolean, keepURL?: boolean) => void;
|
||||
}
|
||||
|
||||
declare interface Tab {
|
||||
tabID: string;
|
||||
tabEl: HTMLDivElement;
|
||||
buttonEl: HTMLSpanElement;
|
||||
preFunc?: () => void;
|
||||
postFunc?: () => void;
|
||||
}
|
||||
|
||||
|
||||
declare interface Modals {
|
||||
about: Modal;
|
||||
login: Modal;
|
||||
|
||||
Reference in New Issue
Block a user