mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-28 16:32:28 +00:00
Refactor video modal Share button to use a popover menu
- Replaced direct copy-to-clipboard action with a Share menu. - Added "Copy URL", "Share on Nostr", "Copy Magnet", and "Copy CDN Link" options. - Moved "Copy Magnet" from the main modal interface to the menu. - Updated `js/app.js` to handle `video:share-nostr` (placeholder), `video:copy-magnet`, and `video:copy-cdn` events. - Removed deprecated `setCopyMagnetState` logic. - Cleaned up `components/video-modal.html`.
This commit is contained in:
1 parent
bd868fb7ea
commit
0ec517d689
9 files changed
+243
-139
No files matched your search
@@ -445,6 +445,29 @@ class Application {
|
||||
this.videoModal &&
|
||||
typeof this.videoModal.addEventListener === "function"
|
||||
) {
|
||||
this.videoModal.addEventListener("video:share-nostr", () => {
|
||||
this.showStatus(
|
||||
"Sharing on Nostr is not implemented yet. Coming soon!",
|
||||
);
|
||||
});
|
||||
|
||||
this.videoModal.addEventListener("video:copy-cdn", (event) => {
|
||||
const video = event?.detail?.video || this.currentVideo;
|
||||
const url = video?.url || "";
|
||||
if (!url) {
|
||||
this.showError("No CDN link available to copy.");
|
||||
return;
|
||||
}
|
||||
navigator.clipboard
|
||||
.writeText(url)
|
||||
.then(() => this.showSuccess("CDN link copied to clipboard!"))
|
||||
.catch(() => this.showError("Failed to copy CDN link."));
|
||||
});
|
||||
|
||||
this.videoModal.addEventListener("video:copy-magnet", () => {
|
||||
this.handleCopyMagnet();
|
||||
});
|
||||
|
||||
this.videoModal.addEventListener("playback:switch-source", (event) => {
|
||||
const detail = event?.detail || {};
|
||||
const { source } = detail;
|
||||
@@ -5094,11 +5117,6 @@ class Application {
|
||||
}
|
||||
}
|
||||
|
||||
setCopyMagnetState(enabled) {
|
||||
if (this.videoModal) {
|
||||
this.videoModal.setCopyEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
setShareButtonState(enabled) {
|
||||
if (this.videoModal) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { createModalAccessibility } from "./modalAccessibility.js";
|
||||
import createPopover from "../overlay/popoverEngine.js";
|
||||
import { createVideoMoreMenuPanel } from "./videoMenuRenderers.js";
|
||||
import {
|
||||
createVideoMoreMenuPanel,
|
||||
createVideoShareMenuPanel,
|
||||
} from "./videoMenuRenderers.js";
|
||||
import { attachAmbientBackground } from "../ambientBackground.js";
|
||||
import { applyDesignSystemAttributes } from "../../designSystem.js";
|
||||
import { devLogger } from "../../utils/logger.js";
|
||||
@@ -661,6 +664,7 @@ export class VideoModal {
|
||||
this.modalZapDialogOpen = false;
|
||||
this.setupModalZapPopover();
|
||||
this.setupModalMorePopover();
|
||||
this.setupModalSharePopover();
|
||||
|
||||
this.refreshActiveVideoModeration({ video: this.activeVideo });
|
||||
|
||||
@@ -2220,7 +2224,8 @@ export class VideoModal {
|
||||
if (this.copyMagnetBtn) {
|
||||
this.copyMagnetBtn.addEventListener("click", this.handleCopyRequest);
|
||||
}
|
||||
if (this.shareBtn) {
|
||||
if (this.shareBtn && this.shareBtn.dataset.modalMenuHandler !== "true") {
|
||||
this.shareBtn.dataset.modalMenuHandler = "true";
|
||||
this.shareBtn.addEventListener("click", this.handleShareRequest);
|
||||
}
|
||||
|
||||
@@ -2523,6 +2528,149 @@ export class VideoModal {
|
||||
this.modalMorePopover = popover;
|
||||
}
|
||||
|
||||
setupModalSharePopover() {
|
||||
if (!this.shareBtn) {
|
||||
this.modalSharePopover = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const documentRef =
|
||||
this.shareBtn.ownerDocument ||
|
||||
this.document ||
|
||||
(typeof document !== "undefined" ? document : null);
|
||||
|
||||
const render = ({ document: doc, close }) => {
|
||||
const panel = this.buildModalShareMenuPanel({ document: doc, close });
|
||||
return panel;
|
||||
};
|
||||
|
||||
const popover = createPopover(this.shareBtn, render, {
|
||||
document: documentRef,
|
||||
placement: "bottom-end",
|
||||
restoreFocusOnClose: true,
|
||||
});
|
||||
|
||||
if (!popover) {
|
||||
this.modalSharePopover = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const originalOpen = popover.open?.bind(popover);
|
||||
if (originalOpen) {
|
||||
popover.open = async (...args) => {
|
||||
this.refreshModalShareMenuPanel();
|
||||
return originalOpen(...args);
|
||||
};
|
||||
}
|
||||
|
||||
const originalDestroy = popover.destroy?.bind(popover);
|
||||
if (originalDestroy) {
|
||||
popover.destroy = (...args) => {
|
||||
originalDestroy(...args);
|
||||
if (this.modalSharePopover === popover) {
|
||||
this.modalSharePopover = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.shareBtn.dataset.shareMenuToggleBound = "true";
|
||||
this.modalSharePopover = popover;
|
||||
}
|
||||
|
||||
buildModalShareMenuPanel({ document: doc, close }) {
|
||||
const isLoggedIn =
|
||||
this.commentComposerState.reason !== "login-required" &&
|
||||
!this.commentComposerState.disabled;
|
||||
|
||||
const hasMagnet = Boolean(
|
||||
(typeof this.activeVideo?.magnet === "string" &&
|
||||
this.activeVideo.magnet.trim()) ||
|
||||
this.activeVideo?.torrentSupported
|
||||
);
|
||||
|
||||
const hasCdn = Boolean(
|
||||
typeof this.activeVideo?.url === "string" && this.activeVideo.url.trim()
|
||||
);
|
||||
|
||||
const panel = createVideoShareMenuPanel({
|
||||
document: doc,
|
||||
video: this.activeVideo,
|
||||
isLoggedIn,
|
||||
hasMagnet,
|
||||
hasCdn,
|
||||
context: "modal",
|
||||
});
|
||||
|
||||
if (!panel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const buttons = panel.querySelectorAll("button[data-action]");
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const dataset = {};
|
||||
Object.entries(button.dataset || {}).forEach(([key, value]) => {
|
||||
dataset[key] = value;
|
||||
});
|
||||
if (!dataset.context) {
|
||||
dataset.context = "modal";
|
||||
}
|
||||
|
||||
const action = dataset.action || "";
|
||||
|
||||
// Map menu actions to events
|
||||
if (action === "share") {
|
||||
this.dispatch("video:share", { video: this.activeVideo });
|
||||
} else if (action === "share-nostr") {
|
||||
this.dispatch("video:share-nostr", { video: this.activeVideo });
|
||||
} else if (action === "copy-magnet") {
|
||||
this.dispatch("video:copy-magnet", { video: this.activeVideo });
|
||||
} else if (action === "copy-cdn") {
|
||||
this.dispatch("video:copy-cdn", { video: this.activeVideo });
|
||||
}
|
||||
|
||||
if (typeof close === "function") {
|
||||
close({ reason: "action" });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
refreshModalShareMenuPanel() {
|
||||
if (!this.modalSharePopover) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingPanel = this.modalSharePopover.getPanel?.();
|
||||
const documentRef =
|
||||
existingPanel?.ownerDocument ||
|
||||
this.shareBtn?.ownerDocument ||
|
||||
this.document ||
|
||||
(typeof document !== "undefined" ? document : null);
|
||||
|
||||
if (!documentRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextPanel = this.buildModalShareMenuPanel({
|
||||
document: documentRef,
|
||||
close: (options) => this.modalSharePopover?.close(options),
|
||||
});
|
||||
|
||||
if (!nextPanel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingPanel && existingPanel.parentNode) {
|
||||
existingPanel.parentNode.replaceChild(nextPanel, existingPanel);
|
||||
}
|
||||
}
|
||||
|
||||
buildModalMoreMenuPanel({ document: doc, close }) {
|
||||
const panel = createVideoMoreMenuPanel({
|
||||
document: doc,
|
||||
@@ -2675,10 +2823,19 @@ export class VideoModal {
|
||||
|
||||
handleShareRequest(event) {
|
||||
event?.preventDefault?.();
|
||||
event?.stopPropagation?.();
|
||||
if (this.shareBtn?.disabled) {
|
||||
return;
|
||||
}
|
||||
this.dispatch("video:share", { video: this.activeVideo });
|
||||
if (this.modalSharePopover?.toggle) {
|
||||
this.modalSharePopover.toggle();
|
||||
return;
|
||||
}
|
||||
if (this.modalSharePopover?.isOpen?.()) {
|
||||
this.modalSharePopover.close?.();
|
||||
} else {
|
||||
this.modalSharePopover?.open?.();
|
||||
}
|
||||
}
|
||||
|
||||
handleCreatorNavigation(event) {
|
||||
|
||||
@@ -309,6 +309,66 @@ export function createVideoMoreMenuPanel({
|
||||
return panel;
|
||||
}
|
||||
|
||||
export function createVideoShareMenuPanel({
|
||||
document: documentRef = null,
|
||||
video = null,
|
||||
isLoggedIn = false,
|
||||
hasMagnet = false,
|
||||
hasCdn = false,
|
||||
designSystem = null,
|
||||
} = {}) {
|
||||
const doc = resolveDocument(documentRef);
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
normalizeDesignSystemContext(designSystem);
|
||||
|
||||
const metadata = resolveVideoMetadata(video);
|
||||
const { panel, list } = ensureMenuContainer(doc, ["w-48", "p-0"]);
|
||||
panel.dataset.menu = "video-share";
|
||||
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Copy URL",
|
||||
action: "share",
|
||||
dataset: { eventId: metadata.id },
|
||||
});
|
||||
|
||||
const nostrBtn = appendMenuAction(doc, list, {
|
||||
text: "Share on Nostr",
|
||||
action: "share-nostr",
|
||||
dataset: { eventId: metadata.id },
|
||||
});
|
||||
if (!isLoggedIn) {
|
||||
nostrBtn.disabled = true;
|
||||
nostrBtn.classList.add("opacity-50", "cursor-not-allowed");
|
||||
nostrBtn.title = "Log in to share on Nostr";
|
||||
}
|
||||
|
||||
const magnetBtn = appendMenuAction(doc, list, {
|
||||
text: "Copy Magnet",
|
||||
action: "copy-magnet",
|
||||
dataset: { eventId: metadata.id },
|
||||
});
|
||||
if (!hasMagnet) {
|
||||
magnetBtn.disabled = true;
|
||||
magnetBtn.classList.add("opacity-50", "cursor-not-allowed");
|
||||
magnetBtn.title = "No magnet link available";
|
||||
}
|
||||
|
||||
const cdnBtn = appendMenuAction(doc, list, {
|
||||
text: "Copy CDN Link",
|
||||
action: "copy-cdn",
|
||||
dataset: { eventId: metadata.id },
|
||||
});
|
||||
if (!hasCdn) {
|
||||
cdnBtn.disabled = true;
|
||||
cdnBtn.classList.add("opacity-50", "cursor-not-allowed");
|
||||
cdnBtn.title = "No CDN link available";
|
||||
}
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
export function createChannelProfileMenuPanel({
|
||||
document: documentRef = null,
|
||||
context = "channel-profile",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 327 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 325 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 327 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 315 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 327 KiB |
@@ -1,131 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
def verify_share_menu(page):
|
||||
print("Navigating to app...")
|
||||
page.goto("http://localhost:8000")
|
||||
|
||||
# Wait for app to load
|
||||
print("Waiting for app to load...")
|
||||
try:
|
||||
# Just wait for anything to indicate readiness, cards might be lazy loaded
|
||||
page.wait_for_load_state("networkidle", timeout=5000)
|
||||
# Check for card container
|
||||
page.wait_for_selector("#videoList", timeout=5000)
|
||||
except Exception as e:
|
||||
print(f"Initial load wait error (ignoring): {e}")
|
||||
|
||||
# Handle disclaimer if present
|
||||
try:
|
||||
print("Checking for disclaimer...")
|
||||
disclaimer = page.query_selector("#disclaimerAgreeBtn")
|
||||
if disclaimer and disclaimer.is_visible():
|
||||
print("Disclaimer found, clicking agree...")
|
||||
page.click("#disclaimerAgreeBtn")
|
||||
page.wait_for_selector("#disclaimerModal", state="hidden", timeout=3000)
|
||||
print("Disclaimer dismissed.")
|
||||
else:
|
||||
print("No visible disclaimer found.")
|
||||
except Exception as e:
|
||||
print(f"Disclaimer check exception: {e}")
|
||||
|
||||
# Force a refresh of the cards or wait explicitly
|
||||
print("Waiting for any card...")
|
||||
try:
|
||||
page.wait_for_selector(".card", timeout=15000)
|
||||
except Exception as e:
|
||||
print(f"Failed to find cards: {e}")
|
||||
page.screenshot(path="verification/debug_no_cards.png")
|
||||
sys.exit(1)
|
||||
|
||||
# Click the first video card to open the modal
|
||||
print("Opening video modal...")
|
||||
try:
|
||||
# Use JS click as it is most reliable against overlays/scrolling
|
||||
page.evaluate("() => { const card = document.querySelector(\".card\"); if(card) { card.scrollIntoView(); card.click(); } else { throw new Error(\"No card found\"); } }")
|
||||
except Exception as e:
|
||||
print(f"Click evaluation failed: {e}")
|
||||
page.screenshot(path="verification/debug_click_fail.png")
|
||||
sys.exit(1)
|
||||
|
||||
# Wait for modal to open
|
||||
print("Waiting for modal...")
|
||||
try:
|
||||
# Check if modal is visible - use a function that returns true immediately if visible
|
||||
# We loop here to be safe and debug
|
||||
for i in range(20):
|
||||
is_visible = page.evaluate("() => { const el = document.getElementById(playerModal); return el && !el.hasAttribute(hidden) && !el.classList.contains(hidden); }")
|
||||
if is_visible:
|
||||
print("Modal is confirmed visible via JS.")
|
||||
break
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
print("Modal did not become visible.")
|
||||
page.screenshot(path="verification/debug_modal_not_visible.png")
|
||||
# sys.exit(1) # Dont exit yet, check if its maybe just taking long or other issue
|
||||
|
||||
except Exception as e:
|
||||
print(f"Modal check failed: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Verify "Copy Magnet" button is NOT in the main actions group
|
||||
print("Verifying absence of Copy Magnet button in main UI...")
|
||||
magnet_btn = page.query_selector("#copyMagnetBtn")
|
||||
if magnet_btn and page.evaluate("el => !el.hasAttribute(\"hidden\") && !el.classList.contains(\"hidden\")", magnet_btn):
|
||||
print("ERROR: Copy Magnet button is still visible in the main UI!")
|
||||
sys.exit(1)
|
||||
|
||||
# Find and click the Share button
|
||||
print("Clicking Share button...")
|
||||
try:
|
||||
# Ensure share button is visible/clickable
|
||||
# If modal wasnt visible to playwright but IS visible to JS, we might need force=True
|
||||
page.click("#shareBtn", force=True)
|
||||
except Exception as e:
|
||||
print(f"Failed to click share button: {e}")
|
||||
page.screenshot(path="verification/debug_share_btn_fail.png")
|
||||
sys.exit(1)
|
||||
|
||||
# Wait for the popover menu to appear
|
||||
print("Waiting for Share menu...")
|
||||
try:
|
||||
page.wait_for_selector("[data-menu=\"video-share\"]", timeout=5000)
|
||||
except Exception as e:
|
||||
print(f"Share menu did not appear: {e}")
|
||||
page.screenshot(path="verification/debug_share_menu_fail.png")
|
||||
sys.exit(1)
|
||||
|
||||
# Take a screenshot of the open menu
|
||||
print("Taking screenshot...")
|
||||
page.screenshot(path="verification/share_menu_verification.png")
|
||||
|
||||
# Verify menu items
|
||||
print("Verifying menu items...")
|
||||
menu = page.locator("[data-menu=\"video-share\"]")
|
||||
text_content = menu.inner_text()
|
||||
print(f"Menu content: {text_content}")
|
||||
|
||||
if "Copy URL" not in text_content:
|
||||
print("ERROR: Copy URL item missing")
|
||||
if "Share on Nostr" not in text_content:
|
||||
print("ERROR: Share on Nostr item missing")
|
||||
if "Copy Magnet" not in text_content:
|
||||
print("ERROR: Copy Magnet item missing")
|
||||
if "Copy CDN Link" not in text_content:
|
||||
print("ERROR: Copy CDN Link item missing")
|
||||
|
||||
print("Verification complete!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page()
|
||||
try:
|
||||
verify_share_menu(page)
|
||||
except Exception as e:
|
||||
print(f"Verification failed: {e}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
browser.close()
|
||||
Reference in new issue
Block a user