mirror of
https://github.com/stashapp/stash.git
synced 2026-02-07 16:05:47 +01:00
initial
This commit is contained in:
parent
0e54a5ceb0
commit
cce025d940
5 changed files with 96 additions and 13 deletions
|
|
@ -20,10 +20,12 @@ input GenerateMetadataInput {
|
|||
|
||||
"scene ids to generate for"
|
||||
sceneIDs: [ID!]
|
||||
"image ids to generate for"
|
||||
imageIDs: [ID!]
|
||||
"marker ids to generate for"
|
||||
markerIDs: [ID!]
|
||||
"image ids to generate for"
|
||||
imageIDs: [ID!]
|
||||
"gallery ids to generate for"
|
||||
galleryIDs: [ID!]
|
||||
|
||||
"overwrite existing media"
|
||||
overwrite: Boolean
|
||||
|
|
|
|||
|
|
@ -35,10 +35,12 @@ type GenerateMetadataInput struct {
|
|||
ImageThumbnails bool `json:"imageThumbnails"`
|
||||
// scene ids to generate for
|
||||
SceneIDs []string `json:"sceneIDs"`
|
||||
// image ids to generate for
|
||||
ImageIDs []string `json:"imageIDs"`
|
||||
// marker ids to generate for
|
||||
MarkerIDs []string `json:"markerIDs"`
|
||||
// image ids to generate for
|
||||
ImageIDs []string `json:"imageIDs"`
|
||||
// gallery ids to generate for
|
||||
GalleryIDs []string `json:"galleryIDs"`
|
||||
// overwrite existing media
|
||||
Overwrite bool `json:"overwrite"`
|
||||
}
|
||||
|
|
@ -114,6 +116,10 @@ func (j *GenerateJob) Execute(ctx context.Context, progress *job.Progress) error
|
|||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
galleryIDs, err := stringslice.StringSliceToIntSlice(j.input.GalleryIDs)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
|
||||
g := &generate.Generator{
|
||||
Encoder: instance.FFMpeg,
|
||||
|
|
@ -127,7 +133,7 @@ func (j *GenerateJob) Execute(ctx context.Context, progress *job.Progress) error
|
|||
r := j.repository
|
||||
if err := r.WithReadTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.Scene
|
||||
if len(j.input.SceneIDs) == 0 && len(j.input.MarkerIDs) == 0 && len(j.input.ImageIDs) == 0 {
|
||||
if len(j.input.SceneIDs) == 0 && len(j.input.MarkerIDs) == 0 && len(j.input.ImageIDs) == 0 && len(j.input.GalleryIDs) == 0 {
|
||||
j.queueTasks(ctx, g, queue)
|
||||
} else {
|
||||
if len(j.input.SceneIDs) > 0 {
|
||||
|
|
@ -161,6 +167,22 @@ func (j *GenerateJob) Execute(ctx context.Context, progress *job.Progress) error
|
|||
j.queueImageJob(g, i, queue)
|
||||
}
|
||||
}
|
||||
|
||||
if len(j.input.GalleryIDs) > 0 {
|
||||
for _, galleryID := range galleryIDs {
|
||||
imgs, err := r.Image.FindByGalleryID(ctx, galleryID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, img := range imgs {
|
||||
if err := img.LoadFiles(ctx, r.Image); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
j.queueImageJob(g, img, queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -14,19 +14,20 @@ import { SettingSection } from "../Settings/SettingSection";
|
|||
import { faCogs, faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { SettingsContext } from "../Settings/context";
|
||||
|
||||
interface ISceneGenerateDialog {
|
||||
interface IGenerateDialog {
|
||||
selectedIds?: string[];
|
||||
onClose: () => void;
|
||||
type: "scene" | "image";
|
||||
type: "scene" | "image" | "gallery";
|
||||
}
|
||||
|
||||
export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
export const GenerateDialog: React.FC<IGenerateDialog> = ({
|
||||
selectedIds,
|
||||
onClose,
|
||||
type,
|
||||
}) => {
|
||||
const sceneIDs = type === "scene" ? selectedIds : undefined;
|
||||
const imageIDs = type === "image" ? selectedIds : undefined;
|
||||
const galleryIDs = type === "gallery" ? selectedIds : undefined;
|
||||
|
||||
const { configuration } = useConfigurationContext();
|
||||
|
||||
|
|
@ -92,6 +93,13 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
|||
}, [configuration, configRead]);
|
||||
|
||||
const selectionStatus = useMemo(() => {
|
||||
const countableIds: Record<typeof type, string> = {
|
||||
scene: "countables.scenes",
|
||||
image: "countables.images",
|
||||
gallery: "countables.galleries",
|
||||
};
|
||||
const countableId = countableIds[type];
|
||||
|
||||
if (selectedIds) {
|
||||
return (
|
||||
<Form.Group id="selected-generate-ids">
|
||||
|
|
@ -101,7 +109,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
|||
num: selectedIds.length,
|
||||
scene: intl.formatMessage(
|
||||
{
|
||||
id: "countables.scenes",
|
||||
id: countableId,
|
||||
},
|
||||
{
|
||||
count: selectedIds.length,
|
||||
|
|
@ -121,7 +129,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
|||
num: intl.formatMessage({ id: "all" }),
|
||||
scene: intl.formatMessage(
|
||||
{
|
||||
id: "countables.scenes",
|
||||
id: countableId,
|
||||
},
|
||||
{
|
||||
count: 0,
|
||||
|
|
@ -138,7 +146,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
|||
<div>{message}</div>
|
||||
</Form.Group>
|
||||
);
|
||||
}, [selectedIds, intl]);
|
||||
}, [selectedIds, intl, type]);
|
||||
|
||||
async function onGenerate() {
|
||||
try {
|
||||
|
|
@ -146,6 +154,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
|||
...options,
|
||||
sceneIDs,
|
||||
imageIDs,
|
||||
galleryIDs,
|
||||
});
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ import {
|
|||
useFindGallery,
|
||||
useGalleryUpdate,
|
||||
} from "src/core/StashService";
|
||||
import { lazyComponent } from "src/utils/lazyComponent";
|
||||
|
||||
const GenerateDialog = lazyComponent(
|
||||
() => import("../../Dialogs/GenerateDialog")
|
||||
);
|
||||
import { ErrorMessage } from "src/components/Shared/ErrorMessage";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
import { Icon } from "src/components/Shared/Icon";
|
||||
|
|
@ -165,6 +170,10 @@ export const GalleryPage: React.FC<IProps> = ({ gallery, add }) => {
|
|||
}
|
||||
|
||||
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
|
||||
const [isGenerateDialogOpen, setIsGenerateDialogOpen] = useState(false);
|
||||
const [generateImageIds, setGenerateImageIds] = useState<string[]>([]);
|
||||
|
||||
const [fetchGalleryImages] = GQL.useFindImagesLazyQuery();
|
||||
|
||||
function onDeleteDialogClosed(deleted: boolean) {
|
||||
setIsDeleteAlertOpen(false);
|
||||
|
|
@ -173,6 +182,28 @@ export const GalleryPage: React.FC<IProps> = ({ gallery, add }) => {
|
|||
}
|
||||
}
|
||||
|
||||
async function onGenerate() {
|
||||
const result = await fetchGalleryImages({
|
||||
variables: {
|
||||
image_filter: {
|
||||
galleries: {
|
||||
modifier: GQL.CriterionModifier.Includes,
|
||||
value: [gallery.id],
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
per_page: -1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result.data?.findImages?.images) {
|
||||
const imageIds = result.data.findImages.images.map((img) => img.id);
|
||||
setGenerateImageIds(imageIds);
|
||||
setIsGenerateDialogOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRenderDeleteDialog() {
|
||||
if (isDeleteAlertOpen && gallery) {
|
||||
return (
|
||||
|
|
@ -184,6 +215,18 @@ export const GalleryPage: React.FC<IProps> = ({ gallery, add }) => {
|
|||
}
|
||||
}
|
||||
|
||||
function maybeRenderGenerateDialog() {
|
||||
if (isGenerateDialogOpen) {
|
||||
return (
|
||||
<GenerateDialog
|
||||
selectedIds={generateImageIds}
|
||||
onClose={() => setIsGenerateDialogOpen(false)}
|
||||
type="image"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderOperations() {
|
||||
return (
|
||||
<Dropdown>
|
||||
|
|
@ -210,6 +253,12 @@ export const GalleryPage: React.FC<IProps> = ({ gallery, add }) => {
|
|||
>
|
||||
<FormattedMessage id="actions.reset_cover" />
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
className="bg-secondary text-white"
|
||||
onClick={() => onGenerate()}
|
||||
>
|
||||
<FormattedMessage id="actions.generate" />
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
className="bg-secondary text-white"
|
||||
onClick={() => setIsDeleteAlertOpen(true)}
|
||||
|
|
@ -387,6 +436,7 @@ export const GalleryPage: React.FC<IProps> = ({ gallery, add }) => {
|
|||
<title>{title}</title>
|
||||
</Helmet>
|
||||
{maybeRenderDeleteDialog()}
|
||||
{maybeRenderGenerateDialog()}
|
||||
<div className={`gallery-tabs ${collapsed ? "collapsed" : ""}`}>
|
||||
<div>
|
||||
<div className="gallery-header-container">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from "../GeneratePreviewOptions";
|
||||
|
||||
interface IGenerateOptions {
|
||||
type?: "scene" | "image";
|
||||
type?: "scene" | "image" | "gallery";
|
||||
selection?: boolean;
|
||||
options: GQL.GenerateMetadataInput;
|
||||
setOptions: (s: GQL.GenerateMetadataInput) => void;
|
||||
|
|
@ -27,7 +27,7 @@ export const GenerateOptions: React.FC<IGenerateOptions> = ({
|
|||
}
|
||||
|
||||
const showSceneOptions = !type || type === "scene";
|
||||
const showImageOptions = !type || type === "image";
|
||||
const showImageOptions = !type || type === "image" || type === "gallery";
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
Loading…
Reference in a new issue