feat(medias): support reactions (#3146)
This commit is contained in:
@@ -14,7 +14,7 @@ import { Table, TableBody, TableCell, TableRow } from "@popcorntime/ui/component
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@popcorntime/ui/components/tabs";
|
||||
import { timeDisplay } from "@popcorntime/ui/lib/time";
|
||||
import { cn } from "@popcorntime/ui/lib/utils";
|
||||
import { Calendar, Clock, ExternalLink, Star, X } from "lucide-react";
|
||||
import { Calendar, Clock, ExternalLink, Star, ThumbsDown, ThumbsUp, X } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router";
|
||||
@@ -24,7 +24,7 @@ import { useCountry } from "@/hooks/useCountry";
|
||||
import { useTauri } from "@/hooks/useTauri";
|
||||
import { NotFoundRoute } from "@/routes/not-found";
|
||||
import { useGlobalStore } from "@/stores/global";
|
||||
import type { Media } from "@/tauri/types";
|
||||
import type { Media, UserReactionType } from "@/tauri/types";
|
||||
|
||||
function MediaContentSkeleton() {
|
||||
return (
|
||||
@@ -45,6 +45,22 @@ function MediaContent() {
|
||||
const { t } = useTranslation();
|
||||
const officialLocales = useMemo(() => [...getLocalesForCountry(country)], [country]);
|
||||
|
||||
const setMediaReaction = useCallback(
|
||||
(reaction: UserReactionType | null) => {
|
||||
if (!media?.id) return;
|
||||
setMedia(prev => (prev ? { ...prev, reaction } : prev));
|
||||
try {
|
||||
void api.setMediaReaction({
|
||||
mediaId: media.id,
|
||||
reaction,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to set media reaction:", error);
|
||||
}
|
||||
},
|
||||
[media?.id, api.setMediaReaction]
|
||||
);
|
||||
|
||||
const fetch = useCallback(
|
||||
async (slug: string) => {
|
||||
setIsLoading(true);
|
||||
@@ -221,18 +237,18 @@ function MediaContent() {
|
||||
</div>
|
||||
|
||||
<div className="absolute right-0 bottom-0 left-0 px-8">
|
||||
<div className="flex h-full items-stretch gap-6">
|
||||
<div className="flex h-full items-stretch gap-6 space-y-2">
|
||||
<MediaPosterAsPicture
|
||||
loading="lazy"
|
||||
title={media.title}
|
||||
posterId={posterId}
|
||||
placeholder={placeholderImg}
|
||||
className="w-32 rounded-md"
|
||||
className="w-44 rounded-md"
|
||||
/>
|
||||
<div className="flex flex-1 flex-col pb-4">
|
||||
<h1 className="mb-3 line-clamp-1 text-4xl leading-tight font-bold">{media.title}</h1>
|
||||
<div className="flex flex-1 flex-col pb-4 gap-2">
|
||||
<h1 className="line-clamp-1 text-4xl leading-tight font-bold">{media.title}</h1>
|
||||
|
||||
<div className="mb-4 flex flex-wrap items-center gap-6">
|
||||
<div className="flex flex-wrap items-center gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Star className="h-5 w-5 fill-current text-yellow-400" />
|
||||
{media.ranking?.score && (
|
||||
@@ -256,7 +272,7 @@ function MediaContent() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-4 flex flex-wrap gap-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="default" className="font-medium capitalize backdrop-blur-sm">
|
||||
{media.__typename === "Movie" ? t("media.movie") : t("media.tv-show")}
|
||||
</Badge>
|
||||
@@ -272,12 +288,29 @@ function MediaContent() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant={media.reaction === "LIKE" ? "accent" : "ghost"}
|
||||
onClick={() => setMediaReaction(media.reaction === "LIKE" ? null : "LIKE")}
|
||||
size="iconXl"
|
||||
>
|
||||
<ThumbsUp />
|
||||
</Button>
|
||||
<Button
|
||||
variant={media.reaction === "DISLIKE" ? "accent" : "ghost"}
|
||||
onClick={() => setMediaReaction(media.reaction === "DISLIKE" ? null : "DISLIKE")}
|
||||
size="iconXl"
|
||||
>
|
||||
<ThumbsDown />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{bestProvider && (
|
||||
<div className="mt-auto flex flex-col space-y-2 space-x-0 sm:flex-row sm:space-y-0 sm:space-x-2 rtl:space-x-reverse">
|
||||
<Link
|
||||
className={cn(
|
||||
buttonVariants({ variant: "default", size: "xl" }),
|
||||
"group bg-primary/40 dark:bg-primary/20 hover:bg-primary/90 hover:text-secondary flex w-full items-center justify-center gap-x-2 px-4 font-extrabold sm:w-auto sm:max-w-sm"
|
||||
"group text-foreground/80 bg-primary/40 dark:bg-primary/20 hover:bg-primary/90 hover:text-secondary flex w-full items-center justify-center gap-x-2 px-4 font-extrabold sm:w-auto sm:max-w-sm"
|
||||
)}
|
||||
to={`https://go.popcorntime.app/${bestProvider.urlHash}?country=${country?.toUpperCase()}`}
|
||||
target="_blank"
|
||||
|
||||
@@ -55,6 +55,14 @@ async setFavoritesProvider(params: SetFavoriteProviderInput) : Promise<Result<Se
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async setMediaReaction(params: SetReactionInput) : Promise<Result<SetReactionMutation | null, { message: string; code: Code }>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("set_media_reaction", { params }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async showMainWindow() : Promise<void> {
|
||||
await TAURI_INVOKE("show_main_window");
|
||||
},
|
||||
@@ -136,7 +144,7 @@ export type MediaSearchConnection = { nodes: MediaSearch[]; pageInfo: PageInfo }
|
||||
export type MediaSimilar = { title: string; overview: string | null; kind: MediaKind; slug: string; poster: string | null; year: number | null }
|
||||
export type MediaVideo = { source: VideoSource; video_id: string }
|
||||
export type MeliSearchProvider = { providerId: string; priceTypes: WatchPriceType[] }
|
||||
export type Movie = { runtime: string; id: number; __typename: string; title: string; slug: string; overview: string | null; tagline: string | null; languages: Language[]; poster: string | null; backdrop: string | null; released: string | null; year: number | null; country: Country | null; tags: Tag[]; trailers: string[]; genres: Genre[]; classification: string | null; countries: Country[]; kind: MediaKind; videos: MediaVideo[]; ratings: ExternalRating[]; ranking: Ranking | null; pochoclinReview: PochoclinReview | null; similars: MediaSimilar[]; similarsFree: MediaSimilar[]; charts: MediaCharts[]; availabilities: Availability[]; talents: People[] }
|
||||
export type Movie = { runtime: string; id: number; __typename: string; title: string; slug: string; overview: string | null; tagline: string | null; languages: Language[]; poster: string | null; backdrop: string | null; released: string | null; year: number | null; country: Country | null; tags: Tag[]; trailers: string[]; genres: Genre[]; classification: string | null; countries: Country[]; kind: MediaKind; videos: MediaVideo[]; ratings: ExternalRating[]; ranking: Ranking | null; pochoclinReview: PochoclinReview | null; similars: MediaSimilar[]; similarsFree: MediaSimilar[]; charts: MediaCharts[]; availabilities: Availability[]; talents: People[]; reaction: UserReactionType | null }
|
||||
export type PageInfo = { endCursor: string | null; hasNextPage: boolean }
|
||||
export type People = { id: number; rank: number; name: string; role: string | null; roleType: RoleType }
|
||||
export type PochoclinReview = { review: string; excerpt: string }
|
||||
@@ -154,12 +162,15 @@ export type SessionServerReady = { authorization_url: string }
|
||||
export type SessionUpdate = null
|
||||
export type SetFavoriteProviderInput = { country: Country; providerKey: string; favorite: boolean }
|
||||
export type SetFavoriteProviderMutation = { setFavoriteProvider: boolean }
|
||||
export type SetReactionInput = { mediaId: number; reaction: UserReactionType | null }
|
||||
export type SetReactionMutation = { setReaction: boolean }
|
||||
export type SortKey = "ID" | "RELEASED_AT" | "CREATED_AT" | "UPDATED_AT" | "POSITION"
|
||||
export type Tag = string
|
||||
export type Tvshow = { inProduction: boolean; id: number; __typename: string; title: string; slug: string; overview: string | null; tagline: string | null; languages: Language[]; poster: string | null; backdrop: string | null; released: string | null; year: number | null; country: Country | null; tags: Tag[]; trailers: string[]; genres: Genre[]; classification: string | null; countries: Country[]; kind: MediaKind; videos: MediaVideo[]; ratings: ExternalRating[]; ranking: Ranking | null; pochoclinReview: PochoclinReview | null; similars: MediaSimilar[]; similarsFree: MediaSimilar[]; charts: MediaCharts[]; availabilities: Availability[]; talents: People[] }
|
||||
export type Tvshow = { inProduction: boolean; id: number; __typename: string; title: string; slug: string; overview: string | null; tagline: string | null; languages: Language[]; poster: string | null; backdrop: string | null; released: string | null; year: number | null; country: Country | null; tags: Tag[]; trailers: string[]; genres: Genre[]; classification: string | null; countries: Country[]; kind: MediaKind; videos: MediaVideo[]; ratings: ExternalRating[]; ranking: Ranking | null; pochoclinReview: PochoclinReview | null; similars: MediaSimilar[]; similarsFree: MediaSimilar[]; charts: MediaCharts[]; availabilities: Availability[]; talents: People[]; reaction: UserReactionType | null }
|
||||
export type UpdatePreferencesInput = { country: Country; language: Language }
|
||||
export type UpdatePreferencesMutation = { updatePreferences: UserPreferences | null }
|
||||
export type UserPreferences = { language: Language; country: Country }
|
||||
export type UserReactionType = "LIKE" | "DISLIKE"
|
||||
export type VideoSource = "RUMBLE" | "YOUTUBE"
|
||||
export type WatchPriceType = "RENT" | "BUY" | "FLATRATE" | "FREE" | "CINEMA"
|
||||
|
||||
|
||||
@@ -247,6 +247,7 @@ type Movie implements Media {
|
||||
updatedAt: String!
|
||||
runtime: String!
|
||||
videos: [MediaVideo!]!
|
||||
reaction: UserReactionType
|
||||
countries: [Country!]!
|
||||
pochoclinReview(language: Language): PochoclinReview
|
||||
similars(country: Country!, language: Language, arguments: SearchArguments): [MediaSearch!]!
|
||||
@@ -349,6 +350,7 @@ type QueryRoot {
|
||||
count(country: Country!): Int!
|
||||
providers(country: Country!, query: String): [Provider!]!
|
||||
preferences: UserPreferences
|
||||
reactions(after: String = null, before: String = null, first: Int, last: Int): UserReactionConnection!
|
||||
}
|
||||
|
||||
type Ranking {
|
||||
@@ -423,6 +425,7 @@ type TVShow implements Media {
|
||||
inProduction: Boolean!
|
||||
lastAirDate: String
|
||||
videos: [MediaVideo!]!
|
||||
reaction: UserReactionType
|
||||
countries: [Country!]!
|
||||
pochoclinReview(language: Language): PochoclinReview
|
||||
similars(country: Country!, language: Language, arguments: SearchArguments): [MediaSearch!]!
|
||||
@@ -451,6 +454,40 @@ type UserPreferences {
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type UserReaction {
|
||||
mediaId: Int!
|
||||
reaction: UserReactionType!
|
||||
}
|
||||
|
||||
type UserReactionConnection {
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [UserReactionEdge!]!
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [UserReaction!]!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type UserReactionEdge {
|
||||
"""
|
||||
The item at the end of the edge
|
||||
"""
|
||||
node: UserReaction!
|
||||
"""
|
||||
A cursor for use in pagination
|
||||
"""
|
||||
cursor: String!
|
||||
}
|
||||
|
||||
enum UserReactionType {
|
||||
LIKE
|
||||
DISLIKE
|
||||
|
||||
@@ -8,6 +8,7 @@ pub mod consts;
|
||||
pub mod media;
|
||||
pub mod preferences;
|
||||
pub mod providers;
|
||||
pub mod reactions;
|
||||
pub mod search;
|
||||
|
||||
impl_scalar!(Date, schema::Date);
|
||||
@@ -64,5 +65,12 @@ impl schema::variable::Variable for Language {
|
||||
cynic::variables::VariableType::Named(<schema::Language as cynic::schema::NamedType>::NAME);
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, specta::Type, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PageInfo {
|
||||
pub end_cursor: Option<String>,
|
||||
pub has_next_page: bool,
|
||||
}
|
||||
|
||||
#[cynic::schema("popcorntime")]
|
||||
mod schema {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{Country, Date, Language, schema};
|
||||
use crate::{Country, Date, Language, reactions::UserReactionType, schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug, specta::Type, Deserialize)]
|
||||
@@ -35,6 +35,7 @@ pub enum Media {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Tvshow {
|
||||
pub in_production: bool,
|
||||
// same as movie -- cynic doesn't support inheritance
|
||||
pub id: i32,
|
||||
#[serde(rename = "__typename")]
|
||||
pub __typename: String,
|
||||
@@ -70,6 +71,7 @@ pub struct Tvshow {
|
||||
#[arguments(country: $country)]
|
||||
pub availabilities: Vec<Availability>,
|
||||
pub talents: Vec<People>,
|
||||
pub reaction: Option<UserReactionType>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, specta::Type, Serialize)]
|
||||
@@ -77,6 +79,8 @@ pub struct Tvshow {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Movie {
|
||||
pub runtime: String,
|
||||
// same as tvshow -- cynic doesn't support inheritance
|
||||
// the cynic(spread) require same type
|
||||
pub id: i32,
|
||||
#[serde(rename = "__typename")]
|
||||
pub __typename: String,
|
||||
@@ -112,6 +116,7 @@ pub struct Movie {
|
||||
#[arguments(country: $country)]
|
||||
pub availabilities: Vec<Availability>,
|
||||
pub talents: Vec<People>,
|
||||
pub reaction: Option<UserReactionType>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, specta::Type, Serialize)]
|
||||
@@ -275,11 +280,10 @@ pub struct Tag(pub String);
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cynic::QueryBuilder;
|
||||
|
||||
#[test]
|
||||
fn media_query_gql_output() {
|
||||
use cynic::QueryBuilder;
|
||||
|
||||
let operation = MediaOutput::build(MediaInput {
|
||||
country: Country("US".to_string()),
|
||||
language: Some(Language("en".to_string())),
|
||||
|
||||
105
crates/popcorntime-graphql-client/src/reactions.rs
Normal file
105
crates/popcorntime-graphql-client/src/reactions.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use crate::{PageInfo, schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(cynic::Enum, Clone, Copy, Debug, specta::Type)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[cynic(graphql_type = "UserReactionType")]
|
||||
pub enum UserReactionType {
|
||||
Like,
|
||||
Dislike,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug, specta::Type, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetReactionInput {
|
||||
pub media_id: i32,
|
||||
pub reaction: Option<UserReactionType>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug, specta::Type, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserReactionsInput<'a> {
|
||||
#[specta(optional)]
|
||||
pub after: Option<&'a str>,
|
||||
#[specta(optional)]
|
||||
pub before: Option<&'a str>,
|
||||
#[specta(optional)]
|
||||
pub first: Option<i32>,
|
||||
#[specta(optional)]
|
||||
pub last: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, specta::Type, Serialize)]
|
||||
#[cynic(graphql_type = "QueryRoot", variables = "UserReactionsInput")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserReactions {
|
||||
#[arguments(after: $after, before: $before, first: $first, last: $last)]
|
||||
pub reactions: UserReactionConnection,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, specta::Type, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserReactionConnection {
|
||||
pub nodes: Vec<UserReaction>,
|
||||
pub page_info: PageInfo,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, specta::Type, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserReaction {
|
||||
pub media_id: i32,
|
||||
pub reaction: UserReactionType,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, specta::Type, Serialize)]
|
||||
#[cynic(graphql_type = "MutationRoot", variables = "SetReactionInput")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetReactionMutation {
|
||||
#[arguments(mediaId: $media_id, reaction: $reaction)]
|
||||
pub set_reaction: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cynic::{MutationBuilder, QueryBuilder};
|
||||
|
||||
#[test]
|
||||
fn reactions_query_gql_output() {
|
||||
let operation = UserReactions::build(UserReactionsInput {
|
||||
first: Some(10),
|
||||
after: None,
|
||||
before: None,
|
||||
last: None,
|
||||
});
|
||||
|
||||
insta::assert_snapshot!(operation.query);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_reaction_like_mutation_gql_output() {
|
||||
let operation = SetReactionMutation::build(SetReactionInput {
|
||||
media_id: 123,
|
||||
reaction: Some(UserReactionType::Like),
|
||||
});
|
||||
insta::assert_snapshot!(operation.query);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_favorite_mutation_gql_output() {
|
||||
let operation = SetReactionMutation::build(SetReactionInput {
|
||||
media_id: 123,
|
||||
reaction: Some(UserReactionType::Dislike),
|
||||
});
|
||||
insta::assert_snapshot!(operation.query);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_favorite_mutation_gql_output() {
|
||||
let operation = SetReactionMutation::build(SetReactionInput {
|
||||
media_id: 123,
|
||||
reaction: None,
|
||||
});
|
||||
insta::assert_snapshot!(operation.query);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::{
|
||||
Country, Date, DateTime, Language,
|
||||
media::Genre,
|
||||
media::{MediaKind, WatchPriceType},
|
||||
Country, Date, DateTime, Language, PageInfo,
|
||||
media::{Genre, MediaKind, WatchPriceType},
|
||||
schema,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -70,13 +69,6 @@ pub struct MediaSearchConnection {
|
||||
pub page_info: PageInfo,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, specta::Type, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PageInfo {
|
||||
pub end_cursor: Option<String>,
|
||||
pub has_next_page: bool,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, specta::Type, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MediaSearch {
|
||||
|
||||
@@ -88,6 +88,7 @@ query MediaOutput($country: Country!, $slug: String!, $language: Language) {
|
||||
role
|
||||
roleType
|
||||
}
|
||||
reaction
|
||||
}
|
||||
... on TVShow {
|
||||
inProduction
|
||||
@@ -172,6 +173,7 @@ query MediaOutput($country: Country!, $slug: String!, $language: Language) {
|
||||
role
|
||||
roleType
|
||||
}
|
||||
reaction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
source: crates/popcorntime-graphql-client/src/reactions.rs
|
||||
expression: operation.query
|
||||
---
|
||||
mutation SetReactionMutation($mediaId: Int!, $reaction: UserReactionType) {
|
||||
setReaction(mediaId: $mediaId, reaction: $reaction)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
source: crates/popcorntime-graphql-client/src/reactions.rs
|
||||
expression: operation.query
|
||||
---
|
||||
query UserReactions($after: String, $before: String, $first: Int, $last: Int) {
|
||||
reactions(after: $after, before: $before, first: $first, last: $last) {
|
||||
nodes {
|
||||
mediaId
|
||||
reaction
|
||||
}
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
source: crates/popcorntime-graphql-client/src/reactions.rs
|
||||
expression: operation.query
|
||||
---
|
||||
mutation SetReactionMutation($mediaId: Int!, $reaction: UserReactionType) {
|
||||
setReaction(mediaId: $mediaId, reaction: $reaction)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
source: crates/popcorntime-graphql-client/src/reactions.rs
|
||||
expression: operation.query
|
||||
---
|
||||
mutation SetReactionMutation($mediaId: Int!, $reaction: UserReactionType) {
|
||||
setReaction(mediaId: $mediaId, reaction: $reaction)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::error::Error;
|
||||
use cynic::{MutationBuilder, QueryBuilder};
|
||||
use popcorntime_graphql_client::{client::ApiClient, media, preferences, providers, search};
|
||||
use popcorntime_graphql_client::{
|
||||
client::ApiClient, media, preferences, providers, reactions, search,
|
||||
};
|
||||
use popcorntime_session::AuthorizationService;
|
||||
use tauri::State;
|
||||
use tracing::instrument;
|
||||
@@ -65,8 +67,9 @@ pub async fn media<'a>(
|
||||
) -> Result<Option<media::MediaOutput>, Error> {
|
||||
auth_service.validate().await?;
|
||||
|
||||
// cache is disabled because this query include user reaction that can change
|
||||
api_client
|
||||
.query(media::MediaOutput::build(params), false)
|
||||
.query(media::MediaOutput::build(params), true)
|
||||
.await
|
||||
.map(|res| res.data)
|
||||
.map_err(Into::into)
|
||||
@@ -106,3 +109,20 @@ pub async fn set_favorites_provider<'a>(
|
||||
.map(|res| res.data)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
#[specta::specta]
|
||||
#[instrument(skip(api_client, auth_service), err(Debug))]
|
||||
pub async fn set_media_reaction(
|
||||
api_client: State<'_, ApiClient>,
|
||||
auth_service: State<'_, AuthorizationService>,
|
||||
params: reactions::SetReactionInput,
|
||||
) -> Result<Option<reactions::SetReactionMutation>, Error> {
|
||||
auth_service.validate().await?;
|
||||
|
||||
api_client
|
||||
.query(reactions::SetReactionMutation::build(params), true)
|
||||
.await
|
||||
.map(|res| res.data)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ fn main() {
|
||||
popcorntime_tauri::graphql::media,
|
||||
popcorntime_tauri::graphql::providers,
|
||||
popcorntime_tauri::graphql::set_favorites_provider,
|
||||
popcorntime_tauri::graphql::set_media_reaction,
|
||||
popcorntime_tauri::window::show_main_window,
|
||||
popcorntime_tauri::session::is_onboarded,
|
||||
popcorntime_tauri::session::set_onboarded,
|
||||
|
||||
@@ -91,14 +91,12 @@ export function MediaPosterAsPicture({
|
||||
}) {
|
||||
if (!posterId) {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<img
|
||||
src={placeholder}
|
||||
alt={title}
|
||||
loading={loading}
|
||||
className={cn("w-full bg-cover", className)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user