Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and fix stash-box submit dialog #4355

Merged
merged 1 commit into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 153 additions & 119 deletions ui/v2.5/src/components/Dialogs/SubmitDraft.tsx
Original file line number Diff line number Diff line change
@@ -1,159 +1,193 @@
import React, { useState } from "react";
import { useMutation, DocumentNode } from "@apollo/client";
import { Button, Form } from "react-bootstrap";
import React, { useEffect, useState } from "react";
import { Form } from "react-bootstrap";
import * as GQL from "src/core/generated-graphql";
import {
mutateSubmitStashBoxPerformerDraft,
mutateSubmitStashBoxSceneDraft,
} from "src/core/StashService";
import { ModalComponent } from "src/components/Shared/Modal";
import { getStashboxBase } from "src/utils/stashbox";
import { FormattedMessage, useIntl } from "react-intl";
import { faPaperPlane } from "@fortawesome/free-solid-svg-icons";

interface IProps {
show: boolean;
entity: {
name?: string | null;
id: string;
title?: string | null;
stash_ids: { stash_id: string; endpoint: string }[];
};
type: "scene" | "performer";
entity: Pick<
GQL.SceneDataFragment | GQL.PerformerDataFragment,
"id" | "stash_ids"
>;
boxes: Pick<GQL.StashBox, "name" | "endpoint">[];
query: DocumentNode;
show: boolean;
onHide: () => void;
}

type Variables =
| GQL.SubmitStashBoxSceneDraftMutationVariables
| GQL.SubmitStashBoxPerformerDraftMutationVariables;
type Query =
| GQL.SubmitStashBoxSceneDraftMutation
| GQL.SubmitStashBoxPerformerDraftMutation;

const isSceneDraft = (
query: Query | null
): query is GQL.SubmitStashBoxSceneDraftMutation =>
(query as GQL.SubmitStashBoxSceneDraftMutation).submitStashBoxSceneDraft !==
undefined;

const getResponseId = (query: Query | null) =>
isSceneDraft(query)
? query.submitStashBoxSceneDraft
: query?.submitStashBoxPerformerDraft;

