mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
* Use more neutral language for content * Add sfw mode setting * Make configuration context mandatory * Add sfw class when sfw mode active * Hide nsfw performer fields in sfw mode * Hide nsfw sort options * Hide nsfw filter/sort options in sfw mode * Replace o-count with like counter in sfw mode * Use sfw label for o-counter filter in sfw mode * Use likes instead of o-count in sfw mode in other places * Rename sfw mode to sfw content mode * Use sfw image for default performers in sfw mode * Document SFW content mode * Add SFW mode setting to setup * Clarify README * Change wording of sfw mode description * Handle configuration loading error correctly * Hide age in performer cards
236 lines
6.6 KiB
TypeScript
236 lines
6.6 KiB
TypeScript
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";
|
|
import {
|
|
ObjectListScrapeResult,
|
|
ObjectScrapeResult,
|
|
ScrapeResult,
|
|
} from "src/components/Shared/ScrapeDialog/scrapeResult";
|
|
import {
|
|
ScrapedPerformersRow,
|
|
ScrapedStudioRow,
|
|
} from "src/components/Shared/ScrapeDialog/ScrapedObjectsRow";
|
|
import { sortStoredIdObjects } from "src/utils/data";
|
|
import { Performer } from "src/components/Performers/PerformerSelect";
|
|
import {
|
|
useCreateScrapedPerformer,
|
|
useCreateScrapedStudio,
|
|
} from "src/components/Shared/ScrapeDialog/createObjects";
|
|
import { uniq } from "lodash-es";
|
|
import { Tag } from "src/components/Tags/TagSelect";
|
|
import { Studio } from "src/components/Studios/StudioSelect";
|
|
import { useScrapedTags } from "src/components/Shared/ScrapeDialog/scrapedTags";
|
|
|
|
interface IImageScrapeDialogProps {
|
|
image: Partial<GQL.ImageUpdateInput>;
|
|
imageStudio: Studio | null;
|
|
imageTags: Tag[];
|
|
imagePerformers: Performer[];
|
|
scraped: GQL.ScrapedImage;
|
|
|
|
onClose: (scrapedImage?: GQL.ScrapedImage) => void;
|
|
}
|
|
|
|
export const ImageScrapeDialog: React.FC<IImageScrapeDialogProps> = ({
|
|
image,
|
|
imageStudio,
|
|
imageTags,
|
|
imagePerformers,
|
|
scraped,
|
|
onClose,
|
|
}) => {
|
|
const intl = useIntl();
|
|
const [title, setTitle] = useState<ScrapeResult<string>>(
|
|
new ScrapeResult<string>(image.title, scraped.title)
|
|
);
|
|
const [code, setCode] = useState<ScrapeResult<string>>(
|
|
new ScrapeResult<string>(image.code, scraped.code)
|
|
);
|
|
const [urls, setURLs] = useState<ScrapeResult<string[]>>(
|
|
new ScrapeResult<string[]>(
|
|
image.urls,
|
|
scraped.urls
|
|
? uniq((image.urls ?? []).concat(scraped.urls ?? []))
|
|
: undefined
|
|
)
|
|
);
|
|
const [date, setDate] = useState<ScrapeResult<string>>(
|
|
new ScrapeResult<string>(image.date, scraped.date)
|
|
);
|
|
|
|
const [photographer, setPhotographer] = useState<ScrapeResult<string>>(
|
|
new ScrapeResult<string>(image.photographer, scraped.photographer)
|
|
);
|
|
|
|
const [studio, setStudio] = useState<ObjectScrapeResult<GQL.ScrapedStudio>>(
|
|
new ObjectScrapeResult<GQL.ScrapedStudio>(
|
|
imageStudio
|
|
? {
|
|
stored_id: imageStudio.id,
|
|
name: imageStudio.name,
|
|
}
|
|
: undefined,
|
|
scraped.studio?.stored_id ? scraped.studio : undefined
|
|
)
|
|
);
|
|
const [newStudio, setNewStudio] = useState<GQL.ScrapedStudio | undefined>(
|
|
scraped.studio && !scraped.studio.stored_id ? scraped.studio : undefined
|
|
);
|
|
|
|
const [performers, setPerformers] = useState<
|
|
ObjectListScrapeResult<GQL.ScrapedPerformer>
|
|
>(
|
|
new ObjectListScrapeResult<GQL.ScrapedPerformer>(
|
|
sortStoredIdObjects(
|
|
imagePerformers.map((p) => ({
|
|
stored_id: p.id,
|
|
name: p.name,
|
|
}))
|
|
),
|
|
sortStoredIdObjects(scraped.performers ?? undefined)
|
|
)
|
|
);
|
|
const [newPerformers, setNewPerformers] = useState<GQL.ScrapedPerformer[]>(
|
|
scraped.performers?.filter((t) => !t.stored_id) ?? []
|
|
);
|
|
|
|
const { tags, newTags, scrapedTagsRow } = useScrapedTags(
|
|
imageTags,
|
|
scraped.tags
|
|
);
|
|
|
|
const [details, setDetails] = useState<ScrapeResult<string>>(
|
|
new ScrapeResult<string>(image.details, scraped.details)
|
|
);
|
|
|
|
const createNewStudio = useCreateScrapedStudio({
|
|
scrapeResult: studio,
|
|
setScrapeResult: setStudio,
|
|
setNewObject: setNewStudio,
|
|
});
|
|
|
|
const createNewPerformer = useCreateScrapedPerformer({
|
|
scrapeResult: performers,
|
|
setScrapeResult: setPerformers,
|
|
newObjects: newPerformers,
|
|
setNewObjects: setNewPerformers,
|
|
});
|
|
|
|
// don't show the dialog if nothing was scraped
|
|
if (
|
|
[
|
|
title,
|
|
code,
|
|
urls,
|
|
date,
|
|
photographer,
|
|
studio,
|
|
performers,
|
|
tags,
|
|
details,
|
|
].every((r) => !r.scraped) &&
|
|
!newStudio &&
|
|
newPerformers.length === 0 &&
|
|
newTags.length === 0
|
|
) {
|
|
onClose();
|
|
return <></>;
|
|
}
|
|
|
|
function makeNewScrapedItem(): GQL.ScrapedImageDataFragment {
|
|
const newStudioValue = studio.getNewValue();
|
|
|
|
return {
|
|
title: title.getNewValue(),
|
|
code: code.getNewValue(),
|
|
urls: urls.getNewValue(),
|
|
date: date.getNewValue(),
|
|
photographer: photographer.getNewValue(),
|
|
studio: newStudioValue,
|
|
performers: performers.getNewValue(),
|
|
tags: tags.getNewValue(),
|
|
details: details.getNewValue(),
|
|
};
|
|
}
|
|
|
|
function renderScrapeRows() {
|
|
return (
|
|
<>
|
|
<ScrapedInputGroupRow
|
|
field="title"
|
|
title={intl.formatMessage({ id: "title" })}
|
|
result={title}
|
|
onChange={(value) => setTitle(value)}
|
|
/>
|
|
<ScrapedInputGroupRow
|
|
field="code"
|
|
title={intl.formatMessage({ id: "scene_code" })}
|
|
result={code}
|
|
onChange={(value) => setCode(value)}
|
|
/>
|
|
<ScrapedStringListRow
|
|
field="urls"
|
|
title={intl.formatMessage({ id: "urls" })}
|
|
result={urls}
|
|
onChange={(value) => setURLs(value)}
|
|
/>
|
|
<ScrapedInputGroupRow
|
|
field="date"
|
|
title={intl.formatMessage({ id: "date" })}
|
|
placeholder="YYYY-MM-DD"
|
|
result={date}
|
|
onChange={(value) => setDate(value)}
|
|
/>
|
|
<ScrapedInputGroupRow
|
|
field="photographer"
|
|
title={intl.formatMessage({ id: "photographer" })}
|
|
result={photographer}
|
|
onChange={(value) => setPhotographer(value)}
|
|
/>
|
|
<ScrapedStudioRow
|
|
field="studio"
|
|
title={intl.formatMessage({ id: "studios" })}
|
|
result={studio}
|
|
onChange={(value) => setStudio(value)}
|
|
newStudio={newStudio}
|
|
onCreateNew={createNewStudio}
|
|
/>
|
|
<ScrapedPerformersRow
|
|
field="performers"
|
|
title={intl.formatMessage({ id: "performers" })}
|
|
result={performers}
|
|
onChange={(value) => setPerformers(value)}
|
|
newObjects={newPerformers}
|
|
onCreateNew={createNewPerformer}
|
|
/>
|
|
{scrapedTagsRow}
|
|
<ScrapedTextAreaRow
|
|
field="details"
|
|
title={intl.formatMessage({ id: "details" })}
|
|
result={details}
|
|
onChange={(value) => setDetails(value)}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ScrapeDialog
|
|
title={intl.formatMessage(
|
|
{ id: "dialogs.scrape_entity_title" },
|
|
{ entity_type: intl.formatMessage({ id: "image" }) }
|
|
)}
|
|
renderScrapeRows={renderScrapeRows}
|
|
onClose={(apply) => {
|
|
onClose(apply ? makeNewScrapedItem() : undefined);
|
|
}}
|
|
/>
|
|
);
|
|
};
|