diff --git a/ui/v2.5/src/components/List/FilteredListToolbar.tsx b/ui/v2.5/src/components/List/FilteredListToolbar.tsx index b3c0883a6..4e101ee4b 100644 --- a/ui/v2.5/src/components/List/FilteredListToolbar.tsx +++ b/ui/v2.5/src/components/List/FilteredListToolbar.tsx @@ -78,6 +78,7 @@ export interface IFilteredListToolbar { onEdit?: () => void; onDelete?: () => void; operations?: IListFilterOperation[]; + operationComponent?: React.ReactNode; zoomable?: boolean; } @@ -90,6 +91,7 @@ export const FilteredListToolbar: React.FC = ({ onEdit, onDelete, operations, + operationComponent, zoomable = false, }) => { const filterOptions = filter.options; @@ -100,6 +102,17 @@ export const FilteredListToolbar: React.FC = ({ const { selectedIds, onSelectAll, onSelectNone } = listSelect; const hasSelection = selectedIds.size > 0; + const renderOperations = operationComponent ?? ( + 0} + onEdit={onEdit} + onDelete={onDelete} + /> + ); + return ( = ({ )} - 0} - onEdit={onEdit} - onDelete={onDelete} - /> + {renderOperations} -> = ({ className, menuPortalTarget, children }) => { +> = ({ className, menuPortalTarget, menuClassName, children }) => { if (!children) return null; const menu = ( - + {children} ); diff --git a/ui/v2.5/src/components/List/ListResultsHeader.tsx b/ui/v2.5/src/components/List/ListResultsHeader.tsx deleted file mode 100644 index 8a1bba05e..000000000 --- a/ui/v2.5/src/components/List/ListResultsHeader.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import { ListFilterModel } from "src/models/list-filter/filter"; -import { Pagination, 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 ( - -
- - onChangeFilter(filter.setSortBy(s ?? undefined)) - } - onChangeSortDirection={() => - onChangeFilter(filter.toggleSortDirection()) - } - onReshuffleRandomSort={() => - onChangeFilter(filter.reshuffleRandomSort()) - } - /> - onChangeFilter(filter.setPageSize(s))} - /> - - onChangeFilter(filter.setDisplayMode(mode)) - } - onSetZoom={(zoom) => onChangeFilter(filter.setZoom(zoom))} - /> -
-
- onChangeFilter(filter.changePage(page))} - /> - -
-
-
- ); -}; diff --git a/ui/v2.5/src/components/List/ListToolbar.tsx b/ui/v2.5/src/components/List/ListToolbar.tsx deleted file mode 100644 index 25ee281c0..000000000 --- a/ui/v2.5/src/components/List/ListToolbar.tsx +++ /dev/null @@ -1,141 +0,0 @@ -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, ButtonGroup, 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"; -import { SavedFilterDropdown } from "./SavedFilterList"; -import { View } from "./views"; - -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; - view?: View; -}> = PatchComponent( - "ToolbarFilterSection", - ({ - filter, - onToggleSidebar, - onSetFilter, - onEditCriterion, - onRemoveCriterion, - onRemoveAllCriterion, - onEditSearchTerm, - onRemoveSearchTerm, - view, - }) => { - const { criteria, searchTerm } = filter; - - return ( - <> -
- -
-
- - - - onEditCriterion()} - count={criteria.length} - /> - - -
- - ); - } -); - -export const ToolbarSelectionSection: React.FC<{ - selected: number; - onToggleSidebar: () => void; - operations?: React.ReactNode; - onSelectAll: () => void; - onSelectNone: () => void; -}> = PatchComponent( - "ToolbarSelectionSection", - ({ selected, onToggleSidebar, operations, onSelectAll, onSelectNone }) => { - const intl = useIntl(); - - return ( -
-
- - - {selected} selected - -
- {operations} -
-
- ); - } -); - -// 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 ( - - {!hasSelection ? filterSection : selectionSection} - {!hasSelection ? ( -
- {operationSection} -
- ) : null} -
- ); -}; diff --git a/ui/v2.5/src/components/List/styles.scss b/ui/v2.5/src/components/List/styles.scss index 60cf0c52f..5f1b4da2a 100644 --- a/ui/v2.5/src/components/List/styles.scss +++ b/ui/v2.5/src/components/List/styles.scss @@ -948,14 +948,6 @@ input[type="range"].zoom-slider { } } -.sidebar-pane .filtered-list-toolbar { - flex-wrap: nowrap; - - & > .btn-group { - align-items: baseline; - } -} - .custom-field-filter { align-items: center; display: flex; @@ -987,14 +979,6 @@ input[type="range"].zoom-slider { } .sidebar { - // make controls slightly larger on mobile - @include media-breakpoint-down(xs) { - .btn, - .form-control { - font-size: 1.25rem; - } - } - .sidebar-search-container { display: flex; margin-bottom: 0.5rem; @@ -1033,18 +1017,22 @@ input[type="range"].zoom-slider { } } -.pagination-footer { +.pagination-footer-container { background-color: transparent; bottom: $navbar-height; - margin: auto; - padding: 0.5rem 1rem 0.75rem; position: sticky; - width: fit-content; z-index: 10; @include media-breakpoint-up(sm) { bottom: 0; } +} + +.pagination-footer { + margin: auto; + padding: 0.5rem 1rem 0.75rem; + + width: fit-content; .pagination.btn-group { box-shadow: 0 8px 10px 2px rgb(0 0 0 / 30%); @@ -1060,6 +1048,18 @@ input[type="range"].zoom-slider { } } +// on very large screens, offset the margins to center the pagination controls +@media (min-width: 1800px) { + .sidebar-pane:not(.hide-sidebar) { + .filter-tags, + .pagination-index-container, + .pagination-footer-container { + margin-left: -$sidebar-width; + margin-right: 0; + } + } +} + // hide sidebar Edit Filter button on larger screens @include media-breakpoint-up(md) { .sidebar .edit-filter-button { @@ -1067,233 +1067,12 @@ input[type="range"].zoom-slider { } } -// 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; - margin-bottom: 0; - 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; - position: sticky; - top: $navbar-height; - z-index: 10; - - @include media-breakpoint-down(xs) { - top: 0; - } - - // 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; - } - } - - .toolbar-selection-section, - div.filter-section { - border: 1px solid $secondary; - border-radius: 0.25rem; - flex-grow: 1; - overflow-x: hidden; - } - - div.toolbar-selection-section { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: center; - - .sidebar-toggle-button { - margin-right: 0.5rem; - } - - > div:first-child, - > div:last-child { - flex: 1; - } - - > div:last-child { - display: flex; - justify-content: flex-end; - } - - .scene-list-operations { - display: flex; - } - - // on smaller viewports move the operation buttons to the right - @include media-breakpoint-down(md) { - div.scene-list-operations { - flex: 1; - justify-content: flex-end; - order: 3; - } - - > div:last-child { - flex: 0; - order: 2; - } - } - } - - // on larger viewports, move the operation buttons to the center - @include media-breakpoint-up(lg) { - div.toolbar-selection-section div.scene-list-operations { - justify-content: center; - - > .btn-group { - gap: 0.5rem; - } - } - - div.toolbar-selection-section .empty-space { - flex: 1; - order: 3; - } - } - - .search-container { - border-right: 1px solid $secondary; - display: flex; - 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; - } - } - } -} - -// hide the search box in the toolbar when sidebar is shown on larger screens -// larger screens don't overlap the sidebar -@include media-breakpoint-up(md) { - .sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar .search-container { +// hide the search input field if the sidebar is open on smaller screens +@media (min-width: 576px) and (max-width: 1400px) { + .sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar .search-term-input { display: none; } } -// hide the search box when sidebar is hidden on smaller screens -@include media-breakpoint-down(md) { - .sidebar-pane.hide-sidebar .filtered-list-toolbar .search-container { - display: none; - } -} - -// hide the filter and saved filters icon buttons when sidebar is shown on smaller screens -@include media-breakpoint-down(sm) { - .sidebar-pane:not(.hide-sidebar) .filtered-list-toolbar { - .filter-button, - .saved-filter-dropdown { - 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 larger viewports -@include media-breakpoint-up(md) { - .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%); @@ -1301,103 +1080,6 @@ input[type="range"].zoom-slider { padding: 0.25rem; } -.list-results-header { - align-items: flex-start; - background-color: $body-bg; - display: flex; - - > div { - align-items: center; - display: flex; - flex: 1; - gap: 0.5rem; - justify-content: flex-start; - - &.pagination-index-container { - justify-content: center; - } - - &:last-child { - flex-shrink: 0; - justify-content: flex-end; - } - } -} - -.list-results-header .pagination-index-container { - display: flex; - flex-direction: column; - gap: 0.5rem; - - .pagination { - // hidden by default. Can be shown via css override if needed - display: none; - margin: 0; - } -} - -.list-results-header { - gap: 0.25rem; - margin-bottom: 0.5rem; - - .paginationIndex { - margin: 0; - } - - // move pagination info to right on medium screens - @include media-breakpoint-down(md) { - & > .empty-space { - flex: 0; - } - - & > div.pagination-index-container { - align-items: flex-end; - order: 3; - } - } - - // center the header on smaller screens - @include media-breakpoint-down(sm) { - & > div, - & > div.pagination-index-container { - flex-basis: 100%; - justify-content: center; - margin-left: auto; - margin-right: auto; - } - - & > div.pagination-index-container { - align-items: center; - } - } -} - -// sidebar visible styling -.sidebar-pane:not(.hide-sidebar) .list-results-header { - // move pagination info to right on medium screens when sidebar - @include media-breakpoint-down(lg) { - & > .empty-space { - flex: 0; - } - - & > div.pagination-index-container { - justify-content: flex-end; - order: 3; - } - } - - // center the header on smaller screens when sidebar is visible - @include media-breakpoint-down(md) { - & > div, - & > div.pagination-index-container { - flex-basis: 100%; - justify-content: center; - margin-left: auto; - margin-right: auto; - } - } -} - // Duration slider styles .duration-slider, .age-slider-container { @@ -1442,7 +1124,23 @@ input[type="range"].zoom-slider { justify-content: flex-start; } -.item-list-container > .filtered-list-toolbar.has-selection { +// modify margins for toolbar within sidebar pane to accommodate toggle button +.sidebar-pane .filtered-list-toolbar { + margin-left: 40px; + margin-right: 40px; +} + +// on very large screens, offset the margins to center the toolbar +@media (min-width: 1800px) { + .sidebar-pane:not(.hide-sidebar) { + .filtered-list-toolbar { + margin-left: -$sidebar-width; + margin-right: 0; + } + } +} + +.item-list-container .filtered-list-toolbar.has-selection { border-radius: 0.5rem; margin-left: auto; margin-right: auto; diff --git a/ui/v2.5/src/components/Scenes/SceneList.tsx b/ui/v2.5/src/components/Scenes/SceneList.tsx index dc3ea76c6..fa390d187 100644 --- a/ui/v2.5/src/components/Scenes/SceneList.tsx +++ b/ui/v2.5/src/components/Scenes/SceneList.tsx @@ -67,17 +67,13 @@ import { useFilteredSidebarKeybinds, } from "../List/Filters/FilterSidebar"; import { PatchContainerComponent } from "src/patch"; -import { Pagination } from "../List/Pagination"; +import { Pagination, PaginationIndex } from "../List/Pagination"; import { Button, ButtonGroup } from "react-bootstrap"; import { Icon } from "../Shared/Icon"; import useFocus from "src/utils/focus"; -import { - FilteredListToolbar2, - ToolbarFilterSection, - ToolbarSelectionSection, -} from "../List/ListToolbar"; -import { ListResultsHeader } from "../List/ListResultsHeader"; import { useZoomKeybinds } from "../List/ZoomSlider"; +import { FilteredListToolbar } from "../List/FilteredListToolbar"; +import { FilterTags } from "../List/FilterTags"; function renderMetadataByline(result: GQL.FindScenesQueryResult) { const duration = result?.data?.findScenes?.duration; @@ -439,6 +435,7 @@ const SceneListOperations: React.FC<{ {operations.map((o) => { @@ -474,7 +471,6 @@ export const FilteredSceneList = (props: IFilteredScenes) => { const history = useHistory(); const searchFocus = useFocus(); - const [, setSearchFocus] = searchFocus; const { filterHook, defaultSort, view, alterQuery, fromGroupId } = props; @@ -752,50 +748,44 @@ export const FilteredSceneList = (props: IFilteredScenes) => { focus={searchFocus} /> - - setShowSidebar(!showSidebar)} - onEditCriterion={(c) => - showEditFilter(c?.criterionOption.type) - } - onRemoveCriterion={removeCriterion} - onRemoveAllCriterion={() => clearAllCriteria(true)} - onEditSearchTerm={() => { - setShowSidebar(true); - setSearchFocus(true); - }} - onRemoveSearchTerm={() => - setFilter(filter.clearSearchTerm()) - } - view={view} - /> - } - selectionSection={ - setShowSidebar(!showSidebar)} - onSelectAll={() => onSelectAll()} - onSelectNone={() => onSelectNone()} - operations={operations} - /> - } - operationSection={operations} + setShowSidebar(!showSidebar)} + > + - setFilter(newFilter)} + showEditFilter(c.criterionOption.type)} + onRemoveCriterion={removeCriterion} + onRemoveAll={clearAllCriteria} /> +
+ setFilter(filter.changePage(page))} + /> + +
+ { {totalCount > filter.itemsPerPage && ( -
- +
+
+ +
)} diff --git a/ui/v2.5/src/components/Scenes/styles.scss b/ui/v2.5/src/components/Scenes/styles.scss index 3b00130b1..92c13d648 100644 --- a/ui/v2.5/src/components/Scenes/styles.scss +++ b/ui/v2.5/src/components/Scenes/styles.scss @@ -964,3 +964,25 @@ input[type="range"].blue-slider { // TODO - this will need to be rolled out to other tables max-height: calc(100dvh - 210px); } + +.scene-list .filtered-list-toolbar { + // 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; + } + } +} + +// hide drop down menu items for play and create new +// when the buttons are visible +@include media-breakpoint-up(sm) { + .scene-list-operations-dropdown { + .dropdown-item.play-item, + .dropdown-item.create-new-item { + display: none; + } + } +} diff --git a/ui/v2.5/src/components/Shared/Sidebar.tsx b/ui/v2.5/src/components/Shared/Sidebar.tsx index bf19f5842..10dbaaaba 100644 --- a/ui/v2.5/src/components/Shared/Sidebar.tsx +++ b/ui/v2.5/src/components/Shared/Sidebar.tsx @@ -60,8 +60,34 @@ export const SidebarPane: React.FC< ); }; -export const SidebarPaneContent: React.FC = ({ children }) => { - return
{children}
; +export const SidebarToggleButton: React.FC<{ + onClick: () => void; +}> = ({ onClick }) => { + const intl = useIntl(); + return ( +
+ +
+ ); +}; + +export const SidebarPaneContent: React.FC<{ onSidebarToggle: () => void }> = ({ + onSidebarToggle, + children, +}) => { + return ( +
+ + {children} +
+ ); }; interface IContext { @@ -125,22 +151,6 @@ export const SidebarSection: React.FC< ); }; -export const SidebarToggleButton: React.FC<{ - onClick: () => void; -}> = ({ onClick }) => { - const intl = useIntl(); - return ( - - ); -}; - // show sidebar by default if not on mobile export function defaultShowSidebar() { return !ScreenUtils.matchesMediaQuery(fixedSidebarMediaQuery); diff --git a/ui/v2.5/src/components/Shared/styles.scss b/ui/v2.5/src/components/Shared/styles.scss index 9985138e0..f7ad76e9d 100644 --- a/ui/v2.5/src/components/Shared/styles.scss +++ b/ui/v2.5/src/components/Shared/styles.scss @@ -815,21 +815,6 @@ button.btn.favorite-button { } } @include media-breakpoint-down(xs) { - .sidebar { - width: 100%; - } - - &.hide-sidebar .sidebar { - margin-left: -100%; - } - - &.hide-sidebar > :nth-child(2) { - width: 100%; - } - } - @include media-breakpoint-down(xs) { - display: block; - .sidebar { margin-bottom: $navbar-height; margin-top: 0; @@ -837,6 +822,36 @@ button.btn.favorite-button { } } +.sidebar-toggle-button-container { + height: 100%; + position: absolute; + + .sidebar-toggle-button { + border-bottom: 1px solid $secondary; + border-bottom-left-radius: 0; + border-bottom-right-radius: 10px; + border-right: 1px solid $secondary; + border-top: 1px solid $secondary; + border-top-left-radius: 0; + border-top-right-radius: 10px; + margin-left: -15px; + opacity: 0.5; + position: sticky; + top: calc($navbar-height + 0.5rem); + z-index: 10; + + @include media-breakpoint-down(sm) { + top: 0.5rem; + } + } +} + +.sidebar-pane:not(.hide-sidebar) .sidebar-toggle-button-container { + .sidebar-toggle-button { + margin-left: -0.5rem; + } +} + .sidebar-toolbar { // TODO - use different colours for sidebar and toolbar background-color: $body-bg; @@ -884,6 +899,14 @@ button.btn.favorite-button { $sticky-header-height: calc(50px + 3.3rem); // special case for sidebar in details view +.detail-body .sidebar-toggle-button-container .sidebar-toggle-button { + top: calc($sticky-header-height + 0.5rem); + + @include media-breakpoint-down(sm) { + top: 0.5rem; + } +} + .detail-body { .sidebar { // required for sticky to work @@ -921,19 +944,11 @@ $sticky-header-height: calc(50px + 3.3rem); } @include media-breakpoint-down(xs) { .sidebar { - flex: 100% 0 0; - height: calc(100vh - 4rem); - max-height: calc(100vh - 4rem); + // flex: 100% 0 0; + height: calc(100vh - $navbar-height); + max-height: calc(100vh - $navbar-height); top: 0; } - - .sidebar-pane:not(.hide-sidebar) .sidebar { - margin-right: -100%; - } - - .sidebar-pane.hide-sidebar .sidebar { - display: none; - } } @include media-breakpoint-up(md) { .sidebar-pane:not(.hide-sidebar) {