export const SubmitStashBoxDraft: React.FC<IProps> = ({
show,
type,
boxes,
entity,
query,
show,
onHide,
}) => {
const [submit, { data, error, loading }] = useMutation<Query, Variables>(
query
);
const [selectedBoxIndex, setSelectedBoxIndex] = useState(0);
const intl = useIntl();

const handleSubmit = () => {
submit({
variables: {
input: {
id: entity.id,
stash_box_index: selectedBoxIndex,
},
},
});
};
const [selectedBoxIndex, setSelectedBoxIndex] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>();
const [reviewUrl, setReviewUrl] = useState<string>();

const selectedBox =
boxes.length > selectedBoxIndex ? boxes[selectedBoxIndex] : undefined;
// this can be undefined, if e.g. boxes is empty
// since we aren't using noUncheckedIndexedAccess, add undefined explicitly
const selectedBox: (typeof boxes)[number] | undefined =
boxes[selectedBoxIndex];

const handleSelectBox = (e: React.ChangeEvent<HTMLSelectElement>) =>
setSelectedBoxIndex(Number.parseInt(e.currentTarget.value) ?? 0);
// #4354: reset state when shown, or if any props change
useEffect(() => {
if (show) {
setSelectedBoxIndex(0);
setLoading(false);
setError(undefined);
setReviewUrl(undefined);
}
}, [show, type, boxes, entity]);

if (!selectedBox) {
return <></>;
async function doSubmit() {
if (type === "scene") {
const r = await mutateSubmitStashBoxSceneDraft({
id: entity.id,
stash_box_index: selectedBoxIndex,
});
return r.data?.submitStashBoxSceneDraft;
} else if (type === "performer") {
const r = await mutateSubmitStashBoxPerformerDraft({
id: entity.id,
stash_box_index: selectedBoxIndex,
});
return r.data?.submitStashBoxPerformerDraft;
}
}

// If the scene has an attached stash_id from that endpoint, the operation will be an update
const isUpdate =
entity.stash_ids.find((id) => id.endpoint === selectedBox.endpoint) !==
undefined;
async function onSubmit() {
if (!selectedBox) return;

return (
<ModalComponent
icon={faPaperPlane}
header={intl.formatMessage({ id: "actions.submit_stash_box" })}
isRunning={loading}
show={show}
accept={{
onClick: onHide,
}}
>
{data === undefined ? (
try {
setLoading(true);
const responseId = await doSubmit();

const stashboxBase = getStashboxBase(selectedBox.endpoint);
if (responseId) {
setReviewUrl(`${stashboxBase}drafts/${responseId}`);
} else {
// if the mutation returned a null id but didn't error, then just link to the drafts page
setReviewUrl(`${stashboxBase}drafts`);
}
} catch (e) {
if (e instanceof Error && e.message) {
setError(e.message);
} else {
setError(String(e));
}
} finally {
setLoading(false);
}
}

function renderContents() {
if (error !== undefined) {
return (
<>
<Form.Group className="form-row align-items-end">
<Form.Label className="col-6">
<FormattedMessage id="stashbox.selected_stash_box" />:
</Form.Label>
<Form.Control
as="select"
onChange={handleSelectBox}
value={selectedBoxIndex}
className="col-6 input-control"
>
{boxes.map((box, i) => (
<option value={i} key={`${box.endpoint}-${i}`}>
{box.name}
</option>
))}
</Form.Control>
</Form.Group>
<div className="text-right">
{isUpdate && (
<span className="mr-2">
<FormattedMessage
id="stashbox.submit_update"
values={{ endpoint_name: boxes[selectedBoxIndex].name }}
/>
</span>
)}
<Button
onClick={handleSubmit}
variant={isUpdate ? "primary" : "success"}
>
<FormattedMessage
id={`actions.${isUpdate ? "submit_update" : "submit"}`}
/>{" "}
</Button>
</div>
<h6 className="mt-2">
<FormattedMessage id="stashbox.submission_failed" />
</h6>
<div>{error}</div>
</>
) : (
);
} else if (reviewUrl !== undefined) {
return (
<>
<h6>
<FormattedMessage id="stashbox.submission_successful" />
</h6>
<div>
<a
target="_blank"
rel="noreferrer noopener"
href={`${getStashboxBase(
boxes[selectedBoxIndex].endpoint
)}drafts/${getResponseId(data)}`}
>
<a target="_blank" rel="noreferrer noopener" href={reviewUrl}>
<FormattedMessage
id="stashbox.go_review_draft"
values={{ endpoint_name: boxes[selectedBoxIndex].name }}
values={{ endpoint_name: selectedBox?.name }}
/>
</a>
</div>
</>
)}
{error !== undefined && (
<>
<h6 className="mt-2">
<FormattedMessage id="stashbox.submission_failed" />
</h6>
<div>{error.message}</div>
</>
)}
);
} else {
return (
<Form.Group className="form-row align-items-end">
<Form.Label className="col-6">
<FormattedMessage id="stashbox.selected_stash_box" />:
</Form.Label>
<Form.Control
as="select"
onChange={(e) => setSelectedBoxIndex(Number(e.currentTarget.value))}
value={selectedBoxIndex}
className="col-6 input-control"
>
{boxes.map((box, i) => (
<option value={i} key={`${box.endpoint}-${i}`}>
{box.name}
</option>
))}
</Form.Control>
</Form.Group>
);
}
}

function getFooterProps() {
if (error !== undefined || reviewUrl !== undefined) {
return {
accept: {
onClick: () => onHide(),
},
};
}

// If the scene has an attached stash_id from that endpoint, the operation will be an update
const isUpdate =
entity.stash_ids.find((id) => id.endpoint === selectedBox?.endpoint) !==
undefined;

return {
footerButtons: isUpdate && !loading && (
<span className="mr-2 align-middle">
<FormattedMessage
id="stashbox.submit_update"
values={{ endpoint_name: selectedBox?.name }}
/>
</span>
),
accept: {
onClick: () => onSubmit(),
text: intl.formatMessage({
id: isUpdate ? "actions.submit_update" : "actions.submit",
}),
variant: isUpdate ? "primary" : "success",
},
cancel: {
onClick: () => onHide(),
variant: "secondary",
},
};
}

return (
<ModalComponent
icon={faPaperPlane}
header={intl.formatMessage({ id: "actions.submit_stash_box" })}
isRunning={loading}
show={show}
onHide={onHide}
disabled={!selectedBox}
{...getFooterProps()}
>
{renderContents()}
</ModalComponent>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export const PerformerSubmitButton: React.FC<IPerformerOperationsProps> = ({
<FormattedMessage id="actions.submit_stash_box" />
</Button>
<SubmitStashBoxDraft
type="performer"
boxes={boxes}
entity={performer}
query={GQL.SubmitStashBoxPerformerDraftDocument}
show={showDraftModal}
onHide={() => setShowDraftModal(false)}
/>
Expand Down
2 changes: 1 addition & 1 deletion ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -531,9 +531,9 @@ const ScenePage: React.FC<IProps> = ({
</Button>
</div>
<SubmitStashBoxDraft
type="scene"
boxes={boxes}
entity={scene}
query={GQL.SubmitStashBoxSceneDraftDocument}
show={showDraftModal}
onHide={() => setShowDraftModal(false)}
/>
Expand Down
3 changes: 2 additions & 1 deletion ui/v2.5/src/components/Shared/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from "react";
import { Button, Modal, Spinner, ModalProps } from "react-bootstrap";
import { ButtonVariant } from "react-bootstrap/types";
import { Icon } from "./Icon";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FormattedMessage } from "react-intl";

interface IButton {
text?: string;
variant?: "danger" | "primary" | "secondary";
variant?: ButtonVariant;
onClick?: () => void;
}

Expand Down
16 changes: 16 additions & 0 deletions ui/v2.5/src/core/StashService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1945,6 +1945,22 @@ export const queryScrapeGalleryURL = (url: string) =>
fetchPolicy: "network-only",
});

export const mutateSubmitStashBoxSceneDraft = (
input: GQL.StashBoxDraftSubmissionInput
) =>
client.mutate<GQL.SubmitStashBoxSceneDraftMutation>({
mutation: GQL.SubmitStashBoxSceneDraftDocument,
variables: { input },
});

export const mutateSubmitStashBoxPerformerDraft = (
input: GQL.StashBoxDraftSubmissionInput
) =>
client.mutate<GQL.SubmitStashBoxPerformerDraftMutation>({
mutation: GQL.SubmitStashBoxPerformerDraftDocument,
variables: { input },
});

/// Packages
export const useInstalledScraperPackages = GQL.useInstalledScraperPackagesQuery;
export const useInstalledScraperPackagesStatus =
Expand Down
Loading