Files
buzz/desktop/src/shared/ui/useVideoContextMenu.tsx
Tyler 084e442d2d Bug-bash: mention/code-span, feed titles, autocomplete, edit-mentions, draft routing, and right-click media Download (#2027)
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>
2026-07-17 10:27:41 -04:00

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,
};
}