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