mirror of
https://github.com/block/buzz.git
synced 2026-10-29 21:41:33 +00:00
Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co> Signed-off-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
84 lines
2.7 KiB
TypeScript
84 lines
2.7 KiB
TypeScript
import * as React from "react";
|
|
|
|
import { invokeTauri } from "@/shared/api/tauri";
|
|
import { copyTextToClipboard } from "@/shared/lib/clipboard";
|
|
import {
|
|
MediaContextMenu,
|
|
type MediaContextMenuItem,
|
|
type MediaContextMenuPosition,
|
|
useDismissMediaContextMenu,
|
|
} from "@/shared/ui/markdown/MediaContextMenu";
|
|
import { resolveVideoDownloadFilename } from "@/shared/ui/videoDownload";
|
|
import { toast } from "sonner";
|
|
|
|
type UseVideoContextMenu = {
|
|
/** `onContextMenuCapture` handler for the inline video surface. */
|
|
onContextMenu: (event: React.MouseEvent) => void;
|
|
/** The positioned menu element while open, or `null`. */
|
|
menu: React.ReactNode;
|
|
};
|
|
|
|
/**
|
|
* Owns the inline video right-click menu: open/close state, the pointer-anchor
|
|
* handler, and the Download/Copy actions. Kept out of `VideoPlayer` so that
|
|
* large component stays focused on playback, and out of the pure
|
|
* `videoDownload.ts` helpers so they keep their DOM-free, Node-testable shape.
|
|
*
|
|
* `downloadUrl` is the original relay `/media/` URL (distinct from a rewritten
|
|
* proxy `src`); when absent the menu omits Download and offers only Copy link,
|
|
* so a non-relay video — which the download command's SSRF gate would reject —
|
|
* never surfaces an action that could only error.
|
|
*/
|
|
export function useVideoContextMenu(
|
|
src: string,
|
|
downloadUrl?: string,
|
|
filename?: string,
|
|
): UseVideoContextMenu {
|
|
const [position, setPosition] =
|
|
React.useState<MediaContextMenuPosition | null>(null);
|
|
const close = React.useCallback(() => setPosition(null), []);
|
|
useDismissMediaContextMenu(Boolean(position), close);
|
|
|
|
const onContextMenu = React.useCallback((event: React.MouseEvent) => {
|
|
event.preventDefault();
|
|
setPosition({ x: event.clientX, y: event.clientY });
|
|
}, []);
|
|
|
|
const items = React.useMemo<MediaContextMenuItem[]>(() => {
|
|
const entries: MediaContextMenuItem[] = [];
|
|
if (downloadUrl) {
|
|
entries.push({
|
|
label: "Download video",
|
|
onSelect: () => {
|
|
close();
|
|
invokeTauri("download_file", {
|
|
url: downloadUrl,
|
|
filename: resolveVideoDownloadFilename(filename),
|
|
}).catch((err: unknown) => {
|
|
toast.error(err instanceof Error ? err.message : "Download failed");
|
|
});
|
|
},
|
|
});
|
|
}
|
|
entries.push({
|
|
label: "Copy link",
|
|
onSelect: () => {
|
|
close();
|
|
copyTextToClipboard(downloadUrl ?? src, "Link copied to clipboard");
|
|
},
|
|
});
|
|
return entries;
|
|
}, [close, downloadUrl, filename, src]);
|
|
|
|
return {
|
|
onContextMenu,
|
|
menu: position ? (
|
|
<MediaContextMenu
|
|
dataAttributes={["data-video-context-menu"]}
|
|
items={items}
|
|
position={position}
|
|
/>
|
|
) : null,
|
|
};
|
|
}
|