From 179afa67801f7720c1d73d1b78cba9c338c2cb26 Mon Sep 17 00:00:00 2001
From: Namyts <35004248+Namyts@users.noreply.github.com>
Date: Tue, 23 Jul 2024 00:45:29 +0100
Subject: [PATCH] added context menu to steam list items with ability to copy
stream infohash. switch steam to use Popup, just like Video
---
.../MetaDetails/StreamsList/Stream/Stream.js | 270 +++++++++++++-----
.../StreamsList/Stream/styles.less | 61 ++++
.../MetaDetails/StreamsList/StreamsList.js | 195 +++++++------
3 files changed, 349 insertions(+), 177 deletions(-)
diff --git a/src/routes/MetaDetails/StreamsList/Stream/Stream.js b/src/routes/MetaDetails/StreamsList/Stream/Stream.js
index d386cbf95..5cb31b251 100644
--- a/src/routes/MetaDetails/StreamsList/Stream/Stream.js
+++ b/src/routes/MetaDetails/StreamsList/Stream/Stream.js
@@ -4,47 +4,93 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
-const { Button, Image, useProfile, platform, useToast } = require('stremio/common');
+const {
+ Button,
+ Image,
+ useProfile,
+ platform,
+ useToast,
+ Popup,
+ useBinaryState,
+} = require('stremio/common');
const { useServices } = require('stremio/services');
const StreamPlaceholder = require('./StreamPlaceholder');
+const { t } = require('i18next');
+
const styles = require('./styles');
-const Stream = ({ className, videoId, videoReleased, addonName, name, description, thumbnail, progress, deepLinks, ...props }) => {
+const Stream = ({
+ className,
+ videoId,
+ videoReleased,
+ addonName,
+ name,
+ description,
+ thumbnail,
+ progress,
+ deepLinks,
+ infoHash,
+ ...props
+}) => {
const profile = useProfile();
const toast = useToast();
const { core } = useServices();
+ const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false);
+
+ const popupLabelOnMouseUp = React.useCallback((event) => {
+ if (!event.nativeEvent.togglePopupPrevented) {
+ if (event.nativeEvent.ctrlKey || event.nativeEvent.button === 2) {
+ event.preventDefault();
+ toggleMenu();
+ }
+ }
+ }, []);
+ const popupLabelOnContextMenu = React.useCallback((event) => {
+ if (!event.nativeEvent.togglePopupPrevented && !event.nativeEvent.ctrlKey) {
+ event.preventDefault();
+ }
+ }, [toggleMenu]);
+ const popupLabelOnLongPress = React.useCallback((event) => {
+ if (event.nativeEvent.pointerType !== 'mouse' && !event.nativeEvent.togglePopupPrevented) {
+ toggleMenu();
+ }
+ }, [toggleMenu]);
+ const popupMenuOnPointerDown = React.useCallback((event) => {
+ event.nativeEvent.togglePopupPrevented = true;
+ }, []);
+ const popupMenuOnContextMenu = React.useCallback((event) => {
+ event.nativeEvent.togglePopupPrevented = true;
+ }, []);
+ const popupMenuOnClick = React.useCallback((event) => {
+ event.nativeEvent.togglePopupPrevented = true;
+ }, []);
+ const popupMenuOnKeyDown = React.useCallback((event) => {
+ event.nativeEvent.buttonClickPrevented = true;
+ }, []);
+
const href = React.useMemo(() => {
- return deepLinks ?
- deepLinks.externalPlayer ?
- deepLinks.externalPlayer.web ?
- deepLinks.externalPlayer.web
- :
- deepLinks.externalPlayer.openPlayer ?
- deepLinks.externalPlayer.openPlayer[platform.name] ?
- deepLinks.externalPlayer.openPlayer[platform.name]
- :
- deepLinks.externalPlayer.playlist
- :
- deepLinks.player
- :
- deepLinks.player
- :
- null;
+ return deepLinks
+ ? deepLinks.externalPlayer
+ ? deepLinks.externalPlayer.web
+ ? deepLinks.externalPlayer.web
+ : deepLinks.externalPlayer.openPlayer
+ ? deepLinks.externalPlayer.openPlayer[platform.name]
+ ? deepLinks.externalPlayer.openPlayer[platform.name]
+ : deepLinks.externalPlayer.playlist
+ : deepLinks.player
+ : deepLinks.player
+ : null;
}, [deepLinks]);
const download = React.useMemo(() => {
- return href === deepLinks?.externalPlayer?.playlist ?
- deepLinks.externalPlayer.fileName
- :
- null;
+ return href === deepLinks?.externalPlayer?.playlist
+ ? deepLinks.externalPlayer.fileName
+ : null;
}, [href, deepLinks]);
const target = React.useMemo(() => {
- return href === deepLinks?.externalPlayer?.web ?
- '_blank'
- :
- null;
+ return href === deepLinks?.externalPlayer?.web ? '_blank' : null;
}, [href, deepLinks]);
const markVideoAsWatched = React.useCallback(() => {
@@ -53,68 +99,136 @@ const Stream = ({ className, videoId, videoReleased, addonName, name, descriptio
action: 'MetaDetails',
args: {
action: 'MarkVideoAsWatched',
- args: [{ id: videoId, released: videoReleased }, true]
- }
+ args: [{ id: videoId, released: videoReleased }, true],
+ },
});
}
}, [videoId, videoReleased]);
- const onClick = React.useCallback((event) => {
- if (profile.settings.playerType !== null) {
- markVideoAsWatched();
+ const onClick = React.useCallback(
+ (event) => {
+ if (profile.settings.playerType !== null) {
+ markVideoAsWatched();
+ toast.show({
+ type: 'success',
+ title: 'Stream opened in external player',
+ timeout: 4000,
+ });
+ }
+
+ if (typeof props.onClick === 'function') {
+ props.onClick(event);
+ }
+ },
+ [props.onClick, profile.settings, markVideoAsWatched]
+ );
+
+ const copyInfoHashToClipboard = React.useCallback((event) => {
+ event.preventDefault();
+ if (infoHash && navigator?.clipboard) {
+ navigator?.clipboard?.writeText(infoHash);
toast.show({
type: 'success',
- title: 'Stream opened in external player',
- timeout: 4000
+ title: t('PLAYER_COPY_DOWNLOAD_LINK_SUCCESS'),
+ timeout: 4000,
});
}
+ }, []);
- if (typeof props.onClick === 'function') {
- props.onClick(event);
- }
- }, [props.onClick, profile.settings, markVideoAsWatched]);
+ const renderThumbnailFallback = React.useCallback(
+ () => (
+