diff --git a/ui/v2.5/src/components/Changelog/versions/v060.md b/ui/v2.5/src/components/Changelog/versions/v060.md index 2eb68aa9a..c04a45045 100644 --- a/ui/v2.5/src/components/Changelog/versions/v060.md +++ b/ui/v2.5/src/components/Changelog/versions/v060.md @@ -1,4 +1,5 @@ ### 🎨 Improvements +* Remember gallery images view mode. * Add option to skip checking of insecure SSL certificates when scraping. * Auto-play video previews on mobile devices. * Replace hover menu with dropdown menu for O-Counter. diff --git a/ui/v2.5/src/components/Galleries/Galleries.tsx b/ui/v2.5/src/components/Galleries/Galleries.tsx index 8fa2c8bde..b4aef94a5 100644 --- a/ui/v2.5/src/components/Galleries/Galleries.tsx +++ b/ui/v2.5/src/components/Galleries/Galleries.tsx @@ -1,5 +1,6 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; +import { PersistanceLevel } from "src/hooks/ListHook"; import { Gallery } from "./GalleryDetails/Gallery"; import { GalleryList } from "./GalleryList"; @@ -8,7 +9,9 @@ const Galleries = () => ( } + render={(props) => ( + + )} /> diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx index f0e9e3adf..05222612c 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx @@ -6,6 +6,7 @@ import { ImageList } from "src/components/Images/ImageList"; import { showWhenSelected } from "src/hooks/ListHook"; import { mutateAddGalleryImages } from "src/core/StashService"; import { useToast } from "src/hooks"; +import { TextUtils } from "src/utils"; interface IGalleryAddProps { gallery: Partial; @@ -17,7 +18,7 @@ export const GalleryAddPanel: React.FC = ({ gallery }) => { function filterHook(filter: ListFilterModel) { const galleryValue = { id: gallery.id!, - label: gallery.title ?? gallery.path ?? "", + label: gallery.title ?? TextUtils.fileNameFromPath(gallery.path ?? ""), }; // if galleries is already present, then we modify it, otherwise add let galleryCriterion = filter.criteria.find((c) => { @@ -77,10 +78,6 @@ export const GalleryAddPanel: React.FC = ({ gallery }) => { ]; return ( - + ); }; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx index d536eba83..ec06b478d 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx @@ -4,8 +4,9 @@ import { GalleriesCriterion } from "src/models/list-filter/criteria/galleries"; import { ListFilterModel } from "src/models/list-filter/filter"; import { ImageList } from "src/components/Images/ImageList"; import { mutateRemoveGalleryImages } from "src/core/StashService"; -import { showWhenSelected } from "src/hooks/ListHook"; +import { showWhenSelected, PersistanceLevel } from "src/hooks/ListHook"; import { useToast } from "src/hooks"; +import { TextUtils } from "src/utils"; interface IGalleryDetailsProps { gallery: GQL.GalleryDataFragment; @@ -19,7 +20,7 @@ export const GalleryImagesPanel: React.FC = ({ function filterHook(filter: ListFilterModel) { const galleryValue = { id: gallery.id!, - label: gallery.title ?? gallery.path ?? "", + label: gallery.title ?? TextUtils.fileNameFromPath(gallery.path ?? ""), }; // if galleries is already present, then we modify it, otherwise add let galleryCriterion = filter.criteria.find((c) => { @@ -82,7 +83,8 @@ export const GalleryImagesPanel: React.FC = ({ ); }; diff --git a/ui/v2.5/src/components/Galleries/GalleryList.tsx b/ui/v2.5/src/components/Galleries/GalleryList.tsx index 283d64e10..8e3096a16 100644 --- a/ui/v2.5/src/components/Galleries/GalleryList.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryList.tsx @@ -8,7 +8,8 @@ import { GallerySlimDataFragment, } from "src/core/generated-graphql"; import { useGalleriesList } from "src/hooks"; -import { showWhenSelected } from "src/hooks/ListHook"; +import { TextUtils } from "src/utils"; +import { showWhenSelected, PersistanceLevel } from "src/hooks/ListHook"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; import { queryFindGalleries } from "src/core/StashService"; @@ -20,7 +21,7 @@ import { ExportDialog } from "../Shared/ExportDialog"; interface IGalleryList { filterHook?: (filter: ListFilterModel) => ListFilterModel; - persistState?: boolean; + persistState?: PersistanceLevel; } export const GalleryList: React.FC = ({ @@ -202,7 +203,9 @@ export const GalleryList: React.FC = ({ - {gallery.title ?? gallery.path} ({gallery.image_count}{" "} + {gallery.title ?? + TextUtils.fileNameFromPath(gallery.path ?? "")}{" "} + ({gallery.image_count}{" "} {gallery.image_count === 1 ? "image" : "images"}) diff --git a/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx b/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx index 376f1062c..b724ea09b 100644 --- a/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx @@ -24,7 +24,7 @@ const GalleryWallCard: React.FC = ({ gallery }) => { ? "landscape" : "portrait"; const cover = gallery?.cover?.paths.thumbnail ?? ""; - const title = gallery.title ?? gallery.path; + const title = gallery.title ?? TextUtils.fileNameFromPath(gallery.path ?? ""); const performerNames = gallery.performers.map((p) => p.name); const performers = performerNames.length >= 2 diff --git a/ui/v2.5/src/components/Images/ImageList.tsx b/ui/v2.5/src/components/Images/ImageList.tsx index 48cdb9824..103e1799f 100644 --- a/ui/v2.5/src/components/Images/ImageList.tsx +++ b/ui/v2.5/src/components/Images/ImageList.tsx @@ -12,7 +12,11 @@ import { useImagesList, useLightbox } from "src/hooks"; import { TextUtils } from "src/utils"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; -import { IListHookOperation, showWhenSelected } from "src/hooks/ListHook"; +import { + IListHookOperation, + showWhenSelected, + PersistanceLevel, +} from "src/hooks/ListHook"; import { ImageCard } from "./ImageCard"; import { EditImagesDialog } from "./EditImagesDialog"; import { DeleteImagesDialog } from "./DeleteImagesDialog"; @@ -79,13 +83,15 @@ const ImageWall: React.FC = ({ interface IImageList { filterHook?: (filter: ListFilterModel) => ListFilterModel; - persistState?: boolean; + persistState?: PersistanceLevel; + persistanceKey?: string; extraOperations?: IListHookOperation[]; } export const ImageList: React.FC = ({ filterHook, persistState, + persistanceKey, extraOperations, }) => { const history = useHistory(); @@ -131,6 +137,7 @@ export const ImageList: React.FC = ({ filterHook, addKeybinds, persistState, + persistanceKey, }); async function viewRandom( diff --git a/ui/v2.5/src/components/Images/Images.tsx b/ui/v2.5/src/components/Images/Images.tsx index 576b6f674..dfbd6224c 100644 --- a/ui/v2.5/src/components/Images/Images.tsx +++ b/ui/v2.5/src/components/Images/Images.tsx @@ -1,5 +1,6 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; +import { PersistanceLevel } from "src/hooks/ListHook"; import { Image } from "./ImageDetails/Image"; import { ImageList } from "./ImageList"; @@ -8,7 +9,9 @@ const Images = () => ( } + render={(props) => ( + + )} /> diff --git a/ui/v2.5/src/components/Movies/MovieList.tsx b/ui/v2.5/src/components/Movies/MovieList.tsx index 2e7a52a55..040cbd400 100644 --- a/ui/v2.5/src/components/Movies/MovieList.tsx +++ b/ui/v2.5/src/components/Movies/MovieList.tsx @@ -9,7 +9,11 @@ import { import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; import { queryFindMovies, useMoviesDestroy } from "src/core/StashService"; -import { showWhenSelected, useMoviesList } from "src/hooks/ListHook"; +import { + showWhenSelected, + useMoviesList, + PersistanceLevel, +} from "src/hooks/ListHook"; import { ExportDialog, DeleteEntityDialog } from "src/components/Shared"; import { MovieCard } from "./MovieCard"; @@ -65,7 +69,7 @@ export const MovieList: React.FC = () => { addKeybinds, otherOperations, selectable: true, - persistState: true, + persistState: PersistanceLevel.ALL, renderDeleteDialog, }); diff --git a/ui/v2.5/src/components/Performers/PerformerList.tsx b/ui/v2.5/src/components/Performers/PerformerList.tsx index b7300a718..2f2966a52 100644 --- a/ui/v2.5/src/components/Performers/PerformerList.tsx +++ b/ui/v2.5/src/components/Performers/PerformerList.tsx @@ -11,7 +11,7 @@ import { usePerformersDestroy, } from "src/core/StashService"; import { usePerformersList } from "src/hooks"; -import { showWhenSelected } from "src/hooks/ListHook"; +import { showWhenSelected, PersistanceLevel } from "src/hooks/ListHook"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; import { ExportDialog, DeleteEntityDialog } from "src/components/Shared"; @@ -100,7 +100,7 @@ export const PerformerList: React.FC = () => { renderContent, addKeybinds, selectable: true, - persistState: true, + persistState: PersistanceLevel.ALL, renderDeleteDialog, }); diff --git a/ui/v2.5/src/components/Scenes/SceneList.tsx b/ui/v2.5/src/components/Scenes/SceneList.tsx index 4f2df0914..e0667e26a 100644 --- a/ui/v2.5/src/components/Scenes/SceneList.tsx +++ b/ui/v2.5/src/components/Scenes/SceneList.tsx @@ -10,7 +10,7 @@ import { queryFindScenes } from "src/core/StashService"; import { useScenesList } from "src/hooks"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; -import { showWhenSelected } from "src/hooks/ListHook"; +import { showWhenSelected, PersistanceLevel } from "src/hooks/ListHook"; import Tagger from "src/components/Tagger"; import { WallPanel } from "../Wall/WallPanel"; import { SceneCard } from "./SceneCard"; @@ -22,7 +22,7 @@ import { ExportDialog } from "../Shared/ExportDialog"; interface ISceneList { filterHook?: (filter: ListFilterModel) => ListFilterModel; - persistState?: boolean; + persistState?: PersistanceLevel.ALL; } export const SceneList: React.FC = ({ diff --git a/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx b/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx index 4e43d547c..1a70af20f 100644 --- a/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx +++ b/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx @@ -6,6 +6,7 @@ import { FindSceneMarkersQueryResult } from "src/core/generated-graphql"; import { queryFindSceneMarkers } from "src/core/StashService"; import { NavUtils } from "src/utils"; import { useSceneMarkersList } from "src/hooks"; +import { PersistanceLevel } from "src/hooks/ListHook"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; import { WallPanel } from "../Wall/WallPanel"; @@ -41,7 +42,7 @@ export const SceneMarkerList: React.FC = ({ filterHook }) => { renderContent, filterHook, addKeybinds, - persistState: true, + persistState: PersistanceLevel.ALL, }); async function playRandom( diff --git a/ui/v2.5/src/components/Scenes/Scenes.tsx b/ui/v2.5/src/components/Scenes/Scenes.tsx index 95bbb46f2..0f301f7f5 100644 --- a/ui/v2.5/src/components/Scenes/Scenes.tsx +++ b/ui/v2.5/src/components/Scenes/Scenes.tsx @@ -1,5 +1,6 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; +import { PersistanceLevel } from "src/hooks/ListHook"; import { Scene } from "./SceneDetails/Scene"; import { SceneList } from "./SceneList"; import { SceneMarkerList } from "./SceneMarkerList"; @@ -9,7 +10,9 @@ const Scenes = () => ( } + render={(props) => ( + + )} /> diff --git a/ui/v2.5/src/components/Studios/StudioList.tsx b/ui/v2.5/src/components/Studios/StudioList.tsx index 6156046d2..4d6aef8f9 100644 --- a/ui/v2.5/src/components/Studios/StudioList.tsx +++ b/ui/v2.5/src/components/Studios/StudioList.tsx @@ -7,7 +7,7 @@ import { SlimStudioDataFragment, } from "src/core/generated-graphql"; import { useStudiosList } from "src/hooks"; -import { showWhenSelected } from "src/hooks/ListHook"; +import { showWhenSelected, PersistanceLevel } from "src/hooks/ListHook"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; import { queryFindStudios, useStudiosDestroy } from "src/core/StashService"; @@ -131,7 +131,7 @@ export const StudioList: React.FC = ({ addKeybinds, otherOperations, selectable: true, - persistState: !fromParent, + persistState: !fromParent ? PersistanceLevel.ALL : PersistanceLevel.NONE, renderDeleteDialog, }); diff --git a/ui/v2.5/src/components/Tags/TagList.tsx b/ui/v2.5/src/components/Tags/TagList.tsx index ed2a918ec..53645afd7 100644 --- a/ui/v2.5/src/components/Tags/TagList.tsx +++ b/ui/v2.5/src/components/Tags/TagList.tsx @@ -4,7 +4,11 @@ import Mousetrap from "mousetrap"; import { FindTagsQueryResult } from "src/core/generated-graphql"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; -import { showWhenSelected, useTagsList } from "src/hooks/ListHook"; +import { + showWhenSelected, + useTagsList, + PersistanceLevel, +} from "src/hooks/ListHook"; import { Button } from "react-bootstrap"; import { Link, useHistory } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; @@ -144,7 +148,7 @@ export const TagList: React.FC = ({ filterHook }) => { selectable: true, zoomable: true, defaultZoomIndex: 0, - persistState: true, + persistState: PersistanceLevel.ALL, renderDeleteDialog, }); diff --git a/ui/v2.5/src/hooks/ListHook.tsx b/ui/v2.5/src/hooks/ListHook.tsx index 20bd5ca04..2e6579115 100644 --- a/ui/v2.5/src/hooks/ListHook.tsx +++ b/ui/v2.5/src/hooks/ListHook.tsx @@ -79,8 +79,15 @@ export interface IListHookOperation { postRefetch?: boolean; } +export enum PersistanceLevel { + NONE, + ALL, + VIEW, +} + interface IListHookOptions { - persistState?: boolean; + persistState?: PersistanceLevel; + persistanceKey?: string; filterHook?: (filter: ListFilterModel) => ListFilterModel; zoomable?: boolean; selectable?: boolean; @@ -421,22 +428,36 @@ const useList = ( ); // Store initial pathname to prevent hooks from operating outside this page const originalPathName = useRef(location.pathname); + const persistanceKey = options.persistanceKey ?? options.filterMode; const [filter, setFilter] = useState( new ListFilterModel(options.filterMode, queryString.parse(location.search)) ); const updateInterfaceConfig = useCallback( - (updatedFilter: ListFilterModel) => { - setInterfaceState({ - [options.filterMode]: { - filter: updatedFilter.makeQueryParameters(), - itemsPerPage: updatedFilter.itemsPerPage, - currentPage: updatedFilter.currentPage, - }, + (updatedFilter: ListFilterModel, level: PersistanceLevel) => { + setInterfaceState((prevState) => { + if (level === PersistanceLevel.VIEW) { + return { + [persistanceKey]: { + ...prevState[persistanceKey], + filter: queryString.stringify({ + ...queryString.parse(prevState[persistanceKey]?.filter ?? ""), + disp: updatedFilter.displayMode, + }), + }, + }; + } + return { + [persistanceKey]: { + filter: updatedFilter.makeQueryParameters(), + itemsPerPage: updatedFilter.itemsPerPage, + currentPage: updatedFilter.currentPage, + }, + }; }); }, - [options.filterMode, setInterfaceState] + [persistanceKey, setInterfaceState] ); useEffect(() => { @@ -451,20 +472,25 @@ const useList = ( if (!options.persistState) return; - const storedQuery = interfaceState.data?.[options.filterMode]; + const storedQuery = interfaceState.data?.[persistanceKey]; if (!storedQuery) return; const queryFilter = queryString.parse(history.location.search); const storedFilter = queryString.parse(storedQuery.filter); + + const activeFilter = + options.persistState === PersistanceLevel.ALL + ? storedFilter + : { disp: storedFilter.disp }; const query = history.location.search ? { - sortby: storedFilter.sortby, - sortdir: storedFilter.sortdir, - disp: storedFilter.disp, - perPage: storedFilter.perPage, + sortby: activeFilter.sortby, + sortdir: activeFilter.sortdir, + disp: activeFilter.disp, + perPage: activeFilter.perPage, ...queryFilter, } - : storedFilter; + : activeFilter; const newFilter = new ListFilterModel(options.filterMode, query); @@ -474,7 +500,7 @@ const useList = ( newLocation.search = newFilter.makeQueryParameters(); if (newLocation.search !== filter.makeQueryParameters()) { setFilter(newFilter); - updateInterfaceConfig(newFilter); + updateInterfaceConfig(newFilter, options.persistState); } // If constructed search is different from current, update it as well if (newLocation.search !== location.search) { @@ -488,6 +514,7 @@ const useList = ( history, location.search, options.filterMode, + persistanceKey, forageInitialised, updateInterfaceConfig, options.persistState, @@ -499,7 +526,7 @@ const useList = ( newLocation.search = listFilter.makeQueryParameters(); history.replace(newLocation); if (options.persistState) { - updateInterfaceConfig(listFilter); + updateInterfaceConfig(listFilter, options.persistState); } }