import React, { useEffect, useMemo, useState } from "react"; import { FormattedMessage, IntlShape, useIntl } from "react-intl"; import { useFindSavedFilters } from "src/core/StashService"; import { LoadingIndicator } from "src/components/Shared"; import { Button, Form, Modal } from "react-bootstrap"; import { FilterMode, FindSavedFiltersQuery, SavedFilter, } from "src/core/generated-graphql"; import { ConfigurationContext } from "src/hooks/Config"; import { IUIConfig, ISavedFilterRow, ICustomFilter, FrontPageContent, generatePremadeFrontPageContent, } from "src/core/config"; interface IAddSavedFilterModalProps { onClose: (content?: FrontPageContent) => void; existingSavedFilterIDs: string[]; candidates: FindSavedFiltersQuery; } const FilterModeToMessageID = { [FilterMode.Galleries]: "galleries", [FilterMode.Images]: "images", [FilterMode.Movies]: "movies", [FilterMode.Performers]: "performers", [FilterMode.SceneMarkers]: "markers", [FilterMode.Scenes]: "scenes", [FilterMode.Studios]: "studios", [FilterMode.Tags]: "tags", }; function filterTitle(intl: IntlShape, f: Pick) { return `${intl.formatMessage({ id: FilterModeToMessageID[f.mode] })}: ${ f.name }`; } const AddContentModal: React.FC = ({ onClose, existingSavedFilterIDs, candidates, }) => { const intl = useIntl(); const premadeFilterOptions = useMemo( () => generatePremadeFrontPageContent(intl), [intl] ); const [contentType, setContentType] = useState( "front_page.types.premade_filter" ); const [premadeFilterIndex, setPremadeFilterIndex] = useState< number | undefined >(0); const [savedFilter, setSavedFilter] = useState(); function onTypeSelected(t: string) { setContentType(t); switch (t) { case "front_page.types.premade_filter": setPremadeFilterIndex(0); setSavedFilter(undefined); break; case "front_page.types.saved_filter": setPremadeFilterIndex(undefined); setSavedFilter(undefined); break; } } function isValid() { switch (contentType) { case "front_page.types.premade_filter": return premadeFilterIndex !== undefined; case "front_page.types.saved_filter": return savedFilter !== undefined; } return false; } const savedFilterOptions = useMemo(() => { const ret = [ { value: "", text: "", }, ].concat( candidates.findSavedFilters .filter((f) => { // markers not currently supported return ( f.mode !== FilterMode.SceneMarkers && !existingSavedFilterIDs.includes(f.id) ); }) .map((f) => { return { value: f.id, text: filterTitle(intl, f), }; }) ); ret.sort((a, b) => { return a.text.localeCompare(b.text); }); return ret; }, [candidates, existingSavedFilterIDs, intl]); function renderTypeSelect() { const options = [ "front_page.types.premade_filter", "front_page.types.saved_filter", ]; return ( onTypeSelected(e.target.value)} className="btn-secondary" > {options.map((c) => ( ))} ); } function maybeRenderPremadeFiltersSelect() { if (contentType !== "front_page.types.premade_filter") return; return ( setPremadeFilterIndex(parseInt(e.target.value))} className="btn-secondary" > {premadeFilterOptions.map((c, i) => ( ))} ); } function maybeRenderSavedFiltersSelect() { if (contentType !== "front_page.types.saved_filter") return; return ( setSavedFilter(e.target.value)} className="btn-secondary" > {savedFilterOptions.map((c) => ( ))} ); } function doAdd() { switch (contentType) { case "front_page.types.premade_filter": onClose(premadeFilterOptions[premadeFilterIndex!]); return; case "front_page.types.saved_filter": onClose({ __typename: "SavedFilter", savedFilterId: parseInt(savedFilter!), }); return; } onClose(); } return ( onClose()}>
{renderTypeSelect()} {maybeRenderSavedFiltersSelect()} {maybeRenderPremadeFiltersSelect()}
); }; interface IFilterRowProps { content: FrontPageContent; allSavedFilters: Pick[]; onDelete: () => void; } const ContentRow: React.FC = (props: IFilterRowProps) => { const intl = useIntl(); function title() { switch (props.content.__typename) { case "SavedFilter": const savedFilter = props.allSavedFilters.find( (f) => f.id === (props.content as ISavedFilterRow).savedFilterId.toString() ); if (!savedFilter) return ""; return filterTitle(intl, savedFilter); case "CustomFilter": const asCustomFilter = props.content as ICustomFilter; if (asCustomFilter.message) return intl.formatMessage( { id: asCustomFilter.message.id }, asCustomFilter.message.values ); return asCustomFilter.title ?? ""; } } return (

{title()}

); }; interface IFrontPageConfigProps { onClose: (content?: FrontPageContent[]) => void; } export const FrontPageConfig: React.FC = ({ onClose, }) => { const { configuration, loading } = React.useContext(ConfigurationContext); const ui = configuration?.ui as IUIConfig; const { data: allFilters, loading: loading2 } = useFindSavedFilters(); const [isAdd, setIsAdd] = useState(false); const [currentContent, setCurrentContent] = useState([]); const [dragIndex, setDragIndex] = useState(); useEffect(() => { if (!allFilters?.findSavedFilters) { return; } if (ui?.frontPageContent) { setCurrentContent(ui.frontPageContent); } }, [allFilters, ui]); function onDragStart(event: React.DragEvent, index: number) { event.dataTransfer.effectAllowed = "move"; setDragIndex(index); } function onDragOver(event: React.DragEvent, index?: number) { if (dragIndex !== undefined && index !== undefined && index !== dragIndex) { const newFilters = [...currentContent]; const moved = newFilters.splice(dragIndex, 1); newFilters.splice(index, 0, moved[0]); setCurrentContent(newFilters); setDragIndex(index); } event.dataTransfer.dropEffect = "move"; event.preventDefault(); } function onDragOverDefault(event: React.DragEvent) { event.dataTransfer.dropEffect = "move"; event.preventDefault(); } function onDrop() { // assume we've already set the temp filter list // feed it up setDragIndex(undefined); } if (loading || loading2) { return ; } const existingSavedFilterIDs = currentContent .filter((f) => f.__typename === "SavedFilter") .map((f) => (f as ISavedFilterRow).savedFilterId.toString()); function addSavedFilter(content?: FrontPageContent) { setIsAdd(false); if (!content) { return; } setCurrentContent([...currentContent, content]); } function deleteSavedFilter(index: number) { setCurrentContent(currentContent.filter((f, i) => i !== index)); } return ( <> {isAdd && allFilters && ( )}
{currentContent.map((content, index) => (
onDragStart(e, index)} onDragEnter={(e) => onDragOver(e, index)} onDrop={() => onDrop()} > deleteSavedFilter(index)} />
))}
); };