From 03c28dacabfdf659c0cc47b243829284d6c3b4a8 Mon Sep 17 00:00:00 2001 From: Keep Creating Online <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:17:49 -0500 Subject: [PATCH] added side bar --- bitvid_logo/home-icon.svg | 5 + src/assets/svg/about-icon.svg | 5 + src/assets/svg/default-profile.svg | 5 + src/assets/svg/explore-icon.svg | 5 + src/assets/svg/getting-started-icon.svg | 5 + src/assets/svg/github-icon.svg | 5 + src/assets/svg/home-icon.svg | 5 + src/assets/svg/mobile-sidebar-menu-icon.svg | 5 + src/assets/svg/roadmap-icon.svg | 5 + src/assets/svg/subscriptions-icon.svg | 5 + src/components/disclaimer.html | 151 +++++++ src/components/sidebar.html | 76 ++++ src/css/style.css | 45 ++ src/index.html | 447 ++------------------ src/js/app.js | 21 +- src/js/disclaimer.js | 45 +- src/js/index.js | 258 +++++++++++ src/js/sidebar.js | 46 ++ src/js/webtorrent.js | 2 +- src/views/explore.html | 5 + src/views/subscriptions.html | 5 + whitelistform.txt | 1 - 22 files changed, 709 insertions(+), 443 deletions(-) create mode 100644 bitvid_logo/home-icon.svg create mode 100644 src/assets/svg/about-icon.svg create mode 100644 src/assets/svg/default-profile.svg create mode 100644 src/assets/svg/explore-icon.svg create mode 100644 src/assets/svg/getting-started-icon.svg create mode 100644 src/assets/svg/github-icon.svg create mode 100644 src/assets/svg/home-icon.svg create mode 100644 src/assets/svg/mobile-sidebar-menu-icon.svg create mode 100644 src/assets/svg/roadmap-icon.svg create mode 100644 src/assets/svg/subscriptions-icon.svg create mode 100644 src/components/disclaimer.html create mode 100644 src/components/sidebar.html create mode 100644 src/js/index.js create mode 100644 src/js/sidebar.js create mode 100644 src/views/explore.html create mode 100644 src/views/subscriptions.html delete mode 100644 whitelistform.txt diff --git a/bitvid_logo/home-icon.svg b/bitvid_logo/home-icon.svg new file mode 100644 index 0000000..2c7c1df --- /dev/null +++ b/bitvid_logo/home-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/about-icon.svg b/src/assets/svg/about-icon.svg new file mode 100644 index 0000000..855d3a2 --- /dev/null +++ b/src/assets/svg/about-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/default-profile.svg b/src/assets/svg/default-profile.svg new file mode 100644 index 0000000..b31b553 --- /dev/null +++ b/src/assets/svg/default-profile.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/explore-icon.svg b/src/assets/svg/explore-icon.svg new file mode 100644 index 0000000..2d80429 --- /dev/null +++ b/src/assets/svg/explore-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/getting-started-icon.svg b/src/assets/svg/getting-started-icon.svg new file mode 100644 index 0000000..dcece7e --- /dev/null +++ b/src/assets/svg/getting-started-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/github-icon.svg b/src/assets/svg/github-icon.svg new file mode 100644 index 0000000..ffb9088 --- /dev/null +++ b/src/assets/svg/github-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/home-icon.svg b/src/assets/svg/home-icon.svg new file mode 100644 index 0000000..2c7c1df --- /dev/null +++ b/src/assets/svg/home-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/mobile-sidebar-menu-icon.svg b/src/assets/svg/mobile-sidebar-menu-icon.svg new file mode 100644 index 0000000..fa9a60c --- /dev/null +++ b/src/assets/svg/mobile-sidebar-menu-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/roadmap-icon.svg b/src/assets/svg/roadmap-icon.svg new file mode 100644 index 0000000..6bd0948 --- /dev/null +++ b/src/assets/svg/roadmap-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/subscriptions-icon.svg b/src/assets/svg/subscriptions-icon.svg new file mode 100644 index 0000000..46ff49d --- /dev/null +++ b/src/assets/svg/subscriptions-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/disclaimer.html b/src/components/disclaimer.html new file mode 100644 index 0000000..a190d9c --- /dev/null +++ b/src/components/disclaimer.html @@ -0,0 +1,151 @@ + + diff --git a/src/components/sidebar.html b/src/components/sidebar.html new file mode 100644 index 0000000..4809b5d --- /dev/null +++ b/src/components/sidebar.html @@ -0,0 +1,76 @@ + diff --git a/src/css/style.css b/src/css/style.css index c8445d4..530fcae 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -470,3 +470,48 @@ footer a:hover { height: 100%; object-fit: cover; } + +/* Sidebar default states */ +#sidebar { + /* You can set a default width here for 'expanded' state. */ + width: 14rem; /* for example */ +} + +/* You could define a class for collapsed states on desktop if desired: */ +.sidebar-collapsed { + width: 4rem; +} +.sidebar-expanded { + width: 14rem; +} + +/* Hide text when collapsed. + If you add .sidebar-collapsed to #sidebar, you can hide text like this: */ +.sidebar-collapsed .sidebar-text { + display: none; +} + +/* Basic scrolling for the sidebar if it's long */ +#sidebar { + overflow-y: auto; +} + +/* The top-level container is already "flex min-h-screen" in index.html. + The main content (#app) has "flex-1", so it fills the rest of the space. */ + +/* Example of customizing the border & background in the sidebar */ +#sidebar hr { + border-color: rgba(255, 255, 255, 0.1); +} + +/* If you want a smooth transition for showing/hiding the entire sidebar + on mobile (via the "hidden" class), you can do so with a small fade, + but you’ll need a separate approach with absolute positioning or something + if you prefer a sliding effect. */ + +@media (max-width: 767px) { + .sidebar-open { + transform: translateX(16rem); + transition: transform 0.3s ease; + } +} diff --git a/src/index.html b/src/index.html index 3a008cd..349f47f 100644 --- a/src/index.html +++ b/src/index.html @@ -39,13 +39,36 @@ -
+ +
+ +
+ + +
-
- +
+ + + + -
- +
- +
@@ -115,165 +137,6 @@
- - -
- - - - + + + diff --git a/src/js/app.js b/src/js/app.js index 4891f11..23afdf5 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -4,7 +4,7 @@ import { loadView } from "./viewManager.js"; import { nostrClient } from "./nostr.js"; import { torrentClient } from "./webtorrent.js"; import { isDevMode } from "./config.js"; -import { disclaimerModal } from "./disclaimer.js"; +import disclaimerModal from "./disclaimer.js"; import { initialBlacklist, initialEventBlacklist } from "./lists.js"; /** @@ -724,12 +724,19 @@ class bitvidApp { /** * Subscribe to videos (older + new) and render them as they come in. */ - async loadVideos() { - console.log("Starting loadVideos..."); + async loadVideos(forceFetch = false) { + console.log("Starting loadVideos... (forceFetch =", forceFetch, ")"); - // We do NOT decode initialEventBlacklist here. - // That happens once in the constructor, creating this.blacklistedEventIds. + // If forceFetch is true, unsubscribe from the old subscription to start fresh + if (forceFetch && this.videoSubscription) { + // If you have a specific unsubscribe method, call it here. + // For example: + nostrClient.unsubscribeVideos(this.videoSubscription); + this.videoSubscription = null; + } + + // The rest of your existing logic: if (!this.videoSubscription) { if (this.videoList) { this.videoList.innerHTML = ` @@ -738,7 +745,7 @@ class bitvidApp {

`; } - // Create a single subscription + // Create a new subscription this.videoSubscription = nostrClient.subscribeVideos(() => { const updatedAll = nostrClient.getActiveVideos(); @@ -749,7 +756,7 @@ class bitvidApp { return false; } - // 2) Check author (if you’re also blacklisting authors by npub) + // 2) Check author if you’re blacklisting authors by npub const authorNpub = this.safeEncodeNpub(video.pubkey) || video.pubkey; if (initialBlacklist.includes(authorNpub)) { return false; diff --git a/src/js/disclaimer.js b/src/js/disclaimer.js index 8725e00..e1a1398 100644 --- a/src/js/disclaimer.js +++ b/src/js/disclaimer.js @@ -1,36 +1,39 @@ +// js/disclaimer.js + class DisclaimerModal { constructor() { - this.modal = document.getElementById("disclaimerModal"); - this.acceptButton = document.getElementById("acceptDisclaimer"); - // If user previously dismissed the disclaimer, we'll store "true" in localStorage: - this.hasSeenDisclaimer = localStorage.getItem("hasSeenDisclaimer"); - - // Set up the click event for the "I Understand" button - this.setupEventListeners(); + // Initialize elements when the disclaimer HTML is in the DOM. + this.init(); } - setupEventListeners() { + init() { + this.modal = document.getElementById("disclaimerModal"); + this.acceptButton = document.getElementById("acceptDisclaimer"); if (this.acceptButton) { this.acceptButton.addEventListener("click", () => { - // Hide the disclaimer by adding the "hidden" class - if (this.modal) { - this.modal.classList.add("hidden"); - } - // Mark that the user has seen the disclaimer, so we don't show it again - localStorage.setItem("hasSeenDisclaimer", "true"); + this.hide(); }); } } + hide() { + if (this.modal) { + this.modal.classList.add("hidden"); + } + localStorage.setItem("hasSeenDisclaimer", "true"); + } + show() { - // Only show it if the user hasn't seen it before - if (!this.hasSeenDisclaimer) { - if (this.modal) { - this.modal.classList.remove("hidden"); - } + // In case the modal hasn't been initialized yet. + if (!this.modal) { + this.init(); + } + if (!localStorage.getItem("hasSeenDisclaimer") && this.modal) { + this.modal.classList.remove("hidden"); } } } -// Export an instance that you can import in your main script -export const disclaimerModal = new DisclaimerModal(); +// Create and export a default instance. +const disclaimerModal = new DisclaimerModal(); +export default disclaimerModal; diff --git a/src/js/index.js b/src/js/index.js new file mode 100644 index 0000000..1475abb --- /dev/null +++ b/src/js/index.js @@ -0,0 +1,258 @@ +// js/index.js + +// 1) Load modals (login, application, etc.) +async function loadModal(url) { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error("Failed to load " + url); + } + const html = await response.text(); + document + .getElementById("modalContainer") + .insertAdjacentHTML("beforeend", html); + console.log(url, "loaded"); + } catch (err) { + console.error(err); + } +} + +// 2) Load sidebar +async function loadSidebar(url, containerId) { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error("Failed to load " + url); + } + const html = await response.text(); + document.getElementById(containerId).innerHTML = html; + console.log(url, "loaded into", containerId); + } catch (err) { + console.error(err); + } +} + +// 3) Load the disclaimer (now separate) +async function loadDisclaimer(url, containerId) { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error("Failed to load " + url); + } + const html = await response.text(); + document.getElementById(containerId).insertAdjacentHTML("beforeend", html); + console.log(url, "disclaimer loaded into", containerId); + } catch (err) { + console.error(err); + } +} + +// 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"), + + // 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."); + + const mobileMenuBtn = document.getElementById("mobileMenuBtn"); + const sidebar = document.getElementById("sidebar"); + const app = document.getElementById("app"); // <-- new + + if (mobileMenuBtn && sidebar && app) { + mobileMenuBtn.addEventListener("click", () => { + sidebar.classList.toggle("hidden"); + sidebar.classList.toggle("-translate-x-full"); + // Toggle the class on #app so it shifts right + app.classList.toggle("sidebar-open"); + }); + } + + return import("./sidebar.js").then((module) => { + module.setupSidebarNavigation(); + }); + }) + .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"); + } + }); + } + + // 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"); + } + }); + } + + // 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"); + } + }); + } + + // 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"); + } + } + }); + } + + // 5) ?modal=appeals => open content appeals form + const urlParams = new URLSearchParams(window.location.search); + const modalParam = urlParams.get("modal"); + + if (modalParam === "appeals") { + const appealsModal = document.getElementById("contentAppealsModal"); + if (appealsModal) { + appealsModal.classList.remove("hidden"); + } + const closeAppealsBtn = document.getElementById( + "closeContentAppealsModal" + ); + if (closeAppealsBtn) { + closeAppealsBtn.addEventListener("click", () => { + appealsModal.classList.add("hidden"); + if (!localStorage.getItem("hasSeenDisclaimer")) { + const disclaimerModal = document.getElementById("disclaimerModal"); + if (disclaimerModal) { + disclaimerModal.classList.remove("hidden"); + } + } + }); + } + } else if (modalParam === "application") { + const appModal = document.getElementById("nostrFormModal"); + if (appModal) { + appModal.classList.remove("hidden"); + } + } else { + // If there's no special param, disclaimers can show if user hasn't seen them + const hasSeenDisclaimer = localStorage.getItem("hasSeenDisclaimer"); + if (!hasSeenDisclaimer) { + const disclaimerModal = document.getElementById("disclaimerModal"); + if (disclaimerModal) { + disclaimerModal.classList.remove("hidden"); + } + } + } + + // 6) Close content appeals modal if needed + const closeAppealsBtn = document.getElementById("closeContentAppealsModal"); + if (closeAppealsBtn) { + closeAppealsBtn.addEventListener("click", () => { + const appealsModal = document.getElementById("contentAppealsModal"); + if (appealsModal) { + appealsModal.classList.add("hidden"); + } + }); + } + + // 7) Disclaimer 'I Understand' Button + const acceptDisclaimerBtn = document.getElementById("acceptDisclaimer"); + if (acceptDisclaimerBtn) { + acceptDisclaimerBtn.addEventListener("click", () => { + const disclaimerModal = document.getElementById("disclaimerModal"); + if (disclaimerModal) { + disclaimerModal.classList.add("hidden"); + } + localStorage.setItem("hasSeenDisclaimer", "true"); + }); + } + + // 8) Query param checks for the three new forms + if (modalParam === "feedback") { + const feedbackModal = document.getElementById("generalFeedbackModal"); + if (feedbackModal) { + feedbackModal.classList.remove("hidden"); + } + } else if (modalParam === "feature") { + const featureModal = document.getElementById("featureRequestModal"); + if (featureModal) { + featureModal.classList.remove("hidden"); + } + } else if (modalParam === "bug") { + const bugModal = document.getElementById("bugFixModal"); + if (bugModal) { + bugModal.classList.remove("hidden"); + } + } + + // 9) Close buttons for the new forms + const closeFeedbackBtn = document.getElementById( + "closeGeneralFeedbackModal" + ); + if (closeFeedbackBtn) { + closeFeedbackBtn.addEventListener("click", () => { + const feedbackModal = document.getElementById("generalFeedbackModal"); + if (feedbackModal) { + feedbackModal.classList.add("hidden"); + } + }); + } + const closeFeatureBtn = document.getElementById("closeFeatureRequestModal"); + if (closeFeatureBtn) { + closeFeatureBtn.addEventListener("click", () => { + const featureModal = document.getElementById("featureRequestModal"); + if (featureModal) { + featureModal.classList.add("hidden"); + } + }); + } + const closeBugBtn = document.getElementById("closeBugFixModal"); + if (closeBugBtn) { + closeBugBtn.addEventListener("click", () => { + const bugModal = document.getElementById("bugFixModal"); + if (bugModal) { + bugModal.classList.add("hidden"); + } + }); + } + }); diff --git a/src/js/sidebar.js b/src/js/sidebar.js new file mode 100644 index 0000000..1b6239f --- /dev/null +++ b/src/js/sidebar.js @@ -0,0 +1,46 @@ +import { loadView } from "./viewManager.js"; + +/** + * Wire up the sidebar links. + * Home => loads the "most-recent-videos" partial and re-renders videos + * Explore => loads explore.html with a "Coming Soon" message + * Subscriptions => loads subscriptions.html with a "Coming Soon" message + */ +export function setupSidebarNavigation() { + // 1) Home + const homeLink = document.querySelector('a[href="#view=most-recent-videos"]'); + if (homeLink) { + homeLink.addEventListener("click", (e) => { + e.preventDefault(); + loadView("views/most-recent-videos.html").then(() => { + // Once the partial is loaded, reassign #videoList + call loadVideos + if (window.app && window.app.loadVideos) { + window.app.videoList = document.getElementById("videoList"); + window.app.loadVideos(); + } + }); + }); + } + + // 2) Explore + const exploreLink = document.querySelector('a[href="#view=explore"]'); + if (exploreLink) { + exploreLink.addEventListener("click", (e) => { + e.preventDefault(); + loadView("views/explore.html"); + // We just show the partial. No dynamic videos needed yet. + }); + } + + // 3) Subscriptions + const subscriptionsLink = document.querySelector( + 'a[href="#view=subscriptions"]' + ); + if (subscriptionsLink) { + subscriptionsLink.addEventListener("click", (e) => { + e.preventDefault(); + loadView("views/subscriptions.html"); + // Also "Coming Soon" in that partial for now. + }); + } +} diff --git a/src/js/webtorrent.js b/src/js/webtorrent.js index 37519ac..97fb6e2 100644 --- a/src/js/webtorrent.js +++ b/src/js/webtorrent.js @@ -175,7 +175,7 @@ export class TorrentClient { return reject(new Error("No compatible video file found in torrent")); } - videoElement.muted = true; + videoElement.muted = false; videoElement.crossOrigin = "anonymous"; videoElement.addEventListener("error", (e) => { diff --git a/src/views/explore.html b/src/views/explore.html new file mode 100644 index 0000000..a73582f --- /dev/null +++ b/src/views/explore.html @@ -0,0 +1,5 @@ + +
+

Explore

+

Coming Soon...

+
diff --git a/src/views/subscriptions.html b/src/views/subscriptions.html new file mode 100644 index 0000000..38545fa --- /dev/null +++ b/src/views/subscriptions.html @@ -0,0 +1,5 @@ + +
+

Subscriptions

+

Coming Soon...

+
diff --git a/whitelistform.txt b/whitelistform.txt deleted file mode 100644 index a469d43..0000000 --- a/whitelistform.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file