mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +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 { useIntl } from "react-intl";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
ScrapeDialog,
|
|
||||||
ScrapedInputGroupRow,
|
ScrapedInputGroupRow,
|
||||||
ScrapedStringListRow,
|
ScrapedStringListRow,
|
||||||
ScrapedTextAreaRow,
|
ScrapedTextAreaRow,
|
||||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
} from "src/components/Shared/ScrapeDialog/ScrapeDialogRow";
|
||||||
|
import { ScrapeDialog } from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||||
import {
|
import {
|
||||||
ObjectListScrapeResult,
|
ObjectListScrapeResult,
|
||||||
ObjectScrapeResult,
|
ObjectScrapeResult,
|
||||||
@@ -225,10 +225,11 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
|||||||
{ id: "dialogs.scrape_entity_title" },
|
{ id: "dialogs.scrape_entity_title" },
|
||||||
{ entity_type: intl.formatMessage({ id: "gallery" }) }
|
{ entity_type: intl.formatMessage({ id: "gallery" }) }
|
||||||
)}
|
)}
|
||||||
renderScrapeRows={renderScrapeRows}
|
|
||||||
onClose={(apply) => {
|
onClose={(apply) => {
|
||||||
onClose(apply ? makeNewScrapedItem() : undefined);
|
onClose(apply ? makeNewScrapedItem() : undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{renderScrapeRows()}
|
||||||
|
</ScrapeDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import React, { useState } from "react";
|
|||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
ScrapeDialog,
|
|
||||||
ScrapedInputGroupRow,
|
ScrapedInputGroupRow,
|
||||||
ScrapedImageRow,
|
ScrapedImageRow,
|
||||||
ScrapedTextAreaRow,
|
ScrapedTextAreaRow,
|
||||||
ScrapedStringListRow,
|
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 TextUtils from "src/utils/text";
|
||||||
import {
|
import {
|
||||||
ObjectScrapeResult,
|
ObjectScrapeResult,
|
||||||
@@ -224,10 +224,11 @@ export const GroupScrapeDialog: React.FC<IGroupScrapeDialogProps> = ({
|
|||||||
{ id: "dialogs.scrape_entity_title" },
|
{ id: "dialogs.scrape_entity_title" },
|
||||||
{ entity_type: intl.formatMessage({ id: "group" }) }
|
{ entity_type: intl.formatMessage({ id: "group" }) }
|
||||||
)}
|
)}
|
||||||
renderScrapeRows={renderScrapeRows}
|
|
||||||
onClose={(apply) => {
|
onClose={(apply) => {
|
||||||
onClose(apply ? makeNewScrapedItem() : undefined);
|
onClose(apply ? makeNewScrapedItem() : undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{renderScrapeRows()}
|
||||||
|
</ScrapeDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import React, { useState } from "react";
|
|||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
ScrapeDialog,
|
|
||||||
ScrapedInputGroupRow,
|
ScrapedInputGroupRow,
|
||||||
ScrapedStringListRow,
|
ScrapedStringListRow,
|
||||||
ScrapedTextAreaRow,
|
ScrapedTextAreaRow,
|
||||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
} from "src/components/Shared/ScrapeDialog/ScrapeDialogRow";
|
||||||
|
import { ScrapeDialog } from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||||
import {
|
import {
|
||||||
ObjectListScrapeResult,
|
ObjectListScrapeResult,
|
||||||
ObjectScrapeResult,
|
ObjectScrapeResult,
|
||||||
@@ -226,10 +226,11 @@ export const ImageScrapeDialog: React.FC<IImageScrapeDialogProps> = ({
|
|||||||
{ id: "dialogs.scrape_entity_title" },
|
{ id: "dialogs.scrape_entity_title" },
|
||||||
{ entity_type: intl.formatMessage({ id: "image" }) }
|
{ entity_type: intl.formatMessage({ id: "image" }) }
|
||||||
)}
|
)}
|
||||||
renderScrapeRows={renderScrapeRows}
|
|
||||||
onClose={(apply) => {
|
onClose={(apply) => {
|
||||||
onClose(apply ? makeNewScrapedItem() : undefined);
|
onClose(apply ? makeNewScrapedItem() : undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{renderScrapeRows()}
|
||||||
|
</ScrapeDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import React, { useState } from "react";
|
|||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
ScrapeDialog,
|
|
||||||
ScrapedInputGroupRow,
|
ScrapedInputGroupRow,
|
||||||
ScrapedImagesRow,
|
ScrapedImagesRow,
|
||||||
ScrapeDialogRow,
|
ScrapeDialogRow,
|
||||||
ScrapedTextAreaRow,
|
ScrapedTextAreaRow,
|
||||||
ScrapedCountryRow,
|
ScrapedCountryRow,
|
||||||
ScrapedStringListRow,
|
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 { Form } from "react-bootstrap";
|
||||||
import {
|
import {
|
||||||
genderStrings,
|
genderStrings,
|
||||||
@@ -66,12 +66,10 @@ function renderScrapedGenderRow(
|
|||||||
field="gender"
|
field="gender"
|
||||||
title={title}
|
title={title}
|
||||||
result={result}
|
result={result}
|
||||||
renderOriginalField={() => renderScrapedGender(result)}
|
originalField={renderScrapedGender(result)}
|
||||||
renderNewField={() =>
|
newField={renderScrapedGender(result, true, (value) =>
|
||||||
renderScrapedGender(result, true, (value) =>
|
onChange(result.cloneWithValue(value))
|
||||||
onChange(result.cloneWithValue(value))
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -116,12 +114,10 @@ function renderScrapedCircumcisedRow(
|
|||||||
title={title}
|
title={title}
|
||||||
field="circumcised"
|
field="circumcised"
|
||||||
result={result}
|
result={result}
|
||||||
renderOriginalField={() => renderScrapedCircumcised(result)}
|
originalField={renderScrapedCircumcised(result)}
|
||||||
renderNewField={() =>
|
newField={renderScrapedCircumcised(result, true, (value) =>
|
||||||
renderScrapedCircumcised(result, true, (value) =>
|
onChange(result.cloneWithValue(value))
|
||||||
onChange(result.cloneWithValue(value))
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -552,10 +548,11 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
|||||||
{ id: "dialogs.scrape_entity_title" },
|
{ id: "dialogs.scrape_entity_title" },
|
||||||
{ entity_type: intl.formatMessage({ id: "performer" }) }
|
{ entity_type: intl.formatMessage({ id: "performer" }) }
|
||||||
)}
|
)}
|
||||||
renderScrapeRows={renderScrapeRows}
|
|
||||||
onClose={(apply) => {
|
onClose={(apply) => {
|
||||||
props.onClose(apply ? makeNewScrapedItem() : undefined);
|
props.onClose(apply ? makeNewScrapedItem() : undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{renderScrapeRows()}
|
||||||
|
</ScrapeDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
ScrapeDialog,
|
|
||||||
ScrapedInputGroupRow,
|
ScrapedInputGroupRow,
|
||||||
ScrapedTextAreaRow,
|
ScrapedTextAreaRow,
|
||||||
ScrapedImageRow,
|
ScrapedImageRow,
|
||||||
ScrapedStringListRow,
|
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 { useIntl } from "react-intl";
|
||||||
import { uniq } from "lodash-es";
|
import { uniq } from "lodash-es";
|
||||||
import { Performer } from "src/components/Performers/PerformerSelect";
|
import { Performer } from "src/components/Performers/PerformerSelect";
|
||||||
@@ -304,11 +304,12 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
|||||||
{ id: "dialogs.scrape_entity_title" },
|
{ id: "dialogs.scrape_entity_title" },
|
||||||
{ entity_type: intl.formatMessage({ id: "scene" }) }
|
{ entity_type: intl.formatMessage({ id: "scene" }) }
|
||||||
)}
|
)}
|
||||||
renderScrapeRows={renderScrapeRows}
|
|
||||||
onClose={(apply) => {
|
onClose={(apply) => {
|
||||||
onClose(apply ? makeNewScrapedItem() : undefined);
|
onClose(apply ? makeNewScrapedItem() : undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{renderScrapeRows()}
|
||||||
|
</ScrapeDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { faExchangeAlt, faSignInAlt } from "@fortawesome/free-solid-svg-icons";
|
import { faExchangeAlt, faSignInAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
import {
|
import {
|
||||||
ScrapeDialog,
|
|
||||||
ScrapeDialogRow,
|
ScrapeDialogRow,
|
||||||
ScrapedImageRow,
|
ScrapedImageRow,
|
||||||
ScrapedInputGroupRow,
|
ScrapedInputGroupRow,
|
||||||
ScrapedStringListRow,
|
ScrapedStringListRow,
|
||||||
ScrapedTextAreaRow,
|
ScrapedTextAreaRow,
|
||||||
} from "../Shared/ScrapeDialog/ScrapeDialog";
|
} from "../Shared/ScrapeDialog/ScrapeDialogRow";
|
||||||
|
import { ScrapeDialog } from "../Shared/ScrapeDialog/ScrapeDialog";
|
||||||
import { clone, uniq } from "lodash-es";
|
import { clone, uniq } from "lodash-es";
|
||||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
import { ModalComponent } from "../Shared/Modal";
|
import { ModalComponent } from "../Shared/Modal";
|
||||||
@@ -400,63 +400,59 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||||||
field="rating"
|
field="rating"
|
||||||
title={intl.formatMessage({ id: "rating" })}
|
title={intl.formatMessage({ id: "rating" })}
|
||||||
result={rating}
|
result={rating}
|
||||||
renderOriginalField={() => (
|
originalField={<RatingSystem value={rating.originalValue} disabled />}
|
||||||
<RatingSystem value={rating.originalValue} disabled />
|
newField={<RatingSystem value={rating.newValue} disabled />}
|
||||||
)}
|
|
||||||
renderNewField={() => (
|
|
||||||
<RatingSystem value={rating.newValue} disabled />
|
|
||||||
)}
|
|
||||||
onChange={(value) => setRating(value)}
|
onChange={(value) => setRating(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
field="o_count"
|
field="o_count"
|
||||||
title={intl.formatMessage({ id: "o_count" })}
|
title={intl.formatMessage({ id: "o_count" })}
|
||||||
result={oCounter}
|
result={oCounter}
|
||||||
renderOriginalField={() => (
|
originalField={
|
||||||
<FormControl
|
<FormControl
|
||||||
value={oCounter.originalValue ?? 0}
|
value={oCounter.originalValue ?? 0}
|
||||||
readOnly
|
readOnly
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
className="bg-secondary text-white border-secondary"
|
className="bg-secondary text-white border-secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
renderNewField={() => (
|
newField={
|
||||||
<FormControl
|
<FormControl
|
||||||
value={oCounter.newValue ?? 0}
|
value={oCounter.newValue ?? 0}
|
||||||
readOnly
|
readOnly
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
className="bg-secondary text-white border-secondary"
|
className="bg-secondary text-white border-secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
onChange={(value) => setOCounter(value)}
|
onChange={(value) => setOCounter(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
field="play_count"
|
field="play_count"
|
||||||
title={intl.formatMessage({ id: "play_count" })}
|
title={intl.formatMessage({ id: "play_count" })}
|
||||||
result={playCount}
|
result={playCount}
|
||||||
renderOriginalField={() => (
|
originalField={
|
||||||
<FormControl
|
<FormControl
|
||||||
value={playCount.originalValue ?? 0}
|
value={playCount.originalValue ?? 0}
|
||||||
readOnly
|
readOnly
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
className="bg-secondary text-white border-secondary"
|
className="bg-secondary text-white border-secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
renderNewField={() => (
|
newField={
|
||||||
<FormControl
|
<FormControl
|
||||||
value={playCount.newValue ?? 0}
|
value={playCount.newValue ?? 0}
|
||||||
readOnly
|
readOnly
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
className="bg-secondary text-white border-secondary"
|
className="bg-secondary text-white border-secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
onChange={(value) => setPlayCount(value)}
|
onChange={(value) => setPlayCount(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
field="play_duration"
|
field="play_duration"
|
||||||
title={intl.formatMessage({ id: "play_duration" })}
|
title={intl.formatMessage({ id: "play_duration" })}
|
||||||
result={playDuration}
|
result={playDuration}
|
||||||
renderOriginalField={() => (
|
originalField={
|
||||||
<FormControl
|
<FormControl
|
||||||
value={TextUtils.secondsToTimestamp(
|
value={TextUtils.secondsToTimestamp(
|
||||||
playDuration.originalValue ?? 0
|
playDuration.originalValue ?? 0
|
||||||
@@ -465,22 +461,22 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
className="bg-secondary text-white border-secondary"
|
className="bg-secondary text-white border-secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
renderNewField={() => (
|
newField={
|
||||||
<FormControl
|
<FormControl
|
||||||
value={TextUtils.secondsToTimestamp(playDuration.newValue ?? 0)}
|
value={TextUtils.secondsToTimestamp(playDuration.newValue ?? 0)}
|
||||||
readOnly
|
readOnly
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
className="bg-secondary text-white border-secondary"
|
className="bg-secondary text-white border-secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
onChange={(value) => setPlayDuration(value)}
|
onChange={(value) => setPlayDuration(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
field="galleries"
|
field="galleries"
|
||||||
title={intl.formatMessage({ id: "galleries" })}
|
title={intl.formatMessage({ id: "galleries" })}
|
||||||
result={galleries}
|
result={galleries}
|
||||||
renderOriginalField={() => (
|
originalField={
|
||||||
<GallerySelect
|
<GallerySelect
|
||||||
className="form-control react-select"
|
className="form-control react-select"
|
||||||
ids={galleries.originalValue ?? []}
|
ids={galleries.originalValue ?? []}
|
||||||
@@ -488,8 +484,8 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||||||
isMulti
|
isMulti
|
||||||
isDisabled
|
isDisabled
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
renderNewField={() => (
|
newField={
|
||||||
<GallerySelect
|
<GallerySelect
|
||||||
className="form-control react-select"
|
className="form-control react-select"
|
||||||
ids={galleries.newValue ?? []}
|
ids={galleries.newValue ?? []}
|
||||||
@@ -497,7 +493,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||||||
isMulti
|
isMulti
|
||||||
isDisabled
|
isDisabled
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
onChange={(value) => setGalleries(value)}
|
onChange={(value) => setGalleries(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedStudioRow
|
<ScrapedStudioRow
|
||||||
@@ -535,34 +531,32 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||||||
field="organized"
|
field="organized"
|
||||||
title={intl.formatMessage({ id: "organized" })}
|
title={intl.formatMessage({ id: "organized" })}
|
||||||
result={organized}
|
result={organized}
|
||||||
renderOriginalField={() => (
|
originalField={
|
||||||
<FormControl
|
<FormControl
|
||||||
value={organized.originalValue ? trueString : falseString}
|
value={organized.originalValue ? trueString : falseString}
|
||||||
readOnly
|
readOnly
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
className="bg-secondary text-white border-secondary"
|
className="bg-secondary text-white border-secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
renderNewField={() => (
|
newField={
|
||||||
<FormControl
|
<FormControl
|
||||||
value={organized.newValue ? trueString : falseString}
|
value={organized.newValue ? trueString : falseString}
|
||||||
readOnly
|
readOnly
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
className="bg-secondary text-white border-secondary"
|
className="bg-secondary text-white border-secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
onChange={(value) => setOrganized(value)}
|
onChange={(value) => setOrganized(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapeDialogRow
|
<ScrapeDialogRow
|
||||||
field="stash_ids"
|
field="stash_ids"
|
||||||
title={intl.formatMessage({ id: "stash_id" })}
|
title={intl.formatMessage({ id: "stash_id" })}
|
||||||
result={stashIDs}
|
result={stashIDs}
|
||||||
renderOriginalField={() => (
|
originalField={
|
||||||
<StashIDsField values={stashIDs?.originalValue ?? []} />
|
<StashIDsField values={stashIDs?.originalValue ?? []} />
|
||||||
)}
|
}
|
||||||
renderNewField={() => (
|
newField={<StashIDsField values={stashIDs?.newValue ?? []} />}
|
||||||
<StashIDsField values={stashIDs?.newValue ?? []} />
|
|
||||||
)}
|
|
||||||
onChange={(value) => setStashIDs(value)}
|
onChange={(value) => setStashIDs(value)}
|
||||||
/>
|
/>
|
||||||
<ScrapedImageRow
|
<ScrapedImageRow
|
||||||
@@ -634,7 +628,6 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||||||
title={dialogTitle}
|
title={dialogTitle}
|
||||||
existingLabel={destinationLabel}
|
existingLabel={destinationLabel}
|
||||||
scrapedLabel={sourceLabel}
|
scrapedLabel={sourceLabel}
|
||||||
renderScrapeRows={renderScrapeRows}
|
|
||||||
onClose={(apply) => {
|
onClose={(apply) => {
|
||||||
if (!apply) {
|
if (!apply) {
|
||||||
onClose();
|
onClose();
|
||||||
@@ -642,7 +635,9 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||||||
onClose(createValues());
|
onClose(createValues());
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{renderScrapeRows()}
|
||||||
|
</ScrapeDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,457 +1,55 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useMemo } from "react";
|
||||||
import {
|
import { Form, Col, Row } from "react-bootstrap";
|
||||||
Form,
|
|
||||||
Col,
|
|
||||||
Row,
|
|
||||||
InputGroup,
|
|
||||||
Button,
|
|
||||||
FormControl,
|
|
||||||
Badge,
|
|
||||||
} from "react-bootstrap";
|
|
||||||
import { CollapseButton } from "../CollapseButton";
|
|
||||||
import { Icon } from "../Icon";
|
|
||||||
import { ModalComponent } from "../Modal";
|
import { ModalComponent } from "../Modal";
|
||||||
import clone from "lodash-es/clone";
|
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import {
|
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
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 { useConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
|
|
||||||
interface IScrapedFieldProps<T> {
|
export interface IScrapeDialogContextState {
|
||||||
result: ScrapeResult<T>;
|
existingLabel?: React.ReactNode;
|
||||||
|
scrapedLabel?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IScrapedRowProps<T, V> extends IScrapedFieldProps<T> {
|
export const ScrapeDialogContext =
|
||||||
className?: string;
|
React.createContext<IScrapeDialogContextState>({});
|
||||||
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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IScrapeDialogProps {
|
interface IScrapeDialogProps {
|
||||||
title: string;
|
title: string;
|
||||||
existingLabel?: string;
|
existingLabel?: React.ReactNode;
|
||||||
scrapedLabel?: string;
|
scrapedLabel?: React.ReactNode;
|
||||||
renderScrapeRows: () => JSX.Element;
|
|
||||||
onClose: (apply?: boolean) => void;
|
onClose: (apply?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ScrapeDialog: React.FC<IScrapeDialogProps> = (
|
export const ScrapeDialog: React.FC<
|
||||||
props: IScrapeDialogProps
|
React.PropsWithChildren<IScrapeDialogProps>
|
||||||
) => {
|
> = (props: React.PropsWithChildren<IScrapeDialogProps>) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { configuration } = useConfigurationContext();
|
const { configuration } = useConfigurationContext();
|
||||||
const { sfwContentMode } = configuration.interface;
|
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 (
|
return (
|
||||||
<ModalComponent
|
<ModalComponent
|
||||||
show
|
show
|
||||||
@@ -474,76 +72,33 @@ export const ScrapeDialog: React.FC<IScrapeDialogProps> = (
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="dialog-container">
|
<div className="dialog-container">
|
||||||
<Form>
|
<ScrapeDialogContext.Provider value={contextState}>
|
||||||
<Row className="px-3 pt-3">
|
<Form>
|
||||||
<Col lg={{ span: 9, offset: 3 }}>
|
<Row className="px-3 pt-3">
|
||||||
<Row>
|
<Col lg={{ span: 9, offset: 3 }}>
|
||||||
<Form.Label column xs="6">
|
<Row>
|
||||||
{props.existingLabel ?? (
|
<Form.Label
|
||||||
<FormattedMessage id="dialogs.scrape_results_existing" />
|
column
|
||||||
)}
|
lg="6"
|
||||||
</Form.Label>
|
className="d-lg-block d-none column-label"
|
||||||
<Form.Label column xs="6">
|
>
|
||||||
{props.scrapedLabel ?? (
|
{existingLabel}
|
||||||
<FormattedMessage id="dialogs.scrape_results_scraped" />
|
</Form.Label>
|
||||||
)}
|
<Form.Label
|
||||||
</Form.Label>
|
column
|
||||||
</Row>
|
lg="6"
|
||||||
</Col>
|
className="d-lg-block d-none column-label"
|
||||||
</Row>
|
>
|
||||||
|
{scrapedLabel}
|
||||||
|
</Form.Label>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
{props.renderScrapeRows()}
|
{props.children}
|
||||||
</Form>
|
</Form>
|
||||||
|
</ScrapeDialogContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
</ModalComponent>
|
</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 React, { useMemo } from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
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 { PerformerSelect } from "src/components/Performers/PerformerSelect";
|
||||||
import {
|
import {
|
||||||
ObjectScrapeResult,
|
ObjectScrapeResult,
|
||||||
@@ -10,6 +10,58 @@ import { TagIDSelect } from "src/components/Tags/TagSelect";
|
|||||||
import { StudioSelect } from "src/components/Studios/StudioSelect";
|
import { StudioSelect } from "src/components/Studios/StudioSelect";
|
||||||
import { GroupSelect } from "src/components/Groups/GroupSelect";
|
import { GroupSelect } from "src/components/Groups/GroupSelect";
|
||||||
import { uniq } from "lodash-es";
|
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 {
|
interface IScrapedStudioRow {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -77,18 +129,20 @@ export const ScrapedStudioRow: React.FC<IScrapedStudioRow> = ({
|
|||||||
title={title}
|
title={title}
|
||||||
field={field}
|
field={field}
|
||||||
result={result}
|
result={result}
|
||||||
renderOriginalField={() => renderScrapedStudio(result)}
|
originalField={renderScrapedStudio(result)}
|
||||||
renderNewField={() =>
|
newField={renderScrapedStudio(result, true, (value) =>
|
||||||
renderScrapedStudio(result, true, (value) =>
|
onChange(result.cloneWithValue(value))
|
||||||
onChange(result.cloneWithValue(value))
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
newValues={newStudio ? [newStudio] : undefined}
|
newValues={
|
||||||
onCreateNew={() => {
|
newStudio && onCreateNew ? (
|
||||||
if (onCreateNew && newStudio) onCreateNew(newStudio);
|
<NewScrapedObjects
|
||||||
}}
|
newValues={[newStudio]}
|
||||||
getName={getObjectName}
|
onCreateNew={onCreateNew}
|
||||||
|
getName={getObjectName}
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -125,18 +179,20 @@ export const ScrapedObjectsRow = <T,>(props: IScrapedObjectsRow<T>) => {
|
|||||||
title={title}
|
title={title}
|
||||||
field={field}
|
field={field}
|
||||||
result={result}
|
result={result}
|
||||||
renderOriginalField={() => renderObjects(result)}
|
originalField={renderObjects(result)}
|
||||||
renderNewField={() =>
|
newField={renderObjects(result, true, (value) =>
|
||||||
renderObjects(result, true, (value) =>
|
onChange(result.cloneWithValue(value))
|
||||||
onChange(result.cloneWithValue(value))
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
newValues={newObjects}
|
newValues={
|
||||||
onCreateNew={(i) => {
|
onCreateNew ? (
|
||||||
if (onCreateNew) onCreateNew(newObjects![i]);
|
<NewScrapedObjects
|
||||||
}}
|
newValues={newObjects ?? []}
|
||||||
getName={getName}
|
onCreateNew={onCreateNew}
|
||||||
|
getName={getName}
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -155,6 +155,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.scrape-dialog {
|
.scrape-dialog {
|
||||||
|
.column-label {
|
||||||
|
color: $muted-gray;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.string-list-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-content .dialog-container {
|
.modal-content .dialog-container {
|
||||||
max-height: calc(100vh - 14rem);
|
max-height: calc(100vh - 14rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -391,8 +400,18 @@ button.collapse-button {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.string-list-input .input-group {
|
.string-list-input {
|
||||||
margin-bottom: 0.35rem;
|
.form-group {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bulk-update-text-input {
|
.bulk-update-text-input {
|
||||||
|
|||||||
@@ -1020,6 +1020,7 @@
|
|||||||
"scrape_entity_query": "{entity_type} Scrape Query",
|
"scrape_entity_query": "{entity_type} Scrape Query",
|
||||||
"scrape_entity_title": "{entity_type} Scrape Results",
|
"scrape_entity_title": "{entity_type} Scrape Results",
|
||||||
"scrape_results_existing": "Existing",
|
"scrape_results_existing": "Existing",
|
||||||
|
"scrape_results_missing": "Missing",
|
||||||
"scrape_results_scraped": "Scraped",
|
"scrape_results_scraped": "Scraped",
|
||||||
"set_default_filter_confirm": "Are you sure you want to set this filter as the default?",
|
"set_default_filter_confirm": "Are you sure you want to set this filter as the default?",
|
||||||
"set_image_url_title": "Image URL",
|
"set_image_url_title": "Image URL",
|
||||||
|
|||||||
Reference in New Issue
Block a user