mirror of
https://github.com/stashapp/stash.git
synced 2026-02-28 02:02:57 +01:00
Add sidebar to scene markers list (#6603)
* Add tag markers filter * Add marker count and markers filter to performer filter * Add sidebar to marker list
This commit is contained in:
parent
c9f0dba62f
commit
5734ee43ff
9 changed files with 504 additions and 123 deletions
|
|
@ -177,6 +177,8 @@ input PerformerFilterType {
|
|||
tag_count: IntCriterionInput
|
||||
"Filter by scene count"
|
||||
scene_count: IntCriterionInput
|
||||
"Filter by marker count (via scene)"
|
||||
marker_count: IntCriterionInput
|
||||
"Filter by image count"
|
||||
image_count: IntCriterionInput
|
||||
"Filter by gallery count"
|
||||
|
|
@ -220,6 +222,8 @@ input PerformerFilterType {
|
|||
galleries_filter: GalleryFilterType
|
||||
"Filter by related tags that meet this criteria"
|
||||
tags_filter: TagFilterType
|
||||
"Filter by related scene markers (via scene) that meet this criteria"
|
||||
markers_filter: SceneMarkerFilterType
|
||||
"Filter by creation time"
|
||||
created_at: TimestampCriterionInput
|
||||
"Filter by last update time"
|
||||
|
|
@ -684,6 +688,8 @@ input TagFilterType {
|
|||
performers_filter: PerformerFilterType
|
||||
"Filter by related studios that meet this criteria"
|
||||
studios_filter: StudioFilterType
|
||||
"Filter by related scene markers that meet this criteria"
|
||||
markers_filter: SceneMarkerFilterType
|
||||
|
||||
"Filter by creation time"
|
||||
created_at: TimestampCriterionInput
|
||||
|
|
|
|||
|
|
@ -158,6 +158,8 @@ type PerformerFilterType struct {
|
|||
TagCount *IntCriterionInput `json:"tag_count"`
|
||||
// Filter by scene count
|
||||
SceneCount *IntCriterionInput `json:"scene_count"`
|
||||
// Filter by scene marker count (via scene)
|
||||
MarkerCount *IntCriterionInput `json:"marker_count"`
|
||||
// Filter by image count
|
||||
ImageCount *IntCriterionInput `json:"image_count"`
|
||||
// Filter by gallery count
|
||||
|
|
@ -202,6 +204,8 @@ type PerformerFilterType struct {
|
|||
GalleriesFilter *GalleryFilterType `json:"galleries_filter"`
|
||||
// Filter by related tags that meet this criteria
|
||||
TagsFilter *TagFilterType `json:"tags_filter"`
|
||||
// Filter by related scene markers (via scene) that meet this criteria
|
||||
MarkersFilter *SceneMarkerFilterType `json:"markers_filter"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ type TagFilterType struct {
|
|||
PerformersFilter *PerformerFilterType `json:"performers_filter"`
|
||||
// Filter by related studios that meet this criteria
|
||||
StudiosFilter *StudioFilterType `json:"studios_filter"`
|
||||
// Filter by related scene markers that meet this criteria
|
||||
MarkersFilter *SceneMarkerFilterType `json:"markers_filter"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
|
|
|
|||
|
|
@ -1089,11 +1089,16 @@ func (h *stashIDsCriterionHandler) handle(ctx context.Context, f *filterBuilder)
|
|||
}
|
||||
|
||||
type relatedFilterHandler struct {
|
||||
relatedIDCol string
|
||||
relatedRepo repository
|
||||
// column on the primary table that relates to the related table (eg scene_id)
|
||||
relatedIDCol string
|
||||
// repository for the related table (eg sceneRepository)
|
||||
relatedRepo repository
|
||||
// handler for the filter on the related table
|
||||
relatedHandler criterionHandler
|
||||
joinFn func(f *filterBuilder)
|
||||
directJoin bool
|
||||
// optional function to perform the necessary join(s) to the related table
|
||||
joinFn func(f *filterBuilder)
|
||||
// if true, related filter handler will be run using the existing filterBuilder instead of a subquery.
|
||||
directJoin bool
|
||||
}
|
||||
|
||||
func (h *relatedFilterHandler) handle(ctx context.Context, f *filterBuilder) {
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ func (qb *performerFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
qb.tagCountCriterionHandler(filter.TagCount),
|
||||
qb.sceneCountCriterionHandler(filter.SceneCount),
|
||||
qb.markerCountCriterionHandler(filter.MarkerCount),
|
||||
qb.imageCountCriterionHandler(filter.ImageCount),
|
||||
qb.galleryCountCriterionHandler(filter.GalleryCount),
|
||||
qb.playCounterCriterionHandler(filter.PlayCount),
|
||||
|
|
@ -204,6 +205,16 @@ func (qb *performerFilterHandler) criterionHandler() criterionHandler {
|
|||
×tampCriterionHandler{filter.CreatedAt, tableName + ".created_at", nil},
|
||||
×tampCriterionHandler{filter.UpdatedAt, tableName + ".updated_at", nil},
|
||||
|
||||
&relatedFilterHandler{
|
||||
relatedIDCol: "scene_markers.id",
|
||||
relatedRepo: sceneMarkerRepository.repository,
|
||||
relatedHandler: &sceneMarkerFilterHandler{filter.MarkersFilter},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
performerRepository.scenes.innerJoin(f, "", "performers.id")
|
||||
f.addInnerJoin(sceneMarkerTable, "", "scene_markers.scene_id = performers_scenes.scene_id")
|
||||
},
|
||||
},
|
||||
|
||||
&relatedFilterHandler{
|
||||
relatedIDCol: "performers_scenes.scene_id",
|
||||
relatedRepo: sceneRepository.repository,
|
||||
|
|
@ -387,6 +398,22 @@ func (qb *performerFilterHandler) sceneCountCriterionHandler(count *models.IntCr
|
|||
return h.handler(count)
|
||||
}
|
||||
|
||||
func (qb *performerFilterHandler) markerCountCriterionHandler(count *models.IntCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if count != nil {
|
||||
performerRepository.scenes.innerJoin(f, "", "performers.id")
|
||||
|
||||
const query = `(SELECT COUNT(*) FROM scene_markers
|
||||
INNER JOIN scenes ON scene_markers.scene_id = scenes.id
|
||||
INNER JOIN performers_scenes ON performers_scenes.scene_id = scenes.id
|
||||
WHERE performers_scenes.performer_id = performers.id)`
|
||||
|
||||
clause, args := getIntCriterionWhereClause(query, *count)
|
||||
f.addWhere(clause, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (qb *performerFilterHandler) imageCountCriterionHandler(count *models.IntCriterionInput) criterionHandlerFunc {
|
||||
h := countCriterionHandlerBuilder{
|
||||
primaryTable: performerTable,
|
||||
|
|
|
|||
|
|
@ -161,6 +161,20 @@ func (qb *tagFilterHandler) criterionHandler() criterionHandler {
|
|||
tagRepository.studios.innerJoin(f, "", "tags.id")
|
||||
},
|
||||
},
|
||||
|
||||
&relatedFilterHandler{
|
||||
relatedIDCol: "markers_tags.marker_id",
|
||||
relatedRepo: sceneMarkerRepository.repository,
|
||||
relatedHandler: &sceneMarkerFilterHandler{tagFilter.MarkersFilter},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
f.addWith(`markers_tags AS (
|
||||
SELECT mt.scene_marker_id AS marker_id, mt.tag_id AS tag_id FROM scene_markers_tags mt
|
||||
UNION
|
||||
SELECT m.id, m.primary_tag_id FROM scene_markers m
|
||||
)`)
|
||||
f.addInnerJoin("markers_tags", "", "markers_tags.tag_id = tags.id")
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
IntCriterionInput,
|
||||
PerformerFilterType,
|
||||
SceneFilterType,
|
||||
SceneMarkerFilterType,
|
||||
StudioFilterType,
|
||||
} from "src/core/generated-graphql";
|
||||
import { useIntl } from "react-intl";
|
||||
|
|
@ -527,6 +528,8 @@ interface IFilterType {
|
|||
group_count?: InputMaybe<IntCriterionInput>;
|
||||
studios_filter?: InputMaybe<StudioFilterType>;
|
||||
studio_count?: InputMaybe<IntCriterionInput>;
|
||||
marker_count?: InputMaybe<IntCriterionInput>;
|
||||
markers_filter?: InputMaybe<SceneMarkerFilterType>;
|
||||
}
|
||||
|
||||
export function setObjectFilter(
|
||||
|
|
@ -549,6 +552,7 @@ export function setObjectFilter(
|
|||
modifier: CriterionModifier.GreaterThan,
|
||||
value: 0,
|
||||
};
|
||||
break;
|
||||
}
|
||||
out.scenes_filter = relatedFilterOutput as SceneFilterType;
|
||||
break;
|
||||
|
|
@ -559,6 +563,7 @@ export function setObjectFilter(
|
|||
modifier: CriterionModifier.GreaterThan,
|
||||
value: 0,
|
||||
};
|
||||
break;
|
||||
}
|
||||
out.performers_filter = relatedFilterOutput as PerformerFilterType;
|
||||
break;
|
||||
|
|
@ -569,6 +574,7 @@ export function setObjectFilter(
|
|||
modifier: CriterionModifier.GreaterThan,
|
||||
value: 0,
|
||||
};
|
||||
break;
|
||||
}
|
||||
out.galleries_filter = relatedFilterOutput as GalleryFilterType;
|
||||
break;
|
||||
|
|
@ -579,6 +585,7 @@ export function setObjectFilter(
|
|||
modifier: CriterionModifier.GreaterThan,
|
||||
value: 0,
|
||||
};
|
||||
break;
|
||||
}
|
||||
out.groups_filter = relatedFilterOutput as GroupFilterType;
|
||||
break;
|
||||
|
|
@ -589,9 +596,21 @@ export function setObjectFilter(
|
|||
modifier: CriterionModifier.GreaterThan,
|
||||
value: 0,
|
||||
};
|
||||
break;
|
||||
}
|
||||
out.studios_filter = relatedFilterOutput as StudioFilterType;
|
||||
break;
|
||||
case FilterMode.SceneMarkers:
|
||||
// if empty, only get objects with scene markers
|
||||
if (empty) {
|
||||
out.marker_count = {
|
||||
modifier: CriterionModifier.GreaterThan,
|
||||
value: 0,
|
||||
};
|
||||
break;
|
||||
}
|
||||
out.markers_filter = relatedFilterOutput as SceneMarkerFilterType;
|
||||
break;
|
||||
default:
|
||||
throw new Error("Invalid filter mode");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import React from "react";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useIntl } from "react-intl";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import Mousetrap from "mousetrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import {
|
||||
|
|
@ -9,7 +9,7 @@ import {
|
|||
useFindSceneMarkers,
|
||||
} from "src/core/StashService";
|
||||
import NavUtils from "src/utils/navigation";
|
||||
import { ItemList, ItemListContext } from "../List/ItemList";
|
||||
import { useFilteredItemList } from "../List/ItemList";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
import { DisplayMode } from "src/models/list-filter/types";
|
||||
import { MarkerWallPanel } from "./SceneMarkerWallPanel";
|
||||
|
|
@ -17,17 +17,179 @@ import { View } from "../List/views";
|
|||
import { SceneMarkerCardGrid } from "./SceneMarkerCardGrid";
|
||||
import { DeleteSceneMarkersDialog } from "./DeleteSceneMarkersDialog";
|
||||
import { EditSceneMarkersDialog } from "./EditSceneMarkersDialog";
|
||||
import { PatchComponent } from "src/patch";
|
||||
import { IItemListOperation } from "../List/FilteredListToolbar";
|
||||
import { PatchComponent, PatchContainerComponent } from "src/patch";
|
||||
import {
|
||||
FilteredListToolbar,
|
||||
IItemListOperation,
|
||||
} from "../List/FilteredListToolbar";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarPane,
|
||||
SidebarPaneContent,
|
||||
SidebarStateContext,
|
||||
useSidebarState,
|
||||
} from "../Shared/Sidebar";
|
||||
import { useCloseEditDelete, useFilterOperations } from "../List/util";
|
||||
import {
|
||||
FilteredSidebarHeader,
|
||||
useFilteredSidebarKeybinds,
|
||||
} from "../List/Filters/FilterSidebar";
|
||||
import { useZoomKeybinds } from "../List/ZoomSlider";
|
||||
import {
|
||||
IListFilterOperation,
|
||||
ListOperations,
|
||||
} from "../List/ListOperationButtons";
|
||||
import cx from "classnames";
|
||||
import { FilterTags } from "../List/FilterTags";
|
||||
import { Pagination, PaginationIndex } from "../List/Pagination";
|
||||
import { LoadedContent } from "../List/PagedList";
|
||||
import useFocus from "src/utils/focus";
|
||||
import { SidebarPerformersFilter } from "../List/Filters/PerformersFilter";
|
||||
import { SidebarTagsFilter } from "../List/Filters/TagsFilter";
|
||||
import { Button } from "react-bootstrap";
|
||||
|
||||
function getItems(result: GQL.FindSceneMarkersQueryResult) {
|
||||
return result?.data?.findSceneMarkers?.scene_markers ?? [];
|
||||
const SceneMarkerList: React.FC<{
|
||||
markers: GQL.SceneMarkerDataFragment[];
|
||||
filter: ListFilterModel;
|
||||
selectedIds: Set<string>;
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
||||
}> = PatchComponent(
|
||||
"SceneList",
|
||||
({ markers, filter, selectedIds, onSelectChange }) => {
|
||||
if (markers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return (
|
||||
<MarkerWallPanel
|
||||
markers={markers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<SceneMarkerCardGrid
|
||||
markers={markers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
function usePlayRandom(filter: ListFilterModel, count: number) {
|
||||
const history = useHistory();
|
||||
|
||||
const playRandom = useCallback(async () => {
|
||||
// query for a random scene
|
||||
if (count === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pages = Math.ceil(count / filter.itemsPerPage);
|
||||
const page = Math.floor(Math.random() * pages) + 1;
|
||||
|
||||
const indexMax = Math.min(filter.itemsPerPage, count);
|
||||
const index = Math.floor(Math.random() * indexMax);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.currentPage = page;
|
||||
filterCopy.sortBy = "random";
|
||||
const queryResults = await queryFindSceneMarkers(filterCopy);
|
||||
const marker = queryResults.data.findSceneMarkers.scene_markers[index];
|
||||
if (marker) {
|
||||
// navigate to the scene player page
|
||||
const url = NavUtils.makeSceneMarkerUrl(marker);
|
||||
history.push(url);
|
||||
}
|
||||
}, [filter, count, history]);
|
||||
|
||||
return playRandom;
|
||||
}
|
||||
|
||||
function getCount(result: GQL.FindSceneMarkersQueryResult) {
|
||||
return result?.data?.findSceneMarkers?.count ?? 0;
|
||||
function useAddKeybinds(filter: ListFilterModel, count: number) {
|
||||
const playRandom = usePlayRandom(filter, count);
|
||||
|
||||
useEffect(() => {
|
||||
Mousetrap.bind("p r", () => {
|
||||
playRandom();
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}, [playRandom]);
|
||||
}
|
||||
|
||||
const ScenesFilterSidebarSections = PatchContainerComponent(
|
||||
"FilteredSceneMarkerList.SidebarSections"
|
||||
);
|
||||
|
||||
const SidebarContent: React.FC<{
|
||||
filter: ListFilterModel;
|
||||
setFilter: (filter: ListFilterModel) => void;
|
||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||
view?: View;
|
||||
sidebarOpen: boolean;
|
||||
onClose?: () => void;
|
||||
showEditFilter: (editingCriterion?: string) => void;
|
||||
count?: number;
|
||||
focus?: ReturnType<typeof useFocus>;
|
||||
}> = ({
|
||||
filter,
|
||||
setFilter,
|
||||
filterHook,
|
||||
view,
|
||||
showEditFilter,
|
||||
sidebarOpen,
|
||||
onClose,
|
||||
count,
|
||||
focus,
|
||||
}) => {
|
||||
const showResultsId =
|
||||
count !== undefined ? "actions.show_count_results" : "actions.show_results";
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilteredSidebarHeader
|
||||
sidebarOpen={sidebarOpen}
|
||||
showEditFilter={showEditFilter}
|
||||
filter={filter}
|
||||
setFilter={setFilter}
|
||||
view={view}
|
||||
focus={focus}
|
||||
/>
|
||||
|
||||
<ScenesFilterSidebarSections>
|
||||
<SidebarPerformersFilter
|
||||
filter={filter}
|
||||
setFilter={setFilter}
|
||||
filterHook={filterHook}
|
||||
/>
|
||||
<SidebarTagsFilter
|
||||
filter={filter}
|
||||
setFilter={setFilter}
|
||||
filterHook={filterHook}
|
||||
/>
|
||||
</ScenesFilterSidebarSections>
|
||||
|
||||
<div className="sidebar-footer">
|
||||
<Button className="sidebar-close-button" onClick={onClose}>
|
||||
<FormattedMessage id={showResultsId} values={{ count }} />
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface ISceneMarkerList {
|
||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||
view?: View;
|
||||
|
|
@ -36,132 +198,274 @@ interface ISceneMarkerList {
|
|||
extraOperations?: IItemListOperation<GQL.FindSceneMarkersQueryResult>[];
|
||||
}
|
||||
|
||||
export const SceneMarkerList: React.FC<ISceneMarkerList> = PatchComponent(
|
||||
"SceneMarkerList",
|
||||
({ filterHook, view, alterQuery, extraOperations = [] }) => {
|
||||
export const FilteredSceneMarkerList = PatchComponent(
|
||||
"FilteredSceneMarkerList",
|
||||
(props: ISceneMarkerList) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
|
||||
const filterMode = GQL.FilterMode.SceneMarkers;
|
||||
const searchFocus = useFocus();
|
||||
|
||||
const otherOperations = [
|
||||
...extraOperations,
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.play_random" }),
|
||||
onClick: playRandom,
|
||||
},
|
||||
];
|
||||
const {
|
||||
filterHook,
|
||||
defaultSort,
|
||||
view,
|
||||
alterQuery,
|
||||
extraOperations = [],
|
||||
} = props;
|
||||
|
||||
function addKeybinds(
|
||||
result: GQL.FindSceneMarkersQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
playRandom(result, filter);
|
||||
// States
|
||||
const {
|
||||
showSidebar,
|
||||
setShowSidebar,
|
||||
loading: sidebarStateLoading,
|
||||
sectionOpen,
|
||||
setSectionOpen,
|
||||
} = useSidebarState(view);
|
||||
|
||||
const { filterState, queryResult, modalState, listSelect, showEditFilter } =
|
||||
useFilteredItemList({
|
||||
filterStateProps: {
|
||||
filterMode: GQL.FilterMode.SceneMarkers,
|
||||
defaultSort,
|
||||
view,
|
||||
useURL: alterQuery,
|
||||
},
|
||||
queryResultProps: {
|
||||
useResult: useFindSceneMarkers,
|
||||
getCount: (r) => r.data?.findSceneMarkers.count ?? 0,
|
||||
getItems: (r) => r.data?.findSceneMarkers.scene_markers ?? [],
|
||||
filterHook,
|
||||
},
|
||||
});
|
||||
|
||||
const { filter, setFilter } = filterState;
|
||||
|
||||
const { effectiveFilter, result, cachedResult, items, totalCount } =
|
||||
queryResult;
|
||||
|
||||
const {
|
||||
selectedIds,
|
||||
selectedItems,
|
||||
onSelectChange,
|
||||
onSelectAll,
|
||||
onSelectNone,
|
||||
onInvertSelection,
|
||||
hasSelection,
|
||||
} = listSelect;
|
||||
|
||||
const { modal, showModal, closeModal } = modalState;
|
||||
|
||||
// Utility hooks
|
||||
const { setPage, removeCriterion, clearAllCriteria } = useFilterOperations({
|
||||
filter,
|
||||
setFilter,
|
||||
});
|
||||
|
||||
useAddKeybinds(filter, totalCount);
|
||||
useFilteredSidebarKeybinds({
|
||||
showSidebar,
|
||||
setShowSidebar,
|
||||
});
|
||||
|
||||
const onCloseEditDelete = useCloseEditDelete({
|
||||
closeModal,
|
||||
onSelectNone,
|
||||
result,
|
||||
});
|
||||
|
||||
const onEdit = useCallback(() => {
|
||||
showModal(
|
||||
<EditSceneMarkersDialog
|
||||
selected={selectedItems}
|
||||
onClose={onCloseEditDelete}
|
||||
/>
|
||||
);
|
||||
}, [showModal, selectedItems, onCloseEditDelete]);
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
showModal(
|
||||
<DeleteSceneMarkersDialog
|
||||
selected={selectedItems}
|
||||
onClose={onCloseEditDelete}
|
||||
/>
|
||||
);
|
||||
}, [showModal, selectedItems, onCloseEditDelete]);
|
||||
|
||||
useEffect(() => {
|
||||
Mousetrap.bind("e", () => {
|
||||
if (hasSelection) {
|
||||
onEdit?.();
|
||||
}
|
||||
});
|
||||
|
||||
Mousetrap.bind("d d", () => {
|
||||
if (hasSelection) {
|
||||
onDelete?.();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
Mousetrap.unbind("e");
|
||||
Mousetrap.unbind("d d");
|
||||
};
|
||||
}
|
||||
}, [onSelectAll, onSelectNone, hasSelection, onEdit, onDelete]);
|
||||
|
||||
async function playRandom(
|
||||
result: GQL.FindSceneMarkersQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random scene
|
||||
if (result.data?.findSceneMarkers) {
|
||||
const { count } = result.data.findSceneMarkers;
|
||||
useZoomKeybinds({
|
||||
zoomIndex: filter.zoomIndex,
|
||||
onChangeZoom: (zoom) => setFilter(filter.setZoom(zoom)),
|
||||
});
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindSceneMarkers(filterCopy);
|
||||
if (singleResult.data.findSceneMarkers.scene_markers.length === 1) {
|
||||
// navigate to the scene player page
|
||||
const url = NavUtils.makeSceneMarkerUrl(
|
||||
singleResult.data.findSceneMarkers.scene_markers[0]
|
||||
);
|
||||
history.push(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
const playRandom = usePlayRandom(effectiveFilter, totalCount);
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindSceneMarkersQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
if (!result.data?.findSceneMarkers) return;
|
||||
const convertedExtraOperations: IListFilterOperation[] =
|
||||
extraOperations.map((o) => ({
|
||||
...o,
|
||||
isDisplayed: o.isDisplayed
|
||||
? () => o.isDisplayed!(result, filter, selectedIds)
|
||||
: undefined,
|
||||
onClick: () => {
|
||||
o.onClick(result, filter, selectedIds);
|
||||
},
|
||||
}));
|
||||
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return (
|
||||
<MarkerWallPanel
|
||||
markers={result.data.findSceneMarkers.scene_markers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const otherOperations = [
|
||||
...convertedExtraOperations,
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.select_all" }),
|
||||
onClick: () => onSelectAll(),
|
||||
isDisplayed: () => totalCount > 0,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.select_none" }),
|
||||
onClick: () => onSelectNone(),
|
||||
isDisplayed: () => hasSelection,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.invert_selection" }),
|
||||
onClick: () => onInvertSelection(),
|
||||
isDisplayed: () => totalCount > 0,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.play_random" }),
|
||||
onClick: playRandom,
|
||||
isDisplayed: () => totalCount > 1,
|
||||
},
|
||||
// {
|
||||
// text: `${intl.formatMessage({ id: "actions.generate" })}…`,
|
||||
// onClick: () =>
|
||||
// showModal(
|
||||
// <GenerateDialog
|
||||
// type="scene"
|
||||
// selectedIds={Array.from(selectedIds.values())}
|
||||
// onClose={() => closeModal()}
|
||||
// />
|
||||
// ),
|
||||
// isDisplayed: () => hasSelection,
|
||||
// },
|
||||
];
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<SceneMarkerCardGrid
|
||||
markers={result.data.findSceneMarkers.scene_markers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
// render
|
||||
if (sidebarStateLoading) return null;
|
||||
|
||||
function renderEditDialog(
|
||||
selectedMarkers: GQL.SceneMarkerDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<EditSceneMarkersDialog selected={selectedMarkers} onClose={onClose} />
|
||||
);
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedSceneMarkers: GQL.SceneMarkerDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteSceneMarkersDialog
|
||||
selected={selectedSceneMarkers}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const operations = (
|
||||
<ListOperations
|
||||
items={items.length}
|
||||
hasSelection={hasSelection}
|
||||
operations={otherOperations}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
operationsMenuClassName="scene-marker-list-operations-dropdown"
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindSceneMarkers}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
selectable
|
||||
<div
|
||||
className={cx("item-list-container scene-list", {
|
||||
"hide-sidebar": !showSidebar,
|
||||
})}
|
||||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
{modal}
|
||||
|
||||
<SidebarStateContext.Provider value={{ sectionOpen, setSectionOpen }}>
|
||||
<SidebarPane hideSidebar={!showSidebar}>
|
||||
<Sidebar hide={!showSidebar} onHide={() => setShowSidebar(false)}>
|
||||
<SidebarContent
|
||||
filter={filter}
|
||||
setFilter={setFilter}
|
||||
filterHook={filterHook}
|
||||
showEditFilter={showEditFilter}
|
||||
view={view}
|
||||
sidebarOpen={showSidebar}
|
||||
onClose={() => setShowSidebar(false)}
|
||||
count={cachedResult.loading ? undefined : totalCount}
|
||||
focus={searchFocus}
|
||||
/>
|
||||
</Sidebar>
|
||||
<SidebarPaneContent
|
||||
onSidebarToggle={() => setShowSidebar(!showSidebar)}
|
||||
>
|
||||
<FilteredListToolbar
|
||||
filter={filter}
|
||||
listSelect={listSelect}
|
||||
setFilter={setFilter}
|
||||
showEditFilter={showEditFilter}
|
||||
onDelete={onDelete}
|
||||
onEdit={onEdit}
|
||||
operationComponent={operations}
|
||||
view={view}
|
||||
zoomable
|
||||
/>
|
||||
|
||||
<FilterTags
|
||||
criteria={filter.criteria}
|
||||
onEditCriterion={(c) => showEditFilter(c.criterionOption.type)}
|
||||
onRemoveCriterion={removeCriterion}
|
||||
onRemoveAll={clearAllCriteria}
|
||||
/>
|
||||
|
||||
<div className="pagination-index-container">
|
||||
<Pagination
|
||||
currentPage={filter.currentPage}
|
||||
itemsPerPage={filter.itemsPerPage}
|
||||
totalItems={totalCount}
|
||||
onChangePage={(page) => setFilter(filter.changePage(page))}
|
||||
/>
|
||||
<PaginationIndex
|
||||
loading={cachedResult.loading}
|
||||
itemsPerPage={filter.itemsPerPage}
|
||||
currentPage={filter.currentPage}
|
||||
totalItems={totalCount}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<LoadedContent loading={result.loading} error={result.error}>
|
||||
<SceneMarkerList
|
||||
filter={effectiveFilter}
|
||||
markers={items}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
</LoadedContent>
|
||||
|
||||
{totalCount > filter.itemsPerPage && (
|
||||
<div className="pagination-footer-container">
|
||||
<div className="pagination-footer">
|
||||
<Pagination
|
||||
itemsPerPage={filter.itemsPerPage}
|
||||
currentPage={filter.currentPage}
|
||||
totalItems={totalCount}
|
||||
onChangePage={setPage}
|
||||
pagePopupPlacement="top"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</SidebarPaneContent>
|
||||
</SidebarPane>
|
||||
</SidebarStateContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default SceneMarkerList;
|
||||
export default FilteredSceneMarkerList;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
TagsCriterion,
|
||||
TagsCriterionOption,
|
||||
} from "src/models/list-filter/criteria/tags";
|
||||
import { SceneMarkerList } from "src/components/Scenes/SceneMarkerList";
|
||||
import { FilteredSceneMarkerList } from "src/components/Scenes/SceneMarkerList";
|
||||
import { View } from "src/components/List/views";
|
||||
|
||||
function useFilterHook(tag: GQL.TagDataFragment, showSubTagContent?: boolean) {
|
||||
|
|
@ -60,7 +60,7 @@ export const TagMarkersPanel: React.FC<ITagMarkersPanel> = ({
|
|||
const filterHook = useFilterHook(tag, showSubTagContent);
|
||||
|
||||
return (
|
||||
<SceneMarkerList
|
||||
<FilteredSceneMarkerList
|
||||
filterHook={filterHook}
|
||||
alterQuery={active}
|
||||
view={View.TagMarkers}
|
||||
|
|
|
|||
Loading…
Reference in a new issue