performer: scrape dialog: allow selecting from multiple images (#3965)

* performer: scrape dialog: allow selecting from multiple images
* Hide selector for single images
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
StashPRs
2023-08-01 00:02:10 -05:00
committed by GitHub
parent 50db9466cb
commit 4311e56109
2 changed files with 174 additions and 3 deletions

View File

@@ -5,7 +5,7 @@ import {
ScrapeDialog, ScrapeDialog,
ScrapeResult, ScrapeResult,
ScrapedInputGroupRow, ScrapedInputGroupRow,
ScrapedImageRow, ScrapedImagesRow,
ScrapeDialogRow, ScrapeDialogRow,
ScrapedTextAreaRow, ScrapedTextAreaRow,
ScrapedCountryRow, ScrapedCountryRow,
@@ -414,6 +414,11 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
) )
); );
const images =
props.scraped.images && props.scraped.images.length > 0
? props.scraped.images
: [];
const allFields = [ const allFields = [
name, name,
disambiguation, disambiguation,
@@ -646,10 +651,11 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
newTags, newTags,
createNewTag createNewTag
)} )}
<ScrapedImageRow <ScrapedImagesRow
title={intl.formatMessage({ id: "performer_image" })} title={intl.formatMessage({ id: "performer_image" })}
className="performer-image" className="performer-image"
result={image} result={image}
images={images}
onChange={(value) => setImage(value)} onChange={(value) => setImage(value)}
/> />
<ScrapedInputGroupRow <ScrapedInputGroupRow

View File

@@ -1,4 +1,4 @@
import React from "react"; import React, { useState } from "react";
import { import {
Form, Form,
Col, Col,
@@ -15,6 +15,8 @@ import isEqual from "lodash-es/isEqual";
import clone from "lodash-es/clone"; import clone from "lodash-es/clone";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { import {
faArrowLeft,
faArrowRight,
faCheck, faCheck,
faPencilAlt, faPencilAlt,
faPlus, faPlus,
@@ -441,6 +443,169 @@ export const ScrapedImageRow: React.FC<IScrapedImageRowProps> = (props) => {
); );
}; };
interface IScrapedImageDialogRowProps<
T extends ScrapeResult<string>,
V extends IHasName
> extends IScrapedFieldProps<string> {
title: string;
renderOriginalField: () => JSX.Element | undefined;
renderNewField: () => JSX.Element | undefined;
onChange: (value: T) => void;
newValues?: V[];
images: string[];
onCreateNew?: (index: number) => void;
}
export const ScrapeImageDialogRow = <
T extends ScrapeResult<string>,
V extends IHasName
>(
props: IScrapedImageDialogRowProps<T, V>
) => {
const [imageIndex, setImageIndex] = useState(0);
function hasNewValues() {
return props.newValues && props.newValues.length > 0 && props.onCreateNew;
}
function setPrev() {
let newIdx = imageIndex - 1;
if (newIdx < 0) {
newIdx = props.images.length - 1;
}
const ret = props.result.cloneWithValue(props.images[newIdx]);
props.onChange(ret as T);
setImageIndex(newIdx);
}
function setNext() {
let newIdx = imageIndex + 1;
if (newIdx >= props.images.length) {
newIdx = 0;
}
const ret = props.result.cloneWithValue(props.images[newIdx]);
props.onChange(ret as T);
setImageIndex(newIdx);
}
if (!props.result.scraped && !hasNewValues()) {
return <></>;
}
function renderSelector() {
return (
props.images.length > 1 && (
<div className="d-flex mt-2 image-selection">
<Button onClick={setPrev}>
<Icon icon={faArrowLeft} />
</Button>
<h5 className="flex-grow-1 px-2">
Select performer image
<br />
{imageIndex + 1} of {props.images.length}
</h5>
<Button onClick={setNext}>
<Icon icon={faArrowRight} />
</Button>
</div>
)
);
}
function renderNewValues() {
if (!hasNewValues()) {
return;
}
const ret = (
<>
{props.newValues!.map((t, i) => (
<Badge
className="tag-item"
variant="secondary"
key={t.name}
onClick={() => props.onCreateNew!(i)}
>
{t.name}
<Button className="minimal ml-2">
<Icon className="fa-fw" icon={faPlus} />
</Button>
</Badge>
))}
</>
);
const minCollapseLength = 10;
if (props.newValues!.length >= minCollapseLength) {
return (
<CollapseButton text={`Missing (${props.newValues!.length})`}>
{ret}
</CollapseButton>
);
}
return ret;
}
return (
<Row className="px-3 pt-3">
<Form.Label column lg="3">
{props.title}
</Form.Label>
<Col lg="9">
<Row>
<Col xs="6">
<InputGroup>{props.renderOriginalField()}</InputGroup>
</Col>
<Col xs="6">
<InputGroup>
{props.renderNewField()}
{renderSelector()}
</InputGroup>
{renderNewValues()}
</Col>
</Row>
</Col>
</Row>
);
};
interface IScrapedImagesRowProps {
title: string;
className?: string;
result: ScrapeResult<string>;
images: string[];
onChange: (value: ScrapeResult<string>) => void;
}
export const ScrapedImagesRow: React.FC<IScrapedImagesRowProps> = (props) => {
return (
<ScrapeImageDialogRow
title={props.title}
result={props.result}
images={props.images}
renderOriginalField={() => (
<ScrapedImage
result={props.result}
className={props.className}
placeholder={props.title}
/>
)}
renderNewField={() => (
<ScrapedImage
result={props.result}
className={props.className}
placeholder={props.title}
isNew
/>
)}
onChange={props.onChange}
/>
);
};
interface IScrapeDialogProps { interface IScrapeDialogProps {
title: string; title: string;
existingLabel?: string; existingLabel?: string;