mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
Scene list cleanup (#6104)
* Generalise and cleanup list toolbar * Generalise ListResultsHeader * Fix padding on sub-pages
This commit is contained in:
parent
af76f4a24a
commit
c5bad48ece
9 changed files with 560 additions and 583 deletions
|
|
@ -8,11 +8,9 @@ import {
|
||||||
IListFilterOperation,
|
IListFilterOperation,
|
||||||
ListOperationButtons,
|
ListOperationButtons,
|
||||||
} from "./ListOperationButtons";
|
} from "./ListOperationButtons";
|
||||||
import { Button, ButtonGroup, ButtonToolbar } from "react-bootstrap";
|
import { ButtonGroup, ButtonToolbar } from "react-bootstrap";
|
||||||
import { View } from "./views";
|
import { View } from "./views";
|
||||||
import { IListSelect, useFilterOperations } from "./util";
|
import { IListSelect, useFilterOperations } from "./util";
|
||||||
import { SidebarIcon } from "../Shared/Sidebar";
|
|
||||||
import { useIntl } from "react-intl";
|
|
||||||
|
|
||||||
export interface IItemListOperation<T extends QueryResult> {
|
export interface IItemListOperation<T extends QueryResult> {
|
||||||
text: string;
|
text: string;
|
||||||
|
|
@ -43,7 +41,6 @@ export interface IFilteredListToolbar {
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
operations?: IListFilterOperation[];
|
operations?: IListFilterOperation[];
|
||||||
zoomable?: boolean;
|
zoomable?: boolean;
|
||||||
onToggleSidebar?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
|
export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
|
||||||
|
|
@ -56,9 +53,7 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
|
||||||
onDelete,
|
onDelete,
|
||||||
operations,
|
operations,
|
||||||
zoomable = false,
|
zoomable = false,
|
||||||
onToggleSidebar,
|
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
|
||||||
const filterOptions = filter.options;
|
const filterOptions = filter.options;
|
||||||
const { setDisplayMode, setZoom } = useFilterOperations({
|
const { setDisplayMode, setZoom } = useFilterOperations({
|
||||||
filter,
|
filter,
|
||||||
|
|
@ -68,21 +63,6 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonToolbar className="filtered-list-toolbar">
|
<ButtonToolbar className="filtered-list-toolbar">
|
||||||
<ButtonGroup>
|
|
||||||
{onToggleSidebar && (
|
|
||||||
<ButtonGroup>
|
|
||||||
<Button
|
|
||||||
className="sidebar-toggle-button"
|
|
||||||
onClick={onToggleSidebar}
|
|
||||||
variant="secondary"
|
|
||||||
title={intl.formatMessage({ id: "actions.sidebar.open" })}
|
|
||||||
>
|
|
||||||
<SidebarIcon />
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
)}
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{showEditFilter && (
|
{showEditFilter && (
|
||||||
<ListFilter
|
<ListFilter
|
||||||
|
|
@ -90,7 +70,6 @@ export const FilteredListToolbar: React.FC<IFilteredListToolbar> = ({
|
||||||
filter={filter}
|
filter={filter}
|
||||||
openFilterDialog={() => showEditFilter()}
|
openFilterDialog={() => showEditFilter()}
|
||||||
view={view}
|
view={view}
|
||||||
withSidebar={!!onToggleSidebar}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ListOperationButtons
|
<ListOperationButtons
|
||||||
|
|
|
||||||
|
|
@ -324,7 +324,6 @@ interface IListFilterProps {
|
||||||
filter: ListFilterModel;
|
filter: ListFilterModel;
|
||||||
view?: View;
|
view?: View;
|
||||||
openFilterDialog: () => void;
|
openFilterDialog: () => void;
|
||||||
withSidebar?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ListFilter: React.FC<IListFilterProps> = ({
|
export const ListFilter: React.FC<IListFilterProps> = ({
|
||||||
|
|
@ -332,7 +331,6 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||||
filter,
|
filter,
|
||||||
openFilterDialog,
|
openFilterDialog,
|
||||||
view,
|
view,
|
||||||
withSidebar,
|
|
||||||
}) => {
|
}) => {
|
||||||
const filterOptions = filter.options;
|
const filterOptions = filter.options;
|
||||||
|
|
||||||
|
|
@ -379,36 +377,32 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||||
function render() {
|
function render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!withSidebar && (
|
<div className="d-flex">
|
||||||
<div className="d-flex">
|
<SearchTermInput filter={filter} onFilterUpdate={onFilterUpdate} />
|
||||||
<SearchTermInput filter={filter} onFilterUpdate={onFilterUpdate} />
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!withSidebar && (
|
<ButtonGroup className="mr-2">
|
||||||
<ButtonGroup className="mr-2">
|
<SavedFilterDropdown
|
||||||
<SavedFilterDropdown
|
filter={filter}
|
||||||
filter={filter}
|
onSetFilter={(f) => {
|
||||||
onSetFilter={(f) => {
|
onFilterUpdate(f);
|
||||||
onFilterUpdate(f);
|
}}
|
||||||
}}
|
view={view}
|
||||||
view={view}
|
/>
|
||||||
|
<OverlayTrigger
|
||||||
|
placement="top"
|
||||||
|
overlay={
|
||||||
|
<Tooltip id="filter-tooltip">
|
||||||
|
<FormattedMessage id="search_filter.name" />
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FilterButton
|
||||||
|
onClick={() => openFilterDialog()}
|
||||||
|
count={filter.count()}
|
||||||
/>
|
/>
|
||||||
<OverlayTrigger
|
</OverlayTrigger>
|
||||||
placement="top"
|
</ButtonGroup>
|
||||||
overlay={
|
|
||||||
<Tooltip id="filter-tooltip">
|
|
||||||
<FormattedMessage id="search_filter.name" />
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FilterButton
|
|
||||||
onClick={() => openFilterDialog()}
|
|
||||||
count={filter.count()}
|
|
||||||
/>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</ButtonGroup>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<SortBySelect
|
<SortBySelect
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
|
|
|
||||||
66
ui/v2.5/src/components/List/ListResultsHeader.tsx
Normal file
66
ui/v2.5/src/components/List/ListResultsHeader.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import React from "react";
|
||||||
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
import { PaginationIndex } from "../List/Pagination";
|
||||||
|
import { ButtonToolbar } from "react-bootstrap";
|
||||||
|
import { ListViewOptions } from "../List/ListViewOptions";
|
||||||
|
import { PageSizeSelector, SortBySelect } from "../List/ListFilter";
|
||||||
|
import cx from "classnames";
|
||||||
|
|
||||||
|
export const ListResultsHeader: React.FC<{
|
||||||
|
className?: string;
|
||||||
|
loading: boolean;
|
||||||
|
filter: ListFilterModel;
|
||||||
|
totalCount: number;
|
||||||
|
metadataByline?: React.ReactNode;
|
||||||
|
onChangeFilter: (filter: ListFilterModel) => void;
|
||||||
|
}> = ({
|
||||||
|
className,
|
||||||
|
loading,
|
||||||
|
filter,
|
||||||
|
totalCount,
|
||||||
|
metadataByline,
|
||||||
|
onChangeFilter,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ButtonToolbar className={cx(className, "list-results-header")}>
|
||||||
|
<div>
|
||||||
|
<PaginationIndex
|
||||||
|
loading={loading}
|
||||||
|
itemsPerPage={filter.itemsPerPage}
|
||||||
|
currentPage={filter.currentPage}
|
||||||
|
totalItems={totalCount}
|
||||||
|
metadataByline={metadataByline}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<SortBySelect
|
||||||
|
options={filter.options.sortByOptions}
|
||||||
|
sortBy={filter.sortBy}
|
||||||
|
sortDirection={filter.sortDirection}
|
||||||
|
onChangeSortBy={(s) =>
|
||||||
|
onChangeFilter(filter.setSortBy(s ?? undefined))
|
||||||
|
}
|
||||||
|
onChangeSortDirection={() =>
|
||||||
|
onChangeFilter(filter.toggleSortDirection())
|
||||||
|
}
|
||||||
|
onReshuffleRandomSort={() =>
|
||||||
|
onChangeFilter(filter.reshuffleRandomSort())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<PageSizeSelector
|
||||||
|
pageSize={filter.itemsPerPage}
|
||||||
|
setPageSize={(s) => onChangeFilter(filter.setPageSize(s))}
|
||||||
|
/>
|
||||||
|
<ListViewOptions
|
||||||
|
displayMode={filter.displayMode}
|
||||||
|
zoomIndex={filter.zoomIndex}
|
||||||
|
displayModeOptions={filter.options.displayModeOptions}
|
||||||
|
onSetDisplayMode={(mode) =>
|
||||||
|
onChangeFilter(filter.setDisplayMode(mode))
|
||||||
|
}
|
||||||
|
onSetZoom={(zoom) => onChangeFilter(filter.setZoom(zoom))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ButtonToolbar>
|
||||||
|
);
|
||||||
|
};
|
||||||
120
ui/v2.5/src/components/List/ListToolbar.tsx
Normal file
120
ui/v2.5/src/components/List/ListToolbar.tsx
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FilterTags } from "../List/FilterTags";
|
||||||
|
import cx from "classnames";
|
||||||
|
import { Button, ButtonToolbar } from "react-bootstrap";
|
||||||
|
import { FilterButton } from "../List/Filters/FilterButton";
|
||||||
|
import { Icon } from "../Shared/Icon";
|
||||||
|
import { SearchTermInput } from "../List/ListFilter";
|
||||||
|
import { Criterion } from "src/models/list-filter/criteria/criterion";
|
||||||
|
import { SidebarToggleButton } from "../Shared/Sidebar";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
|
export const ToolbarFilterSection: React.FC<{
|
||||||
|
filter: ListFilterModel;
|
||||||
|
onToggleSidebar: () => void;
|
||||||
|
onSetFilter: (filter: ListFilterModel) => void;
|
||||||
|
onEditCriterion: (c?: Criterion) => void;
|
||||||
|
onRemoveCriterion: (criterion: Criterion, valueIndex?: number) => void;
|
||||||
|
onRemoveAllCriterion: () => void;
|
||||||
|
onEditSearchTerm: () => void;
|
||||||
|
onRemoveSearchTerm: () => void;
|
||||||
|
}> = PatchComponent(
|
||||||
|
"ToolbarFilterSection",
|
||||||
|
({
|
||||||
|
filter,
|
||||||
|
onToggleSidebar,
|
||||||
|
onSetFilter,
|
||||||
|
onEditCriterion,
|
||||||
|
onRemoveCriterion,
|
||||||
|
onRemoveAllCriterion,
|
||||||
|
onEditSearchTerm,
|
||||||
|
onRemoveSearchTerm,
|
||||||
|
}) => {
|
||||||
|
const { criteria, searchTerm } = filter;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="search-container">
|
||||||
|
<SearchTermInput filter={filter} onFilterUpdate={onSetFilter} />
|
||||||
|
</div>
|
||||||
|
<div className="filter-section">
|
||||||
|
<FilterButton
|
||||||
|
onClick={() => onEditCriterion()}
|
||||||
|
count={criteria.length}
|
||||||
|
/>
|
||||||
|
<FilterTags
|
||||||
|
searchTerm={searchTerm}
|
||||||
|
criteria={criteria}
|
||||||
|
onEditCriterion={onEditCriterion}
|
||||||
|
onRemoveCriterion={onRemoveCriterion}
|
||||||
|
onRemoveAll={onRemoveAllCriterion}
|
||||||
|
onEditSearchTerm={onEditSearchTerm}
|
||||||
|
onRemoveSearchTerm={onRemoveSearchTerm}
|
||||||
|
truncateOnOverflow
|
||||||
|
/>
|
||||||
|
<SidebarToggleButton onClick={onToggleSidebar} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ToolbarSelectionSection: React.FC<{
|
||||||
|
selected: number;
|
||||||
|
onToggleSidebar: () => void;
|
||||||
|
onSelectAll: () => void;
|
||||||
|
onSelectNone: () => void;
|
||||||
|
}> = PatchComponent(
|
||||||
|
"ToolbarSelectionSection",
|
||||||
|
({ selected, onToggleSidebar, onSelectAll, onSelectNone }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="selected-items-info">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
className="minimal"
|
||||||
|
onClick={() => onSelectNone()}
|
||||||
|
title={intl.formatMessage({ id: "actions.select_none" })}
|
||||||
|
>
|
||||||
|
<Icon icon={faTimes} />
|
||||||
|
</Button>
|
||||||
|
<span>{selected} selected</span>
|
||||||
|
<Button variant="link" onClick={() => onSelectAll()}>
|
||||||
|
<FormattedMessage id="actions.select_all" />
|
||||||
|
</Button>
|
||||||
|
<SidebarToggleButton onClick={onToggleSidebar} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO - rename to FilteredListToolbar once all list components have been updated
|
||||||
|
// TODO - and expose to plugins
|
||||||
|
export const FilteredListToolbar2: React.FC<{
|
||||||
|
className?: string;
|
||||||
|
hasSelection: boolean;
|
||||||
|
filterSection: React.ReactNode;
|
||||||
|
selectionSection: React.ReactNode;
|
||||||
|
operationSection: React.ReactNode;
|
||||||
|
}> = ({
|
||||||
|
className,
|
||||||
|
hasSelection,
|
||||||
|
filterSection,
|
||||||
|
selectionSection,
|
||||||
|
operationSection,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ButtonToolbar
|
||||||
|
className={cx(className, "filtered-list-toolbar", {
|
||||||
|
"has-selection": hasSelection,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{!hasSelection ? filterSection : selectionSection}
|
||||||
|
<div className="filtered-list-toolbar-operations">{operationSection}</div>
|
||||||
|
</ButtonToolbar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1046,3 +1046,234 @@ input[type="range"].zoom-slider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide sidebar Edit Filter button on larger screens
|
||||||
|
@include media-breakpoint-up(lg) {
|
||||||
|
.sidebar .edit-filter-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the following refers to the new FilteredListToolbar2 component
|
||||||
|
// ensure the rules here don't conflict with the original filtered-list-toolbar above
|
||||||
|
// TODO - replace with only .filtered-list-toolbar once all lists use the new toolbar
|
||||||
|
.scene-list-toolbar {
|
||||||
|
&.filtered-list-toolbar {
|
||||||
|
align-items: center;
|
||||||
|
background-color: $body-bg;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
row-gap: 1rem;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
flex-shrink: 0;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.filtered-list-toolbar {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 1rem;
|
||||||
|
// offset the main padding
|
||||||
|
margin-top: -0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
position: sticky;
|
||||||
|
top: $navbar-height;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-items-info .btn {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide drop down menu items for play and create new
|
||||||
|
// when the buttons are visible
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
.scene-list-operations {
|
||||||
|
.play-item,
|
||||||
|
.create-new-item {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide play and create new buttons on xs screens
|
||||||
|
// show these in the drop down menu instead
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
.play-button,
|
||||||
|
.create-new-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-items-info,
|
||||||
|
div.filter-section {
|
||||||
|
border: 1px solid $secondary;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-toggle-button {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
border-right: 1px solid $secondary;
|
||||||
|
display: block;
|
||||||
|
margin-right: -0.5rem;
|
||||||
|
min-width: calc($sidebar-width - 15px);
|
||||||
|
padding-right: 10px;
|
||||||
|
|
||||||
|
.search-term-input {
|
||||||
|
margin-right: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.clearable-text-field {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tags {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
// account for filter button, and toggle sidebar buttons with gaps
|
||||||
|
width: calc(100% - 70px - 1rem);
|
||||||
|
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(xl) {
|
||||||
|
.sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar .search-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
.sidebar-pane.hide-sidebar .filtered-list-toolbar .search-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide the filter icon button when sidebar is shown on smaller screens
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
.sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar .filter-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust the width of the filter-tags as well
|
||||||
|
.sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar .filter-tags {
|
||||||
|
width: calc(100% - 35px - 0.5rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the sidebar toggle to the left on xl viewports
|
||||||
|
@include media-breakpoint-up(xl) {
|
||||||
|
.filtered-list-toolbar .filter-section {
|
||||||
|
.sidebar-toggle-button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tags {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide the search term tag item when the search box is visible
|
||||||
|
@include media-breakpoint-up(lg) {
|
||||||
|
// TODO - remove scene-list-toolbar when all lists use the new toolbar
|
||||||
|
.scene-list-toolbar.filtered-list-toolbar
|
||||||
|
.filter-tags
|
||||||
|
.search-term-filter-tag {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
// TODO - remove scene-list-toolbar when all lists use the new toolbar
|
||||||
|
.sidebar-pane:not(.hide-sidebar)
|
||||||
|
.scene-list-toolbar.filtered-list-toolbar
|
||||||
|
.filter-tags
|
||||||
|
.search-term-filter-tag {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - remove scene-list-toolbar when all lists use the new toolbar
|
||||||
|
.detail-body .scene-list-toolbar.filtered-list-toolbar {
|
||||||
|
top: calc($sticky-detail-header-height + $navbar-height);
|
||||||
|
|
||||||
|
@include media-breakpoint-down(xs) {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#more-criteria-popover {
|
||||||
|
box-shadow: 0 8px 10px 2px rgb(0 0 0 / 30%);
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-results-header {
|
||||||
|
align-items: center;
|
||||||
|
background-color: $body-bg;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
flex-shrink: 0;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-results-header {
|
||||||
|
flex-wrap: wrap-reverse;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
.paginationIndex {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// center the header on smaller screens
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
& > div,
|
||||||
|
& > div:last-child {
|
||||||
|
flex-basis: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,6 @@ import {
|
||||||
faPencil,
|
faPencil,
|
||||||
faPlay,
|
faPlay,
|
||||||
faPlus,
|
faPlus,
|
||||||
faSliders,
|
|
||||||
faTimes,
|
|
||||||
faTrash,
|
faTrash,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { SceneMergeModal } from "./SceneMergeDialog";
|
import { SceneMergeModal } from "./SceneMergeDialog";
|
||||||
|
|
@ -39,7 +37,6 @@ import {
|
||||||
OperationDropdownItem,
|
OperationDropdownItem,
|
||||||
} from "../List/ListOperationButtons";
|
} from "../List/ListOperationButtons";
|
||||||
import { useFilteredItemList } from "../List/ItemList";
|
import { useFilteredItemList } from "../List/ItemList";
|
||||||
import { FilterTags } from "../List/FilterTags";
|
|
||||||
import { Sidebar, SidebarPane, useSidebarState } from "../Shared/Sidebar";
|
import { Sidebar, SidebarPane, useSidebarState } from "../Shared/Sidebar";
|
||||||
import { SidebarPerformersFilter } from "../List/Filters/PerformersFilter";
|
import { SidebarPerformersFilter } from "../List/Filters/PerformersFilter";
|
||||||
import { SidebarStudiosFilter } from "../List/Filters/StudiosFilter";
|
import { SidebarStudiosFilter } from "../List/Filters/StudiosFilter";
|
||||||
|
|
@ -57,18 +54,16 @@ import {
|
||||||
useFilteredSidebarKeybinds,
|
useFilteredSidebarKeybinds,
|
||||||
} from "../List/Filters/FilterSidebar";
|
} from "../List/Filters/FilterSidebar";
|
||||||
import { PatchContainerComponent } from "src/patch";
|
import { PatchContainerComponent } from "src/patch";
|
||||||
import { Pagination, PaginationIndex } from "../List/Pagination";
|
import { Pagination } from "../List/Pagination";
|
||||||
import { Button, ButtonGroup, ButtonToolbar } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import { FilterButton } from "../List/Filters/FilterButton";
|
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { ListViewOptions } from "../List/ListViewOptions";
|
|
||||||
import {
|
|
||||||
PageSizeSelector,
|
|
||||||
SearchTermInput,
|
|
||||||
SortBySelect,
|
|
||||||
} from "../List/ListFilter";
|
|
||||||
import { Criterion } from "src/models/list-filter/criteria/criterion";
|
|
||||||
import useFocus from "src/utils/focus";
|
import useFocus from "src/utils/focus";
|
||||||
|
import {
|
||||||
|
FilteredListToolbar2,
|
||||||
|
ToolbarFilterSection,
|
||||||
|
ToolbarSelectionSection,
|
||||||
|
} from "../List/ListToolbar";
|
||||||
|
import { ListResultsHeader } from "../List/ListResultsHeader";
|
||||||
|
|
||||||
function renderMetadataByline(result: GQL.FindScenesQueryResult) {
|
function renderMetadataByline(result: GQL.FindScenesQueryResult) {
|
||||||
const duration = result?.data?.findScenes?.duration;
|
const duration = result?.data?.findScenes?.duration;
|
||||||
|
|
@ -340,38 +335,18 @@ interface IOperations {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListToolbarContent: React.FC<{
|
const SceneListOperations: React.FC<{
|
||||||
filter: ListFilterModel;
|
items: number;
|
||||||
items: GQL.SlimSceneDataFragment[];
|
hasSelection: boolean;
|
||||||
selectedIds: Set<string>;
|
|
||||||
operations: IOperations[];
|
operations: IOperations[];
|
||||||
onToggleSidebar: () => void;
|
|
||||||
onSetFilter: (filter: ListFilterModel) => void;
|
|
||||||
onEditCriterion: (c?: Criterion) => void;
|
|
||||||
onRemoveCriterion: (criterion: Criterion, valueIndex?: number) => void;
|
|
||||||
onRemoveAllCriterion: () => void;
|
|
||||||
onEditSearchTerm: () => void;
|
|
||||||
onRemoveSearchTerm: () => void;
|
|
||||||
onSelectAll: () => void;
|
|
||||||
onSelectNone: () => void;
|
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onPlay: () => void;
|
onPlay: () => void;
|
||||||
onCreateNew: () => void;
|
onCreateNew: () => void;
|
||||||
}> = ({
|
}> = ({
|
||||||
filter,
|
|
||||||
items,
|
items,
|
||||||
selectedIds,
|
hasSelection,
|
||||||
operations,
|
operations,
|
||||||
onToggleSidebar,
|
|
||||||
onSetFilter,
|
|
||||||
onEditCriterion,
|
|
||||||
onRemoveCriterion,
|
|
||||||
onRemoveAllCriterion,
|
|
||||||
onEditSearchTerm,
|
|
||||||
onRemoveSearchTerm,
|
|
||||||
onSelectAll,
|
|
||||||
onSelectNone,
|
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete,
|
onDelete,
|
||||||
onPlay,
|
onPlay,
|
||||||
|
|
@ -379,174 +354,66 @@ const ListToolbarContent: React.FC<{
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { criteria, searchTerm } = filter;
|
|
||||||
const hasSelection = selectedIds.size > 0;
|
|
||||||
|
|
||||||
const sidebarToggle = (
|
|
||||||
<Button
|
|
||||||
className="minimal sidebar-toggle-button ignore-sidebar-outside-click"
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => onToggleSidebar()}
|
|
||||||
title={intl.formatMessage({ id: "actions.sidebar.toggle" })}
|
|
||||||
>
|
|
||||||
<Icon icon={faSliders} />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
{!hasSelection && (
|
<ButtonGroup>
|
||||||
<>
|
{!!items && (
|
||||||
<div className="search-container">
|
|
||||||
<SearchTermInput filter={filter} onFilterUpdate={onSetFilter} />
|
|
||||||
</div>
|
|
||||||
<div className="filter-section">
|
|
||||||
<FilterButton
|
|
||||||
onClick={() => onEditCriterion()}
|
|
||||||
count={criteria.length}
|
|
||||||
/>
|
|
||||||
<FilterTags
|
|
||||||
searchTerm={searchTerm}
|
|
||||||
criteria={criteria}
|
|
||||||
onEditCriterion={onEditCriterion}
|
|
||||||
onRemoveCriterion={onRemoveCriterion}
|
|
||||||
onRemoveAll={onRemoveAllCriterion}
|
|
||||||
onEditSearchTerm={onEditSearchTerm}
|
|
||||||
onRemoveSearchTerm={onRemoveSearchTerm}
|
|
||||||
truncateOnOverflow
|
|
||||||
/>
|
|
||||||
{sidebarToggle}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{hasSelection && (
|
|
||||||
<div className="selected-items-info">
|
|
||||||
<Button
|
<Button
|
||||||
|
className="play-button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="minimal"
|
onClick={() => onPlay()}
|
||||||
onClick={() => onSelectNone()}
|
title={intl.formatMessage({ id: "actions.play" })}
|
||||||
title={intl.formatMessage({ id: "actions.select_none" })}
|
|
||||||
>
|
>
|
||||||
<Icon icon={faTimes} />
|
<Icon icon={faPlay} />
|
||||||
</Button>
|
</Button>
|
||||||
<span>{selectedIds.size} selected</span>
|
)}
|
||||||
<Button variant="link" onClick={() => onSelectAll()}>
|
{!hasSelection && (
|
||||||
<FormattedMessage id="actions.select_all" />
|
<Button
|
||||||
|
className="create-new-button"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => onCreateNew()}
|
||||||
|
title={intl.formatMessage(
|
||||||
|
{ id: "actions.create_entity" },
|
||||||
|
{ entityType: intl.formatMessage({ id: "scene" }) }
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon icon={faPlus} />
|
||||||
</Button>
|
</Button>
|
||||||
{sidebarToggle}
|
)}
|
||||||
</div>
|
|
||||||
)}
|
{hasSelection && (
|
||||||
<div>
|
<>
|
||||||
<ButtonGroup>
|
<Button variant="secondary" onClick={() => onEdit()}>
|
||||||
{!!items.length && (
|
<Icon icon={faPencil} />
|
||||||
<Button
|
|
||||||
className="play-button"
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => onPlay()}
|
|
||||||
title={intl.formatMessage({ id: "actions.play" })}
|
|
||||||
>
|
|
||||||
<Icon icon={faPlay} />
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{!hasSelection && (
|
|
||||||
<Button
|
<Button
|
||||||
className="create-new-button"
|
variant="danger"
|
||||||
variant="secondary"
|
className="btn-danger-minimal"
|
||||||
onClick={() => onCreateNew()}
|
onClick={() => onDelete()}
|
||||||
title={intl.formatMessage(
|
|
||||||
{ id: "actions.create_entity" },
|
|
||||||
{ entityType: intl.formatMessage({ id: "scene" }) }
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Icon icon={faPlus} />
|
<Icon icon={faTrash} />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{hasSelection && (
|
<OperationDropdown className="scene-list-operations">
|
||||||
<>
|
{operations.map((o) => {
|
||||||
<Button variant="secondary" onClick={() => onEdit()}>
|
if (o.isDisplayed && !o.isDisplayed()) {
|
||||||
<Icon icon={faPencil} />
|
return null;
|
||||||
</Button>
|
}
|
||||||
<Button
|
|
||||||
variant="danger"
|
|
||||||
className="btn-danger-minimal"
|
|
||||||
onClick={() => onDelete()}
|
|
||||||
>
|
|
||||||
<Icon icon={faTrash} />
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<OperationDropdown className="scene-list-operations">
|
return (
|
||||||
{operations.map((o) => {
|
<OperationDropdownItem
|
||||||
if (o.isDisplayed && !o.isDisplayed()) {
|
key={o.text}
|
||||||
return null;
|
onClick={o.onClick}
|
||||||
}
|
text={o.text}
|
||||||
|
className={o.className}
|
||||||
return (
|
/>
|
||||||
<OperationDropdownItem
|
);
|
||||||
key={o.text}
|
})}
|
||||||
onClick={o.onClick}
|
</OperationDropdown>
|
||||||
text={o.text}
|
</ButtonGroup>
|
||||||
className={o.className}
|
</div>
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</OperationDropdown>
|
|
||||||
</ButtonGroup>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ListResultsHeader: React.FC<{
|
|
||||||
loading: boolean;
|
|
||||||
filter: ListFilterModel;
|
|
||||||
totalCount: number;
|
|
||||||
metadataByline?: React.ReactNode;
|
|
||||||
onChangeFilter: (filter: ListFilterModel) => void;
|
|
||||||
}> = ({ loading, filter, totalCount, metadataByline, onChangeFilter }) => {
|
|
||||||
return (
|
|
||||||
<ButtonToolbar className="scene-list-header">
|
|
||||||
<div>
|
|
||||||
<PaginationIndex
|
|
||||||
loading={loading}
|
|
||||||
itemsPerPage={filter.itemsPerPage}
|
|
||||||
currentPage={filter.currentPage}
|
|
||||||
totalItems={totalCount}
|
|
||||||
metadataByline={metadataByline}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<SortBySelect
|
|
||||||
options={filter.options.sortByOptions}
|
|
||||||
sortBy={filter.sortBy}
|
|
||||||
sortDirection={filter.sortDirection}
|
|
||||||
onChangeSortBy={(s) =>
|
|
||||||
onChangeFilter(filter.setSortBy(s ?? undefined))
|
|
||||||
}
|
|
||||||
onChangeSortDirection={() =>
|
|
||||||
onChangeFilter(filter.toggleSortDirection())
|
|
||||||
}
|
|
||||||
onReshuffleRandomSort={() =>
|
|
||||||
onChangeFilter(filter.reshuffleRandomSort())
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<PageSizeSelector
|
|
||||||
pageSize={filter.itemsPerPage}
|
|
||||||
setPageSize={(s) => onChangeFilter(filter.setPageSize(s))}
|
|
||||||
/>
|
|
||||||
<ListViewOptions
|
|
||||||
displayMode={filter.displayMode}
|
|
||||||
zoomIndex={filter.zoomIndex}
|
|
||||||
displayModeOptions={filter.options.displayModeOptions}
|
|
||||||
onSetDisplayMode={(mode) =>
|
|
||||||
onChangeFilter(filter.setDisplayMode(mode))
|
|
||||||
}
|
|
||||||
onSetZoom={(zoom) => onChangeFilter(filter.setZoom(zoom))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ButtonToolbar>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -823,34 +690,46 @@ export const FilteredSceneList = (props: IFilteredScenes) => {
|
||||||
/>
|
/>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
<div>
|
<div>
|
||||||
<ButtonToolbar
|
<FilteredListToolbar2
|
||||||
className={cx("scene-list-toolbar", {
|
className="scene-list-toolbar"
|
||||||
"has-selection": hasSelection,
|
hasSelection={hasSelection}
|
||||||
})}
|
filterSection={
|
||||||
>
|
<ToolbarFilterSection
|
||||||
<ListToolbarContent
|
filter={filter}
|
||||||
filter={filter}
|
onSetFilter={setFilter}
|
||||||
onSetFilter={setFilter}
|
onToggleSidebar={() => setShowSidebar(!showSidebar)}
|
||||||
items={items}
|
onEditCriterion={(c) =>
|
||||||
selectedIds={selectedIds}
|
showEditFilter(c?.criterionOption.type)
|
||||||
operations={otherOperations}
|
}
|
||||||
onToggleSidebar={() => setShowSidebar(!showSidebar)}
|
onRemoveCriterion={removeCriterion}
|
||||||
onEditCriterion={(c) => showEditFilter(c?.criterionOption.type)}
|
onRemoveAllCriterion={() => clearAllCriteria(true)}
|
||||||
onRemoveCriterion={removeCriterion}
|
onEditSearchTerm={() => {
|
||||||
onRemoveAllCriterion={() => clearAllCriteria(true)}
|
setShowSidebar(true);
|
||||||
onEditSearchTerm={() => {
|
setSearchFocus(true);
|
||||||
setShowSidebar(true);
|
}}
|
||||||
setSearchFocus(true);
|
onRemoveSearchTerm={() => setFilter(filter.clearSearchTerm())}
|
||||||
}}
|
/>
|
||||||
onRemoveSearchTerm={() => setFilter(filter.clearSearchTerm())}
|
}
|
||||||
onSelectAll={() => onSelectAll()}
|
selectionSection={
|
||||||
onSelectNone={() => onSelectNone()}
|
<ToolbarSelectionSection
|
||||||
onEdit={onEdit}
|
selected={selectedIds.size}
|
||||||
onDelete={onDelete}
|
onToggleSidebar={() => setShowSidebar(!showSidebar)}
|
||||||
onCreateNew={onCreateNew}
|
onSelectAll={() => onSelectAll()}
|
||||||
onPlay={onPlay}
|
onSelectNone={() => onSelectNone()}
|
||||||
/>
|
/>
|
||||||
</ButtonToolbar>
|
}
|
||||||
|
operationSection={
|
||||||
|
<SceneListOperations
|
||||||
|
items={items.length}
|
||||||
|
hasSelection={hasSelection}
|
||||||
|
operations={otherOperations}
|
||||||
|
onEdit={onEdit}
|
||||||
|
onDelete={onDelete}
|
||||||
|
onPlay={onPlay}
|
||||||
|
onCreateNew={onCreateNew}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<ListResultsHeader
|
<ListResultsHeader
|
||||||
loading={cachedResult.loading}
|
loading={cachedResult.loading}
|
||||||
|
|
|
||||||
|
|
@ -902,46 +902,6 @@ input[type="range"].blue-slider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.scene-list .filtered-list-toolbar {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
row-gap: 1rem;
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-down(xs) {
|
|
||||||
&:nth-child(2) {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-list.hide-sidebar .sidebar-toggle-button {
|
|
||||||
transition-delay: 0.1s;
|
|
||||||
transition-duration: 0;
|
|
||||||
transition-property: opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-wall,
|
.scene-wall,
|
||||||
.marker-wall {
|
.marker-wall {
|
||||||
.wall-item {
|
.wall-item {
|
||||||
|
|
@ -998,214 +958,3 @@ input[type="range"].blue-slider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.scene-list-toolbar,
|
|
||||||
.scene-list-header {
|
|
||||||
align-items: center;
|
|
||||||
background-color: $body-bg;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
flex-shrink: 0;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-list-toolbar {
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 1rem;
|
|
||||||
// offset the main padding
|
|
||||||
margin-top: -0.5rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
position: sticky;
|
|
||||||
top: $navbar-height;
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
@include media-breakpoint-down(xs) {
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-items-info .btn {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide drop down menu items for play and create new
|
|
||||||
// when the buttons are visible
|
|
||||||
@include media-breakpoint-up(sm) {
|
|
||||||
.scene-list-operations {
|
|
||||||
.play-item,
|
|
||||||
.create-new-item {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide play and create new buttons on xs screens
|
|
||||||
// show these in the drop down menu instead
|
|
||||||
@include media-breakpoint-down(xs) {
|
|
||||||
.play-button,
|
|
||||||
.create-new-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-items-info,
|
|
||||||
> div.filter-section {
|
|
||||||
border: 1px solid $secondary;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div.filter-toolbar {
|
|
||||||
border: 1px solid $secondary;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
|
|
||||||
.filter-button {
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-toggle-button {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-container {
|
|
||||||
border-right: 1px solid $secondary;
|
|
||||||
display: block;
|
|
||||||
margin-right: -0.5rem;
|
|
||||||
min-width: calc($sidebar-width - 15px);
|
|
||||||
padding-right: 10px;
|
|
||||||
|
|
||||||
.search-term-input {
|
|
||||||
margin-right: 0;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.clearable-text-field {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-tags {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
margin-bottom: 0;
|
|
||||||
|
|
||||||
// account for filter button, and toggle sidebar buttons with gaps
|
|
||||||
width: calc(100% - 70px - 1rem);
|
|
||||||
|
|
||||||
@include media-breakpoint-down(xs) {
|
|
||||||
overflow-x: auto;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-item {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-up(xl) {
|
|
||||||
.sidebar-pane:not(.hide-sidebar) .scene-list-toolbar .search-container {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@include media-breakpoint-down(md) {
|
|
||||||
.sidebar-pane.hide-sidebar .scene-list-toolbar .search-container {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide Edit Filter button on larger screens
|
|
||||||
@include media-breakpoint-up(lg) {
|
|
||||||
.scene-list .sidebar .edit-filter-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide the filter icon button when sidebar is shown on smaller screens
|
|
||||||
@include media-breakpoint-down(md) {
|
|
||||||
.sidebar-pane:not(.hide-sidebar) .scene-list-toolbar .filter-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// adjust the width of the filter-tags as well
|
|
||||||
.sidebar-pane:not(.hide-sidebar) .scene-list-toolbar .filter-tags {
|
|
||||||
width: calc(100% - 35px - 0.5rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// move the sidebar toggle to the left on xl viewports
|
|
||||||
@include media-breakpoint-up(xl) {
|
|
||||||
.scene-list .scene-list-toolbar .filter-section {
|
|
||||||
.sidebar-toggle-button {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-tags {
|
|
||||||
order: 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide the search term tag item when the search box is visible
|
|
||||||
@include media-breakpoint-up(lg) {
|
|
||||||
.scene-list-toolbar .filter-tags .search-term-filter-tag {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@include media-breakpoint-down(md) {
|
|
||||||
.sidebar-pane:not(.hide-sidebar)
|
|
||||||
.scene-list-toolbar
|
|
||||||
.filter-tags
|
|
||||||
.search-term-filter-tag {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-list-header {
|
|
||||||
flex-wrap: wrap-reverse;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
|
|
||||||
.paginationIndex {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// center the header on smaller screens
|
|
||||||
@include media-breakpoint-down(sm) {
|
|
||||||
& > div,
|
|
||||||
& > div:last-child {
|
|
||||||
flex-basis: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-body .scene-list-toolbar {
|
|
||||||
top: calc($sticky-detail-header-height + $navbar-height);
|
|
||||||
|
|
||||||
@include media-breakpoint-down(xs) {
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#more-criteria-popover {
|
|
||||||
box-shadow: 0 8px 10px 2px rgb(0 0 0 / 30%);
|
|
||||||
max-width: 400px;
|
|
||||||
padding: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,10 @@ import ScreenUtils, { useMediaQuery } from "src/utils/screen";
|
||||||
import { IViewConfig, useInterfaceLocalForage } from "src/hooks/LocalForage";
|
import { IViewConfig, useInterfaceLocalForage } from "src/hooks/LocalForage";
|
||||||
import { View } from "../List/views";
|
import { View } from "../List/views";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { Button, ButtonToolbar, CollapseProps } from "react-bootstrap";
|
import { Button, CollapseProps } from "react-bootstrap";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
import { Icon } from "./Icon";
|
||||||
|
import { faSliders } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
const fixedSidebarMediaQuery = "only screen and (max-width: 1199px)";
|
const fixedSidebarMediaQuery = "only screen and (max-width: 1199px)";
|
||||||
|
|
||||||
|
|
@ -79,62 +81,19 @@ export const SidebarSection: React.FC<
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarIcon: React.FC = () => (
|
export const SidebarToggleButton: React.FC<{
|
||||||
<>
|
onClick: () => void;
|
||||||
{/* From: https://iconduck.com/icons/19707/sidebar
|
}> = ({ onClick }) => {
|
||||||
MIT License
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE. */}
|
|
||||||
<svg
|
|
||||||
className="svg-inline--fa fa-icon"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="3"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
|
||||||
<line x1="9" y1="3" x2="9" y2="21" />
|
|
||||||
</svg>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const SidebarToolbar: React.FC<{
|
|
||||||
onClose?: () => void;
|
|
||||||
}> = ({ onClose, children }) => {
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonToolbar className="sidebar-toolbar">
|
<Button
|
||||||
{onClose ? (
|
className="minimal sidebar-toggle-button ignore-sidebar-outside-click"
|
||||||
<Button
|
variant="secondary"
|
||||||
onClick={onClose}
|
onClick={onClick}
|
||||||
className="sidebar-close-button"
|
title={intl.formatMessage({ id: "actions.sidebar.toggle" })}
|
||||||
variant="secondary"
|
>
|
||||||
title={intl.formatMessage({ id: "actions.sidebar.close" })}
|
<Icon icon={faSliders} />
|
||||||
>
|
</Button>
|
||||||
<SidebarIcon />
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
{children}
|
|
||||||
</ButtonToolbar>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -941,11 +941,11 @@ $sticky-header-height: calc(50px + 3.3rem);
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-pane.hide-sidebar {
|
.sidebar-pane.hide-sidebar {
|
||||||
> :nth-child(2) {
|
> :nth-child(2) {
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue