Rename Movie to Group in UI (#4963)

* Replace movies with groups in the UI
* Massage menu items
* Change view names
* Rename Movie components to Group
* Refactor movie to group variable names
* Rename movie class names to group
This commit is contained in:
WithoutPants
2024-06-26 11:39:31 +10:00
committed by GitHub
parent d986a9eb4f
commit af6841be49
63 changed files with 643 additions and 612 deletions

View File

@@ -66,7 +66,7 @@ const Galleries = lazyComponent(
() => import("./components/Galleries/Galleries") () => import("./components/Galleries/Galleries")
); );
const Movies = lazyComponent(() => import("./components/Movies/Movies")); const Groups = lazyComponent(() => import("./components/Movies/Movies"));
const Tags = lazyComponent(() => import("./components/Tags/Tags")); const Tags = lazyComponent(() => import("./components/Tags/Tags"));
const Images = lazyComponent(() => import("./components/Images/Images")); const Images = lazyComponent(() => import("./components/Images/Images"));
const Setup = lazyComponent(() => import("./components/Setup/Setup")); const Setup = lazyComponent(() => import("./components/Setup/Setup"));
@@ -312,7 +312,7 @@ export const App: React.FC = () => {
<Route path="/performers" component={Performers} /> <Route path="/performers" component={Performers} />
<Route path="/tags" component={Tags} /> <Route path="/tags" component={Tags} />
<Route path="/studios" component={Studios} /> <Route path="/studios" component={Studios} />
<Route path="/movies" component={Movies} /> <Route path="/groups" component={Groups} />
<Route path="/stats" component={Stats} /> <Route path="/stats" component={Stats} />
<Route path="/settings" component={Settings} /> <Route path="/settings" component={Settings} />
<Route <Route

View File

@@ -7,7 +7,7 @@ import { ConfigurationContext } from "src/hooks/Config";
import { ListFilterModel } from "src/models/list-filter/filter"; import { ListFilterModel } from "src/models/list-filter/filter";
import { GalleryRecommendationRow } from "../Galleries/GalleryRecommendationRow"; import { GalleryRecommendationRow } from "../Galleries/GalleryRecommendationRow";
import { ImageRecommendationRow } from "../Images/ImageRecommendationRow"; import { ImageRecommendationRow } from "../Images/ImageRecommendationRow";
import { MovieRecommendationRow } from "../Movies/MovieRecommendationRow"; import { GroupRecommendationRow } from "../Movies/MovieRecommendationRow";
import { PerformerRecommendationRow } from "../Performers/PerformerRecommendationRow"; import { PerformerRecommendationRow } from "../Performers/PerformerRecommendationRow";
import { SceneRecommendationRow } from "../Scenes/SceneRecommendationRow"; import { SceneRecommendationRow } from "../Scenes/SceneRecommendationRow";
import { StudioRecommendationRow } from "../Studios/StudioRecommendationRow"; import { StudioRecommendationRow } from "../Studios/StudioRecommendationRow";
@@ -45,7 +45,7 @@ const RecommendationRow: React.FC<IFilter> = ({ mode, filter, header }) => {
); );
case GQL.FilterMode.Movies: case GQL.FilterMode.Movies:
return ( return (
<MovieRecommendationRow <GroupRecommendationRow
isTouch={isTouch} isTouch={isTouch}
filter={filter} filter={filter}
header={header} header={header}

View File

@@ -22,7 +22,7 @@ interface IAddSavedFilterModalProps {
const FilterModeToMessageID = { const FilterModeToMessageID = {
[GQL.FilterMode.Galleries]: "galleries", [GQL.FilterMode.Galleries]: "galleries",
[GQL.FilterMode.Images]: "images", [GQL.FilterMode.Images]: "images",
[GQL.FilterMode.Movies]: "movies", [GQL.FilterMode.Movies]: "groups",
[GQL.FilterMode.Performers]: "performers", [GQL.FilterMode.Performers]: "performers",
[GQL.FilterMode.SceneMarkers]: "markers", [GQL.FilterMode.SceneMarkers]: "markers",
[GQL.FilterMode.Scenes]: "scenes", [GQL.FilterMode.Scenes]: "scenes",

View File

@@ -111,7 +111,7 @@
} }
} }
.movie-skeleton { .group-skeleton {
max-width: 240px; max-width: 240px;
min-height: 540px; min-height: 540px;
min-width: 240px; min-width: 240px;
@@ -313,7 +313,7 @@
width: 20rem; width: 20rem;
} }
.slick-list .movie-card.card { .slick-list .group-card.card {
width: 16rem; width: 16rem;
} }

View File

@@ -193,7 +193,7 @@ const CriterionOptionList: React.FC<ICriterionList> = ({
const FilterModeToConfigKey = { const FilterModeToConfigKey = {
[FilterMode.Galleries]: "galleries", [FilterMode.Galleries]: "galleries",
[FilterMode.Images]: "images", [FilterMode.Images]: "images",
[FilterMode.Movies]: "movies", [FilterMode.Movies]: "groups",
[FilterMode.Performers]: "performers", [FilterMode.Performers]: "performers",
[FilterMode.SceneMarkers]: "sceneMarkers", [FilterMode.SceneMarkers]: "sceneMarkers",
[FilterMode.Scenes]: "scenes", [FilterMode.Scenes]: "scenes",

View File

@@ -24,7 +24,7 @@ export const LabeledIdFilter: React.FC<ILabeledIdFilterProps> = ({
inputType !== "performer_tags" && inputType !== "performer_tags" &&
inputType !== "tags" && inputType !== "tags" &&
inputType !== "scenes" && inputType !== "scenes" &&
inputType !== "movies" && inputType !== "groups" &&
inputType !== "galleries" inputType !== "galleries"
) { ) {
return null; return null;

View File

@@ -2,7 +2,7 @@ export enum View {
Galleries = "galleries", Galleries = "galleries",
Images = "images", Images = "images",
Scenes = "scenes", Scenes = "scenes",
Movies = "movies", Groups = "groups",
Performers = "performers", Performers = "performers",
Tags = "tags", Tags = "tags",
SceneMarkers = "scene_markers", SceneMarkers = "scene_markers",
@@ -17,7 +17,7 @@ export enum View {
PerformerScenes = "performer_scenes", PerformerScenes = "performer_scenes",
PerformerGalleries = "performer_galleries", PerformerGalleries = "performer_galleries",
PerformerImages = "performer_images", PerformerImages = "performer_images",
PerformerMovies = "performer_movies", PerformerGroups = "performer_groups",
PerformerAppearsWith = "performer_appears_with", PerformerAppearsWith = "performer_appears_with",
StudioGalleries = "studio_galleries", StudioGalleries = "studio_galleries",
@@ -26,9 +26,9 @@ export enum View {
GalleryImages = "gallery_images", GalleryImages = "gallery_images",
StudioScenes = "studio_scenes", StudioScenes = "studio_scenes",
StudioMovies = "studio_movies", StudioGroups = "studio_groups",
StudioPerformers = "studio_performers", StudioPerformers = "studio_performers",
StudioChildren = "studio_children", StudioChildren = "studio_children",
MovieScenes = "movie_scenes", GroupScenes = "group_scenes",
} }

View File

@@ -1,4 +1,10 @@
import React, { useEffect, useRef, useState, useCallback } from "react"; import React, {
useEffect,
useRef,
useState,
useCallback,
useMemo,
} from "react";
import { import {
defineMessages, defineMessages,
FormattedMessage, FormattedMessage,
@@ -52,9 +58,9 @@ const messages = defineMessages({
id: "images", id: "images",
defaultMessage: "Images", defaultMessage: "Images",
}, },
movies: { groups: {
id: "movies", id: "groups",
defaultMessage: "Movies", defaultMessage: "Groups",
}, },
markers: { markers: {
id: "markers", id: "markers",
@@ -107,9 +113,9 @@ const allMenuItems: IMenuItem[] = [
hotkey: "g i", hotkey: "g i",
}, },
{ {
name: "movies", name: "groups",
message: messages.movies, message: messages.groups,
href: "/movies", href: "/groups",
icon: faFilm, icon: faFilm,
hotkey: "g v", hotkey: "g v",
userCreatable: true, userCreatable: true,
@@ -179,20 +185,26 @@ export const MainNavbar: React.FC = () => {
const { configuration, loading } = React.useContext(ConfigurationContext); const { configuration, loading } = React.useContext(ConfigurationContext);
const { openManual } = React.useContext(ManualStateContext); const { openManual } = React.useContext(ManualStateContext);
// Show all menu items by default, unless config says otherwise
const [menuItems, setMenuItems] = useState<IMenuItem[]>(allMenuItems);
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
useEffect(() => { // Show all menu items by default, unless config says otherwise
const iCfg = configuration?.interface; const menuItems = useMemo(() => {
if (iCfg?.menuItems) { let cfgMenuItems = configuration?.interface.menuItems;
setMenuItems( if (!cfgMenuItems) {
allMenuItems.filter((menuItem) => return allMenuItems;
iCfg.menuItems!.includes(menuItem.name)
)
);
} }
// translate old movies menu item to groups
cfgMenuItems = cfgMenuItems.map((item) => {
if (item === "movies") {
return "groups";
}
return item;
});
return allMenuItems.filter((menuItem) =>
cfgMenuItems!.includes(menuItem.name)
);
}, [configuration]); }, [configuration]);
// react-bootstrap typing bug // react-bootstrap typing bug

View File

@@ -24,7 +24,7 @@ interface IListOperationProps {
onClose: (applied: boolean) => void; onClose: (applied: boolean) => void;
} }
export const EditMoviesDialog: React.FC<IListOperationProps> = ( export const EditGroupsDialog: React.FC<IListOperationProps> = (
props: IListOperationProps props: IListOperationProps
) => { ) => {
const intl = useIntl(); const intl = useIntl();
@@ -69,7 +69,7 @@ export const EditMoviesDialog: React.FC<IListOperationProps> = (
intl.formatMessage( intl.formatMessage(
{ id: "toast.updated_entity" }, { id: "toast.updated_entity" },
{ {
entity: intl.formatMessage({ id: "movies" }).toLocaleLowerCase(), entity: intl.formatMessage({ id: "groups" }).toLocaleLowerCase(),
} }
) )
); );
@@ -126,7 +126,7 @@ export const EditMoviesDialog: React.FC<IListOperationProps> = (
icon={faPencilAlt} icon={faPencilAlt}
header={intl.formatMessage( header={intl.formatMessage(
{ id: "actions.edit_entity" }, { id: "actions.edit_entity" },
{ entityType: intl.formatMessage({ id: "movies" }) } { entityType: intl.formatMessage({ id: "groups" }) }
)} )}
accept={{ accept={{
onClick: onSave, onClick: onSave,

View File

@@ -12,7 +12,7 @@ import { faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
import ScreenUtils from "src/utils/screen"; import ScreenUtils from "src/utils/screen";
interface IProps { interface IProps {
movie: GQL.MovieDataFragment; group: GQL.MovieDataFragment;
containerWidth?: number; containerWidth?: number;
sceneIndex?: number; sceneIndex?: number;
selecting?: boolean; selecting?: boolean;
@@ -20,8 +20,8 @@ interface IProps {
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void; onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
} }
export const MovieCard: React.FC<IProps> = ({ export const GroupCard: React.FC<IProps> = ({
movie, group,
sceneIndex, sceneIndex,
containerWidth, containerWidth,
selecting, selecting,
@@ -47,7 +47,7 @@ export const MovieCard: React.FC<IProps> = ({
return ( return (
<> <>
<hr /> <hr />
<span className="movie-scene-number"> <span className="group-scene-number">
<FormattedMessage id="scene" /> #{sceneIndex} <FormattedMessage id="scene" /> #{sceneIndex}
</span> </span>
</> </>
@@ -55,9 +55,9 @@ export const MovieCard: React.FC<IProps> = ({
} }
function maybeRenderScenesPopoverButton() { function maybeRenderScenesPopoverButton() {
if (movie.scenes.length === 0) return; if (group.scenes.length === 0) return;
const popoverContent = movie.scenes.map((scene) => ( const popoverContent = group.scenes.map((scene) => (
<SceneLink key={scene.id} scene={scene} /> <SceneLink key={scene.id} scene={scene} />
)); ));
@@ -69,31 +69,31 @@ export const MovieCard: React.FC<IProps> = ({
> >
<Button className="minimal"> <Button className="minimal">
<Icon icon={faPlayCircle} /> <Icon icon={faPlayCircle} />
<span>{movie.scenes.length}</span> <span>{group.scenes.length}</span>
</Button> </Button>
</HoverPopover> </HoverPopover>
); );
} }
function maybeRenderTagPopoverButton() { function maybeRenderTagPopoverButton() {
if (movie.tags.length <= 0) return; if (group.tags.length <= 0) return;
const popoverContent = movie.tags.map((tag) => ( const popoverContent = group.tags.map((tag) => (
<TagLink key={tag.id} linkType="movie" tag={tag} /> <TagLink key={tag.id} linkType="group" tag={tag} />
)); ));
return ( return (
<HoverPopover placement="bottom" content={popoverContent}> <HoverPopover placement="bottom" content={popoverContent}>
<Button className="minimal tag-count"> <Button className="minimal tag-count">
<Icon icon={faTag} /> <Icon icon={faTag} />
<span>{movie.tags.length}</span> <span>{group.tags.length}</span>
</Button> </Button>
</HoverPopover> </HoverPopover>
); );
} }
function maybeRenderPopoverButtonGroup() { function maybeRenderPopoverButtonGroup() {
if (sceneIndex || movie.scenes.length > 0 || movie.tags.length > 0) { if (sceneIndex || group.scenes.length > 0 || group.tags.length > 0) {
return ( return (
<> <>
{maybeRenderSceneNumber()} {maybeRenderSceneNumber()}
@@ -109,28 +109,28 @@ export const MovieCard: React.FC<IProps> = ({
return ( return (
<GridCard <GridCard
className="movie-card" className="group-card"
url={`/movies/${movie.id}`} url={`/groups/${group.id}`}
width={cardWidth} width={cardWidth}
title={movie.name} title={group.name}
linkClassName="movie-card-header" linkClassName="group-card-header"
image={ image={
<> <>
<img <img
loading="lazy" loading="lazy"
className="movie-card-image" className="group-card-image"
alt={movie.name ?? ""} alt={group.name ?? ""}
src={movie.front_image_path ?? ""} src={group.front_image_path ?? ""}
/> />
<RatingBanner rating={movie.rating100} /> <RatingBanner rating={group.rating100} />
</> </>
} }
details={ details={
<div className="movie-card__details"> <div className="group-card__details">
<span className="movie-card__date">{movie.date}</span> <span className="group-card__date">{group.date}</span>
<TruncatedText <TruncatedText
className="movie-card__description" className="group-card__description"
text={movie.synopsis} text={group.synopsis}
lineCount={3} lineCount={3}
/> />
</div> </div>

View File

@@ -1,27 +1,27 @@
import React from "react"; import React from "react";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { MovieCard } from "./MovieCard"; import { GroupCard } from "./MovieCard";
import { useContainerDimensions } from "../Shared/GridCard/GridCard"; import { useContainerDimensions } from "../Shared/GridCard/GridCard";
interface IMovieCardGrid { interface IGroupCardGrid {
movies: GQL.MovieDataFragment[]; groups: GQL.MovieDataFragment[];
selectedIds: Set<string>; selectedIds: Set<string>;
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void; onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
} }
export const MovieCardGrid: React.FC<IMovieCardGrid> = ({ export const GroupCardGrid: React.FC<IGroupCardGrid> = ({
movies, groups,
selectedIds, selectedIds,
onSelectChange, onSelectChange,
}) => { }) => {
const [componentRef, { width }] = useContainerDimensions(); const [componentRef, { width }] = useContainerDimensions();
return ( return (
<div className="row justify-content-center" ref={componentRef}> <div className="row justify-content-center" ref={componentRef}>
{movies.map((p) => ( {groups.map((p) => (
<MovieCard <GroupCard
key={p.id} key={p.id}
containerWidth={width} containerWidth={width}
movie={p} group={p}
selecting={selectedIds.size > 0} selecting={selectedIds.size > 0}
selected={selectedIds.has(p.id)} selected={selectedIds.has(p.id)}
onSelectedChanged={(selected: boolean, shiftKey: boolean) => onSelectedChanged={(selected: boolean, shiftKey: boolean) =>

View File

@@ -17,12 +17,12 @@ import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
import { useLightbox } from "src/hooks/Lightbox/hooks"; import { useLightbox } from "src/hooks/Lightbox/hooks";
import { ModalComponent } from "src/components/Shared/Modal"; import { ModalComponent } from "src/components/Shared/Modal";
import { useToast } from "src/hooks/Toast"; import { useToast } from "src/hooks/Toast";
import { MovieScenesPanel } from "./MovieScenesPanel"; import { GroupScenesPanel } from "./MovieScenesPanel";
import { import {
CompressedMovieDetailsPanel, CompressedMovieDetailsPanel,
MovieDetailsPanel, GroupDetailsPanel,
} from "./MovieDetailsPanel"; } from "./MovieDetailsPanel";
import { MovieEditPanel } from "./MovieEditPanel"; import { GroupEditPanel } from "./MovieEditPanel";
import { import {
faChevronDown, faChevronDown,
faChevronUp, faChevronUp,
@@ -38,14 +38,14 @@ import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
import { ExternalLinksButton } from "src/components/Shared/ExternalLinksButton"; import { ExternalLinksButton } from "src/components/Shared/ExternalLinksButton";
interface IProps { interface IProps {
movie: GQL.MovieDataFragment; group: GQL.MovieDataFragment;
} }
interface IMovieParams { interface IGroupParams {
id: string; id: string;
} }
const MoviePage: React.FC<IProps> = ({ movie }) => { const GroupPage: React.FC<IProps> = ({ group }) => {
const intl = useIntl(); const intl = useIntl();
const history = useHistory(); const history = useHistory();
const Toast = useToast(); const Toast = useToast();
@@ -70,35 +70,35 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
const [encodingImage, setEncodingImage] = useState<boolean>(false); const [encodingImage, setEncodingImage] = useState<boolean>(false);
const defaultImage = const defaultImage =
movie.front_image_path && movie.front_image_path.includes("default=true") group.front_image_path && group.front_image_path.includes("default=true")
? true ? true
: false; : false;
const lightboxImages = useMemo(() => { const lightboxImages = useMemo(() => {
const covers = [ const covers = [
...(movie.front_image_path && !defaultImage ...(group.front_image_path && !defaultImage
? [ ? [
{ {
paths: { paths: {
thumbnail: movie.front_image_path, thumbnail: group.front_image_path,
image: movie.front_image_path, image: group.front_image_path,
}, },
}, },
] ]
: []), : []),
...(movie.back_image_path ...(group.back_image_path
? [ ? [
{ {
paths: { paths: {
thumbnail: movie.back_image_path, thumbnail: group.back_image_path,
image: movie.back_image_path, image: group.back_image_path,
}, },
}, },
] ]
: []), : []),
]; ];
return covers; return covers;
}, [movie.front_image_path, movie.back_image_path, defaultImage]); }, [group.front_image_path, group.back_image_path, defaultImage]);
const index = lightboxImages.length; const index = lightboxImages.length;
@@ -108,7 +108,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
const [updateMovie, { loading: updating }] = useMovieUpdate(); const [updateMovie, { loading: updating }] = useMovieUpdate();
const [deleteMovie, { loading: deleting }] = useMovieDestroy({ const [deleteMovie, { loading: deleting }] = useMovieDestroy({
id: movie.id, id: group.id,
}); });
// set up hotkeys // set up hotkeys
@@ -135,7 +135,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
await updateMovie({ await updateMovie({
variables: { variables: {
input: { input: {
id: movie.id, id: group.id,
...input, ...input,
}, },
}, },
@@ -144,7 +144,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
Toast.success( Toast.success(
intl.formatMessage( intl.formatMessage(
{ id: "toast.updated_entity" }, { id: "toast.updated_entity" },
{ entity: intl.formatMessage({ id: "movie" }).toLocaleLowerCase() } { entity: intl.formatMessage({ id: "group" }).toLocaleLowerCase() }
) )
); );
} }
@@ -157,7 +157,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
} }
// redirect to movies page // redirect to movies page
history.push(`/movies`); history.push(`/groups`);
} }
function toggleEditing(value?: boolean) { function toggleEditing(value?: boolean) {
@@ -187,8 +187,8 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
id="dialogs.delete_confirm" id="dialogs.delete_confirm"
values={{ values={{
entityName: entityName:
movie.name ?? group.name ??
intl.formatMessage({ id: "movie" }).toLocaleLowerCase(), intl.formatMessage({ id: "group" }).toLocaleLowerCase(),
}} }}
/> />
</p> </p>
@@ -216,7 +216,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
} }
function renderFrontImage() { function renderFrontImage() {
let image = movie.front_image_path; let image = group.front_image_path;
if (isEditing) { if (isEditing) {
if (frontImage === null && image) { if (frontImage === null && image) {
const imageURL = new URL(image); const imageURL = new URL(image);
@@ -229,14 +229,14 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
if (image && defaultImage) { if (image && defaultImage) {
return ( return (
<div className="movie-image-container"> <div className="group-image-container">
<DetailImage alt="Front Cover" src={image} /> <DetailImage alt="Front Cover" src={image} />
</div> </div>
); );
} else if (image) { } else if (image) {
return ( return (
<Button <Button
className="movie-image-container" className="group-image-container"
variant="link" variant="link"
onClick={() => showLightbox()} onClick={() => showLightbox()}
> >
@@ -247,7 +247,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
} }
function renderBackImage() { function renderBackImage() {
let image = movie.back_image_path; let image = group.back_image_path;
if (isEditing) { if (isEditing) {
if (backImage === null) { if (backImage === null) {
image = undefined; image = undefined;
@@ -259,7 +259,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
if (image) { if (image) {
return ( return (
<Button <Button
className="movie-image-container" className="group-image-container"
variant="link" variant="link"
onClick={() => showLightbox(index - 1)} onClick={() => showLightbox(index - 1)}
> >
@@ -271,26 +271,26 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
const renderClickableIcons = () => ( const renderClickableIcons = () => (
<span className="name-icons"> <span className="name-icons">
{movie.urls.length > 0 && <ExternalLinksButton urls={movie.urls} />} {group.urls.length > 0 && <ExternalLinksButton urls={group.urls} />}
</span> </span>
); );
function maybeRenderAliases() { function maybeRenderAliases() {
if (movie?.aliases) { if (group?.aliases) {
return ( return (
<div> <div>
<span className="alias-head">{movie?.aliases}</span> <span className="alias-head">{group?.aliases}</span>
</div> </div>
); );
} }
} }
function setRating(v: number | null) { function setRating(v: number | null) {
if (movie.id) { if (group.id) {
updateMovie({ updateMovie({
variables: { variables: {
input: { input: {
id: movie.id, id: group.id,
rating100: v, rating100: v,
}, },
}, },
@@ -298,13 +298,13 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
} }
} }
const renderTabs = () => <MovieScenesPanel active={true} movie={movie} />; const renderTabs = () => <GroupScenesPanel active={true} group={group} />;
function maybeRenderDetails() { function maybeRenderDetails() {
if (!isEditing) { if (!isEditing) {
return ( return (
<MovieDetailsPanel <GroupDetailsPanel
movie={movie} group={group}
collapsed={collapsed} collapsed={collapsed}
fullWidth={!collapsed && !compactExpandedDetails} fullWidth={!collapsed && !compactExpandedDetails}
/> />
@@ -315,8 +315,8 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
function maybeRenderEditPanel() { function maybeRenderEditPanel() {
if (isEditing) { if (isEditing) {
return ( return (
<MovieEditPanel <GroupEditPanel
movie={movie} group={group}
onSubmit={onSave} onSubmit={onSave}
onCancel={() => toggleEditing()} onCancel={() => toggleEditing()}
onDelete={onDelete} onDelete={onDelete}
@@ -329,7 +329,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
{ {
return ( return (
<DetailsEditNavbar <DetailsEditNavbar
objectName={movie.name} objectName={group.name}
isNew={false} isNew={false}
isEditing={isEditing} isEditing={isEditing}
onToggleEdit={() => toggleEditing()} onToggleEdit={() => toggleEditing()}
@@ -343,12 +343,12 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
function maybeRenderCompressedDetails() { function maybeRenderCompressedDetails() {
if (!isEditing && loadStickyHeader) { if (!isEditing && loadStickyHeader) {
return <CompressedMovieDetailsPanel movie={movie} />; return <CompressedMovieDetailsPanel group={group} />;
} }
} }
function maybeRenderHeaderBackgroundImage() { function maybeRenderHeaderBackgroundImage() {
let image = movie.front_image_path; let image = group.front_image_path;
if (enableBackgroundImage && !isEditing && image) { if (enableBackgroundImage && !isEditing && image) {
const imageURL = new URL(image); const imageURL = new URL(image);
let isDefaultImage = imageURL.searchParams.get("default"); let isDefaultImage = imageURL.searchParams.get("default");
@@ -360,7 +360,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
<img <img
className="background-image" className="background-image"
src={image} src={image}
alt={`${movie.name} background`} alt={`${group.name} background`}
/> />
</picture> </picture>
</div> </div>
@@ -384,9 +384,9 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
}); });
return ( return (
<div id="movie-page" className="row"> <div id="group-page" className="row">
<Helmet> <Helmet>
<title>{movie?.name}</title> <title>{group?.name}</title>
</Helmet> </Helmet>
<div className={headerClassName}> <div className={headerClassName}>
@@ -399,7 +399,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
message={intl.formatMessage({ id: "actions.encoding_image" })} message={intl.formatMessage({ id: "actions.encoding_image" })}
/> />
) : ( ) : (
<div className="movie-images"> <div className="group-images">
{renderFrontImage()} {renderFrontImage()}
{renderBackImage()} {renderBackImage()}
</div> </div>
@@ -407,15 +407,15 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
</div> </div>
</div> </div>
<div className="row"> <div className="row">
<div className="movie-head col"> <div className="group-head col">
<h2> <h2>
<span className="movie-name">{movie.name}</span> <span className="group-name">{group.name}</span>
{maybeRenderShowCollapseButton()} {maybeRenderShowCollapseButton()}
{renderClickableIcons()} {renderClickableIcons()}
</h2> </h2>
{maybeRenderAliases()} {maybeRenderAliases()}
<RatingSystem <RatingSystem
value={movie.rating100} value={group.rating100}
onSetRating={(value) => setRating(value)} onSetRating={(value) => setRating(value)}
clickToRate clickToRate
withoutContext withoutContext
@@ -428,8 +428,8 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
</div> </div>
{maybeRenderCompressedDetails()} {maybeRenderCompressedDetails()}
<div className="detail-body"> <div className="detail-body">
<div className="movie-body"> <div className="group-body">
<div className="movie-tabs">{maybeRenderTab()}</div> <div className="group-tabs">{maybeRenderTab()}</div>
</div> </div>
</div> </div>
{renderDeleteAlert()} {renderDeleteAlert()}
@@ -437,7 +437,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
); );
}; };
const MovieLoader: React.FC<RouteComponentProps<IMovieParams>> = ({ const GroupLoader: React.FC<RouteComponentProps<IGroupParams>> = ({
match, match,
}) => { }) => {
const { id } = match.params; const { id } = match.params;
@@ -450,7 +450,7 @@ const MovieLoader: React.FC<RouteComponentProps<IMovieParams>> = ({
if (!data?.findMovie) if (!data?.findMovie)
return <ErrorMessage error={`No movie found with id ${id}.`} />; return <ErrorMessage error={`No movie found with id ${id}.`} />;
return <MoviePage movie={data.findMovie} />; return <GroupPage group={data.findMovie} />;
}; };
export default MovieLoader; export default GroupLoader;

View File

@@ -5,16 +5,16 @@ import { useHistory, useLocation } from "react-router-dom";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator"; import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
import { useToast } from "src/hooks/Toast"; import { useToast } from "src/hooks/Toast";
import { MovieEditPanel } from "./MovieEditPanel"; import { GroupEditPanel } from "./MovieEditPanel";
const MovieCreate: React.FC = () => { const GroupCreate: React.FC = () => {
const history = useHistory(); const history = useHistory();
const intl = useIntl(); const intl = useIntl();
const Toast = useToast(); const Toast = useToast();
const location = useLocation(); const location = useLocation();
const query = useMemo(() => new URLSearchParams(location.search), [location]); const query = useMemo(() => new URLSearchParams(location.search), [location]);
const movie = { const group = {
name: query.get("q") ?? undefined, name: query.get("q") ?? undefined,
}; };
@@ -30,7 +30,7 @@ const MovieCreate: React.FC = () => {
variables: { input }, variables: { input },
}); });
if (result.data?.movieCreate?.id) { if (result.data?.movieCreate?.id) {
history.push(`/movies/${result.data.movieCreate.id}`); history.push(`/groups/${result.data.movieCreate.id}`);
Toast.success( Toast.success(
intl.formatMessage( intl.formatMessage(
{ id: "toast.created_entity" }, { id: "toast.created_entity" },
@@ -43,7 +43,7 @@ const MovieCreate: React.FC = () => {
function renderFrontImage() { function renderFrontImage() {
if (frontImage) { if (frontImage) {
return ( return (
<div className="movie-image-container"> <div className="group-image-container">
<img alt="Front Cover" src={frontImage} /> <img alt="Front Cover" src={frontImage} />
</div> </div>
); );
@@ -53,7 +53,7 @@ const MovieCreate: React.FC = () => {
function renderBackImage() { function renderBackImage() {
if (backImage) { if (backImage) {
return ( return (
<div className="movie-image-container"> <div className="group-image-container">
<img alt="Back Cover" src={backImage} /> <img alt="Back Cover" src={backImage} />
</div> </div>
); );
@@ -63,24 +63,24 @@ const MovieCreate: React.FC = () => {
// TODO: CSS class // TODO: CSS class
return ( return (
<div className="row"> <div className="row">
<div className="movie-details mb-3 col"> <div className="group-details mb-3 col">
<div className="logo w-100"> <div className="logo w-100">
{encodingImage ? ( {encodingImage ? (
<LoadingIndicator <LoadingIndicator
message={intl.formatMessage({ id: "actions.encoding_image" })} message={intl.formatMessage({ id: "actions.encoding_image" })}
/> />
) : ( ) : (
<div className="movie-images"> <div className="group-images">
{renderFrontImage()} {renderFrontImage()}
{renderBackImage()} {renderBackImage()}
</div> </div>
)} )}
</div> </div>
<MovieEditPanel <GroupEditPanel
movie={movie} group={group}
onSubmit={onSave} onSubmit={onSave}
onCancel={() => history.push("/movies")} onCancel={() => history.push("/groups")}
onDelete={() => {}} onDelete={() => {}}
setFrontImage={setFrontImage} setFrontImage={setFrontImage}
setBackImage={setBackImage} setBackImage={setBackImage}
@@ -91,4 +91,4 @@ const MovieCreate: React.FC = () => {
); );
}; };
export default MovieCreate; export default GroupCreate;

View File

@@ -7,14 +7,14 @@ import { Link } from "react-router-dom";
import { DirectorLink } from "src/components/Shared/Link"; import { DirectorLink } from "src/components/Shared/Link";
import { TagLink } from "src/components/Shared/TagLink"; import { TagLink } from "src/components/Shared/TagLink";
interface IMovieDetailsPanel { interface IGroupDetailsPanel {
movie: GQL.MovieDataFragment; group: GQL.MovieDataFragment;
collapsed?: boolean; collapsed?: boolean;
fullWidth?: boolean; fullWidth?: boolean;
} }
export const MovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({ export const GroupDetailsPanel: React.FC<IGroupDetailsPanel> = ({
movie, group,
collapsed, collapsed,
fullWidth, fullWidth,
}) => { }) => {
@@ -22,13 +22,13 @@ export const MovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({
const intl = useIntl(); const intl = useIntl();
function renderTagsField() { function renderTagsField() {
if (!movie.tags.length) { if (!group.tags.length) {
return; return;
} }
return ( return (
<ul className="pl-0"> <ul className="pl-0">
{(movie.tags ?? []).map((tag) => ( {(group.tags ?? []).map((tag) => (
<TagLink key={tag.id} linkType="movie" tag={tag} /> <TagLink key={tag.id} linkType="group" tag={tag} />
))} ))}
</ul> </ul>
); );
@@ -40,7 +40,7 @@ export const MovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({
<> <>
<DetailItem <DetailItem
id="synopsis" id="synopsis"
value={movie.synopsis} value={group.synopsis}
fullWidth={fullWidth} fullWidth={fullWidth}
/> />
<DetailItem <DetailItem
@@ -58,21 +58,21 @@ export const MovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({
<DetailItem <DetailItem
id="duration" id="duration"
value={ value={
movie.duration ? TextUtils.secondsToTimestamp(movie.duration) : "" group.duration ? TextUtils.secondsToTimestamp(group.duration) : ""
} }
fullWidth={fullWidth} fullWidth={fullWidth}
/> />
<DetailItem <DetailItem
id="date" id="date"
value={movie.date ? TextUtils.formatDate(intl, movie.date) : ""} value={group.date ? TextUtils.formatDate(intl, group.date) : ""}
fullWidth={fullWidth} fullWidth={fullWidth}
/> />
<DetailItem <DetailItem
id="studio" id="studio"
value={ value={
movie.studio?.id ? ( group.studio?.id ? (
<Link to={`/studios/${movie.studio?.id}`}> <Link to={`/studios/${group.studio?.id}`}>
{movie.studio?.name} {group.studio?.name}
</Link> </Link>
) : ( ) : (
"" ""
@@ -84,8 +84,8 @@ export const MovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({
<DetailItem <DetailItem
id="director" id="director"
value={ value={
movie.director ? ( group.director ? (
<DirectorLink director={movie.director} linkType="movie" /> <DirectorLink director={group.director} linkType="group" />
) : ( ) : (
"" ""
) )
@@ -97,8 +97,8 @@ export const MovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({
); );
}; };
export const CompressedMovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({ export const CompressedMovieDetailsPanel: React.FC<IGroupDetailsPanel> = ({
movie, group,
}) => { }) => {
function scrollToTop() { function scrollToTop() {
window.scrollTo({ top: 0, behavior: "smooth" }); window.scrollTo({ top: 0, behavior: "smooth" });
@@ -107,13 +107,13 @@ export const CompressedMovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({
return ( return (
<div className="sticky detail-header"> <div className="sticky detail-header">
<div className="sticky detail-header-group"> <div className="sticky detail-header-group">
<a className="movie-name" onClick={() => scrollToTop()}> <a className="group-name" onClick={() => scrollToTop()}>
{movie.name} {group.name}
</a> </a>
{movie?.studio?.name ? ( {group?.studio?.name ? (
<> <>
<span className="detail-divider">/</span> <span className="detail-divider">/</span>
<span className="movie-studio">{movie?.studio?.name}</span> <span className="group-studio">{group?.studio?.name}</span>
</> </>
) : ( ) : (
"" ""

View File

@@ -15,7 +15,7 @@ import TextUtils from "src/utils/text";
import ImageUtils from "src/utils/image"; import ImageUtils from "src/utils/image";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { Prompt } from "react-router-dom"; import { Prompt } from "react-router-dom";
import { MovieScrapeDialog } from "./MovieScrapeDialog"; import { GroupScrapeDialog } from "./MovieScrapeDialog";
import isEqual from "lodash-es/isEqual"; import isEqual from "lodash-es/isEqual";
import { handleUnsavedChanges } from "src/utils/navigation"; import { handleUnsavedChanges } from "src/utils/navigation";
import { formikUtils } from "src/utils/form"; import { formikUtils } from "src/utils/form";
@@ -27,8 +27,8 @@ import {
import { Studio, StudioSelect } from "src/components/Studios/StudioSelect"; import { Studio, StudioSelect } from "src/components/Studios/StudioSelect";
import { useTagsEdit } from "src/hooks/tagsEdit"; import { useTagsEdit } from "src/hooks/tagsEdit";
interface IMovieEditPanel { interface IGroupEditPanel {
movie: Partial<GQL.MovieDataFragment>; group: Partial<GQL.MovieDataFragment>;
onSubmit: (movie: GQL.MovieCreateInput) => Promise<void>; onSubmit: (movie: GQL.MovieCreateInput) => Promise<void>;
onCancel: () => void; onCancel: () => void;
onDelete: () => void; onDelete: () => void;
@@ -37,8 +37,8 @@ interface IMovieEditPanel {
setEncodingImage: (loading: boolean) => void; setEncodingImage: (loading: boolean) => void;
} }
export const MovieEditPanel: React.FC<IMovieEditPanel> = ({ export const GroupEditPanel: React.FC<IGroupEditPanel> = ({
movie, group,
onSubmit, onSubmit,
onCancel, onCancel,
onDelete, onDelete,
@@ -49,7 +49,7 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
const intl = useIntl(); const intl = useIntl();
const Toast = useToast(); const Toast = useToast();
const isNew = movie.id === undefined; const isNew = group.id === undefined;
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isImageAlertOpen, setIsImageAlertOpen] = useState<boolean>(false); const [isImageAlertOpen, setIsImageAlertOpen] = useState<boolean>(false);
@@ -57,7 +57,7 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
const [imageClipboard, setImageClipboard] = useState<string>(); const [imageClipboard, setImageClipboard] = useState<string>();
const Scrapers = useListMovieScrapers(); const Scrapers = useListMovieScrapers();
const [scrapedMovie, setScrapedMovie] = useState<GQL.ScrapedMovie>(); const [scrapedGroup, setScrapedGroup] = useState<GQL.ScrapedMovie>();
const [studio, setStudio] = useState<Studio | null>(null); const [studio, setStudio] = useState<Studio | null>(null);
@@ -76,15 +76,15 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
}); });
const initialValues = { const initialValues = {
name: movie?.name ?? "", name: group?.name ?? "",
aliases: movie?.aliases ?? "", aliases: group?.aliases ?? "",
duration: movie?.duration ?? null, duration: group?.duration ?? null,
date: movie?.date ?? "", date: group?.date ?? "",
studio_id: movie?.studio?.id ?? null, studio_id: group?.studio?.id ?? null,
tag_ids: (movie?.tags ?? []).map((t) => t.id), tag_ids: (group?.tags ?? []).map((t) => t.id),
director: movie?.director ?? "", director: group?.director ?? "",
urls: movie?.urls ?? [], urls: group?.urls ?? [],
synopsis: movie?.synopsis ?? "", synopsis: group?.synopsis ?? "",
}; };
type InputValues = yup.InferType<typeof schema>; type InputValues = yup.InferType<typeof schema>;
@@ -97,7 +97,7 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
}); });
const { tags, updateTagsStateFromScraper, tagsControl } = useTagsEdit( const { tags, updateTagsStateFromScraper, tagsControl } = useTagsEdit(
movie.tags, group.tags,
(ids) => formik.setFieldValue("tag_ids", ids) (ids) => formik.setFieldValue("tag_ids", ids)
); );
@@ -107,8 +107,8 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
} }
useEffect(() => { useEffect(() => {
setStudio(movie.studio ?? null); setStudio(group.studio ?? null);
}, [movie.studio]); }, [group.studio]);
// set up hotkeys // set up hotkeys
useEffect(() => { useEffect(() => {
@@ -128,7 +128,7 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
}; };
}); });
function updateMovieEditStateFromScraper( function updateGroupEditStateFromScraper(
state: Partial<GQL.ScrapedMovieDataFragment> state: Partial<GQL.ScrapedMovieDataFragment>
) { ) {
if (state.name) { if (state.name) {
@@ -200,11 +200,11 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
return; return;
} }
// if this is a new movie, just dump the data // if this is a new group, just dump the data
if (isNew) { if (isNew) {
updateMovieEditStateFromScraper(result.data.scrapeMovieURL); updateGroupEditStateFromScraper(result.data.scrapeMovieURL);
} else { } else {
setScrapedMovie(result.data.scrapeMovieURL); setScrapedGroup(result.data.scrapeMovieURL);
} }
} catch (e) { } catch (e) {
Toast.error(e); Toast.error(e);
@@ -223,25 +223,25 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
} }
function maybeRenderScrapeDialog() { function maybeRenderScrapeDialog() {
if (!scrapedMovie) { if (!scrapedGroup) {
return; return;
} }
const currentMovie = { const currentGroup = {
id: movie.id!, id: group.id!,
...formik.values, ...formik.values,
}; };
// Get image paths for scrape gui // Get image paths for scrape gui
currentMovie.front_image = movie?.front_image_path; currentGroup.front_image = group?.front_image_path;
currentMovie.back_image = movie?.back_image_path; currentGroup.back_image = group?.back_image_path;
return ( return (
<MovieScrapeDialog <GroupScrapeDialog
movie={currentMovie} group={currentGroup}
movieStudio={studio} groupStudio={studio}
movieTags={tags} groupTags={tags}
scraped={scrapedMovie} scraped={scrapedGroup}
onClose={(m) => { onClose={(m) => {
onScrapeDialogClosed(m); onScrapeDialogClosed(m);
}} }}
@@ -251,9 +251,9 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
function onScrapeDialogClosed(p?: GQL.ScrapedMovieDataFragment) { function onScrapeDialogClosed(p?: GQL.ScrapedMovieDataFragment) {
if (p) { if (p) {
updateMovieEditStateFromScraper(p); updateGroupEditStateFromScraper(p);
} }
setScrapedMovie(undefined); setScrapedGroup(undefined);
} }
const encodingImage = ImageUtils.usePasteImage(showImageAlert); const encodingImage = ImageUtils.usePasteImage(showImageAlert);
@@ -373,7 +373,7 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
<h2> <h2>
{intl.formatMessage( {intl.formatMessage(
{ id: "actions.add_entity" }, { id: "actions.add_entity" },
{ entityType: intl.formatMessage({ id: "movie" }) } { entityType: intl.formatMessage({ id: "group" }) }
)} )}
</h2> </h2>
)} )}
@@ -382,14 +382,14 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
when={formik.dirty} when={formik.dirty}
message={(location, action) => { message={(location, action) => {
// Check if it's a redirect after movie creation // Check if it's a redirect after movie creation
if (action === "PUSH" && location.pathname.startsWith("/movies/")) if (action === "PUSH" && location.pathname.startsWith("/groups/"))
return true; return true;
return handleUnsavedChanges(intl, "movies", movie.id)(location); return handleUnsavedChanges(intl, "groups", group.id)(location);
}} }}
/> />
<Form noValidate onSubmit={formik.handleSubmit} id="movie-edit"> <Form noValidate onSubmit={formik.handleSubmit} id="group-edit">
{renderInputField("name")} {renderInputField("name")}
{renderInputField("aliases")} {renderInputField("aliases")}
{renderDurationField("duration")} {renderDurationField("duration")}
@@ -402,7 +402,7 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
</Form> </Form>
<DetailsEditNavbar <DetailsEditNavbar
objectName={movie?.name ?? intl.formatMessage({ id: "movie" })} objectName={group?.name ?? intl.formatMessage({ id: "group" })}
isNew={isNew} isNew={isNew}
classNames="col-xl-9 mt-3" classNames="col-xl-9 mt-3"
isEditing isEditing

View File

@@ -5,17 +5,17 @@ import { ListFilterModel } from "src/models/list-filter/filter";
import { SceneList } from "src/components/Scenes/SceneList"; import { SceneList } from "src/components/Scenes/SceneList";
import { View } from "src/components/List/views"; import { View } from "src/components/List/views";
interface IMovieScenesPanel { interface IGroupScenesPanel {
active: boolean; active: boolean;
movie: GQL.MovieDataFragment; group: GQL.MovieDataFragment;
} }
export const MovieScenesPanel: React.FC<IMovieScenesPanel> = ({ export const GroupScenesPanel: React.FC<IGroupScenesPanel> = ({
active, active,
movie, group,
}) => { }) => {
function filterHook(filter: ListFilterModel) { function filterHook(filter: ListFilterModel) {
const movieValue = { id: movie.id, label: movie.name }; const movieValue = { id: group.id, label: group.name };
// if movie is already present, then we modify it, otherwise add // if movie is already present, then we modify it, otherwise add
let movieCriterion = filter.criteria.find((c) => { let movieCriterion = filter.criteria.find((c) => {
return c.criterionOption.type === "movies"; return c.criterionOption.type === "movies";
@@ -29,7 +29,7 @@ export const MovieScenesPanel: React.FC<IMovieScenesPanel> = ({
// add the movie if not present // add the movie if not present
if ( if (
!movieCriterion.value.find((p) => { !movieCriterion.value.find((p) => {
return p.id === movie.id; return p.id === group.id;
}) })
) { ) {
movieCriterion.value.push(movieValue); movieCriterion.value.push(movieValue);
@@ -46,13 +46,13 @@ export const MovieScenesPanel: React.FC<IMovieScenesPanel> = ({
return filter; return filter;
} }
if (movie && movie.id) { if (group && group.id) {
return ( return (
<SceneList <SceneList
filterHook={filterHook} filterHook={filterHook}
defaultSort="movie_scene_number" defaultSort="movie_scene_number"
alterQuery={active} alterQuery={active}
view={View.MovieScenes} view={View.GroupScenes}
/> />
); );
} }

View File

@@ -20,33 +20,33 @@ import { uniq } from "lodash-es";
import { Tag } from "src/components/Tags/TagSelect"; import { Tag } from "src/components/Tags/TagSelect";
import { useScrapedTags } from "src/components/Shared/ScrapeDialog/scrapedTags"; import { useScrapedTags } from "src/components/Shared/ScrapeDialog/scrapedTags";
interface IMovieScrapeDialogProps { interface IGroupScrapeDialogProps {
movie: Partial<GQL.MovieUpdateInput>; group: Partial<GQL.MovieUpdateInput>;
movieStudio: Studio | null; groupStudio: Studio | null;
movieTags: Tag[]; groupTags: Tag[];
scraped: GQL.ScrapedMovie; scraped: GQL.ScrapedMovie;
onClose: (scrapedMovie?: GQL.ScrapedMovie) => void; onClose: (scrapedMovie?: GQL.ScrapedMovie) => void;
} }
export const MovieScrapeDialog: React.FC<IMovieScrapeDialogProps> = ({ export const GroupScrapeDialog: React.FC<IGroupScrapeDialogProps> = ({
movie, group,
movieStudio, groupStudio: groupStudio,
movieTags, groupTags: groupTags,
scraped, scraped,
onClose, onClose,
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const [name, setName] = useState<ScrapeResult<string>>( const [name, setName] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(movie.name, scraped.name) new ScrapeResult<string>(group.name, scraped.name)
); );
const [aliases, setAliases] = useState<ScrapeResult<string>>( const [aliases, setAliases] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(movie.aliases, scraped.aliases) new ScrapeResult<string>(group.aliases, scraped.aliases)
); );
const [duration, setDuration] = useState<ScrapeResult<string>>( const [duration, setDuration] = useState<ScrapeResult<string>>(
new ScrapeResult<string>( new ScrapeResult<string>(
TextUtils.secondsToTimestamp(movie.duration || 0), TextUtils.secondsToTimestamp(group.duration || 0),
// convert seconds to string if it's a number // convert seconds to string if it's a number
scraped.duration && !isNaN(+scraped.duration) scraped.duration && !isNaN(+scraped.duration)
? TextUtils.secondsToTimestamp(parseInt(scraped.duration, 10)) ? TextUtils.secondsToTimestamp(parseInt(scraped.duration, 10))
@@ -54,20 +54,20 @@ export const MovieScrapeDialog: React.FC<IMovieScrapeDialogProps> = ({
) )
); );
const [date, setDate] = useState<ScrapeResult<string>>( const [date, setDate] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(movie.date, scraped.date) new ScrapeResult<string>(group.date, scraped.date)
); );
const [director, setDirector] = useState<ScrapeResult<string>>( const [director, setDirector] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(movie.director, scraped.director) new ScrapeResult<string>(group.director, scraped.director)
); );
const [synopsis, setSynopsis] = useState<ScrapeResult<string>>( const [synopsis, setSynopsis] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(movie.synopsis, scraped.synopsis) new ScrapeResult<string>(group.synopsis, scraped.synopsis)
); );
const [studio, setStudio] = useState<ObjectScrapeResult<GQL.ScrapedStudio>>( const [studio, setStudio] = useState<ObjectScrapeResult<GQL.ScrapedStudio>>(
new ObjectScrapeResult<GQL.ScrapedStudio>( new ObjectScrapeResult<GQL.ScrapedStudio>(
movieStudio groupStudio
? { ? {
stored_id: movieStudio.id, stored_id: groupStudio.id,
name: movieStudio.name, name: groupStudio.name,
} }
: undefined, : undefined,
scraped.studio?.stored_id ? scraped.studio : undefined scraped.studio?.stored_id ? scraped.studio : undefined
@@ -75,17 +75,17 @@ export const MovieScrapeDialog: React.FC<IMovieScrapeDialogProps> = ({
); );
const [urls, setURLs] = useState<ScrapeResult<string[]>>( const [urls, setURLs] = useState<ScrapeResult<string[]>>(
new ScrapeResult<string[]>( new ScrapeResult<string[]>(
movie.urls, group.urls,
scraped.urls scraped.urls
? uniq((movie.urls ?? []).concat(scraped.urls ?? [])) ? uniq((group.urls ?? []).concat(scraped.urls ?? []))
: undefined : undefined
) )
); );
const [frontImage, setFrontImage] = useState<ScrapeResult<string>>( const [frontImage, setFrontImage] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(movie.front_image, scraped.front_image) new ScrapeResult<string>(group.front_image, scraped.front_image)
); );
const [backImage, setBackImage] = useState<ScrapeResult<string>>( const [backImage, setBackImage] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(movie.back_image, scraped.back_image) new ScrapeResult<string>(group.back_image, scraped.back_image)
); );
const [newStudio, setNewStudio] = useState<GQL.ScrapedStudio | undefined>( const [newStudio, setNewStudio] = useState<GQL.ScrapedStudio | undefined>(
@@ -99,7 +99,7 @@ export const MovieScrapeDialog: React.FC<IMovieScrapeDialogProps> = ({
}); });
const { tags, newTags, scrapedTagsRow } = useScrapedTags( const { tags, newTags, scrapedTagsRow } = useScrapedTags(
movieTags, groupTags,
scraped.tags scraped.tags
); );
@@ -194,13 +194,13 @@ export const MovieScrapeDialog: React.FC<IMovieScrapeDialogProps> = ({
{scrapedTagsRow} {scrapedTagsRow}
<ScrapedImageRow <ScrapedImageRow
title="Front Image" title="Front Image"
className="movie-image" className="group-image"
result={frontImage} result={frontImage}
onChange={(value) => setFrontImage(value)} onChange={(value) => setFrontImage(value)}
/> />
<ScrapedImageRow <ScrapedImageRow
title="Back Image" title="Back Image"
className="movie-image" className="group-image"
result={backImage} result={backImage}
onChange={(value) => setBackImage(value)} onChange={(value) => setBackImage(value)}
/> />
@@ -212,7 +212,7 @@ export const MovieScrapeDialog: React.FC<IMovieScrapeDialogProps> = ({
<ScrapeDialog <ScrapeDialog
title={intl.formatMessage( title={intl.formatMessage(
{ id: "dialogs.scrape_entity_title" }, { id: "dialogs.scrape_entity_title" },
{ entity_type: intl.formatMessage({ id: "movie" }) } { entity_type: intl.formatMessage({ id: "group" }) }
)} )}
renderScrapeRows={renderScrapeRows} renderScrapeRows={renderScrapeRows}
onClose={(apply) => { onClose={(apply) => {

View File

@@ -14,11 +14,11 @@ import {
import { makeItemList, showWhenSelected } from "../List/ItemList"; import { makeItemList, showWhenSelected } from "../List/ItemList";
import { ExportDialog } from "../Shared/ExportDialog"; import { ExportDialog } from "../Shared/ExportDialog";
import { DeleteEntityDialog } from "../Shared/DeleteEntityDialog"; import { DeleteEntityDialog } from "../Shared/DeleteEntityDialog";
import { MovieCardGrid } from "./MovieCardGrid"; import { GroupCardGrid } from "./MovieCardGrid";
import { EditMoviesDialog } from "./EditMoviesDialog"; import { EditGroupsDialog } from "./EditMoviesDialog";
import { View } from "../List/views"; import { View } from "../List/views";
const MovieItemList = makeItemList({ const GroupItemList = makeItemList({
filterMode: GQL.FilterMode.Movies, filterMode: GQL.FilterMode.Movies,
useResult: useFindMovies, useResult: useFindMovies,
getItems(result: GQL.FindMoviesQueryResult) { getItems(result: GQL.FindMoviesQueryResult) {
@@ -29,13 +29,13 @@ const MovieItemList = makeItemList({
}, },
}); });
interface IMovieList { interface IGroupList {
filterHook?: (filter: ListFilterModel) => ListFilterModel; filterHook?: (filter: ListFilterModel) => ListFilterModel;
view?: View; view?: View;
alterQuery?: boolean; alterQuery?: boolean;
} }
export const MovieList: React.FC<IMovieList> = ({ export const GroupList: React.FC<IGroupList> = ({
filterHook, filterHook,
alterQuery, alterQuery,
view, view,
@@ -90,7 +90,7 @@ export const MovieList: React.FC<IMovieList> = ({
if (singleResult.data.findMovies.movies.length === 1) { if (singleResult.data.findMovies.movies.length === 1) {
const { id } = singleResult.data.findMovies.movies[0]; const { id } = singleResult.data.findMovies.movies[0];
// navigate to the movie page // navigate to the movie page
history.push(`/movies/${id}`); history.push(`/groups/${id}`);
} }
} }
} }
@@ -111,7 +111,7 @@ export const MovieList: React.FC<IMovieList> = ({
selectedIds: Set<string>, selectedIds: Set<string>,
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
) { ) {
function maybeRenderMovieExportDialog() { function maybeRenderGroupExportDialog() {
if (isExportDialogOpen) { if (isExportDialogOpen) {
return ( return (
<ExportDialog <ExportDialog
@@ -127,13 +127,13 @@ export const MovieList: React.FC<IMovieList> = ({
} }
} }
function renderMovies() { function renderGroups() {
if (!result.data?.findMovies) return; if (!result.data?.findMovies) return;
if (filter.displayMode === DisplayMode.Grid) { if (filter.displayMode === DisplayMode.Grid) {
return ( return (
<MovieCardGrid <GroupCardGrid
movies={result.data.findMovies.movies} groups={result.data.findMovies.movies}
selectedIds={selectedIds} selectedIds={selectedIds}
onSelectChange={onSelectChange} onSelectChange={onSelectChange}
/> />
@@ -145,36 +145,36 @@ export const MovieList: React.FC<IMovieList> = ({
} }
return ( return (
<> <>
{maybeRenderMovieExportDialog()} {maybeRenderGroupExportDialog()}
{renderMovies()} {renderGroups()}
</> </>
); );
} }
function renderEditDialog( function renderEditDialog(
selectedMovies: GQL.MovieDataFragment[], selectedGroups: GQL.MovieDataFragment[],
onClose: (applied: boolean) => void onClose: (applied: boolean) => void
) { ) {
return <EditMoviesDialog selected={selectedMovies} onClose={onClose} />; return <EditGroupsDialog selected={selectedGroups} onClose={onClose} />;
} }
function renderDeleteDialog( function renderDeleteDialog(
selectedMovies: GQL.SlimMovieDataFragment[], selectedGroups: GQL.SlimMovieDataFragment[],
onClose: (confirmed: boolean) => void onClose: (confirmed: boolean) => void
) { ) {
return ( return (
<DeleteEntityDialog <DeleteEntityDialog
selected={selectedMovies} selected={selectedGroups}
onClose={onClose} onClose={onClose}
singularEntity={intl.formatMessage({ id: "movie" })} singularEntity={intl.formatMessage({ id: "group" })}
pluralEntity={intl.formatMessage({ id: "movies" })} pluralEntity={intl.formatMessage({ id: "groups" })}
destroyMutation={useMoviesDestroy} destroyMutation={useMoviesDestroy}
/> />
); );
} }
return ( return (
<MovieItemList <GroupItemList
selectable selectable
filterHook={filterHook} filterHook={filterHook}
view={view} view={view}

View File

@@ -2,7 +2,7 @@ import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useFindMovies } from "src/core/StashService"; import { useFindMovies } from "src/core/StashService";
import Slider from "@ant-design/react-slick"; import Slider from "@ant-design/react-slick";
import { MovieCard } from "./MovieCard"; import { GroupCard } from "./MovieCard";
import { ListFilterModel } from "src/models/list-filter/filter"; import { ListFilterModel } from "src/models/list-filter/filter";
import { getSlickSliderSettings } from "src/core/recommendations"; import { getSlickSliderSettings } from "src/core/recommendations";
import { RecommendationRow } from "../FrontPage/RecommendationRow"; import { RecommendationRow } from "../FrontPage/RecommendationRow";
@@ -14,7 +14,7 @@ interface IProps {
header: string; header: string;
} }
export const MovieRecommendationRow: React.FC<IProps> = (props: IProps) => { export const GroupRecommendationRow: React.FC<IProps> = (props: IProps) => {
const result = useFindMovies(props.filter); const result = useFindMovies(props.filter);
const cardCount = result.data?.findMovies.count; const cardCount = result.data?.findMovies.count;
@@ -24,10 +24,10 @@ export const MovieRecommendationRow: React.FC<IProps> = (props: IProps) => {
return ( return (
<RecommendationRow <RecommendationRow
className="movie-recommendations" className="group-recommendations"
header={props.header} header={props.header}
link={ link={
<Link to={`/movies?${props.filter.makeQueryParameters()}`}> <Link to={`/groups?${props.filter.makeQueryParameters()}`}>
<FormattedMessage id="view_all" /> <FormattedMessage id="view_all" />
</Link> </Link>
} }
@@ -40,10 +40,10 @@ export const MovieRecommendationRow: React.FC<IProps> = (props: IProps) => {
> >
{result.loading {result.loading
? [...Array(props.filter.itemsPerPage)].map((i) => ( ? [...Array(props.filter.itemsPerPage)].map((i) => (
<div key={`_${i}`} className="movie-skeleton skeleton-card"></div> <div key={`_${i}`} className="group-skeleton skeleton-card"></div>
)) ))
: result.data?.findMovies.movies.map((m) => ( : result.data?.findMovies.movies.map((m) => (
<MovieCard key={m.id} movie={m} /> <GroupCard key={m.id} group={m} />
))} ))}
</Slider> </Slider>
</RecommendationRow> </RecommendationRow>

View File

@@ -30,13 +30,13 @@ import { sortByRelevance } from "src/utils/query";
import { PatchComponent, PatchFunction } from "src/patch"; import { PatchComponent, PatchFunction } from "src/patch";
import { TruncatedText } from "../Shared/TruncatedText"; import { TruncatedText } from "../Shared/TruncatedText";
export type Movie = Pick< export type Group = Pick<
GQL.Movie, GQL.Movie,
"id" | "name" | "date" | "front_image_path" | "aliases" "id" | "name" | "date" | "front_image_path" | "aliases"
> & { > & {
studio?: Pick<GQL.Studio, "name"> | null; studio?: Pick<GQL.Studio, "name"> | null;
}; };
type Option = SelectOption<Movie>; type Option = SelectOption<Group>;
type FindMoviesResult = Awaited< type FindMoviesResult = Awaited<
ReturnType<typeof queryFindMoviesForSelect> ReturnType<typeof queryFindMoviesForSelect>
@@ -56,9 +56,9 @@ const movieSelectSort = PatchFunction(
sortMoviesByRelevance sortMoviesByRelevance
); );
const _MovieSelect: React.FC< const _GroupSelect: React.FC<
IFilterProps & IFilterProps &
IFilterValueProps<Movie> & { IFilterValueProps<Group> & {
hoverPlacement?: Placement; hoverPlacement?: Placement;
excludeIds?: string[]; excludeIds?: string[];
} }
@@ -94,7 +94,7 @@ const _MovieSelect: React.FC<
})); }));
} }
const MovieOption: React.FC<OptionProps<Option, boolean>> = (optionProps) => { const GroupOption: React.FC<OptionProps<Option, boolean>> = (optionProps) => {
let thisOptionProps = optionProps; let thisOptionProps = optionProps;
const { object } = optionProps.data; const { object } = optionProps.data;
@@ -111,24 +111,24 @@ const _MovieSelect: React.FC<
thisOptionProps = { thisOptionProps = {
...optionProps, ...optionProps,
children: ( children: (
<span className="movie-select-option"> <span className="group-select-option">
<span className="movie-select-row"> <span className="group-select-row">
{object.front_image_path && ( {object.front_image_path && (
<img <img
className="movie-select-image" className="group-select-image"
src={object.front_image_path} src={object.front_image_path}
loading="lazy" loading="lazy"
/> />
)} )}
<span className="movie-select-details"> <span className="group-select-details">
<TruncatedText <TruncatedText
className="movie-select-title" className="group-select-title"
text={ text={
<span> <span>
{title} {title}
{alias && ( {alias && (
<span className="movie-select-alias">{` (${alias})`}</span> <span className="group-select-alias">{` (${alias})`}</span>
)} )}
</span> </span>
} }
@@ -136,13 +136,13 @@ const _MovieSelect: React.FC<
/> />
{object.studio?.name && ( {object.studio?.name && (
<span className="movie-select-studio"> <span className="group-select-studio">
{object.studio?.name} {object.studio?.name}
</span> </span>
)} )}
{object.date && ( {object.date && (
<span className="movie-select-date">{object.date}</span> <span className="group-select-date">{object.date}</span>
)} )}
</span> </span>
</span> </span>
@@ -153,7 +153,7 @@ const _MovieSelect: React.FC<
return <reactSelectComponents.Option {...thisOptionProps} />; return <reactSelectComponents.Option {...thisOptionProps} />;
}; };
const MovieMultiValueLabel: React.FC< const GroupMultiValueLabel: React.FC<
MultiValueGenericProps<Option, boolean> MultiValueGenericProps<Option, boolean>
> = (optionProps) => { > = (optionProps) => {
let thisOptionProps = optionProps; let thisOptionProps = optionProps;
@@ -168,7 +168,7 @@ const _MovieSelect: React.FC<
return <reactSelectComponents.MultiValueLabel {...thisOptionProps} />; return <reactSelectComponents.MultiValueLabel {...thisOptionProps} />;
}; };
const MovieValueLabel: React.FC<SingleValueProps<Option, boolean>> = ( const GroupValueLabel: React.FC<SingleValueProps<Option, boolean>> = (
optionProps optionProps
) => { ) => {
let thisOptionProps = optionProps; let thisOptionProps = optionProps;
@@ -190,7 +190,7 @@ const _MovieSelect: React.FC<
return { return {
value: result.data!.movieCreate!.id, value: result.data!.movieCreate!.id,
item: result.data!.movieCreate!, item: result.data!.movieCreate!,
message: "Created movie", message: "Created group",
}; };
}; };
@@ -201,7 +201,7 @@ const _MovieSelect: React.FC<
}; };
}; };
const isValidNewOption = (inputValue: string, options: Movie[]) => { const isValidNewOption = (inputValue: string, options: Group[]) => {
if (!inputValue) { if (!inputValue) {
return false; return false;
} }
@@ -221,12 +221,12 @@ const _MovieSelect: React.FC<
}; };
return ( return (
<FilterSelectComponent<Movie, boolean> <FilterSelectComponent<Group, boolean>
{...props} {...props}
className={cx( className={cx(
"movie-select", "group-select",
{ {
"movie-select-active": props.active, "group-select-active": props.active,
}, },
props.className props.className
)} )}
@@ -234,9 +234,9 @@ const _MovieSelect: React.FC<
getNamedObject={getNamedObject} getNamedObject={getNamedObject}
isValidNewOption={isValidNewOption} isValidNewOption={isValidNewOption}
components={{ components={{
Option: MovieOption, Option: GroupOption,
MultiValueLabel: MovieMultiValueLabel, MultiValueLabel: GroupMultiValueLabel,
SingleValue: MovieValueLabel, SingleValue: GroupValueLabel,
}} }}
isMulti={props.isMulti ?? false} isMulti={props.isMulti ?? false}
creatable={props.creatable ?? defaultCreatable} creatable={props.creatable ?? defaultCreatable}
@@ -247,7 +247,7 @@ const _MovieSelect: React.FC<
{ id: "actions.select_entity" }, { id: "actions.select_entity" },
{ {
entityType: intl.formatMessage({ entityType: intl.formatMessage({
id: props.isMulti ? "movies" : "movie", id: props.isMulti ? "groups" : "group",
}), }),
} }
) )
@@ -257,22 +257,22 @@ const _MovieSelect: React.FC<
); );
}; };
export const MovieSelect = PatchComponent("MovieSelect", _MovieSelect); export const GroupSelect = PatchComponent("GroupSelect", _GroupSelect);
const _MovieIDSelect: React.FC<IFilterProps & IFilterIDProps<Movie>> = ( const _GroupIDSelect: React.FC<IFilterProps & IFilterIDProps<Group>> = (
props props
) => { ) => {
const { ids, onSelect: onSelectValues } = props; const { ids, onSelect: onSelectValues } = props;
const [values, setValues] = useState<Movie[]>([]); const [values, setValues] = useState<Group[]>([]);
const idsChanged = useCompare(ids); const idsChanged = useCompare(ids);
function onSelect(items: Movie[]) { function onSelect(items: Group[]) {
setValues(items); setValues(items);
onSelectValues?.(items); onSelectValues?.(items);
} }
async function loadObjectsByID(idsToLoad: string[]): Promise<Movie[]> { async function loadObjectsByID(idsToLoad: string[]): Promise<Group[]> {
const query = await queryFindMoviesByIDForSelect(idsToLoad); const query = await queryFindMoviesByIDForSelect(idsToLoad);
const { movies: loadedMovies } = query.data.findMovies; const { movies: loadedMovies } = query.data.findMovies;
@@ -303,7 +303,7 @@ const _MovieIDSelect: React.FC<IFilterProps & IFilterIDProps<Movie>> = (
load(); load();
}, [ids, idsChanged, values]); }, [ids, idsChanged, values]);
return <MovieSelect {...props} values={values} onSelect={onSelect} />; return <GroupSelect {...props} values={values} onSelect={onSelect} />;
}; };
export const MovieIDSelect = PatchComponent("MovieIDSelect", _MovieIDSelect); export const GroupIDSelect = PatchComponent("GroupIDSelect", _GroupIDSelect);

View File

@@ -2,30 +2,30 @@ import React from "react";
import { Route, Switch } from "react-router-dom"; import { Route, Switch } from "react-router-dom";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTitleProps } from "src/hooks/title"; import { useTitleProps } from "src/hooks/title";
import Movie from "./MovieDetails/Movie"; import Group from "./MovieDetails/Movie";
import MovieCreate from "./MovieDetails/MovieCreate"; import GroupCreate from "./MovieDetails/MovieCreate";
import { MovieList } from "./MovieList"; import { GroupList } from "./MovieList";
import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
import { View } from "../List/views"; import { View } from "../List/views";
const Movies: React.FC = () => { const Groups: React.FC = () => {
useScrollToTopOnMount(); useScrollToTopOnMount();
return <MovieList view={View.Movies} />; return <GroupList view={View.Groups} />;
}; };
const MovieRoutes: React.FC = () => { const GroupRoutes: React.FC = () => {
const titleProps = useTitleProps({ id: "movies" }); const titleProps = useTitleProps({ id: "groups" });
return ( return (
<> <>
<Helmet {...titleProps} /> <Helmet {...titleProps} />
<Switch> <Switch>
<Route exact path="/movies" component={Movies} /> <Route exact path="/groups" component={Groups} />
<Route exact path="/movies/new" component={MovieCreate} /> <Route exact path="/groups/new" component={GroupCreate} />
<Route path="/movies/:id/:tab?" component={Movie} /> <Route path="/groups/:id/:tab?" component={Group} />
</Switch> </Switch>
</> </>
); );
}; };
export default MovieRoutes; export default GroupRoutes;

View File

@@ -1,4 +1,4 @@
.movie-card { .group-card {
width: 240px; width: 240px;
@media (max-width: 576px) { @media (max-width: 576px) {
@@ -14,7 +14,7 @@
width: 100%; width: 100%;
} }
.movie-scene-number { .group-scene-number {
text-align: center; text-align: center;
} }
@@ -23,14 +23,14 @@
} }
} }
.movie-images { .group-images {
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-evenly; justify-content: space-evenly;
max-width: 100%; max-width: 100%;
.movie-image-container { .group-image-container {
box-shadow: none; box-shadow: none;
} }
@@ -40,17 +40,17 @@
} }
} }
#movie-page .rating-number .text-input { #group-page .rating-number .text-input {
width: auto; width: auto;
} }
.movie-select-option { .group-select-option {
.movie-select-row { .group-select-row {
align-items: center; align-items: center;
display: flex; display: flex;
width: 100%; width: 100%;
.movie-select-image { .group-select-image {
background-color: $body-bg; background-color: $body-bg;
margin-right: 0.4em; margin-right: 0.4em;
max-height: 50px; max-height: 50px;
@@ -59,26 +59,26 @@
object-position: center; object-position: center;
} }
.movie-select-details { .group-select-details {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
max-height: 4.1rem; max-height: 4.1rem;
overflow: hidden; overflow: hidden;
.movie-select-title { .group-select-title {
flex-shrink: 0; flex-shrink: 0;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-all;
.movie-select-alias { .group-select-alias {
font-size: 0.8rem; font-size: 0.8rem;
font-weight: bold; font-weight: bold;
} }
} }
.movie-select-date, .group-select-date,
.movie-select-studio { .group-select-studio {
color: $text-muted; color: $text-muted;
flex-shrink: 0; flex-shrink: 0;
font-size: 0.9rem; font-size: 0.9rem;

View File

@@ -178,15 +178,15 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
); );
} }
function maybeRenderMoviesPopoverButton() { function maybeRenderGroupsPopoverButton() {
if (!performer.movie_count) return; if (!performer.movie_count) return;
return ( return (
<PopoverCountButton <PopoverCountButton
className="movie-count" className="group-count"
type="movie" type="group"
count={performer.movie_count} count={performer.movie_count}
url={NavUtils.makePerformerMoviesUrl( url={NavUtils.makePerformerGroupsUrl(
performer, performer,
extraCriteria?.performer, extraCriteria?.performer,
extraCriteria?.movies extraCriteria?.movies
@@ -209,7 +209,7 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
<hr /> <hr />
<ButtonGroup className="card-popovers"> <ButtonGroup className="card-popovers">
{maybeRenderScenesPopoverButton()} {maybeRenderScenesPopoverButton()}
{maybeRenderMoviesPopoverButton()} {maybeRenderGroupsPopoverButton()}
{maybeRenderImagesPopoverButton()} {maybeRenderImagesPopoverButton()}
{maybeRenderGalleriesPopoverButton()} {maybeRenderGalleriesPopoverButton()}
{maybeRenderTagPopoverButton()} {maybeRenderTagPopoverButton()}

View File

@@ -27,7 +27,7 @@ import {
} from "./PerformerDetailsPanel"; } from "./PerformerDetailsPanel";
import { PerformerScenesPanel } from "./PerformerScenesPanel"; import { PerformerScenesPanel } from "./PerformerScenesPanel";
import { PerformerGalleriesPanel } from "./PerformerGalleriesPanel"; import { PerformerGalleriesPanel } from "./PerformerGalleriesPanel";
import { PerformerMoviesPanel } from "./PerformerMoviesPanel"; import { PerformerGroupsPanel } from "./PerformerMoviesPanel";
import { PerformerImagesPanel } from "./PerformerImagesPanel"; import { PerformerImagesPanel } from "./PerformerImagesPanel";
import { PerformerAppearsWithPanel } from "./performerAppearsWithPanel"; import { PerformerAppearsWithPanel } from "./performerAppearsWithPanel";
import { PerformerEditPanel } from "./PerformerEditPanel"; import { PerformerEditPanel } from "./PerformerEditPanel";
@@ -60,7 +60,7 @@ const validTabs = [
"scenes", "scenes",
"galleries", "galleries",
"images", "images",
"movies", "groups",
"appearswith", "appearswith",
] as const; ] as const;
type TabKey = (typeof validTabs)[number]; type TabKey = (typeof validTabs)[number];
@@ -146,7 +146,7 @@ const PerformerPage: React.FC<IProps> = ({ performer, tabKey }) => {
} else if (performer.image_count != 0) { } else if (performer.image_count != 0) {
ret = "images"; ret = "images";
} else if (performer.movie_count != 0) { } else if (performer.movie_count != 0) {
ret = "movies"; ret = "groups";
} }
} }
@@ -191,7 +191,7 @@ const PerformerPage: React.FC<IProps> = ({ performer, tabKey }) => {
Mousetrap.bind("e", () => toggleEditing()); Mousetrap.bind("e", () => toggleEditing());
Mousetrap.bind("c", () => setTabKey("scenes")); Mousetrap.bind("c", () => setTabKey("scenes"));
Mousetrap.bind("g", () => setTabKey("galleries")); Mousetrap.bind("g", () => setTabKey("galleries"));
Mousetrap.bind("m", () => setTabKey("movies")); Mousetrap.bind("m", () => setTabKey("groups"));
Mousetrap.bind("f", () => setFavorite(!performer.favorite)); Mousetrap.bind("f", () => setFavorite(!performer.favorite));
Mousetrap.bind(",", () => setCollapsed(!collapsed)); Mousetrap.bind(",", () => setCollapsed(!collapsed));
@@ -319,10 +319,10 @@ const PerformerPage: React.FC<IProps> = ({ performer, tabKey }) => {
/> />
</Tab> </Tab>
<Tab <Tab
eventKey="movies" eventKey="groups"
title={ title={
<> <>
{intl.formatMessage({ id: "movies" })} {intl.formatMessage({ id: "groups" })}
<Counter <Counter
abbreviateCounter={abbreviateCounter} abbreviateCounter={abbreviateCounter}
count={performer.movie_count} count={performer.movie_count}
@@ -331,8 +331,8 @@ const PerformerPage: React.FC<IProps> = ({ performer, tabKey }) => {
</> </>
} }
> >
<PerformerMoviesPanel <PerformerGroupsPanel
active={tabKey === "movies"} active={tabKey === "groups"}
performer={performer} performer={performer}
/> />
</Tab> </Tab>

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { MovieList } from "src/components/Movies/MovieList"; import { GroupList } from "src/components/Movies/MovieList";
import { usePerformerFilterHook } from "src/core/performers"; import { usePerformerFilterHook } from "src/core/performers";
import { View } from "src/components/List/views"; import { View } from "src/components/List/views";
@@ -9,16 +9,16 @@ interface IPerformerDetailsProps {
performer: GQL.PerformerDataFragment; performer: GQL.PerformerDataFragment;
} }
export const PerformerMoviesPanel: React.FC<IPerformerDetailsProps> = ({ export const PerformerGroupsPanel: React.FC<IPerformerDetailsProps> = ({
active, active,
performer, performer,
}) => { }) => {
const filterHook = usePerformerFilterHook(performer); const filterHook = usePerformerFilterHook(performer);
return ( return (
<MovieList <GroupList
filterHook={filterHook} filterHook={filterHook}
alterQuery={active} alterQuery={active}
view={View.PerformerMovies} view={View.PerformerGroups}
/> />
); );
}; };

View File

@@ -21,7 +21,7 @@ import { HoverPopover } from "../Shared/HoverPopover";
import { Icon } from "../Shared/Icon"; import { Icon } from "../Shared/Icon";
import { import {
GalleryLink, GalleryLink,
MovieLink, GroupLink,
SceneMarkerLink, SceneMarkerLink,
TagLink, TagLink,
} from "../Shared/TagLink"; } from "../Shared/TagLink";
@@ -386,14 +386,14 @@ export const SceneDuplicateChecker: React.FC = () => {
return <PerformerPopoverButton performers={scene.performers} />; return <PerformerPopoverButton performers={scene.performers} />;
} }
function maybeRenderMoviePopoverButton(scene: GQL.SlimSceneDataFragment) { function maybeRenderGroupPopoverButton(scene: GQL.SlimSceneDataFragment) {
if (scene.movies.length <= 0) return; if (scene.movies.length <= 0) return;
const popoverContent = scene.movies.map((sceneMovie) => ( const popoverContent = scene.movies.map((sceneMovie) => (
<div className="movie-tag-container row" key="movie"> <div className="group-tag-container row" key={sceneMovie.movie.id}>
<Link <Link
to={`/movies/${sceneMovie.movie.id}`} to={`/groups/${sceneMovie.movie.id}`}
className="movie-tag col m-auto zoom-2" className="group-tag col m-auto zoom-2"
> >
<img <img
className="image-thumbnail" className="image-thumbnail"
@@ -401,9 +401,9 @@ export const SceneDuplicateChecker: React.FC = () => {
src={sceneMovie.movie.front_image_path ?? ""} src={sceneMovie.movie.front_image_path ?? ""}
/> />
</Link> </Link>
<MovieLink <GroupLink
key={sceneMovie.movie.id} key={sceneMovie.movie.id}
movie={sceneMovie.movie} group={sceneMovie.movie}
className="d-block" className="d-block"
/> />
</div> </div>
@@ -523,7 +523,7 @@ export const SceneDuplicateChecker: React.FC = () => {
<ButtonGroup className="flex-wrap"> <ButtonGroup className="flex-wrap">
{maybeRenderTagPopoverButton(scene)} {maybeRenderTagPopoverButton(scene)}
{maybeRenderPerformerPopoverButton(scene)} {maybeRenderPerformerPopoverButton(scene)}
{maybeRenderMoviePopoverButton(scene)} {maybeRenderGroupPopoverButton(scene)}
{maybeRenderSceneMarkerPopoverButton(scene)} {maybeRenderSceneMarkerPopoverButton(scene)}
{maybeRenderOCounter(scene)} {maybeRenderOCounter(scene)}
{maybeRenderGallery(scene)} {maybeRenderGallery(scene)}

View File

@@ -13,7 +13,7 @@ import { RatingSystem } from "../Shared/Rating/RatingSystem";
import { import {
getAggregateInputIDs, getAggregateInputIDs,
getAggregateInputValue, getAggregateInputValue,
getAggregateMovieIds, getAggregateGroupIds,
getAggregatePerformerIds, getAggregatePerformerIds,
getAggregateRating, getAggregateRating,
getAggregateStudioId, getAggregateStudioId,
@@ -42,11 +42,11 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
); );
const [tagIds, setTagIds] = useState<string[]>(); const [tagIds, setTagIds] = useState<string[]>();
const [existingTagIds, setExistingTagIds] = useState<string[]>(); const [existingTagIds, setExistingTagIds] = useState<string[]>();
const [movieMode, setMovieMode] = React.useState<GQL.BulkUpdateIdMode>( const [groupMode, setGroupMode] = React.useState<GQL.BulkUpdateIdMode>(
GQL.BulkUpdateIdMode.Add GQL.BulkUpdateIdMode.Add
); );
const [movieIds, setMovieIds] = useState<string[]>(); const [groupIds, setGroupIds] = useState<string[]>();
const [existingMovieIds, setExistingMovieIds] = useState<string[]>(); const [existingGroupIds, setExistingGroupIds] = useState<string[]>();
const [organized, setOrganized] = useState<boolean | undefined>(); const [organized, setOrganized] = useState<boolean | undefined>();
const [updateScenes] = useBulkSceneUpdate(getSceneInput()); const [updateScenes] = useBulkSceneUpdate(getSceneInput());
@@ -62,7 +62,7 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
const aggregateStudioId = getAggregateStudioId(props.selected); const aggregateStudioId = getAggregateStudioId(props.selected);
const aggregatePerformerIds = getAggregatePerformerIds(props.selected); const aggregatePerformerIds = getAggregatePerformerIds(props.selected);
const aggregateTagIds = getAggregateTagIds(props.selected); const aggregateTagIds = getAggregateTagIds(props.selected);
const aggregateMovieIds = getAggregateMovieIds(props.selected); const aggregateGroupIds = getAggregateGroupIds(props.selected);
const sceneInput: GQL.BulkSceneUpdateInput = { const sceneInput: GQL.BulkSceneUpdateInput = {
ids: props.selected.map((scene) => { ids: props.selected.map((scene) => {
@@ -80,9 +80,9 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
); );
sceneInput.tag_ids = getAggregateInputIDs(tagMode, tagIds, aggregateTagIds); sceneInput.tag_ids = getAggregateInputIDs(tagMode, tagIds, aggregateTagIds);
sceneInput.movie_ids = getAggregateInputIDs( sceneInput.movie_ids = getAggregateInputIDs(
movieMode, groupMode,
movieIds, groupIds,
aggregateMovieIds aggregateGroupIds
); );
if (organized !== undefined) { if (organized !== undefined) {
@@ -115,7 +115,7 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
let updateStudioID: string | undefined; let updateStudioID: string | undefined;
let updatePerformerIds: string[] = []; let updatePerformerIds: string[] = [];
let updateTagIds: string[] = []; let updateTagIds: string[] = [];
let updateMovieIds: string[] = []; let updateGroupIds: string[] = [];
let updateOrganized: boolean | undefined; let updateOrganized: boolean | undefined;
let first = true; let first = true;
@@ -126,14 +126,14 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
.map((p) => p.id) .map((p) => p.id)
.sort(); .sort();
const sceneTagIDs = (scene.tags ?? []).map((p) => p.id).sort(); const sceneTagIDs = (scene.tags ?? []).map((p) => p.id).sort();
const sceneMovieIDs = (scene.movies ?? []).map((m) => m.movie.id).sort(); const sceneGroupIDs = (scene.movies ?? []).map((m) => m.movie.id).sort();
if (first) { if (first) {
updateRating = sceneRating ?? undefined; updateRating = sceneRating ?? undefined;
updateStudioID = sceneStudioID; updateStudioID = sceneStudioID;
updatePerformerIds = scenePerformerIDs; updatePerformerIds = scenePerformerIDs;
updateTagIds = sceneTagIDs; updateTagIds = sceneTagIDs;
updateMovieIds = sceneMovieIDs; updateGroupIds = sceneGroupIDs;
first = false; first = false;
updateOrganized = scene.organized; updateOrganized = scene.organized;
} else { } else {
@@ -149,8 +149,8 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
if (!isEqual(sceneTagIDs, updateTagIds)) { if (!isEqual(sceneTagIDs, updateTagIds)) {
updateTagIds = []; updateTagIds = [];
} }
if (!isEqual(sceneMovieIDs, updateMovieIds)) { if (!isEqual(sceneGroupIDs, updateGroupIds)) {
updateMovieIds = []; updateGroupIds = [];
} }
if (scene.organized !== updateOrganized) { if (scene.organized !== updateOrganized) {
updateOrganized = undefined; updateOrganized = undefined;
@@ -162,7 +162,7 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
setStudioId(updateStudioID); setStudioId(updateStudioID);
setExistingPerformerIds(updatePerformerIds); setExistingPerformerIds(updatePerformerIds);
setExistingTagIds(updateTagIds); setExistingTagIds(updateTagIds);
setExistingMovieIds(updateMovieIds); setExistingGroupIds(updateGroupIds);
setOrganized(updateOrganized); setOrganized(updateOrganized);
}, [props.selected]); }, [props.selected]);
@@ -173,7 +173,7 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
}, [organized, checkboxRef]); }, [organized, checkboxRef]);
function renderMultiSelect( function renderMultiSelect(
type: "performers" | "tags" | "movies", type: "performers" | "tags" | "groups",
ids: string[] | undefined ids: string[] | undefined
) { ) {
let mode = GQL.BulkUpdateIdMode.Add; let mode = GQL.BulkUpdateIdMode.Add;
@@ -187,9 +187,9 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
mode = tagMode; mode = tagMode;
existingIds = existingTagIds; existingIds = existingTagIds;
break; break;
case "movies": case "groups":
mode = movieMode; mode = groupMode;
existingIds = existingMovieIds; existingIds = existingGroupIds;
break; break;
} }
@@ -205,8 +205,8 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
case "tags": case "tags":
setTagIds(itemIDs); setTagIds(itemIDs);
break; break;
case "movies": case "groups":
setMovieIds(itemIDs); setGroupIds(itemIDs);
break; break;
} }
}} }}
@@ -218,8 +218,8 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
case "tags": case "tags":
setTagMode(newMode); setTagMode(newMode);
break; break;
case "movies": case "groups":
setMovieMode(newMode); setGroupMode(newMode);
break; break;
} }
}} }}
@@ -306,11 +306,11 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
{renderMultiSelect("tags", tagIds)} {renderMultiSelect("tags", tagIds)}
</Form.Group> </Form.Group>
<Form.Group controlId="movies"> <Form.Group controlId="groups">
<Form.Label> <Form.Label>
<FormattedMessage id="movies" /> <FormattedMessage id="groups" />
</Form.Label> </Form.Label>
{renderMultiSelect("movies", movieIds)} {renderMultiSelect("groups", groupIds)}
</Form.Group> </Form.Group>
<Form.Group controlId="organized"> <Form.Group controlId="organized">

View File

@@ -7,7 +7,7 @@ import { Icon } from "../Shared/Icon";
import { import {
GalleryLink, GalleryLink,
TagLink, TagLink,
MovieLink, GroupLink,
SceneMarkerLink, SceneMarkerLink,
} from "../Shared/TagLink"; } from "../Shared/TagLink";
import { HoverPopover } from "../Shared/HoverPopover"; import { HoverPopover } from "../Shared/HoverPopover";
@@ -143,24 +143,24 @@ const SceneCardPopovers = PatchComponent(
return <PerformerPopoverButton performers={props.scene.performers} />; return <PerformerPopoverButton performers={props.scene.performers} />;
} }
function maybeRenderMoviePopoverButton() { function maybeRenderGroupPopoverButton() {
if (props.scene.movies.length <= 0) return; if (props.scene.movies.length <= 0) return;
const popoverContent = props.scene.movies.map((sceneMovie) => ( const popoverContent = props.scene.movies.map((sceneGroup) => (
<div className="movie-tag-container row" key="movie"> <div className="group-tag-container row" key={sceneGroup.movie.id}>
<Link <Link
to={`/movies/${sceneMovie.movie.id}`} to={`/groups/${sceneGroup.movie.id}`}
className="movie-tag col m-auto zoom-2" className="group-tag col m-auto zoom-2"
> >
<img <img
className="image-thumbnail" className="image-thumbnail"
alt={sceneMovie.movie.name ?? ""} alt={sceneGroup.movie.name ?? ""}
src={sceneMovie.movie.front_image_path ?? ""} src={sceneGroup.movie.front_image_path ?? ""}
/> />
</Link> </Link>
<MovieLink <GroupLink
key={sceneMovie.movie.id} key={sceneGroup.movie.id}
movie={sceneMovie.movie} group={sceneGroup.movie}
className="d-block" className="d-block"
/> />
</div> </div>
@@ -170,7 +170,7 @@ const SceneCardPopovers = PatchComponent(
<HoverPopover <HoverPopover
placement="bottom" placement="bottom"
content={popoverContent} content={popoverContent}
className="movie-count tag-tooltip" className="group-count tag-tooltip"
> >
<Button className="minimal"> <Button className="minimal">
<Icon icon={faFilm} /> <Icon icon={faFilm} />
@@ -291,7 +291,7 @@ const SceneCardPopovers = PatchComponent(
<ButtonGroup className="card-popovers"> <ButtonGroup className="card-popovers">
{maybeRenderTagPopoverButton()} {maybeRenderTagPopoverButton()}
{maybeRenderPerformerPopoverButton()} {maybeRenderPerformerPopoverButton()}
{maybeRenderMoviePopoverButton()} {maybeRenderGroupPopoverButton()}
{maybeRenderSceneMarkerPopoverButton()} {maybeRenderSceneMarkerPopoverButton()}
{maybeRenderOCounter()} {maybeRenderOCounter()}
{maybeRenderGallery()} {maybeRenderGallery()}

View File

@@ -70,7 +70,7 @@ const SceneMarkersPanel = lazyComponent(() => import("./SceneMarkersPanel"));
const SceneFileInfoPanel = lazyComponent(() => import("./SceneFileInfoPanel")); const SceneFileInfoPanel = lazyComponent(() => import("./SceneFileInfoPanel"));
const SceneDetailPanel = lazyComponent(() => import("./SceneDetailPanel")); const SceneDetailPanel = lazyComponent(() => import("./SceneDetailPanel"));
const SceneHistoryPanel = lazyComponent(() => import("./SceneHistoryPanel")); const SceneHistoryPanel = lazyComponent(() => import("./SceneHistoryPanel"));
const SceneMoviePanel = lazyComponent(() => import("./SceneMoviePanel")); const SceneGroupPanel = lazyComponent(() => import("./SceneMoviePanel"));
const SceneGalleriesPanel = lazyComponent( const SceneGalleriesPanel = lazyComponent(
() => import("./SceneGalleriesPanel") () => import("./SceneGalleriesPanel")
); );
@@ -443,9 +443,9 @@ const ScenePage: React.FC<IProps> = ({
</Nav.Item> </Nav.Item>
{scene.movies.length > 0 ? ( {scene.movies.length > 0 ? (
<Nav.Item> <Nav.Item>
<Nav.Link eventKey="scene-movie-panel"> <Nav.Link eventKey="scene-group-panel">
<FormattedMessage <FormattedMessage
id="countables.movies" id="countables.groups"
values={{ count: scene.movies.length }} values={{ count: scene.movies.length }}
/> />
</Nav.Link> </Nav.Link>
@@ -514,8 +514,8 @@ const ScenePage: React.FC<IProps> = ({
isVisible={activeTabKey === "scene-markers-panel"} isVisible={activeTabKey === "scene-markers-panel"}
/> />
</Tab.Pane> </Tab.Pane>
<Tab.Pane eventKey="scene-movie-panel"> <Tab.Pane eventKey="scene-group-panel">
<SceneMoviePanel scene={scene} /> <SceneGroupPanel scene={scene} />
</Tab.Pane> </Tab.Pane>
{scene.galleries.length >= 1 && ( {scene.galleries.length >= 1 && (
<Tab.Pane eventKey="scene-galleries-panel"> <Tab.Pane eventKey="scene-galleries-panel">

View File

@@ -29,7 +29,7 @@ import { useFormik } from "formik";
import { Prompt } from "react-router-dom"; import { Prompt } from "react-router-dom";
import { ConfigurationContext } from "src/hooks/Config"; import { ConfigurationContext } from "src/hooks/Config";
import { stashboxDisplayName } from "src/utils/stashbox"; import { stashboxDisplayName } from "src/utils/stashbox";
import { IMovieEntry, SceneMovieTable } from "./SceneMovieTable"; import { IGroupEntry, SceneGroupTable } from "./SceneMovieTable";
import { faSearch, faSyncAlt } from "@fortawesome/free-solid-svg-icons"; import { faSearch, faSyncAlt } from "@fortawesome/free-solid-svg-icons";
import { objectTitle } from "src/core/files"; import { objectTitle } from "src/core/files";
import { galleryTitle } from "src/core/galleries"; import { galleryTitle } from "src/core/galleries";
@@ -47,7 +47,7 @@ import {
import { formikUtils } from "src/utils/form"; import { formikUtils } from "src/utils/form";
import { Studio, StudioSelect } from "src/components/Studios/StudioSelect"; import { Studio, StudioSelect } from "src/components/Studios/StudioSelect";
import { Gallery, GallerySelect } from "src/components/Galleries/GallerySelect"; import { Gallery, GallerySelect } from "src/components/Galleries/GallerySelect";
import { Movie } from "src/components/Movies/MovieSelect"; import { Group } from "src/components/Movies/MovieSelect";
import { useTagsEdit } from "src/hooks/tagsEdit"; import { useTagsEdit } from "src/hooks/tagsEdit";
const SceneScrapeDialog = lazyComponent(() => import("./SceneScrapeDialog")); const SceneScrapeDialog = lazyComponent(() => import("./SceneScrapeDialog"));
@@ -75,7 +75,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
const [galleries, setGalleries] = useState<Gallery[]>([]); const [galleries, setGalleries] = useState<Gallery[]>([]);
const [performers, setPerformers] = useState<Performer[]>([]); const [performers, setPerformers] = useState<Performer[]>([]);
const [movies, setMovies] = useState<Movie[]>([]); const [groups, setGroups] = useState<Group[]>([]);
const [studio, setStudio] = useState<Studio | null>(null); const [studio, setStudio] = useState<Studio | null>(null);
const Scrapers = useListSceneScrapers(); const Scrapers = useListSceneScrapers();
@@ -104,7 +104,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
}, [scene.performers]); }, [scene.performers]);
useEffect(() => { useEffect(() => {
setMovies(scene.movies?.map((m) => m.movie) ?? []); setGroups(scene.movies?.map((m) => m.movie) ?? []);
}, [scene.movies]); }, [scene.movies]);
useEffect(() => { useEffect(() => {
@@ -191,12 +191,12 @@ export const SceneEditPanel: React.FC<IProps> = ({
return formik.values.movies return formik.values.movies
.map((m) => { .map((m) => {
return { return {
movie: movies.find((mm) => mm.id === m.movie_id), movie: groups.find((mm) => mm.id === m.movie_id),
scene_index: m.scene_index, scene_index: m.scene_index,
}; };
}) })
.filter((m) => m.movie !== undefined) as IMovieEntry[]; .filter((m) => m.movie !== undefined) as IGroupEntry[];
}, [formik.values.movies, movies]); }, [formik.values.movies, groups]);
function onSetGalleries(items: Gallery[]) { function onSetGalleries(items: Gallery[]) {
setGalleries(items); setGalleries(items);
@@ -253,8 +253,8 @@ export const SceneEditPanel: React.FC<IProps> = ({
setQueryableScrapers(newQueryableScrapers); setQueryableScrapers(newQueryableScrapers);
}, [Scrapers, stashConfig]); }, [Scrapers, stashConfig]);
function onSetMovies(items: Movie[]) { function onSetGroups(items: Group[]) {
setMovies(items); setGroups(items);
const existingMovies = formik.values.movies; const existingMovies = formik.values.movies;
@@ -386,7 +386,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
sceneStudio={studio} sceneStudio={studio}
sceneTags={tags} sceneTags={tags}
scenePerformers={performers} scenePerformers={performers}
sceneMovies={movies} sceneGroups={groups}
scraped={scrapedScene} scraped={scrapedScene}
endpoint={endpoint} endpoint={endpoint}
onClose={(s) => onScrapeDialogClosed(s)} onClose={(s) => onScrapeDialogClosed(s)}
@@ -574,7 +574,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
}); });
if (idMovis.length > 0) { if (idMovis.length > 0) {
onSetMovies( onSetGroups(
idMovis.map((p) => { idMovis.map((p) => {
return { return {
id: p.stored_id!, id: p.stored_id!,
@@ -725,8 +725,8 @@ export const SceneEditPanel: React.FC<IProps> = ({
return renderField("performer_ids", title, control, fullWidthProps); return renderField("performer_ids", title, control, fullWidthProps);
} }
function onSetMovieEntries(input: IMovieEntry[]) { function onSetMovieEntries(input: IGroupEntry[]) {
setMovies(input.map((m) => m.movie)); setGroups(input.map((m) => m.movie));
const newMovies = input.map((m) => ({ const newMovies = input.map((m) => ({
movie_id: m.movie.id, movie_id: m.movie.id,
@@ -737,9 +737,9 @@ export const SceneEditPanel: React.FC<IProps> = ({
} }
function renderMoviesField() { function renderMoviesField() {
const title = intl.formatMessage({ id: "movies" }); const title = intl.formatMessage({ id: "groups" });
const control = ( const control = (
<SceneMovieTable value={movieEntries} onUpdate={onSetMovieEntries} /> <SceneGroupTable value={movieEntries} onUpdate={onSetMovieEntries} />
); );
return renderField("movies", title, control, fullWidthProps); return renderField("movies", title, control, fullWidthProps);

View File

@@ -1,19 +1,19 @@
import React from "react"; import React from "react";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { MovieCard } from "src/components/Movies/MovieCard"; import { GroupCard } from "src/components/Movies/MovieCard";
interface ISceneMoviePanelProps { interface ISceneGroupPanelProps {
scene: GQL.SceneDataFragment; scene: GQL.SceneDataFragment;
} }
export const SceneMoviePanel: React.FC<ISceneMoviePanelProps> = ( export const SceneGroupPanel: React.FC<ISceneGroupPanelProps> = (
props: ISceneMoviePanelProps props: ISceneGroupPanelProps
) => { ) => {
const cards = props.scene.movies.map((sceneMovie) => ( const cards = props.scene.movies.map((sceneGroup) => (
<MovieCard <GroupCard
key={sceneMovie.movie.id} key={sceneGroup.movie.id}
movie={sceneMovie.movie} group={sceneGroup.movie}
sceneIndex={sceneMovie.scene_index ?? undefined} sceneIndex={sceneGroup.scene_index ?? undefined}
/> />
)); ));
@@ -24,4 +24,4 @@ export const SceneMoviePanel: React.FC<ISceneMoviePanelProps> = (
); );
}; };
export default SceneMoviePanel; export default SceneGroupPanel;

View File

@@ -2,27 +2,27 @@ import React, { useMemo } from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { Form, Row, Col } from "react-bootstrap"; import { Form, Row, Col } from "react-bootstrap";
import { Movie, MovieSelect } from "src/components/Movies/MovieSelect"; import { Group, GroupSelect } from "src/components/Movies/MovieSelect";
import cx from "classnames"; import cx from "classnames";
export type MovieSceneIndexMap = Map<string, number | undefined>; export type MovieSceneIndexMap = Map<string, number | undefined>;
export interface IMovieEntry { export interface IGroupEntry {
movie: Movie; movie: Group;
scene_index?: GQL.InputMaybe<number> | undefined; scene_index?: GQL.InputMaybe<number> | undefined;
} }
export interface IProps { export interface IProps {
value: IMovieEntry[]; value: IGroupEntry[];
onUpdate: (input: IMovieEntry[]) => void; onUpdate: (input: IGroupEntry[]) => void;
} }
export const SceneMovieTable: React.FC<IProps> = (props) => { export const SceneGroupTable: React.FC<IProps> = (props) => {
const { value, onUpdate } = props; const { value, onUpdate } = props;
const intl = useIntl(); const intl = useIntl();
const movieIDs = useMemo(() => value.map((m) => m.movie.id), [value]); const groupIDs = useMemo(() => value.map((m) => m.movie.id), [value]);
const updateFieldChanged = (index: number, sceneIndex: number | null) => { const updateFieldChanged = (index: number, sceneIndex: number | null) => {
const newValues = value.map((existing, i) => { const newValues = value.map((existing, i) => {
@@ -38,21 +38,21 @@ export const SceneMovieTable: React.FC<IProps> = (props) => {
onUpdate(newValues); onUpdate(newValues);
}; };
function onMovieSet(index: number, movies: Movie[]) { function onGroupSet(index: number, groups: Group[]) {
if (!movies.length) { if (!groups.length) {
// remove this entry // remove this entry
const newValues = value.filter((_, i) => i !== index); const newValues = value.filter((_, i) => i !== index);
onUpdate(newValues); onUpdate(newValues);
return; return;
} }
const movie = movies[0]; const group = groups[0];
const newValues = value.map((existing, i) => { const newValues = value.map((existing, i) => {
if (i === index) { if (i === index) {
return { return {
...existing, ...existing,
movie: movie, movie: group,
}; };
} }
return existing; return existing;
@@ -61,17 +61,17 @@ export const SceneMovieTable: React.FC<IProps> = (props) => {
onUpdate(newValues); onUpdate(newValues);
} }
function onNewMovieSet(movies: Movie[]) { function onNewGroupSet(groups: Group[]) {
if (!movies.length) { if (!groups.length) {
return; return;
} }
const movie = movies[0]; const group = groups[0];
const newValues = [ const newValues = [
...value, ...value,
{ {
movie: movie, movie: group,
scene_index: null, scene_index: null,
}, },
]; ];
@@ -83,12 +83,12 @@ export const SceneMovieTable: React.FC<IProps> = (props) => {
return ( return (
<> <>
{value.map((m, i) => ( {value.map((m, i) => (
<Row key={m.movie.id} className="movie-row"> <Row key={m.movie.id} className="group-row">
<Col xs={9}> <Col xs={9}>
<MovieSelect <GroupSelect
onSelect={(items) => onMovieSet(i, items)} onSelect={(items) => onGroupSet(i, items)}
values={[m.movie!]} values={[m.movie!]}
excludeIds={movieIDs} excludeIds={groupIDs}
/> />
</Col> </Col>
<Col xs={3}> <Col xs={3}>
@@ -108,12 +108,12 @@ export const SceneMovieTable: React.FC<IProps> = (props) => {
</Col> </Col>
</Row> </Row>
))} ))}
<Row className="movie-row"> <Row className="group-row">
<Col xs={12}> <Col xs={12}>
<MovieSelect <GroupSelect
onSelect={(items) => onNewMovieSet(items)} onSelect={(items) => onNewGroupSet(items)}
values={[]} values={[]}
excludeIds={movieIDs} excludeIds={groupIDs}
/> />
</Col> </Col>
</Row> </Row>
@@ -122,11 +122,11 @@ export const SceneMovieTable: React.FC<IProps> = (props) => {
} }
return ( return (
<div className={cx("movie-table", { "no-movies": !value.length })}> <div className={cx("group-table", { "no-groups": !value.length })}>
<Row className="movie-table-header"> <Row className="group-table-header">
<Col xs={9}></Col> <Col xs={9}></Col>
<Form.Label column xs={3} className="movie-scene-number-header"> <Form.Label column xs={3} className="group-scene-number-header">
{intl.formatMessage({ id: "movie_scene_number" })} {intl.formatMessage({ id: "group_scene_number" })}
</Form.Label> </Form.Label>
</Row> </Row>
{renderTableData()} {renderTableData()}

View File

@@ -17,18 +17,18 @@ import {
ScrapeResult, ScrapeResult,
} from "src/components/Shared/ScrapeDialog/scrapeResult"; } from "src/components/Shared/ScrapeDialog/scrapeResult";
import { import {
ScrapedMoviesRow, ScrapedGroupsRow,
ScrapedPerformersRow, ScrapedPerformersRow,
ScrapedStudioRow, ScrapedStudioRow,
} from "src/components/Shared/ScrapeDialog/ScrapedObjectsRow"; } from "src/components/Shared/ScrapeDialog/ScrapedObjectsRow";
import { import {
useCreateScrapedMovie, useCreateScrapedGroup,
useCreateScrapedPerformer, useCreateScrapedPerformer,
useCreateScrapedStudio, useCreateScrapedStudio,
} from "src/components/Shared/ScrapeDialog/createObjects"; } from "src/components/Shared/ScrapeDialog/createObjects";
import { Tag } from "src/components/Tags/TagSelect"; import { Tag } from "src/components/Tags/TagSelect";
import { Studio } from "src/components/Studios/StudioSelect"; import { Studio } from "src/components/Studios/StudioSelect";
import { Movie } from "src/components/Movies/MovieSelect"; import { Group } from "src/components/Movies/MovieSelect";
import { useScrapedTags } from "src/components/Shared/ScrapeDialog/scrapedTags"; import { useScrapedTags } from "src/components/Shared/ScrapeDialog/scrapedTags";
interface ISceneScrapeDialogProps { interface ISceneScrapeDialogProps {
@@ -36,7 +36,7 @@ interface ISceneScrapeDialogProps {
sceneStudio: Studio | null; sceneStudio: Studio | null;
scenePerformers: Performer[]; scenePerformers: Performer[];
sceneTags: Tag[]; sceneTags: Tag[];
sceneMovies: Movie[]; sceneGroups: Group[];
scraped: GQL.ScrapedScene; scraped: GQL.ScrapedScene;
endpoint?: string; endpoint?: string;
@@ -48,7 +48,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
sceneStudio, sceneStudio,
scenePerformers, scenePerformers,
sceneTags, sceneTags,
sceneMovies, sceneGroups,
scraped, scraped,
onClose, onClose,
endpoint, endpoint,
@@ -114,12 +114,12 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
scraped.performers?.filter((t) => !t.stored_id) ?? [] scraped.performers?.filter((t) => !t.stored_id) ?? []
); );
const [movies, setMovies] = useState< const [groups, setGroups] = useState<
ObjectListScrapeResult<GQL.ScrapedMovie> ObjectListScrapeResult<GQL.ScrapedMovie>
>( >(
new ObjectListScrapeResult<GQL.ScrapedMovie>( new ObjectListScrapeResult<GQL.ScrapedMovie>(
sortStoredIdObjects( sortStoredIdObjects(
sceneMovies.map((p) => ({ sceneGroups.map((p) => ({
stored_id: p.id, stored_id: p.id,
name: p.name, name: p.name,
})) }))
@@ -127,7 +127,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
sortStoredIdObjects(scraped.movies ?? undefined) sortStoredIdObjects(scraped.movies ?? undefined)
) )
); );
const [newMovies, setNewMovies] = useState<GQL.ScrapedMovie[]>( const [newGroups, setNewGroups] = useState<GQL.ScrapedMovie[]>(
scraped.movies?.filter((t) => !t.stored_id) ?? [] scraped.movies?.filter((t) => !t.stored_id) ?? []
); );
@@ -157,11 +157,11 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
setNewObjects: setNewPerformers, setNewObjects: setNewPerformers,
}); });
const createNewMovie = useCreateScrapedMovie({ const createNewGroup = useCreateScrapedGroup({
scrapeResult: movies, scrapeResult: groups,
setScrapeResult: setMovies, setScrapeResult: setGroups,
newObjects: newMovies, newObjects: newGroups,
setNewObjects: setNewMovies, setNewObjects: setNewGroups,
}); });
const intl = useIntl(); const intl = useIntl();
@@ -176,7 +176,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
director, director,
studio, studio,
performers, performers,
movies, groups,
tags, tags,
details, details,
image, image,
@@ -184,7 +184,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
].every((r) => !r.scraped) && ].every((r) => !r.scraped) &&
newTags.length === 0 && newTags.length === 0 &&
newPerformers.length === 0 && newPerformers.length === 0 &&
newMovies.length === 0 && newGroups.length === 0 &&
!newStudio !newStudio
) { ) {
onClose(); onClose();
@@ -202,7 +202,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
director: director.getNewValue(), director: director.getNewValue(),
studio: newStudioValue, studio: newStudioValue,
performers: performers.getNewValue(), performers: performers.getNewValue(),
movies: movies.getNewValue(), movies: groups.getNewValue(),
tags: tags.getNewValue(), tags: tags.getNewValue(),
details: details.getNewValue(), details: details.getNewValue(),
image: image.getNewValue(), image: image.getNewValue(),
@@ -253,12 +253,12 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
newObjects={newPerformers} newObjects={newPerformers}
onCreateNew={createNewPerformer} onCreateNew={createNewPerformer}
/> />
<ScrapedMoviesRow <ScrapedGroupsRow
title={intl.formatMessage({ id: "movies" })} title={intl.formatMessage({ id: "groups" })}
result={movies} result={groups}
onChange={(value) => setMovies(value)} onChange={(value) => setGroups(value)}
newObjects={newMovies} newObjects={newGroups}
onCreateNew={createNewMovie} onCreateNew={createNewGroup}
/> />
{scrapedTagsRow} {scrapedTagsRow}
<ScrapedTextAreaRow <ScrapedTextAreaRow

View File

@@ -124,12 +124,12 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
} }
}; };
const MovieCell = (scene: GQL.SlimSceneDataFragment) => ( const GroupCell = (scene: GQL.SlimSceneDataFragment) => (
<ul className="comma-list overflowable"> <ul className="comma-list overflowable">
{scene.movies.map((sceneMovie) => ( {scene.movies.map((sceneGroup) => (
<li key={sceneMovie.movie.id}> <li key={sceneGroup.movie.id}>
<Link to={NavUtils.makeMovieScenesUrl(sceneMovie.movie)}> <Link to={NavUtils.makeGroupScenesUrl(sceneGroup.movie)}>
<span className="ellips-data">{sceneMovie.movie.name}</span> <span className="ellips-data">{sceneGroup.movie.name}</span>
</Link> </Link>
</li> </li>
))} ))}
@@ -322,10 +322,10 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
render: TagCell, render: TagCell,
}, },
{ {
value: "movies", value: "groups",
label: intl.formatMessage({ id: "movies" }), label: intl.formatMessage({ id: "groups" }),
defaultShow: true, defaultShow: true,
render: MovieCell, render: GroupCell,
}, },
{ {
value: "galleries", value: "galleries",

View File

@@ -30,7 +30,7 @@ import {
hasScrapedValues, hasScrapedValues,
} from "../Shared/ScrapeDialog/scrapeResult"; } from "../Shared/ScrapeDialog/scrapeResult";
import { import {
ScrapedMoviesRow, ScrapedGroupsRow,
ScrapedPerformersRow, ScrapedPerformersRow,
ScrapedStudioRow, ScrapedStudioRow,
ScrapedTagsRow, ScrapedTagsRow,
@@ -100,7 +100,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
}; };
} }
function movieToStoredID(o: { movie: { id: string; name: string } }) { function groupToStoredID(o: { movie: { id: string; name: string } }) {
return { return {
stored_id: o.movie.id, stored_id: o.movie.id,
name: o.movie.name, name: o.movie.name,
@@ -141,11 +141,11 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
) )
); );
const [movies, setMovies] = useState< const [groups, setGroups] = useState<
ObjectListScrapeResult<GQL.ScrapedMovie> ObjectListScrapeResult<GQL.ScrapedMovie>
>( >(
new ObjectListScrapeResult<GQL.ScrapedMovie>( new ObjectListScrapeResult<GQL.ScrapedMovie>(
sortStoredIdObjects(dest.movies.map(movieToStoredID)) sortStoredIdObjects(dest.movies.map(groupToStoredID))
) )
); );
@@ -252,10 +252,10 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
) )
); );
setMovies( setGroups(
new ObjectListScrapeResult<GQL.ScrapedMovie>( new ObjectListScrapeResult<GQL.ScrapedMovie>(
sortStoredIdObjects(dest.movies.map(movieToStoredID)), sortStoredIdObjects(dest.movies.map(groupToStoredID)),
uniqIDStoredIDs(all.map((s) => s.movies.map(movieToStoredID)).flat()) uniqIDStoredIDs(all.map((s) => s.movies.map(groupToStoredID)).flat())
) )
); );
@@ -331,7 +331,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
galleries, galleries,
studio, studio,
performers, performers,
movies, groups,
tags, tags,
details, details,
organized, organized,
@@ -348,7 +348,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
galleries, galleries,
studio, studio,
performers, performers,
movies, groups,
tags, tags,
details, details,
organized, organized,
@@ -508,10 +508,10 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
result={performers} result={performers}
onChange={(value) => setPerformers(value)} onChange={(value) => setPerformers(value)}
/> />
<ScrapedMoviesRow <ScrapedGroupsRow
title={intl.formatMessage({ id: "movies" })} title={intl.formatMessage({ id: "groups" })}
result={movies} result={groups}
onChange={(value) => setMovies(value)} onChange={(value) => setGroups(value)}
/> />
<ScrapedTagsRow <ScrapedTagsRow
title={intl.formatMessage({ id: "tags" })} title={intl.formatMessage({ id: "tags" })}
@@ -585,7 +585,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
gallery_ids: galleries.getNewValue(), gallery_ids: galleries.getNewValue(),
studio_id: studio.getNewValue()?.stored_id, studio_id: studio.getNewValue()?.stored_id,
performer_ids: performers.getNewValue()?.map((p) => p.stored_id!), performer_ids: performers.getNewValue()?.map((p) => p.stored_id!),
movies: movies.getNewValue()?.map((m) => { movies: groups.getNewValue()?.map((m) => {
// find the equivalent movie in the original scenes // find the equivalent movie in the original scenes
const found = all const found = all
.map((s) => s.movies) .map((s) => s.movies)

View File

@@ -24,7 +24,7 @@
} }
.performer-tag-container, .performer-tag-container,
.movie-tag-container { .group-tag-container {
display: inline-block; display: inline-block;
margin: 5px; margin: 5px;
} }
@@ -34,7 +34,7 @@
} }
.performer-tag.image, .performer-tag.image,
.movie-tag.image { .group-tag.image {
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: cover; background-size: cover;
@@ -288,19 +288,19 @@ textarea.scene-description {
max-width: 100%; max-width: 100%;
} }
.movie-image { .group-image {
max-width: 100%; max-width: 100%;
} }
.movie-table { .group-table {
width: 100%; width: 100%;
.movie-row { .group-row {
align-items: center; align-items: center;
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
.movie-scene-number-header { .group-scene-number-header {
color: $text-muted; color: $text-muted;
font-size: 0.8em; font-size: 0.8em;
padding-bottom: 0; padding-bottom: 0;
@@ -308,7 +308,7 @@ textarea.scene-description {
} }
} }
.movie-table.no-movies .movie-table-header { .group-table.no-groups .group-table-header {
display: none; display: none;
} }

View File

@@ -1,4 +1,4 @@
import React from "react"; import React, { useCallback, useMemo } from "react";
import { Button, Form } from "react-bootstrap"; import { Button, Form } from "react-bootstrap";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { DurationInput } from "src/components/Shared/DurationInput"; import { DurationInput } from "src/components/Shared/DurationInput";
@@ -47,7 +47,7 @@ import { defaultMaxOptionsShown } from "src/core/config";
const allMenuItems = [ const allMenuItems = [
{ id: "scenes", headingID: "scenes" }, { id: "scenes", headingID: "scenes" },
{ id: "images", headingID: "images" }, { id: "images", headingID: "images" },
{ id: "movies", headingID: "movies" }, { id: "groups", headingID: "groups" },
{ id: "markers", headingID: "markers" }, { id: "markers", headingID: "markers" },
{ id: "galleries", headingID: "galleries" }, { id: "galleries", headingID: "galleries" },
{ id: "performers", headingID: "performers" }, { id: "performers", headingID: "performers" },
@@ -67,6 +67,22 @@ export const SettingsInterfacePanel: React.FC = () => {
error, error,
} = useSettings(); } = useSettings();
// convert old movies menu item to groups
const massageMenuItems = useCallback((menuItems: string[]) => {
return menuItems.map((item) => {
if (item === "movies") {
return "groups";
}
return item;
});
}, []);
const massagedMenuItems = useMemo(() => {
if (!iface.menuItems) return iface.menuItems;
return massageMenuItems(iface.menuItems);
}, [iface.menuItems, massageMenuItems]);
const { const {
interactive, interactive,
state: interactiveState, state: interactiveState,
@@ -231,8 +247,8 @@ export const SettingsInterfacePanel: React.FC = () => {
<CheckboxGroup <CheckboxGroup
groupId="menu-items" groupId="menu-items"
items={allMenuItems} items={allMenuItems}
checkedIds={iface.menuItems ?? undefined} checkedIds={massagedMenuItems ?? undefined}
onChange={(v) => saveInterface({ menuItems: v })} onChange={(v) => saveInterface({ menuItems: massageMenuItems(v) })}
/> />
</div> </div>
@@ -563,7 +579,7 @@ export const SettingsInterfacePanel: React.FC = () => {
</div> </div>
<BooleanSetting <BooleanSetting
id="enableMovieBackgroundImage" id="enableMovieBackgroundImage"
headingID="movie" headingID="group"
checked={ui.enableMovieBackgroundImage ?? undefined} checked={ui.enableMovieBackgroundImage ?? undefined}
onChange={(v) => saveUI({ enableMovieBackgroundImage: v })} onChange={(v) => saveUI({ enableMovieBackgroundImage: v })}
/> />
@@ -659,8 +675,8 @@ export const SettingsInterfacePanel: React.FC = () => {
} }
/> />
<BooleanSetting <BooleanSetting
id="disableDropdownCreate_movie" id="disableDropdownCreate_group"
headingID="movie" headingID="group"
checked={iface.disableDropdownCreate?.movie ?? undefined} checked={iface.disableDropdownCreate?.movie ?? undefined}
onChange={(v) => onChange={(v) =>
saveInterface({ saveInterface({

View File

@@ -79,7 +79,7 @@ export const SettingsScrapingPanel: React.FC = () => {
useListSceneScrapers(); useListSceneScrapers();
const { data: galleryScrapers, loading: loadingGalleries } = const { data: galleryScrapers, loading: loadingGalleries } =
useListGalleryScrapers(); useListGalleryScrapers();
const { data: movieScrapers, loading: loadingMovies } = const { data: groupScrapers, loading: loadingGroups } =
useListMovieScrapers(); useListMovieScrapers();
const { general, scraping, loading, error, saveGeneral, saveScraping } = const { general, scraping, loading, error, saveGeneral, saveScraping } =
@@ -158,13 +158,13 @@ export const SettingsScrapingPanel: React.FC = () => {
); );
} }
function renderMovieScrapeTypes(types: ScrapeType[]) { function renderGroupScrapeTypes(types: ScrapeType[]) {
const typeStrings = types.map((t) => { const typeStrings = types.map((t) => {
switch (t) { switch (t) {
case ScrapeType.Fragment: case ScrapeType.Fragment:
return intl.formatMessage( return intl.formatMessage(
{ id: "config.scraping.entity_metadata" }, { id: "config.scraping.entity_metadata" },
{ entityType: intl.formatMessage({ id: "movie" }) } { entityType: intl.formatMessage({ id: "group" }) }
); );
default: default:
return t; return t;
@@ -246,12 +246,12 @@ export const SettingsScrapingPanel: React.FC = () => {
); );
} }
function renderMovieScrapers() { function renderGroupScrapers() {
const elements = (movieScrapers?.listScrapers ?? []).map((scraper) => ( const elements = (groupScrapers?.listScrapers ?? []).map((scraper) => (
<tr key={scraper.id}> <tr key={scraper.id}>
<td>{scraper.name}</td> <td>{scraper.name}</td>
<td> <td>
{renderMovieScrapeTypes(scraper.movie?.supported_scrapes ?? [])} {renderGroupScrapeTypes(scraper.movie?.supported_scrapes ?? [])}
</td> </td>
<td>{renderURLs(scraper.movie?.urls ?? [])}</td> <td>{renderURLs(scraper.movie?.urls ?? [])}</td>
</tr> </tr>
@@ -260,7 +260,7 @@ export const SettingsScrapingPanel: React.FC = () => {
return renderTable( return renderTable(
intl.formatMessage( intl.formatMessage(
{ id: "config.scraping.entity_scrapers" }, { id: "config.scraping.entity_scrapers" },
{ entityType: intl.formatMessage({ id: "movie" }) } { entityType: intl.formatMessage({ id: "group" }) }
), ),
elements elements
); );
@@ -297,7 +297,7 @@ export const SettingsScrapingPanel: React.FC = () => {
loadingScenes || loadingScenes ||
loadingGalleries || loadingGalleries ||
loadingPerformers || loadingPerformers ||
loadingMovies loadingGroups
) )
return <LoadingIndicator />; return <LoadingIndicator />;
@@ -361,7 +361,7 @@ export const SettingsScrapingPanel: React.FC = () => {
{renderSceneScrapers()} {renderSceneScrapers()}
{renderGalleryScrapers()} {renderGalleryScrapers()}
{renderPerformerScrapers()} {renderPerformerScrapers()}
{renderMovieScrapers()} {renderGroupScrapers()}
</div> </div>
</SettingSection> </SettingSection>
</> </>

View File

@@ -6,14 +6,14 @@ import NavUtils from "src/utils/navigation";
export const DirectorLink: React.FC<{ export const DirectorLink: React.FC<{
director: string; director: string;
linkType: "scene" | "movie"; linkType: "scene" | "group";
}> = ({ director: director, linkType = "scene" }) => { }> = ({ director: director, linkType = "scene" }) => {
const link = useMemo(() => { const link = useMemo(() => {
switch (linkType) { switch (linkType) {
case "scene": case "scene":
return NavUtils.makeDirectorScenesUrl(director); return NavUtils.makeDirectorScenesUrl(director);
case "movie": case "group":
return NavUtils.makeDirectorMoviesUrl(director); return NavUtils.makeDirectorGroupsUrl(director);
} }
}, [director, linkType]); }, [director, linkType]);

View File

@@ -10,7 +10,7 @@ import {
} from "../Galleries/GallerySelect"; } from "../Galleries/GallerySelect";
interface IMultiSetProps { interface IMultiSetProps {
type: "performers" | "studios" | "tags" | "movies" | "galleries"; type: "performers" | "studios" | "tags" | "groups" | "galleries";
existingIds?: string[]; existingIds?: string[];
ids?: string[]; ids?: string[];
mode: GQL.BulkUpdateIdMode; mode: GQL.BulkUpdateIdMode;

View File

@@ -20,7 +20,7 @@ type PopoverLinkType =
| "image" | "image"
| "gallery" | "gallery"
| "marker" | "marker"
| "movie" | "group"
| "performer" | "performer"
| "studio"; | "studio";
@@ -52,7 +52,7 @@ export const PopoverCountButton: React.FC<IProps> = ({
return faImages; return faImages;
case "marker": case "marker":
return faMapMarkerAlt; return faMapMarkerAlt;
case "movie": case "group":
return faFilm; return faFilm;
case "performer": case "performer":
return faUser; return faUser;
@@ -83,10 +83,10 @@ export const PopoverCountButton: React.FC<IProps> = ({
one: "marker", one: "marker",
other: "markers", other: "markers",
}; };
case "movie": case "group":
return { return {
one: "movie", one: "group",
other: "movies", other: "groups",
}; };
case "performer": case "performer":
return { return {

View File

@@ -8,7 +8,7 @@ import {
} from "src/components/Shared/ScrapeDialog/scrapeResult"; } from "src/components/Shared/ScrapeDialog/scrapeResult";
import { TagSelect } from "src/components/Tags/TagSelect"; import { TagSelect } from "src/components/Tags/TagSelect";
import { StudioSelect } from "src/components/Studios/StudioSelect"; import { StudioSelect } from "src/components/Studios/StudioSelect";
import { MovieSelect } from "src/components/Movies/MovieSelect"; import { GroupSelect } from "src/components/Movies/MovieSelect";
interface IScrapedStudioRow { interface IScrapedStudioRow {
title: string; title: string;
@@ -196,10 +196,10 @@ export const ScrapedPerformersRow: React.FC<
); );
}; };
export const ScrapedMoviesRow: React.FC< export const ScrapedGroupsRow: React.FC<
IScrapedObjectRowImpl<GQL.ScrapedMovie> IScrapedObjectRowImpl<GQL.ScrapedMovie>
> = ({ title, result, onChange, newObjects, onCreateNew }) => { > = ({ title, result, onChange, newObjects, onCreateNew }) => {
const moviesCopy = useMemo(() => { const groupsCopy = useMemo(() => {
return ( return (
newObjects?.map((p) => { newObjects?.map((p) => {
const name: string = p.name ?? ""; const name: string = p.name ?? "";
@@ -208,7 +208,7 @@ export const ScrapedMoviesRow: React.FC<
); );
}, [newObjects]); }, [newObjects]);
function renderScrapedMovies( function renderScrapedGroups(
scrapeResult: ScrapeResult<GQL.ScrapedMovie[]>, scrapeResult: ScrapeResult<GQL.ScrapedMovie[]>,
isNew?: boolean, isNew?: boolean,
onChangeFn?: (value: GQL.ScrapedMovie[]) => void onChangeFn?: (value: GQL.ScrapedMovie[]) => void
@@ -228,7 +228,7 @@ export const ScrapedMoviesRow: React.FC<
}); });
return ( return (
<MovieSelect <GroupSelect
isMulti isMulti
className="form-control react-select" className="form-control react-select"
isDisabled={!isNew} isDisabled={!isNew}
@@ -247,9 +247,9 @@ export const ScrapedMoviesRow: React.FC<
<ScrapedObjectsRow<GQL.ScrapedMovie> <ScrapedObjectsRow<GQL.ScrapedMovie>
title={title} title={title}
result={result} result={result}
renderObjects={renderScrapedMovies} renderObjects={renderScrapedGroups}
onChange={onChange} onChange={onChange}
newObjects={moviesCopy} newObjects={groupsCopy}
onCreateNew={onCreateNew} onCreateNew={onCreateNew}
getName={(value) => value.name ?? ""} getName={(value) => value.name ?? ""}
/> />

View File

@@ -9,7 +9,7 @@ import {
import { ObjectScrapeResult, ScrapeResult } from "./scrapeResult"; import { ObjectScrapeResult, ScrapeResult } from "./scrapeResult";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { scrapedPerformerToCreateInput } from "src/core/performers"; import { scrapedPerformerToCreateInput } from "src/core/performers";
import { scrapedMovieToCreateInput } from "src/core/movies"; import { scrapedGroupToCreateInput } from "src/core/movies";
function useCreateObject<T>( function useCreateObject<T>(
entityTypeID: string, entityTypeID: string,
@@ -123,16 +123,16 @@ export function useCreateScrapedPerformer(
return useCreateObject("performer", createNewPerformer); return useCreateObject("performer", createNewPerformer);
} }
export function useCreateScrapedMovie( export function useCreateScrapedGroup(
props: IUseCreateNewObjectProps<GQL.ScrapedMovie> props: IUseCreateNewObjectProps<GQL.ScrapedMovie>
) { ) {
const { scrapeResult, setScrapeResult, newObjects, setNewObjects } = props; const { scrapeResult, setScrapeResult, newObjects, setNewObjects } = props;
const [createMovie] = useMovieCreate(); const [createGroup] = useMovieCreate();
async function createNewMovie(toCreate: GQL.ScrapedMovie) { async function createNewGroup(toCreate: GQL.ScrapedMovie) {
const input = scrapedMovieToCreateInput(toCreate); const input = scrapedGroupToCreateInput(toCreate);
const result = await createMovie({ const result = await createGroup({
variables: { input: input }, variables: { input: input },
}); });
@@ -150,14 +150,14 @@ export function useCreateScrapedMovie(
// remove the object from the list // remove the object from the list
const newObjectsClone = newObjects.concat(); const newObjectsClone = newObjects.concat();
const pIndex = newObjectsClone.findIndex((p) => p.name === toCreate.name); const pIndex = newObjectsClone.findIndex((p) => p.name === toCreate.name);
if (pIndex === -1) throw new Error("Could not find movie to remove"); if (pIndex === -1) throw new Error("Could not find group to remove");
newObjectsClone.splice(pIndex, 1); newObjectsClone.splice(pIndex, 1);
setNewObjects(newObjectsClone); setNewObjects(newObjectsClone);
} }
return useCreateObject("movie", createNewMovie); return useCreateObject("group", createNewGroup);
} }
export function useCreateScrapedTag( export function useCreateScrapedTag(

View File

@@ -26,7 +26,7 @@ import { faTableColumns } from "@fortawesome/free-solid-svg-icons";
import { TagIDSelect } from "../Tags/TagSelect"; import { TagIDSelect } from "../Tags/TagSelect";
import { StudioIDSelect } from "../Studios/StudioSelect"; import { StudioIDSelect } from "../Studios/StudioSelect";
import { GalleryIDSelect } from "../Galleries/GallerySelect"; import { GalleryIDSelect } from "../Galleries/GallerySelect";
import { MovieIDSelect } from "../Movies/MovieSelect"; import { GroupIDSelect } from "../Movies/MovieSelect";
import { SceneIDSelect } from "../Scenes/SceneSelect"; import { SceneIDSelect } from "../Scenes/SceneSelect";
export type SelectObject = { export type SelectObject = {
@@ -44,7 +44,7 @@ interface ITypeProps {
| "scene_tags" | "scene_tags"
| "performer_tags" | "performer_tags"
| "scenes" | "scenes"
| "movies" | "groups"
| "galleries"; | "galleries";
} }
interface IFilterProps { interface IFilterProps {
@@ -364,8 +364,8 @@ export const StudioSelect: React.FC<
return <StudioIDSelect {...props} />; return <StudioIDSelect {...props} />;
}; };
export const MovieSelect: React.FC<IFilterProps> = (props) => { export const GroupSelect: React.FC<IFilterProps> = (props) => {
return <MovieIDSelect {...props} />; return <GroupIDSelect {...props} />;
}; };
export const TagSelect: React.FC< export const TagSelect: React.FC<
@@ -382,8 +382,8 @@ export const FilterSelect: React.FC<IFilterProps & ITypeProps> = (props) => {
return <StudioSelect {...props} creatable={false} />; return <StudioSelect {...props} creatable={false} />;
case "scenes": case "scenes":
return <SceneSelect {...props} creatable={false} />; return <SceneSelect {...props} creatable={false} />;
case "movies": case "groups":
return <MovieSelect {...props} creatable={false} />; return <GroupSelect {...props} creatable={false} />;
case "galleries": case "galleries":
return <GallerySelect {...props} />; return <GallerySelect {...props} />;
default: default:

View File

@@ -71,25 +71,25 @@ export const PerformerLink: React.FC<IPerformerLinkProps> = ({
); );
}; };
interface IMovieLinkProps { interface IGroupLinkProps {
movie: INamedObject; group: INamedObject;
linkType?: "scene"; linkType?: "scene";
className?: string; className?: string;
} }
export const MovieLink: React.FC<IMovieLinkProps> = ({ export const GroupLink: React.FC<IGroupLinkProps> = ({
movie, group,
linkType = "scene", linkType = "scene",
className, className,
}) => { }) => {
const link = useMemo(() => { const link = useMemo(() => {
switch (linkType) { switch (linkType) {
case "scene": case "scene":
return NavUtils.makeMovieScenesUrl(movie); return NavUtils.makeGroupScenesUrl(group);
} }
}, [movie, linkType]); }, [group, linkType]);
const title = movie.name || ""; const title = group.name || "";
return ( return (
<CommonLinkComponent link={link} className={className}> <CommonLinkComponent link={link} className={className}>
@@ -197,7 +197,7 @@ interface ITagLinkProps {
| "image" | "image"
| "details" | "details"
| "performer" | "performer"
| "movie" | "group"
| "studio"; | "studio";
className?: string; className?: string;
hoverPlacement?: Placement; hoverPlacement?: Placement;
@@ -225,8 +225,8 @@ export const TagLink: React.FC<ITagLinkProps> = ({
return NavUtils.makeTagGalleriesUrl(tag); return NavUtils.makeTagGalleriesUrl(tag);
case "image": case "image":
return NavUtils.makeTagImagesUrl(tag); return NavUtils.makeTagImagesUrl(tag);
case "movie": case "group":
return NavUtils.makeTagMoviesUrl(tag); return NavUtils.makeTagGroupsUrl(tag);
case "details": case "details":
return NavUtils.makeTagUrl(tag.id ?? ""); return NavUtils.makeTagUrl(tag.id ?? "");
} }

View File

@@ -53,7 +53,7 @@ export const Stats: React.FC = () => {
<FormattedNumber value={data.stats.movie_count} /> <FormattedNumber value={data.stats.movie_count} />
</p> </p>
<p className="heading"> <p className="heading">
<FormattedMessage id="movies" /> <FormattedMessage id="groups" />
</p> </p>
</div> </div>
<div className="stats-element"> <div className="stats-element">

View File

@@ -142,15 +142,15 @@ export const StudioCard: React.FC<IProps> = ({
); );
} }
function maybeRenderMoviesPopoverButton() { function maybeRenderGroupsPopoverButton() {
if (!studio.movie_count) return; if (!studio.movie_count) return;
return ( return (
<PopoverCountButton <PopoverCountButton
className="movie-count" className="group-count"
type="movie" type="group"
count={studio.movie_count} count={studio.movie_count}
url={NavUtils.makeStudioMoviesUrl(studio)} url={NavUtils.makeStudioGroupsUrl(studio)}
/> />
); );
} }
@@ -199,7 +199,7 @@ export const StudioCard: React.FC<IProps> = ({
<hr /> <hr />
<ButtonGroup className="card-popovers"> <ButtonGroup className="card-popovers">
{maybeRenderScenesPopoverButton()} {maybeRenderScenesPopoverButton()}
{maybeRenderMoviesPopoverButton()} {maybeRenderGroupsPopoverButton()}
{maybeRenderImagesPopoverButton()} {maybeRenderImagesPopoverButton()}
{maybeRenderGalleriesPopoverButton()} {maybeRenderGalleriesPopoverButton()}
{maybeRenderPerformersPopoverButton()} {maybeRenderPerformersPopoverButton()}

View File

@@ -31,7 +31,7 @@ import {
CompressedStudioDetailsPanel, CompressedStudioDetailsPanel,
StudioDetailsPanel, StudioDetailsPanel,
} from "./StudioDetailsPanel"; } from "./StudioDetailsPanel";
import { StudioMoviesPanel } from "./StudioMoviesPanel"; import { StudioGroupsPanel } from "./StudioMoviesPanel";
import { import {
faTrashAlt, faTrashAlt,
faLink, faLink,
@@ -63,7 +63,7 @@ const validTabs = [
"galleries", "galleries",
"images", "images",
"performers", "performers",
"movies", "groups",
"childstudios", "childstudios",
] as const; ] as const;
type TabKey = (typeof validTabs)[number]; type TabKey = (typeof validTabs)[number];
@@ -108,7 +108,7 @@ const StudioPage: React.FC<IProps> = ({ studio, tabKey }) => {
(showAllCounts ? studio.image_count_all : studio.image_count) ?? 0; (showAllCounts ? studio.image_count_all : studio.image_count) ?? 0;
const performerCount = const performerCount =
(showAllCounts ? studio.performer_count_all : studio.performer_count) ?? 0; (showAllCounts ? studio.performer_count_all : studio.performer_count) ?? 0;
const movieCount = const groupCount =
(showAllCounts ? studio.movie_count_all : studio.movie_count) ?? 0; (showAllCounts ? studio.movie_count_all : studio.movie_count) ?? 0;
const populatedDefaultTab = useMemo(() => { const populatedDefaultTab = useMemo(() => {
@@ -120,8 +120,8 @@ const StudioPage: React.FC<IProps> = ({ studio, tabKey }) => {
ret = "images"; ret = "images";
} else if (performerCount != 0) { } else if (performerCount != 0) {
ret = "performers"; ret = "performers";
} else if (movieCount != 0) { } else if (groupCount != 0) {
ret = "movies"; ret = "groups";
} else if (studio.child_studios.length != 0) { } else if (studio.child_studios.length != 0) {
ret = "childstudios"; ret = "childstudios";
} }
@@ -133,7 +133,7 @@ const StudioPage: React.FC<IProps> = ({ studio, tabKey }) => {
galleryCount, galleryCount,
imageCount, imageCount,
performerCount, performerCount,
movieCount, groupCount,
studio, studio,
]); ]);
@@ -437,19 +437,19 @@ const StudioPage: React.FC<IProps> = ({ studio, tabKey }) => {
/> />
</Tab> </Tab>
<Tab <Tab
eventKey="movies" eventKey="groups"
title={ title={
<> <>
{intl.formatMessage({ id: "movies" })} {intl.formatMessage({ id: "groups" })}
<Counter <Counter
abbreviateCounter={abbreviateCounter} abbreviateCounter={abbreviateCounter}
count={movieCount} count={groupCount}
hideZero hideZero
/> />
</> </>
} }
> >
<StudioMoviesPanel active={tabKey === "movies"} studio={studio} /> <StudioGroupsPanel active={tabKey === "groups"} studio={studio} />
</Tab> </Tab>
<Tab <Tab
eventKey="childstudios" eventKey="childstudios"

View File

@@ -1,24 +1,24 @@
import React from "react"; import React from "react";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { MovieList } from "src/components/Movies/MovieList"; import { GroupList } from "src/components/Movies/MovieList";
import { useStudioFilterHook } from "src/core/studios"; import { useStudioFilterHook } from "src/core/studios";
import { View } from "src/components/List/views"; import { View } from "src/components/List/views";
interface IStudioMoviesPanel { interface IStudioGroupsPanel {
active: boolean; active: boolean;
studio: GQL.StudioDataFragment; studio: GQL.StudioDataFragment;
} }
export const StudioMoviesPanel: React.FC<IStudioMoviesPanel> = ({ export const StudioGroupsPanel: React.FC<IStudioGroupsPanel> = ({
active, active,
studio, studio,
}) => { }) => {
const filterHook = useStudioFilterHook(studio); const filterHook = useStudioFilterHook(studio);
return ( return (
<MovieList <GroupList
filterHook={filterHook} filterHook={filterHook}
alterQuery={active} alterQuery={active}
view={View.StudioMovies} view={View.StudioGroups}
/> />
); );
}; };

View File

@@ -236,15 +236,15 @@ export const TagCard: React.FC<IProps> = ({
); );
} }
function maybeRenderMoviesPopoverButton() { function maybeRenderGroupsPopoverButton() {
if (!tag.movie_count) return; if (!tag.movie_count) return;
return ( return (
<PopoverCountButton <PopoverCountButton
className="movie-count" className="group-count"
type="movie" type="group"
count={tag.movie_count} count={tag.movie_count}
url={NavUtils.makeTagMoviesUrl(tag)} url={NavUtils.makeTagGroupsUrl(tag)}
/> />
); );
} }
@@ -258,7 +258,7 @@ export const TagCard: React.FC<IProps> = ({
{maybeRenderScenesPopoverButton()} {maybeRenderScenesPopoverButton()}
{maybeRenderImagesPopoverButton()} {maybeRenderImagesPopoverButton()}
{maybeRenderGalleriesPopoverButton()} {maybeRenderGalleriesPopoverButton()}
{maybeRenderMoviesPopoverButton()} {maybeRenderGroupsPopoverButton()}
{maybeRenderSceneMarkersPopoverButton()} {maybeRenderSceneMarkersPopoverButton()}
{maybeRenderPerformersPopoverButton()} {maybeRenderPerformersPopoverButton()}
{maybeRenderStudiosPopoverButton()} {maybeRenderStudiosPopoverButton()}

View File

@@ -42,7 +42,7 @@ import {
import { DetailImage } from "src/components/Shared/DetailImage"; import { DetailImage } from "src/components/Shared/DetailImage";
import { useLoadStickyHeader } from "src/hooks/detailsPanel"; import { useLoadStickyHeader } from "src/hooks/detailsPanel";
import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
import { TagMoviesPanel } from "./TagMoviesPanel"; import { TagGroupsPanel } from "./TagMoviesPanel";
interface IProps { interface IProps {
tag: GQL.TagDataFragment; tag: GQL.TagDataFragment;
@@ -59,7 +59,7 @@ const validTabs = [
"scenes", "scenes",
"images", "images",
"galleries", "galleries",
"movies", "groups",
"markers", "markers",
"performers", "performers",
"studios", "studios",
@@ -105,7 +105,7 @@ const TagPage: React.FC<IProps> = ({ tag, tabKey }) => {
(showAllCounts ? tag.image_count_all : tag.image_count) ?? 0; (showAllCounts ? tag.image_count_all : tag.image_count) ?? 0;
const galleryCount = const galleryCount =
(showAllCounts ? tag.gallery_count_all : tag.gallery_count) ?? 0; (showAllCounts ? tag.gallery_count_all : tag.gallery_count) ?? 0;
const movieCount = const groupCount =
(showAllCounts ? tag.movie_count_all : tag.movie_count) ?? 0; (showAllCounts ? tag.movie_count_all : tag.movie_count) ?? 0;
const sceneMarkerCount = const sceneMarkerCount =
(showAllCounts ? tag.scene_marker_count_all : tag.scene_marker_count) ?? 0; (showAllCounts ? tag.scene_marker_count_all : tag.scene_marker_count) ?? 0;
@@ -121,8 +121,8 @@ const TagPage: React.FC<IProps> = ({ tag, tabKey }) => {
ret = "images"; ret = "images";
} else if (galleryCount != 0) { } else if (galleryCount != 0) {
ret = "galleries"; ret = "galleries";
} else if (movieCount != 0) { } else if (groupCount != 0) {
ret = "movies"; ret = "groups";
} else if (sceneMarkerCount != 0) { } else if (sceneMarkerCount != 0) {
ret = "markers"; ret = "markers";
} else if (performerCount != 0) { } else if (performerCount != 0) {
@@ -140,7 +140,7 @@ const TagPage: React.FC<IProps> = ({ tag, tabKey }) => {
sceneMarkerCount, sceneMarkerCount,
performerCount, performerCount,
studioCount, studioCount,
movieCount, groupCount,
]); ]);
const setTabKey = useCallback( const setTabKey = useCallback(
@@ -484,19 +484,19 @@ const TagPage: React.FC<IProps> = ({ tag, tabKey }) => {
<TagGalleriesPanel active={tabKey === "galleries"} tag={tag} /> <TagGalleriesPanel active={tabKey === "galleries"} tag={tag} />
</Tab> </Tab>
<Tab <Tab
eventKey="movies" eventKey="groups"
title={ title={
<> <>
{intl.formatMessage({ id: "movies" })} {intl.formatMessage({ id: "groups" })}
<Counter <Counter
abbreviateCounter={abbreviateCounter} abbreviateCounter={abbreviateCounter}
count={movieCount} count={groupCount}
hideZero hideZero
/> />
</> </>
} }
> >
<TagMoviesPanel active={tabKey === "movies"} tag={tag} /> <TagGroupsPanel active={tabKey === "groups"} tag={tag} />
</Tab> </Tab>
<Tab <Tab
eventKey="markers" eventKey="markers"

View File

@@ -1,12 +1,12 @@
import React from "react"; import React from "react";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { useTagFilterHook } from "src/core/tags"; import { useTagFilterHook } from "src/core/tags";
import { MovieList } from "src/components/Movies/MovieList"; import { GroupList } from "src/components/Movies/MovieList";
export const TagMoviesPanel: React.FC<{ export const TagGroupsPanel: React.FC<{
active: boolean; active: boolean;
tag: GQL.TagDataFragment; tag: GQL.TagDataFragment;
}> = ({ active, tag }) => { }> = ({ active, tag }) => {
const filterHook = useTagFilterHook(tag); const filterHook = useTagFilterHook(tag);
return <MovieList filterHook={filterHook} alterQuery={active} />; return <GroupList filterHook={filterHook} alterQuery={active} />;
}; };

View File

@@ -143,7 +143,7 @@ export function generateDefaultFrontPageContent(intl: IntlShape) {
return [ return [
recentlyReleased(intl, FilterMode.Scenes, "scenes"), recentlyReleased(intl, FilterMode.Scenes, "scenes"),
recentlyAdded(intl, FilterMode.Studios, "studios"), recentlyAdded(intl, FilterMode.Studios, "studios"),
recentlyReleased(intl, FilterMode.Movies, "movies"), recentlyReleased(intl, FilterMode.Movies, "groups"),
recentlyAdded(intl, FilterMode.Performers, "performers"), recentlyAdded(intl, FilterMode.Performers, "performers"),
recentlyReleased(intl, FilterMode.Galleries, "galleries"), recentlyReleased(intl, FilterMode.Galleries, "galleries"),
]; ];
@@ -156,8 +156,8 @@ export function generatePremadeFrontPageContent(intl: IntlShape) {
recentlyReleased(intl, FilterMode.Galleries, "galleries"), recentlyReleased(intl, FilterMode.Galleries, "galleries"),
recentlyAdded(intl, FilterMode.Galleries, "galleries"), recentlyAdded(intl, FilterMode.Galleries, "galleries"),
recentlyAdded(intl, FilterMode.Images, "images"), recentlyAdded(intl, FilterMode.Images, "images"),
recentlyReleased(intl, FilterMode.Movies, "movies"), recentlyReleased(intl, FilterMode.Movies, "groups"),
recentlyAdded(intl, FilterMode.Movies, "movies"), recentlyAdded(intl, FilterMode.Movies, "groups"),
recentlyAdded(intl, FilterMode.Studios, "studios"), recentlyAdded(intl, FilterMode.Studios, "studios"),
recentlyAdded(intl, FilterMode.Performers, "performers"), recentlyAdded(intl, FilterMode.Performers, "performers"),
]; ];

View File

@@ -1,7 +1,7 @@
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import TextUtils from "src/utils/text"; import TextUtils from "src/utils/text";
export const scrapedMovieToCreateInput = (toCreate: GQL.ScrapedMovie) => { export const scrapedGroupToCreateInput = (toCreate: GQL.ScrapedMovie) => {
const input: GQL.MovieCreateInput = { const input: GQL.MovieCreateInput = {
name: toCreate.name ?? "", name: toCreate.name ?? "",
url: toCreate.url, url: toCreate.url,

View File

@@ -54,7 +54,7 @@ body {
} }
} }
#movie-page, #group-page,
#performer-page, #performer-page,
#studio-page, #studio-page,
#tag-page { #tag-page {
@@ -83,7 +83,7 @@ dd {
display: none; display: none;
} }
.movie-name, .group-name,
.performer-name, .performer-name,
.studio-name, .studio-name,
.tag-name { .tag-name {
@@ -93,7 +93,7 @@ dd {
.sticky.detail-header-group { .sticky.detail-header-group {
padding: 1rem 2.5rem; padding: 1rem 2.5rem;
a.movie-name, a.group-name,
a.performer-name, a.performer-name,
a.studio-name, a.studio-name,
a.tag-name { a.tag-name {
@@ -313,7 +313,7 @@ dd {
justify-content: center; justify-content: center;
padding: 0 1rem; padding: 0 1rem;
.movie-images { .group-images {
height: 100%; height: 100%;
} }
@@ -322,7 +322,7 @@ dd {
height: auto; height: auto;
padding: 0; padding: 0;
.movie-images { .group-images {
.img { .img {
max-width: 100%; max-width: 100%;
} }
@@ -335,18 +335,18 @@ dd {
transition: 0.5s; transition: 0.5s;
} }
.movie-images img { .group-images img {
@media (max-width: 576px) { @media (max-width: 576px) {
max-width: 100%; max-width: 100%;
} }
} }
} }
#movie-page .detail-header-image .movie-images img { #group-page .detail-header-image .group-images img {
max-width: 13rem; max-width: 13rem;
} }
#movie-page .detail-header-image img, #group-page .detail-header-image img,
#performer-page .detail-header-image img, #performer-page .detail-header-image img,
#tag-page .detail-header-image img { #tag-page .detail-header-image img {
border-radius: 0.5rem; border-radius: 0.5rem;

View File

@@ -798,9 +798,9 @@
"countables": { "countables": {
"files": "{count, plural, one {File} other {Files}}", "files": "{count, plural, one {File} other {Files}}",
"galleries": "{count, plural, one {Gallery} other {Galleries}}", "galleries": "{count, plural, one {Gallery} other {Galleries}}",
"groups": "{count, plural, one {Group} other {Groups}}",
"images": "{count, plural, one {Image} other {Images}}", "images": "{count, plural, one {Image} other {Images}}",
"markers": "{count, plural, one {Marker} other {Markers}}", "markers": "{count, plural, one {Marker} other {Markers}}",
"movies": "{count, plural, one {Movie} other {Movies}}",
"performers": "{count, plural, one {Performer} other {Performers}}", "performers": "{count, plural, one {Performer} other {Performers}}",
"scenes": "{count, plural, one {Scene} other {Scenes}}", "scenes": "{count, plural, one {Scene} other {Scenes}}",
"studios": "{count, plural, one {Studio} other {Studios}}", "studios": "{count, plural, one {Studio} other {Studios}}",
@@ -1060,6 +1060,10 @@
"TRANSGENDER_FEMALE": "Transgender Female", "TRANSGENDER_FEMALE": "Transgender Female",
"TRANSGENDER_MALE": "Transgender Male" "TRANSGENDER_MALE": "Transgender Male"
}, },
"group": "Group",
"group_count": "Group Count",
"group_scene_number": "Scene Number",
"groups": "Groups",
"hair_color": "Hair Colour", "hair_color": "Hair Colour",
"handy_connection_status": { "handy_connection_status": {
"connecting": "Connecting", "connecting": "Connecting",
@@ -1117,10 +1121,6 @@
}, },
"megabits_per_second": "{value} mbps", "megabits_per_second": "{value} mbps",
"metadata": "Metadata", "metadata": "Metadata",
"movie": "Movie",
"movie_count": "Movie Count",
"movie_scene_number": "Scene Number",
"movies": "Movies",
"name": "Name", "name": "Name",
"new": "New", "new": "New",
"none": "None", "none": "None",

View File

@@ -173,7 +173,7 @@ export type InputType =
| "performer_tags" | "performer_tags"
| "scenes" | "scenes"
| "scene_tags" | "scene_tags"
| "movies" | "groups"
| "galleries" | "galleries"
| undefined; | undefined;

View File

@@ -1,9 +1,9 @@
import { ILabeledIdCriterion, ILabeledIdCriterionOption } from "./criterion"; import { ILabeledIdCriterion, ILabeledIdCriterionOption } from "./criterion";
const inputType = "movies"; const inputType = "groups";
export const MoviesCriterionOption = new ILabeledIdCriterionOption( export const MoviesCriterionOption = new ILabeledIdCriterionOption(
"movies", "groups",
"movies", "movies",
false, false,
inputType, inputType,

View File

@@ -47,7 +47,6 @@ const sortByOptions = [
"resume_time", "resume_time",
"play_duration", "play_duration",
"play_count", "play_count",
"movie_scene_number",
"interactive", "interactive",
"interactive_speed", "interactive_speed",
"perceptual_similarity", "perceptual_similarity",
@@ -59,6 +58,10 @@ const sortByOptions = [
messageID: "o_count", messageID: "o_count",
value: "o_counter", value: "o_counter",
}, },
{
messageID: "group_scene_number",
value: "movie_scene_number",
},
]); ]);
const displayModeOptions = [ const displayModeOptions = [
DisplayMode.Grid, DisplayMode.Grid,

View File

@@ -36,7 +36,7 @@ const sortByOptions = ["name", "random"]
value: "scenes_count", value: "scenes_count",
}, },
{ {
messageID: "movie_count", messageID: "group_count",
value: "movies_count", value: "movies_count",
}, },
{ {
@@ -62,7 +62,7 @@ const criterionOptions = [
createMandatoryNumberCriterionOption("gallery_count"), createMandatoryNumberCriterionOption("gallery_count"),
createMandatoryNumberCriterionOption("performer_count"), createMandatoryNumberCriterionOption("performer_count"),
createMandatoryNumberCriterionOption("studio_count"), createMandatoryNumberCriterionOption("studio_count"),
createMandatoryNumberCriterionOption("movie_count"), createMandatoryNumberCriterionOption("movie_count", "group_count"),
createMandatoryNumberCriterionOption("marker_count"), createMandatoryNumberCriterionOption("marker_count"),
ParentTagsCriterionOption, ParentTagsCriterionOption,
new MandatoryNumberCriterionOption("parent_tag_count", "parent_count"), new MandatoryNumberCriterionOption("parent_tag_count", "parent_count"),

View File

@@ -693,12 +693,12 @@ declare namespace PluginApi {
function makePerformerScenesUrl(...args: any[]): any; function makePerformerScenesUrl(...args: any[]): any;
function makePerformerImagesUrl(...args: any[]): any; function makePerformerImagesUrl(...args: any[]): any;
function makePerformerGalleriesUrl(...args: any[]): any; function makePerformerGalleriesUrl(...args: any[]): any;
function makePerformerMoviesUrl(...args: any[]): any; function makePerformerGroupsUrl(...args: any[]): any;
function makePerformersCountryUrl(...args: any[]): any; function makePerformersCountryUrl(...args: any[]): any;
function makeStudioScenesUrl(...args: any[]): any; function makeStudioScenesUrl(...args: any[]): any;
function makeStudioImagesUrl(...args: any[]): any; function makeStudioImagesUrl(...args: any[]): any;
function makeStudioGalleriesUrl(...args: any[]): any; function makeStudioGalleriesUrl(...args: any[]): any;
function makeStudioMoviesUrl(...args: any[]): any; function makeStudioGroupsUrl(...args: any[]): any;
function makeStudioPerformersUrl(...args: any[]): any; function makeStudioPerformersUrl(...args: any[]): any;
function makeTagUrl(...args: any[]): any; function makeTagUrl(...args: any[]): any;
function makeParentTagsUrl(...args: any[]): any; function makeParentTagsUrl(...args: any[]): any;
@@ -710,7 +710,7 @@ declare namespace PluginApi {
function makeTagImagesUrl(...args: any[]): any; function makeTagImagesUrl(...args: any[]): any;
function makeScenesPHashMatchUrl(...args: any[]): any; function makeScenesPHashMatchUrl(...args: any[]): any;
function makeSceneMarkerUrl(...args: any[]): any; function makeSceneMarkerUrl(...args: any[]): any;
function makeMovieScenesUrl(...args: any[]): any; function makeGroupScenesUrl(...args: any[]): any;
function makeChildStudiosUrl(...args: any[]): any; function makeChildStudiosUrl(...args: any[]): any;
function makeGalleryImagesUrl(...args: any[]): any; function makeGalleryImagesUrl(...args: any[]): any;
} }

View File

@@ -81,11 +81,11 @@ export function getAggregateTagIds(state: { tags: IHasID[] }[]) {
return getAggregateIds(sortedLists); return getAggregateIds(sortedLists);
} }
interface IMovie { interface IGroup {
movie: IHasID; movie: IHasID;
} }
export function getAggregateMovieIds(state: { movies: IMovie[] }[]) { export function getAggregateGroupIds(state: { movies: IGroup[] }[]) {
const sortedLists = state.map((o) => const sortedLists = state.map((o) =>
o.movies.map((oo) => oo.movie.id).sort() o.movies.map((oo) => oo.movie.id).sort()
); );

View File

@@ -103,7 +103,7 @@ const makePerformerGalleriesUrl = (
return `/galleries?${filter.makeQueryParameters()}`; return `/galleries?${filter.makeQueryParameters()}`;
}; };
const makePerformerMoviesUrl = ( const makePerformerGroupsUrl = (
performer: Partial<GQL.PerformerDataFragment>, performer: Partial<GQL.PerformerDataFragment>,
extraPerformer?: ILabeledId, extraPerformer?: ILabeledId,
extraCriteria?: Criterion<CriterionValue>[] extraCriteria?: Criterion<CriterionValue>[]
@@ -121,7 +121,7 @@ const makePerformerMoviesUrl = (
filter.criteria.push(criterion); filter.criteria.push(criterion);
addExtraCriteria(filter.criteria, extraCriteria); addExtraCriteria(filter.criteria, extraCriteria);
return `/movies?${filter.makeQueryParameters()}`; return `/groups?${filter.makeQueryParameters()}`;
}; };
const makePerformersCountryUrl = ( const makePerformersCountryUrl = (
@@ -174,7 +174,7 @@ const makeStudioGalleriesUrl = (studio: Partial<GQL.StudioDataFragment>) => {
return `/galleries?${filter.makeQueryParameters()}`; return `/galleries?${filter.makeQueryParameters()}`;
}; };
const makeStudioMoviesUrl = (studio: Partial<GQL.StudioDataFragment>) => { const makeStudioGroupsUrl = (studio: Partial<GQL.StudioDataFragment>) => {
if (!studio.id) return "#"; if (!studio.id) return "#";
const filter = new ListFilterModel(GQL.FilterMode.Movies, undefined); const filter = new ListFilterModel(GQL.FilterMode.Movies, undefined);
const criterion = new StudiosCriterion(); const criterion = new StudiosCriterion();
@@ -184,7 +184,7 @@ const makeStudioMoviesUrl = (studio: Partial<GQL.StudioDataFragment>) => {
depth: 0, depth: 0,
}; };
filter.criteria.push(criterion); filter.criteria.push(criterion);
return `/movies?${filter.makeQueryParameters()}`; return `/groups?${filter.makeQueryParameters()}`;
}; };
const makeStudioPerformersUrl = (studio: Partial<GQL.StudioDataFragment>) => { const makeStudioPerformersUrl = (studio: Partial<GQL.StudioDataFragment>) => {
@@ -211,12 +211,12 @@ const makeChildStudiosUrl = (studio: Partial<GQL.StudioDataFragment>) => {
return `/studios?${filter.makeQueryParameters()}`; return `/studios?${filter.makeQueryParameters()}`;
}; };
const makeMovieScenesUrl = (movie: Partial<GQL.MovieDataFragment>) => { const makeGroupScenesUrl = (group: Partial<GQL.MovieDataFragment>) => {
if (!movie.id) return "#"; if (!group.id) return "#";
const filter = new ListFilterModel(GQL.FilterMode.Scenes, undefined); const filter = new ListFilterModel(GQL.FilterMode.Scenes, undefined);
const criterion = new MoviesCriterion(); const criterion = new MoviesCriterion();
criterion.value = [ criterion.value = [
{ id: movie.id, label: movie.name || `Movie ${movie.id}` }, { id: group.id, label: group.name || `Group ${group.id}` },
]; ];
filter.criteria.push(criterion); filter.criteria.push(criterion);
return `/scenes?${filter.makeQueryParameters()}`; return `/scenes?${filter.makeQueryParameters()}`;
@@ -298,8 +298,8 @@ const makeTagImagesUrl = (tag: INamedObject) => {
return `/images?${makeTagFilter(GQL.FilterMode.Images, tag)}`; return `/images?${makeTagFilter(GQL.FilterMode.Images, tag)}`;
}; };
const makeTagMoviesUrl = (tag: INamedObject) => { const makeTagGroupsUrl = (tag: INamedObject) => {
return `/movies?${makeTagFilter(GQL.FilterMode.Movies, tag)}`; return `/groups?${makeTagFilter(GQL.FilterMode.Movies, tag)}`;
}; };
type SceneMarkerDataFragment = Pick<GQL.SceneMarker, "id" | "seconds"> & { type SceneMarkerDataFragment = Pick<GQL.SceneMarker, "id" | "seconds"> & {
@@ -349,13 +349,13 @@ const makeDirectorScenesUrl = (director: string) => {
return `/scenes?${filter.makeQueryParameters()}`; return `/scenes?${filter.makeQueryParameters()}`;
}; };
const makeDirectorMoviesUrl = (director: string) => { const makeDirectorGroupsUrl = (director: string) => {
if (director.length == 0) return "#"; if (director.length == 0) return "#";
const filter = new ListFilterModel(GQL.FilterMode.Movies, undefined); const filter = new ListFilterModel(GQL.FilterMode.Movies, undefined);
filter.criteria.push( filter.criteria.push(
stringEqualsCriterion(createStringCriterionOption("director"), director) stringEqualsCriterion(createStringCriterionOption("director"), director)
); );
return `/movies?${filter.makeQueryParameters()}`; return `/groups?${filter.makeQueryParameters()}`;
}; };
const makePhotographerGalleriesUrl = (photographer: string) => { const makePhotographerGalleriesUrl = (photographer: string) => {
@@ -401,12 +401,12 @@ const NavUtils = {
makePerformerScenesUrl, makePerformerScenesUrl,
makePerformerImagesUrl, makePerformerImagesUrl,
makePerformerGalleriesUrl, makePerformerGalleriesUrl,
makePerformerMoviesUrl, makePerformerGroupsUrl,
makePerformersCountryUrl, makePerformersCountryUrl,
makeStudioScenesUrl, makeStudioScenesUrl,
makeStudioImagesUrl, makeStudioImagesUrl,
makeStudioGalleriesUrl, makeStudioGalleriesUrl,
makeStudioMoviesUrl, makeStudioGroupsUrl: makeStudioGroupsUrl,
makeStudioPerformersUrl, makeStudioPerformersUrl,
makeTagUrl, makeTagUrl,
makeParentTagsUrl, makeParentTagsUrl,
@@ -417,16 +417,16 @@ const NavUtils = {
makeTagStudiosUrl, makeTagStudiosUrl,
makeTagGalleriesUrl, makeTagGalleriesUrl,
makeTagImagesUrl, makeTagImagesUrl,
makeTagMoviesUrl, makeTagGroupsUrl,
makeScenesPHashMatchUrl, makeScenesPHashMatchUrl,
makeSceneMarkerUrl, makeSceneMarkerUrl,
makeMovieScenesUrl, makeGroupScenesUrl,
makeChildStudiosUrl, makeChildStudiosUrl,
makeGalleryImagesUrl, makeGalleryImagesUrl,
makeDirectorScenesUrl, makeDirectorScenesUrl,
makePhotographerGalleriesUrl, makePhotographerGalleriesUrl,
makePhotographerImagesUrl, makePhotographerImagesUrl,
makeDirectorMoviesUrl, makeDirectorGroupsUrl,
}; };
export default NavUtils; export default NavUtils;