mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 17:15:48 +00:00
Merge pull request #931 from Stremio/feat/user-item-ratings
MetaPreview: Implement user item ratings
This commit is contained in:
commit
306dd09f24
12 changed files with 187 additions and 14 deletions
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -12,8 +12,8 @@
|
|||
"@babel/runtime": "7.26.0",
|
||||
"@sentry/browser": "8.42.0",
|
||||
"@stremio/stremio-colors": "5.2.0",
|
||||
"@stremio/stremio-core-web": "0.49.3",
|
||||
"@stremio/stremio-icons": "5.4.1",
|
||||
"@stremio/stremio-core-web": "0.49.4",
|
||||
"@stremio/stremio-icons": "5.7.1",
|
||||
"@stremio/stremio-video": "0.0.60",
|
||||
"a-color-picker": "1.2.1",
|
||||
"bowser": "2.11.0",
|
||||
|
|
@ -3374,10 +3374,9 @@
|
|||
"integrity": "sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg=="
|
||||
},
|
||||
"node_modules/@stremio/stremio-core-web": {
|
||||
"version": "0.49.3",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.49.3.tgz",
|
||||
"integrity": "sha512-Ql/08LbwU99IUL6fOLy+v1Iv75boHXpunEPScKgXJALdq/OV5tZLG/IycN0O+5+50Nc/NHrI6HslnMNLTWA8JQ==",
|
||||
"license": "MIT",
|
||||
"version": "0.49.4",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.49.4.tgz",
|
||||
"integrity": "sha512-K9LJGKXs8juV3pZOHH6thWTwOShAhjFt9bLL6K1VlORAe6AiieZ2uRp9wdOwFmPX+UgzWLIOd0r2aFXJ4OsJCw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.24.1"
|
||||
}
|
||||
|
|
@ -3401,9 +3400,10 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stremio/stremio-icons": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-icons/-/stremio-icons-5.4.1.tgz",
|
||||
"integrity": "sha512-7g4JP7tPRT1UDZxbuH/Urq7fc6te3joy8qyx/NGWIW7wO169TTISO7ZWdejzESvUVgZ/7i6rzkRmXZ3wefWcBg==",
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-icons/-/stremio-icons-5.7.1.tgz",
|
||||
"integrity": "sha512-Z96p36LLX3G+ewMnFKmNZVsO/AtcHA33WQ3wGOYFubxiYADPRAkcLVU5rHIfiGSC9IUaUVhxQWTPVB9ScY4Q5Q==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"react",
|
||||
"react-native",
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
"@babel/runtime": "7.26.0",
|
||||
"@sentry/browser": "8.42.0",
|
||||
"@stremio/stremio-colors": "5.2.0",
|
||||
"@stremio/stremio-core-web": "0.49.3",
|
||||
"@stremio/stremio-icons": "5.4.1",
|
||||
"@stremio/stremio-core-web": "0.49.4",
|
||||
"@stremio/stremio-icons": "5.7.1",
|
||||
"@stremio/stremio-video": "0.0.60",
|
||||
"a-color-picker": "1.2.1",
|
||||
"bowser": "2.11.0",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const ActionButton = require('./ActionButton');
|
|||
const MetaLinks = require('./MetaLinks');
|
||||
const MetaPreviewPlaceholder = require('./MetaPreviewPlaceholder');
|
||||
const styles = require('./styles');
|
||||
const { Ratings } = require('./Ratings');
|
||||
|
||||
const ALLOWED_LINK_REDIRECTS = [
|
||||
routesRegexp.search.regexp,
|
||||
|
|
@ -24,7 +25,7 @@ const ALLOWED_LINK_REDIRECTS = [
|
|||
routesRegexp.metadetails.regexp
|
||||
];
|
||||
|
||||
const MetaPreview = React.forwardRef(({ className, compact, name, logo, background, runtime, releaseInfo, released, description, deepLinks, links, trailerStreams, inLibrary, toggleInLibrary }, ref) => {
|
||||
const MetaPreview = React.forwardRef(({ className, compact, name, logo, background, runtime, releaseInfo, released, description, deepLinks, links, trailerStreams, inLibrary, toggleInLibrary, ratingInfo }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [shareModalOpen, openShareModal, closeShareModal] = useBinaryState(false);
|
||||
const linksGroups = React.useMemo(() => {
|
||||
|
|
@ -232,6 +233,15 @@ const MetaPreview = React.forwardRef(({ className, compact, name, logo, backgrou
|
|||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
!compact && ratingInfo !== null ?
|
||||
<Ratings
|
||||
ratingInfo={ratingInfo}
|
||||
className={styles['ratings']}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
linksGroups.has(CONSTANTS.SHARE_LINK_CATEGORY) && !compact ?
|
||||
<React.Fragment>
|
||||
|
|
@ -287,7 +297,8 @@ MetaPreview.propTypes = {
|
|||
})),
|
||||
trailerStreams: PropTypes.array,
|
||||
inLibrary: PropTypes.bool,
|
||||
toggleInLibrary: PropTypes.func
|
||||
toggleInLibrary: PropTypes.func,
|
||||
ratingInfo: PropTypes.object,
|
||||
};
|
||||
|
||||
module.exports = MetaPreview;
|
||||
|
|
|
|||
62
src/components/MetaPreview/Ratings/Ratings.less
Normal file
62
src/components/MetaPreview/Ratings/Ratings.less
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (C) 2017-2025 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
@height: 4rem;
|
||||
@width: 4rem;
|
||||
@height-mobile: 3rem;
|
||||
@width-mobile: 3rem;
|
||||
|
||||
|
||||
.ratings-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
background-color: var(--overlay-color);
|
||||
border-radius: 2rem;
|
||||
height: @height;
|
||||
width: fit-content;
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: @height;
|
||||
width: @width;
|
||||
padding: 0 1rem;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
width: calc(@width / 2);
|
||||
height: calc(@height / 2);
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.7;
|
||||
transition: 0.3s all ease-in-out;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media @phone-landscape {
|
||||
.ratings-container {
|
||||
height: @height-mobile;
|
||||
|
||||
.icon-container {
|
||||
height: @height-mobile;
|
||||
width: @width-mobile;
|
||||
|
||||
.icon {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/components/MetaPreview/Ratings/Ratings.tsx
Normal file
31
src/components/MetaPreview/Ratings/Ratings.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (C) 2017-2025 Smart code 203358507
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import useRating from './useRating';
|
||||
import styles from './Ratings.less';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
metaId?: string;
|
||||
ratingInfo?: Loadable<RatingInfo>;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Ratings = ({ ratingInfo, className }: Props) => {
|
||||
const { onLiked, onLoved, liked, loved } = useRating(ratingInfo);
|
||||
const disabled = useMemo(() => ratingInfo?.type !== 'Ready', [ratingInfo]);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles['ratings-container'], className)}>
|
||||
<div className={classNames(styles['icon-container'], { [styles['disabled']]: disabled })} onClick={onLiked}>
|
||||
<Icon name={liked ? 'thumbs-up' : 'thumbs-up-outline'} className={styles['icon']} />
|
||||
</div>
|
||||
<div className={classNames(styles['icon-container'], { [styles['disabled']]: disabled })} onClick={onLoved}>
|
||||
<Icon name={loved ? 'heart' : 'heart-outline'} className={styles['icon']} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Ratings;
|
||||
5
src/components/MetaPreview/Ratings/index.ts
Normal file
5
src/components/MetaPreview/Ratings/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2025 Smart code 203358507
|
||||
|
||||
import Ratings from './Ratings';
|
||||
|
||||
export { Ratings };
|
||||
48
src/components/MetaPreview/Ratings/useRating.ts
Normal file
48
src/components/MetaPreview/Ratings/useRating.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (C) 2017-2025 Smart code 203358507
|
||||
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import { useServices } from 'stremio/services';
|
||||
|
||||
const useRating = (ratingInfo?: Loadable<RatingInfo>) => {
|
||||
const { core } = useServices();
|
||||
|
||||
const setRating = useCallback((status: Rating) => {
|
||||
core.transport.dispatch({
|
||||
action: 'MetaDetails',
|
||||
args: {
|
||||
action: 'Rate',
|
||||
args: status,
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
const status = useMemo(() => {
|
||||
const content = ratingInfo?.type === 'Ready' ? ratingInfo.content as RatingInfo : null;
|
||||
return content?.status;
|
||||
}, [ratingInfo]);
|
||||
|
||||
const liked = useMemo(() => {
|
||||
return status === 'liked';
|
||||
}, [status]);
|
||||
|
||||
const loved = useMemo(() => {
|
||||
return status === 'loved';
|
||||
}, [status]);
|
||||
|
||||
const onLiked = useCallback(() => {
|
||||
setRating(status === 'liked' ? null : 'liked');
|
||||
}, [status]);
|
||||
|
||||
const onLoved = useCallback(() => {
|
||||
setRating(status === 'loved' ? null : 'loved');
|
||||
}, [status]);
|
||||
|
||||
return {
|
||||
onLiked,
|
||||
onLoved,
|
||||
liked,
|
||||
loved,
|
||||
};
|
||||
};
|
||||
|
||||
export default useRating;
|
||||
|
|
@ -159,7 +159,6 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
max-height: 15rem;
|
||||
flex-wrap: wrap;
|
||||
padding-top: 3.5rem;
|
||||
overflow: visible;
|
||||
|
|
@ -209,6 +208,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ratings {
|
||||
margin-bottom: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.share-prompt {
|
||||
|
|
|
|||
|
|
@ -193,6 +193,8 @@ const Discover = ({ urlParams, queryParams }) => {
|
|||
trailerStreams={selectedMetaItem.trailerStreams}
|
||||
inLibrary={selectedMetaItem.inLibrary}
|
||||
toggleInLibrary={selectedMetaItem.inLibrary ? removeFromLibrary : addToLibrary}
|
||||
metaId={selectedMetaItem.id}
|
||||
like={selectedMetaItem.like}
|
||||
/>
|
||||
:
|
||||
discover.catalog !== null && discover.catalog.content.type === 'Loading' ?
|
||||
|
|
|
|||
|
|
@ -168,6 +168,8 @@ const MetaDetails = ({ urlParams, queryParams }) => {
|
|||
trailerStreams={metaDetails.metaItem.content.content.trailerStreams}
|
||||
inLibrary={metaDetails.metaItem.content.content.inLibrary}
|
||||
toggleInLibrary={metaDetails.metaItem.content.content.inLibrary ? removeFromLibrary : addToLibrary}
|
||||
metaId={metaDetails.metaItem.content.content.id}
|
||||
ratingInfo={metaDetails.ratingInfo}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
|
|
|||
1
src/types/models/MetaDetails.d.ts
vendored
1
src/types/models/MetaDetails.d.ts
vendored
|
|
@ -24,4 +24,5 @@ type MetaDetails = {
|
|||
content: Loadable<Stream[]>
|
||||
}[],
|
||||
title: string | null,
|
||||
ratingInfo: Loadable<RatingInfo> | null,
|
||||
};
|
||||
|
|
|
|||
7
src/types/types.d.ts
vendored
7
src/types/types.d.ts
vendored
|
|
@ -68,3 +68,10 @@ type AudioTrack = {
|
|||
lang: string,
|
||||
origin: string,
|
||||
};
|
||||
|
||||
type Rating = 'liked' | 'loved' | null;
|
||||
|
||||
type RatingInfo = {
|
||||
metaId: string,
|
||||
status: Rating,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue