mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Organised flag (#988)
* Add organized boolean to scene model (#729) * Add organized button to scene page * Add flag to galleries and images * Import/export changes * Make organized flag not null * Ignore organized scenes for autotag Co-authored-by: com1234 <com1234@notarealemail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
### ✨ New Features
|
||||
* Add organized flag for scenes, galleries and images.
|
||||
* Allow configuration of visible navbar items.
|
||||
|
||||
### 🎨 Improvements
|
||||
|
||||
@@ -28,12 +28,15 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
|
||||
GQL.BulkUpdateIdMode.Add
|
||||
);
|
||||
const [tagIds, setTagIds] = useState<string[]>();
|
||||
const [organized, setOrganized] = useState<boolean | undefined>();
|
||||
|
||||
const [updateGalleries] = useBulkGalleryUpdate();
|
||||
|
||||
// Network state
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
const checkboxRef = React.createRef<HTMLInputElement>();
|
||||
|
||||
function makeBulkUpdateIds(
|
||||
ids: string[],
|
||||
mode: GQL.BulkUpdateIdMode
|
||||
@@ -119,6 +122,10 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
|
||||
galleryInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode);
|
||||
}
|
||||
|
||||
if (organized !== undefined) {
|
||||
galleryInput.organized = organized;
|
||||
}
|
||||
|
||||
return galleryInput;
|
||||
}
|
||||
|
||||
@@ -223,6 +230,7 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
|
||||
let updateStudioID: string | undefined;
|
||||
let updatePerformerIds: string[] = [];
|
||||
let updateTagIds: string[] = [];
|
||||
let updateOrganized: boolean | undefined;
|
||||
let first = true;
|
||||
|
||||
state.forEach((gallery: GQL.GallerySlimDataFragment) => {
|
||||
@@ -238,6 +246,7 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
|
||||
updateStudioID = GalleriestudioID;
|
||||
updatePerformerIds = galleryPerformerIDs;
|
||||
updateTagIds = galleryTagIDs;
|
||||
updateOrganized = gallery.organized;
|
||||
first = false;
|
||||
} else {
|
||||
if (galleryRating !== updateRating) {
|
||||
@@ -252,6 +261,9 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
|
||||
if (!_.isEqual(galleryTagIDs, updateTagIds)) {
|
||||
updateTagIds = [];
|
||||
}
|
||||
if (gallery.organized !== updateOrganized) {
|
||||
updateOrganized = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -264,8 +276,16 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
|
||||
if (tagMode === GQL.BulkUpdateIdMode.Set) {
|
||||
setTagIds(updateTagIds);
|
||||
}
|
||||
|
||||
setOrganized(updateOrganized);
|
||||
}, [props.selected, performerMode, tagMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkboxRef.current) {
|
||||
checkboxRef.current.indeterminate = organized === undefined;
|
||||
}
|
||||
}, [organized, checkboxRef]);
|
||||
|
||||
function renderMultiSelect(
|
||||
type: "performers" | "tags",
|
||||
ids: string[] | undefined
|
||||
@@ -311,6 +331,16 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
|
||||
);
|
||||
}
|
||||
|
||||
function cycleOrganized() {
|
||||
if (organized) {
|
||||
setOrganized(undefined);
|
||||
} else if (organized === undefined) {
|
||||
setOrganized(false);
|
||||
} else {
|
||||
setOrganized(true);
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
return (
|
||||
<Modal
|
||||
@@ -359,10 +389,20 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
|
||||
{renderMultiSelect("performers", performerIds)}
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="performers">
|
||||
<Form.Group controlId="tags">
|
||||
<Form.Label>Tags</Form.Label>
|
||||
{renderMultiSelect("tags", tagIds)}
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="organized">
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
label="Organized"
|
||||
checked={organized}
|
||||
ref={checkboxRef}
|
||||
onChange={() => cycleOrganized()}
|
||||
/>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -104,11 +104,24 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderOrganized() {
|
||||
if (props.gallery.organized) {
|
||||
return (
|
||||
<div>
|
||||
<Button className="minimal">
|
||||
<Icon icon="box" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRenderPopoverButtonGroup() {
|
||||
if (
|
||||
props.gallery.scene ||
|
||||
props.gallery.performers.length > 0 ||
|
||||
props.gallery.tags.length > 0
|
||||
props.gallery.tags.length > 0 ||
|
||||
props.gallery.organized
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
@@ -117,6 +130,7 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
||||
{maybeRenderTagPopoverButton()}
|
||||
{maybeRenderPerformerPopoverButton()}
|
||||
{maybeRenderScenePopoverButton()}
|
||||
{maybeRenderOrganized()}
|
||||
</ButtonGroup>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Tab, Nav, Dropdown } from "react-bootstrap";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useParams, useHistory, Link } from "react-router-dom";
|
||||
import { useFindGallery } from "src/core/StashService";
|
||||
import { useFindGallery, useGalleryUpdate } from "src/core/StashService";
|
||||
import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared";
|
||||
import { TextUtils } from "src/utils";
|
||||
import * as Mousetrap from "mousetrap";
|
||||
import { useToast } from "src/hooks";
|
||||
import { OrganizedButton } from "src/components/Scenes/SceneDetails/OrganizedButton";
|
||||
import { GalleryEditPanel } from "./GalleryEditPanel";
|
||||
import { GalleryDetailPanel } from "./GalleryDetailPanel";
|
||||
import { DeleteGalleriesDialog } from "../DeleteGalleriesDialog";
|
||||
@@ -20,6 +22,7 @@ interface IGalleryParams {
|
||||
export const Gallery: React.FC = () => {
|
||||
const { tab = "images", id = "new" } = useParams<IGalleryParams>();
|
||||
const history = useHistory();
|
||||
const Toast = useToast();
|
||||
const isNew = id === "new";
|
||||
|
||||
const { data, error, loading } = useFindGallery(id);
|
||||
@@ -34,6 +37,28 @@ export const Gallery: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const [updateGallery] = useGalleryUpdate();
|
||||
|
||||
const [organizedLoading, setOrganizedLoading] = useState(false);
|
||||
|
||||
const onOrganizedClick = async () => {
|
||||
try {
|
||||
setOrganizedLoading(true);
|
||||
await updateGallery({
|
||||
variables: {
|
||||
input: {
|
||||
id: gallery?.id ?? "",
|
||||
organized: !gallery?.organized,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
} finally {
|
||||
setOrganizedLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
|
||||
|
||||
function onDeleteDialogClosed(deleted: boolean) {
|
||||
@@ -103,7 +128,14 @@ export const Gallery: React.FC = () => {
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="gallery-edit-panel">Edit</Nav.Link>
|
||||
</Nav.Item>
|
||||
<Nav.Item className="ml-auto">{renderOperations()}</Nav.Item>
|
||||
<Nav.Item className="ml-auto">
|
||||
<OrganizedButton
|
||||
loading={organizedLoading}
|
||||
organized={gallery.organized}
|
||||
onClick={onOrganizedClick}
|
||||
/>
|
||||
</Nav.Item>
|
||||
<Nav.Item>{renderOperations()}</Nav.Item>
|
||||
</Nav>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -28,12 +28,15 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
|
||||
GQL.BulkUpdateIdMode.Add
|
||||
);
|
||||
const [tagIds, setTagIds] = useState<string[]>();
|
||||
const [organized, setOrganized] = useState<boolean | undefined>();
|
||||
|
||||
const [updateImages] = useBulkImageUpdate();
|
||||
|
||||
// Network state
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
const checkboxRef = React.createRef<HTMLInputElement>();
|
||||
|
||||
function makeBulkUpdateIds(
|
||||
ids: string[],
|
||||
mode: GQL.BulkUpdateIdMode
|
||||
@@ -119,6 +122,10 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
|
||||
imageInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode);
|
||||
}
|
||||
|
||||
if (organized !== undefined) {
|
||||
imageInput.organized = organized;
|
||||
}
|
||||
|
||||
return imageInput;
|
||||
}
|
||||
|
||||
@@ -221,6 +228,7 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
|
||||
let updateStudioID: string | undefined;
|
||||
let updatePerformerIds: string[] = [];
|
||||
let updateTagIds: string[] = [];
|
||||
let updateOrganized: boolean | undefined;
|
||||
let first = true;
|
||||
|
||||
state.forEach((image: GQL.SlimImageDataFragment) => {
|
||||
@@ -236,6 +244,7 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
|
||||
updateStudioID = imageStudioID;
|
||||
updatePerformerIds = imagePerformerIDs;
|
||||
updateTagIds = imageTagIDs;
|
||||
updateOrganized = image.organized;
|
||||
first = false;
|
||||
} else {
|
||||
if (imageRating !== updateRating) {
|
||||
@@ -250,6 +259,9 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
|
||||
if (!_.isEqual(imageTagIDs, updateTagIds)) {
|
||||
updateTagIds = [];
|
||||
}
|
||||
if (image.organized !== updateOrganized) {
|
||||
updateOrganized = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -262,8 +274,15 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
|
||||
if (tagMode === GQL.BulkUpdateIdMode.Set) {
|
||||
setTagIds(updateTagIds);
|
||||
}
|
||||
setOrganized(updateOrganized);
|
||||
}, [props.selected, performerMode, tagMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkboxRef.current) {
|
||||
checkboxRef.current.indeterminate = organized === undefined;
|
||||
}
|
||||
}, [organized, checkboxRef]);
|
||||
|
||||
function renderMultiSelect(
|
||||
type: "performers" | "tags",
|
||||
ids: string[] | undefined
|
||||
@@ -309,6 +328,16 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
|
||||
);
|
||||
}
|
||||
|
||||
function cycleOrganized() {
|
||||
if (organized) {
|
||||
setOrganized(undefined);
|
||||
} else if (organized === undefined) {
|
||||
setOrganized(false);
|
||||
} else {
|
||||
setOrganized(true);
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
return (
|
||||
<Modal
|
||||
@@ -357,10 +386,20 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
|
||||
{renderMultiSelect("performers", performerIds)}
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="performers">
|
||||
<Form.Group controlId="tags">
|
||||
<Form.Label>Tags</Form.Label>
|
||||
{renderMultiSelect("tags", tagIds)}
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="organized">
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
label="Organized"
|
||||
checked={organized}
|
||||
ref={checkboxRef}
|
||||
onChange={() => cycleOrganized()}
|
||||
/>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -93,11 +93,24 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRenderOrganized() {
|
||||
if (props.image.organized) {
|
||||
return (
|
||||
<div>
|
||||
<Button className="minimal">
|
||||
<Icon icon="box" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRenderPopoverButtonGroup() {
|
||||
if (
|
||||
props.image.tags.length > 0 ||
|
||||
props.image.performers.length > 0 ||
|
||||
props.image?.o_counter
|
||||
props.image.o_counter ||
|
||||
props.image.organized
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
@@ -106,6 +119,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
||||
{maybeRenderTagPopoverButton()}
|
||||
{maybeRenderPerformerPopoverButton()}
|
||||
{maybeRenderOCounter()}
|
||||
{maybeRenderOrganized()}
|
||||
</ButtonGroup>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -6,12 +6,14 @@ import {
|
||||
useImageIncrementO,
|
||||
useImageDecrementO,
|
||||
useImageResetO,
|
||||
useImageUpdate,
|
||||
} from "src/core/StashService";
|
||||
import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
import { TextUtils } from "src/utils";
|
||||
import * as Mousetrap from "mousetrap";
|
||||
import { OCounterButton } from "src/components/Scenes/SceneDetails/OCounterButton";
|
||||
import { OrganizedButton } from "src/components/Scenes/SceneDetails/OrganizedButton";
|
||||
import { ImageFileInfoPanel } from "./ImageFileInfoPanel";
|
||||
import { ImageEditPanel } from "./ImageEditPanel";
|
||||
import { ImageDetailPanel } from "./ImageDetailPanel";
|
||||
@@ -33,10 +35,32 @@ export const Image: React.FC = () => {
|
||||
const [decrementO] = useImageDecrementO(image?.id ?? "0");
|
||||
const [resetO] = useImageResetO(image?.id ?? "0");
|
||||
|
||||
const [updateImage] = useImageUpdate();
|
||||
|
||||
const [organizedLoading, setOrganizedLoading] = useState(false);
|
||||
|
||||
const [activeTabKey, setActiveTabKey] = useState("image-details-panel");
|
||||
|
||||
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
|
||||
|
||||
const onOrganizedClick = async () => {
|
||||
try {
|
||||
setOrganizedLoading(true);
|
||||
await updateImage({
|
||||
variables: {
|
||||
input: {
|
||||
id: image?.id ?? "",
|
||||
organized: !image?.organized,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
} finally {
|
||||
setOrganizedLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onIncrementClick = async () => {
|
||||
try {
|
||||
setOLoading(true);
|
||||
@@ -139,6 +163,13 @@ export const Image: React.FC = () => {
|
||||
onReset={onResetClick}
|
||||
/>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<OrganizedButton
|
||||
loading={organizedLoading}
|
||||
organized={image.organized}
|
||||
onClick={onOrganizedClick}
|
||||
/>
|
||||
</Nav.Item>
|
||||
<Nav.Item>{renderOperations()}</Nav.Item>
|
||||
</Nav>
|
||||
</div>
|
||||
|
||||
@@ -28,12 +28,15 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||
GQL.BulkUpdateIdMode.Add
|
||||
);
|
||||
const [tagIds, setTagIds] = useState<string[]>();
|
||||
const [organized, setOrganized] = useState<boolean | undefined>();
|
||||
|
||||
const [updateScenes] = useBulkSceneUpdate(getSceneInput());
|
||||
|
||||
// Network state
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
const checkboxRef = React.createRef<HTMLInputElement>();
|
||||
|
||||
function makeBulkUpdateIds(
|
||||
ids: string[],
|
||||
mode: GQL.BulkUpdateIdMode
|
||||
@@ -119,6 +122,10 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||
sceneInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode);
|
||||
}
|
||||
|
||||
if (organized !== undefined) {
|
||||
sceneInput.organized = organized;
|
||||
}
|
||||
|
||||
return sceneInput;
|
||||
}
|
||||
|
||||
@@ -217,6 +224,7 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||
let updateStudioID: string | undefined;
|
||||
let updatePerformerIds: string[] = [];
|
||||
let updateTagIds: string[] = [];
|
||||
let updateOrganized: boolean | undefined;
|
||||
let first = true;
|
||||
|
||||
state.forEach((scene: GQL.SlimSceneDataFragment) => {
|
||||
@@ -233,6 +241,7 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||
updatePerformerIds = scenePerformerIDs;
|
||||
updateTagIds = sceneTagIDs;
|
||||
first = false;
|
||||
updateOrganized = scene.organized;
|
||||
} else {
|
||||
if (sceneRating !== updateRating) {
|
||||
updateRating = undefined;
|
||||
@@ -246,6 +255,9 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||
if (!_.isEqual(sceneTagIDs, updateTagIds)) {
|
||||
updateTagIds = [];
|
||||
}
|
||||
if (scene.organized !== updateOrganized) {
|
||||
updateOrganized = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -258,8 +270,15 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||
if (tagMode === GQL.BulkUpdateIdMode.Set) {
|
||||
setTagIds(updateTagIds);
|
||||
}
|
||||
setOrganized(updateOrganized);
|
||||
}, [props.selected, performerMode, tagMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkboxRef.current) {
|
||||
checkboxRef.current.indeterminate = organized === undefined;
|
||||
}
|
||||
}, [organized, checkboxRef]);
|
||||
|
||||
function renderMultiSelect(
|
||||
type: "performers" | "tags",
|
||||
ids: string[] | undefined
|
||||
@@ -305,6 +324,16 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||
);
|
||||
}
|
||||
|
||||
function cycleOrganized() {
|
||||
if (organized) {
|
||||
setOrganized(undefined);
|
||||
} else if (organized === undefined) {
|
||||
setOrganized(false);
|
||||
} else {
|
||||
setOrganized(true);
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
return (
|
||||
<Modal
|
||||
@@ -353,10 +382,20 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||
{renderMultiSelect("performers", performerIds)}
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="performers">
|
||||
<Form.Group controlId="tags">
|
||||
<Form.Label>Tags</Form.Label>
|
||||
{renderMultiSelect("tags", tagIds)}
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="organized">
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
label="Organized"
|
||||
checked={organized}
|
||||
ref={checkboxRef}
|
||||
onChange={() => cycleOrganized()}
|
||||
/>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -267,6 +267,18 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRenderOrganized() {
|
||||
if (props.scene.organized) {
|
||||
return (
|
||||
<div>
|
||||
<Button className="minimal">
|
||||
<Icon icon="box" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRenderPopoverButtonGroup() {
|
||||
if (
|
||||
props.scene.tags.length > 0 ||
|
||||
@@ -274,7 +286,8 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
props.scene.movies.length > 0 ||
|
||||
props.scene.scene_markers.length > 0 ||
|
||||
props.scene?.o_counter ||
|
||||
props.scene.gallery
|
||||
props.scene.gallery ||
|
||||
props.scene.organized
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
@@ -286,6 +299,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
{maybeRenderSceneMarkerPopoverButton()}
|
||||
{maybeRenderOCounter()}
|
||||
{maybeRenderGallery()}
|
||||
{maybeRenderOrganized()}
|
||||
</ButtonGroup>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
import cx from "classnames";
|
||||
import { Button, Spinner } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
|
||||
export interface IOrganizedButtonProps {
|
||||
loading: boolean;
|
||||
organized: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const OrganizedButton: React.FC<IOrganizedButtonProps> = (
|
||||
props: IOrganizedButtonProps
|
||||
) => {
|
||||
const intl = useIntl();
|
||||
const messages = defineMessages({
|
||||
organized: {
|
||||
id: "organized",
|
||||
defaultMessage: "Organized",
|
||||
},
|
||||
});
|
||||
|
||||
if (props.loading) return <Spinner animation="border" role="status" />;
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="secondary"
|
||||
title={intl.formatMessage(messages.organized)}
|
||||
className={cx(
|
||||
"minimal",
|
||||
"organized-button",
|
||||
props.organized ? "organized" : "not-organized"
|
||||
)}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<Icon icon="box" />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
useSceneResetO,
|
||||
useSceneStreams,
|
||||
useSceneGenerateScreenshot,
|
||||
useSceneUpdate,
|
||||
} from "src/core/StashService";
|
||||
import { GalleryViewer } from "src/components/Galleries/GalleryViewer";
|
||||
import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared";
|
||||
@@ -26,6 +27,7 @@ import { SceneMoviePanel } from "./SceneMoviePanel";
|
||||
import { DeleteScenesDialog } from "../DeleteScenesDialog";
|
||||
import { SceneGenerateDialog } from "../SceneGenerateDialog";
|
||||
import { SceneVideoFilterPanel } from "./SceneVideoFilterPanel";
|
||||
import { OrganizedButton } from "./OrganizedButton";
|
||||
|
||||
interface ISceneParams {
|
||||
id?: string;
|
||||
@@ -36,6 +38,7 @@ export const Scene: React.FC = () => {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const Toast = useToast();
|
||||
const [updateScene] = useSceneUpdate();
|
||||
const [generateScreenshot] = useSceneGenerateScreenshot();
|
||||
const [timestamp, setTimestamp] = useState<number>(getInitialTimestamp());
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
@@ -52,6 +55,8 @@ export const Scene: React.FC = () => {
|
||||
const [decrementO] = useSceneDecrementO(scene?.id ?? "0");
|
||||
const [resetO] = useSceneResetO(scene?.id ?? "0");
|
||||
|
||||
const [organizedLoading, setOrganizedLoading] = useState(false);
|
||||
|
||||
const [activeTabKey, setActiveTabKey] = useState("scene-details-panel");
|
||||
|
||||
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
|
||||
@@ -69,6 +74,24 @@ export const Scene: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const onOrganizedClick = async () => {
|
||||
try {
|
||||
setOrganizedLoading(true);
|
||||
await updateScene({
|
||||
variables: {
|
||||
input: {
|
||||
id: scene?.id ?? "",
|
||||
organized: !scene?.organized,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
} finally {
|
||||
setOrganizedLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onIncrementClick = async () => {
|
||||
try {
|
||||
setOLoading(true);
|
||||
@@ -246,6 +269,13 @@ export const Scene: React.FC = () => {
|
||||
onReset={onResetClick}
|
||||
/>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<OrganizedButton
|
||||
loading={organizedLoading}
|
||||
organized={scene.organized}
|
||||
onClick={onOrganizedClick}
|
||||
/>
|
||||
</Nav.Item>
|
||||
<Nav.Item>{renderOperations()}</Nav.Item>
|
||||
</ButtonGroup>
|
||||
</Nav>
|
||||
|
||||
@@ -70,7 +70,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
||||
// Network state
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [updateScene] = useSceneUpdate(getSceneInput());
|
||||
const [updateScene] = useSceneUpdate();
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isVisible) {
|
||||
@@ -230,7 +230,11 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
||||
async function onSave() {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await updateScene();
|
||||
const result = await updateScene({
|
||||
variables: {
|
||||
input: getSceneInput(),
|
||||
},
|
||||
});
|
||||
if (result.data?.sceneUpdate) {
|
||||
Toast.success({ content: "Updated scene" });
|
||||
}
|
||||
|
||||
@@ -535,3 +535,13 @@ input[type="range"].blue-slider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.organized-button {
|
||||
&.not-organized {
|
||||
color: rgba(191, 204, 214, 0.5);
|
||||
}
|
||||
|
||||
&.organized {
|
||||
color: #664c3f;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user