import React, { useState, useEffect } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { Button, Form } from "react-bootstrap"; import { mutateMetadataScan, mutateMetadataAutoTag, mutateMetadataGenerate, useConfigureDefaults, } from "src/core/StashService"; import { withoutTypename } from "src/utils"; import { ConfigurationContext } from "src/hooks/Config"; import { IdentifyDialog } from "../../Dialogs/IdentifyDialog/IdentifyDialog"; import * as GQL from "src/core/generated-graphql"; import { DirectorySelectionDialog } from "./DirectorySelectionDialog"; import { ScanOptions } from "./ScanOptions"; import { useToast } from "src/hooks"; import { GenerateOptions } from "./GenerateOptions"; import { SettingSection } from "../SettingSection"; import { BooleanSetting, Setting, SettingGroup } from "../Inputs"; import { ManualLink } from "src/components/Help/Manual"; import { Icon } from "src/components/Shared"; interface IAutoTagOptions { options: GQL.AutoTagMetadataInput; setOptions: (s: GQL.AutoTagMetadataInput) => void; } const AutoTagOptions: React.FC = ({ options, setOptions: setOptionsState, }) => { const { performers, studios, tags } = options; const wildcard = ["*"]; function set(v?: boolean) { if (v) { return wildcard; } return []; } function setOptions(input: Partial) { setOptionsState({ ...options, ...input }); } return ( <> setOptions({ performers: set(v) })} /> setOptions({ studios: set(v) })} /> setOptions({ tags: set(v) })} /> ); }; export const LibraryTasks: React.FC = () => { const intl = useIntl(); const Toast = useToast(); const [configureDefaults] = useConfigureDefaults(); const [dialogOpen, setDialogOpenState] = useState({ clean: false, scan: false, autoTag: false, identify: false, }); const [scanOptions, setScanOptions] = useState({}); const [ autoTagOptions, setAutoTagOptions, ] = useState({ performers: ["*"], studios: ["*"], tags: ["*"], }); function getDefaultGenerateOptions(): GQL.GenerateMetadataInput { return { sprites: true, phashes: true, previews: true, markers: true, previewOptions: { previewSegments: 0, previewSegmentDuration: 0, previewPreset: GQL.PreviewPreset.Slow, }, }; } const [ generateOptions, setGenerateOptions, ] = useState(getDefaultGenerateOptions()); type DialogOpenState = typeof dialogOpen; const { configuration } = React.useContext(ConfigurationContext); useEffect(() => { if (!configuration?.defaults) { return; } const { scan, autoTag } = configuration.defaults; if (scan) { setScanOptions(withoutTypename(scan)); } if (autoTag) { setAutoTagOptions(withoutTypename(autoTag)); } if (configuration?.defaults.generate) { const { generate } = configuration.defaults; setGenerateOptions(withoutTypename(generate)); } else if (configuration?.general) { // backwards compatibility const { general } = configuration; setGenerateOptions((existing) => ({ ...existing, previewOptions: { ...existing.previewOptions, previewSegments: general.previewSegments ?? existing.previewOptions?.previewSegments, previewSegmentDuration: general.previewSegmentDuration ?? existing.previewOptions?.previewSegmentDuration, previewExcludeStart: general.previewExcludeStart ?? existing.previewOptions?.previewExcludeStart, previewExcludeEnd: general.previewExcludeEnd ?? existing.previewOptions?.previewExcludeEnd, previewPreset: general.previewPreset ?? existing.previewOptions?.previewPreset, }, })); } }, [configuration]); function setDialogOpen(s: Partial) { setDialogOpenState((v) => { return { ...v, ...s }; }); } function renderScanDialog() { if (!dialogOpen.scan) { return; } return ; } function onScanDialogClosed(paths?: string[]) { if (paths) { runScan(paths); } setDialogOpen({ scan: false }); } async function runScan(paths?: string[]) { try { configureDefaults({ variables: { input: { scan: scanOptions, }, }, }); await mutateMetadataScan({ ...scanOptions, paths, }); Toast.success({ content: intl.formatMessage( { id: "config.tasks.added_job_to_queue" }, { operation_name: intl.formatMessage({ id: "actions.scan" }) } ), }); } catch (e) { Toast.error(e); } } function renderAutoTagDialog() { if (!dialogOpen.autoTag) { return; } return ; } function onAutoTagDialogClosed(paths?: string[]) { if (paths) { runAutoTag(paths); } setDialogOpen({ autoTag: false }); } async function runAutoTag(paths?: string[]) { try { configureDefaults({ variables: { input: { autoTag: autoTagOptions, }, }, }); await mutateMetadataAutoTag({ ...autoTagOptions, paths, }); Toast.success({ content: intl.formatMessage( { id: "config.tasks.added_job_to_queue" }, { operation_name: intl.formatMessage({ id: "actions.auto_tag" }) } ), }); } catch (e) { Toast.error(e); } } function maybeRenderIdentifyDialog() { if (!dialogOpen.identify) return; return ( setDialogOpen({ identify: false })} /> ); } async function onGenerateClicked() { try { configureDefaults({ variables: { input: { generate: generateOptions, }, }, }); await mutateMetadataGenerate(generateOptions); Toast.success({ content: intl.formatMessage( { id: "config.tasks.added_job_to_queue" }, { operation_name: intl.formatMessage({ id: "actions.generate" }) } ), }); } catch (e) { Toast.error(e); } } return ( {renderScanDialog()} {renderAutoTagDialog()} {maybeRenderIdentifyDialog()} ), subHeadingID: "config.tasks.scan_for_content_desc", }} topLevel={ <> } collapsible > } subHeadingID="config.tasks.identify.description" > ), subHeadingID: "config.tasks.auto_tag_based_on_filenames", }} topLevel={ <> } collapsible > setAutoTagOptions(o)} /> ), subHeadingID: "config.tasks.generate_desc", }} topLevel={ } collapsible > ); };