diff --git a/ui/v2.5/src/components/Galleries/GalleryList.tsx b/ui/v2.5/src/components/Galleries/GalleryList.tsx index a0930b927..105557175 100644 --- a/ui/v2.5/src/components/Galleries/GalleryList.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryList.tsx @@ -195,7 +195,6 @@ export const GalleryList: React.FC = ({ selectable > = ({ selectable={selectable} > = ({ selectable > { text: string; @@ -63,34 +65,47 @@ export const FilteredListToolbar: React.FC = ({ return ( + + - {showEditFilter && ( - showEditFilter()} - view={view} - /> - )} - 0} - onEdit={onEdit} - onDelete={onDelete} + - - - + showEditFilter()} count={filter.count()} /> - + + setFilter(filter.setSortBy(e ?? undefined))} + onChangeSortDirection={() => setFilter(filter.toggleSortDirection())} + onReshuffleRandomSort={() => setFilter(filter.reshuffleRandomSort())} + /> + + setFilter(filter.setPageSize(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 f1d811ff2..b68077b55 100644 --- a/ui/v2.5/src/components/List/ItemList.tsx +++ b/ui/v2.5/src/components/List/ItemList.tsx @@ -43,6 +43,8 @@ import { } from "./FilteredListToolbar"; import { PagedList } from "./PagedList"; import { useConfigurationContext } from "src/hooks/Config"; +import { useZoomKeybinds } from "./ZoomSlider"; +import { DisplayMode } from "src/models/list-filter/types"; interface IFilteredItemList { filterStateProps: IFilterStateHook; @@ -112,7 +114,6 @@ export function useFilteredItemList< interface IItemListProps { view?: View; - zoomable?: boolean; otherOperations?: IItemListOperation[]; renderContent: ( result: T, @@ -144,7 +145,6 @@ export const ItemList = ( ) => { const { view, - zoomable, otherOperations, renderContent, renderEditDialog, @@ -216,6 +216,15 @@ export const ItemList = ( showEditFilter, }); + const zoomable = + filter.displayMode === DisplayMode.Grid || + filter.displayMode === DisplayMode.Wall; + + useZoomKeybinds({ + zoomIndex: zoomable ? filter.zoomIndex : undefined, + onChangeZoom: (zoom) => updateFilter(filter.setZoom(zoom)), + }); + useEffect(() => { if (addKeybinds) { const unbindExtras = addKeybinds(result, effectiveFilter, selectedIds); diff --git a/ui/v2.5/src/components/List/ListFilter.tsx b/ui/v2.5/src/components/List/ListFilter.tsx index b40951081..ff3be0360 100644 --- a/ui/v2.5/src/components/List/ListFilter.tsx +++ b/ui/v2.5/src/components/List/ListFilter.tsx @@ -1,4 +1,3 @@ -import cloneDeep from "lodash-es/cloneDeep"; import React, { useCallback, useEffect, @@ -23,17 +22,14 @@ import { import { Icon } from "../Shared/Icon"; import { ListFilterModel } from "src/models/list-filter/filter"; import useFocus from "src/utils/focus"; -import { FormattedMessage, useIntl } from "react-intl"; -import { SavedFilterDropdown } from "./SavedFilterList"; +import { useIntl } from "react-intl"; import { faCaretDown, faCaretUp, faCheck, faRandom, } from "@fortawesome/free-solid-svg-icons"; -import { FilterButton } from "./Filters/FilterButton"; import { useDebounce } from "src/hooks/debounce"; -import { View } from "./views"; import { ClearableInput } from "../Shared/ClearableInput"; import { useStopWheelScroll } from "src/utils/form"; import { ISortByOption } from "src/models/list-filter/filter-options"; @@ -330,109 +326,3 @@ export const SortBySelect: React.FC<{ ); }; - -interface IListFilterProps { - onFilterUpdate: (newFilter: ListFilterModel) => void; - filter: ListFilterModel; - view?: View; - openFilterDialog: () => void; -} - -export const ListFilter: React.FC = ({ - onFilterUpdate, - filter, - openFilterDialog, - view, -}) => { - const filterOptions = filter.options; - - useEffect(() => { - Mousetrap.bind("r", () => onReshuffleRandomSort()); - - return () => { - Mousetrap.unbind("r"); - }; - }); - - function onChangePageSize(pp: number) { - const newFilter = cloneDeep(filter); - newFilter.itemsPerPage = pp; - newFilter.currentPage = 1; - onFilterUpdate(newFilter); - } - - function onChangeSortDirection() { - const newFilter = cloneDeep(filter); - if (filter.sortDirection === SortDirectionEnum.Asc) { - newFilter.sortDirection = SortDirectionEnum.Desc; - } else { - newFilter.sortDirection = SortDirectionEnum.Asc; - } - - onFilterUpdate(newFilter); - } - - function onChangeSortBy(eventKey: string | null) { - const newFilter = cloneDeep(filter); - newFilter.sortBy = eventKey ?? undefined; - newFilter.currentPage = 1; - onFilterUpdate(newFilter); - } - - function onReshuffleRandomSort() { - const newFilter = cloneDeep(filter); - newFilter.currentPage = 1; - newFilter.randomSeed = -1; - onFilterUpdate(newFilter); - } - - function render() { - return ( - <> -
- -
- - - { - onFilterUpdate(f); - }} - view={view} - /> - - - - } - > - openFilterDialog()} - count={filter.count()} - /> - - - - - - - - ); - } - - return render(); -}; diff --git a/ui/v2.5/src/components/List/ListOperationButtons.tsx b/ui/v2.5/src/components/List/ListOperationButtons.tsx index bdb87fa3f..2d8e83039 100644 --- a/ui/v2.5/src/components/List/ListOperationButtons.tsx +++ b/ui/v2.5/src/components/List/ListOperationButtons.tsx @@ -1,11 +1,5 @@ -import React, { PropsWithChildren, useEffect } from "react"; -import { - Button, - ButtonGroup, - Dropdown, - OverlayTrigger, - Tooltip, -} from "react-bootstrap"; +import React, { PropsWithChildren, useEffect, useMemo } from "react"; +import { Button, ButtonGroup, Dropdown } from "react-bootstrap"; import Mousetrap from "mousetrap"; import { FormattedMessage, useIntl } from "react-intl"; import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; @@ -108,8 +102,8 @@ export const ListOperationButtons: React.FC = ({ }; }); - function maybeRenderButtons() { - const buttons = (otherOperations ?? []).filter((o) => { + const buttons = useMemo(() => { + const ret = (otherOperations ?? []).filter((o) => { if (!o.icon) { return false; } @@ -120,16 +114,17 @@ export const ListOperationButtons: React.FC = ({ return o.isDisplayed(); }); + if (itemsSelected) { if (onEdit) { - buttons.push({ + ret.push({ icon: faPencilAlt, text: intl.formatMessage({ id: "actions.edit" }), onClick: onEdit, }); } if (onDelete) { - buttons.push({ + ret.push({ icon: faTrash, text: intl.formatMessage({ id: "actions.delete" }), onClick: onDelete, @@ -138,58 +133,57 @@ export const ListOperationButtons: React.FC = ({ } } - if (buttons.length > 0) { - return ( - - {buttons.map((button) => { - return ( - {button.text}} - key={button.text} - > - - - ); - })} - - ); - } - } + return ret; + }, [otherOperations, itemsSelected, onEdit, onDelete, intl]); - function renderSelectAll() { - if (onSelectAll) { - return ( - onSelectAll?.()} - > - - - ); - } - } + const operationButtons = useMemo(() => { + return ( + <> + {buttons.map((button) => { + return ( + + ); + })} + + ); + }, [buttons]); - function renderSelectNone() { - if (onSelectNone) { - return ( - onSelectNone?.()} - > - - - ); + const moreDropdown = useMemo(() => { + function renderSelectAll() { + if (onSelectAll) { + return ( + onSelectAll?.()} + > + + + ); + } + } + + function renderSelectNone() { + if (onSelectNone) { + return ( + onSelectNone?.()} + > + + + ); + } } - } - function renderMore() { const options = [renderSelectAll(), renderSelectNone()].filter((o) => o); if (otherOperations) { @@ -224,13 +218,19 @@ export const ListOperationButtons: React.FC = ({ {options.length > 0 ? options : undefined} ); + }, [otherOperations, onSelectAll, onSelectNone]); + + // don't render anything if there are no buttons or operations + if (buttons.length === 0 && !moreDropdown) { + return null; } return ( <> - {maybeRenderButtons()} - - {renderMore()} + + {operationButtons} + {moreDropdown} + ); }; diff --git a/ui/v2.5/src/components/List/ListViewOptions.tsx b/ui/v2.5/src/components/List/ListViewOptions.tsx index 04adcaa74..b681e086d 100644 --- a/ui/v2.5/src/components/List/ListViewOptions.tsx +++ b/ui/v2.5/src/components/List/ListViewOptions.tsx @@ -1,8 +1,16 @@ import React, { useEffect, useRef, useState } from "react"; import Mousetrap from "mousetrap"; -import { Button, Dropdown, Overlay, Popover } from "react-bootstrap"; +import { + Button, + ButtonGroup, + Dropdown, + Overlay, + OverlayTrigger, + Popover, + Tooltip, +} from "react-bootstrap"; import { DisplayMode } from "src/models/list-filter/types"; -import { useIntl } from "react-intl"; +import { IntlShape, useIntl } from "react-intl"; import { Icon } from "../Shared/Icon"; import { faChevronDown, @@ -53,6 +61,10 @@ function getLabelId(option: DisplayMode) { return `display_mode.${displayModeId}`; } +function getLabel(intl: IntlShape, option: DisplayMode) { + return intl.formatMessage({ id: getLabelId(option) }); +} + export const ListViewOptions: React.FC = ({ zoomIndex, onSetZoom, @@ -60,9 +72,6 @@ export const ListViewOptions: React.FC = ({ onSetDisplayMode, displayModeOptions, }) => { - const minZoom = 0; - const maxZoom = 3; - const intl = useIntl(); const overlayTarget = useRef(null); @@ -98,10 +107,6 @@ export const ListViewOptions: React.FC = ({ }; }); - function getLabel(option: DisplayMode) { - return intl.formatMessage({ id: getLabelId(option) }); - } - function onChangeZoom(v: number) { if (onSetZoom) { onSetZoom(v); @@ -116,7 +121,7 @@ export const ListViewOptions: React.FC = ({ variant="secondary" title={intl.formatMessage( { id: "display_mode.label_current" }, - { current: getLabel(displayMode) } + { current: getLabel(intl, displayMode) } )} onClick={() => setShowOptions(!showOptions)} > @@ -140,8 +145,6 @@ export const ListViewOptions: React.FC = ({ displayMode === DisplayMode.Wall) ? (
@@ -156,7 +159,7 @@ export const ListViewOptions: React.FC = ({ onSetDisplayMode(option); }} > - {getLabel(option)} + {getLabel(intl, option)} ))}
@@ -167,3 +170,48 @@ export const ListViewOptions: React.FC = ({ ); }; + +export const ListViewButtonGroup: React.FC = ({ + zoomIndex, + onSetZoom, + displayMode, + onSetDisplayMode, + displayModeOptions, +}) => { + const intl = useIntl(); + + return ( + <> + {displayModeOptions.length > 1 && ( + + {displayModeOptions.map((option) => ( + + {getLabel(intl, option)} + + } + > + + + ))} + + )} +
+ {onSetZoom && + zoomIndex !== undefined && + (displayMode === DisplayMode.Grid || + displayMode === DisplayMode.Wall) ? ( + + ) : null} +
+ + ); +}; diff --git a/ui/v2.5/src/components/List/ZoomSlider.tsx b/ui/v2.5/src/components/List/ZoomSlider.tsx index dff8e4f57..093b5ec7a 100644 --- a/ui/v2.5/src/components/List/ZoomSlider.tsx +++ b/ui/v2.5/src/components/List/ZoomSlider.tsx @@ -2,19 +2,14 @@ import React, { useEffect } from "react"; import Mousetrap from "mousetrap"; import { Form } from "react-bootstrap"; -export interface IZoomSelectProps { - minZoom: number; - maxZoom: number; - zoomIndex: number; - onChangeZoom: (v: number) => void; -} +const minZoom = 0; +const maxZoom = 3; -export const ZoomSelect: React.FC = ({ - minZoom, - maxZoom, - zoomIndex, - onChangeZoom, -}) => { +export function useZoomKeybinds(props: { + zoomIndex: number | undefined; + onChangeZoom: (v: number) => void; +}) { + const { zoomIndex, onChangeZoom } = props; useEffect(() => { Mousetrap.bind("+", () => { if (zoomIndex !== undefined && zoomIndex < maxZoom) { @@ -32,7 +27,17 @@ export const ZoomSelect: React.FC = ({ Mousetrap.unbind("-"); }; }); +} +export interface IZoomSelectProps { + zoomIndex: number; + onChangeZoom: (v: number) => void; +} + +export const ZoomSelect: React.FC = ({ + zoomIndex, + onChangeZoom, +}) => { return ( = ({ selectable > { Mousetrap.unbind("d d"); }; }); + useZoomKeybinds({ + zoomIndex: filter.zoomIndex, + onChangeZoom: (zoom) => setFilter(filter.setZoom(zoom)), + }); const onCloseEditDelete = useCloseEditDelete({ closeModal, diff --git a/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx b/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx index dbe6e2e23..3ae595b7c 100644 --- a/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx +++ b/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx @@ -148,7 +148,6 @@ export const SceneMarkerList: React.FC = ({ selectable > = ({ selectable > = ({ filterHook, alterQuery }) => { >