mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user