Support StashIDs in scrape dialog (#1955)

This commit is contained in:
WithoutPants
2021-11-05 12:10:29 +11:00
committed by GitHub
parent 392b28915a
commit dbfd92f9a8
3 changed files with 140 additions and 65 deletions

View File

@@ -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))

View File

@@ -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,41 +813,51 @@ 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">
<ul className="pl-0"> <Form.Label>
{formik.values.stash_ids.map((stashID) => { <FormattedMessage id="stash_ids" />
const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0]; </Form.Label>
const link = base ? ( <ul className="pl-0">
<a {formik.values.stash_ids.map((stashID) => {
href={`${base}scenes/${stashID.stash_id}`} const base = stashID.endpoint.match(
target="_blank" /https?:\/\/.*?\//
rel="noopener noreferrer" )?.[0];
> const link = base ? (
{stashID.stash_id} <a
</a> href={`${base}scenes/${stashID.stash_id}`}
) : ( target="_blank"
stashID.stash_id rel="noopener noreferrer"
);
return (
<li key={stashID.stash_id} className="row no-gutters">
<Button
variant="danger"
className="mr-2 py-0"
title={intl.formatMessage(
{ id: "actions.delete_entity" },
{ entityType: intl.formatMessage({ id: "stash_id" }) }
)}
onClick={() => removeStashID(stashID)}
> >
<Icon icon="trash-alt" /> {stashID.stash_id}
</Button> </a>
{link} ) : (
</li> stashID.stash_id
); );
})} return (
</ul> <li key={stashID.stash_id} className="row no-gutters">
</Form.Group> <Button
variant="danger"
className="mr-2 py-0"
title={intl.formatMessage(
{ id: "actions.delete_entity" },
{
entityType: intl.formatMessage({
id: "stash_id",
}),
}
)}
onClick={() => removeStashID(stashID)}
>
<Icon icon="trash-alt" />
</Button>
{link}
</li>
);
})}
</ul>
</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">

View File

@@ -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);
}} }}
/> />
); );