Merge pull request #279 from PR0M3TH3AN/codex/refactor-application-initialization-and-exports

Refactor application bootstrap to run on DOM ready
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-04 13:47:22 -04:00
commit 88ad16326b
2 files changed
+188 -153

No files matched your search

+20 -13
View File
@@ -4,20 +4,27 @@ import Application from "./app.js";
import nostrService from "./services/nostrService.js";
import r2Service from "./services/r2Service.js";
import { loadView } from "./viewManager.js";
import { setApplication, setApplicationReady } from "./applicationContext.js";
const app = new Application({
services: {
nostrService,
r2Service,
},
loadView,
});
function mergeServices(overrides = {}) {
const merged = { nostrService, r2Service };
setApplication(app);
if (overrides && typeof overrides === "object") {
if (overrides.nostrService) {
merged.nostrService = overrides.nostrService;
}
if (overrides.r2Service) {
merged.r2Service = overrides.r2Service;
}
}
const appReady = app.init();
setApplicationReady(appReady);
return merged;
}
export { app, appReady };
export default app;
export function createApplication({ services, loadView: loadViewOverride } = {}) {
return new Application({
services: mergeServices(services),
loadView: typeof loadViewOverride === "function" ? loadViewOverride : loadView,
});
}
export default createApplication;
+168 -140
View File
@@ -1,8 +1,46 @@
// js/index.js
import "./bufferPolyfill.js";
import { trackPageView } from "./analytics.js";
import { appReady } from "./bootstrap.js";
import Application from "./app.js";
import { setApplication, setApplicationReady } from "./applicationContext.js";
import nostrService from "./services/nostrService.js";
import r2Service from "./services/r2Service.js";
import { loadView, viewInitRegistry } from "./viewManager.js";
let application = null;
let applicationReadyPromise = Promise.resolve();
function startApplication() {
if (application) {
return applicationReadyPromise;
}
application = new Application({
services: {
nostrService,
r2Service,
},
loadView,
});
setApplication(application);
const startupPromise = (async () => {
await application.init();
if (typeof application.start === "function") {
await application.start();
}
})();
applicationReadyPromise = startupPromise;
setApplicationReady(startupPromise);
startupPromise.catch((error) => {
console.error("Application failed to initialize:", error);
});
return startupPromise;
}
const INTERFACE_FADE_IN_ANIMATION = "interface-fade-in";
const VIDEO_THUMBNAIL_FADE_IN_ANIMATION = "video-thumbnail-fade-in";
@@ -112,137 +150,139 @@ async function loadDisclaimer(url, containerId) {
}
}
// 4) Load everything: modals, sidebar, disclaimers
Promise.all([
// Existing modals
loadModal("components/login-modal.html"),
loadModal("components/application-form.html"),
loadModal("components/content-appeals-form.html"),
async function bootstrapInterface() {
await Promise.all([
loadModal("components/login-modal.html"),
loadModal("components/application-form.html"),
loadModal("components/content-appeals-form.html"),
loadModal("components/general-feedback-form.html"),
loadModal("components/feature-request-form.html"),
loadModal("components/bug-fix-form.html"),
]);
// New forms
loadModal("components/general-feedback-form.html"),
loadModal("components/feature-request-form.html"),
loadModal("components/bug-fix-form.html"),
])
.then(() => {
console.log("Modals loaded.");
return loadSidebar("components/sidebar.html", "sidebarContainer");
})
.then(() => {
console.log("Sidebar loaded.");
console.log("Modals loaded.");
// Attach mobile menu button toggle logic (for sidebar)
const mobileMenuBtn = document.getElementById("mobileMenuBtn");
const sidebar = document.getElementById("sidebar");
const app = document.getElementById("app");
if (mobileMenuBtn && sidebar && app) {
mobileMenuBtn.addEventListener("click", () => {
sidebar.classList.toggle("sidebar-open");
app.classList.toggle("sidebar-open");
});
}
await loadSidebar("components/sidebar.html", "sidebarContainer");
console.log("Sidebar loaded.");
// Attach "More" button toggle logic for footer links
const footerDropdownButton = document.getElementById(
"footerDropdownButton"
);
if (footerDropdownButton) {
footerDropdownButton.addEventListener("click", () => {
const footerLinksContainer = document.getElementById(
"footerLinksContainer"
);
if (!footerLinksContainer) return;
footerLinksContainer.classList.toggle("hidden");
if (footerLinksContainer.classList.contains("hidden")) {
footerDropdownButton.innerHTML = "More ▼";
} else {
footerDropdownButton.innerHTML = "Less ▲";
}
});
}
// Load and set up sidebar navigation
return import("./sidebar.js").then((module) => {
module.setupSidebarNavigation();
const mobileMenuBtn = document.getElementById("mobileMenuBtn");
const sidebar = document.getElementById("sidebar");
const appElement = document.getElementById("app");
if (mobileMenuBtn && sidebar && appElement) {
mobileMenuBtn.addEventListener("click", () => {
sidebar.classList.toggle("sidebar-open");
appElement.classList.toggle("sidebar-open");
});
})
.then(() => {
// Now load the disclaimer
return loadDisclaimer("components/disclaimer.html", "modalContainer");
})
.then(() => {
console.log("Disclaimer loaded.");
}
// 1) Login button => open login modal
const loginNavBtn = document.getElementById("loginButton");
if (loginNavBtn) {
loginNavBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.remove("hidden");
}
});
const footerDropdownButton = document.getElementById("footerDropdownButton");
if (footerDropdownButton) {
footerDropdownButton.addEventListener("click", () => {
const footerLinksContainer = document.getElementById("footerLinksContainer");
if (!footerLinksContainer) return;
footerLinksContainer.classList.toggle("hidden");
if (footerLinksContainer.classList.contains("hidden")) {
footerDropdownButton.innerHTML = "More ▼";
} else {
footerDropdownButton.innerHTML = "Less ▲";
}
});
}
try {
const sidebarModule = await import("./sidebar.js");
if (typeof sidebarModule.setupSidebarNavigation === "function") {
sidebarModule.setupSidebarNavigation();
}
} catch (error) {
console.error("Failed to set up sidebar navigation:", error);
}
// 2) Close login modal
const closeLoginBtn = document.getElementById("closeLoginModal");
if (closeLoginBtn) {
closeLoginBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.add("hidden");
await loadDisclaimer("components/disclaimer.html", "modalContainer");
console.log("Disclaimer loaded.");
const loginNavBtn = document.getElementById("loginButton");
if (loginNavBtn) {
loginNavBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.remove("hidden");
}
});
}
const closeLoginBtn = document.getElementById("closeLoginModal");
if (closeLoginBtn) {
closeLoginBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.add("hidden");
}
});
}
const openAppFormBtn = document.getElementById("openApplicationModal");
if (openAppFormBtn) {
openAppFormBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.add("hidden");
}
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.remove("hidden");
}
});
}
const closeNostrFormBtn = document.getElementById("closeNostrFormModal");
if (closeNostrFormBtn) {
closeNostrFormBtn.addEventListener("click", () => {
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.add("hidden");
}
if (!localStorage.getItem("hasSeenDisclaimer")) {
const disclaimerModal = document.getElementById("disclaimerModal");
if (disclaimerModal) {
disclaimerModal.classList.remove("hidden");
}
});
}
}
});
}
// 3) "Application Form" => open application form
const openAppFormBtn = document.getElementById("openApplicationModal");
if (openAppFormBtn) {
openAppFormBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.add("hidden");
}
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.remove("hidden");
}
});
}
handleQueryParams();
// 4) Close application form
const closeNostrFormBtn = document.getElementById("closeNostrFormModal");
if (closeNostrFormBtn) {
closeNostrFormBtn.addEventListener("click", () => {
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.add("hidden");
}
// If user hasn't seen disclaimer, show it
if (!localStorage.getItem("hasSeenDisclaimer")) {
const disclaimerModal = document.getElementById("disclaimerModal");
if (disclaimerModal) {
disclaimerModal.classList.remove("hidden");
}
}
});
}
window.addEventListener("hashchange", handleHashChange);
// Once everything is loaded, handle the query params (modal? v?) & disclaimers
handleQueryParams();
await handleHashChange();
// Listen for hash changes
window.addEventListener("hashchange", handleHashChange);
const { default: disclaimerModal } = await import("./disclaimer.js");
disclaimerModal.init();
disclaimerModal.show();
}
// Also run once on initial load
handleHashChange();
async function initializeInterface() {
startApplication();
return import("./disclaimer.js");
})
.then(({ default: disclaimerModal }) => {
disclaimerModal.init();
disclaimerModal.show();
try {
await bootstrapInterface();
} catch (error) {
console.error("Failed to bootstrap Bitvid interface:", error);
}
}
function onDomReady() {
initializeInterface().catch((error) => {
console.error("Unhandled error during Bitvid initialization:", error);
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", onDomReady, { once: true });
} else {
onDomReady();
}
/* -------------------------------------------
HELPER FUNCTIONS FOR QUERY AND HASH
@@ -367,27 +407,17 @@ function handleQueryParams() {
}
}
async function waitForAppInitialization() {
const maybePromise = appReady;
if (!maybePromise || typeof maybePromise.then !== "function") {
return;
}
try {
await maybePromise;
} catch (error) {
console.warn(
"Proceeding with hash handling despite app initialization failure:",
error
);
}
}
async function handleHashChange() {
console.log("handleHashChange called, current hash =", window.location.hash);
await waitForAppInitialization();
try {
await applicationReadyPromise;
} catch (error) {
console.warn(
"Proceeding with hash handling despite application initialization failure:",
error
);
}
const hash = window.location.hash || "";
// Use a regex that captures up to the first ampersand or end of string.
@@ -395,14 +425,12 @@ async function handleHashChange() {
const match = hash.match(/^#view=([^&]+)/);
try {
const { loadView, viewInitRegistry } = await import("./viewManager.js");
if (!match || !match[1]) {
// No valid "#view=..." => default to "most-recent-videos"
await loadView("views/most-recent-videos.html");
const initFn = viewInitRegistry["most-recent-videos"];
if (typeof initFn === "function") {
initFn();
await initFn();
}
return;
}
@@ -416,7 +444,7 @@ async function handleHashChange() {
await loadView(viewUrl);
const initFn = viewInitRegistry[viewName];
if (typeof initFn === "function") {
initFn();
await initFn();
}
} catch (error) {
console.error("Failed to handle hash change:", error);