mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 20:07:05 +03:00
Refactor and restyle scrape dialog on smaller viewports (#6387)
* Improve string-list-input styling * Rename ScrapedDialog file * Move ScrapeDialog into separate file * Refactor scrape dialog row inputs * Refactor new value handling * Add context for labels * Refactor scrape dialog to accept children * Add existing/scraped labels for smaller viewports
This commit is contained in:
@@ -2,11 +2,11 @@ import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import {
|
||||
ScrapeDialog,
|
||||
ScrapedInputGroupRow,
|
||||
ScrapedStringListRow,
|
||||
ScrapedTextAreaRow,
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialogRow";
|
||||
import { ScrapeDialog } from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
import {
|
||||
ObjectListScrapeResult,
|
||||
ObjectScrapeResult,
|
||||
@@ -225,10 +225,11 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
||||
{ id: "dialogs.scrape_entity_title" },
|
||||
{ entity_type: intl.formatMessage({ id: "gallery" }) }
|
||||
)}
|
||||
renderScrapeRows={renderScrapeRows}
|
||||
onClose={(apply) => {
|
||||
onClose(apply ? makeNewScrapedItem() : undefined);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{renderScrapeRows()}
|
||||
</ScrapeDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,12 +2,12 @@ import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import {
|
||||
ScrapeDialog,
|
||||
ScrapedInputGroupRow,
|
||||
ScrapedImageRow,
|
||||
ScrapedTextAreaRow,
|
||||
ScrapedStringListRow,
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialogRow";
|
||||
import { ScrapeDialog } from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
import TextUtils from "src/utils/text";
|
||||
import {
|
||||
ObjectScrapeResult,
|
||||
@@ -224,10 +224,11 @@ export const GroupScrapeDialog: React.FC<IGroupScrapeDialogProps> = ({
|
||||
{ id: "dialogs.scrape_entity_title" },
|
||||
{ entity_type: intl.formatMessage({ id: "group" }) }
|
||||
)}
|
||||
renderScrapeRows={renderScrapeRows}
|
||||
onClose={(apply) => {
|
||||
onClose(apply ? makeNewScrapedItem() : undefined);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{renderScrapeRows()}
|
||||
</ScrapeDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,11 +2,11 @@ import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import {
|
||||
ScrapeDialog,
|
||||
ScrapedInputGroupRow,
|
||||
ScrapedStringListRow,
|
||||
ScrapedTextAreaRow,
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialogRow";
|
||||
import { ScrapeDialog } from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
import {
|
||||
ObjectListScrapeResult,
|
||||
ObjectScrapeResult,
|
||||
@@ -226,10 +226,11 @@ export const ImageScrapeDialog: React.FC<IImageScrapeDialogProps> = ({
|
||||
{ id: "dialogs.scrape_entity_title" },
|
||||
{ entity_type: intl.formatMessage({ id: "image" }) }
|
||||
)}
|
||||
renderScrapeRows={renderScrapeRows}
|
||||
onClose={(apply) => {
|
||||
onClose(apply ? makeNewScrapedItem() : undefined);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{renderScrapeRows()}
|
||||
</ScrapeDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,14 +2,14 @@ import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import {
|
||||
ScrapeDialog,
|
||||
ScrapedInputGroupRow,
|
||||
ScrapedImagesRow,
|
||||
ScrapeDialogRow,
|
||||
ScrapedTextAreaRow,
|
||||
ScrapedCountryRow,
|
||||
ScrapedStringListRow,
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialogRow";
|
||||
import { ScrapeDialog } from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
import { Form } from "react-bootstrap";
|
||||
import {
|
||||
genderStrings,
|
||||
@@ -66,12 +66,10 @@ function renderScrapedGenderRow(
|
||||
field="gender"
|
||||
title={title}
|
||||
result={result}
|
||||
renderOriginalField={() => renderScrapedGender(result)}
|
||||
renderNewField={() =>
|
||||
renderScrapedGender(result, true, (value) =>
|
||||
onChange(result.cloneWithValue(value))
|
||||
)
|
||||
}
|
||||
originalField={renderScrapedGender(result)}
|
||||
newField={renderScrapedGender(result, true, (value) =>
|
||||
onChange(result.cloneWithValue(value))
|
||||
)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
@@ -116,12 +114,10 @@ function renderScrapedCircumcisedRow(
|
||||
title={title}
|
||||
field="circumcised"
|
||||
result={result}
|
||||
renderOriginalField={() => renderScrapedCircumcised(result)}
|
||||
renderNewField={() =>
|
||||
renderScrapedCircumcised(result, true, (value) =>
|
||||
onChange(result.cloneWithValue(value))
|
||||
)
|
||||
}
|
||||
originalField={renderScrapedCircumcised(result)}
|
||||
newField={renderScrapedCircumcised(result, true, (value) =>
|
||||
onChange(result.cloneWithValue(value))
|
||||
)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
@@ -552,10 +548,11 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
||||
{ id: "dialogs.scrape_entity_title" },
|
||||
{ entity_type: intl.formatMessage({ id: "performer" }) }
|
||||
)}
|
||||
renderScrapeRows={renderScrapeRows}
|
||||
onClose={(apply) => {
|
||||
props.onClose(apply ? makeNewScrapedItem() : undefined);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{renderScrapeRows()}
|
||||
</ScrapeDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { useState } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import {
|
||||
ScrapeDialog,
|
||||
ScrapedInputGroupRow,
|
||||
ScrapedTextAreaRow,
|
||||
ScrapedImageRow,
|
||||
ScrapedStringListRow,
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialogRow";
|
||||
import { ScrapeDialog } from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
import { useIntl } from "react-intl";
|
||||
import { uniq } from "lodash-es";
|
||||
import { Performer } from "src/components/Performers/PerformerSelect";
|
||||
@@ -304,11 +304,12 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||
{ id: "dialogs.scrape_entity_title" },
|
||||
{ entity_type: intl.formatMessage({ id: "scene" }) }
|
||||
)}
|
||||
renderScrapeRows={renderScrapeRows}
|
||||
onClose={(apply) => {
|
||||
onClose(apply ? makeNewScrapedItem() : undefined);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{renderScrapeRows()}
|
||||
</ScrapeDialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useToast } from "src/hooks/Toast";
|
||||
import { faExchangeAlt, faSignInAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
ScrapeDialog,
|
||||
ScrapeDialogRow,
|
||||
ScrapedImageRow,
|
||||
ScrapedInputGroupRow,
|
||||
ScrapedStringListRow,
|
||||
ScrapedTextAreaRow,
|
||||
} from "../Shared/ScrapeDialog/ScrapeDialog";
|
||||
} from "../Shared/ScrapeDialog/ScrapeDialogRow";
|
||||
import { ScrapeDialog } from "../Shared/ScrapeDialog/ScrapeDialog";
|
||||
import { clone, uniq } from "lodash-es";
|
||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||
import { ModalComponent } from "../Shared/Modal";
|
||||
@@ -400,63 +400,59 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
field="rating"
|
||||
title={intl.formatMessage({ id: "rating" })}
|
||||
result={rating}
|
||||
renderOriginalField={() => (
|
||||
<RatingSystem value={rating.originalValue} disabled />
|
||||
)}
|
||||
renderNewField={() => (
|
||||
<RatingSystem value={rating.newValue} disabled />
|
||||
)}
|
||||
originalField={<RatingSystem value={rating.originalValue} disabled />}
|
||||
newField={<RatingSystem value={rating.newValue} disabled />}
|
||||
onChange={(value) => setRating(value)}
|
||||
/>
|
||||
<ScrapeDialogRow
|
||||
field="o_count"
|
||||
title={intl.formatMessage({ id: "o_count" })}
|
||||
result={oCounter}
|
||||
renderOriginalField={() => (
|
||||
originalField={
|
||||
<FormControl
|
||||
value={oCounter.originalValue ?? 0}
|
||||
readOnly
|
||||
onChange={() => {}}
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
)}
|
||||
renderNewField={() => (
|
||||
}
|
||||
newField={
|
||||
<FormControl
|
||||
value={oCounter.newValue ?? 0}
|
||||
readOnly
|
||||
onChange={() => {}}
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
)}
|
||||
}
|
||||
onChange={(value) => setOCounter(value)}
|
||||
/>
|
||||
<ScrapeDialogRow
|
||||
field="play_count"
|
||||
title={intl.formatMessage({ id: "play_count" })}
|
||||
result={playCount}
|
||||
renderOriginalField={() => (
|
||||
originalField={
|
||||
<FormControl
|
||||
value={playCount.originalValue ?? 0}
|
||||
readOnly
|
||||
onChange={() => {}}
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
)}
|
||||
renderNewField={() => (
|
||||
}
|
||||
newField={
|
||||
<FormControl
|
||||
value={playCount.newValue ?? 0}
|
||||
readOnly
|
||||
onChange={() => {}}
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
)}
|
||||
}
|
||||
onChange={(value) => setPlayCount(value)}
|
||||
/>
|
||||
<ScrapeDialogRow
|
||||
field="play_duration"
|
||||
title={intl.formatMessage({ id: "play_duration" })}
|
||||
result={playDuration}
|
||||
renderOriginalField={() => (
|
||||
originalField={
|
||||
<FormControl
|
||||
value={TextUtils.secondsToTimestamp(
|
||||
playDuration.originalValue ?? 0
|
||||
@@ -465,22 +461,22 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
onChange={() => {}}
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
)}
|
||||
renderNewField={() => (
|
||||
}
|
||||
newField={
|
||||
<FormControl
|
||||
value={TextUtils.secondsToTimestamp(playDuration.newValue ?? 0)}
|
||||
readOnly
|
||||
onChange={() => {}}
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
)}
|
||||
}
|
||||
onChange={(value) => setPlayDuration(value)}
|
||||
/>
|
||||
<ScrapeDialogRow
|
||||
field="galleries"
|
||||
title={intl.formatMessage({ id: "galleries" })}
|
||||
result={galleries}
|
||||
renderOriginalField={() => (
|
||||
originalField={
|
||||
<GallerySelect
|
||||
className="form-control react-select"
|
||||
ids={galleries.originalValue ?? []}
|
||||
@@ -488,8 +484,8 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
isMulti
|
||||
isDisabled
|
||||
/>
|
||||
)}
|
||||
renderNewField={() => (
|
||||
}
|
||||
newField={
|
||||
<GallerySelect
|
||||
className="form-control react-select"
|
||||
ids={galleries.newValue ?? []}
|
||||
@@ -497,7 +493,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
isMulti
|
||||
isDisabled
|
||||
/>
|
||||
)}
|
||||
}
|
||||
onChange={(value) => setGalleries(value)}
|
||||
/>
|
||||
<ScrapedStudioRow
|
||||
@@ -535,34 +531,32 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
field="organized"
|
||||
title={intl.formatMessage({ id: "organized" })}
|
||||
result={organized}
|
||||
renderOriginalField={() => (
|
||||
originalField={
|
||||
<FormControl
|
||||
value={organized.originalValue ? trueString : falseString}
|
||||
readOnly
|
||||
onChange={() => {}}
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
)}
|
||||
renderNewField={() => (
|
||||
}
|
||||
newField={
|
||||
<FormControl
|
||||
value={organized.newValue ? trueString : falseString}
|
||||
readOnly
|
||||
onChange={() => {}}
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
)}
|
||||
}
|
||||
onChange={(value) => setOrganized(value)}
|
||||
/>
|
||||
<ScrapeDialogRow
|
||||
field="stash_ids"
|
||||
title={intl.formatMessage({ id: "stash_id" })}
|
||||
result={stashIDs}
|
||||
renderOriginalField={() => (
|
||||
originalField={
|
||||
<StashIDsField values={stashIDs?.originalValue ?? []} />
|
||||
)}
|
||||
renderNewField={() => (
|
||||
<StashIDsField values={stashIDs?.newValue ?? []} />
|
||||
)}
|
||||
}
|
||||
newField={<StashIDsField values={stashIDs?.newValue ?? []} />}
|
||||
onChange={(value) => setStashIDs(value)}
|
||||
/>
|
||||
<ScrapedImageRow
|
||||
@@ -634,7 +628,6 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
title={dialogTitle}
|
||||
existingLabel={destinationLabel}
|
||||
scrapedLabel={sourceLabel}
|
||||
renderScrapeRows={renderScrapeRows}
|
||||
onClose={(apply) => {
|
||||
if (!apply) {
|
||||
onClose();
|
||||
@@ -642,7 +635,9 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
onClose(createValues());
|
||||
}
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{renderScrapeRows()}
|
||||
</ScrapeDialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,457 +1,55 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Form,
|
||||
Col,
|
||||
Row,
|
||||
InputGroup,
|
||||
Button,
|
||||
FormControl,
|
||||
Badge,
|
||||
} from "react-bootstrap";
|
||||
import { CollapseButton } from "../CollapseButton";
|
||||
import { Icon } from "../Icon";
|
||||
import React, { useMemo } from "react";
|
||||
import { Form, Col, Row } from "react-bootstrap";
|
||||
import { ModalComponent } from "../Modal";
|
||||
import clone from "lodash-es/clone";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import {
|
||||
faCheck,
|
||||
faPencilAlt,
|
||||
faPlus,
|
||||
faTimes,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { getCountryByISO } from "src/utils/country";
|
||||
import { CountrySelect } from "../CountrySelect";
|
||||
import { StringListInput } from "../StringListInput";
|
||||
import { ImageSelector } from "../ImageSelector";
|
||||
import { ScrapeResult } from "./scrapeResult";
|
||||
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useConfigurationContext } from "src/hooks/Config";
|
||||
|
||||
interface IScrapedFieldProps<T> {
|
||||
result: ScrapeResult<T>;
|
||||
export interface IScrapeDialogContextState {
|
||||
existingLabel?: React.ReactNode;
|
||||
scrapedLabel?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface IScrapedRowProps<T, V> extends IScrapedFieldProps<T> {
|
||||
className?: string;
|
||||
field: string;
|
||||
title: string;
|
||||
renderOriginalField: (result: ScrapeResult<T>) => JSX.Element | undefined;
|
||||
renderNewField: (result: ScrapeResult<T>) => JSX.Element | undefined;
|
||||
onChange: (value: ScrapeResult<T>) => void;
|
||||
newValues?: V[];
|
||||
onCreateNew?: (index: number) => void;
|
||||
getName?: (value: V) => string;
|
||||
}
|
||||
|
||||
function renderButtonIcon(selected: boolean) {
|
||||
const className = selected ? "text-success" : "text-muted";
|
||||
|
||||
return (
|
||||
<Icon
|
||||
className={`fa-fw ${className}`}
|
||||
icon={selected ? faCheck : faTimes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const ScrapeDialogRow = <T, V>(props: IScrapedRowProps<T, V>) => {
|
||||
const { getName = () => "" } = props;
|
||||
|
||||
function handleSelectClick(isNew: boolean) {
|
||||
const ret = clone(props.result);
|
||||
ret.useNewValue = isNew;
|
||||
props.onChange(ret);
|
||||
}
|
||||
|
||||
function hasNewValues() {
|
||||
return props.newValues && props.newValues.length > 0 && props.onCreateNew;
|
||||
}
|
||||
|
||||
if (!props.result.scraped && !hasNewValues()) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
function renderNewValues() {
|
||||
if (!hasNewValues()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ret = (
|
||||
<>
|
||||
{props.newValues!.map((t, i) => (
|
||||
<Badge
|
||||
className="tag-item"
|
||||
variant="secondary"
|
||||
key={getName(t)}
|
||||
onClick={() => props.onCreateNew!(i)}
|
||||
>
|
||||
{getName(t)}
|
||||
<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 ${props.className ?? ""}`}
|
||||
data-field={props.field}
|
||||
>
|
||||
<Form.Label column lg="3">
|
||||
{props.title}
|
||||
</Form.Label>
|
||||
|
||||
<Col lg="9">
|
||||
<Row>
|
||||
<Col xs="6">
|
||||
<InputGroup>
|
||||
<InputGroup.Prepend className="bg-secondary text-white border-secondary">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => handleSelectClick(false)}
|
||||
>
|
||||
{renderButtonIcon(!props.result.useNewValue)}
|
||||
</Button>
|
||||
</InputGroup.Prepend>
|
||||
{props.renderOriginalField(props.result)}
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<Col xs="6">
|
||||
<InputGroup>
|
||||
<InputGroup.Prepend>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => handleSelectClick(true)}
|
||||
>
|
||||
{renderButtonIcon(props.result.useNewValue)}
|
||||
</Button>
|
||||
</InputGroup.Prepend>
|
||||
{props.renderNewField(props.result)}
|
||||
</InputGroup>
|
||||
{renderNewValues()}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedInputGroupProps {
|
||||
isNew?: boolean;
|
||||
placeholder?: string;
|
||||
locked?: boolean;
|
||||
result: ScrapeResult<string>;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const ScrapedInputGroup: React.FC<IScrapedInputGroupProps> = (props) => {
|
||||
return (
|
||||
<FormControl
|
||||
placeholder={props.placeholder}
|
||||
value={props.isNew ? props.result.newValue : props.result.originalValue}
|
||||
readOnly={!props.isNew || props.locked}
|
||||
onChange={(e) => {
|
||||
if (props.isNew && props.onChange) {
|
||||
props.onChange(e.target.value);
|
||||
}
|
||||
}}
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function getNameString(value: string) {
|
||||
return value;
|
||||
}
|
||||
|
||||
interface IScrapedInputGroupRowProps {
|
||||
title: string;
|
||||
field: string;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
result: ScrapeResult<string>;
|
||||
locked?: boolean;
|
||||
onChange: (value: ScrapeResult<string>) => void;
|
||||
}
|
||||
|
||||
export const ScrapedInputGroupRow: React.FC<IScrapedInputGroupRowProps> = (
|
||||
props
|
||||
) => {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
title={props.title}
|
||||
field={props.field}
|
||||
className={props.className}
|
||||
result={props.result}
|
||||
renderOriginalField={() => (
|
||||
<ScrapedInputGroup
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
/>
|
||||
)}
|
||||
renderNewField={() => (
|
||||
<ScrapedInputGroup
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
isNew
|
||||
locked={props.locked}
|
||||
onChange={(value) =>
|
||||
props.onChange(props.result.cloneWithValue(value))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
onChange={props.onChange}
|
||||
getName={getNameString}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedStringListProps {
|
||||
isNew?: boolean;
|
||||
placeholder?: string;
|
||||
locked?: boolean;
|
||||
result: ScrapeResult<string[]>;
|
||||
onChange?: (value: string[]) => void;
|
||||
}
|
||||
|
||||
const ScrapedStringList: React.FC<IScrapedStringListProps> = (props) => {
|
||||
const value = props.isNew
|
||||
? props.result.newValue
|
||||
: props.result.originalValue;
|
||||
|
||||
return (
|
||||
<StringListInput
|
||||
value={value ?? []}
|
||||
setValue={(v) => {
|
||||
if (props.isNew && props.onChange) {
|
||||
props.onChange(v);
|
||||
}
|
||||
}}
|
||||
placeholder={props.placeholder}
|
||||
readOnly={!props.isNew || props.locked}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedStringListRowProps {
|
||||
title: string;
|
||||
field: string;
|
||||
placeholder?: string;
|
||||
result: ScrapeResult<string[]>;
|
||||
locked?: boolean;
|
||||
onChange: (value: ScrapeResult<string[]>) => void;
|
||||
}
|
||||
|
||||
export const ScrapedStringListRow: React.FC<IScrapedStringListRowProps> = (
|
||||
props
|
||||
) => {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
className="string-list-row"
|
||||
title={props.title}
|
||||
field={props.field}
|
||||
result={props.result}
|
||||
renderOriginalField={() => (
|
||||
<ScrapedStringList
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
/>
|
||||
)}
|
||||
renderNewField={() => (
|
||||
<ScrapedStringList
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
isNew
|
||||
locked={props.locked}
|
||||
onChange={(value) =>
|
||||
props.onChange(props.result.cloneWithValue(value))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
onChange={props.onChange}
|
||||
getName={getNameString}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ScrapedTextArea: React.FC<IScrapedInputGroupProps> = (props) => {
|
||||
return (
|
||||
<FormControl
|
||||
as="textarea"
|
||||
placeholder={props.placeholder}
|
||||
value={props.isNew ? props.result.newValue : props.result.originalValue}
|
||||
readOnly={!props.isNew}
|
||||
onChange={(e) => {
|
||||
if (props.isNew && props.onChange) {
|
||||
props.onChange(e.target.value);
|
||||
}
|
||||
}}
|
||||
className="bg-secondary text-white border-secondary scene-description"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ScrapedTextAreaRow: React.FC<IScrapedInputGroupRowProps> = (
|
||||
props
|
||||
) => {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
title={props.title}
|
||||
field={props.field}
|
||||
result={props.result}
|
||||
renderOriginalField={() => (
|
||||
<ScrapedTextArea
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
/>
|
||||
)}
|
||||
renderNewField={() => (
|
||||
<ScrapedTextArea
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
isNew
|
||||
onChange={(value) =>
|
||||
props.onChange(props.result.cloneWithValue(value))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
onChange={props.onChange}
|
||||
getName={getNameString}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedImageProps {
|
||||
isNew?: boolean;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
result: ScrapeResult<string>;
|
||||
}
|
||||
|
||||
const ScrapedImage: React.FC<IScrapedImageProps> = (props) => {
|
||||
const value = props.isNew
|
||||
? props.result.newValue
|
||||
: props.result.originalValue;
|
||||
|
||||
if (!value) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<img className={props.className} src={value} alt={props.placeholder} />
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedImageRowProps {
|
||||
title: string;
|
||||
field: string;
|
||||
className?: string;
|
||||
result: ScrapeResult<string>;
|
||||
onChange: (value: ScrapeResult<string>) => void;
|
||||
}
|
||||
|
||||
export const ScrapedImageRow: React.FC<IScrapedImageRowProps> = (props) => {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
title={props.title}
|
||||
field={props.field}
|
||||
result={props.result}
|
||||
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}
|
||||
getName={getNameString}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedImagesRowProps {
|
||||
title: string;
|
||||
field: string;
|
||||
className?: string;
|
||||
result: ScrapeResult<string>;
|
||||
images: string[];
|
||||
onChange: (value: ScrapeResult<string>) => void;
|
||||
}
|
||||
|
||||
export const ScrapedImagesRow: React.FC<IScrapedImagesRowProps> = (props) => {
|
||||
const [imageIndex, setImageIndex] = useState(0);
|
||||
|
||||
function onSetImageIndex(newIdx: number) {
|
||||
const ret = props.result.cloneWithValue(props.images[newIdx]);
|
||||
props.onChange(ret);
|
||||
setImageIndex(newIdx);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
title={props.title}
|
||||
field={props.field}
|
||||
result={props.result}
|
||||
renderOriginalField={() => (
|
||||
<ScrapedImage
|
||||
result={props.result}
|
||||
className={props.className}
|
||||
placeholder={props.title}
|
||||
/>
|
||||
)}
|
||||
renderNewField={() => (
|
||||
<div className="image-selection-parent">
|
||||
<ImageSelector
|
||||
imageClassName={props.className}
|
||||
images={props.images}
|
||||
imageIndex={imageIndex}
|
||||
setImageIndex={onSetImageIndex}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
onChange={props.onChange}
|
||||
getName={getNameString}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const ScrapeDialogContext =
|
||||
React.createContext<IScrapeDialogContextState>({});
|
||||
|
||||
interface IScrapeDialogProps {
|
||||
title: string;
|
||||
existingLabel?: string;
|
||||
scrapedLabel?: string;
|
||||
renderScrapeRows: () => JSX.Element;
|
||||
existingLabel?: React.ReactNode;
|
||||
scrapedLabel?: React.ReactNode;
|
||||
onClose: (apply?: boolean) => void;
|
||||
}
|
||||
|
||||
export const ScrapeDialog: React.FC<IScrapeDialogProps> = (
|
||||
props: IScrapeDialogProps
|
||||
) => {
|
||||
export const ScrapeDialog: React.FC<
|
||||
React.PropsWithChildren<IScrapeDialogProps>
|
||||
> = (props: React.PropsWithChildren<IScrapeDialogProps>) => {
|
||||
const intl = useIntl();
|
||||
const { configuration } = useConfigurationContext();
|
||||
const { sfwContentMode } = configuration.interface;
|
||||
|
||||
const existingLabel = useMemo(
|
||||
() =>
|
||||
props.existingLabel ?? (
|
||||
<FormattedMessage id="dialogs.scrape_results_existing" />
|
||||
),
|
||||
[props.existingLabel]
|
||||
);
|
||||
const scrapedLabel = useMemo(
|
||||
() =>
|
||||
props.scrapedLabel ?? (
|
||||
<FormattedMessage id="dialogs.scrape_results_scraped" />
|
||||
),
|
||||
[props.scrapedLabel]
|
||||
);
|
||||
|
||||
const contextState = useMemo(
|
||||
() => ({
|
||||
existingLabel: existingLabel,
|
||||
scrapedLabel: scrapedLabel,
|
||||
}),
|
||||
[existingLabel, scrapedLabel]
|
||||
);
|
||||
|
||||
return (
|
||||
<ModalComponent
|
||||
show
|
||||
@@ -474,76 +72,33 @@ export const ScrapeDialog: React.FC<IScrapeDialogProps> = (
|
||||
}}
|
||||
>
|
||||
<div className="dialog-container">
|
||||
<Form>
|
||||
<Row className="px-3 pt-3">
|
||||
<Col lg={{ span: 9, offset: 3 }}>
|
||||
<Row>
|
||||
<Form.Label column xs="6">
|
||||
{props.existingLabel ?? (
|
||||
<FormattedMessage id="dialogs.scrape_results_existing" />
|
||||
)}
|
||||
</Form.Label>
|
||||
<Form.Label column xs="6">
|
||||
{props.scrapedLabel ?? (
|
||||
<FormattedMessage id="dialogs.scrape_results_scraped" />
|
||||
)}
|
||||
</Form.Label>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<ScrapeDialogContext.Provider value={contextState}>
|
||||
<Form>
|
||||
<Row className="px-3 pt-3">
|
||||
<Col lg={{ span: 9, offset: 3 }}>
|
||||
<Row>
|
||||
<Form.Label
|
||||
column
|
||||
lg="6"
|
||||
className="d-lg-block d-none column-label"
|
||||
>
|
||||
{existingLabel}
|
||||
</Form.Label>
|
||||
<Form.Label
|
||||
column
|
||||
lg="6"
|
||||
className="d-lg-block d-none column-label"
|
||||
>
|
||||
{scrapedLabel}
|
||||
</Form.Label>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{props.renderScrapeRows()}
|
||||
</Form>
|
||||
{props.children}
|
||||
</Form>
|
||||
</ScrapeDialogContext.Provider>
|
||||
</div>
|
||||
</ModalComponent>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedCountryRowProps {
|
||||
title: string;
|
||||
field: string;
|
||||
result: ScrapeResult<string>;
|
||||
onChange: (value: ScrapeResult<string>) => void;
|
||||
locked?: boolean;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export const ScrapedCountryRow: React.FC<IScrapedCountryRowProps> = ({
|
||||
title,
|
||||
field,
|
||||
result,
|
||||
onChange,
|
||||
locked,
|
||||
locale,
|
||||
}) => (
|
||||
<ScrapeDialogRow
|
||||
title={title}
|
||||
field={field}
|
||||
result={result}
|
||||
renderOriginalField={() => (
|
||||
<FormControl
|
||||
value={
|
||||
getCountryByISO(result.originalValue, locale) ?? result.originalValue
|
||||
}
|
||||
readOnly
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
)}
|
||||
renderNewField={() => (
|
||||
<CountrySelect
|
||||
value={result.newValue}
|
||||
disabled={locked}
|
||||
onChange={(value) => {
|
||||
if (onChange) {
|
||||
onChange(result.cloneWithValue(value));
|
||||
}
|
||||
}}
|
||||
showFlag={false}
|
||||
isClearable={false}
|
||||
className="flex-grow-1"
|
||||
/>
|
||||
)}
|
||||
onChange={onChange}
|
||||
getName={getNameString}
|
||||
/>
|
||||
);
|
||||
|
||||
433
ui/v2.5/src/components/Shared/ScrapeDialog/ScrapeDialogRow.tsx
Normal file
433
ui/v2.5/src/components/Shared/ScrapeDialog/ScrapeDialogRow.tsx
Normal file
@@ -0,0 +1,433 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import {
|
||||
Form,
|
||||
Col,
|
||||
Row,
|
||||
InputGroup,
|
||||
Button,
|
||||
FormControl,
|
||||
} from "react-bootstrap";
|
||||
import { Icon } from "../Icon";
|
||||
import clone from "lodash-es/clone";
|
||||
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
import { getCountryByISO } from "src/utils/country";
|
||||
import { CountrySelect } from "../CountrySelect";
|
||||
import { StringListInput } from "../StringListInput";
|
||||
import { ImageSelector } from "../ImageSelector";
|
||||
import { ScrapeResult } from "./scrapeResult";
|
||||
import { ScrapeDialogContext } from "./ScrapeDialog";
|
||||
|
||||
function renderButtonIcon(selected: boolean) {
|
||||
const className = selected ? "text-success" : "text-muted";
|
||||
|
||||
return (
|
||||
<Icon
|
||||
className={`fa-fw ${className}`}
|
||||
icon={selected ? faCheck : faTimes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface IScrapedFieldProps<T> {
|
||||
result: ScrapeResult<T>;
|
||||
}
|
||||
|
||||
interface IScrapedRowProps<T> extends IScrapedFieldProps<T> {
|
||||
className?: string;
|
||||
field: string;
|
||||
title: string;
|
||||
originalField: React.ReactNode;
|
||||
newField: React.ReactNode;
|
||||
onChange: (value: ScrapeResult<T>) => void;
|
||||
newValues?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ScrapeDialogRow = <T,>(props: IScrapedRowProps<T>) => {
|
||||
const { existingLabel, scrapedLabel } = useContext(ScrapeDialogContext);
|
||||
|
||||
function handleSelectClick(isNew: boolean) {
|
||||
const ret = clone(props.result);
|
||||
ret.useNewValue = isNew;
|
||||
props.onChange(ret);
|
||||
}
|
||||
|
||||
if (!props.result.scraped && !props.newValues) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Row
|
||||
className={`px-3 pt-3 ${props.className ?? ""}`}
|
||||
data-field={props.field}
|
||||
>
|
||||
<Form.Label column lg="3">
|
||||
{props.title}
|
||||
</Form.Label>
|
||||
|
||||
<Col lg="9">
|
||||
<Row>
|
||||
<Form.Label column className="d-lg-none column-label">
|
||||
{existingLabel}
|
||||
</Form.Label>
|
||||
<Col lg="6">
|
||||
<InputGroup>
|
||||
<InputGroup.Prepend className="bg-secondary text-white border-secondary">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => handleSelectClick(false)}
|
||||
>
|
||||
{renderButtonIcon(!props.result.useNewValue)}
|
||||
</Button>
|
||||
</InputGroup.Prepend>
|
||||
{props.originalField}
|
||||
</InputGroup>
|
||||
</Col>
|
||||
|
||||
<Form.Label column className="d-lg-none column-label">
|
||||
{scrapedLabel}
|
||||
</Form.Label>
|
||||
<Col lg="6">
|
||||
<InputGroup>
|
||||
<InputGroup.Prepend>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => handleSelectClick(true)}
|
||||
>
|
||||
{renderButtonIcon(props.result.useNewValue)}
|
||||
</Button>
|
||||
</InputGroup.Prepend>
|
||||
{props.newField}
|
||||
</InputGroup>
|
||||
{props.newValues}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedInputGroupProps {
|
||||
isNew?: boolean;
|
||||
placeholder?: string;
|
||||
locked?: boolean;
|
||||
result: ScrapeResult<string>;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const ScrapedInputGroup: React.FC<IScrapedInputGroupProps> = (props) => {
|
||||
return (
|
||||
<FormControl
|
||||
placeholder={props.placeholder}
|
||||
value={props.isNew ? props.result.newValue : props.result.originalValue}
|
||||
readOnly={!props.isNew || props.locked}
|
||||
onChange={(e) => {
|
||||
if (props.isNew && props.onChange) {
|
||||
props.onChange(e.target.value);
|
||||
}
|
||||
}}
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedInputGroupRowProps {
|
||||
title: string;
|
||||
field: string;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
result: ScrapeResult<string>;
|
||||
locked?: boolean;
|
||||
onChange: (value: ScrapeResult<string>) => void;
|
||||
}
|
||||
|
||||
export const ScrapedInputGroupRow: React.FC<IScrapedInputGroupRowProps> = (
|
||||
props
|
||||
) => {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
title={props.title}
|
||||
field={props.field}
|
||||
className={props.className}
|
||||
result={props.result}
|
||||
originalField={
|
||||
<ScrapedInputGroup
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
/>
|
||||
}
|
||||
newField={
|
||||
<ScrapedInputGroup
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
isNew
|
||||
locked={props.locked}
|
||||
onChange={(value) =>
|
||||
props.onChange(props.result.cloneWithValue(value))
|
||||
}
|
||||
/>
|
||||
}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedStringListProps {
|
||||
isNew?: boolean;
|
||||
placeholder?: string;
|
||||
locked?: boolean;
|
||||
result: ScrapeResult<string[]>;
|
||||
onChange?: (value: string[]) => void;
|
||||
}
|
||||
|
||||
const ScrapedStringList: React.FC<IScrapedStringListProps> = (props) => {
|
||||
const value = props.isNew
|
||||
? props.result.newValue
|
||||
: props.result.originalValue;
|
||||
|
||||
return (
|
||||
<StringListInput
|
||||
value={value ?? []}
|
||||
setValue={(v) => {
|
||||
if (props.isNew && props.onChange) {
|
||||
props.onChange(v);
|
||||
}
|
||||
}}
|
||||
placeholder={props.placeholder}
|
||||
readOnly={!props.isNew || props.locked}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedStringListRowProps {
|
||||
title: string;
|
||||
field: string;
|
||||
placeholder?: string;
|
||||
result: ScrapeResult<string[]>;
|
||||
locked?: boolean;
|
||||
onChange: (value: ScrapeResult<string[]>) => void;
|
||||
}
|
||||
|
||||
export const ScrapedStringListRow: React.FC<IScrapedStringListRowProps> = (
|
||||
props
|
||||
) => {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
className="string-list-row"
|
||||
title={props.title}
|
||||
field={props.field}
|
||||
result={props.result}
|
||||
originalField={
|
||||
<ScrapedStringList
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
/>
|
||||
}
|
||||
newField={
|
||||
<ScrapedStringList
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
isNew
|
||||
locked={props.locked}
|
||||
onChange={(value) =>
|
||||
props.onChange(props.result.cloneWithValue(value))
|
||||
}
|
||||
/>
|
||||
}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ScrapedTextArea: React.FC<IScrapedInputGroupProps> = (props) => {
|
||||
return (
|
||||
<FormControl
|
||||
as="textarea"
|
||||
placeholder={props.placeholder}
|
||||
value={props.isNew ? props.result.newValue : props.result.originalValue}
|
||||
readOnly={!props.isNew}
|
||||
onChange={(e) => {
|
||||
if (props.isNew && props.onChange) {
|
||||
props.onChange(e.target.value);
|
||||
}
|
||||
}}
|
||||
className="bg-secondary text-white border-secondary scene-description"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ScrapedTextAreaRow: React.FC<IScrapedInputGroupRowProps> = (
|
||||
props
|
||||
) => {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
title={props.title}
|
||||
field={props.field}
|
||||
result={props.result}
|
||||
originalField={
|
||||
<ScrapedTextArea
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
/>
|
||||
}
|
||||
newField={
|
||||
<ScrapedTextArea
|
||||
placeholder={props.placeholder || props.title}
|
||||
result={props.result}
|
||||
isNew
|
||||
onChange={(value) =>
|
||||
props.onChange(props.result.cloneWithValue(value))
|
||||
}
|
||||
/>
|
||||
}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedImageProps {
|
||||
isNew?: boolean;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
result: ScrapeResult<string>;
|
||||
}
|
||||
|
||||
const ScrapedImage: React.FC<IScrapedImageProps> = (props) => {
|
||||
const value = props.isNew
|
||||
? props.result.newValue
|
||||
: props.result.originalValue;
|
||||
|
||||
if (!value) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<img className={props.className} src={value} alt={props.placeholder} />
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedImageRowProps {
|
||||
title: string;
|
||||
field: string;
|
||||
className?: string;
|
||||
result: ScrapeResult<string>;
|
||||
onChange: (value: ScrapeResult<string>) => void;
|
||||
}
|
||||
|
||||
export const ScrapedImageRow: React.FC<IScrapedImageRowProps> = (props) => {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
title={props.title}
|
||||
field={props.field}
|
||||
result={props.result}
|
||||
originalField={
|
||||
<ScrapedImage
|
||||
result={props.result}
|
||||
className={props.className}
|
||||
placeholder={props.title}
|
||||
/>
|
||||
}
|
||||
newField={
|
||||
<ScrapedImage
|
||||
result={props.result}
|
||||
className={props.className}
|
||||
placeholder={props.title}
|
||||
isNew
|
||||
/>
|
||||
}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedImagesRowProps {
|
||||
title: string;
|
||||
field: string;
|
||||
className?: string;
|
||||
result: ScrapeResult<string>;
|
||||
images: string[];
|
||||
onChange: (value: ScrapeResult<string>) => void;
|
||||
}
|
||||
|
||||
export const ScrapedImagesRow: React.FC<IScrapedImagesRowProps> = (props) => {
|
||||
const [imageIndex, setImageIndex] = useState(0);
|
||||
|
||||
function onSetImageIndex(newIdx: number) {
|
||||
const ret = props.result.cloneWithValue(props.images[newIdx]);
|
||||
props.onChange(ret);
|
||||
setImageIndex(newIdx);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
title={props.title}
|
||||
field={props.field}
|
||||
result={props.result}
|
||||
originalField={
|
||||
<ScrapedImage
|
||||
result={props.result}
|
||||
className={props.className}
|
||||
placeholder={props.title}
|
||||
/>
|
||||
}
|
||||
newField={
|
||||
<div className="image-selection-parent">
|
||||
<ImageSelector
|
||||
imageClassName={props.className}
|
||||
images={props.images}
|
||||
imageIndex={imageIndex}
|
||||
setImageIndex={onSetImageIndex}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IScrapedCountryRowProps {
|
||||
title: string;
|
||||
field: string;
|
||||
result: ScrapeResult<string>;
|
||||
onChange: (value: ScrapeResult<string>) => void;
|
||||
locked?: boolean;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export const ScrapedCountryRow: React.FC<IScrapedCountryRowProps> = ({
|
||||
title,
|
||||
field,
|
||||
result,
|
||||
onChange,
|
||||
locked,
|
||||
locale,
|
||||
}) => (
|
||||
<ScrapeDialogRow
|
||||
title={title}
|
||||
field={field}
|
||||
result={result}
|
||||
originalField={
|
||||
<FormControl
|
||||
value={
|
||||
getCountryByISO(result.originalValue, locale) ?? result.originalValue
|
||||
}
|
||||
readOnly
|
||||
className="bg-secondary text-white border-secondary"
|
||||
/>
|
||||
}
|
||||
newField={
|
||||
<CountrySelect
|
||||
value={result.newValue}
|
||||
disabled={locked}
|
||||
onChange={(value) => {
|
||||
if (onChange) {
|
||||
onChange(result.cloneWithValue(value));
|
||||
}
|
||||
}}
|
||||
showFlag={false}
|
||||
isClearable={false}
|
||||
className="flex-grow-1"
|
||||
/>
|
||||
}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { ScrapeDialogRow } from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
import { ScrapeDialogRow } from "src/components/Shared/ScrapeDialog/ScrapeDialogRow";
|
||||
import { PerformerSelect } from "src/components/Performers/PerformerSelect";
|
||||
import {
|
||||
ObjectScrapeResult,
|
||||
@@ -10,6 +10,58 @@ import { TagIDSelect } from "src/components/Tags/TagSelect";
|
||||
import { StudioSelect } from "src/components/Studios/StudioSelect";
|
||||
import { GroupSelect } from "src/components/Groups/GroupSelect";
|
||||
import { uniq } from "lodash-es";
|
||||
import { CollapseButton } from "../CollapseButton";
|
||||
import { Badge, Button } from "react-bootstrap";
|
||||
import { Icon } from "../Icon";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
interface INewScrapedObjects<T> {
|
||||
newValues: T[];
|
||||
onCreateNew: (value: T) => void;
|
||||
getName: (value: T) => string;
|
||||
}
|
||||
|
||||
export const NewScrapedObjects = <T,>(props: INewScrapedObjects<T>) => {
|
||||
const intl = useIntl();
|
||||
|
||||
if (props.newValues.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ret = (
|
||||
<>
|
||||
{props.newValues.map((t) => (
|
||||
<Badge
|
||||
className="tag-item"
|
||||
variant="secondary"
|
||||
key={props.getName(t)}
|
||||
onClick={() => props.onCreateNew(t)}
|
||||
>
|
||||
{props.getName(t)}
|
||||
<Button className="minimal ml-2">
|
||||
<Icon className="fa-fw" icon={faPlus} />
|
||||
</Button>
|
||||
</Badge>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
const minCollapseLength = 10;
|
||||
|
||||
if (props.newValues!.length >= minCollapseLength) {
|
||||
const missingText = intl.formatMessage({
|
||||
id: "dialogs.scrape_results_missing",
|
||||
});
|
||||
return (
|
||||
<CollapseButton text={`${missingText} (${props.newValues!.length})`}>
|
||||
{ret}
|
||||
</CollapseButton>
|
||||
);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
interface IScrapedStudioRow {
|
||||
title: string;
|
||||
@@ -77,18 +129,20 @@ export const ScrapedStudioRow: React.FC<IScrapedStudioRow> = ({
|
||||
title={title}
|
||||
field={field}
|
||||
result={result}
|
||||
renderOriginalField={() => renderScrapedStudio(result)}
|
||||
renderNewField={() =>
|
||||
renderScrapedStudio(result, true, (value) =>
|
||||
onChange(result.cloneWithValue(value))
|
||||
)
|
||||
}
|
||||
originalField={renderScrapedStudio(result)}
|
||||
newField={renderScrapedStudio(result, true, (value) =>
|
||||
onChange(result.cloneWithValue(value))
|
||||
)}
|
||||
onChange={onChange}
|
||||
newValues={newStudio ? [newStudio] : undefined}
|
||||
onCreateNew={() => {
|
||||
if (onCreateNew && newStudio) onCreateNew(newStudio);
|
||||
}}
|
||||
getName={getObjectName}
|
||||
newValues={
|
||||
newStudio && onCreateNew ? (
|
||||
<NewScrapedObjects
|
||||
newValues={[newStudio]}
|
||||
onCreateNew={onCreateNew}
|
||||
getName={getObjectName}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -125,18 +179,20 @@ export const ScrapedObjectsRow = <T,>(props: IScrapedObjectsRow<T>) => {
|
||||
title={title}
|
||||
field={field}
|
||||
result={result}
|
||||
renderOriginalField={() => renderObjects(result)}
|
||||
renderNewField={() =>
|
||||
renderObjects(result, true, (value) =>
|
||||
onChange(result.cloneWithValue(value))
|
||||
)
|
||||
}
|
||||
originalField={renderObjects(result)}
|
||||
newField={renderObjects(result, true, (value) =>
|
||||
onChange(result.cloneWithValue(value))
|
||||
)}
|
||||
onChange={onChange}
|
||||
newValues={newObjects}
|
||||
onCreateNew={(i) => {
|
||||
if (onCreateNew) onCreateNew(newObjects![i]);
|
||||
}}
|
||||
getName={getName}
|
||||
newValues={
|
||||
onCreateNew ? (
|
||||
<NewScrapedObjects
|
||||
newValues={newObjects ?? []}
|
||||
onCreateNew={onCreateNew}
|
||||
getName={getName}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -155,6 +155,15 @@
|
||||
}
|
||||
|
||||
.scrape-dialog {
|
||||
.column-label {
|
||||
color: $muted-gray;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.string-list-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-content .dialog-container {
|
||||
max-height: calc(100vh - 14rem);
|
||||
overflow-y: auto;
|
||||
@@ -391,8 +400,18 @@ button.collapse-button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.string-list-input .input-group {
|
||||
margin-bottom: 0.35rem;
|
||||
.string-list-input {
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 0.35rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bulk-update-text-input {
|
||||
|
||||
@@ -1020,6 +1020,7 @@
|
||||
"scrape_entity_query": "{entity_type} Scrape Query",
|
||||
"scrape_entity_title": "{entity_type} Scrape Results",
|
||||
"scrape_results_existing": "Existing",
|
||||
"scrape_results_missing": "Missing",
|
||||
"scrape_results_scraped": "Scraped",
|
||||
"set_default_filter_confirm": "Are you sure you want to set this filter as the default?",
|
||||
"set_image_url_title": "Image URL",
|
||||
|
||||
Reference in New Issue
Block a user