From 02f4ba6e8e1a1b97bc758055f93bc737073921c9 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Wed, 28 Aug 2024 14:18:52 +0100 Subject: [PATCH] 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. --- ' | 239 +++++++++++++++++++++++++++++++++++++++++ Makefile | 23 ++-- ts/admin.ts | 102 +++++++----------- ts/modules/accounts.ts | 10 +- ts/modules/activity.ts | 14 +-- ts/modules/invites.ts | 9 +- ts/modules/pages.ts | 32 +++--- ts/modules/settings.ts | 7 +- ts/modules/tabs.ts | 91 +++++++++++----- ts/typings/d.ts | 12 +-- 10 files changed, 388 insertions(+), 151 deletions(-) create mode 100644 ' diff --git a/' b/' new file mode 100644 index 0000000..13c2425 --- /dev/null +++ b/' @@ -0,0 +1,239 @@ +.PHONY: configuration email typescript swagger copy compile compress inline-css variants-html install clean npm config-description config-default precompile + +all: compile + +GOESBUILD ?= off +ifeq ($(GOESBUILD), on) + ESBUILD := esbuild +else + ESBUILD := npx esbuild +endif +GOBINARY ?= go + +CSSVERSION ?= v3 +CSS_BUNDLE = $(DATA)/web/css/$(CSSVERSION)bundle.css + +VERSION ?= $(shell git describe --exact-match HEAD 2> /dev/null || echo vgit) +VERSION := $(shell echo $(VERSION) | sed 's/v//g') +COMMIT ?= $(shell git rev-parse --short HEAD || echo unknown) +BUILDTIME ?= $(shell date +%s) + +UPDATER ?= off +LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.cssVersion=$(CSSVERSION) -X main.buildTimeUnix=$(BUILDTIME) $(if $(BUILTBY),-X 'main.builtBy=$(BUILTBY)',) +ifeq ($(UPDATER), on) + LDFLAGS := $(LDFLAGS) -X main.updater=binary +else ifneq ($(UPDATER), off) + LDFLAGS := $(LDFLAGS) -X main.updater=$(UPDATER) +endif + + + +INTERNAL ?= on +TRAY ?= off +E2EE ?= on +TAGS := -tags " + +ifeq ($(INTERNAL), on) + DATA := data +else + DATA := build/data + TAGS := $(TAGS) external +endif + +ifeq ($(TRAY), on) + TAGS := $(TAGS) tray +endif + +ifeq ($(E2EE), on) + TAGS := $(TAGS) e2ee +endif + +TAGS := $(TAGS)" + +OS := $(shell go env GOOS) +ifeq ($(TRAY)$(OS), onwindows) + LDFLAGS := $(LDFLAGS) -H=windowsgui +endif + +DEBUG ?= off +ifeq ($(DEBUG), on) + SOURCEMAP := --sourcemap + MINIFY := + TYPECHECK := npx tsc -noEmit --project ts/tsconfig.json + # jank + COPYTS := rm -r $(DATA)/web/js/ts; cp -r tempts $(DATA)/web/js/ts + UNCSS := cp $(CSS_BUNDLE) $(DATA)/bundle.css + # TAILWIND := --content "" +else + LDFLAGS := -s -w $(LDFLAGS) + SOURCEMAP := + MINIFY := --minify + COPYTS := + TYPECHECK := + UNCSS := npx tailwindcss -i $(CSS_BUNDLE) -o $(DATA)/bundle.css --content "html/crash.html" + # UNCSS := npx uncss $(DATA)/crash.html --csspath web/css --output $(DATA)/bundle.css + TAILWIND := +endif + +RACE ?= off +ifeq ($(RACE), on) + RACEDETECTOR := -race +else + RACEDETECTOR := +endif + +ifeq (, $(shell which esbuild)) + ESBUILDINSTALL := go install github.com/evanw/esbuild/cmd/esbuild@latest +else + ESBUILDINSTALL := +endif + +ifeq ($(GOESBUILD), on) + NPMIGNOREOPTIONAL := --no-optional + NPMOPTS := $(NPMIGNOREOPTIONAL); $(ESBUILDINSTALL) +else + NPMOPTS := +endif + +ifeq (, $(shell which swag)) + SWAGINSTALL := $(GOBINARY) install github.com/swaggo/swag/cmd/swag@latest +else + SWAGINSTALL := +endif + +CONFIG_BASE = config/config-base.yaml + +# CONFIG_DESCRIPTION = $(DATA)/config-base.json +CONFIG_DEFAULT = $(DATA)/config-default.ini +# $(CONFIG_DESCRIPTION) &: $(CONFIG_BASE) +# $(info Fixing config-base) +# -mkdir -p $(DATA) + +$(DATA): + mkdir -p $(DATA) + +$(CONFIG_DEFAULT): $(DATA) $(CONFIG_BASE) + $(info Generating config-default.ini) + go run scripts/ini/main.go -in $(CONFIG_BASE) -out $(DATA)/config-default.ini + +configuration: $(CONFIG_DEFAULT) + +EMAIL_SRC = $(wildcard mail/*) +EMAIL_TARGET = mail/confirmation.html +$(EMAIL_TARGET): $(EMAIL_SRC) + $(info Generating email html) + npx mjml mail/*.mjml -o $(DATA)/ + $(info Copying plaintext mail) + cp mail/*.txt $(DATA)/ + +TYPESCRIPT_FULLSRC = $(shell find ts/ -type f -name "*.ts") +TYPESCRIPT_SRC = $(wildcard ts/*.ts) +TYPESCRIPT_TEMPSRC = $(TYPESCRIPT_SRC:ts/%=tempts/%) +# TYPESCRIPT_TARGET = $(patsubst %.ts,%.js,$(subst tempts/,./$(DATA)/web/js/,$(TYPESCRIPT_TEMPSRC))) +TYPESCRIPT_TARGET = $(DATA)/web/js/admin.js +$(TYPESCRIPT_TARGET): $(TYPESCRIPT_FULLSRC) ts/tsconfig.json + $(TYPECHECK) + rm -rf tempts + cp -r ts tempts + $(adding dark variants to typescript) + scripts/dark-variant.sh tempts + scripts/dark-variant.sh tempts/modules + $(info compiling typescript) + mkdir -p $(DATA)/web/js + $(foreach tempsrc,$(TYPESCRIPT_TEMPSRC),$(ESBUILD) --target=es6 --bundle $(tempsrc) $(SOURCEMAP) --outfile=$(patsubst %.ts,%.js,$(subst tempts/,./$(DATA)/web/js/,$(tempsrc))) $(MINIFY);) + mv $(DATA)/web/js/crash.js $(DATA)/ + $(COPYTS) + +SWAGGER_SRC = $(wildcard api*.go) $(wildcard *auth.go) views.go +SWAGGER_TARGET = docs/docs.go +$(SWAGGER_TARGET): $(SWAGGER_SRC) + $(SWAGINSTALL) + swag init -g main.go + +VARIANTS_SRC = $(wildcard html/*.html) +VARIANTS_TARGET = $(DATA)/html/admin.html +$(VARIANTS_TARGET): $(VARIANTS_SRC) + $(info copying html) + cp -r html $(DATA)/ + $(info adding dark variants to html) + node scripts/missing-colors.js html $(DATA)/html + +ICON_SRC = node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 +ICON_TARGET = $(ICON_SRC:node_modules/remixicon/fonts/%=$(DATA)/web/css/%) +CSS_SRC = $(wildcard css/*.css) +CSS_TARGET = $(DATA)/web/css/part-bundle.css +CSS_FULLTARGET = $(CSS_BUNDLE) +ALL_CSS_SRC = $(ICON_SRC) $(CSS_SRC) +ALL_CSS_TARGET = $(ICON_TARGET) + +$(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wildcard html/*.html) + mkdir -p $(DATA)/web/css + $(info copying fonts) + cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/ + $(info bundling css) + $(ESBUILD) --bundle css/base.css --outfile=$(CSS_TARGET) --external:remixicon.css --external:../fonts/hanken* --minify + + npx tailwindcss -i $(CSS_TARGET) -o $(CSS_FULLTARGET) $(TAILWIND) + rm $(CSS_TARGET) + # mv $(CSS_BUNDLE) $(DATA)/web/css/$(CSSVERSION)bundle.css + # npx postcss -o $(CSS_TARGET) $(CSS_TARGET) + +INLINE_SRC = html/crash.html +INLINE_TARGET = $(DATA)/crash.html +$(INLINE_TARGET): $(CSS_FULLTARGET) $(INLINE_SRC) + cp html/crash.html $(DATA)/crash.html + $(UNCSS) # generates $(DATA)/bundle.css for us + node scripts/inline.js root $(DATA) $(DATA)/crash.html $(DATA)/crash.html + rm $(DATA)/bundle.css + +LANG_SRC = $(shell find ./lang) +LANG_TARGET = $(LANG_SRC:lang/%=$(DATA)/lang/%) +STATIC_SRC = $(wildcard static/*) +STATIC_TARGET = $(STATIC_SRC:static/%=$(DATA)/web/%) +COPY_SRC = images/banner.svg jfa-go.service LICENSE $(LANG_SRC) $(STATIC_SRC) +COPY_TARGET = $(DATA)/jfa-go.service +# $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css +$(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC) + $(info copying $(CONFIG_BASE)) + cp $(CONFIG_BASE) $(DATA)/ + $(info copying crash page) + cp $(DATA)/crash.html $(DATA)/html/ + $(info copying static data) + mkdir -p $(DATA)/web + cp images/banner.svg static/banner.svg + cp -r static/* $(DATA)/web/ + $(info copying systemd service) + cp jfa-go.service $(DATA)/ + $(info copying language files) + cp -r lang $(DATA)/ + cp LICENSE $(DATA)/ + +precompile: $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) + +GO_SRC = $(shell find ./ -name "*.go") +GO_TARGET = build/jfa-go +$(GO_TARGET): $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum + $(info Downloading deps) + $(GOBINARY) mod download + $(info Building) + mkdir -p build + $(GOBINARY) build $(RACEDETECTOR) -ldflags="$(LDFLAGS)" $(TAGS) -o $(GO_TARGET) + +compile: $(GO_TARGET) + +compress: + upx --lzma build/jfa-go + +install: + cp -r build $(DESTDIR)/jfa-go + +clean: + -rm -r $(DATA) + -rm -r build + -rm mail/*.html + -rm docs/docs.go docs/swagger.json docs/swagger.yaml + go clean + +npm: + $(info installing npm dependencies) + npm install $(NPMOPTS) diff --git a/Makefile b/Makefile index dfcef82..81ca24d 100644 --- a/Makefile +++ b/Makefile @@ -110,22 +110,18 @@ CONFIG_DEFAULT = $(DATA)/config-default.ini # -mkdir -p $(DATA) $(DATA): - mkdir -p $(DATA) + mkdir -p $(DATA)/web/js + mkdir -p $(DATA)/web/css -$(CONFIG_DEFAULT): $(DATA) $(CONFIG_BASE) +$(CONFIG_DEFAULT): $(CONFIG_BASE) $(info Generating config-default.ini) go run scripts/ini/main.go -in $(CONFIG_BASE) -out $(DATA)/config-default.ini configuration: $(CONFIG_DEFAULT) -EMAIL_SRC_MJML = $(wildcard mail/*.mjml) -EMAIL_SRC_TXT = $(wildcard mail/*.txt) -EMAIL_DATA_MJML = $(EMAIL_SRC_MJML:mail/%=data/%) -EMAIL_HTML = $(EMAIL_DATA_MJML:.mjml=.html) -EMAIL_TXT = $(EMAIL_SRC_TXT:mail/%=data/%) -EMAIL_ALL = $(EMAIL_HTML) $(EMAIL_TXT) -EMAIL_TARGET = mail/confirmation.html -$(EMAIL_TARGET): $(EMAIL_SRC_MJML) $(EMAIL_SRC_TXT) +EMAIL_SRC = $(wildcard mail/*) +EMAIL_TARGET = $(DATA)/confirmation.html +$(EMAIL_TARGET): $(EMAIL_SRC) $(info Generating email html) npx mjml mail/*.mjml -o $(DATA)/ $(info Copying plaintext mail) @@ -144,7 +140,6 @@ $(TYPESCRIPT_TARGET): $(TYPESCRIPT_FULLSRC) ts/tsconfig.json scripts/dark-variant.sh tempts scripts/dark-variant.sh tempts/modules $(info compiling typescript) - mkdir -p $(DATA)/web/js $(foreach tempsrc,$(TYPESCRIPT_TEMPSRC),$(ESBUILD) --target=es6 --bundle $(tempsrc) $(SOURCEMAP) --outfile=$(patsubst %.ts,%.js,$(subst tempts/,./$(DATA)/web/js/,$(tempsrc))) $(MINIFY);) mv $(DATA)/web/js/crash.js $(DATA)/ $(COPYTS) @@ -172,7 +167,6 @@ ALL_CSS_SRC = $(ICON_SRC) $(CSS_SRC) ALL_CSS_TARGET = $(ICON_TARGET) $(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wildcard html/*.html) - mkdir -p $(DATA)/web/css $(info copying fonts) cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/ $(info bundling css) @@ -204,7 +198,6 @@ $(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC) $(info copying crash page) cp $(DATA)/crash.html $(DATA)/html/ $(info copying static data) - mkdir -p $(DATA)/web cp images/banner.svg static/banner.svg cp -r static/* $(DATA)/web/ $(info copying systemd service) @@ -213,11 +206,11 @@ $(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC) cp -r lang $(DATA)/ cp LICENSE $(DATA)/ -precompile: $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) +precompile: $(DATA) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) GO_SRC = $(shell find ./ -name "*.go") GO_TARGET = build/jfa-go -$(GO_TARGET): $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum +$(GO_TARGET): $(DATA) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum $(info Downloading deps) $(GOBINARY) mod download $(info Building) diff --git a/ts/admin.ts b/ts/admin.ts index 86b540f..814fa37 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 } 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(); diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts index 812e476..dc39c22 100644 --- a/ts/modules/accounts.ts +++ b/ts/modules/accounts.ts @@ -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); } diff --git a/ts/modules/activity.ts b/ts/modules/activity.ts index d5bf3d3..48a2cc6 100644 --- a/ts/modules/activity.ts +++ b/ts/modules/activity.ts @@ -64,17 +64,17 @@ export class Activity implements activity, SearchableItem { } _genUserLink = (): string => { - return `${this._genUserText()}`; + return `${this._genUserText()}`; } _genSrcUserLink = (): string => { - return `${this._genSrcUserText()}`; + return `${this._genSrcUserText()}`; } private _renderInvText = (): string => { return `${this.value || this.invite_code || "???"}`; } private _genInvLink = (): string => { - return `${this._renderInvText()}`; + return `${this._renderInvText()}`; } @@ -307,17 +307,17 @@ export class Activity implements activity, SearchableItem { const pseudoInvites = this._card.getElementsByClassName("activity-pseudo-link-invite") as HTMLCollectionOf; 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;*/ } } diff --git a/ts/modules/invites.ts b/ts/modules/invites.ts index 0074aee..c9b759c 100644 --- a/ts/modules/invites.ts +++ b/ts/modules/invites.ts @@ -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")); } diff --git a/ts/modules/pages.ts b/ts/modules/pages.ts index 948474c..686f771 100644 --- a/ts/modules/pages.ts +++ b/ts/modules/pages.ts @@ -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); } }; diff --git a/ts/modules/settings.ts b/ts/modules/settings.ts index 0fcfbdc..01aa5df 100644 --- a/ts/modules/settings.ts +++ b/ts/modules/settings.ts @@ -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"); diff --git a/ts/modules/tabs.ts b/ts/modules/tabs.ts index ee6333e..628ed7e 100644 --- a/ts/modules/tabs.ts +++ b/ts/modules/tabs.ts @@ -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; + private _baseOffset = -1; + tabs: Map; + pages: PageManager; constructor() { - this.tabs = []; + this.tabs = new Map; + 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(); } } } diff --git a/ts/typings/d.ts b/ts/typings/d.ts index bcba96d..a53a3fb 100644 --- a/ts/typings/d.ts +++ b/ts/typings/d.ts @@ -79,20 +79,10 @@ declare interface NotificationBox { declare interface Tabs { current: string; - tabs: Array; - 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;