Skip to content

Commit

Permalink
Refactor and fix stashbox submit dialog (#4355)
Browse files Browse the repository at this point in the history
  • Loading branch information
DingDongSoLong4 authored Dec 12, 2023
1 parent d37de0e commit 74ddfa4
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 122 deletions.
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

0 comments on commit 74ddfa4

Please sign in to comment.