mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
Add Movie option to Scene bulk edit (#1676)
* Add Movie option to Scene bulk edit
This commit is contained in:
parent
7a468413da
commit
b2b05fb332
5 changed files with 126 additions and 3 deletions
|
|
@ -103,6 +103,7 @@ input BulkSceneUpdateInput {
|
||||||
gallery_ids: BulkUpdateIds
|
gallery_ids: BulkUpdateIds
|
||||||
performer_ids: BulkUpdateIds
|
performer_ids: BulkUpdateIds
|
||||||
tag_ids: BulkUpdateIds
|
tag_ids: BulkUpdateIds
|
||||||
|
movie_ids: BulkUpdateIds
|
||||||
}
|
}
|
||||||
|
|
||||||
input SceneDestroyInput {
|
input SceneDestroyInput {
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,18 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the movies
|
||||||
|
if translator.hasField("movie_ids") {
|
||||||
|
movies, err := adjustSceneMovieIDs(qb, sceneID, *input.MovieIds)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := qb.UpdateMovies(sceneID, movies); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -395,6 +407,48 @@ func adjustSceneGalleryIDs(qb models.SceneReader, sceneID int, ids models.BulkUp
|
||||||
return adjustIDs(ret, ids), nil
|
return adjustIDs(ret, ids), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func adjustSceneMovieIDs(qb models.SceneReader, sceneID int, updateIDs models.BulkUpdateIds) ([]models.MoviesScenes, error) {
|
||||||
|
existingMovies, err := qb.GetMovies(sceneID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are setting the ids, just return the ids
|
||||||
|
if updateIDs.Mode == models.BulkUpdateIDModeSet {
|
||||||
|
existingMovies = []models.MoviesScenes{}
|
||||||
|
for _, idStr := range updateIDs.Ids {
|
||||||
|
id, _ := strconv.Atoi(idStr)
|
||||||
|
existingMovies = append(existingMovies, models.MoviesScenes{MovieID: id})
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingMovies, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, idStr := range updateIDs.Ids {
|
||||||
|
id, _ := strconv.Atoi(idStr)
|
||||||
|
|
||||||
|
// look for the id in the list
|
||||||
|
foundExisting := false
|
||||||
|
for idx, existingMovie := range existingMovies {
|
||||||
|
if existingMovie.MovieID == id {
|
||||||
|
if updateIDs.Mode == models.BulkUpdateIDModeRemove {
|
||||||
|
// remove from the list
|
||||||
|
existingMovies = append(existingMovies[:idx], existingMovies[idx+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
foundExisting = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundExisting && updateIDs.Mode != models.BulkUpdateIDModeRemove {
|
||||||
|
existingMovies = append(existingMovies, models.MoviesScenes{MovieID: id})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingMovies, err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneDestroyInput) (bool, error) {
|
func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneDestroyInput) (bool, error) {
|
||||||
sceneID, err := strconv.Atoi(input.ID)
|
sceneID, err := strconv.Atoi(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
### ✨ New Features
|
### ✨ New Features
|
||||||
|
* Added Movies to Scene bulk edit dialog. ([#1676](https://github.com/stashapp/stash/pull/1676))
|
||||||
* Added Movies tab to Studio and Performer pages. ([#1675](https://github.com/stashapp/stash/pull/1675))
|
* Added Movies tab to Studio and Performer pages. ([#1675](https://github.com/stashapp/stash/pull/1675))
|
||||||
* Support filtering Movies by Performers. ([#1675](https://github.com/stashapp/stash/pull/1675))
|
* Support filtering Movies by Performers. ([#1675](https://github.com/stashapp/stash/pull/1675))
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
);
|
);
|
||||||
const [tagIds, setTagIds] = useState<string[]>();
|
const [tagIds, setTagIds] = useState<string[]>();
|
||||||
const [existingTagIds, setExistingTagIds] = useState<string[]>();
|
const [existingTagIds, setExistingTagIds] = useState<string[]>();
|
||||||
|
const [movieMode, setMovieMode] = React.useState<GQL.BulkUpdateIdMode>(
|
||||||
|
GQL.BulkUpdateIdMode.Add
|
||||||
|
);
|
||||||
|
const [movieIds, setMovieIds] = useState<string[]>();
|
||||||
|
const [existingMovieIds, setExistingMovieIds] = useState<string[]>();
|
||||||
const [organized, setOrganized] = useState<boolean | undefined>();
|
const [organized, setOrganized] = useState<boolean | undefined>();
|
||||||
|
|
||||||
const [updateScenes] = useBulkSceneUpdate(getSceneInput());
|
const [updateScenes] = useBulkSceneUpdate(getSceneInput());
|
||||||
|
|
@ -58,6 +63,7 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
const aggregateStudioId = getStudioId(props.selected);
|
const aggregateStudioId = getStudioId(props.selected);
|
||||||
const aggregatePerformerIds = getPerformerIds(props.selected);
|
const aggregatePerformerIds = getPerformerIds(props.selected);
|
||||||
const aggregateTagIds = getTagIds(props.selected);
|
const aggregateTagIds = getTagIds(props.selected);
|
||||||
|
const aggregateMovieIds = getMovieIds(props.selected);
|
||||||
|
|
||||||
const sceneInput: GQL.BulkSceneUpdateInput = {
|
const sceneInput: GQL.BulkSceneUpdateInput = {
|
||||||
ids: props.selected.map((scene) => {
|
ids: props.selected.map((scene) => {
|
||||||
|
|
@ -127,6 +133,21 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
sceneInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode);
|
sceneInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if movieIds non-empty, then we are setting them
|
||||||
|
if (
|
||||||
|
movieMode === GQL.BulkUpdateIdMode.Set &&
|
||||||
|
(!movieIds || movieIds.length === 0)
|
||||||
|
) {
|
||||||
|
// and all scenes have the same ids,
|
||||||
|
if (aggregateMovieIds.length > 0) {
|
||||||
|
// then unset the movieIds, otherwise ignore
|
||||||
|
sceneInput.movie_ids = makeBulkUpdateIds(movieIds || [], movieMode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if movieIds non-empty, then we are setting them
|
||||||
|
sceneInput.movie_ids = makeBulkUpdateIds(movieIds || [], movieMode);
|
||||||
|
}
|
||||||
|
|
||||||
if (organized !== undefined) {
|
if (organized !== undefined) {
|
||||||
sceneInput.organized = organized;
|
sceneInput.organized = organized;
|
||||||
}
|
}
|
||||||
|
|
@ -228,12 +249,35 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMovieIds(state: GQL.SlimSceneDataFragment[]) {
|
||||||
|
let ret: string[] = [];
|
||||||
|
let first = true;
|
||||||
|
|
||||||
|
state.forEach((scene: GQL.SlimSceneDataFragment) => {
|
||||||
|
if (first) {
|
||||||
|
ret = scene.movies ? scene.movies.map((m) => m.movie.id).sort() : [];
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
const mIds = scene.movies
|
||||||
|
? scene.movies.map((m) => m.movie.id).sort()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (!_.isEqual(ret, mIds)) {
|
||||||
|
ret = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const state = props.selected;
|
const state = props.selected;
|
||||||
let updateRating: number | undefined;
|
let updateRating: number | undefined;
|
||||||
let updateStudioID: string | undefined;
|
let updateStudioID: string | undefined;
|
||||||
let updatePerformerIds: string[] = [];
|
let updatePerformerIds: string[] = [];
|
||||||
let updateTagIds: string[] = [];
|
let updateTagIds: string[] = [];
|
||||||
|
let updateMovieIds: string[] = [];
|
||||||
let updateOrganized: boolean | undefined;
|
let updateOrganized: boolean | undefined;
|
||||||
let first = true;
|
let first = true;
|
||||||
|
|
||||||
|
|
@ -244,12 +288,14 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
.map((p) => p.id)
|
.map((p) => p.id)
|
||||||
.sort();
|
.sort();
|
||||||
const sceneTagIDs = (scene.tags ?? []).map((p) => p.id).sort();
|
const sceneTagIDs = (scene.tags ?? []).map((p) => p.id).sort();
|
||||||
|
const sceneMovieIDs = (scene.movies ?? []).map((m) => m.movie.id).sort();
|
||||||
|
|
||||||
if (first) {
|
if (first) {
|
||||||
updateRating = sceneRating ?? undefined;
|
updateRating = sceneRating ?? undefined;
|
||||||
updateStudioID = sceneStudioID;
|
updateStudioID = sceneStudioID;
|
||||||
updatePerformerIds = scenePerformerIDs;
|
updatePerformerIds = scenePerformerIDs;
|
||||||
updateTagIds = sceneTagIDs;
|
updateTagIds = sceneTagIDs;
|
||||||
|
updateMovieIds = sceneMovieIDs;
|
||||||
first = false;
|
first = false;
|
||||||
updateOrganized = scene.organized;
|
updateOrganized = scene.organized;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -265,6 +311,9 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
if (!_.isEqual(sceneTagIDs, updateTagIds)) {
|
if (!_.isEqual(sceneTagIDs, updateTagIds)) {
|
||||||
updateTagIds = [];
|
updateTagIds = [];
|
||||||
}
|
}
|
||||||
|
if (!_.isEqual(sceneMovieIDs, updateMovieIds)) {
|
||||||
|
updateMovieIds = [];
|
||||||
|
}
|
||||||
if (scene.organized !== updateOrganized) {
|
if (scene.organized !== updateOrganized) {
|
||||||
updateOrganized = undefined;
|
updateOrganized = undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -275,8 +324,9 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
setStudioId(updateStudioID);
|
setStudioId(updateStudioID);
|
||||||
setExistingPerformerIds(updatePerformerIds);
|
setExistingPerformerIds(updatePerformerIds);
|
||||||
setExistingTagIds(updateTagIds);
|
setExistingTagIds(updateTagIds);
|
||||||
|
setExistingMovieIds(updateMovieIds);
|
||||||
setOrganized(updateOrganized);
|
setOrganized(updateOrganized);
|
||||||
}, [props.selected, performerMode, tagMode]);
|
}, [props.selected, performerMode, tagMode, movieMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (checkboxRef.current) {
|
if (checkboxRef.current) {
|
||||||
|
|
@ -285,7 +335,7 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
}, [organized, checkboxRef]);
|
}, [organized, checkboxRef]);
|
||||||
|
|
||||||
function renderMultiSelect(
|
function renderMultiSelect(
|
||||||
type: "performers" | "tags",
|
type: "performers" | "tags" | "movies",
|
||||||
ids: string[] | undefined
|
ids: string[] | undefined
|
||||||
) {
|
) {
|
||||||
let mode = GQL.BulkUpdateIdMode.Add;
|
let mode = GQL.BulkUpdateIdMode.Add;
|
||||||
|
|
@ -299,6 +349,10 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
mode = tagMode;
|
mode = tagMode;
|
||||||
existingIds = existingTagIds;
|
existingIds = existingTagIds;
|
||||||
break;
|
break;
|
||||||
|
case "movies":
|
||||||
|
mode = movieMode;
|
||||||
|
existingIds = existingMovieIds;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -313,6 +367,9 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
case "tags":
|
case "tags":
|
||||||
setTagIds(itemIDs);
|
setTagIds(itemIDs);
|
||||||
break;
|
break;
|
||||||
|
case "movies":
|
||||||
|
setMovieIds(itemIDs);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onSetMode={(newMode) => {
|
onSetMode={(newMode) => {
|
||||||
|
|
@ -323,6 +380,9 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
case "tags":
|
case "tags":
|
||||||
setTagMode(newMode);
|
setTagMode(newMode);
|
||||||
break;
|
break;
|
||||||
|
case "movies":
|
||||||
|
setMovieMode(newMode);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
ids={ids ?? []}
|
ids={ids ?? []}
|
||||||
|
|
@ -409,6 +469,13 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
||||||
{renderMultiSelect("tags", tagIds)}
|
{renderMultiSelect("tags", tagIds)}
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group controlId="movies">
|
||||||
|
<Form.Label>
|
||||||
|
<FormattedMessage id="movies" />
|
||||||
|
</Form.Label>
|
||||||
|
{renderMultiSelect("movies", movieIds)}
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
<Form.Group controlId="organized">
|
<Form.Group controlId="organized">
|
||||||
<Form.Check
|
<Form.Check
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ type ValidTypes =
|
||||||
| GQL.SlimMovieDataFragment;
|
| GQL.SlimMovieDataFragment;
|
||||||
|
|
||||||
interface IMultiSetProps {
|
interface IMultiSetProps {
|
||||||
type: "performers" | "studios" | "tags";
|
type: "performers" | "studios" | "tags" | "movies";
|
||||||
existingIds?: string[];
|
existingIds?: string[];
|
||||||
ids?: string[];
|
ids?: string[];
|
||||||
mode: GQL.BulkUpdateIdMode;
|
mode: GQL.BulkUpdateIdMode;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue