mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 20:07:05 +03:00
Add additional fields and restyle Movie select and Gallery select (#4851)
* Add new fields and restyle gallery selector * Add new fields and style movie selector
This commit is contained in:
@@ -358,7 +358,7 @@ func (qb *MovieStore) makeQuery(ctx context.Context, movieFilter *models.MovieFi
|
|||||||
distinctIDs(&query, movieTable)
|
distinctIDs(&query, movieTable)
|
||||||
|
|
||||||
if q := findFilter.Q; q != nil && *q != "" {
|
if q := findFilter.Q; q != nil && *q != "" {
|
||||||
searchColumns := []string{"movies.name"}
|
searchColumns := []string{"movies.name", "movies.aliases"}
|
||||||
query.parseQueryString(searchColumns, *q)
|
query.parseQueryString(searchColumns, *q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,16 @@ fragment GalleryData on Gallery {
|
|||||||
fragment SelectGalleryData on Gallery {
|
fragment SelectGalleryData on Gallery {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
|
date
|
||||||
|
code
|
||||||
|
studio {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
cover {
|
||||||
|
paths {
|
||||||
|
thumbnail
|
||||||
|
}
|
||||||
|
}
|
||||||
files {
|
files {
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,10 @@ fragment SlimMovieData on Movie {
|
|||||||
fragment SelectMovieData on Movie {
|
fragment SelectMovieData on Movie {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
aliases
|
||||||
|
date
|
||||||
|
studio {
|
||||||
|
name
|
||||||
|
}
|
||||||
front_image_path
|
front_image_path
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,10 +33,13 @@ import {
|
|||||||
CriterionValue,
|
CriterionValue,
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
import { PathCriterion } from "src/models/list-filter/criteria/path";
|
import { PathCriterion } from "src/models/list-filter/criteria/path";
|
||||||
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
|
|
||||||
export type Gallery = Pick<GQL.Gallery, "id" | "title"> & {
|
export type Gallery = Pick<GQL.Gallery, "id" | "title" | "date" | "code"> & {
|
||||||
|
studio?: Pick<GQL.Studio, "name"> | null;
|
||||||
files: Pick<GQL.GalleryFile, "path">[];
|
files: Pick<GQL.GalleryFile, "path">[];
|
||||||
folder?: Pick<GQL.Folder, "path"> | null;
|
folder?: Pick<GQL.Folder, "path"> | null;
|
||||||
|
cover?: Pick<GQL.Image, "paths"> | null;
|
||||||
};
|
};
|
||||||
type Option = SelectOption<Gallery>;
|
type Option = SelectOption<Gallery>;
|
||||||
|
|
||||||
@@ -111,10 +114,41 @@ const _GallerySelect: React.FC<
|
|||||||
thisOptionProps = {
|
thisOptionProps = {
|
||||||
...optionProps,
|
...optionProps,
|
||||||
children: (
|
children: (
|
||||||
<span>
|
<span className="gallery-select-option">
|
||||||
<span>{title}</span>
|
<span className="gallery-select-row">
|
||||||
|
{object.cover?.paths?.thumbnail && (
|
||||||
|
<img
|
||||||
|
className="gallery-select-image"
|
||||||
|
src={object.cover.paths.thumbnail}
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className="gallery-select-details">
|
||||||
|
<TruncatedText
|
||||||
|
className="gallery-select-title"
|
||||||
|
text={title}
|
||||||
|
lineCount={1}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{object.studio?.name && (
|
||||||
|
<span className="gallery-select-studio">
|
||||||
|
{object.studio?.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{object.date && (
|
||||||
|
<span className="gallery-select-date">{object.date}</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{object.code && (
|
||||||
|
<span className="gallery-select-code">{object.code}</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
{matchedPath && (
|
{matchedPath && (
|
||||||
<span className="gallery-select-alias">{` (${matchedPath})`}</span>
|
<span className="gallery-select-alias">{`(${matchedPath})`}</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -349,8 +349,51 @@ $galleryTabWidth: 450px;
|
|||||||
padding-right: 2px;
|
padding-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-select-alias {
|
.gallery-select-option {
|
||||||
font-size: 0.8rem;
|
.gallery-select-row {
|
||||||
font-weight: bold;
|
align-items: center;
|
||||||
white-space: pre;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.gallery-select-image {
|
||||||
|
background-color: $body-bg;
|
||||||
|
margin-right: 0.4em;
|
||||||
|
max-height: 50px;
|
||||||
|
max-width: 89px;
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-select-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
max-height: 4.1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.gallery-select-title {
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-select-date,
|
||||||
|
.gallery-select-studio,
|
||||||
|
.gallery-select-code {
|
||||||
|
color: $text-muted;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-select-alias {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 100%;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,14 @@ import { useCompare } from "src/hooks/state";
|
|||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
import { sortByRelevance } from "src/utils/query";
|
import { sortByRelevance } from "src/utils/query";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent } from "src/patch";
|
||||||
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
|
|
||||||
export type Movie = Pick<GQL.Movie, "id" | "name">;
|
export type Movie = Pick<
|
||||||
|
GQL.Movie,
|
||||||
|
"id" | "name" | "date" | "front_image_path" | "aliases"
|
||||||
|
> & {
|
||||||
|
studio?: Pick<GQL.Studio, "name"> | null;
|
||||||
|
};
|
||||||
type Option = SelectOption<Movie>;
|
type Option = SelectOption<Movie>;
|
||||||
|
|
||||||
const _MovieSelect: React.FC<
|
const _MovieSelect: React.FC<
|
||||||
@@ -64,7 +70,12 @@ const _MovieSelect: React.FC<
|
|||||||
return !exclude.includes(movie.id.toString());
|
return !exclude.includes(movie.id.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
return sortByRelevance(input, ret, (m) => m.name).map((movie) => ({
|
return sortByRelevance(
|
||||||
|
input,
|
||||||
|
ret,
|
||||||
|
(m) => m.name,
|
||||||
|
(m) => (m.aliases ? [m.aliases] : [])
|
||||||
|
).map((movie) => ({
|
||||||
value: movie.id,
|
value: movie.id,
|
||||||
object: movie,
|
object: movie,
|
||||||
}));
|
}));
|
||||||
@@ -77,9 +88,53 @@ const _MovieSelect: React.FC<
|
|||||||
|
|
||||||
const title = object.name;
|
const title = object.name;
|
||||||
|
|
||||||
|
// if name does not match the input value but an alias does, show the alias
|
||||||
|
const { inputValue } = optionProps.selectProps;
|
||||||
|
let alias: string | undefined = "";
|
||||||
|
if (!title.toLowerCase().includes(inputValue.toLowerCase())) {
|
||||||
|
alias = object.aliases || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
thisOptionProps = {
|
thisOptionProps = {
|
||||||
...optionProps,
|
...optionProps,
|
||||||
children: <span>{title}</span>,
|
children: (
|
||||||
|
<span className="movie-select-option">
|
||||||
|
<span className="movie-select-row">
|
||||||
|
{object.front_image_path && (
|
||||||
|
<img
|
||||||
|
className="movie-select-image"
|
||||||
|
src={object.front_image_path}
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className="movie-select-details">
|
||||||
|
<TruncatedText
|
||||||
|
className="movie-select-title"
|
||||||
|
text={
|
||||||
|
<span>
|
||||||
|
{title}
|
||||||
|
{alias && (
|
||||||
|
<span className="movie-select-alias">{` (${alias})`}</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
lineCount={1}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{object.studio?.name && (
|
||||||
|
<span className="movie-select-studio">
|
||||||
|
{object.studio?.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{object.date && (
|
||||||
|
<span className="movie-select-date">{object.date}</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
return <reactSelectComponents.Option {...thisOptionProps} />;
|
return <reactSelectComponents.Option {...thisOptionProps} />;
|
||||||
@@ -140,7 +195,10 @@ const _MovieSelect: React.FC<
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
options.some((o) => {
|
options.some((o) => {
|
||||||
return o.name.toLowerCase() === inputValue.toLowerCase();
|
return (
|
||||||
|
o.name.toLowerCase() === inputValue.toLowerCase() ||
|
||||||
|
o.aliases?.toLowerCase() === inputValue.toLowerCase()
|
||||||
|
);
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -43,3 +43,49 @@
|
|||||||
#movie-page .rating-number .text-input {
|
#movie-page .rating-number .text-input {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movie-select-option {
|
||||||
|
.movie-select-row {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.movie-select-image {
|
||||||
|
background-color: $body-bg;
|
||||||
|
margin-right: 0.4em;
|
||||||
|
max-height: 50px;
|
||||||
|
max-width: 89px;
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movie-select-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
max-height: 4.1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.movie-select-title {
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
|
||||||
|
.movie-select-alias {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.movie-select-date,
|
||||||
|
.movie-select-studio {
|
||||||
|
color: $text-muted;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,9 +36,7 @@ import { TruncatedText } from "../Shared/TruncatedText";
|
|||||||
|
|
||||||
export type Scene = Pick<GQL.Scene, "id" | "title" | "date" | "code"> & {
|
export type Scene = Pick<GQL.Scene, "id" | "title" | "date" | "code"> & {
|
||||||
studio?: Pick<GQL.Studio, "name"> | null;
|
studio?: Pick<GQL.Studio, "name"> | null;
|
||||||
} & {
|
|
||||||
files?: Pick<GQL.VideoFile, "path">[];
|
files?: Pick<GQL.VideoFile, "path">[];
|
||||||
} & {
|
|
||||||
paths?: Pick<GQL.ScenePathsType, "screenshot">;
|
paths?: Pick<GQL.ScenePathsType, "screenshot">;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export const ScrapedMoviesRow: React.FC<
|
|||||||
const value = resultValue ?? [];
|
const value = resultValue ?? [];
|
||||||
|
|
||||||
const selectValue = value.map((p) => {
|
const selectValue = value.map((p) => {
|
||||||
const aliases: string[] = [];
|
const aliases: string = "";
|
||||||
return {
|
return {
|
||||||
id: p.stored_id ?? "",
|
id: p.stored_id ?? "",
|
||||||
name: p.name ?? "",
|
name: p.name ?? "",
|
||||||
|
|||||||
Reference in New Issue
Block a user