mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 20:07:05 +03:00
Add more patchable components (#6404)
This commit is contained in:
@@ -9,98 +9,102 @@ import { useToast } from "src/hooks/Toast";
|
||||
import { useIntl } from "react-intl";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { galleryTitle } from "src/core/galleries";
|
||||
import { IItemListOperation } from "src/components/List/FilteredListToolbar";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
interface IGalleryAddProps {
|
||||
active: boolean;
|
||||
gallery: GQL.GalleryDataFragment;
|
||||
extraOperations?: IItemListOperation<GQL.FindImagesQueryResult>[];
|
||||
}
|
||||
|
||||
export const GalleryAddPanel: React.FC<IGalleryAddProps> = ({
|
||||
active,
|
||||
gallery,
|
||||
}) => {
|
||||
const Toast = useToast();
|
||||
const intl = useIntl();
|
||||
export const GalleryAddPanel: React.FC<IGalleryAddProps> = PatchComponent(
|
||||
"GalleryAddPanel",
|
||||
({ active, gallery, extraOperations = [] }) => {
|
||||
const Toast = useToast();
|
||||
const intl = useIntl();
|
||||
|
||||
function filterHook(filter: ListFilterModel) {
|
||||
const galleryValue = {
|
||||
id: gallery.id,
|
||||
label: galleryTitle(gallery),
|
||||
};
|
||||
// if galleries is already present, then we modify it, otherwise add
|
||||
let galleryCriterion = filter.criteria.find((c) => {
|
||||
return c.criterionOption.type === "galleries";
|
||||
}) as GalleriesCriterion | undefined;
|
||||
function filterHook(filter: ListFilterModel) {
|
||||
const galleryValue = {
|
||||
id: gallery.id,
|
||||
label: galleryTitle(gallery),
|
||||
};
|
||||
// if galleries is already present, then we modify it, otherwise add
|
||||
let galleryCriterion = filter.criteria.find((c) => {
|
||||
return c.criterionOption.type === "galleries";
|
||||
}) as GalleriesCriterion | undefined;
|
||||
|
||||
if (
|
||||
galleryCriterion &&
|
||||
galleryCriterion.modifier === GQL.CriterionModifier.Excludes
|
||||
) {
|
||||
// add the gallery if not present
|
||||
if (
|
||||
!galleryCriterion.value.find((p) => {
|
||||
return p.id === gallery.id;
|
||||
})
|
||||
galleryCriterion &&
|
||||
galleryCriterion.modifier === GQL.CriterionModifier.Excludes
|
||||
) {
|
||||
galleryCriterion.value.push(galleryValue);
|
||||
// add the gallery if not present
|
||||
if (
|
||||
!galleryCriterion.value.find((p) => {
|
||||
return p.id === gallery.id;
|
||||
})
|
||||
) {
|
||||
galleryCriterion.value.push(galleryValue);
|
||||
}
|
||||
|
||||
galleryCriterion.modifier = GQL.CriterionModifier.Excludes;
|
||||
} else {
|
||||
// overwrite
|
||||
galleryCriterion = new GalleriesCriterion();
|
||||
galleryCriterion.modifier = GQL.CriterionModifier.Excludes;
|
||||
galleryCriterion.value = [galleryValue];
|
||||
filter.criteria.push(galleryCriterion);
|
||||
}
|
||||
|
||||
galleryCriterion.modifier = GQL.CriterionModifier.Excludes;
|
||||
} else {
|
||||
// overwrite
|
||||
galleryCriterion = new GalleriesCriterion();
|
||||
galleryCriterion.modifier = GQL.CriterionModifier.Excludes;
|
||||
galleryCriterion.value = [galleryValue];
|
||||
filter.criteria.push(galleryCriterion);
|
||||
return filter;
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
async function addImages(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>
|
||||
) {
|
||||
try {
|
||||
await mutateAddGalleryImages({
|
||||
gallery_id: gallery.id!,
|
||||
image_ids: Array.from(selectedIds.values()),
|
||||
});
|
||||
const imageCount = selectedIds.size;
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.added_entity" },
|
||||
{
|
||||
count: imageCount,
|
||||
singularEntity: intl.formatMessage({ id: "image" }),
|
||||
pluralEntity: intl.formatMessage({ id: "images" }),
|
||||
}
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
async function addImages(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>
|
||||
) {
|
||||
try {
|
||||
await mutateAddGalleryImages({
|
||||
gallery_id: gallery.id!,
|
||||
image_ids: Array.from(selectedIds.values()),
|
||||
});
|
||||
const imageCount = selectedIds.size;
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.added_entity" },
|
||||
{
|
||||
count: imageCount,
|
||||
singularEntity: intl.formatMessage({ id: "image" }),
|
||||
pluralEntity: intl.formatMessage({ id: "images" }),
|
||||
}
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
const otherOperations = [
|
||||
...extraOperations,
|
||||
{
|
||||
text: intl.formatMessage(
|
||||
{ id: "actions.add_to_entity" },
|
||||
{ entityType: intl.formatMessage({ id: "gallery" }) }
|
||||
),
|
||||
onClick: addImages,
|
||||
isDisplayed: showWhenSelected,
|
||||
postRefetch: true,
|
||||
icon: faPlus,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ImageList
|
||||
filterHook={filterHook}
|
||||
extraOperations={otherOperations}
|
||||
alterQuery={active}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const otherOperations = [
|
||||
{
|
||||
text: intl.formatMessage(
|
||||
{ id: "actions.add_to_entity" },
|
||||
{ entityType: intl.formatMessage({ id: "gallery" }) }
|
||||
),
|
||||
onClick: addImages,
|
||||
isDisplayed: showWhenSelected,
|
||||
postRefetch: true,
|
||||
icon: faPlus,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ImageList
|
||||
filterHook={filterHook}
|
||||
extraOperations={otherOperations}
|
||||
alterQuery={active}
|
||||
/>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
@@ -16,132 +16,139 @@ import { useIntl } from "react-intl";
|
||||
import { faMinus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { galleryTitle } from "src/core/galleries";
|
||||
import { View } from "src/components/List/views";
|
||||
import { PatchComponent } from "src/patch";
|
||||
import { IItemListOperation } from "src/components/List/FilteredListToolbar";
|
||||
|
||||
interface IGalleryDetailsProps {
|
||||
active: boolean;
|
||||
gallery: GQL.GalleryDataFragment;
|
||||
extraOperations?: IItemListOperation<GQL.FindImagesQueryResult>[];
|
||||
}
|
||||
|
||||
export const GalleryImagesPanel: React.FC<IGalleryDetailsProps> = ({
|
||||
active,
|
||||
gallery,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const Toast = useToast();
|
||||
export const GalleryImagesPanel: React.FC<IGalleryDetailsProps> =
|
||||
PatchComponent(
|
||||
"GalleryImagesPanel",
|
||||
({ active, gallery, extraOperations = [] }) => {
|
||||
const intl = useIntl();
|
||||
const Toast = useToast();
|
||||
|
||||
function filterHook(filter: ListFilterModel) {
|
||||
const galleryValue = {
|
||||
id: gallery.id!,
|
||||
label: galleryTitle(gallery),
|
||||
};
|
||||
// if galleries is already present, then we modify it, otherwise add
|
||||
let galleryCriterion = filter.criteria.find((c) => {
|
||||
return c.criterionOption.type === "galleries";
|
||||
}) as GalleriesCriterion | undefined;
|
||||
function filterHook(filter: ListFilterModel) {
|
||||
const galleryValue = {
|
||||
id: gallery.id!,
|
||||
label: galleryTitle(gallery),
|
||||
};
|
||||
// if galleries is already present, then we modify it, otherwise add
|
||||
let galleryCriterion = filter.criteria.find((c) => {
|
||||
return c.criterionOption.type === "galleries";
|
||||
}) as GalleriesCriterion | undefined;
|
||||
|
||||
if (
|
||||
galleryCriterion &&
|
||||
(galleryCriterion.modifier === GQL.CriterionModifier.IncludesAll ||
|
||||
galleryCriterion.modifier === GQL.CriterionModifier.Includes)
|
||||
) {
|
||||
// add the gallery if not present
|
||||
if (
|
||||
!galleryCriterion.value.find((p) => {
|
||||
return p.id === gallery.id;
|
||||
})
|
||||
) {
|
||||
galleryCriterion.value.push(galleryValue);
|
||||
if (
|
||||
galleryCriterion &&
|
||||
(galleryCriterion.modifier === GQL.CriterionModifier.IncludesAll ||
|
||||
galleryCriterion.modifier === GQL.CriterionModifier.Includes)
|
||||
) {
|
||||
// add the gallery if not present
|
||||
if (
|
||||
!galleryCriterion.value.find((p) => {
|
||||
return p.id === gallery.id;
|
||||
})
|
||||
) {
|
||||
galleryCriterion.value.push(galleryValue);
|
||||
}
|
||||
|
||||
galleryCriterion.modifier = GQL.CriterionModifier.IncludesAll;
|
||||
} else {
|
||||
// overwrite
|
||||
galleryCriterion = new GalleriesCriterion();
|
||||
galleryCriterion.value = [galleryValue];
|
||||
filter.criteria.push(galleryCriterion);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
galleryCriterion.modifier = GQL.CriterionModifier.IncludesAll;
|
||||
} else {
|
||||
// overwrite
|
||||
galleryCriterion = new GalleriesCriterion();
|
||||
galleryCriterion.value = [galleryValue];
|
||||
filter.criteria.push(galleryCriterion);
|
||||
}
|
||||
async function setCover(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>
|
||||
) {
|
||||
const coverImageID = selectedIds.values().next();
|
||||
if (coverImageID.done) {
|
||||
// operation should only be displayed when exactly one image is selected
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await mutateSetGalleryCover({
|
||||
gallery_id: gallery.id!,
|
||||
cover_image_id: coverImageID.value,
|
||||
});
|
||||
|
||||
return filter;
|
||||
}
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.updated_entity" },
|
||||
{
|
||||
entity: intl
|
||||
.formatMessage({ id: "gallery" })
|
||||
.toLocaleLowerCase(),
|
||||
}
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function setCover(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>
|
||||
) {
|
||||
const coverImageID = selectedIds.values().next();
|
||||
if (coverImageID.done) {
|
||||
// operation should only be displayed when exactly one image is selected
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await mutateSetGalleryCover({
|
||||
gallery_id: gallery.id!,
|
||||
cover_image_id: coverImageID.value,
|
||||
});
|
||||
async function removeImages(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>
|
||||
) {
|
||||
try {
|
||||
await mutateRemoveGalleryImages({
|
||||
gallery_id: gallery.id!,
|
||||
image_ids: Array.from(selectedIds.values()),
|
||||
});
|
||||
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.updated_entity" },
|
||||
{
|
||||
entity: intl.formatMessage({ id: "gallery" }).toLocaleLowerCase(),
|
||||
}
|
||||
)
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.removed_entity" },
|
||||
{
|
||||
count: selectedIds.size,
|
||||
singularEntity: intl.formatMessage({ id: "image" }),
|
||||
pluralEntity: intl.formatMessage({ id: "images" }),
|
||||
}
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
const otherOperations = [
|
||||
...extraOperations,
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.set_cover" }),
|
||||
onClick: setCover,
|
||||
isDisplayed: showWhenSingleSelection,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.remove_from_gallery" }),
|
||||
onClick: removeImages,
|
||||
isDisplayed: showWhenSelected,
|
||||
postRefetch: true,
|
||||
icon: faMinus,
|
||||
buttonVariant: "danger",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ImageList
|
||||
filterHook={filterHook}
|
||||
alterQuery={active}
|
||||
extraOperations={otherOperations}
|
||||
view={View.GalleryImages}
|
||||
chapters={gallery.chapters}
|
||||
/>
|
||||
);
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function removeImages(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>
|
||||
) {
|
||||
try {
|
||||
await mutateRemoveGalleryImages({
|
||||
gallery_id: gallery.id!,
|
||||
image_ids: Array.from(selectedIds.values()),
|
||||
});
|
||||
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.removed_entity" },
|
||||
{
|
||||
count: selectedIds.size,
|
||||
singularEntity: intl.formatMessage({ id: "image" }),
|
||||
pluralEntity: intl.formatMessage({ id: "images" }),
|
||||
}
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
const otherOperations = [
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.set_cover" }),
|
||||
onClick: setCover,
|
||||
isDisplayed: showWhenSingleSelection,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.remove_from_gallery" }),
|
||||
onClick: removeImages,
|
||||
isDisplayed: showWhenSelected,
|
||||
postRefetch: true,
|
||||
icon: faMinus,
|
||||
buttonVariant: "danger",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ImageList
|
||||
filterHook={filterHook}
|
||||
alterQuery={active}
|
||||
extraOperations={otherOperations}
|
||||
view={View.GalleryImages}
|
||||
chapters={gallery.chapters}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,6 +15,8 @@ import { ExportDialog } from "../Shared/ExportDialog";
|
||||
import { GalleryListTable } from "./GalleryListTable";
|
||||
import { GalleryCardGrid } from "./GalleryGridCard";
|
||||
import { View } from "../List/views";
|
||||
import { PatchComponent } from "src/patch";
|
||||
import { IItemListOperation } from "../List/FilteredListToolbar";
|
||||
|
||||
function getItems(result: GQL.FindGalleriesQueryResult) {
|
||||
return result?.data?.findGalleries?.galleries ?? [];
|
||||
@@ -28,180 +30,183 @@ interface IGalleryList {
|
||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||
view?: View;
|
||||
alterQuery?: boolean;
|
||||
extraOperations?: IItemListOperation<GQL.FindGalleriesQueryResult>[];
|
||||
}
|
||||
|
||||
export const GalleryList: React.FC<IGalleryList> = ({
|
||||
filterHook,
|
||||
view,
|
||||
alterQuery,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
export const GalleryList: React.FC<IGalleryList> = PatchComponent(
|
||||
"GalleryList",
|
||||
({ filterHook, view, alterQuery, extraOperations = [] }) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
|
||||
const filterMode = GQL.FilterMode.Galleries;
|
||||
const filterMode = GQL.FilterMode.Galleries;
|
||||
|
||||
const otherOperations = [
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.view_random" }),
|
||||
onClick: viewRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
];
|
||||
const otherOperations = [
|
||||
...extraOperations,
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.view_random" }),
|
||||
onClick: viewRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
];
|
||||
|
||||
function addKeybinds(
|
||||
result: GQL.FindGalleriesQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
viewRandom(result, filter);
|
||||
});
|
||||
function addKeybinds(
|
||||
result: GQL.FindGalleriesQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
viewRandom(result, filter);
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
|
||||
async function viewRandom(
|
||||
result: GQL.FindGalleriesQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random image
|
||||
if (result.data?.findGalleries) {
|
||||
const { count } = result.data.findGalleries;
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindGalleries(filterCopy);
|
||||
if (singleResult.data.findGalleries.galleries.length === 1) {
|
||||
const { id } = singleResult.data.findGalleries.galleries[0];
|
||||
// navigate to the image player page
|
||||
history.push(`/galleries/${id}`);
|
||||
}
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
async function viewRandom(
|
||||
result: GQL.FindGalleriesQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random image
|
||||
if (result.data?.findGalleries) {
|
||||
const { count } = result.data.findGalleries;
|
||||
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindGalleriesQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
function maybeRenderGalleryExportDialog() {
|
||||
if (isExportDialogOpen) {
|
||||
return (
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
galleries: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
}}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
/>
|
||||
);
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindGalleries(filterCopy);
|
||||
if (singleResult.data.findGalleries.galleries.length === 1) {
|
||||
const { id } = singleResult.data.findGalleries.galleries[0];
|
||||
// navigate to the image player page
|
||||
history.push(`/galleries/${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderGalleries() {
|
||||
if (!result.data?.findGalleries) return;
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<GalleryCardGrid
|
||||
galleries={result.data.findGalleries.galleries}
|
||||
selectedIds={selectedIds}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindGalleriesQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
function maybeRenderGalleryExportDialog() {
|
||||
if (isExportDialogOpen) {
|
||||
return (
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
galleries: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
}}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
return (
|
||||
<GalleryListTable
|
||||
galleries={result.data.findGalleries.galleries}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className={`GalleryWall zoom-${filter.zoomIndex}`}>
|
||||
{result.data.findGalleries.galleries.map((gallery) => (
|
||||
<GalleryWallCard key={gallery.id} gallery={gallery} />
|
||||
))}
|
||||
|
||||
function renderGalleries() {
|
||||
if (!result.data?.findGalleries) return;
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<GalleryCardGrid
|
||||
galleries={result.data.findGalleries.galleries}
|
||||
selectedIds={selectedIds}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
return (
|
||||
<GalleryListTable
|
||||
galleries={result.data.findGalleries.galleries}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className={`GalleryWall zoom-${filter.zoomIndex}`}>
|
||||
{result.data.findGalleries.galleries.map((gallery) => (
|
||||
<GalleryWallCard key={gallery.id} gallery={gallery} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{maybeRenderGalleryExportDialog()}
|
||||
{renderGalleries()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedImages: GQL.SlimGalleryDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<EditGalleriesDialog selected={selectedImages} onClose={onClose} />
|
||||
);
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedImages: GQL.SlimGalleryDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteGalleriesDialog selected={selectedImages} onClose={onClose} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{maybeRenderGalleryExportDialog()}
|
||||
{renderGalleries()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedImages: GQL.SlimGalleryDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return <EditGalleriesDialog selected={selectedImages} onClose={onClose} />;
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedImages: GQL.SlimGalleryDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteGalleriesDialog selected={selectedImages} onClose={onClose} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindGalleries}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindGalleries}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
};
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -18,7 +18,10 @@ import {
|
||||
SearchTermInput,
|
||||
} from "src/components/List/ListFilter";
|
||||
import { useFilter } from "src/components/List/FilterProvider";
|
||||
import { IFilteredListToolbar } from "src/components/List/FilteredListToolbar";
|
||||
import {
|
||||
IFilteredListToolbar,
|
||||
IItemListOperation,
|
||||
} from "src/components/List/FilteredListToolbar";
|
||||
import {
|
||||
showWhenNoneSelected,
|
||||
showWhenSelected,
|
||||
@@ -28,6 +31,7 @@ import { useIntl } from "react-intl";
|
||||
import { useToast } from "src/hooks/Toast";
|
||||
import { useModal } from "src/hooks/modal";
|
||||
import { AddSubGroupsDialog } from "./AddGroupsDialog";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
const useContainingGroupFilterHook = (
|
||||
group: Pick<GQL.StudioDataFragment, "id" | "name">,
|
||||
@@ -99,6 +103,7 @@ const Toolbar: React.FC<IFilteredListToolbar> = ({
|
||||
interface IGroupSubGroupsPanel {
|
||||
active: boolean;
|
||||
group: GQL.GroupDataFragment;
|
||||
extraOperations?: IItemListOperation<GQL.FindGroupsQueryResult>[];
|
||||
}
|
||||
|
||||
const defaultFilter = (() => {
|
||||
@@ -113,92 +118,99 @@ const defaultFilter = (() => {
|
||||
return ret;
|
||||
})();
|
||||
|
||||
export const GroupSubGroupsPanel: React.FC<IGroupSubGroupsPanel> = ({
|
||||
active,
|
||||
group,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const Toast = useToast();
|
||||
const { modal, showModal, closeModal } = useModal();
|
||||
export const GroupSubGroupsPanel: React.FC<IGroupSubGroupsPanel> =
|
||||
PatchComponent(
|
||||
"GroupSubGroupsPanel",
|
||||
({ active, group, extraOperations = [] }) => {
|
||||
const intl = useIntl();
|
||||
const Toast = useToast();
|
||||
const { modal, showModal, closeModal } = useModal();
|
||||
|
||||
const [reorderSubGroups] = useReorderSubGroupsMutation();
|
||||
const mutateRemoveSubGroups = useRemoveSubGroups();
|
||||
const [reorderSubGroups] = useReorderSubGroupsMutation();
|
||||
const mutateRemoveSubGroups = useRemoveSubGroups();
|
||||
|
||||
const filterHook = useContainingGroupFilterHook(group);
|
||||
const filterHook = useContainingGroupFilterHook(group);
|
||||
|
||||
async function removeSubGroups(
|
||||
result: GQL.FindGroupsQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>
|
||||
) {
|
||||
try {
|
||||
await mutateRemoveSubGroups(group.id, Array.from(selectedIds.values()));
|
||||
async function removeSubGroups(
|
||||
result: GQL.FindGroupsQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>
|
||||
) {
|
||||
try {
|
||||
await mutateRemoveSubGroups(
|
||||
group.id,
|
||||
Array.from(selectedIds.values())
|
||||
);
|
||||
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.removed_entity" },
|
||||
{
|
||||
count: selectedIds.size,
|
||||
singularEntity: intl.formatMessage({ id: "group" }),
|
||||
pluralEntity: intl.formatMessage({ id: "groups" }),
|
||||
}
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.removed_entity" },
|
||||
{
|
||||
count: selectedIds.size,
|
||||
singularEntity: intl.formatMessage({ id: "group" }),
|
||||
pluralEntity: intl.formatMessage({ id: "groups" }),
|
||||
}
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function onAddSubGroups() {
|
||||
showModal(
|
||||
<AddSubGroupsDialog containingGroup={group} onClose={closeModal} />
|
||||
);
|
||||
}
|
||||
async function onAddSubGroups() {
|
||||
showModal(
|
||||
<AddSubGroupsDialog containingGroup={group} onClose={closeModal} />
|
||||
);
|
||||
}
|
||||
|
||||
const otherOperations = [
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.add_sub_groups" }),
|
||||
onClick: onAddSubGroups,
|
||||
isDisplayed: showWhenNoneSelected,
|
||||
postRefetch: true,
|
||||
icon: faPlus,
|
||||
buttonVariant: "secondary",
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.remove_from_containing_group" }),
|
||||
onClick: removeSubGroups,
|
||||
isDisplayed: showWhenSelected,
|
||||
postRefetch: true,
|
||||
icon: faMinus,
|
||||
buttonVariant: "danger",
|
||||
},
|
||||
];
|
||||
|
||||
function onMove(srcIds: string[], targetId: string, after: boolean) {
|
||||
reorderSubGroups({
|
||||
variables: {
|
||||
input: {
|
||||
group_id: group.id,
|
||||
sub_group_ids: srcIds,
|
||||
insert_at_id: targetId,
|
||||
insert_after: after,
|
||||
const otherOperations = [
|
||||
...extraOperations,
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.add_sub_groups" }),
|
||||
onClick: onAddSubGroups,
|
||||
isDisplayed: showWhenNoneSelected,
|
||||
postRefetch: true,
|
||||
icon: faPlus,
|
||||
buttonVariant: "secondary",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: "actions.remove_from_containing_group",
|
||||
}),
|
||||
onClick: removeSubGroups,
|
||||
isDisplayed: showWhenSelected,
|
||||
postRefetch: true,
|
||||
icon: faMinus,
|
||||
buttonVariant: "danger",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{modal}
|
||||
<GroupList
|
||||
defaultFilter={defaultFilter}
|
||||
filterHook={filterHook}
|
||||
alterQuery={active}
|
||||
fromGroupId={group.id}
|
||||
otherOperations={otherOperations}
|
||||
onMove={onMove}
|
||||
renderToolbar={(props) => <Toolbar {...props} />}
|
||||
/>
|
||||
</>
|
||||
function onMove(srcIds: string[], targetId: string, after: boolean) {
|
||||
reorderSubGroups({
|
||||
variables: {
|
||||
input: {
|
||||
group_id: group.id,
|
||||
sub_group_ids: srcIds,
|
||||
insert_at_id: targetId,
|
||||
insert_after: after,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{modal}
|
||||
<GroupList
|
||||
defaultFilter={defaultFilter}
|
||||
filterHook={filterHook}
|
||||
alterQuery={active}
|
||||
fromGroupId={group.id}
|
||||
otherOperations={otherOperations}
|
||||
onMove={onMove}
|
||||
renderToolbar={(props) => <Toolbar {...props} />}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
IFilteredListToolbar,
|
||||
IItemListOperation,
|
||||
} from "../List/FilteredListToolbar";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
const GroupExportDialog: React.FC<{
|
||||
open?: boolean;
|
||||
@@ -90,150 +91,153 @@ interface IGroupList extends IGroupListContext {
|
||||
otherOperations?: IItemListOperation<GQL.FindGroupsQueryResult>[];
|
||||
}
|
||||
|
||||
export const GroupList: React.FC<IGroupList> = ({
|
||||
filterHook,
|
||||
alterQuery,
|
||||
defaultFilter,
|
||||
view,
|
||||
fromGroupId,
|
||||
onMove,
|
||||
selectable,
|
||||
renderToolbar,
|
||||
otherOperations: providedOperations = [],
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
export const GroupList: React.FC<IGroupList> = PatchComponent(
|
||||
"GroupList",
|
||||
({
|
||||
filterHook,
|
||||
alterQuery,
|
||||
defaultFilter,
|
||||
view,
|
||||
fromGroupId,
|
||||
onMove,
|
||||
selectable,
|
||||
renderToolbar,
|
||||
otherOperations: providedOperations = [],
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
|
||||
const otherOperations = [
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.view_random" }),
|
||||
onClick: viewRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
...providedOperations,
|
||||
];
|
||||
const otherOperations = [
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.view_random" }),
|
||||
onClick: viewRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
...providedOperations,
|
||||
];
|
||||
|
||||
function addKeybinds(
|
||||
result: GQL.FindGroupsQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
viewRandom(result, filter);
|
||||
});
|
||||
function addKeybinds(
|
||||
result: GQL.FindGroupsQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
viewRandom(result, filter);
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
|
||||
async function viewRandom(
|
||||
result: GQL.FindGroupsQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random image
|
||||
if (result.data?.findGroups) {
|
||||
const { count } = result.data.findGroups;
|
||||
async function viewRandom(
|
||||
result: GQL.FindGroupsQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random image
|
||||
if (result.data?.findGroups) {
|
||||
const { count } = result.data.findGroups;
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindGroups(filterCopy);
|
||||
if (singleResult.data.findGroups.groups.length === 1) {
|
||||
const { id } = singleResult.data.findGroups.groups[0];
|
||||
// navigate to the group page
|
||||
history.push(`/groups/${id}`);
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindGroups(filterCopy);
|
||||
if (singleResult.data.findGroups.groups.length === 1) {
|
||||
const { id } = singleResult.data.findGroups.groups[0];
|
||||
// navigate to the group page
|
||||
history.push(`/groups/${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindGroupsQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<GroupExportDialog
|
||||
open={isExportDialogOpen}
|
||||
selectedIds={selectedIds}
|
||||
isExportAll={isExportAll}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
/>
|
||||
{filter.displayMode === DisplayMode.Grid && (
|
||||
<GroupCardGrid
|
||||
groups={result.data?.findGroups.groups ?? []}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
function renderContent(
|
||||
result: GQL.FindGroupsQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<GroupExportDialog
|
||||
open={isExportDialogOpen}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
fromGroupId={fromGroupId}
|
||||
onMove={onMove}
|
||||
isExportAll={isExportAll}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
{filter.displayMode === DisplayMode.Grid && (
|
||||
<GroupCardGrid
|
||||
groups={result.data?.findGroups.groups ?? []}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
fromGroupId={fromGroupId}
|
||||
onMove={onMove}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedGroups: GQL.GroupDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return <EditGroupsDialog selected={selectedGroups} onClose={onClose} />;
|
||||
}
|
||||
function renderEditDialog(
|
||||
selectedGroups: GQL.GroupDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return <EditGroupsDialog selected={selectedGroups} onClose={onClose} />;
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedGroups: GQL.SlimGroupDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteEntityDialog
|
||||
selected={selectedGroups}
|
||||
onClose={onClose}
|
||||
singularEntity={intl.formatMessage({ id: "group" })}
|
||||
pluralEntity={intl.formatMessage({ id: "groups" })}
|
||||
destroyMutation={useGroupsDestroy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedGroups: GQL.SlimGroupDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteEntityDialog
|
||||
selected={selectedGroups}
|
||||
onClose={onClose}
|
||||
singularEntity={intl.formatMessage({ id: "group" })}
|
||||
pluralEntity={intl.formatMessage({ id: "groups" })}
|
||||
destroyMutation={useGroupsDestroy}
|
||||
/>
|
||||
<GroupListContext
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
defaultFilter={defaultFilter}
|
||||
selectable={selectable}
|
||||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
renderToolbar={renderToolbar}
|
||||
/>
|
||||
</GroupListContext>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GroupListContext
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
defaultFilter={defaultFilter}
|
||||
selectable={selectable}
|
||||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
renderToolbar={renderToolbar}
|
||||
/>
|
||||
</GroupListContext>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
@@ -26,6 +26,7 @@ import { ImageGridCard } from "./ImageGridCard";
|
||||
import { View } from "../List/views";
|
||||
import { IItemListOperation } from "../List/FilteredListToolbar";
|
||||
import { FileSize } from "../Shared/FileSize";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
interface IImageWallProps {
|
||||
images: GQL.SlimImageDataFragment[];
|
||||
@@ -318,167 +319,168 @@ interface IImageList {
|
||||
chapters?: GQL.GalleryChapterDataFragment[];
|
||||
}
|
||||
|
||||
export const ImageList: React.FC<IImageList> = ({
|
||||
filterHook,
|
||||
view,
|
||||
alterQuery,
|
||||
extraOperations,
|
||||
chapters = [],
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
const [slideshowRunning, setSlideshowRunning] = useState<boolean>(false);
|
||||
export const ImageList: React.FC<IImageList> = PatchComponent(
|
||||
"ImageList",
|
||||
({ filterHook, view, alterQuery, extraOperations = [], chapters = [] }) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
const [slideshowRunning, setSlideshowRunning] = useState<boolean>(false);
|
||||
|
||||
const filterMode = GQL.FilterMode.Images;
|
||||
const filterMode = GQL.FilterMode.Images;
|
||||
|
||||
const otherOperations = [
|
||||
...(extraOperations ?? []),
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.view_random" }),
|
||||
onClick: viewRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
];
|
||||
const otherOperations = [
|
||||
...extraOperations,
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.view_random" }),
|
||||
onClick: viewRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
];
|
||||
|
||||
function addKeybinds(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
viewRandom(result, filter);
|
||||
});
|
||||
function addKeybinds(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
viewRandom(result, filter);
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
|
||||
async function viewRandom(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random image
|
||||
if (result.data?.findImages) {
|
||||
const { count } = result.data.findImages;
|
||||
async function viewRandom(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random image
|
||||
if (result.data?.findImages) {
|
||||
const { count } = result.data.findImages;
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindImages(filterCopy);
|
||||
if (singleResult.data.findImages.images.length === 1) {
|
||||
const { id } = singleResult.data.findImages.images[0];
|
||||
// navigate to the image player page
|
||||
history.push(`/images/${id}`);
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindImages(filterCopy);
|
||||
if (singleResult.data.findImages.images.length === 1) {
|
||||
const { id } = singleResult.data.findImages.images[0];
|
||||
// navigate to the image player page
|
||||
history.push(`/images/${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (
|
||||
id: string,
|
||||
selected: boolean,
|
||||
shiftKey: boolean
|
||||
) => void,
|
||||
onChangePage: (page: number) => void,
|
||||
pageCount: number
|
||||
) {
|
||||
function maybeRenderImageExportDialog() {
|
||||
if (isExportDialogOpen) {
|
||||
return (
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
images: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
}}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderImages() {
|
||||
if (!result.data?.findImages) return;
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindImagesQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void,
|
||||
onChangePage: (page: number) => void,
|
||||
pageCount: number
|
||||
) {
|
||||
function maybeRenderImageExportDialog() {
|
||||
if (isExportDialogOpen) {
|
||||
return (
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
images: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
}}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
<ImageListImages
|
||||
filter={filter}
|
||||
images={result.data.findImages.images}
|
||||
onChangePage={onChangePage}
|
||||
onSelectChange={onSelectChange}
|
||||
pageCount={pageCount}
|
||||
selectedIds={selectedIds}
|
||||
slideshowRunning={slideshowRunning}
|
||||
setSlideshowRunning={setSlideshowRunning}
|
||||
chapters={chapters}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderImages() {
|
||||
if (!result.data?.findImages) return;
|
||||
|
||||
return (
|
||||
<ImageListImages
|
||||
filter={filter}
|
||||
images={result.data.findImages.images}
|
||||
onChangePage={onChangePage}
|
||||
onSelectChange={onSelectChange}
|
||||
pageCount={pageCount}
|
||||
selectedIds={selectedIds}
|
||||
slideshowRunning={slideshowRunning}
|
||||
setSlideshowRunning={setSlideshowRunning}
|
||||
chapters={chapters}
|
||||
/>
|
||||
<>
|
||||
{maybeRenderImageExportDialog()}
|
||||
{renderImages()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedImages: GQL.SlimImageDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return <EditImagesDialog selected={selectedImages} onClose={onClose} />;
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedImages: GQL.SlimImageDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return <DeleteImagesDialog selected={selectedImages} onClose={onClose} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{maybeRenderImageExportDialog()}
|
||||
{renderImages()}
|
||||
</>
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindImages}
|
||||
useMetadataInfo={useFindImagesMetadata}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
renderMetadataByline={renderMetadataByline}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedImages: GQL.SlimImageDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return <EditImagesDialog selected={selectedImages} onClose={onClose} />;
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedImages: GQL.SlimImageDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return <DeleteImagesDialog selected={selectedImages} onClose={onClose} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindImages}
|
||||
useMetadataInfo={useFindImagesMetadata}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
renderMetadataByline={renderMetadataByline}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
@@ -22,6 +22,8 @@ import { cmToImperial, cmToInches, kgToLbs } from "src/utils/units";
|
||||
import TextUtils from "src/utils/text";
|
||||
import { PerformerCardGrid } from "./PerformerCardGrid";
|
||||
import { View } from "../List/views";
|
||||
import { IItemListOperation } from "../List/FilteredListToolbar";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
function getItems(result: GQL.FindPerformersQueryResult) {
|
||||
return result?.data?.findPerformers?.performers ?? [];
|
||||
@@ -159,183 +161,185 @@ interface IPerformerList {
|
||||
view?: View;
|
||||
alterQuery?: boolean;
|
||||
extraCriteria?: IPerformerCardExtraCriteria;
|
||||
extraOperations?: IItemListOperation<GQL.FindPerformersQueryResult>[];
|
||||
}
|
||||
|
||||
export const PerformerList: React.FC<IPerformerList> = ({
|
||||
filterHook,
|
||||
view,
|
||||
alterQuery,
|
||||
extraCriteria,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
export const PerformerList: React.FC<IPerformerList> = PatchComponent(
|
||||
"PerformerList",
|
||||
({ filterHook, view, alterQuery, extraCriteria, extraOperations = [] }) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
|
||||
const filterMode = GQL.FilterMode.Performers;
|
||||
const filterMode = GQL.FilterMode.Performers;
|
||||
|
||||
const otherOperations = [
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.open_random" }),
|
||||
onClick: openRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
];
|
||||
const otherOperations = [
|
||||
...extraOperations,
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.open_random" }),
|
||||
onClick: openRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
];
|
||||
|
||||
function addKeybinds(
|
||||
result: GQL.FindPerformersQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
openRandom(result, filter);
|
||||
});
|
||||
function addKeybinds(
|
||||
result: GQL.FindPerformersQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
openRandom(result, filter);
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
|
||||
async function openRandom(
|
||||
result: GQL.FindPerformersQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
if (result.data?.findPerformers) {
|
||||
const { count } = result.data.findPerformers;
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindPerformers(filterCopy);
|
||||
if (singleResult.data.findPerformers.performers.length === 1) {
|
||||
const { id } = singleResult.data.findPerformers.performers[0]!;
|
||||
history.push(`/performers/${id}`);
|
||||
async function openRandom(
|
||||
result: GQL.FindPerformersQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
if (result.data?.findPerformers) {
|
||||
const { count } = result.data.findPerformers;
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindPerformers(filterCopy);
|
||||
if (singleResult.data.findPerformers.performers.length === 1) {
|
||||
const { id } = singleResult.data.findPerformers.performers[0]!;
|
||||
history.push(`/performers/${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindPerformersQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
function maybeRenderPerformerExportDialog() {
|
||||
if (isExportDialogOpen) {
|
||||
return (
|
||||
<>
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
performers: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
}}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
function renderContent(
|
||||
result: GQL.FindPerformersQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
function maybeRenderPerformerExportDialog() {
|
||||
if (isExportDialogOpen) {
|
||||
return (
|
||||
<>
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
performers: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
}}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPerformers() {
|
||||
if (!result.data?.findPerformers) return;
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<PerformerCardGrid
|
||||
performers={result.data.findPerformers.performers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
extraCriteria={extraCriteria}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
return (
|
||||
<PerformerListTable
|
||||
performers={result.data.findPerformers.performers}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Tagger) {
|
||||
return (
|
||||
<PerformerTagger
|
||||
performers={result.data.findPerformers.performers}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{maybeRenderPerformerExportDialog()}
|
||||
{renderPerformers()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderPerformers() {
|
||||
if (!result.data?.findPerformers) return;
|
||||
function renderEditDialog(
|
||||
selectedPerformers: GQL.SlimPerformerDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<EditPerformersDialog selected={selectedPerformers} onClose={onClose} />
|
||||
);
|
||||
}
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<PerformerCardGrid
|
||||
performers={result.data.findPerformers.performers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
extraCriteria={extraCriteria}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
return (
|
||||
<PerformerListTable
|
||||
performers={result.data.findPerformers.performers}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Tagger) {
|
||||
return (
|
||||
<PerformerTagger performers={result.data.findPerformers.performers} />
|
||||
);
|
||||
}
|
||||
function renderDeleteDialog(
|
||||
selectedPerformers: GQL.SlimPerformerDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteEntityDialog
|
||||
selected={selectedPerformers}
|
||||
onClose={onClose}
|
||||
singularEntity={intl.formatMessage({ id: "performer" })}
|
||||
pluralEntity={intl.formatMessage({ id: "performers" })}
|
||||
destroyMutation={usePerformersDestroy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{maybeRenderPerformerExportDialog()}
|
||||
{renderPerformers()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedPerformers: GQL.SlimPerformerDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<EditPerformersDialog selected={selectedPerformers} onClose={onClose} />
|
||||
);
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedPerformers: GQL.SlimPerformerDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteEntityDialog
|
||||
selected={selectedPerformers}
|
||||
onClose={onClose}
|
||||
singularEntity={intl.formatMessage({ id: "performer" })}
|
||||
pluralEntity={intl.formatMessage({ id: "performers" })}
|
||||
destroyMutation={usePerformersDestroy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindPerformers}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindPerformers}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
};
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -66,7 +66,7 @@ import {
|
||||
FilteredSidebarHeader,
|
||||
useFilteredSidebarKeybinds,
|
||||
} from "../List/Filters/FilterSidebar";
|
||||
import { PatchContainerComponent } from "src/patch";
|
||||
import { PatchComponent, PatchContainerComponent } from "src/patch";
|
||||
import { Pagination, PaginationIndex } from "../List/Pagination";
|
||||
import { Button, ButtonGroup } from "react-bootstrap";
|
||||
import { Icon } from "../Shared/Icon";
|
||||
@@ -380,83 +380,86 @@ const SceneListOperations: React.FC<{
|
||||
onDelete: () => void;
|
||||
onPlay: () => void;
|
||||
onCreateNew: () => void;
|
||||
}> = ({
|
||||
items,
|
||||
hasSelection,
|
||||
operations,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onPlay,
|
||||
onCreateNew,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
}> = PatchComponent(
|
||||
"SceneListOperations",
|
||||
({
|
||||
items,
|
||||
hasSelection,
|
||||
operations,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onPlay,
|
||||
onCreateNew,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<div className="scene-list-operations">
|
||||
<ButtonGroup>
|
||||
{!!items && (
|
||||
<Button
|
||||
className="play-button"
|
||||
variant="secondary"
|
||||
onClick={() => onPlay()}
|
||||
title={intl.formatMessage({ id: "actions.play" })}
|
||||
>
|
||||
<Icon icon={faPlay} />
|
||||
</Button>
|
||||
)}
|
||||
{!hasSelection && (
|
||||
<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>
|
||||
)}
|
||||
|
||||
{hasSelection && (
|
||||
<>
|
||||
<Button variant="secondary" onClick={() => onEdit()}>
|
||||
<Icon icon={faPencil} />
|
||||
</Button>
|
||||
return (
|
||||
<div className="scene-list-operations">
|
||||
<ButtonGroup>
|
||||
{!!items && (
|
||||
<Button
|
||||
variant="danger"
|
||||
className="btn-danger-minimal"
|
||||
onClick={() => onDelete()}
|
||||
className="play-button"
|
||||
variant="secondary"
|
||||
onClick={() => onPlay()}
|
||||
title={intl.formatMessage({ id: "actions.play" })}
|
||||
>
|
||||
<Icon icon={faTrash} />
|
||||
<Icon icon={faPlay} />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
{!hasSelection && (
|
||||
<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>
|
||||
)}
|
||||
|
||||
<OperationDropdown
|
||||
className="scene-list-operations"
|
||||
menuClassName="scene-list-operations-dropdown"
|
||||
menuPortalTarget={document.body}
|
||||
>
|
||||
{operations.map((o) => {
|
||||
if (o.isDisplayed && !o.isDisplayed()) {
|
||||
return null;
|
||||
}
|
||||
{hasSelection && (
|
||||
<>
|
||||
<Button variant="secondary" onClick={() => onEdit()}>
|
||||
<Icon icon={faPencil} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
className="btn-danger-minimal"
|
||||
onClick={() => onDelete()}
|
||||
>
|
||||
<Icon icon={faTrash} />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
return (
|
||||
<OperationDropdownItem
|
||||
key={o.text}
|
||||
onClick={o.onClick}
|
||||
text={o.text}
|
||||
className={o.className}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OperationDropdown>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
<OperationDropdown
|
||||
className="scene-list-operations"
|
||||
menuClassName="scene-list-operations-dropdown"
|
||||
menuPortalTarget={document.body}
|
||||
>
|
||||
{operations.map((o) => {
|
||||
if (o.isDisplayed && !o.isDisplayed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<OperationDropdownItem
|
||||
key={o.text}
|
||||
onClick={o.onClick}
|
||||
text={o.text}
|
||||
className={o.className}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OperationDropdown>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
interface IFilteredScenes {
|
||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||
|
||||
@@ -17,6 +17,8 @@ import { View } from "../List/views";
|
||||
import { SceneMarkerCardsGrid } from "./SceneMarkerCardsGrid";
|
||||
import { DeleteSceneMarkersDialog } from "./DeleteSceneMarkersDialog";
|
||||
import { EditSceneMarkersDialog } from "./EditSceneMarkersDialog";
|
||||
import { PatchComponent } from "src/patch";
|
||||
import { IItemListOperation } from "../List/FilteredListToolbar";
|
||||
|
||||
function getItems(result: GQL.FindSceneMarkersQueryResult) {
|
||||
return result?.data?.findSceneMarkers?.scene_markers ?? [];
|
||||
@@ -31,132 +33,133 @@ interface ISceneMarkerList {
|
||||
view?: View;
|
||||
alterQuery?: boolean;
|
||||
defaultSort?: string;
|
||||
extraOperations?: IItemListOperation<GQL.FindSceneMarkersQueryResult>[];
|
||||
}
|
||||
|
||||
export const SceneMarkerList: React.FC<ISceneMarkerList> = ({
|
||||
filterHook,
|
||||
view,
|
||||
alterQuery,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
export const SceneMarkerList: React.FC<ISceneMarkerList> = PatchComponent(
|
||||
"SceneMarkerList",
|
||||
({ filterHook, view, alterQuery, extraOperations = [] }) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
|
||||
const filterMode = GQL.FilterMode.SceneMarkers;
|
||||
const filterMode = GQL.FilterMode.SceneMarkers;
|
||||
|
||||
const otherOperations = [
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.play_random" }),
|
||||
onClick: playRandom,
|
||||
},
|
||||
];
|
||||
const otherOperations = [
|
||||
...extraOperations,
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.play_random" }),
|
||||
onClick: playRandom,
|
||||
},
|
||||
];
|
||||
|
||||
function addKeybinds(
|
||||
result: GQL.FindSceneMarkersQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
playRandom(result, filter);
|
||||
});
|
||||
function addKeybinds(
|
||||
result: GQL.FindSceneMarkersQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
playRandom(result, filter);
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
|
||||
async function playRandom(
|
||||
result: GQL.FindSceneMarkersQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random scene
|
||||
if (result.data?.findSceneMarkers) {
|
||||
const { count } = result.data.findSceneMarkers;
|
||||
async function playRandom(
|
||||
result: GQL.FindSceneMarkersQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random scene
|
||||
if (result.data?.findSceneMarkers) {
|
||||
const { count } = result.data.findSceneMarkers;
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindSceneMarkersQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
if (!result.data?.findSceneMarkers) return;
|
||||
function renderContent(
|
||||
result: GQL.FindSceneMarkersQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
if (!result.data?.findSceneMarkers) return;
|
||||
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return (
|
||||
<MarkerWallPanel
|
||||
markers={result.data.findSceneMarkers.scene_markers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<SceneMarkerCardsGrid
|
||||
markers={result.data.findSceneMarkers.scene_markers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedMarkers: GQL.SceneMarkerDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<MarkerWallPanel
|
||||
markers={result.data.findSceneMarkers.scene_markers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
<EditSceneMarkersDialog selected={selectedMarkers} onClose={onClose} />
|
||||
);
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedSceneMarkers: GQL.SceneMarkerDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteSceneMarkersDialog
|
||||
selected={selectedSceneMarkers}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<SceneMarkerCardsGrid
|
||||
markers={result.data.findSceneMarkers.scene_markers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindSceneMarkers}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindSceneMarkers}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
};
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default SceneMarkerList;
|
||||
|
||||
@@ -18,6 +18,8 @@ import { StudioTagger } from "../Tagger/studios/StudioTagger";
|
||||
import { StudioCardGrid } from "./StudioCardGrid";
|
||||
import { View } from "../List/views";
|
||||
import { EditStudiosDialog } from "./EditStudiosDialog";
|
||||
import { IItemListOperation } from "../List/FilteredListToolbar";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
function getItems(result: GQL.FindStudiosQueryResult) {
|
||||
return result?.data?.findStudios?.studios ?? [];
|
||||
@@ -32,177 +34,177 @@ interface IStudioList {
|
||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||
view?: View;
|
||||
alterQuery?: boolean;
|
||||
extraOperations?: IItemListOperation<GQL.FindStudiosQueryResult>[];
|
||||
}
|
||||
|
||||
export const StudioList: React.FC<IStudioList> = ({
|
||||
fromParent,
|
||||
filterHook,
|
||||
view,
|
||||
alterQuery,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
export const StudioList: React.FC<IStudioList> = PatchComponent(
|
||||
"StudioList",
|
||||
({ fromParent, filterHook, view, alterQuery, extraOperations = [] }) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
|
||||
const filterMode = GQL.FilterMode.Studios;
|
||||
const filterMode = GQL.FilterMode.Studios;
|
||||
|
||||
const otherOperations = [
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.view_random" }),
|
||||
onClick: viewRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
];
|
||||
const otherOperations = [
|
||||
...extraOperations,
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.view_random" }),
|
||||
onClick: viewRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
];
|
||||
|
||||
function addKeybinds(
|
||||
result: GQL.FindStudiosQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
viewRandom(result, filter);
|
||||
});
|
||||
function addKeybinds(
|
||||
result: GQL.FindStudiosQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
viewRandom(result, filter);
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
|
||||
async function viewRandom(
|
||||
result: GQL.FindStudiosQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random studio
|
||||
if (result.data?.findStudios) {
|
||||
const { count } = result.data.findStudios;
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindStudios(filterCopy);
|
||||
if (singleResult.data.findStudios.studios.length === 1) {
|
||||
const { id } = singleResult.data.findStudios.studios[0];
|
||||
// navigate to the studio page
|
||||
history.push(`/studios/${id}`);
|
||||
}
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
async function viewRandom(
|
||||
result: GQL.FindStudiosQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random studio
|
||||
if (result.data?.findStudios) {
|
||||
const { count } = result.data.findStudios;
|
||||
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindStudiosQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
function maybeRenderExportDialog() {
|
||||
if (isExportDialogOpen) {
|
||||
return (
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
studios: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
}}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
/>
|
||||
);
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindStudios(filterCopy);
|
||||
if (singleResult.data.findStudios.studios.length === 1) {
|
||||
const { id } = singleResult.data.findStudios.studios[0];
|
||||
// navigate to the studio page
|
||||
history.push(`/studios/${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderStudios() {
|
||||
if (!result.data?.findStudios) return;
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<StudioCardGrid
|
||||
studios={result.data.findStudios.studios}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
fromParent={fromParent}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindStudiosQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
function maybeRenderExportDialog() {
|
||||
if (isExportDialogOpen) {
|
||||
return (
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
studios: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
}}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
return <h1>TODO</h1>;
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return <h1>TODO</h1>;
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Tagger) {
|
||||
return <StudioTagger studios={result.data.findStudios.studios} />;
|
||||
|
||||
function renderStudios() {
|
||||
if (!result.data?.findStudios) return;
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<StudioCardGrid
|
||||
studios={result.data.findStudios.studios}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
fromParent={fromParent}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
return <h1>TODO</h1>;
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return <h1>TODO</h1>;
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Tagger) {
|
||||
return <StudioTagger studios={result.data.findStudios.studios} />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{maybeRenderExportDialog()}
|
||||
{renderStudios()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedStudios: GQL.SlimStudioDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return <EditStudiosDialog selected={selectedStudios} onClose={onClose} />;
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedStudios: GQL.SlimStudioDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteEntityDialog
|
||||
selected={selectedStudios}
|
||||
onClose={onClose}
|
||||
singularEntity={intl.formatMessage({ id: "studio" })}
|
||||
pluralEntity={intl.formatMessage({ id: "studios" })}
|
||||
destroyMutation={useStudiosDestroy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{maybeRenderExportDialog()}
|
||||
{renderStudios()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedStudios: GQL.SlimStudioDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return <EditStudiosDialog selected={selectedStudios} onClose={onClose} />;
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedStudios: GQL.SlimStudioDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteEntityDialog
|
||||
selected={selectedStudios}
|
||||
onClose={onClose}
|
||||
singularEntity={intl.formatMessage({ id: "studio" })}
|
||||
pluralEntity={intl.formatMessage({ id: "studios" })}
|
||||
destroyMutation={useStudiosDestroy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindStudios}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindStudios}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
};
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -26,6 +26,8 @@ import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import { TagCardGrid } from "./TagCardGrid";
|
||||
import { EditTagsDialog } from "./EditTagsDialog";
|
||||
import { View } from "../List/views";
|
||||
import { IItemListOperation } from "../List/FilteredListToolbar";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
function getItems(result: GQL.FindTagsForListQueryResult) {
|
||||
return result?.data?.findTags?.tags ?? [];
|
||||
@@ -38,341 +40,346 @@ function getCount(result: GQL.FindTagsForListQueryResult) {
|
||||
interface ITagList {
|
||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||
alterQuery?: boolean;
|
||||
extraOperations?: IItemListOperation<GQL.FindTagsForListQueryResult>[];
|
||||
}
|
||||
|
||||
export const TagList: React.FC<ITagList> = ({ filterHook, alterQuery }) => {
|
||||
const Toast = useToast();
|
||||
const [deletingTag, setDeletingTag] =
|
||||
useState<Partial<GQL.TagListDataFragment> | null>(null);
|
||||
export const TagList: React.FC<ITagList> = PatchComponent(
|
||||
"TagList",
|
||||
({ filterHook, alterQuery, extraOperations = [] }) => {
|
||||
const Toast = useToast();
|
||||
const [deletingTag, setDeletingTag] =
|
||||
useState<Partial<GQL.TagListDataFragment> | null>(null);
|
||||
|
||||
const filterMode = GQL.FilterMode.Tags;
|
||||
const view = View.Tags;
|
||||
const filterMode = GQL.FilterMode.Tags;
|
||||
const view = View.Tags;
|
||||
|
||||
function getDeleteTagInput() {
|
||||
const tagInput: Partial<GQL.TagDestroyInput> = {};
|
||||
if (deletingTag) {
|
||||
tagInput.id = deletingTag.id;
|
||||
}
|
||||
return tagInput as GQL.TagDestroyInput;
|
||||
}
|
||||
const [deleteTag] = useTagDestroy(getDeleteTagInput());
|
||||
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
|
||||
const otherOperations = [
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.view_random" }),
|
||||
onClick: viewRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
];
|
||||
|
||||
function addKeybinds(
|
||||
result: GQL.FindTagsForListQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
viewRandom(result, filter);
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
|
||||
async function viewRandom(
|
||||
result: GQL.FindTagsForListQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random tag
|
||||
if (result.data?.findTags) {
|
||||
const { count } = result.data.findTags;
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindTagsForList(filterCopy);
|
||||
if (singleResult.data.findTags.tags.length === 1) {
|
||||
const { id } = singleResult.data.findTags.tags[0];
|
||||
// navigate to the tag page
|
||||
history.push(`/tags/${id}`);
|
||||
function getDeleteTagInput() {
|
||||
const tagInput: Partial<GQL.TagDestroyInput> = {};
|
||||
if (deletingTag) {
|
||||
tagInput.id = deletingTag.id;
|
||||
}
|
||||
return tagInput as GQL.TagDestroyInput;
|
||||
}
|
||||
}
|
||||
const [deleteTag] = useTagDestroy(getDeleteTagInput());
|
||||
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
|
||||
const [isExportAll, setIsExportAll] = useState(false);
|
||||
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
const otherOperations = [
|
||||
...extraOperations,
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.view_random" }),
|
||||
onClick: viewRandom,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export" }),
|
||||
onClick: onExport,
|
||||
isDisplayed: showWhenSelected,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({ id: "actions.export_all" }),
|
||||
onClick: onExportAll,
|
||||
},
|
||||
];
|
||||
|
||||
async function onAutoTag(tag: GQL.TagListDataFragment) {
|
||||
if (!tag) return;
|
||||
try {
|
||||
await mutateMetadataAutoTag({ tags: [tag.id] });
|
||||
Toast.success(intl.formatMessage({ id: "toast.started_auto_tagging" }));
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function onDelete() {
|
||||
try {
|
||||
const oldRelations = {
|
||||
parents: deletingTag?.parents ?? [],
|
||||
children: deletingTag?.children ?? [],
|
||||
};
|
||||
await deleteTag();
|
||||
tagRelationHook(deletingTag as GQL.TagListDataFragment, oldRelations, {
|
||||
parents: [],
|
||||
children: [],
|
||||
function addKeybinds(
|
||||
result: GQL.FindTagsForListQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
viewRandom(result, filter);
|
||||
});
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.delete_past_tense" },
|
||||
{
|
||||
count: 1,
|
||||
singularEntity: intl.formatMessage({ id: "tag" }),
|
||||
pluralEntity: intl.formatMessage({ id: "tags" }),
|
||||
}
|
||||
)
|
||||
);
|
||||
setDeletingTag(null);
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindTagsForListQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
function maybeRenderExportDialog() {
|
||||
if (isExportDialogOpen) {
|
||||
return (
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
tags: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
}}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
/>
|
||||
);
|
||||
return () => {
|
||||
Mousetrap.unbind("p r");
|
||||
};
|
||||
}
|
||||
|
||||
async function viewRandom(
|
||||
result: GQL.FindTagsForListQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
// query for a random tag
|
||||
if (result.data?.findTags) {
|
||||
const { count } = result.data.findTags;
|
||||
|
||||
const index = Math.floor(Math.random() * count);
|
||||
const filterCopy = cloneDeep(filter);
|
||||
filterCopy.itemsPerPage = 1;
|
||||
filterCopy.currentPage = index + 1;
|
||||
const singleResult = await queryFindTagsForList(filterCopy);
|
||||
if (singleResult.data.findTags.tags.length === 1) {
|
||||
const { id } = singleResult.data.findTags.tags[0];
|
||||
// navigate to the tag page
|
||||
history.push(`/tags/${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderTags() {
|
||||
if (!result.data?.findTags) return;
|
||||
async function onExport() {
|
||||
setIsExportAll(false);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<TagCardGrid
|
||||
tags={result.data.findTags.tags}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
async function onExportAll() {
|
||||
setIsExportAll(true);
|
||||
setIsExportDialogOpen(true);
|
||||
}
|
||||
|
||||
async function onAutoTag(tag: GQL.TagListDataFragment) {
|
||||
if (!tag) return;
|
||||
try {
|
||||
await mutateMetadataAutoTag({ tags: [tag.id] });
|
||||
Toast.success(intl.formatMessage({ id: "toast.started_auto_tagging" }));
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
const deleteAlert = (
|
||||
<ModalComponent
|
||||
onHide={() => {}}
|
||||
show={!!deletingTag}
|
||||
icon={faTrashAlt}
|
||||
accept={{
|
||||
onClick: onDelete,
|
||||
variant: "danger",
|
||||
text: intl.formatMessage({ id: "actions.delete" }),
|
||||
}}
|
||||
cancel={{ onClick: () => setDeletingTag(null) }}
|
||||
>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="dialogs.delete_confirm"
|
||||
values={{ entityName: deletingTag && deletingTag.name }}
|
||||
/>
|
||||
</span>
|
||||
</ModalComponent>
|
||||
);
|
||||
}
|
||||
|
||||
const tagElements = result.data.findTags.tags.map((tag) => {
|
||||
async function onDelete() {
|
||||
try {
|
||||
const oldRelations = {
|
||||
parents: deletingTag?.parents ?? [],
|
||||
children: deletingTag?.children ?? [],
|
||||
};
|
||||
await deleteTag();
|
||||
tagRelationHook(deletingTag as GQL.TagListDataFragment, oldRelations, {
|
||||
parents: [],
|
||||
children: [],
|
||||
});
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.delete_past_tense" },
|
||||
{
|
||||
count: 1,
|
||||
singularEntity: intl.formatMessage({ id: "tag" }),
|
||||
pluralEntity: intl.formatMessage({ id: "tags" }),
|
||||
}
|
||||
)
|
||||
);
|
||||
setDeletingTag(null);
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindTagsForListQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
) {
|
||||
function maybeRenderExportDialog() {
|
||||
if (isExportDialogOpen) {
|
||||
return (
|
||||
<div key={tag.id} className="tag-list-row row">
|
||||
<Link to={`/tags/${tag.id}`}>{tag.name}</Link>
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
tags: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
}}
|
||||
onClose={() => setIsExportDialogOpen(false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
<div className="ml-auto">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="tag-list-button"
|
||||
onClick={() => onAutoTag(tag)}
|
||||
>
|
||||
<FormattedMessage id="actions.auto_tag" />
|
||||
</Button>
|
||||
<Button variant="secondary" className="tag-list-button">
|
||||
<Link
|
||||
to={NavUtils.makeTagScenesUrl(tag)}
|
||||
className="tag-list-anchor"
|
||||
function renderTags() {
|
||||
if (!result.data?.findTags) return;
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<TagCardGrid
|
||||
tags={result.data.findTags.tags}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
const deleteAlert = (
|
||||
<ModalComponent
|
||||
onHide={() => {}}
|
||||
show={!!deletingTag}
|
||||
icon={faTrashAlt}
|
||||
accept={{
|
||||
onClick: onDelete,
|
||||
variant: "danger",
|
||||
text: intl.formatMessage({ id: "actions.delete" }),
|
||||
}}
|
||||
cancel={{ onClick: () => setDeletingTag(null) }}
|
||||
>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="dialogs.delete_confirm"
|
||||
values={{ entityName: deletingTag && deletingTag.name }}
|
||||
/>
|
||||
</span>
|
||||
</ModalComponent>
|
||||
);
|
||||
|
||||
const tagElements = result.data.findTags.tags.map((tag) => {
|
||||
return (
|
||||
<div key={tag.id} className="tag-list-row row">
|
||||
<Link to={`/tags/${tag.id}`}>{tag.name}</Link>
|
||||
|
||||
<div className="ml-auto">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="tag-list-button"
|
||||
onClick={() => onAutoTag(tag)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="countables.scenes"
|
||||
values={{
|
||||
count: tag.scene_count ?? 0,
|
||||
}}
|
||||
<FormattedMessage id="actions.auto_tag" />
|
||||
</Button>
|
||||
<Button variant="secondary" className="tag-list-button">
|
||||
<Link
|
||||
to={NavUtils.makeTagScenesUrl(tag)}
|
||||
className="tag-list-anchor"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="countables.scenes"
|
||||
values={{
|
||||
count: tag.scene_count ?? 0,
|
||||
}}
|
||||
/>
|
||||
: <FormattedNumber value={tag.scene_count ?? 0} />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="secondary" className="tag-list-button">
|
||||
<Link
|
||||
to={NavUtils.makeTagImagesUrl(tag)}
|
||||
className="tag-list-anchor"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="countables.images"
|
||||
values={{
|
||||
count: tag.image_count ?? 0,
|
||||
}}
|
||||
/>
|
||||
: <FormattedNumber value={tag.image_count ?? 0} />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="secondary" className="tag-list-button">
|
||||
<Link
|
||||
to={NavUtils.makeTagGalleriesUrl(tag)}
|
||||
className="tag-list-anchor"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="countables.galleries"
|
||||
values={{
|
||||
count: tag.gallery_count ?? 0,
|
||||
}}
|
||||
/>
|
||||
: <FormattedNumber value={tag.gallery_count ?? 0} />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="secondary" className="tag-list-button">
|
||||
<Link
|
||||
to={NavUtils.makeTagSceneMarkersUrl(tag)}
|
||||
className="tag-list-anchor"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="countables.markers"
|
||||
values={{
|
||||
count: tag.scene_marker_count ?? 0,
|
||||
}}
|
||||
/>
|
||||
: <FormattedNumber value={tag.scene_marker_count ?? 0} />
|
||||
</Link>
|
||||
</Button>
|
||||
<span className="tag-list-count">
|
||||
<FormattedMessage id="total" />:{" "}
|
||||
<FormattedNumber
|
||||
value={
|
||||
(tag.scene_count || 0) +
|
||||
(tag.scene_marker_count || 0) +
|
||||
(tag.image_count || 0) +
|
||||
(tag.gallery_count || 0)
|
||||
}
|
||||
/>
|
||||
: <FormattedNumber value={tag.scene_count ?? 0} />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="secondary" className="tag-list-button">
|
||||
<Link
|
||||
to={NavUtils.makeTagImagesUrl(tag)}
|
||||
className="tag-list-anchor"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="countables.images"
|
||||
values={{
|
||||
count: tag.image_count ?? 0,
|
||||
}}
|
||||
/>
|
||||
: <FormattedNumber value={tag.image_count ?? 0} />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="secondary" className="tag-list-button">
|
||||
<Link
|
||||
to={NavUtils.makeTagGalleriesUrl(tag)}
|
||||
className="tag-list-anchor"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="countables.galleries"
|
||||
values={{
|
||||
count: tag.gallery_count ?? 0,
|
||||
}}
|
||||
/>
|
||||
: <FormattedNumber value={tag.gallery_count ?? 0} />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="secondary" className="tag-list-button">
|
||||
<Link
|
||||
to={NavUtils.makeTagSceneMarkersUrl(tag)}
|
||||
className="tag-list-anchor"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="countables.markers"
|
||||
values={{
|
||||
count: tag.scene_marker_count ?? 0,
|
||||
}}
|
||||
/>
|
||||
: <FormattedNumber value={tag.scene_marker_count ?? 0} />
|
||||
</Link>
|
||||
</Button>
|
||||
<span className="tag-list-count">
|
||||
<FormattedMessage id="total" />:{" "}
|
||||
<FormattedNumber
|
||||
value={
|
||||
(tag.scene_count || 0) +
|
||||
(tag.scene_marker_count || 0) +
|
||||
(tag.image_count || 0) +
|
||||
(tag.gallery_count || 0)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<Button variant="danger" onClick={() => setDeletingTag(tag)}>
|
||||
<Icon icon={faTrashAlt} color="danger" />
|
||||
</Button>
|
||||
</span>
|
||||
<Button variant="danger" onClick={() => setDeletingTag(tag)}>
|
||||
<Icon icon={faTrashAlt} color="danger" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="col col-sm-8 m-auto">
|
||||
{tagElements}
|
||||
{deleteAlert}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="col col-sm-8 m-auto">
|
||||
{tagElements}
|
||||
{deleteAlert}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return <h1>TODO</h1>;
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return <h1>TODO</h1>;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{maybeRenderExportDialog()}
|
||||
{renderTags()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedTags: GQL.TagListDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return <EditTagsDialog selected={selectedTags} onClose={onClose} />;
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedTags: GQL.TagListDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteEntityDialog
|
||||
selected={selectedTags}
|
||||
onClose={onClose}
|
||||
singularEntity={intl.formatMessage({ id: "tag" })}
|
||||
pluralEntity={intl.formatMessage({ id: "tags" })}
|
||||
destroyMutation={useTagsDestroy}
|
||||
onDeleted={() => {
|
||||
selectedTags.forEach((t) =>
|
||||
tagRelationHook(
|
||||
t,
|
||||
{ parents: t.parents ?? [], children: t.children ?? [] },
|
||||
{ parents: [], children: [] }
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{maybeRenderExportDialog()}
|
||||
{renderTags()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedTags: GQL.TagListDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return <EditTagsDialog selected={selectedTags} onClose={onClose} />;
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedTags: GQL.TagListDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
<DeleteEntityDialog
|
||||
selected={selectedTags}
|
||||
onClose={onClose}
|
||||
singularEntity={intl.formatMessage({ id: "tag" })}
|
||||
pluralEntity={intl.formatMessage({ id: "tags" })}
|
||||
destroyMutation={useTagsDestroy}
|
||||
onDeleted={() => {
|
||||
selectedTags.forEach((t) =>
|
||||
tagRelationHook(
|
||||
t,
|
||||
{ parents: t.parents ?? [], children: t.children ?? [] },
|
||||
{ parents: [], children: [] }
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindTagsForList}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
<ItemListContext
|
||||
filterMode={filterMode}
|
||||
useResult={useFindTagsForList}
|
||||
getItems={getItems}
|
||||
getCount={getCount}
|
||||
alterQuery={alterQuery}
|
||||
filterHook={filterHook}
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
};
|
||||
selectable
|
||||
>
|
||||
<ItemList
|
||||
view={view}
|
||||
otherOperations={otherOperations}
|
||||
addKeybinds={addKeybinds}
|
||||
renderContent={renderContent}
|
||||
renderEditDialog={renderEditDialog}
|
||||
renderDeleteDialog={renderDeleteDialog}
|
||||
/>
|
||||
</ItemListContext>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
12
ui/v2.5/src/pluginApi.d.ts
vendored
12
ui/v2.5/src/pluginApi.d.ts
vendored
@@ -671,14 +671,20 @@ declare namespace PluginApi {
|
||||
"GalleryCard.Image": React.FC<any>;
|
||||
"GalleryCard.Overlays": React.FC<any>;
|
||||
"GalleryCard.Popovers": React.FC<any>;
|
||||
GalleryAddPanel: React.FC<any>;
|
||||
GalleryIDSelect: React.FC<any>;
|
||||
GalleryImagesPanel: React.FC<any>;
|
||||
GalleryList: React.FC<any>;
|
||||
GallerySelect: React.FC<any>;
|
||||
GroupIDSelect: React.FC<any>;
|
||||
GroupList: React.FC<any>;
|
||||
GroupSelect: React.FC<any>;
|
||||
GroupSubGroupsPanel: React.FC<any>;
|
||||
HeaderImage: React.FC<any>;
|
||||
HoverPopover: React.FC<any>;
|
||||
Icon: React.FC<any>;
|
||||
ImageInput: React.FC<any>;
|
||||
ImageList: React.FC<any>;
|
||||
LightboxLink: React.FC<any>;
|
||||
LoadingIndicator: React.FC<any>;
|
||||
"MainNavBar.MenuItems": React.FC<any>;
|
||||
@@ -699,6 +705,7 @@ declare namespace PluginApi {
|
||||
PerformerHeaderImage: React.FC<any>;
|
||||
PerformerIDSelect: React.FC<any>;
|
||||
PerformerImagesPanel: React.FC<any>;
|
||||
PerformerList: React.FC<any>;
|
||||
PerformerPage: React.FC<any>;
|
||||
PerformerScenesPanel: React.FC<any>;
|
||||
PerformerSelect: React.FC<any>;
|
||||
@@ -717,6 +724,9 @@ declare namespace PluginApi {
|
||||
"SceneCard.Image": React.FC<any>;
|
||||
"SceneCard.Overlays": React.FC<any>;
|
||||
"SceneCard.Popovers": React.FC<any>;
|
||||
SceneList: React.FC<any>;
|
||||
SceneListOperations: React.FC<any>;
|
||||
SceneMarkerList: React.FC<any>;
|
||||
SelectSetting: React.FC<any>;
|
||||
Setting: React.FC<any>;
|
||||
SettingGroup: React.FC<any>;
|
||||
@@ -724,6 +734,7 @@ declare namespace PluginApi {
|
||||
StringListSetting: React.FC<any>;
|
||||
StringSetting: React.FC<any>;
|
||||
StudioIDSelect: React.FC<any>;
|
||||
StudioList: React.FC<any>;
|
||||
StudioSelect: React.FC<any>;
|
||||
SweatDrops: React.FC<any>;
|
||||
TabTitleCounter: React.FC<any>;
|
||||
@@ -734,6 +745,7 @@ declare namespace PluginApi {
|
||||
"TagCard.Popovers": React.FC<any>;
|
||||
"TagCard.Title": React.FC<any>;
|
||||
TagLink: React.FC<any>;
|
||||
TagList: React.FC<any>;
|
||||
TagSelect: React.FC<any>;
|
||||
TruncatedText: React.FC<any>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user