mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
Recommendations page bug fixes and refactoring (#2578)
* Changed Most Active Studios to Latest Studios * dynamically create view all link and created message for view all * created shared determineSlidesToScroll method * removed added code in Shared/index.ts * renamed getSlickSettings to getSlickSliderSettings * Updated row headers to follow plex naming convention * removed extra s in Sceness * updated row header css to better align header text with view all anchor
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
import { FindGalleriesQueryResult } from "src/core/generated-graphql";
|
||||||
|
import Slider from "react-slick";
|
||||||
|
import { GalleryCard } from "./GalleryCard";
|
||||||
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
import { getSlickSliderSettings } from "src/core/recommendations";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
isTouch: boolean;
|
||||||
|
filter: ListFilterModel;
|
||||||
|
result: FindGalleriesQueryResult;
|
||||||
|
header: String;
|
||||||
|
linkText: String;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GalleryRecommendationRow: FunctionComponent<IProps> = (
|
||||||
|
props: IProps
|
||||||
|
) => {
|
||||||
|
const cardCount = props.result.data?.findGalleries.count;
|
||||||
|
return (
|
||||||
|
<div className="recommendation-row gallery-recommendations">
|
||||||
|
<div className="recommendation-row-head">
|
||||||
|
<div>
|
||||||
|
<h2>{props.header}</h2>
|
||||||
|
</div>
|
||||||
|
<a href={`/galleries?${props.filter.makeQueryParameters()}`}>
|
||||||
|
{props.linkText}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Slider {...getSlickSliderSettings(cardCount!, props.isTouch)}>
|
||||||
|
{props.result.data?.findGalleries.galleries.map((gallery) => (
|
||||||
|
<GalleryCard key={gallery.id} gallery={gallery} zoomIndex={1} />
|
||||||
|
))}
|
||||||
|
</Slider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
37
ui/v2.5/src/components/Movies/MovieRecommendationRow.tsx
Normal file
37
ui/v2.5/src/components/Movies/MovieRecommendationRow.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
import { FindMoviesQueryResult } from "src/core/generated-graphql";
|
||||||
|
import Slider from "react-slick";
|
||||||
|
import { MovieCard } from "./MovieCard";
|
||||||
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
import { getSlickSliderSettings } from "src/core/recommendations";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
isTouch: boolean;
|
||||||
|
filter: ListFilterModel;
|
||||||
|
result: FindMoviesQueryResult;
|
||||||
|
header: String;
|
||||||
|
linkText: String;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MovieRecommendationRow: FunctionComponent<IProps> = (
|
||||||
|
props: IProps
|
||||||
|
) => {
|
||||||
|
const cardCount = props.result.data?.findMovies.count;
|
||||||
|
return (
|
||||||
|
<div className="recommendation-row movie-recommendations">
|
||||||
|
<div className="recommendation-row-head">
|
||||||
|
<div>
|
||||||
|
<h2>{props.header}</h2>
|
||||||
|
</div>
|
||||||
|
<a href={`/movies?${props.filter.makeQueryParameters()}`}>
|
||||||
|
{props.linkText}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Slider {...getSlickSliderSettings(cardCount!, props.isTouch)}>
|
||||||
|
{props.result.data?.findMovies.movies.map((p) => (
|
||||||
|
<MovieCard key={p.id} movie={p} />
|
||||||
|
))}
|
||||||
|
</Slider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
import { FindPerformersQueryResult } from "src/core/generated-graphql";
|
||||||
|
import Slider from "react-slick";
|
||||||
|
import { PerformerCard } from "./PerformerCard";
|
||||||
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
import { getSlickSliderSettings } from "src/core/recommendations";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
isTouch: boolean;
|
||||||
|
filter: ListFilterModel;
|
||||||
|
result: FindPerformersQueryResult;
|
||||||
|
header: String;
|
||||||
|
linkText: String;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PerformerRecommendationRow: FunctionComponent<IProps> = (
|
||||||
|
props: IProps
|
||||||
|
) => {
|
||||||
|
const cardCount = props.result.data?.findPerformers.count;
|
||||||
|
return (
|
||||||
|
<div className="recommendation-row performer-recommendations">
|
||||||
|
<div className="recommendation-row-head">
|
||||||
|
<div>
|
||||||
|
<h2>{props.header}</h2>
|
||||||
|
</div>
|
||||||
|
<a href={`/performers?${props.filter.makeQueryParameters()}`}>
|
||||||
|
{props.linkText}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Slider {...getSlickSliderSettings(cardCount!, props.isTouch)}>
|
||||||
|
{props.result.data?.findPerformers.performers.map((p) => (
|
||||||
|
<PerformerCard key={p.id} performer={p} />
|
||||||
|
))}
|
||||||
|
</Slider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -8,14 +8,14 @@ import {
|
|||||||
useFindGalleries,
|
useFindGalleries,
|
||||||
useFindPerformers,
|
useFindPerformers,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { SceneCard } from "src/components/Scenes/SceneCard";
|
import { SceneRecommendationRow } from "src/components/Scenes/SceneRecommendationRow";
|
||||||
import { StudioCard } from "src/components/Studios/StudioCard";
|
import { StudioRecommendationRow } from "src/components/Studios/StudioRecommendationRow";
|
||||||
import { MovieCard } from "src/components/Movies/MovieCard";
|
import { MovieRecommendationRow } from "src/components/Movies/MovieRecommendationRow";
|
||||||
import { PerformerCard } from "src/components/Performers/PerformerCard";
|
import { PerformerRecommendationRow } from "src/components/Performers/PerformerRecommendationRow";
|
||||||
import { GalleryCard } from "src/components/Galleries/GalleryCard";
|
import { GalleryRecommendationRow } from "src/components/Galleries/GalleryRecommendationRow";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
import Slider from "react-slick";
|
import { LoadingIndicator } from "src/components/Shared";
|
||||||
|
|
||||||
const Recommendations: React.FC = () => {
|
const Recommendations: React.FC = () => {
|
||||||
function isTouchEnabled() {
|
function isTouchEnabled() {
|
||||||
@@ -31,50 +31,35 @@ const Recommendations: React.FC = () => {
|
|||||||
scenefilter.sortDirection = GQL.SortDirectionEnum.Desc;
|
scenefilter.sortDirection = GQL.SortDirectionEnum.Desc;
|
||||||
scenefilter.itemsPerPage = itemsPerPage;
|
scenefilter.itemsPerPage = itemsPerPage;
|
||||||
const sceneResult = useFindScenes(scenefilter);
|
const sceneResult = useFindScenes(scenefilter);
|
||||||
const hasScenes =
|
const hasScenes = !!sceneResult?.data?.findScenes?.count;
|
||||||
sceneResult.data &&
|
|
||||||
sceneResult.data.findScenes &&
|
|
||||||
sceneResult.data.findScenes.count > 0;
|
|
||||||
|
|
||||||
const studiofilter = new ListFilterModel(GQL.FilterMode.Studios);
|
const studiofilter = new ListFilterModel(GQL.FilterMode.Studios);
|
||||||
studiofilter.sortBy = "scenes_count";
|
studiofilter.sortBy = "created_at";
|
||||||
studiofilter.sortDirection = GQL.SortDirectionEnum.Desc;
|
studiofilter.sortDirection = GQL.SortDirectionEnum.Desc;
|
||||||
studiofilter.itemsPerPage = itemsPerPage;
|
studiofilter.itemsPerPage = itemsPerPage;
|
||||||
const studioResult = useFindStudios(studiofilter);
|
const studioResult = useFindStudios(studiofilter);
|
||||||
const hasStudios =
|
const hasStudios = !!studioResult?.data?.findStudios?.count;
|
||||||
studioResult.data &&
|
|
||||||
studioResult.data.findStudios &&
|
|
||||||
studioResult.data.findStudios.count > 0;
|
|
||||||
|
|
||||||
const moviefilter = new ListFilterModel(GQL.FilterMode.Movies);
|
const moviefilter = new ListFilterModel(GQL.FilterMode.Movies);
|
||||||
moviefilter.sortBy = "date";
|
moviefilter.sortBy = "date";
|
||||||
moviefilter.sortDirection = GQL.SortDirectionEnum.Desc;
|
moviefilter.sortDirection = GQL.SortDirectionEnum.Desc;
|
||||||
moviefilter.itemsPerPage = itemsPerPage;
|
moviefilter.itemsPerPage = itemsPerPage;
|
||||||
const movieResult = useFindMovies(moviefilter);
|
const movieResult = useFindMovies(moviefilter);
|
||||||
const hasMovies =
|
const hasMovies = !!movieResult?.data?.findMovies?.count;
|
||||||
movieResult.data &&
|
|
||||||
movieResult.data.findMovies &&
|
|
||||||
movieResult.data.findMovies.count > 0;
|
|
||||||
|
|
||||||
const performerfilter = new ListFilterModel(GQL.FilterMode.Performers);
|
const performerfilter = new ListFilterModel(GQL.FilterMode.Performers);
|
||||||
performerfilter.sortBy = "created_at";
|
performerfilter.sortBy = "created_at";
|
||||||
performerfilter.sortDirection = GQL.SortDirectionEnum.Desc;
|
performerfilter.sortDirection = GQL.SortDirectionEnum.Desc;
|
||||||
performerfilter.itemsPerPage = itemsPerPage;
|
performerfilter.itemsPerPage = itemsPerPage;
|
||||||
const performerResult = useFindPerformers(performerfilter);
|
const performerResult = useFindPerformers(performerfilter);
|
||||||
const hasPerformers =
|
const hasPerformers = !!performerResult?.data?.findPerformers?.count;
|
||||||
performerResult.data &&
|
|
||||||
performerResult.data.findPerformers &&
|
|
||||||
performerResult.data.findPerformers.count > 0;
|
|
||||||
|
|
||||||
const galleryfilter = new ListFilterModel(GQL.FilterMode.Galleries);
|
const galleryfilter = new ListFilterModel(GQL.FilterMode.Galleries);
|
||||||
galleryfilter.sortBy = "date";
|
galleryfilter.sortBy = "date";
|
||||||
galleryfilter.sortDirection = GQL.SortDirectionEnum.Desc;
|
galleryfilter.sortDirection = GQL.SortDirectionEnum.Desc;
|
||||||
galleryfilter.itemsPerPage = itemsPerPage;
|
galleryfilter.itemsPerPage = itemsPerPage;
|
||||||
const galleryResult = useFindGalleries(galleryfilter);
|
const galleryResult = useFindGalleries(galleryfilter);
|
||||||
const hasGalleries =
|
const hasGalleries = !!galleryResult?.data?.findGalleries?.count;
|
||||||
galleryResult.data &&
|
|
||||||
galleryResult.data.findGalleries &&
|
|
||||||
galleryResult.data.findGalleries.count > 0;
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
emptyServer: {
|
emptyServer: {
|
||||||
@@ -82,182 +67,108 @@ const Recommendations: React.FC = () => {
|
|||||||
defaultMessage:
|
defaultMessage:
|
||||||
"Add some scenes to your server to view recommendations on this page.",
|
"Add some scenes to your server to view recommendations on this page.",
|
||||||
},
|
},
|
||||||
latestScenes: {
|
recentlyAddedStudios: {
|
||||||
id: "latest_scenes",
|
id: "recently_added_studios",
|
||||||
defaultMessage: "Latest Scenes",
|
defaultMessage: "Recently Added Studios",
|
||||||
},
|
},
|
||||||
mostActiveStudios: {
|
recentlyAddedPerformers: {
|
||||||
id: "most_active_studios",
|
id: "recently_added_performers",
|
||||||
defaultMessage: "Most Active Studios",
|
defaultMessage: "Recently Added Performers",
|
||||||
},
|
},
|
||||||
latestMovies: {
|
recentlyReleasedGalleries: {
|
||||||
id: "latest_movies",
|
id: "recently_released_galleries",
|
||||||
defaultMessage: "Latest Movies",
|
defaultMessage: "Recently Released Galleries",
|
||||||
},
|
},
|
||||||
latestPerformers: {
|
recentlyReleasedMovies: {
|
||||||
id: "latest_performers",
|
id: "recently_released_movies",
|
||||||
defaultMessage: "Latest Performers",
|
defaultMessage: "Recently Released Movies",
|
||||||
},
|
},
|
||||||
latestGalleries: {
|
recentlyReleasedScenes: {
|
||||||
id: "latest_galleries",
|
id: "recently_released_scenes",
|
||||||
defaultMessage: "Latest Galleries",
|
defaultMessage: "Recently Released Scenes",
|
||||||
|
},
|
||||||
|
viewAll: {
|
||||||
|
id: "view_all",
|
||||||
|
defaultMessage: "View All",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var settings = {
|
if (
|
||||||
dots: !isTouch,
|
sceneResult.loading ||
|
||||||
arrows: !isTouch,
|
studioResult.loading ||
|
||||||
infinite: !isTouch,
|
movieResult.loading ||
|
||||||
speed: 300,
|
performerResult.loading ||
|
||||||
variableWidth: true,
|
galleryResult.loading
|
||||||
swipeToSlide: true,
|
) {
|
||||||
slidesToShow: 5,
|
return <LoadingIndicator />;
|
||||||
slidesToScroll: !isTouch ? 5 : 1,
|
} else {
|
||||||
responsive: [
|
return (
|
||||||
{
|
<div className="recommendations-container">
|
||||||
breakpoint: 1909,
|
{!hasScenes &&
|
||||||
settings: {
|
!hasStudios &&
|
||||||
slidesToShow: 4,
|
!hasMovies &&
|
||||||
slidesToScroll: !isTouch ? 4 : 1,
|
!hasPerformers &&
|
||||||
},
|
!hasGalleries ? (
|
||||||
},
|
<div className="no-recommendations">
|
||||||
{
|
{intl.formatMessage(messages.emptyServer)}
|
||||||
breakpoint: 1542,
|
</div>
|
||||||
settings: {
|
) : (
|
||||||
slidesToShow: 3,
|
<div>
|
||||||
slidesToScroll: !isTouch ? 3 : 1,
|
{hasScenes && (
|
||||||
},
|
<SceneRecommendationRow
|
||||||
},
|
isTouch={isTouch}
|
||||||
{
|
filter={scenefilter}
|
||||||
breakpoint: 1170,
|
result={sceneResult}
|
||||||
settings: {
|
queue={SceneQueue.fromListFilterModel(scenefilter)}
|
||||||
slidesToShow: 2,
|
header={intl.formatMessage(messages.recentlyReleasedScenes)}
|
||||||
slidesToScroll: !isTouch ? 2 : 1,
|
linkText={intl.formatMessage(messages.viewAll)}
|
||||||
},
|
/>
|
||||||
},
|
)}
|
||||||
{
|
|
||||||
breakpoint: 801,
|
|
||||||
settings: {
|
|
||||||
slidesToShow: 1,
|
|
||||||
slidesToScroll: 1,
|
|
||||||
dots: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const queue = SceneQueue.fromListFilterModel(scenefilter);
|
|
||||||
|
|
||||||
return (
|
{hasStudios && (
|
||||||
<div className="recommendations-container">
|
<StudioRecommendationRow
|
||||||
{!hasScenes &&
|
isTouch={isTouch}
|
||||||
!hasStudios &&
|
filter={studiofilter}
|
||||||
!hasMovies &&
|
result={studioResult}
|
||||||
!hasPerformers &&
|
header={intl.formatMessage(messages.recentlyAddedStudios)}
|
||||||
!hasGalleries ? (
|
linkText={intl.formatMessage(messages.viewAll)}
|
||||||
<div className="no-recommendations">
|
/>
|
||||||
{intl.formatMessage(messages.emptyServer)}
|
)}
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
{hasScenes && (
|
|
||||||
<div className="recommendation-row">
|
|
||||||
<div className="recommendation-row-head">
|
|
||||||
<div>
|
|
||||||
<h2>{intl.formatMessage(messages.latestScenes)}</h2>
|
|
||||||
</div>
|
|
||||||
<a href="/scenes?sortby=date&sortdir=desc">View all</a>
|
|
||||||
</div>
|
|
||||||
<Slider {...settings}>
|
|
||||||
{sceneResult.data?.findScenes.scenes.map((scene, index) => (
|
|
||||||
<SceneCard
|
|
||||||
key={scene.id}
|
|
||||||
scene={scene}
|
|
||||||
queue={queue}
|
|
||||||
index={index}
|
|
||||||
zoomIndex={1}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Slider>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasStudios && (
|
{hasMovies && (
|
||||||
<div className="recommendation-row">
|
<MovieRecommendationRow
|
||||||
<div className="recommendation-row-head">
|
isTouch={isTouch}
|
||||||
<div>
|
filter={moviefilter}
|
||||||
<h2>{intl.formatMessage(messages.mostActiveStudios)}</h2>
|
result={movieResult}
|
||||||
</div>
|
header={intl.formatMessage(messages.recentlyReleasedMovies)}
|
||||||
<a href="/studios?sortby=scenes_count&sortdir=desc">View all</a>
|
linkText={intl.formatMessage(messages.viewAll)}
|
||||||
</div>
|
/>
|
||||||
<Slider {...settings}>
|
)}
|
||||||
{studioResult.data?.findStudios.studios.map((studio) => (
|
|
||||||
<StudioCard
|
|
||||||
key={studio.id}
|
|
||||||
studio={studio}
|
|
||||||
hideParent={true}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Slider>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasMovies && (
|
{hasPerformers && (
|
||||||
<div className="recommendation-row">
|
<PerformerRecommendationRow
|
||||||
<div className="recommendation-row-head">
|
isTouch={isTouch}
|
||||||
<div>
|
filter={performerfilter}
|
||||||
<h2>{intl.formatMessage(messages.latestMovies)}</h2>
|
result={performerResult}
|
||||||
</div>
|
header={intl.formatMessage(messages.recentlyAddedPerformers)}
|
||||||
<a href="/movies?sortby=date&sortdir=desc">View all</a>
|
linkText={intl.formatMessage(messages.viewAll)}
|
||||||
</div>
|
/>
|
||||||
<Slider {...settings}>
|
)}
|
||||||
{movieResult.data?.findMovies.movies.map((p) => (
|
|
||||||
<MovieCard key={p.id} movie={p} />
|
|
||||||
))}
|
|
||||||
</Slider>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasPerformers && (
|
{hasGalleries && (
|
||||||
<div className="recommendation-row">
|
<GalleryRecommendationRow
|
||||||
<div className="recommendation-row-head">
|
isTouch={isTouch}
|
||||||
<div>
|
filter={galleryfilter}
|
||||||
<h2>{intl.formatMessage(messages.latestPerformers)}</h2>
|
result={galleryResult}
|
||||||
</div>
|
header={intl.formatMessage(messages.recentlyReleasedGalleries)}
|
||||||
<a href="/performers?sortby=created_at&sortdir=desc">
|
linkText={intl.formatMessage(messages.viewAll)}
|
||||||
View all
|
/>
|
||||||
</a>
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Slider {...settings}>
|
)}
|
||||||
{performerResult.data?.findPerformers.performers.map((p) => (
|
</div>
|
||||||
<PerformerCard key={p.id} performer={p} />
|
);
|
||||||
))}
|
}
|
||||||
</Slider>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasGalleries && (
|
|
||||||
<div className="recommendation-row">
|
|
||||||
<div className="recommendation-row-head">
|
|
||||||
<div>
|
|
||||||
<h2>{intl.formatMessage(messages.latestGalleries)}</h2>
|
|
||||||
</div>
|
|
||||||
<a href="/galleries?sortby=date&sortdir=desc">View all</a>
|
|
||||||
</div>
|
|
||||||
<Slider {...settings}>
|
|
||||||
{galleryResult.data?.findGalleries.galleries.map((gallery) => (
|
|
||||||
<GalleryCard
|
|
||||||
key={gallery.id}
|
|
||||||
gallery={gallery}
|
|
||||||
zoomIndex={1}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Slider>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Recommendations;
|
export default Recommendations;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
margin-bottom: 0;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|||||||
45
ui/v2.5/src/components/Scenes/SceneRecommendationRow.tsx
Normal file
45
ui/v2.5/src/components/Scenes/SceneRecommendationRow.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
import { FindScenesQueryResult } from "src/core/generated-graphql";
|
||||||
|
import Slider from "react-slick";
|
||||||
|
import { SceneCard } from "./SceneCard";
|
||||||
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
import { getSlickSliderSettings } from "src/core/recommendations";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
isTouch: boolean;
|
||||||
|
filter: ListFilterModel;
|
||||||
|
result: FindScenesQueryResult;
|
||||||
|
queue: SceneQueue;
|
||||||
|
header: String;
|
||||||
|
linkText: String;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SceneRecommendationRow: FunctionComponent<IProps> = (
|
||||||
|
props: IProps
|
||||||
|
) => {
|
||||||
|
const cardCount = props.result.data?.findScenes.count;
|
||||||
|
return (
|
||||||
|
<div className="recommendation-row scene-recommendations">
|
||||||
|
<div className="recommendation-row-head">
|
||||||
|
<div>
|
||||||
|
<h2>{props.header}</h2>
|
||||||
|
</div>
|
||||||
|
<a href={`/scenes?${props.filter.makeQueryParameters()}`}>
|
||||||
|
{props.linkText}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Slider {...getSlickSliderSettings(cardCount!, props.isTouch)}>
|
||||||
|
{props.result.data?.findScenes.scenes.map((scene, index) => (
|
||||||
|
<SceneCard
|
||||||
|
key={scene.id}
|
||||||
|
scene={scene}
|
||||||
|
queue={props.queue}
|
||||||
|
index={index}
|
||||||
|
zoomIndex={1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Slider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
37
ui/v2.5/src/components/Studios/StudioRecommendationRow.tsx
Normal file
37
ui/v2.5/src/components/Studios/StudioRecommendationRow.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
import { FindStudiosQueryResult } from "src/core/generated-graphql";
|
||||||
|
import Slider from "react-slick";
|
||||||
|
import { StudioCard } from "./StudioCard";
|
||||||
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
import { getSlickSliderSettings } from "src/core/recommendations";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
isTouch: boolean;
|
||||||
|
filter: ListFilterModel;
|
||||||
|
result: FindStudiosQueryResult;
|
||||||
|
header: String;
|
||||||
|
linkText: String;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StudioRecommendationRow: FunctionComponent<IProps> = (
|
||||||
|
props: IProps
|
||||||
|
) => {
|
||||||
|
const cardCount = props.result.data?.findStudios.count;
|
||||||
|
return (
|
||||||
|
<div className="recommendation-row studio-recommendations">
|
||||||
|
<div className="recommendation-row-head">
|
||||||
|
<div>
|
||||||
|
<h2>{props.header}</h2>
|
||||||
|
</div>
|
||||||
|
<a href={`/studios?${props.filter.makeQueryParameters()}`}>
|
||||||
|
{props.linkText}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Slider {...getSlickSliderSettings(cardCount!, props.isTouch)}>
|
||||||
|
{props.result.data?.findStudios.studios.map((studio) => (
|
||||||
|
<StudioCard key={studio.id} studio={studio} hideParent={true} />
|
||||||
|
))}
|
||||||
|
</Slider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
57
ui/v2.5/src/core/recommendations.ts
Normal file
57
ui/v2.5/src/core/recommendations.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
function determineSlidesToScroll(
|
||||||
|
cardCount: number,
|
||||||
|
prefered: number,
|
||||||
|
isTouch: boolean
|
||||||
|
) {
|
||||||
|
if (isTouch) {
|
||||||
|
return 1;
|
||||||
|
} else if (cardCount! > prefered) {
|
||||||
|
return prefered;
|
||||||
|
} else {
|
||||||
|
return cardCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSlickSliderSettings(cardCount: number, isTouch: boolean) {
|
||||||
|
return {
|
||||||
|
dots: !isTouch,
|
||||||
|
arrows: !isTouch,
|
||||||
|
infinite: !isTouch,
|
||||||
|
speed: 300,
|
||||||
|
variableWidth: true,
|
||||||
|
swipeToSlide: true,
|
||||||
|
slidesToShow: cardCount! > 5 ? 5 : cardCount,
|
||||||
|
slidesToScroll: determineSlidesToScroll(cardCount!, 5, isTouch),
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 1909,
|
||||||
|
settings: {
|
||||||
|
slidesToShow: cardCount! > 4 ? 4 : cardCount,
|
||||||
|
slidesToScroll: determineSlidesToScroll(cardCount!, 4, isTouch),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: 1542,
|
||||||
|
settings: {
|
||||||
|
slidesToShow: cardCount! > 3 ? 3 : cardCount,
|
||||||
|
slidesToScroll: determineSlidesToScroll(cardCount!, 3, isTouch),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: 1170,
|
||||||
|
settings: {
|
||||||
|
slidesToShow: cardCount! > 2 ? 2 : cardCount,
|
||||||
|
slidesToScroll: determineSlidesToScroll(cardCount!, 2, isTouch),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: 801,
|
||||||
|
settings: {
|
||||||
|
slidesToShow: 1,
|
||||||
|
slidesToScroll: 1,
|
||||||
|
dots: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -769,10 +769,6 @@
|
|||||||
"interactive": "Interactive",
|
"interactive": "Interactive",
|
||||||
"interactive_speed": "Interactive speed",
|
"interactive_speed": "Interactive speed",
|
||||||
"isMissing": "Is Missing",
|
"isMissing": "Is Missing",
|
||||||
"latest_galleries": "Latest Galleries",
|
|
||||||
"latest_movies": "Latest Movies",
|
|
||||||
"latest_performers": "Latest Performers",
|
|
||||||
"latest_scenes": "Latest Scenes",
|
|
||||||
"library": "Library",
|
"library": "Library",
|
||||||
"loading": {
|
"loading": {
|
||||||
"generic": "Loading…"
|
"generic": "Loading…"
|
||||||
@@ -796,7 +792,6 @@
|
|||||||
},
|
},
|
||||||
"megabits_per_second": "{value} megabits per second",
|
"megabits_per_second": "{value} megabits per second",
|
||||||
"metadata": "Metadata",
|
"metadata": "Metadata",
|
||||||
"most_active_studios": "Most Active Studios",
|
|
||||||
"movie": "Movie",
|
"movie": "Movie",
|
||||||
"movie_scene_number": "Movie Scene Number",
|
"movie_scene_number": "Movie Scene Number",
|
||||||
"movies": "Movies",
|
"movies": "Movies",
|
||||||
@@ -830,6 +825,11 @@
|
|||||||
"queue": "Queue",
|
"queue": "Queue",
|
||||||
"random": "Random",
|
"random": "Random",
|
||||||
"rating": "Rating",
|
"rating": "Rating",
|
||||||
|
"recently_added_performers": "Recently Added Performers",
|
||||||
|
"recently_added_studios": "Recently Added Studios",
|
||||||
|
"recently_released_galleries": "Recently Released Galleries",
|
||||||
|
"recently_released_movies": "Recently Released Movies",
|
||||||
|
"recently_released_scenes": "Recently Released Scenes",
|
||||||
"resolution": "Resolution",
|
"resolution": "Resolution",
|
||||||
"scene": "Scene",
|
"scene": "Scene",
|
||||||
"sceneTagger": "Scene Tagger",
|
"sceneTagger": "Scene Tagger",
|
||||||
@@ -969,6 +969,7 @@
|
|||||||
"updated_at": "Updated At",
|
"updated_at": "Updated At",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"videos": "Videos",
|
"videos": "Videos",
|
||||||
|
"view_all": "View All",
|
||||||
"weight": "Weight",
|
"weight": "Weight",
|
||||||
"years_old": "years old",
|
"years_old": "years old",
|
||||||
"stashbox": {
|
"stashbox": {
|
||||||
|
|||||||
Reference in New Issue
Block a user