/* eslint-disable react/no-this-in-sfc */ import React, { useEffect, useState } from "react"; import { Collapse, Dropdown, DropdownButton, Form, Button, Spinner } from "react-bootstrap"; import * as GQL from "src/core/generated-graphql"; import { StashService } from "src/core/StashService"; import { FilterSelect, StudioSelect, SceneGallerySelect, Modal, Icon } from "src/components/Shared"; import { useToast } from "src/hooks"; import { ImageUtils } from "src/utils"; interface IProps { scene: GQL.SceneDataFragment; onUpdate: (scene: GQL.SceneDataFragment) => void; onDelete: () => void; } export const SceneEditPanel: React.FC = (props: IProps) => { const Toast = useToast(); const [title, setTitle] = useState(); const [details, setDetails] = useState(); const [url, setUrl] = useState(); const [date, setDate] = useState(); const [rating, setRating] = useState(); const [galleryId, setGalleryId] = useState(); const [studioId, setStudioId] = useState(); const [performerIds, setPerformerIds] = useState(); const [tagIds, setTagIds] = useState(); const [coverImage, setCoverImage] = useState(); const Scrapers = StashService.useListSceneScrapers(); const [queryableScrapers, setQueryableScrapers] = useState([]); const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); const [deleteFile, setDeleteFile] = useState(false); const [deleteGenerated, setDeleteGenerated] = useState(true); const [isCoverImageOpen, setIsCoverImageOpen] = useState(false); const [coverImagePreview, setCoverImagePreview] = useState(); // Network state const [isLoading, setIsLoading] = useState(false); const [updateScene] = StashService.useSceneUpdate(getSceneInput()); const [deleteScene] = StashService.useSceneDestroy(getSceneDeleteInput()); useEffect(() => { const newQueryableScrapers = (Scrapers?.data?.listSceneScrapers ?? []).filter(s => ( s.scene?.supported_scrapes.includes(GQL.ScrapeType.Fragment) )); setQueryableScrapers(newQueryableScrapers); }, [Scrapers]); function updateSceneEditState(state: Partial) { const perfIds = state.performers ? state.performers.map(performer => performer.id) : undefined; const tIds = state.tags ? state.tags.map(tag => tag.id) : undefined; setTitle(state.title ?? undefined); setDetails(state.details ?? undefined); setUrl(state.url ?? undefined); setDate(state.date ?? undefined); setRating(state.rating === null ? NaN : state.rating); setGalleryId(state?.gallery?.id ?? undefined); setStudioId(state?.studio?.id ?? undefined); setPerformerIds(perfIds); setTagIds(tIds); } useEffect(() => { updateSceneEditState(props.scene); setCoverImagePreview(props.scene?.paths?.screenshot ?? undefined); }, [props.scene]); ImageUtils.usePasteImage(onImageLoad); function getSceneInput(): GQL.SceneUpdateInput { return { id: props.scene.id, title, details, url, date, rating, gallery_id: galleryId, studio_id: studioId, performer_ids: performerIds, tag_ids: tagIds, cover_image: coverImage }; } async function onSave() { setIsLoading(true); try { const result = await updateScene(); if(result.data?.sceneUpdate) { props.onUpdate(result.data.sceneUpdate); Toast.success({ content: "Updated scene" }); } } catch (e) { Toast.error(e); } setIsLoading(false); } function getSceneDeleteInput(): GQL.SceneDestroyInput { return { id: props.scene.id, delete_file: deleteFile, delete_generated: deleteGenerated }; } async function onDelete() { setIsDeleteAlertOpen(false); setIsLoading(true); try { await deleteScene(); Toast.success({ content: "Deleted scene" }); } catch (e) { Toast.error(e); } setIsLoading(false); props.onDelete(); } function renderMultiSelect( type: "performers" | "tags", initialIds: string[] = [] ) { return ( { const ids = items.map(i => i.id); switch (type) { case "performers": setPerformerIds(ids); break; case "tags": setTagIds(ids); break; } }} initialIds={initialIds} /> ); } function renderDeleteAlert() { return ( setIsDeleteAlertOpen(false), text: "Cancel" }} >

Are you sure you want to delete this scene? Unless the file is also deleted, this scene will be re-added when scan is performed.

setDeleteFile(!deleteFile)} /> setDeleteGenerated(!deleteGenerated)} />
); } function onImageLoad(this: FileReader) { setCoverImagePreview(this.result as string); setCoverImage(this.result as string); } function onCoverImageChange(event: React.FormEvent) { ImageUtils.onImageChange(event, onImageLoad); } async function onScrapeClicked( scraper: GQL.Scraper ) { setIsLoading(true); try { const result = await StashService.queryScrapeScene( scraper.id, getSceneInput() ); if (!result.data || !result.data.scrapeScene) { return; } updateSceneFromScrapedScene(result.data.scrapeScene); } catch (e) { Toast.error(e); } finally { setIsLoading(false); } } function renderScraperMenu() { if (!queryableScrapers || queryableScrapers.length === 0) { return; } return ( {queryableScrapers.map(s => ( onScrapeClicked(s)}> {s.name} ))} ); } function urlScrapable(scrapedUrl: string): boolean { return (Scrapers?.data?.listSceneScrapers ?? []).some(s => (s?.scene?.urls ?? []).some(u => scrapedUrl.includes(u)) ); } function updateSceneFromScrapedScene(scene: GQL.ScrapedSceneDataFragment) { if (!title && scene.title) { setTitle(scene.title); } if (!details && scene.details) { setDetails(scene.details); } if (!date && scene.date) { setDate(scene.date); } if (!url && scene.url) { setUrl(scene.url); } if (!studioId && scene.studio && scene.studio.id) { setStudioId(scene.studio.id); } if ( (!performerIds || performerIds.length === 0) && scene.performers && scene.performers.length > 0 ) { const idPerfs = scene.performers.filter(p => { return p.id !== undefined && p.id !== null; }); if (idPerfs.length > 0) { const newIds = idPerfs.map(p => p.id); setPerformerIds(newIds as string[]); } } if ( (!tagIds || tagIds.length === 0) && scene.tags && scene.tags.length > 0 ) { const idTags = scene.tags.filter(p => { return p.id !== undefined && p.id !== null; }); if (idTags.length > 0) { const newIds = idTags.map(p => p.id); setTagIds(newIds as string[]); } } } async function onScrapeSceneURL() { if (!url) { return; } setIsLoading(true); try { const result = await StashService.queryScrapeSceneURL(url); if (!result.data || !result.data.scrapeSceneURL) { return; } updateSceneFromScrapedScene(result.data.scrapeSceneURL); } catch (e) { Toast.error(e); } finally { setIsLoading(false); } } function maybeRenderScrapeButton() { if (!url || !urlScrapable(url)) { return undefined; } return ( ); } return ( <> {renderDeleteAlert()} {isLoading ? : undefined}
Title setTitle(newValue.target.value)} value={title} /> Details setDetails(newValue.target.value)} value={details} /> URL setUrl(newValue.target.value)} value={url} /> {maybeRenderScrapeButton()} Date setDate(newValue.target.value)} value={date} />
YYYY-MM-DD
Rating setRating(parseInt(event.target.value, 10)) } > {["", 1, 2, 3, 4, 5].map(opt => ( ))} Gallery setGalleryId(item ? item.id : undefined)} /> Studio items.length && setStudioId(items[0]?.id)} initialIds={studioId ? [studioId] : []} /> Performers {renderMultiSelect("performers", performerIds)} Tags {renderMultiSelect("tags", tagIds)}
{renderScraperMenu()} ); };