mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Refactor filtered list toolbar (#6317)
* Refactor list operation buttons into a single button group * Refactor ListFilter into FilteredListToolbar and restyle * Move zoom keybinds out of zoom control * Use button group for display mode select * Hide zoom slider on xs devices
This commit is contained in:
parent
50ad3c0778
commit
d6a2953371
15 changed files with 208 additions and 242 deletions
|
|
@ -195,7 +195,6 @@ export const GalleryList: React.FC<IGalleryList> = ({
|
|||
selectable
|
||||
>
|
||||
<ItemList
|
||||
zoomable
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
|
|
|
|||
|
|
@ -226,7 +226,6 @@ export const GroupList: React.FC<IGroupList> = ({
|
|||
selectable={selectable}
|
||||
>
|
||||
<ItemList
|
||||
zoomable
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
|
|
|
|||
|
|
@ -458,7 +458,6 @@ export const ImageList: React.FC<IImageList> = ({
|
|||
selectable
|
||||
>
|
||||
<ItemList
|
||||
zoomable
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import React from "react";
|
|||
import { QueryResult } from "@apollo/client";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { ListFilter } from "./ListFilter";
|
||||
import { ListViewOptions } from "./ListViewOptions";
|
||||
import { PageSizeSelector, SearchTermInput, SortBySelect } from "./ListFilter";
|
||||
import { ListViewButtonGroup } from "./ListViewOptions";
|
||||
import {
|
||||
IListFilterOperation,
|
||||
ListOperationButtons,
|
||||
|
|
@ -11,6 +11,8 @@ import {
|
|||
import { ButtonGroup, ButtonToolbar } from "react-bootstrap";
|
||||
import { View } from "./views";
|
||||
import { IListSelect, useFilterOperations } from "./util";
|
||||
import { SavedFilterDropdown } from "./SavedFilterList";
|
||||
import { FilterButton } from "./Filters/FilterButton";
|
||||
|
||||
export interface IItemListOperation<T extends QueryResult> {
|
||||
text: string;
|
||||
|
|
@ -63,34 +65,47 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
|
|||
|
||||
return (
|
||||
<ButtonToolbar className="filtered-list-toolbar">
|
||||
<SearchTermInput filter={filter} onFilterUpdate={setFilter} />
|
||||
|
||||
<ButtonGroup>
|
||||
{showEditFilter && (
|
||||
<ListFilter
|
||||
onFilterUpdate={setFilter}
|
||||
filter={filter}
|
||||
openFilterDialog={() => showEditFilter()}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
<ListOperationButtons
|
||||
onSelectAll={onSelectAll}
|
||||
onSelectNone={onSelectNone}
|
||||
otherOperations={operations}
|
||||
itemsSelected={selectedIds.size > 0}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
<SavedFilterDropdown
|
||||
filter={filter}
|
||||
onSetFilter={setFilter}
|
||||
view={view}
|
||||
/>
|
||||
<ButtonGroup>
|
||||
<ListViewOptions
|
||||
displayMode={filter.displayMode}
|
||||
displayModeOptions={filterOptions.displayModeOptions}
|
||||
onSetDisplayMode={setDisplayMode}
|
||||
zoomIndex={zoomable ? filter.zoomIndex : undefined}
|
||||
onSetZoom={zoomable ? setZoom : undefined}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<FilterButton onClick={() => showEditFilter()} count={filter.count()} />
|
||||
</ButtonGroup>
|
||||
<ButtonGroup></ButtonGroup>
|
||||
|
||||
<SortBySelect
|
||||
sortBy={filter.sortBy}
|
||||
sortDirection={filter.sortDirection}
|
||||
options={filterOptions.sortByOptions}
|
||||
onChangeSortBy={(e) => setFilter(filter.setSortBy(e ?? undefined))}
|
||||
onChangeSortDirection={() => setFilter(filter.toggleSortDirection())}
|
||||
onReshuffleRandomSort={() => setFilter(filter.reshuffleRandomSort())}
|
||||
/>
|
||||
|
||||
<PageSizeSelector
|
||||
pageSize={filter.itemsPerPage}
|
||||
setPageSize={(size) => setFilter(filter.setPageSize(size))}
|
||||
/>
|
||||
|
||||
<ListOperationButtons
|
||||
onSelectAll={onSelectAll}
|
||||
onSelectNone={onSelectNone}
|
||||
otherOperations={operations}
|
||||
itemsSelected={selectedIds.size > 0}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
|
||||
<ListViewButtonGroup
|
||||
displayMode={filter.displayMode}
|
||||
displayModeOptions={filterOptions.displayModeOptions}
|
||||
onSetDisplayMode={setDisplayMode}
|
||||
zoomIndex={zoomable ? filter.zoomIndex : undefined}
|
||||
onSetZoom={zoomable ? setZoom : undefined}
|
||||
/>
|
||||
</ButtonToolbar>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<T extends QueryResult, E extends IHasID = IHasID> {
|
||||
filterStateProps: IFilterStateHook;
|
||||
|
|
@ -112,7 +114,6 @@ export function useFilteredItemList<
|
|||
|
||||
interface IItemListProps<T extends QueryResult, E extends IHasID> {
|
||||
view?: View;
|
||||
zoomable?: boolean;
|
||||
otherOperations?: IItemListOperation<T>[];
|
||||
renderContent: (
|
||||
result: T,
|
||||
|
|
@ -144,7 +145,6 @@ export const ItemList = <T extends QueryResult, E extends IHasID>(
|
|||
) => {
|
||||
const {
|
||||
view,
|
||||
zoomable,
|
||||
otherOperations,
|
||||
renderContent,
|
||||
renderEditDialog,
|
||||
|
|
@ -216,6 +216,15 @@ export const ItemList = <T extends QueryResult, E extends IHasID>(
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -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<{
|
|||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
interface IListFilterProps {
|
||||
onFilterUpdate: (newFilter: ListFilterModel) => void;
|
||||
filter: ListFilterModel;
|
||||
view?: View;
|
||||
openFilterDialog: () => void;
|
||||
}
|
||||
|
||||
export const ListFilter: React.FC<IListFilterProps> = ({
|
||||
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 (
|
||||
<>
|
||||
<div className="d-flex">
|
||||
<SearchTermInput filter={filter} onFilterUpdate={onFilterUpdate} />
|
||||
</div>
|
||||
|
||||
<ButtonGroup className="mr-2">
|
||||
<SavedFilterDropdown
|
||||
filter={filter}
|
||||
onSetFilter={(f) => {
|
||||
onFilterUpdate(f);
|
||||
}}
|
||||
view={view}
|
||||
/>
|
||||
<OverlayTrigger
|
||||
placement="top"
|
||||
overlay={
|
||||
<Tooltip id="filter-tooltip">
|
||||
<FormattedMessage id="search_filter.name" />
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FilterButton
|
||||
onClick={() => openFilterDialog()}
|
||||
count={filter.count()}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
</ButtonGroup>
|
||||
|
||||
<SortBySelect
|
||||
className="mr-2"
|
||||
sortBy={filter.sortBy}
|
||||
sortDirection={filter.sortDirection}
|
||||
options={filterOptions.sortByOptions}
|
||||
onChangeSortBy={onChangeSortBy}
|
||||
onChangeSortDirection={onChangeSortDirection}
|
||||
onReshuffleRandomSort={onReshuffleRandomSort}
|
||||
/>
|
||||
|
||||
<PageSizeSelector
|
||||
pageSize={filter.itemsPerPage}
|
||||
setPageSize={onChangePageSize}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return render();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<IListOperationButtonsProps> = ({
|
|||
};
|
||||
});
|
||||
|
||||
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<IListOperationButtonsProps> = ({
|
|||
|
||||
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<IListOperationButtonsProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
if (buttons.length > 0) {
|
||||
return (
|
||||
<ButtonGroup className="ml-2">
|
||||
{buttons.map((button) => {
|
||||
return (
|
||||
<OverlayTrigger
|
||||
overlay={<Tooltip id="edit">{button.text}</Tooltip>}
|
||||
key={button.text}
|
||||
>
|
||||
<Button
|
||||
variant={button.buttonVariant ?? "secondary"}
|
||||
onClick={button.onClick}
|
||||
>
|
||||
{button.icon ? <Icon icon={button.icon} /> : undefined}
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
})}
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}, [otherOperations, itemsSelected, onEdit, onDelete, intl]);
|
||||
|
||||
function renderSelectAll() {
|
||||
if (onSelectAll) {
|
||||
return (
|
||||
<Dropdown.Item
|
||||
key="select-all"
|
||||
className="bg-secondary text-white"
|
||||
onClick={() => onSelectAll?.()}
|
||||
>
|
||||
<FormattedMessage id="actions.select_all" />
|
||||
</Dropdown.Item>
|
||||
);
|
||||
}
|
||||
}
|
||||
const operationButtons = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{buttons.map((button) => {
|
||||
return (
|
||||
<Button
|
||||
key={button.text}
|
||||
variant={button.buttonVariant ?? "secondary"}
|
||||
onClick={button.onClick}
|
||||
title={button.text}
|
||||
>
|
||||
<Icon icon={button.icon!} />
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}, [buttons]);
|
||||
|
||||
function renderSelectNone() {
|
||||
if (onSelectNone) {
|
||||
return (
|
||||
<Dropdown.Item
|
||||
key="select-none"
|
||||
className="bg-secondary text-white"
|
||||
onClick={() => onSelectNone?.()}
|
||||
>
|
||||
<FormattedMessage id="actions.select_none" />
|
||||
</Dropdown.Item>
|
||||
);
|
||||
const moreDropdown = useMemo(() => {
|
||||
function renderSelectAll() {
|
||||
if (onSelectAll) {
|
||||
return (
|
||||
<Dropdown.Item
|
||||
key="select-all"
|
||||
className="bg-secondary text-white"
|
||||
onClick={() => onSelectAll?.()}
|
||||
>
|
||||
<FormattedMessage id="actions.select_all" />
|
||||
</Dropdown.Item>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSelectNone() {
|
||||
if (onSelectNone) {
|
||||
return (
|
||||
<Dropdown.Item
|
||||
key="select-none"
|
||||
className="bg-secondary text-white"
|
||||
onClick={() => onSelectNone?.()}
|
||||
>
|
||||
<FormattedMessage id="actions.select_none" />
|
||||
</Dropdown.Item>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderMore() {
|
||||
const options = [renderSelectAll(), renderSelectNone()].filter((o) => o);
|
||||
|
||||
if (otherOperations) {
|
||||
|
|
@ -224,13 +218,19 @@ export const ListOperationButtons: React.FC<IListOperationButtonsProps> = ({
|
|||
{options.length > 0 ? options : undefined}
|
||||
</OperationDropdown>
|
||||
);
|
||||
}, [otherOperations, onSelectAll, onSelectNone]);
|
||||
|
||||
// don't render anything if there are no buttons or operations
|
||||
if (buttons.length === 0 && !moreDropdown) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{maybeRenderButtons()}
|
||||
|
||||
<ButtonGroup className="ml-2">{renderMore()}</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
{operationButtons}
|
||||
{moreDropdown}
|
||||
</ButtonGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<IListViewOptionsProps> = ({
|
||||
zoomIndex,
|
||||
onSetZoom,
|
||||
|
|
@ -60,9 +72,6 @@ export const ListViewOptions: React.FC<IListViewOptionsProps> = ({
|
|||
onSetDisplayMode,
|
||||
displayModeOptions,
|
||||
}) => {
|
||||
const minZoom = 0;
|
||||
const maxZoom = 3;
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const overlayTarget = useRef(null);
|
||||
|
|
@ -98,10 +107,6 @@ export const ListViewOptions: React.FC<IListViewOptionsProps> = ({
|
|||
};
|
||||
});
|
||||
|
||||
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<IListViewOptionsProps> = ({
|
|||
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<IListViewOptionsProps> = ({
|
|||
displayMode === DisplayMode.Wall) ? (
|
||||
<div className="zoom-slider-container">
|
||||
<ZoomSelect
|
||||
minZoom={minZoom}
|
||||
maxZoom={maxZoom}
|
||||
zoomIndex={zoomIndex}
|
||||
onChangeZoom={onChangeZoom}
|
||||
/>
|
||||
|
|
@ -156,7 +159,7 @@ export const ListViewOptions: React.FC<IListViewOptionsProps> = ({
|
|||
onSetDisplayMode(option);
|
||||
}}
|
||||
>
|
||||
<Icon icon={getIcon(option)} /> {getLabel(option)}
|
||||
<Icon icon={getIcon(option)} /> {getLabel(intl, option)}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -167,3 +170,48 @@ export const ListViewOptions: React.FC<IListViewOptionsProps> = ({
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ListViewButtonGroup: React.FC<IListViewOptionsProps> = ({
|
||||
zoomIndex,
|
||||
onSetZoom,
|
||||
displayMode,
|
||||
onSetDisplayMode,
|
||||
displayModeOptions,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
{displayModeOptions.length > 1 && (
|
||||
<ButtonGroup>
|
||||
{displayModeOptions.map((option) => (
|
||||
<OverlayTrigger
|
||||
key={option}
|
||||
overlay={
|
||||
<Tooltip id="display-mode-tooltip">
|
||||
{getLabel(intl, option)}
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
active={displayMode === option}
|
||||
onClick={() => onSetDisplayMode(option)}
|
||||
>
|
||||
<Icon icon={getIcon(option)} />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
)}
|
||||
<div className="zoom-slider-container">
|
||||
{onSetZoom &&
|
||||
zoomIndex !== undefined &&
|
||||
(displayMode === DisplayMode.Grid ||
|
||||
displayMode === DisplayMode.Wall) ? (
|
||||
<ZoomSelect zoomIndex={zoomIndex} onChangeZoom={onSetZoom} />
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<IZoomSelectProps> = ({
|
||||
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<IZoomSelectProps> = ({
|
|||
Mousetrap.unbind("-");
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export interface IZoomSelectProps {
|
||||
zoomIndex: number;
|
||||
onChangeZoom: (v: number) => void;
|
||||
}
|
||||
|
||||
export const ZoomSelect: React.FC<IZoomSelectProps> = ({
|
||||
zoomIndex,
|
||||
onChangeZoom,
|
||||
}) => {
|
||||
return (
|
||||
<Form.Control
|
||||
className="zoom-slider"
|
||||
|
|
|
|||
|
|
@ -93,7 +93,8 @@
|
|||
|
||||
// hide zoom slider in xs viewport
|
||||
@include media-breakpoint-down(xs) {
|
||||
.display-mode-menu .zoom-slider-container {
|
||||
.display-mode-menu .zoom-slider-container,
|
||||
.zoom-slider-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -916,6 +917,8 @@ input[type="range"].zoom-slider {
|
|||
}
|
||||
|
||||
.filtered-list-toolbar {
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
|
|
@ -933,8 +936,10 @@ input[type="range"].zoom-slider {
|
|||
}
|
||||
}
|
||||
|
||||
.btn.display-mode-select {
|
||||
margin-left: 0.5rem;
|
||||
// set the width of the zoom-slider-container to prevent buttons moving when
|
||||
// the slider appears/disappears
|
||||
.zoom-slider-container {
|
||||
min-width: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -946,10 +951,6 @@ input[type="range"].zoom-slider {
|
|||
}
|
||||
}
|
||||
|
||||
.search-term-input {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.custom-field-filter {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -329,7 +329,6 @@ export const PerformerList: React.FC<IPerformerList> = ({
|
|||
selectable
|
||||
>
|
||||
<ItemList
|
||||
zoomable
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ import {
|
|||
ToolbarSelectionSection,
|
||||
} from "../List/ListToolbar";
|
||||
import { ListResultsHeader } from "../List/ListResultsHeader";
|
||||
import { useZoomKeybinds } from "../List/ZoomSlider";
|
||||
|
||||
function renderMetadataByline(result: GQL.FindScenesQueryResult) {
|
||||
const duration = result?.data?.findScenes?.duration;
|
||||
|
|
@ -548,6 +549,10 @@ export const FilteredSceneList = (props: IFilteredScenes) => {
|
|||
Mousetrap.unbind("d d");
|
||||
};
|
||||
});
|
||||
useZoomKeybinds({
|
||||
zoomIndex: filter.zoomIndex,
|
||||
onChangeZoom: (zoom) => setFilter(filter.setZoom(zoom)),
|
||||
});
|
||||
|
||||
const onCloseEditDelete = useCloseEditDelete({
|
||||
closeModal,
|
||||
|
|
|
|||
|
|
@ -148,7 +148,6 @@ export const SceneMarkerList: React.FC<ISceneMarkerList> = ({
|
|||
selectable
|
||||
>
|
||||
<ItemList
|
||||
zoomable
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,6 @@ export const StudioList: React.FC<IStudioList> = ({
|
|||
selectable
|
||||
>
|
||||
<ItemList
|
||||
zoomable
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
|
|
|
|||
|
|
@ -367,7 +367,6 @@ export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
|
|||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
zoomable
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
|
|
|
|||
Loading…
Reference in a new issue