mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Support StashIDs in scrape dialog (#1955)
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
* Added interface options to disable creating performers/studios/tags from dropdown selectors. ([#1814](https://github.com/stashapp/stash/pull/1814))
|
* Added interface options to disable creating performers/studios/tags from dropdown selectors. ([#1814](https://github.com/stashapp/stash/pull/1814))
|
||||||
|
|
||||||
### 🎨 Improvements
|
### 🎨 Improvements
|
||||||
|
* Added stash-id to scene scrape dialog. ([#1955](https://github.com/stashapp/stash/pull/1955))
|
||||||
* Reworked main navbar and positioned at bottom for mobile devices. ([#1769](https://github.com/stashapp/stash/pull/1769))
|
* Reworked main navbar and positioned at bottom for mobile devices. ([#1769](https://github.com/stashapp/stash/pull/1769))
|
||||||
* Show files being deleted in the Delete dialogs. ([#1852](https://github.com/stashapp/stash/pull/1852))
|
* Show files being deleted in the Delete dialogs. ([#1852](https://github.com/stashapp/stash/pull/1852))
|
||||||
* Added specific page titles. ([#1831](https://github.com/stashapp/stash/pull/1831))
|
* Added specific page titles. ([#1831](https://github.com/stashapp/stash/pull/1831))
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
setIsScraperQueryModalOpen,
|
setIsScraperQueryModalOpen,
|
||||||
] = useState<boolean>(false);
|
] = useState<boolean>(false);
|
||||||
const [scrapedScene, setScrapedScene] = useState<GQL.ScrapedScene | null>();
|
const [scrapedScene, setScrapedScene] = useState<GQL.ScrapedScene | null>();
|
||||||
|
const [endpoint, setEndpoint] = useState<string | undefined>();
|
||||||
|
|
||||||
const [coverImagePreview, setCoverImagePreview] = useState<
|
const [coverImagePreview, setCoverImagePreview] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
@@ -299,6 +300,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
}
|
}
|
||||||
// assume one returned scene
|
// assume one returned scene
|
||||||
setScrapedScene(result.data.scrapeSingleScene[0]);
|
setScrapedScene(result.data.scrapeSingleScene[0]);
|
||||||
|
setEndpoint(s.stash_box_endpoint ?? undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.error(e);
|
Toast.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -338,6 +340,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
|
|
||||||
function onScrapeQueryClicked(s: GQL.ScraperSourceInput) {
|
function onScrapeQueryClicked(s: GQL.ScraperSourceInput) {
|
||||||
setScraper(s);
|
setScraper(s);
|
||||||
|
setEndpoint(s.stash_box_endpoint ?? undefined);
|
||||||
setIsScraperQueryModalOpen(true);
|
setIsScraperQueryModalOpen(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,6 +379,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
<SceneScrapeDialog
|
<SceneScrapeDialog
|
||||||
scene={currentScene}
|
scene={currentScene}
|
||||||
scraped={scrapedScene}
|
scraped={scrapedScene}
|
||||||
|
endpoint={endpoint}
|
||||||
onClose={(s) => onScrapeDialogClosed(s)}
|
onClose={(s) => onScrapeDialogClosed(s)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -396,7 +400,12 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
{stashBoxes.map((s, index) => (
|
{stashBoxes.map((s, index) => (
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
key={s.endpoint}
|
key={s.endpoint}
|
||||||
onClick={() => onScrapeQueryClicked({ stash_box_index: index })}
|
onClick={() =>
|
||||||
|
onScrapeQueryClicked({
|
||||||
|
stash_box_index: index,
|
||||||
|
stash_box_endpoint: s.endpoint,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{stashboxDisplayName(s.name, index)}
|
{stashboxDisplayName(s.name, index)}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
@@ -463,7 +472,12 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
{stashBoxes.map((s, index) => (
|
{stashBoxes.map((s, index) => (
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
key={s.endpoint}
|
key={s.endpoint}
|
||||||
onClick={() => onScrapeClicked({ stash_box_index: index })}
|
onClick={() =>
|
||||||
|
onScrapeClicked({
|
||||||
|
stash_box_index: index,
|
||||||
|
stash_box_endpoint: s.endpoint,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{stashboxDisplayName(s.name, index)}
|
{stashboxDisplayName(s.name, index)}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
@@ -555,6 +569,34 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
formik.setFieldValue("cover_image", updatedScene.image);
|
formik.setFieldValue("cover_image", updatedScene.image);
|
||||||
setCoverImagePreview(updatedScene.image);
|
setCoverImagePreview(updatedScene.image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (updatedScene.remote_site_id && endpoint) {
|
||||||
|
let found = false;
|
||||||
|
formik.setFieldValue(
|
||||||
|
"stash_ids",
|
||||||
|
formik.values.stash_ids.map((s) => {
|
||||||
|
if (s.endpoint === endpoint) {
|
||||||
|
found = true;
|
||||||
|
return {
|
||||||
|
endpoint,
|
||||||
|
stash_id: updatedScene.remote_site_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
formik.setFieldValue(
|
||||||
|
"stash_ids",
|
||||||
|
formik.values.stash_ids.concat({
|
||||||
|
endpoint,
|
||||||
|
stash_id: updatedScene.remote_site_id,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onScrapeSceneURL() {
|
async function onScrapeSceneURL() {
|
||||||
@@ -771,11 +813,16 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group controlId="details">
|
{formik.values.stash_ids.length ? (
|
||||||
<Form.Label>StashIDs</Form.Label>
|
<Form.Group controlId="stashIDs">
|
||||||
|
<Form.Label>
|
||||||
|
<FormattedMessage id="stash_ids" />
|
||||||
|
</Form.Label>
|
||||||
<ul className="pl-0">
|
<ul className="pl-0">
|
||||||
{formik.values.stash_ids.map((stashID) => {
|
{formik.values.stash_ids.map((stashID) => {
|
||||||
const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0];
|
const base = stashID.endpoint.match(
|
||||||
|
/https?:\/\/.*?\//
|
||||||
|
)?.[0];
|
||||||
const link = base ? (
|
const link = base ? (
|
||||||
<a
|
<a
|
||||||
href={`${base}scenes/${stashID.stash_id}`}
|
href={`${base}scenes/${stashID.stash_id}`}
|
||||||
@@ -794,7 +841,11 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
className="mr-2 py-0"
|
className="mr-2 py-0"
|
||||||
title={intl.formatMessage(
|
title={intl.formatMessage(
|
||||||
{ id: "actions.delete_entity" },
|
{ id: "actions.delete_entity" },
|
||||||
{ entityType: intl.formatMessage({ id: "stash_id" }) }
|
{
|
||||||
|
entityType: intl.formatMessage({
|
||||||
|
id: "stash_id",
|
||||||
|
}),
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
onClick={() => removeStashID(stashID)}
|
onClick={() => removeStashID(stashID)}
|
||||||
>
|
>
|
||||||
@@ -806,6 +857,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-lg-5 col-xl-12">
|
<div className="col-12 col-lg-5 col-xl-12">
|
||||||
<Form.Group controlId="details">
|
<Form.Group controlId="details">
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ function renderScrapedTagsRow(
|
|||||||
interface ISceneScrapeDialogProps {
|
interface ISceneScrapeDialogProps {
|
||||||
scene: Partial<GQL.SceneUpdateInput>;
|
scene: Partial<GQL.SceneUpdateInput>;
|
||||||
scraped: GQL.ScrapedScene;
|
scraped: GQL.ScrapedScene;
|
||||||
|
endpoint?: string;
|
||||||
|
|
||||||
onClose: (scrapedScene?: GQL.ScrapedScene) => void;
|
onClose: (scrapedScene?: GQL.ScrapedScene) => void;
|
||||||
}
|
}
|
||||||
@@ -238,28 +239,33 @@ interface IHasStoredID {
|
|||||||
stored_id?: string | null;
|
stored_id?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||||
props: ISceneScrapeDialogProps
|
scene,
|
||||||
) => {
|
scraped,
|
||||||
|
onClose,
|
||||||
|
endpoint,
|
||||||
|
}) => {
|
||||||
const [title, setTitle] = useState<ScrapeResult<string>>(
|
const [title, setTitle] = useState<ScrapeResult<string>>(
|
||||||
new ScrapeResult<string>(props.scene.title, props.scraped.title)
|
new ScrapeResult<string>(scene.title, scraped.title)
|
||||||
);
|
);
|
||||||
const [url, setURL] = useState<ScrapeResult<string>>(
|
const [url, setURL] = useState<ScrapeResult<string>>(
|
||||||
new ScrapeResult<string>(props.scene.url, props.scraped.url)
|
new ScrapeResult<string>(scene.url, scraped.url)
|
||||||
);
|
);
|
||||||
const [date, setDate] = useState<ScrapeResult<string>>(
|
const [date, setDate] = useState<ScrapeResult<string>>(
|
||||||
new ScrapeResult<string>(props.scene.date, props.scraped.date)
|
new ScrapeResult<string>(scene.date, scraped.date)
|
||||||
);
|
);
|
||||||
const [studio, setStudio] = useState<ScrapeResult<string>>(
|
const [studio, setStudio] = useState<ScrapeResult<string>>(
|
||||||
new ScrapeResult<string>(
|
new ScrapeResult<string>(scene.studio_id, scraped.studio?.stored_id)
|
||||||
props.scene.studio_id,
|
|
||||||
props.scraped.studio?.stored_id
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
const [newStudio, setNewStudio] = useState<GQL.ScrapedStudio | undefined>(
|
const [newStudio, setNewStudio] = useState<GQL.ScrapedStudio | undefined>(
|
||||||
props.scraped.studio && !props.scraped.studio.stored_id
|
scraped.studio && !scraped.studio.stored_id ? scraped.studio : undefined
|
||||||
? props.scraped.studio
|
);
|
||||||
: undefined
|
|
||||||
|
const [stashID, setStashID] = useState(
|
||||||
|
new ScrapeResult<string>(
|
||||||
|
scene.stash_ids?.find((s) => s.endpoint === endpoint)?.stash_id,
|
||||||
|
scraped.remote_site_id
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
function mapStoredIdObjects(
|
function mapStoredIdObjects(
|
||||||
@@ -302,39 +308,39 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
|||||||
|
|
||||||
const [performers, setPerformers] = useState<ScrapeResult<string[]>>(
|
const [performers, setPerformers] = useState<ScrapeResult<string[]>>(
|
||||||
new ScrapeResult<string[]>(
|
new ScrapeResult<string[]>(
|
||||||
sortIdList(props.scene.performer_ids),
|
sortIdList(scene.performer_ids),
|
||||||
mapStoredIdObjects(props.scraped.performers ?? undefined)
|
mapStoredIdObjects(scraped.performers ?? undefined)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const [newPerformers, setNewPerformers] = useState<GQL.ScrapedPerformer[]>(
|
const [newPerformers, setNewPerformers] = useState<GQL.ScrapedPerformer[]>(
|
||||||
props.scraped.performers?.filter((t) => !t.stored_id) ?? []
|
scraped.performers?.filter((t) => !t.stored_id) ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
const [movies, setMovies] = useState<ScrapeResult<string[]>>(
|
const [movies, setMovies] = useState<ScrapeResult<string[]>>(
|
||||||
new ScrapeResult<string[]>(
|
new ScrapeResult<string[]>(
|
||||||
sortIdList(props.scene.movies?.map((p) => p.movie_id)),
|
sortIdList(scene.movies?.map((p) => p.movie_id)),
|
||||||
mapStoredIdObjects(props.scraped.movies ?? undefined)
|
mapStoredIdObjects(scraped.movies ?? undefined)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const [newMovies, setNewMovies] = useState<GQL.ScrapedMovie[]>(
|
const [newMovies, setNewMovies] = useState<GQL.ScrapedMovie[]>(
|
||||||
props.scraped.movies?.filter((t) => !t.stored_id) ?? []
|
scraped.movies?.filter((t) => !t.stored_id) ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
const [tags, setTags] = useState<ScrapeResult<string[]>>(
|
const [tags, setTags] = useState<ScrapeResult<string[]>>(
|
||||||
new ScrapeResult<string[]>(
|
new ScrapeResult<string[]>(
|
||||||
sortIdList(props.scene.tag_ids),
|
sortIdList(scene.tag_ids),
|
||||||
mapStoredIdObjects(props.scraped.tags ?? undefined)
|
mapStoredIdObjects(scraped.tags ?? undefined)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const [newTags, setNewTags] = useState<GQL.ScrapedTag[]>(
|
const [newTags, setNewTags] = useState<GQL.ScrapedTag[]>(
|
||||||
props.scraped.tags?.filter((t) => !t.stored_id) ?? []
|
scraped.tags?.filter((t) => !t.stored_id) ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
const [details, setDetails] = useState<ScrapeResult<string>>(
|
const [details, setDetails] = useState<ScrapeResult<string>>(
|
||||||
new ScrapeResult<string>(props.scene.details, props.scraped.details)
|
new ScrapeResult<string>(scene.details, scraped.details)
|
||||||
);
|
);
|
||||||
const [image, setImage] = useState<ScrapeResult<string>>(
|
const [image, setImage] = useState<ScrapeResult<string>>(
|
||||||
new ScrapeResult<string>(props.scene.cover_image, props.scraped.image)
|
new ScrapeResult<string>(scene.cover_image, scraped.image)
|
||||||
);
|
);
|
||||||
|
|
||||||
const [createStudio] = useStudioCreate();
|
const [createStudio] = useStudioCreate();
|
||||||
@@ -347,11 +353,20 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
|||||||
|
|
||||||
// don't show the dialog if nothing was scraped
|
// don't show the dialog if nothing was scraped
|
||||||
if (
|
if (
|
||||||
[title, url, date, studio, performers, movies, tags, details, image].every(
|
[
|
||||||
(r) => !r.scraped
|
title,
|
||||||
)
|
url,
|
||||||
|
date,
|
||||||
|
studio,
|
||||||
|
performers,
|
||||||
|
movies,
|
||||||
|
tags,
|
||||||
|
details,
|
||||||
|
image,
|
||||||
|
stashID,
|
||||||
|
].every((r) => !r.scraped)
|
||||||
) {
|
) {
|
||||||
props.onClose();
|
onClose();
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,6 +550,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
|||||||
}),
|
}),
|
||||||
details: details.getNewValue(),
|
details: details.getNewValue(),
|
||||||
image: image.getNewValue(),
|
image: image.getNewValue(),
|
||||||
|
remote_site_id: stashID.getNewValue(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,6 +606,12 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
|||||||
result={details}
|
result={details}
|
||||||
onChange={(value) => setDetails(value)}
|
onChange={(value) => setDetails(value)}
|
||||||
/>
|
/>
|
||||||
|
<ScrapedInputGroupRow
|
||||||
|
title={intl.formatMessage({ id: "stash_id" })}
|
||||||
|
result={stashID}
|
||||||
|
locked
|
||||||
|
onChange={(value) => setStashID(value)}
|
||||||
|
/>
|
||||||
<ScrapedImageRow
|
<ScrapedImageRow
|
||||||
title={intl.formatMessage({ id: "cover_image" })}
|
title={intl.formatMessage({ id: "cover_image" })}
|
||||||
className="scene-cover"
|
className="scene-cover"
|
||||||
@@ -608,7 +630,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
|||||||
)}
|
)}
|
||||||
renderScrapeRows={renderScrapeRows}
|
renderScrapeRows={renderScrapeRows}
|
||||||
onClose={(apply) => {
|
onClose={(apply) => {
|
||||||
props.onClose(apply ? makeNewScrapedItem() : undefined);
|
onClose(apply ? makeNewScrapedItem() : undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user