mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-03 03:12:31 +00:00
Refactor video menus to overlay popovers
This commit is contained in:
1 parent
5928ede539
commit
7ef16cd004
6 files changed
+975
-529
No files matched your search
@@ -0,0 +1,344 @@
|
||||
import { normalizeDesignSystemContext } from "../../designSystem.js";
|
||||
|
||||
function resolveDocument(documentRef) {
|
||||
if (documentRef && documentRef.nodeType === 9) {
|
||||
return documentRef;
|
||||
}
|
||||
if (typeof document !== "undefined" && document?.nodeType === 9) {
|
||||
return document;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function createElement(doc, tagName, { classNames = [], attrs = {}, textContent } = {}) {
|
||||
const el = doc.createElement(tagName);
|
||||
classNames
|
||||
.filter((name) => typeof name === "string" && name.trim())
|
||||
.forEach((name) => el.classList.add(name));
|
||||
Object.entries(attrs).forEach(([key, value]) => {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return;
|
||||
}
|
||||
el.setAttribute(key, value);
|
||||
});
|
||||
if (typeof textContent === "string") {
|
||||
el.textContent = textContent;
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
function normalizePointer(pointerInfo) {
|
||||
if (!pointerInfo || typeof pointerInfo !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pointer = Array.isArray(pointerInfo.pointer)
|
||||
? pointerInfo.pointer
|
||||
: null;
|
||||
if (!pointer || pointer.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [type, value, relay] = pointer;
|
||||
const normalized = {
|
||||
key: typeof pointerInfo.key === "string" ? pointerInfo.key : "",
|
||||
type: typeof type === "string" ? type : "",
|
||||
value: typeof value === "string" ? value : "",
|
||||
relay: typeof relay === "string" ? relay : "",
|
||||
};
|
||||
|
||||
if (!normalized.type || !normalized.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function resolveVideoMetadata(video) {
|
||||
if (!video || typeof video !== "object") {
|
||||
return {
|
||||
id: "",
|
||||
pubkey: "",
|
||||
title: "",
|
||||
description: "",
|
||||
thumbnail: "",
|
||||
isPrivate: false,
|
||||
kind: null,
|
||||
};
|
||||
}
|
||||
|
||||
const normalized = {
|
||||
id: typeof video.id === "string" ? video.id : "",
|
||||
pubkey: typeof video.pubkey === "string" ? video.pubkey : "",
|
||||
title: typeof video.title === "string" ? video.title : "",
|
||||
description:
|
||||
typeof video.description === "string" ? video.description : "",
|
||||
thumbnail:
|
||||
typeof video.thumbnail === "string" ? video.thumbnail : "",
|
||||
isPrivate: video.isPrivate === true,
|
||||
};
|
||||
|
||||
if (Number.isFinite(video.kind) && video.kind > 0) {
|
||||
normalized.kind = Math.floor(video.kind);
|
||||
} else {
|
||||
normalized.kind = null;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function ensureMenuContainer(doc, panelClassNames = []) {
|
||||
const panel = createElement(doc, "div", {
|
||||
classNames: ["popover__panel", ...panelClassNames],
|
||||
attrs: { role: "menu" },
|
||||
});
|
||||
|
||||
const list = createElement(doc, "div", {
|
||||
classNames: ["menu"],
|
||||
attrs: { role: "none" },
|
||||
});
|
||||
|
||||
panel.appendChild(list);
|
||||
return { panel, list };
|
||||
}
|
||||
|
||||
function appendMenuSeparator(doc, list) {
|
||||
const separator = createElement(doc, "div", {
|
||||
classNames: ["menu__separator"],
|
||||
attrs: { role: "separator" },
|
||||
});
|
||||
list.appendChild(separator);
|
||||
return separator;
|
||||
}
|
||||
|
||||
function appendMenuHeading(doc, list, text) {
|
||||
const heading = createElement(doc, "div", {
|
||||
classNames: ["menu__heading"],
|
||||
textContent: text,
|
||||
});
|
||||
list.appendChild(heading);
|
||||
return heading;
|
||||
}
|
||||
|
||||
function appendMenuAction(doc, list, {
|
||||
text,
|
||||
action,
|
||||
dataset = {},
|
||||
variant = null,
|
||||
context = "",
|
||||
}) {
|
||||
const button = createElement(doc, "button", {
|
||||
classNames: ["menu__item", "justify-start"],
|
||||
textContent: text,
|
||||
attrs: { type: "button", role: "menuitem" },
|
||||
});
|
||||
|
||||
button.dataset.action = action;
|
||||
if (variant) {
|
||||
button.dataset.variant = variant;
|
||||
}
|
||||
if (context) {
|
||||
button.dataset.context = context;
|
||||
}
|
||||
|
||||
Object.entries(dataset).forEach(([key, value]) => {
|
||||
if (value === undefined || value === null) {
|
||||
return;
|
||||
}
|
||||
button.dataset[key] = String(value);
|
||||
});
|
||||
|
||||
list.appendChild(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
export function createVideoMoreMenuPanel({
|
||||
document: documentRef = null,
|
||||
video = null,
|
||||
pointerInfo = null,
|
||||
playbackUrl = "",
|
||||
playbackMagnet = "",
|
||||
canManageBlacklist = false,
|
||||
context = "card",
|
||||
designSystem = null,
|
||||
} = {}) {
|
||||
const doc = resolveDocument(documentRef);
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
normalizeDesignSystemContext(designSystem);
|
||||
|
||||
const metadata = resolveVideoMetadata(video);
|
||||
const pointer = normalizePointer(pointerInfo);
|
||||
|
||||
const { panel, list } = ensureMenuContainer(doc, ["w-48", "p-0"]);
|
||||
panel.dataset.menu = "video-more";
|
||||
panel.dataset.menuContext = context;
|
||||
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Open channel",
|
||||
action: "open-channel",
|
||||
dataset: { author: metadata.pubkey, context },
|
||||
});
|
||||
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Copy link",
|
||||
action: "copy-link",
|
||||
dataset: { eventId: metadata.id, context },
|
||||
});
|
||||
|
||||
const baseBoostDataset = {
|
||||
eventId: metadata.id,
|
||||
author: metadata.pubkey,
|
||||
context,
|
||||
};
|
||||
|
||||
if (pointer) {
|
||||
baseBoostDataset.pointerType = pointer.type;
|
||||
baseBoostDataset.pointerValue = pointer.value;
|
||||
if (pointer.relay) {
|
||||
baseBoostDataset.pointerRelay = pointer.relay;
|
||||
}
|
||||
if (pointer.key) {
|
||||
baseBoostDataset.pointerKey = pointer.key;
|
||||
}
|
||||
}
|
||||
|
||||
if (Number.isFinite(metadata.kind) && metadata.kind > 0) {
|
||||
baseBoostDataset.kind = String(metadata.kind);
|
||||
}
|
||||
|
||||
appendMenuHeading(doc, list, "Boost on Nostr…");
|
||||
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Repost (kind 6)",
|
||||
action: "repost-event",
|
||||
dataset: baseBoostDataset,
|
||||
});
|
||||
|
||||
if (playbackUrl && metadata.isPrivate !== true) {
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Mirror (kind 1063)",
|
||||
action: "mirror-video",
|
||||
dataset: {
|
||||
...baseBoostDataset,
|
||||
url: playbackUrl,
|
||||
magnet: playbackMagnet || "",
|
||||
thumbnail: metadata.thumbnail,
|
||||
description: metadata.description,
|
||||
title: metadata.title,
|
||||
isPrivate: metadata.isPrivate ? "true" : "false",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Rebroadcast",
|
||||
action: "ensure-presence",
|
||||
dataset: {
|
||||
...baseBoostDataset,
|
||||
pubkey: metadata.pubkey,
|
||||
},
|
||||
});
|
||||
|
||||
appendMenuSeparator(doc, list);
|
||||
|
||||
if (pointer) {
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Remove from history",
|
||||
action: "remove-history",
|
||||
dataset: {
|
||||
pointerKey: pointer.key,
|
||||
pointerType: pointer.type,
|
||||
pointerValue: pointer.value,
|
||||
pointerRelay: pointer.relay,
|
||||
reason: "remove-item",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (canManageBlacklist) {
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Blacklist creator",
|
||||
action: "blacklist-author",
|
||||
variant: "critical",
|
||||
dataset: { author: metadata.pubkey },
|
||||
});
|
||||
}
|
||||
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Block creator",
|
||||
action: "block-author",
|
||||
variant: "critical",
|
||||
dataset: { author: metadata.pubkey },
|
||||
});
|
||||
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Report",
|
||||
action: "report",
|
||||
dataset: { eventId: metadata.id },
|
||||
});
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
export function createVideoSettingsMenuPanel({
|
||||
document: documentRef = null,
|
||||
video = null,
|
||||
index = 0,
|
||||
capabilities = {},
|
||||
designSystem = null,
|
||||
} = {}) {
|
||||
const doc = resolveDocument(documentRef);
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
normalizeDesignSystemContext(designSystem);
|
||||
|
||||
const metadata = resolveVideoMetadata(video);
|
||||
const normalizedIndex = Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0;
|
||||
const perms = {
|
||||
canEdit: capabilities?.canEdit === true,
|
||||
canRevert: capabilities?.canRevert === true,
|
||||
canDelete: capabilities?.canDelete === true,
|
||||
};
|
||||
|
||||
const { panel, list } = ensureMenuContainer(doc, ["w-44", "p-0"]);
|
||||
panel.dataset.menu = "video-settings";
|
||||
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Edit",
|
||||
action: "edit",
|
||||
dataset: {
|
||||
index: String(normalizedIndex),
|
||||
eventId: metadata.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (perms.canRevert) {
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Revert",
|
||||
action: "revert",
|
||||
variant: "critical",
|
||||
dataset: {
|
||||
index: String(normalizedIndex),
|
||||
eventId: metadata.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.canDelete) {
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Delete All",
|
||||
action: "delete",
|
||||
variant: "critical",
|
||||
dataset: {
|
||||
index: String(normalizedIndex),
|
||||
eventId: metadata.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user