Skip to content

Commit

Permalink
Front end changes
Browse files Browse the repository at this point in the history
  • Loading branch information
WithoutPants committed Jun 15, 2024
1 parent 706c23f commit c18a01c
Show file tree
Hide file tree
Showing 17 changed files with 283 additions and 102 deletions.
6 changes: 6 additions & 0 deletions ui/v2.5/graphql/data/scrapers.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ fragment ScrapedMovieData on ScrapedMovie {
studio {
...ScrapedMovieStudioData
}
tags {
...ScrapedSceneTagData
}
}

fragment ScrapedSceneMovieData on ScrapedMovie {
Expand All @@ -116,6 +119,9 @@ fragment ScrapedSceneMovieData on ScrapedMovie {
studio {
...ScrapedMovieStudioData
}
tags {
...ScrapedSceneTagData
}
}

fragment ScrapedSceneStudioData on ScrapedStudio {
Expand Down
2 changes: 2 additions & 0 deletions ui/v2.5/graphql/data/tag.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ fragment TagData on Tag {
gallery_count_all: gallery_count(depth: -1)
performer_count
performer_count_all: performer_count(depth: -1)
movie_count
movie_count_all: movie_count(depth: -1)

parents {
...SlimTagData
Expand Down
34 changes: 34 additions & 0 deletions ui/v2.5/src/components/Movies/EditMoviesDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import { useToast } from "src/hooks/Toast";
import * as FormUtils from "src/utils/form";
import { RatingSystem } from "../Shared/Rating/RatingSystem";
import {
getAggregateInputIDs,
getAggregateInputValue,
getAggregateRating,
getAggregateStudioId,
getAggregateTagIds,
} from "src/utils/bulkUpdate";
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
import { isEqual } from "lodash-es";
import { MultiSet } from "../Shared/MultiSet";

interface IListOperationProps {
selected: GQL.MovieDataFragment[];
Expand All @@ -29,13 +33,20 @@ export const EditMoviesDialog: React.FC<IListOperationProps> = (
const [studioId, setStudioId] = useState<string | undefined>();
const [director, setDirector] = useState<string | undefined>();

const [tagMode, setTagMode] = React.useState<GQL.BulkUpdateIdMode>(
GQL.BulkUpdateIdMode.Add
);
const [tagIds, setTagIds] = useState<string[]>();
const [existingTagIds, setExistingTagIds] = useState<string[]>();

const [updateMovies] = useBulkMovieUpdate(getMovieInput());

const [isUpdating, setIsUpdating] = useState(false);

function getMovieInput(): GQL.BulkMovieUpdateInput {
const aggregateRating = getAggregateRating(props.selected);
const aggregateStudioId = getAggregateStudioId(props.selected);
const aggregateTagIds = getAggregateTagIds(props.selected);

const movieInput: GQL.BulkMovieUpdateInput = {
ids: props.selected.map((movie) => movie.id),
Expand All @@ -45,6 +56,7 @@ export const EditMoviesDialog: React.FC<IListOperationProps> = (
// if rating is undefined
movieInput.rating100 = getAggregateInputValue(rating100, aggregateRating);
movieInput.studio_id = getAggregateInputValue(studioId, aggregateStudioId);
movieInput.tag_ids = getAggregateInputIDs(tagMode, tagIds, aggregateTagIds);

return movieInput;
}
Expand Down Expand Up @@ -72,14 +84,18 @@ export const EditMoviesDialog: React.FC<IListOperationProps> = (
const state = props.selected;
let updateRating: number | undefined;
let updateStudioId: string | undefined;
let updateTagIds: string[] = [];
let updateDirector: string | undefined;
let first = true;

state.forEach((movie: GQL.MovieDataFragment) => {
const movieTagIDs = (movie.tags ?? []).map((p) => p.id).sort();

if (first) {
first = false;
updateRating = movie.rating100 ?? undefined;
updateStudioId = movie.studio?.id ?? undefined;
updateTagIds = movieTagIDs;
updateDirector = movie.director ?? undefined;
} else {
if (movie.rating100 !== updateRating) {
Expand All @@ -91,11 +107,15 @@ export const EditMoviesDialog: React.FC<IListOperationProps> = (
if (movie.director !== updateDirector) {
updateDirector = undefined;
}
if (!isEqual(movieTagIDs, updateTagIds)) {
updateTagIds = [];
}
}
});

setRating(updateRating);
setStudioId(updateStudioId);
setExistingTagIds(updateTagIds);
setDirector(updateDirector);
}, [props.selected]);

Expand Down Expand Up @@ -158,6 +178,20 @@ export const EditMoviesDialog: React.FC<IListOperationProps> = (
placeholder={intl.formatMessage({ id: "director" })}
/>
</Form.Group>
<Form.Group controlId="tags">
<Form.Label>
<FormattedMessage id="tags" />
</Form.Label>
<MultiSet
type="tags"
disabled={isUpdating}
onUpdate={(itemIDs) => setTagIds(itemIDs)}
onSetMode={(newMode) => setTagMode(newMode)}
existingIds={existingTagIds ?? []}
ids={tagIds ?? []}
mode={tagMode}
/>
</Form.Group>
</Form>
</ModalComponent>
);
Expand Down
69 changes: 47 additions & 22 deletions ui/v2.5/src/components/Movies/MovieCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import * as GQL from "src/core/generated-graphql";
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
import { HoverPopover } from "../Shared/HoverPopover";
import { Icon } from "../Shared/Icon";
import { SceneLink } from "../Shared/TagLink";
import { SceneLink, TagLink } from "../Shared/TagLink";
import { TruncatedText } from "../Shared/TruncatedText";
import { FormattedMessage } from "react-intl";
import { RatingBanner } from "../Shared/RatingBanner";
import { faPlayCircle } from "@fortawesome/free-solid-svg-icons";
import { faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
import ScreenUtils from "src/utils/screen";

interface IProps {
Expand All @@ -20,37 +20,44 @@ interface IProps {
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
}

export const MovieCard: React.FC<IProps> = (props: IProps) => {
export const MovieCard: React.FC<IProps> = ({
movie,
sceneIndex,
containerWidth,
selecting,
selected,
onSelectedChanged,
}) => {
const [cardWidth, setCardWidth] = useState<number>();

useEffect(() => {
if (!props.containerWidth || ScreenUtils.isMobile()) return;
if (!containerWidth || ScreenUtils.isMobile()) return;

let preferredCardWidth = 250;
let fittedCardWidth = calculateCardWidth(
props.containerWidth,
containerWidth,
preferredCardWidth!
);
setCardWidth(fittedCardWidth);
}, [props, props.containerWidth]);
}, [containerWidth]);

function maybeRenderSceneNumber() {
if (!props.sceneIndex) return;
if (!sceneIndex) return;

return (
<>
<hr />
<span className="movie-scene-number">
<FormattedMessage id="scene" /> #{props.sceneIndex}
<FormattedMessage id="scene" /> #{sceneIndex}
</span>
</>
);
}

function maybeRenderScenesPopoverButton() {
if (props.movie.scenes.length === 0) return;
if (movie.scenes.length === 0) return;

const popoverContent = props.movie.scenes.map((scene) => (
const popoverContent = movie.scenes.map((scene) => (
<SceneLink key={scene.id} scene={scene} />
));

Expand All @@ -62,20 +69,38 @@ export const MovieCard: React.FC<IProps> = (props: IProps) => {
>
<Button className="minimal">
<Icon icon={faPlayCircle} />
<span>{props.movie.scenes.length}</span>
<span>{movie.scenes.length}</span>
</Button>
</HoverPopover>
);
}

function maybeRenderTagPopoverButton() {
if (movie.tags.length <= 0) return;

const popoverContent = movie.tags.map((tag) => (
<TagLink key={tag.id} linkType="movie" tag={tag} />
));

return (
<HoverPopover placement="bottom" content={popoverContent}>
<Button className="minimal tag-count">
<Icon icon={faTag} />
<span>{movie.tags.length}</span>
</Button>
</HoverPopover>
);
}

function maybeRenderPopoverButtonGroup() {
if (props.sceneIndex || props.movie.scenes.length > 0) {
if (sceneIndex || movie.scenes.length > 0 || movie.tags.length > 0) {
return (
<>
{maybeRenderSceneNumber()}
<hr />
<ButtonGroup className="card-popovers">
{maybeRenderScenesPopoverButton()}
{maybeRenderTagPopoverButton()}
</ButtonGroup>
</>
);
Expand All @@ -85,34 +110,34 @@ export const MovieCard: React.FC<IProps> = (props: IProps) => {
return (
<GridCard
className="movie-card"
url={`/movies/${props.movie.id}`}
url={`/movies/${movie.id}`}
width={cardWidth}
title={props.movie.name}
title={movie.name}
linkClassName="movie-card-header"
image={
<>
<img
loading="lazy"
className="movie-card-image"
alt={props.movie.name ?? ""}
src={props.movie.front_image_path ?? ""}
alt={movie.name ?? ""}
src={movie.front_image_path ?? ""}
/>
<RatingBanner rating={props.movie.rating100} />
<RatingBanner rating={movie.rating100} />
</>
}
details={
<div className="movie-card__details">
<span className="movie-card__date">{props.movie.date}</span>
<span className="movie-card__date">{movie.date}</span>
<TruncatedText
className="movie-card__description"
text={props.movie.synopsis}
text={movie.synopsis}
lineCount={3}
/>
</div>
}
selected={props.selected}
selecting={props.selecting}
onSelectedChanged={props.onSelectedChanged}
selected={selected}
selecting={selecting}
onSelectedChanged={onSelectedChanged}
popovers={maybeRenderPopoverButtonGroup()}
/>
);
Expand Down
1 change: 1 addition & 0 deletions ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
return (
<MovieDetailsPanel
movie={movie}
collapsed={collapsed}
fullWidth={!collapsed && !compactExpandedDetails}
/>
);
Expand Down
37 changes: 36 additions & 1 deletion ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,54 @@ import TextUtils from "src/utils/text";
import { DetailItem } from "src/components/Shared/DetailItem";
import { Link } from "react-router-dom";
import { DirectorLink } from "src/components/Shared/Link";
import { TagLink } from "src/components/Shared/TagLink";

interface IMovieDetailsPanel {
movie: GQL.MovieDataFragment;
collapsed?: boolean;
fullWidth?: boolean;
}

export const MovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({
movie,
collapsed,
fullWidth,
}) => {
// Network state
const intl = useIntl();

function renderTagsField() {
if (!movie.tags.length) {
return;
}
return (
<ul className="pl-0">
{(movie.tags ?? []).map((tag) => (
<TagLink key={tag.id} linkType="movie" tag={tag} />
))}
</ul>
);
}

function maybeRenderExtraDetails() {
if (!collapsed) {
return (
<>
<DetailItem
id="synopsis"
value={movie.synopsis}
fullWidth={fullWidth}
/>
<DetailItem
id="tags"
value={renderTagsField()}
fullWidth={fullWidth}
/>
</>
);
}
}

return (
<div className="detail-group">
<DetailItem
Expand Down Expand Up @@ -57,7 +92,7 @@ export const MovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({
}
fullWidth={fullWidth}
/>
<DetailItem id="synopsis" value={movie.synopsis} fullWidth={fullWidth} />
{maybeRenderExtraDetails()}
</div>
);
};
Expand Down
Loading

0 comments on commit c18a01c

Please sign in to comment.