diff --git a/ui/v2.5/src/components/Groups/GroupDetails/GroupScenesPanel.tsx b/ui/v2.5/src/components/Groups/GroupDetails/GroupScenesPanel.tsx index ab9ec3fea..a03424789 100644 --- a/ui/v2.5/src/components/Groups/GroupDetails/GroupScenesPanel.tsx +++ b/ui/v2.5/src/components/Groups/GroupDetails/GroupScenesPanel.tsx @@ -5,7 +5,7 @@ import { GroupsCriterionOption, } from "src/models/list-filter/criteria/groups"; import { ListFilterModel } from "src/models/list-filter/filter"; -import { SceneList } from "src/components/Scenes/SceneList"; +import { FilteredSceneList } from "src/components/Scenes/SceneList"; import { View } from "src/components/List/views"; interface IGroupScenesPanel { @@ -64,7 +64,7 @@ export const GroupScenesPanel: React.FC = ({ if (group && group.id) { return ( - = ({ ); }; + +export function useShowEditFilter(props: { + filter: ListFilterModel; + setFilter: (f: ListFilterModel) => void; + showModal: (content: React.ReactNode) => void; + closeModal: () => void; +}) { + const { filter, setFilter, showModal, closeModal } = props; + + const showEditFilter = useCallback( + (editingCriterion?: string) => { + function onApplyEditFilter(f: ListFilterModel) { + closeModal(); + setFilter(f); + } + + showModal( + closeModal()} + editingCriterion={editingCriterion} + /> + ); + }, + [filter, setFilter, showModal, closeModal] + ); + + return showEditFilter; +} diff --git a/ui/v2.5/src/components/List/FilteredListToolbar.tsx b/ui/v2.5/src/components/List/FilteredListToolbar.tsx index 6018dd836..7464c25ac 100644 --- a/ui/v2.5/src/components/List/FilteredListToolbar.tsx +++ b/ui/v2.5/src/components/List/FilteredListToolbar.tsx @@ -8,11 +8,9 @@ import { IListFilterOperation, ListOperationButtons, } from "./ListOperationButtons"; -import { DisplayMode } from "src/models/list-filter/types"; import { ButtonToolbar } from "react-bootstrap"; import { View } from "./views"; -import { useListContext } from "./ListProvider"; -import { useFilter } from "./FilterProvider"; +import { IListSelect, useFilterOperations } from "./util"; export interface IItemListOperation { text: string; @@ -32,8 +30,13 @@ export interface IItemListOperation { } export interface IFilteredListToolbar { - showEditFilter?: (editingCriterion?: string) => void; + filter: ListFilterModel; + setFilter: ( + value: ListFilterModel | ((prevState: ListFilterModel) => ListFilterModel) + ) => void; + showEditFilter: () => void; view?: View; + listSelect: IListSelect; onEdit?: () => void; onDelete?: () => void; operations?: IListFilterOperation[]; @@ -41,25 +44,22 @@ export interface IFilteredListToolbar { } export const FilteredListToolbar: React.FC = ({ + filter, + setFilter, showEditFilter, view, + listSelect, onEdit, onDelete, operations, zoomable = false, }) => { - const { getSelected, onSelectAll, onSelectNone } = useListContext(); - const { filter, setFilter } = useFilter(); - const filterOptions = filter.options; - - function onChangeDisplayMode(displayMode: DisplayMode) { - setFilter(filter.setDisplayMode(displayMode)); - } - - function onChangeZoom(newZoomIndex: number) { - setFilter(filter.setZoom(newZoomIndex)); - } + const { setDisplayMode, setZoom } = useFilterOperations({ + filter, + setFilter, + }); + const { selectedIds, onSelectAll, onSelectNone } = listSelect; return ( @@ -75,16 +75,16 @@ export const FilteredListToolbar: React.FC = ({ onSelectAll={onSelectAll} onSelectNone={onSelectNone} otherOperations={operations} - itemsSelected={getSelected().length > 0} + itemsSelected={selectedIds.size > 0} onEdit={onEdit} onDelete={onDelete} /> ); diff --git a/ui/v2.5/src/components/List/ItemList.tsx b/ui/v2.5/src/components/List/ItemList.tsx index bf937b2b4..4ffeff766 100644 --- a/ui/v2.5/src/components/List/ItemList.tsx +++ b/ui/v2.5/src/components/List/ItemList.tsx @@ -10,7 +10,10 @@ import * as GQL from "src/core/generated-graphql"; import { QueryResult } from "@apollo/client"; import { Criterion } from "src/models/list-filter/criteria/criterion"; import { ListFilterModel } from "src/models/list-filter/filter"; -import { EditFilterDialog } from "src/components/List/EditFilterDialog"; +import { + EditFilterDialog, + useShowEditFilter, +} from "src/components/List/EditFilterDialog"; import { FilterTags } from "./FilterTags"; import { View } from "./views"; import { IHasID } from "src/utils/data"; @@ -23,9 +26,15 @@ import { import { FilterContext, SetFilterURL, useFilter } from "./FilterProvider"; import { useModal } from "src/hooks/modal"; import { + IFilterStateHook, + IQueryResultHook, useDefaultFilter, useEnsureValidPage, + useFilterOperations, + useFilterState, useListKeyboardShortcuts, + useListSelect, + useQueryResult, useScrollToTopOnPageChange, } from "./util"; import { @@ -36,6 +45,72 @@ import { import { PagedList } from "./PagedList"; import { ConfigurationContext } from "src/hooks/Config"; +interface IFilteredItemList { + filterStateProps: IFilterStateHook; + queryResultProps: IQueryResultHook; +} + +// Provides the common state and behaviour for filtered item list components +export function useFilteredItemList< + T extends QueryResult, + E extends IHasID = IHasID +>(props: IFilteredItemList) { + const { configuration: config } = useContext(ConfigurationContext); + + // States + const filterState = useFilterState({ + config, + ...props.filterStateProps, + }); + + const { filter, setFilter } = filterState; + + const queryResult = useQueryResult({ + filter, + ...props.queryResultProps, + }); + const { result, items, totalCount, pages } = queryResult; + + const listSelect = useListSelect(items); + const { onSelectAll, onSelectNone } = listSelect; + + const modalState = useModal(); + const { showModal, closeModal } = modalState; + + // Utility hooks + const { setPage } = useFilterOperations({ filter, setFilter }); + + // scroll to the top of the page when the page changes + useScrollToTopOnPageChange(filter.currentPage, result.loading); + + // ensure that the current page is valid + useEnsureValidPage(filter, totalCount, setFilter); + + const showEditFilter = useShowEditFilter({ + showModal, + closeModal, + filter, + setFilter, + }); + + useListKeyboardShortcuts({ + currentPage: filter.currentPage, + onChangePage: setPage, + onSelectAll, + onSelectNone, + pages, + showEditFilter, + }); + + return { + filterState, + queryResult, + listSelect, + modalState, + showEditFilter, + }; +} + interface IItemListProps { view?: View; zoomable?: boolean; @@ -83,13 +158,14 @@ export const ItemList = ( const { filter, setFilter: updateFilter } = useFilter(); const { effectiveFilter, result, cachedResult, totalCount } = useQueryResultContext(); + const listSelect = useListContext(); const { selectedIds, getSelected, onSelectChange, onSelectAll, onSelectNone, - } = useListContext(); + } = listSelect; // scroll to the top of the page when the page changes useScrollToTopOnPageChange(filter.currentPage, result.loading); @@ -236,7 +312,10 @@ export const ItemList = ( updateFilter(filter.clearCriteria()); } - const filterListToolbarProps = { + const filterListToolbarProps: IFilteredListToolbar = { + filter, + setFilter: updateFilter, + listSelect, showEditFilter, view: view, operations: operations, diff --git a/ui/v2.5/src/components/List/ListProvider.tsx b/ui/v2.5/src/components/List/ListProvider.tsx index 6ef0d5055..2e8854586 100644 --- a/ui/v2.5/src/components/List/ListProvider.tsx +++ b/ui/v2.5/src/components/List/ListProvider.tsx @@ -29,22 +29,12 @@ export const ListContext = ( ) => { const { selectable = false, items, children } = props; - const { - selectedIds, - getSelected, - onSelectChange, - onSelectAll, - onSelectNone, - } = useListSelect(items); + const listSelect = useListSelect(items); const state: IListContextState = { selectable, - selectedIds, - getSelected, - onSelectChange, - onSelectAll, - onSelectNone, items, + ...listSelect, }; return ( @@ -74,6 +64,8 @@ const emptyState: IListContextState = { onSelectAll: () => {}, onSelectNone: () => {}, items: [], + hasSelection: false, + selectedItems: [], }; export function useListContextOptional() { diff --git a/ui/v2.5/src/components/List/util.ts b/ui/v2.5/src/components/List/util.ts index 329eba289..bb85145e7 100644 --- a/ui/v2.5/src/components/List/util.ts +++ b/ui/v2.5/src/components/List/util.ts @@ -8,6 +8,9 @@ import { IHasID } from "src/utils/data"; import { ConfigurationContext } from "src/hooks/Config"; import { View } from "./views"; import { usePrevious } from "src/hooks/state"; +import * as GQL from "src/core/generated-graphql"; +import { DisplayMode } from "src/models/list-filter/types"; +import { Criterion } from "src/models/list-filter/criteria/criterion"; export function useFilterURL( filter: ListFilterModel, @@ -106,6 +109,106 @@ export function useDefaultFilter(emptyFilter: ListFilterModel, view?: View) { return { defaultFilter: retFilter, loading }; } +function useEmptyFilter(props: { + filterMode: GQL.FilterMode; + defaultSort?: string; + config?: GQL.ConfigDataFragment; +}) { + const { filterMode, defaultSort, config } = props; + + const emptyFilter = useMemo( + () => + new ListFilterModel(filterMode, config, { + defaultSortBy: defaultSort, + }), + [config, filterMode, defaultSort] + ); + + return emptyFilter; +} + +export interface IFilterStateHook { + filterMode: GQL.FilterMode; + defaultSort?: string; + view?: View; + useURL?: boolean; +} + +export function useFilterState( + props: IFilterStateHook & { + config?: GQL.ConfigDataFragment; + } +) { + const { filterMode, defaultSort, config, view, useURL } = props; + + const [filter, setFilterState] = useState( + () => + new ListFilterModel(filterMode, config, { defaultSortBy: defaultSort }) + ); + + const emptyFilter = useEmptyFilter({ filterMode, defaultSort, config }); + + const { defaultFilter, loading } = useDefaultFilter(emptyFilter, view); + + const { setFilter } = useFilterURL(filter, setFilterState, { + defaultFilter, + active: useURL, + }); + + return { loading, filter, setFilter }; +} + +export function useFilterOperations(props: { + filter: ListFilterModel; + setFilter: ( + value: ListFilterModel | ((prevState: ListFilterModel) => ListFilterModel) + ) => void; +}) { + const { setFilter } = props; + + const setPage = useCallback( + (p: number) => { + setFilter((cv) => cv.changePage(p)); + }, + [setFilter] + ); + + const setDisplayMode = useCallback( + (displayMode: DisplayMode) => { + setFilter((cv) => cv.setDisplayMode(displayMode)); + }, + [setFilter] + ); + + const setZoom = useCallback( + (newZoomIndex: number) => { + setFilter((cv) => cv.setZoom(newZoomIndex)); + }, + [setFilter] + ); + + const removeCriterion = useCallback( + (removedCriterion: Criterion) => { + setFilter((cv) => + cv.removeCriterion(removedCriterion.criterionOption.type) + ); + }, + [setFilter] + ); + + const clearAllCriteria = useCallback(() => { + setFilter((cv) => cv.clearCriteria()); + }, [setFilter]); + + return { + setPage, + setDisplayMode, + setZoom, + removeCriterion, + clearAllCriteria, + }; +} + export function useListKeyboardShortcuts(props: { currentPage?: number; onChangePage?: (page: number) => void; @@ -190,10 +293,11 @@ export function useListKeyboardShortcuts(props: { }, [onSelectAll, onSelectNone]); } -export function useListSelect(items: T[]) { +export function useListSelect(items: T[]) { const [itemsSelected, setItemsSelected] = useState([]); const [lastClickedId, setLastClickedId] = useState(); + // TODO - this doesn't get updated when items changes const selectedIds = useMemo(() => { const newSelectedIds = new Set(); itemsSelected.forEach((item) => { @@ -303,18 +407,26 @@ export function useListSelect(items: T[]) { setLastClickedId(undefined); } + // TODO - this is for backwards compatibility const getSelected = useCallback(() => itemsSelected, [itemsSelected]); + // convenience state + const hasSelection = itemsSelected.length > 0; + return { + selectedItems: itemsSelected, selectedIds, getSelected, onSelectChange, onSelectAll, onSelectNone, + hasSelection, }; } -export type IListSelect = ReturnType>; +export type IListSelect = ReturnType< + typeof useListSelect +>; // returns true if the filter has changed in a way that impacts the total count function totalCountImpacted( @@ -358,6 +470,81 @@ export function useCachedQueryResult( return cachedResult; } +export interface IQueryResultHook< + T extends QueryResult, + E extends IHasID = IHasID +> { + filterHook?: (filter: ListFilterModel) => ListFilterModel; + useResult: (filter: ListFilterModel) => T; + getCount: (data: T) => number; + getItems: (data: T) => E[]; +} + +export function useQueryResult< + T extends QueryResult, + E extends IHasID = IHasID +>( + props: IQueryResultHook & { + filter: ListFilterModel; + } +) { + const { filter, filterHook, useResult, getItems, getCount } = props; + + const effectiveFilter = useMemo(() => { + if (filterHook) { + return filterHook(filter.clone()); + } + return filter; + }, [filter, filterHook]); + + const result = useResult(effectiveFilter); + + // use cached query result for pagination and metadata rendering + const cachedResult = useCachedQueryResult(effectiveFilter, result); + + const items = useMemo(() => getItems(result), [getItems, result]); + const totalCount = useMemo( + () => getCount(cachedResult), + [getCount, cachedResult] + ); + + const pages = Math.ceil(totalCount / filter.itemsPerPage); + + return { + effectiveFilter, + result, + cachedResult, + items, + totalCount, + pages, + }; +} + +// this hook collects the common logic when closing the edit/delete dialog +// if applied is true, then the list should be refetched and selection cleared +export function useCloseEditDelete(props: { + onSelectNone: () => void; + closeModal: () => void; + result: QueryResult; +}) { + const { onSelectNone, closeModal, result } = props; + + const onCloseEditDelete = useCallback( + (applied?: boolean) => { + closeModal(); + if (applied) { + onSelectNone(); + + // refetch + result.refetch(); + } + }, + [onSelectNone, closeModal, result] + ); + + return onCloseEditDelete; +} + export function useScrollToTopOnPageChange( currentPage: number, loading: boolean diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScenesPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScenesPanel.tsx index 9bdd4553f..4b30f3220 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScenesPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScenesPanel.tsx @@ -1,6 +1,6 @@ import React from "react"; import * as GQL from "src/core/generated-graphql"; -import { SceneList } from "src/components/Scenes/SceneList"; +import { FilteredSceneList } from "src/components/Scenes/SceneList"; import { usePerformerFilterHook } from "src/core/performers"; import { View } from "src/components/List/views"; import { PatchComponent } from "src/patch"; @@ -14,7 +14,7 @@ export const PerformerScenesPanel: React.FC = PatchComponent("PerformerScenesPanel", ({ active, performer }) => { const filterHook = usePerformerFilterHook(performer); return ( - { + history.push(queue.makeLink(sceneID, options)); + }, + [history] + ); + + return playScene; +} + +function usePlaySelected(selectedIds: Set) { + const { configuration: config } = useContext(ConfigurationContext); + const playScene = usePlayScene(); + + const playSelected = useCallback(() => { + // populate queue and go to first scene + const sceneIDs = Array.from(selectedIds.values()); + const queue = SceneQueue.fromSceneIDList(sceneIDs); + const autoPlay = config?.interface.autostartVideoOnPlaySelected ?? false; + playScene(queue, sceneIDs[0], { autoPlay }); + }, [selectedIds, config?.interface.autostartVideoOnPlaySelected, playScene]); + + return playSelected; +} + +function usePlayRandom(filter: ListFilterModel, count: number) { + const { configuration: config } = useContext(ConfigurationContext); + const playScene = usePlayScene(); + + const playRandom = useCallback(async () => { + // query for a random scene + if (count === 0) { + return; + } + + const pages = Math.ceil(count / filter.itemsPerPage); + const page = Math.floor(Math.random() * pages) + 1; + + const indexMax = Math.min(filter.itemsPerPage, count); + const index = Math.floor(Math.random() * indexMax); + const filterCopy = cloneDeep(filter); + filterCopy.currentPage = page; + filterCopy.sortBy = "random"; + const queryResults = await queryFindScenes(filterCopy); + const scene = queryResults.data.findScenes.scenes[index]; + if (scene) { + // navigate to the image player page + const queue = SceneQueue.fromListFilterModel(filterCopy); + const autoPlay = config?.interface.autostartVideoOnPlaySelected ?? false; + playScene(queue, scene.id, { sceneIndex: index, autoPlay }); + } + }, [ + filter, + count, + config?.interface.autostartVideoOnPlaySelected, + playScene, + ]); + + return playRandom; +} + +function useAddKeybinds(filter: ListFilterModel, count: number) { + const playRandom = usePlayRandom(filter, count); + + useEffect(() => { + Mousetrap.bind("p r", () => { + playRandom(); + }); + + return () => { + Mousetrap.unbind("p r"); + }; + }, [playRandom]); +} + +const SceneList: React.FC<{ + scenes: GQL.SlimSceneDataFragment[]; + filter: ListFilterModel; + selectedIds: Set; + onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void; + fromGroupId?: string; +}> = ({ scenes, filter, selectedIds, onSelectChange, fromGroupId }) => { + const queue = useMemo(() => SceneQueue.fromListFilterModel(filter), [filter]); + + if (scenes.length === 0) { + return null; + } + + if (filter.displayMode === DisplayMode.Grid) { + return ( + + ); + } + if (filter.displayMode === DisplayMode.List) { + return ( + + ); + } + if (filter.displayMode === DisplayMode.Wall) { + return ; + } + if (filter.displayMode === DisplayMode.Tagger) { + return ; + } + + return null; +}; + +interface IFilteredScenes { filterHook?: (filter: ListFilterModel) => ListFilterModel; defaultSort?: string; view?: View; @@ -72,30 +192,108 @@ interface ISceneList { fromGroupId?: string; } -export const SceneList: React.FC = ({ - filterHook, - defaultSort, - view, - alterQuery, - fromGroupId, -}) => { +export const FilteredSceneList = (props: IFilteredScenes) => { const intl = useIntl(); const history = useHistory(); - const config = React.useContext(ConfigurationContext); - const [isGenerateDialogOpen, setIsGenerateDialogOpen] = useState(false); - const [mergeScenes, setMergeScenes] = - useState<{ id: string; title: string }[]>(); - const [isIdentifyDialogOpen, setIsIdentifyDialogOpen] = useState(false); - const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); - const [isExportAll, setIsExportAll] = useState(false); - const filterMode = GQL.FilterMode.Scenes; + const { filterHook, defaultSort, view, alterQuery, fromGroupId } = props; - const otherOperations = [ + // States + const { filterState, queryResult, modalState, listSelect, showEditFilter } = + useFilteredItemList({ + filterStateProps: { + filterMode: GQL.FilterMode.Scenes, + defaultSort, + view, + useURL: alterQuery, + }, + queryResultProps: { + useResult: useFindScenes, + getCount: (r) => r.data?.findScenes.count ?? 0, + getItems: (r) => r.data?.findScenes.scenes ?? [], + filterHook, + }, + }); + + const { filter, setFilter, loading: filterLoading } = filterState; + + const { effectiveFilter, result, cachedResult, items, totalCount } = + queryResult; + + const { + selectedIds, + selectedItems, + onSelectChange, + onSelectNone, + hasSelection, + } = listSelect; + + const { modal, showModal, closeModal } = modalState; + + // Utility hooks + const { setPage, removeCriterion, clearAllCriteria } = useFilterOperations({ + filter, + setFilter, + }); + + useAddKeybinds(filter, totalCount); + + const onCloseEditDelete = useCloseEditDelete({ + closeModal, + onSelectNone, + result, + }); + + const metadataByline = useMemo(() => { + if (cachedResult.loading) return ""; + + return renderMetadataByline(cachedResult) ?? ""; + }, [cachedResult]); + + const playSelected = usePlaySelected(selectedIds); + const playRandom = usePlayRandom(filter, totalCount); + + function onExport(all: boolean) { + showModal( + closeModal()} + /> + ); + } + + function onMerge() { + const selected = + selectedItems.map((s) => { + return { + id: s.id, + title: objectTitle(s), + }; + }) ?? []; + showModal( + { + closeModal(); + if (mergedID) { + history.push(`/scenes/${mergedID}`); + } + }} + show + /> + ); + } + + const otherOperations: IListFilterOperation[] = [ { text: intl.formatMessage({ id: "actions.play_selected" }), onClick: playSelected, - isDisplayed: showWhenSelected, + isDisplayed: () => hasSelection, icon: faPlay, }, { @@ -104,273 +302,103 @@ export const SceneList: React.FC = ({ }, { text: `${intl.formatMessage({ id: "actions.generate" })}…`, - onClick: async () => setIsGenerateDialogOpen(true), - isDisplayed: showWhenSelected, - }, - { - text: `${intl.formatMessage({ id: "actions.identify" })}…`, - onClick: async () => setIsIdentifyDialogOpen(true), - isDisplayed: showWhenSelected, - }, - { - text: `${intl.formatMessage({ id: "actions.merge" })}…`, - onClick: onMerge, - isDisplayed: showWhenSelected, - }, - { - text: intl.formatMessage({ id: "actions.export" }), - onClick: onExport, - isDisplayed: showWhenSelected, - }, - { - text: intl.formatMessage({ id: "actions.export_all" }), - onClick: onExportAll, - }, - ]; - - function addKeybinds( - result: GQL.FindScenesQueryResult, - filter: ListFilterModel - ) { - Mousetrap.bind("p r", () => { - playRandom(result, filter); - }); - - return () => { - Mousetrap.unbind("p r"); - }; - } - - function playScene( - queue: SceneQueue, - sceneID: string, - options: IPlaySceneOptions - ) { - history.push(queue.makeLink(sceneID, options)); - } - - async function playSelected( - result: GQL.FindScenesQueryResult, - filter: ListFilterModel, - selectedIds: Set - ) { - // populate queue and go to first scene - const sceneIDs = Array.from(selectedIds.values()); - const queue = SceneQueue.fromSceneIDList(sceneIDs); - const autoPlay = - config.configuration?.interface.autostartVideoOnPlaySelected ?? false; - playScene(queue, sceneIDs[0], { autoPlay }); - } - - async function playRandom( - result: GQL.FindScenesQueryResult, - filter: ListFilterModel - ) { - // query for a random scene - if (result.data?.findScenes) { - const { count } = result.data.findScenes; - - const pages = Math.ceil(count / filter.itemsPerPage); - const page = Math.floor(Math.random() * pages) + 1; - - const indexMax = Math.min(filter.itemsPerPage, count); - const index = Math.floor(Math.random() * indexMax); - const filterCopy = cloneDeep(filter); - filterCopy.currentPage = page; - filterCopy.sortBy = "random"; - const queryResults = await queryFindScenes(filterCopy); - const scene = queryResults.data.findScenes.scenes[index]; - if (scene) { - // navigate to the image player page - const queue = SceneQueue.fromListFilterModel(filterCopy); - const autoPlay = - config.configuration?.interface.autostartVideoOnPlaySelected ?? false; - playScene(queue, scene.id, { sceneIndex: index, autoPlay }); - } - } - } - - async function onMerge( - result: GQL.FindScenesQueryResult, - filter: ListFilterModel, - selectedIds: Set - ) { - const selected = - result.data?.findScenes.scenes - .filter((s) => selectedIds.has(s.id)) - .map((s) => { - return { - id: s.id, - title: objectTitle(s), - }; - }) ?? []; - - setMergeScenes(selected); - } - - async function onExport() { - setIsExportAll(false); - setIsExportDialogOpen(true); - } - - async function onExportAll() { - setIsExportAll(true); - setIsExportDialogOpen(true); - } - - function renderContent( - result: GQL.FindScenesQueryResult, - filter: ListFilterModel, - selectedIds: Set, - onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void - ) { - function maybeRenderSceneGenerateDialog() { - if (isGenerateDialogOpen) { - return ( + onClick: () => + showModal( setIsGenerateDialogOpen(false)} + onClose={() => closeModal()} /> - ); - } - } - - function maybeRenderSceneIdentifyDialog() { - if (isIdentifyDialogOpen) { - return ( + ), + isDisplayed: () => hasSelection, + }, + { + text: `${intl.formatMessage({ id: "actions.identify" })}…`, + onClick: () => + showModal( setIsIdentifyDialogOpen(false)} + onClose={() => closeModal()} /> - ); - } - } + ), + isDisplayed: () => hasSelection, + }, + { + text: `${intl.formatMessage({ id: "actions.merge" })}…`, + onClick: () => onMerge(), + isDisplayed: () => hasSelection, + }, + { + text: intl.formatMessage({ id: "actions.export" }), + onClick: () => onExport(false), + isDisplayed: () => hasSelection, + }, + { + text: intl.formatMessage({ id: "actions.export_all" }), + onClick: () => onExport(true), + }, + ]; - function maybeRenderSceneExportDialog() { - if (isExportDialogOpen) { - return ( - setIsExportDialogOpen(false)} - /> - ); - } - } + // render + if (filterLoading) return null; - function renderMergeDialog() { - if (mergeScenes) { - return ( - { - setMergeScenes(undefined); - if (mergedID) { - history.push(`/scenes/${mergedID}`); - } - }} - show - /> - ); - } - } + return ( + +
+ {modal} - function renderScenes() { - if (!result.data?.findScenes) return; + + showModal( + + ) + } + onDelete={() => { + showModal( + + ); + }} + operations={otherOperations} + zoomable + /> - const queue = SceneQueue.fromListFilterModel(filter); + showEditFilter(c.criterionOption.type)} + onRemoveCriterion={removeCriterion} + onRemoveAll={() => clearAllCriteria()} + /> - if (filter.displayMode === DisplayMode.Grid) { - return ( - + - ); - } - if (filter.displayMode === DisplayMode.List) { - return ( - - ); - } - if (filter.displayMode === DisplayMode.Wall) { - return ( - - ); - } - if (filter.displayMode === DisplayMode.Tagger) { - return ; - } - } - - return ( - <> - {maybeRenderSceneGenerateDialog()} - {maybeRenderSceneIdentifyDialog()} - {maybeRenderSceneExportDialog()} - {renderMergeDialog()} - {renderScenes()} - - ); - } - - function renderEditDialog( - selectedScenes: GQL.SlimSceneDataFragment[], - onClose: (applied: boolean) => void - ) { - return ; - } - - function renderDeleteDialog( - selectedScenes: GQL.SlimSceneDataFragment[], - onClose: (confirmed: boolean) => void - ) { - return ; - } - - return ( - - - - + +
); }; -export default SceneList; +export default FilteredSceneList; diff --git a/ui/v2.5/src/components/Studios/StudioDetails/StudioScenesPanel.tsx b/ui/v2.5/src/components/Studios/StudioDetails/StudioScenesPanel.tsx index 4005332f7..f7fb8c174 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/StudioScenesPanel.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/StudioScenesPanel.tsx @@ -1,6 +1,6 @@ import React from "react"; import * as GQL from "src/core/generated-graphql"; -import { SceneList } from "src/components/Scenes/SceneList"; +import { FilteredSceneList } from "src/components/Scenes/SceneList"; import { useStudioFilterHook } from "src/core/studios"; import { View } from "src/components/List/views"; @@ -17,7 +17,7 @@ export const StudioScenesPanel: React.FC = ({ }) => { const filterHook = useStudioFilterHook(studio, showChildStudioContent); return ( - = ({ }) => { const filterHook = useTagFilterHook(tag, showSubTagContent); return ( -