From 27396968131d969109c713d29b5db5f8cbf6abec Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:59:40 +1000 Subject: [PATCH] Add group graphql interfaces (#5017) * Deprecate movie and add group interfaces * UI changes --- gqlgen.yml | 5 + graphql/schema/schema.graphql | 37 +- graphql/schema/types/filters.graphql | 49 ++- graphql/schema/types/group.graphql | 80 +++++ graphql/schema/types/metadata.graphql | 3 +- graphql/schema/types/performer.graphql | 6 +- graphql/schema/types/scene.graphql | 22 +- ...ed-movie.graphql => scraped-group.graphql} | 32 ++ graphql/schema/types/scraper.graphql | 20 +- graphql/schema/types/stats.graphql | 3 +- graphql/schema/types/studio.graphql | 6 +- graphql/schema/types/tag.graphql | 3 +- internal/api/changeset_translator.go | 43 +++ internal/api/resolver.go | 14 +- internal/api/resolver_model_performer.go | 14 +- internal/api/resolver_model_scene.go | 31 ++ internal/api/resolver_model_studio.go | 14 +- internal/api/resolver_model_tag.go | 6 +- internal/api/resolver_mutation_group.go | 335 ++++++++++++++++++ internal/api/resolver_mutation_movie.go | 10 + internal/api/resolver_mutation_scene.go | 40 ++- internal/api/resolver_query_find_group.go | 59 +++ internal/api/resolver_query_scraper.go | 37 ++ internal/manager/task_export.go | 27 +- pkg/models/model_saved_filter.go | 4 +- pkg/models/model_scraped_item.go | 21 ++ pkg/models/scene.go | 12 + pkg/models/tag.go | 2 + pkg/plugin/hook/hooks.go | 6 + pkg/scraper/config.go | 5 +- pkg/scraper/group.go | 2 +- pkg/scraper/json.go | 2 +- pkg/scraper/scene.go | 1 + pkg/scraper/scraper.go | 6 +- pkg/scraper/script.go | 2 +- pkg/scraper/xpath.go | 2 +- pkg/sqlite/gallery_test.go | 2 +- pkg/sqlite/image_test.go | 2 +- pkg/sqlite/performer_test.go | 2 +- pkg/sqlite/saved_filter.go | 19 +- pkg/sqlite/scene.go | 3 +- pkg/sqlite/scene_filter.go | 7 +- pkg/sqlite/setup_test.go | 13 +- pkg/sqlite/tag.go | 2 +- pkg/sqlite/tag_filter.go | 7 +- ...{movie-slim.graphql => group-slim.graphql} | 4 +- .../data/{movie.graphql => group.graphql} | 2 +- ui/v2.5/graphql/data/performer.graphql | 2 +- ui/v2.5/graphql/data/scene-slim.graphql | 4 +- ui/v2.5/graphql/data/scene.graphql | 6 +- ui/v2.5/graphql/data/scrapers.graphql | 18 +- ui/v2.5/graphql/data/studio.graphql | 4 +- ui/v2.5/graphql/data/tag.graphql | 4 +- ui/v2.5/graphql/mutations/group.graphql | 25 ++ ui/v2.5/graphql/mutations/movie.graphql | 25 -- ui/v2.5/graphql/queries/misc.graphql | 2 +- ui/v2.5/graphql/queries/movie.graphql | 24 +- .../graphql/queries/scrapers/scrapers.graphql | 12 +- ui/v2.5/src/components/FrontPage/Control.tsx | 1 + .../components/FrontPage/FrontPageConfig.tsx | 1 + .../src/components/List/EditFilterDialog.tsx | 1 + .../components/Movies/EditMoviesDialog.tsx | 42 +-- ui/v2.5/src/components/Movies/MovieCard.tsx | 2 +- .../src/components/Movies/MovieCardGrid.tsx | 2 +- .../components/Movies/MovieDetails/Movie.tsx | 36 +- .../Movies/MovieDetails/MovieCreate.tsx | 16 +- .../Movies/MovieDetails/MovieDetailsPanel.tsx | 4 +- .../Movies/MovieDetails/MovieEditPanel.tsx | 32 +- .../Movies/MovieDetails/MovieScenesPanel.tsx | 36 +- .../Movies/MovieDetails/MovieScrapeDialog.tsx | 8 +- ui/v2.5/src/components/Movies/MovieList.tsx | 48 +-- .../Movies/MovieRecommendationRow.tsx | 10 +- ui/v2.5/src/components/Movies/MovieSelect.tsx | 56 +-- .../components/Performers/PerformerCard.tsx | 10 +- .../Performers/PerformerDetails/Performer.tsx | 4 +- .../SceneDuplicateChecker.tsx | 20 +- .../components/Scenes/EditScenesDialog.tsx | 4 +- ui/v2.5/src/components/Scenes/SceneCard.tsx | 20 +- .../components/Scenes/SceneDetails/Scene.tsx | 4 +- .../Scenes/SceneDetails/SceneEditPanel.tsx | 54 +-- .../Scenes/SceneDetails/SceneMoviePanel.tsx | 6 +- .../Scenes/SceneDetails/SceneMovieTable.tsx | 14 +- .../Scenes/SceneDetails/SceneScrapeDialog.tsx | 12 +- .../src/components/Scenes/SceneListTable.tsx | 8 +- .../components/Scenes/SceneMergeDialog.tsx | 28 +- .../Settings/SettingsScrapingPanel.tsx | 8 +- .../Shared/ScrapeDialog/ScrapedObjectsRow.tsx | 8 +- .../Shared/ScrapeDialog/createObjects.ts | 14 +- ui/v2.5/src/components/Stats.tsx | 2 +- ui/v2.5/src/components/Studios/StudioCard.tsx | 6 +- .../Studios/StudioDetails/Studio.tsx | 2 +- .../StudioDetails/StudioPerformersPanel.tsx | 2 +- ui/v2.5/src/components/Tags/TagCard.tsx | 4 +- .../src/components/Tags/TagDetails/Tag.tsx | 2 +- ui/v2.5/src/core/StashService.ts | 146 ++++---- ui/v2.5/src/core/config.ts | 6 +- ui/v2.5/src/core/createClient.ts | 6 +- ui/v2.5/src/core/movies.ts | 6 +- .../models/list-filter/criteria/is-missing.ts | 2 +- .../src/models/list-filter/criteria/movies.ts | 10 +- ui/v2.5/src/models/list-filter/factory.ts | 5 +- ui/v2.5/src/models/list-filter/movies.ts | 6 +- ui/v2.5/src/models/list-filter/scenes.ts | 4 +- ui/v2.5/src/models/list-filter/tags.ts | 2 +- ui/v2.5/src/models/list-filter/types.ts | 4 +- ui/v2.5/src/pluginApi.d.ts | 110 +++--- ui/v2.5/src/utils/bulkUpdate.ts | 6 +- ui/v2.5/src/utils/navigation.ts | 14 +- 108 files changed, 1437 insertions(+), 567 deletions(-) create mode 100644 graphql/schema/types/group.graphql rename graphql/schema/types/{scraped-movie.graphql => scraped-group.graphql} (53%) create mode 100644 internal/api/resolver_mutation_group.go create mode 100644 internal/api/resolver_query_find_group.go rename ui/v2.5/graphql/data/{movie-slim.graphql => group-slim.graphql} (63%) rename ui/v2.5/graphql/data/{movie.graphql => group.graphql} (88%) create mode 100644 ui/v2.5/graphql/mutations/group.graphql delete mode 100644 ui/v2.5/graphql/mutations/movie.graphql diff --git a/gqlgen.yml b/gqlgen.yml index c6a434e25..36febdaae 100644 --- a/gqlgen.yml +++ b/gqlgen.yml @@ -51,6 +51,11 @@ models: fieldName: DurationFinite frame_rate: fieldName: FrameRateFinite + # group is movie under the hood + Group: + model: github.com/stashapp/stash/pkg/models.Movie + GroupFilterType: + model: github.com/stashapp/stash/pkg/models.MovieFilterType # autobind on config causes generation issues BlobsStorageType: model: github.com/stashapp/stash/internal/manager/config.BlobsStorageType diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index a1f163ecc..da02af575 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -77,13 +77,22 @@ type Query { ): FindStudiosResultType! "Find a movie by ID" - findMovie(id: ID!): Movie + findMovie(id: ID!): Movie @deprecated(reason: "Use findGroup instead") "A function which queries Movie objects" findMovies( movie_filter: MovieFilterType filter: FindFilterType ids: [ID!] - ): FindMoviesResultType! + ): FindMoviesResultType! @deprecated(reason: "Use findGroups instead") + + "Find a group by ID" + findGroup(id: ID!): Group + "A function which queries Group objects" + findGroups( + group_filter: GroupFilterType + filter: FindFilterType + ids: [ID!] + ): FindGroupsResultType! findGallery(id: ID!): Gallery findGalleries( @@ -156,7 +165,13 @@ type Query { scrapeSingleMovie( source: ScraperSourceInput! input: ScrapeSingleMovieInput! - ): [ScrapedMovie!]! + ): [ScrapedMovie!]! @deprecated(reason: "Use scrapeSingleGroup instead") + + "Scrape for a single group" + scrapeSingleGroup( + source: ScraperSourceInput! + input: ScrapeSingleGroupInput! + ): [ScrapedGroup!]! "Scrapes content based on a URL" scrapeURL(url: String!, ty: ScrapeContentType!): ScrapedContent @@ -169,6 +184,9 @@ type Query { scrapeGalleryURL(url: String!): ScrapedGallery "Scrapes a complete movie record based on a URL" scrapeMovieURL(url: String!): ScrapedMovie + @deprecated(reason: "Use scrapeGroupURL instead") + "Scrapes a complete group record based on a URL" + scrapeGroupURL(url: String!): ScrapedGroup # Plugins "List loaded plugins" @@ -214,7 +232,7 @@ type Query { allPerformers: [Performer!]! allTags: [Tag!]! @deprecated(reason: "Use findTags instead") allStudios: [Studio!]! @deprecated(reason: "Use findStudios instead") - allMovies: [Movie!]! @deprecated(reason: "Use findMovies instead") + allMovies: [Movie!]! @deprecated(reason: "Use findGroups instead") # Get everything with minimal metadata @@ -316,10 +334,21 @@ type Mutation { studiosDestroy(ids: [ID!]!): Boolean! movieCreate(input: MovieCreateInput!): Movie + @deprecated(reason: "Use groupCreate instead") movieUpdate(input: MovieUpdateInput!): Movie + @deprecated(reason: "Use groupUpdate instead") movieDestroy(input: MovieDestroyInput!): Boolean! + @deprecated(reason: "Use groupDestroy instead") moviesDestroy(ids: [ID!]!): Boolean! + @deprecated(reason: "Use groupsDestroy instead") bulkMovieUpdate(input: BulkMovieUpdateInput!): [Movie!] + @deprecated(reason: "Use bulkGroupUpdate instead") + + groupCreate(input: GroupCreateInput!): Group + groupUpdate(input: GroupUpdateInput!): Group + groupDestroy(input: GroupDestroyInput!): Boolean! + groupsDestroy(ids: [ID!]!): Boolean! + bulkGroupUpdate(input: BulkGroupUpdateInput!): [Group!] tagCreate(input: TagCreateInput!): Tag tagUpdate(input: TagUpdateInput!): Tag diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index 98b790d4f..d1b169769 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -257,7 +257,9 @@ input SceneFilterType { "Filter to only include scenes with this studio" studios: HierarchicalMultiCriterionInput "Filter to only include scenes with this movie" - movies: MultiCriterionInput + movies: MultiCriterionInput @deprecated(reason: "use groups instead") + "Filter to only include scenes with this group" + groups: MultiCriterionInput "Filter to only include scenes with this gallery" galleries: MultiCriterionInput "Filter to only include scenes with these tags" @@ -309,6 +311,9 @@ input SceneFilterType { tags_filter: TagFilterType "Filter by related movies that meet this criteria" movies_filter: MovieFilterType + @deprecated(reason: "use groups_filter instead") + "Filter by related groups that meet this criteria" + groups_filter: GroupFilterType "Filter by related markers that meet this criteria" markers_filter: SceneMarkerFilterType } @@ -351,6 +356,44 @@ input MovieFilterType { studios_filter: StudioFilterType } +input GroupFilterType { + AND: GroupFilterType + OR: GroupFilterType + NOT: GroupFilterType + + name: StringCriterionInput + director: StringCriterionInput + synopsis: StringCriterionInput + + "Filter by duration (in seconds)" + duration: IntCriterionInput + # rating expressed as 1-100 + rating100: IntCriterionInput + "Filter to only include groups with this studio" + studios: HierarchicalMultiCriterionInput + "Filter to only include groups missing this property" + is_missing: String + "Filter by url" + url: StringCriterionInput + "Filter to only include groups where performer appears in a scene" + performers: MultiCriterionInput + "Filter to only include groups with these tags" + tags: HierarchicalMultiCriterionInput + "Filter by tag count" + tag_count: IntCriterionInput + "Filter by date" + date: DateCriterionInput + "Filter by creation time" + created_at: TimestampCriterionInput + "Filter by last update time" + updated_at: TimestampCriterionInput + + "Filter by related scenes that meet this criteria" + scenes_filter: SceneFilterType + "Filter by related studios that meet this criteria" + studios_filter: StudioFilterType +} + input StudioFilterType { AND: StudioFilterType OR: StudioFilterType @@ -508,6 +551,9 @@ input TagFilterType { "Filter by number of movies with this tag" movie_count: IntCriterionInput + "Filter by number of group with this tag" + group_count: IntCriterionInput + "Filter by number of markers with this tag" marker_count: IntCriterionInput @@ -702,6 +748,7 @@ enum FilterMode { GALLERIES SCENE_MARKERS MOVIES + GROUPS TAGS IMAGES } diff --git a/graphql/schema/types/group.graphql b/graphql/schema/types/group.graphql new file mode 100644 index 000000000..15bb3556c --- /dev/null +++ b/graphql/schema/types/group.graphql @@ -0,0 +1,80 @@ +type Group { + id: ID! + name: String! + aliases: String + "Duration in seconds" + duration: Int + date: String + # rating expressed as 1-100 + rating100: Int + studio: Studio + director: String + synopsis: String + urls: [String!]! + tags: [Tag!]! + created_at: Time! + updated_at: Time! + + front_image_path: String # Resolver + back_image_path: String # Resolver + scene_count: Int! # Resolver + scenes: [Scene!]! +} + +input GroupCreateInput { + name: String! + aliases: String + "Duration in seconds" + duration: Int + date: String + # rating expressed as 1-100 + rating100: Int + studio_id: ID + director: String + synopsis: String + urls: [String!] + tag_ids: [ID!] + "This should be a URL or a base64 encoded data URL" + front_image: String + "This should be a URL or a base64 encoded data URL" + back_image: String +} + +input GroupUpdateInput { + id: ID! + name: String + aliases: String + duration: Int + date: String + # rating expressed as 1-100 + rating100: Int + studio_id: ID + director: String + synopsis: String + urls: [String!] + tag_ids: [ID!] + "This should be a URL or a base64 encoded data URL" + front_image: String + "This should be a URL or a base64 encoded data URL" + back_image: String +} + +input BulkGroupUpdateInput { + clientMutationId: String + ids: [ID!] + # rating expressed as 1-100 + rating100: Int + studio_id: ID + director: String + urls: BulkUpdateStrings + tag_ids: BulkUpdateIds +} + +input GroupDestroyInput { + id: ID! +} + +type FindGroupsResultType { + count: Int! + groups: [Group!]! +} diff --git a/graphql/schema/types/metadata.graphql b/graphql/schema/types/metadata.graphql index 3221b0cc6..38c910d36 100644 --- a/graphql/schema/types/metadata.graphql +++ b/graphql/schema/types/metadata.graphql @@ -284,7 +284,8 @@ input ExportObjectsInput { studios: ExportObjectTypeInput performers: ExportObjectTypeInput tags: ExportObjectTypeInput - movies: ExportObjectTypeInput + groups: ExportObjectTypeInput + movies: ExportObjectTypeInput @deprecated(reason: "Use groups instead") galleries: ExportObjectTypeInput includeDependencies: Boolean } diff --git a/graphql/schema/types/performer.graphql b/graphql/schema/types/performer.graphql index d6d6b2696..8ac6c6579 100644 --- a/graphql/schema/types/performer.graphql +++ b/graphql/schema/types/performer.graphql @@ -42,7 +42,8 @@ type Performer { scene_count: Int! # Resolver image_count: Int! # Resolver gallery_count: Int! # Resolver - movie_count: Int! # Resolver + group_count: Int! # Resolver + movie_count: Int! @deprecated(reason: "use group_count instead") # Resolver performer_count: Int! # Resolver o_counter: Int # Resolver scenes: [Scene!]! @@ -55,7 +56,8 @@ type Performer { weight: Int created_at: Time! updated_at: Time! - movies: [Movie!]! + groups: [Group!]! @deprecated(reason: "use groups instead") + movies: [Movie!]! @deprecated(reason: "use groups instead") } input PerformerCreateInput { diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index a5bb9f905..eca01d15e 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -26,6 +26,11 @@ type SceneMovie { scene_index: Int } +type SceneGroup { + group: Group! + scene_index: Int +} + type VideoCaption { language_code: String! caption_type: String! @@ -68,7 +73,8 @@ type Scene { scene_markers: [SceneMarker!]! galleries: [Gallery!]! studio: Studio - movies: [SceneMovie!]! + groups: [SceneGroup!]! + movies: [SceneMovie!]! @deprecated(reason: "Use groups") tags: [Tag!]! performers: [Performer!]! stash_ids: [StashID!]! @@ -82,6 +88,11 @@ input SceneMovieInput { scene_index: Int } +input SceneGroupInput { + group_id: ID! + scene_index: Int +} + input SceneCreateInput { title: String code: String @@ -96,7 +107,8 @@ input SceneCreateInput { studio_id: ID gallery_ids: [ID!] performer_ids: [ID!] - movies: [SceneMovieInput!] + groups: [SceneGroupInput!] + movies: [SceneMovieInput!] @deprecated(reason: "Use groups") tag_ids: [ID!] "This should be a URL or a base64 encoded data URL" cover_image: String @@ -128,7 +140,8 @@ input SceneUpdateInput { studio_id: ID gallery_ids: [ID!] performer_ids: [ID!] - movies: [SceneMovieInput!] + groups: [SceneGroupInput!] + movies: [SceneMovieInput!] @deprecated(reason: "Use groups") tag_ids: [ID!] "This should be a URL or a base64 encoded data URL" cover_image: String @@ -175,7 +188,8 @@ input BulkSceneUpdateInput { gallery_ids: BulkUpdateIds performer_ids: BulkUpdateIds tag_ids: BulkUpdateIds - movie_ids: BulkUpdateIds + group_ids: BulkUpdateIds + movie_ids: BulkUpdateIds @deprecated(reason: "Use group_ids") } input SceneDestroyInput { diff --git a/graphql/schema/types/scraped-movie.graphql b/graphql/schema/types/scraped-group.graphql similarity index 53% rename from graphql/schema/types/scraped-movie.graphql rename to graphql/schema/types/scraped-group.graphql index 5b07a222c..e490f32bb 100644 --- a/graphql/schema/types/scraped-movie.graphql +++ b/graphql/schema/types/scraped-group.graphql @@ -31,3 +31,35 @@ input ScrapedMovieInput { synopsis: String # not including tags for the input } + +"A group from a scraping operation..." +type ScrapedGroup { + stored_id: ID + name: String + aliases: String + duration: String + date: String + rating: String + director: String + urls: [String!] + synopsis: String + studio: ScrapedStudio + tags: [ScrapedTag!] + + "This should be a base64 encoded data URL" + front_image: String + "This should be a base64 encoded data URL" + back_image: String +} + +input ScrapedGroupInput { + name: String + aliases: String + duration: String + date: String + rating: String + director: String + urls: [String!] + synopsis: String + # not including tags for the input +} diff --git a/graphql/schema/types/scraper.graphql b/graphql/schema/types/scraper.graphql index ccc888dc3..d49df1b2b 100644 --- a/graphql/schema/types/scraper.graphql +++ b/graphql/schema/types/scraper.graphql @@ -11,6 +11,7 @@ enum ScrapeType { enum ScrapeContentType { GALLERY MOVIE + GROUP PERFORMER SCENE } @@ -22,6 +23,7 @@ union ScrapedContent = | ScrapedScene | ScrapedGallery | ScrapedMovie + | ScrapedGroup | ScrapedPerformer type ScraperSpec { @@ -40,7 +42,9 @@ type Scraper { "Details for gallery scraper" gallery: ScraperSpec "Details for movie scraper" - movie: ScraperSpec + movie: ScraperSpec @deprecated(reason: "use group") + "Details for group scraper" + group: ScraperSpec } type ScrapedStudio { @@ -76,7 +80,8 @@ type ScrapedScene { studio: ScrapedStudio tags: [ScrapedTag!] performers: [ScrapedPerformer!] - movies: [ScrapedMovie!] + movies: [ScrapedMovie!] @deprecated(reason: "use groups") + groups: [ScrapedGroup!] remote_site_id: String duration: Int @@ -190,10 +195,19 @@ input ScrapeSingleMovieInput { query: String "Instructs to query by movie id" movie_id: ID - "Instructs to query by gallery fragment" + "Instructs to query by movie fragment" movie_input: ScrapedMovieInput } +input ScrapeSingleGroupInput { + "Instructs to query by string" + query: String + "Instructs to query by group id" + group_id: ID + "Instructs to query by group fragment" + group_input: ScrapedGroupInput +} + input StashBoxSceneQueryInput { "Index of the configured stash-box instance to use" stash_box_index: Int @deprecated(reason: "use stash_box_endpoint") diff --git a/graphql/schema/types/stats.graphql b/graphql/schema/types/stats.graphql index 3675c2a6b..6d78c919b 100644 --- a/graphql/schema/types/stats.graphql +++ b/graphql/schema/types/stats.graphql @@ -7,7 +7,8 @@ type StatsResultType { gallery_count: Int! performer_count: Int! studio_count: Int! - movie_count: Int! + group_count: Int! + movie_count: Int! @deprecated(reason: "use group_count instead") tag_count: Int! total_o_count: Int! total_play_duration: Float! diff --git a/graphql/schema/types/studio.graphql b/graphql/schema/types/studio.graphql index f90183ed0..7823bf0c4 100644 --- a/graphql/schema/types/studio.graphql +++ b/graphql/schema/types/studio.graphql @@ -13,7 +13,8 @@ type Studio { image_count(depth: Int): Int! # Resolver gallery_count(depth: Int): Int! # Resolver performer_count(depth: Int): Int! # Resolver - movie_count(depth: Int): Int! # Resolver + group_count(depth: Int): Int! # Resolver + movie_count(depth: Int): Int! @deprecated(reason: "use group_count instead") # Resolver stash_ids: [StashID!]! # rating expressed as 1-100 rating100: Int @@ -21,7 +22,8 @@ type Studio { details: String created_at: Time! updated_at: Time! - movies: [Movie!]! + groups: [Group!]! + movies: [Movie!]! @deprecated(reason: "use groups instead") } input StudioCreateInput { diff --git a/graphql/schema/types/tag.graphql b/graphql/schema/types/tag.graphql index 6263b64a8..3c62c899c 100644 --- a/graphql/schema/types/tag.graphql +++ b/graphql/schema/types/tag.graphql @@ -14,7 +14,8 @@ type Tag { gallery_count(depth: Int): Int! # Resolver performer_count(depth: Int): Int! # Resolver studio_count(depth: Int): Int! # Resolver - movie_count(depth: Int): Int! # Resolver + group_count(depth: Int): Int! # Resolver + movie_count(depth: Int): Int! @deprecated(reason: "use group_count instead") # Resolver parents: [Tag!]! children: [Tag!]! diff --git a/internal/api/changeset_translator.go b/internal/api/changeset_translator.go index d148d47da..b5bd5835a 100644 --- a/internal/api/changeset_translator.go +++ b/internal/api/changeset_translator.go @@ -355,6 +355,33 @@ func (t changesetTranslator) relatedMovies(value []models.SceneMovieInput) (mode return models.NewRelatedMovies(moviesScenes), nil } +func moviesScenesFromGroupInput(input []models.SceneGroupInput) ([]models.MoviesScenes, error) { + ret := make([]models.MoviesScenes, len(input)) + + for i, v := range input { + mID, err := strconv.Atoi(v.GroupID) + if err != nil { + return nil, fmt.Errorf("invalid group ID: %s", v.GroupID) + } + + ret[i] = models.MoviesScenes{ + MovieID: mID, + SceneIndex: v.SceneIndex, + } + } + + return ret, nil +} + +func (t changesetTranslator) relatedMoviesFromGroups(value []models.SceneGroupInput) (models.RelatedMovies, error) { + moviesScenes, err := moviesScenesFromGroupInput(value) + if err != nil { + return models.RelatedMovies{}, err + } + + return models.NewRelatedMovies(moviesScenes), nil +} + func (t changesetTranslator) updateMovieIDs(value []models.SceneMovieInput, field string) (*models.UpdateMovieIDs, error) { if !t.hasField(field) { return nil, nil @@ -371,6 +398,22 @@ func (t changesetTranslator) updateMovieIDs(value []models.SceneMovieInput, fiel }, nil } +func (t changesetTranslator) updateMovieIDsFromGroups(value []models.SceneGroupInput, field string) (*models.UpdateMovieIDs, error) { + if !t.hasField(field) { + return nil, nil + } + + moviesScenes, err := moviesScenesFromGroupInput(value) + if err != nil { + return nil, err + } + + return &models.UpdateMovieIDs{ + Movies: moviesScenes, + Mode: models.RelationshipUpdateModeSet, + }, nil +} + func (t changesetTranslator) updateMovieIDsBulk(value *BulkUpdateIds, field string) (*models.UpdateMovieIDs, error) { if !t.hasField(field) || value == nil { return nil, nil diff --git a/internal/api/resolver.go b/internal/api/resolver.go index 50adea9ad..78ec0fc58 100644 --- a/internal/api/resolver.go +++ b/internal/api/resolver.go @@ -72,9 +72,14 @@ func (r *Resolver) SceneMarker() SceneMarkerResolver { func (r *Resolver) Studio() StudioResolver { return &studioResolver{r} } + +func (r *Resolver) Group() GroupResolver { + return &groupResolver{&movieResolver{r}} +} func (r *Resolver) Movie() MovieResolver { return &movieResolver{r} } + func (r *Resolver) Subscription() SubscriptionResolver { return &subscriptionResolver{r} } @@ -111,7 +116,11 @@ type sceneResolver struct{ *Resolver } type sceneMarkerResolver struct{ *Resolver } type imageResolver struct{ *Resolver } type studioResolver struct{ *Resolver } + +// group is movie under the hood type movieResolver struct{ *Resolver } +type groupResolver struct{ *movieResolver } + type tagResolver struct{ *Resolver } type galleryFileResolver struct{ *Resolver } type videoFileResolver struct{ *Resolver } @@ -218,7 +227,7 @@ func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) { return err } - moviesCount, err := movieQB.Count(ctx) + groupsCount, err := movieQB.Count(ctx) if err != nil { return err } @@ -262,7 +271,8 @@ func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) { GalleryCount: galleryCount, PerformerCount: performersCount, StudioCount: studiosCount, - MovieCount: moviesCount, + GroupCount: groupsCount, + MovieCount: groupsCount, TagCount: tagsCount, TotalOCount: totalOCount, TotalPlayDuration: totalPlayDuration, diff --git a/internal/api/resolver_model_performer.go b/internal/api/resolver_model_performer.go index 58fac77ff..224e733bd 100644 --- a/internal/api/resolver_model_performer.go +++ b/internal/api/resolver_model_performer.go @@ -179,7 +179,7 @@ func (r *performerResolver) GalleryCount(ctx context.Context, obj *models.Perfor return ret, nil } -func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret int, err error) { +func (r *performerResolver) GroupCount(ctx context.Context, obj *models.Performer) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { ret, err = r.repository.Movie.CountByPerformerID(ctx, obj.ID) return err @@ -190,6 +190,11 @@ func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performe return ret, nil } +// deprecated +func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret int, err error) { + return r.GroupCount(ctx, obj) +} + func (r *performerResolver) PerformerCount(ctx context.Context, obj *models.Performer) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { ret, err = performer.CountByAppearsWith(ctx, r.repository.Performer, obj.ID) @@ -252,7 +257,7 @@ func (r *performerResolver) DeathDate(ctx context.Context, obj *models.Performer return nil, nil } -func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (ret []*models.Movie, err error) { +func (r *performerResolver) Groups(ctx context.Context, obj *models.Performer) (ret []*models.Movie, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { ret, err = r.repository.Movie.FindByPerformerID(ctx, obj.ID) return err @@ -262,3 +267,8 @@ func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) ( return ret, nil } + +// deprecated +func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (ret []*models.Movie, err error) { + return r.Groups(ctx, obj) +} diff --git a/internal/api/resolver_model_scene.go b/internal/api/resolver_model_scene.go index 2376ca227..987c6e7b8 100644 --- a/internal/api/resolver_model_scene.go +++ b/internal/api/resolver_model_scene.go @@ -214,6 +214,37 @@ func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*S return ret, nil } +func (r *sceneResolver) Groups(ctx context.Context, obj *models.Scene) (ret []*SceneGroup, err error) { + if !obj.Movies.Loaded() { + if err := r.withReadTxn(ctx, func(ctx context.Context) error { + qb := r.repository.Scene + + return obj.LoadMovies(ctx, qb) + }); err != nil { + return nil, err + } + } + + loader := loaders.From(ctx).MovieByID + + for _, sm := range obj.Movies.List() { + movie, err := loader.Load(sm.MovieID) + if err != nil { + return nil, err + } + + sceneIdx := sm.SceneIndex + sceneGroup := &SceneGroup{ + Group: movie, + SceneIndex: sceneIdx, + } + + ret = append(ret, sceneGroup) + } + + return ret, nil +} + func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) (ret []*models.Tag, err error) { if !obj.TagIDs.Loaded() { if err := r.withReadTxn(ctx, func(ctx context.Context) error { diff --git a/internal/api/resolver_model_studio.go b/internal/api/resolver_model_studio.go index 011ab343e..1f8142e99 100644 --- a/internal/api/resolver_model_studio.go +++ b/internal/api/resolver_model_studio.go @@ -98,7 +98,7 @@ func (r *studioResolver) PerformerCount(ctx context.Context, obj *models.Studio, return ret, nil } -func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) { +func (r *studioResolver) GroupCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { ret, err = movie.CountByStudioID(ctx, r.repository.Movie, obj.ID, depth) return err @@ -109,6 +109,11 @@ func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio, dep return ret, nil } +// deprecated +func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) { + return r.GroupCount(ctx, obj, depth) +} + func (r *studioResolver) ParentStudio(ctx context.Context, obj *models.Studio) (ret *models.Studio, err error) { if obj.ParentID == nil { return nil, nil @@ -144,7 +149,7 @@ func (r *studioResolver) Rating100(ctx context.Context, obj *models.Studio) (*in return obj.Rating, nil } -func (r *studioResolver) Movies(ctx context.Context, obj *models.Studio) (ret []*models.Movie, err error) { +func (r *studioResolver) Groups(ctx context.Context, obj *models.Studio) (ret []*models.Movie, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { ret, err = r.repository.Movie.FindByStudioID(ctx, obj.ID) return err @@ -154,3 +159,8 @@ func (r *studioResolver) Movies(ctx context.Context, obj *models.Studio) (ret [] return ret, nil } + +// deprecated +func (r *studioResolver) Movies(ctx context.Context, obj *models.Studio) (ret []*models.Movie, err error) { + return r.Groups(ctx, obj) +} diff --git a/internal/api/resolver_model_tag.go b/internal/api/resolver_model_tag.go index a9930fb23..3cf0bd1d9 100644 --- a/internal/api/resolver_model_tag.go +++ b/internal/api/resolver_model_tag.go @@ -120,7 +120,7 @@ func (r *tagResolver) StudioCount(ctx context.Context, obj *models.Tag, depth *i return ret, nil } -func (r *tagResolver) MovieCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) { +func (r *tagResolver) GroupCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { ret, err = movie.CountByTagID(ctx, r.repository.Movie, obj.ID, depth) return err @@ -131,6 +131,10 @@ func (r *tagResolver) MovieCount(ctx context.Context, obj *models.Tag, depth *in return ret, nil } +func (r *tagResolver) MovieCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) { + return r.GroupCount(ctx, obj, depth) +} + func (r *tagResolver) ImagePath(ctx context.Context, obj *models.Tag) (*string, error) { var hasImage bool if err := r.withReadTxn(ctx, func(ctx context.Context) error { diff --git a/internal/api/resolver_mutation_group.go b/internal/api/resolver_mutation_group.go new file mode 100644 index 000000000..1645d80b3 --- /dev/null +++ b/internal/api/resolver_mutation_group.go @@ -0,0 +1,335 @@ +package api + +import ( + "context" + "fmt" + "strconv" + + "github.com/stashapp/stash/internal/static" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/plugin/hook" + "github.com/stashapp/stash/pkg/sliceutil/stringslice" + "github.com/stashapp/stash/pkg/utils" +) + +func movieFromGroupCreateInput(ctx context.Context, input GroupCreateInput) (*models.Movie, error) { + translator := changesetTranslator{ + inputMap: getUpdateInputMap(ctx), + } + + // Populate a new movie from the input + newMovie := models.NewMovie() + + newMovie.Name = input.Name + newMovie.Aliases = translator.string(input.Aliases) + newMovie.Duration = input.Duration + newMovie.Rating = input.Rating100 + newMovie.Director = translator.string(input.Director) + newMovie.Synopsis = translator.string(input.Synopsis) + + var err error + + newMovie.Date, err = translator.datePtr(input.Date) + if err != nil { + return nil, fmt.Errorf("converting date: %w", err) + } + newMovie.StudioID, err = translator.intPtrFromString(input.StudioID) + if err != nil { + return nil, fmt.Errorf("converting studio id: %w", err) + } + + newMovie.TagIDs, err = translator.relatedIds(input.TagIds) + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) + } + + if input.Urls != nil { + newMovie.URLs = models.NewRelatedStrings(input.Urls) + } + + return &newMovie, nil +} + +func (r *mutationResolver) GroupCreate(ctx context.Context, input GroupCreateInput) (*models.Movie, error) { + newMovie, err := movieFromGroupCreateInput(ctx, input) + if err != nil { + return nil, err + } + + // Process the base 64 encoded image string + var frontimageData []byte + if input.FrontImage != nil { + frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage) + if err != nil { + return nil, fmt.Errorf("processing front image: %w", err) + } + } + + // Process the base 64 encoded image string + var backimageData []byte + if input.BackImage != nil { + backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage) + if err != nil { + return nil, fmt.Errorf("processing back image: %w", err) + } + } + + // HACK: if back image is being set, set the front image to the default. + // This is because we can't have a null front image with a non-null back image. + if len(frontimageData) == 0 && len(backimageData) != 0 { + frontimageData = static.ReadAll(static.DefaultMovieImage) + } + + // Start the transaction and save the movie + if err := r.withTxn(ctx, func(ctx context.Context) error { + qb := r.repository.Movie + + err = qb.Create(ctx, newMovie) + if err != nil { + return err + } + + // update image table + if len(frontimageData) > 0 { + if err := qb.UpdateFrontImage(ctx, newMovie.ID, frontimageData); err != nil { + return err + } + } + + if len(backimageData) > 0 { + if err := qb.UpdateBackImage(ctx, newMovie.ID, backimageData); err != nil { + return err + } + } + + return nil + }); err != nil { + return nil, err + } + + // for backwards compatibility - run both movie and group hooks + r.hookExecutor.ExecutePostHooks(ctx, newMovie.ID, hook.GroupCreatePost, input, nil) + r.hookExecutor.ExecutePostHooks(ctx, newMovie.ID, hook.MovieCreatePost, input, nil) + return r.getMovie(ctx, newMovie.ID) +} + +func moviePartialFromGroupUpdateInput(translator changesetTranslator, input GroupUpdateInput) (ret models.MoviePartial, err error) { + // Populate movie from the input + updatedMovie := models.NewMoviePartial() + + updatedMovie.Name = translator.optionalString(input.Name, "name") + updatedMovie.Aliases = translator.optionalString(input.Aliases, "aliases") + updatedMovie.Duration = translator.optionalInt(input.Duration, "duration") + updatedMovie.Rating = translator.optionalInt(input.Rating100, "rating100") + updatedMovie.Director = translator.optionalString(input.Director, "director") + updatedMovie.Synopsis = translator.optionalString(input.Synopsis, "synopsis") + + updatedMovie.Date, err = translator.optionalDate(input.Date, "date") + if err != nil { + err = fmt.Errorf("converting date: %w", err) + return + } + updatedMovie.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id") + if err != nil { + err = fmt.Errorf("converting studio id: %w", err) + return + } + + updatedMovie.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids") + if err != nil { + err = fmt.Errorf("converting tag ids: %w", err) + return + } + + updatedMovie.URLs = translator.updateStrings(input.Urls, "urls") + + return updatedMovie, nil +} + +func (r *mutationResolver) GroupUpdate(ctx context.Context, input GroupUpdateInput) (*models.Movie, error) { + movieID, err := strconv.Atoi(input.ID) + if err != nil { + return nil, fmt.Errorf("converting id: %w", err) + } + + translator := changesetTranslator{ + inputMap: getUpdateInputMap(ctx), + } + + updatedMovie, err := moviePartialFromGroupUpdateInput(translator, input) + if err != nil { + return nil, err + } + + var frontimageData []byte + frontImageIncluded := translator.hasField("front_image") + if input.FrontImage != nil { + frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage) + if err != nil { + return nil, fmt.Errorf("processing front image: %w", err) + } + } + + var backimageData []byte + backImageIncluded := translator.hasField("back_image") + if input.BackImage != nil { + backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage) + if err != nil { + return nil, fmt.Errorf("processing back image: %w", err) + } + } + + // Start the transaction and save the movie + var movie *models.Movie + if err := r.withTxn(ctx, func(ctx context.Context) error { + qb := r.repository.Movie + movie, err = qb.UpdatePartial(ctx, movieID, updatedMovie) + if err != nil { + return err + } + + // update image table + if frontImageIncluded { + if err := qb.UpdateFrontImage(ctx, movie.ID, frontimageData); err != nil { + return err + } + } + + if backImageIncluded { + if err := qb.UpdateBackImage(ctx, movie.ID, backimageData); err != nil { + return err + } + } + + return nil + }); err != nil { + return nil, err + } + + // for backwards compatibility - run both movie and group hooks + r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.GroupUpdatePost, input, translator.getFields()) + r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.MovieUpdatePost, input, translator.getFields()) + return r.getMovie(ctx, movie.ID) +} + +func moviePartialFromBulkGroupUpdateInput(translator changesetTranslator, input BulkGroupUpdateInput) (ret models.MoviePartial, err error) { + updatedMovie := models.NewMoviePartial() + + updatedMovie.Rating = translator.optionalInt(input.Rating100, "rating100") + updatedMovie.Director = translator.optionalString(input.Director, "director") + + updatedMovie.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id") + if err != nil { + err = fmt.Errorf("converting studio id: %w", err) + return + } + + updatedMovie.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids") + if err != nil { + err = fmt.Errorf("converting tag ids: %w", err) + return + } + + updatedMovie.URLs = translator.optionalURLsBulk(input.Urls, nil) + + return updatedMovie, nil +} + +func (r *mutationResolver) BulkGroupUpdate(ctx context.Context, input BulkGroupUpdateInput) ([]*models.Movie, error) { + movieIDs, err := stringslice.StringSliceToIntSlice(input.Ids) + if err != nil { + return nil, fmt.Errorf("converting ids: %w", err) + } + + translator := changesetTranslator{ + inputMap: getUpdateInputMap(ctx), + } + + // Populate movie from the input + updatedMovie, err := moviePartialFromBulkGroupUpdateInput(translator, input) + if err != nil { + return nil, err + } + + ret := []*models.Movie{} + + if err := r.withTxn(ctx, func(ctx context.Context) error { + qb := r.repository.Movie + + for _, movieID := range movieIDs { + movie, err := qb.UpdatePartial(ctx, movieID, updatedMovie) + if err != nil { + return err + } + + ret = append(ret, movie) + } + + return nil + }); err != nil { + return nil, err + } + + var newRet []*models.Movie + for _, movie := range ret { + // for backwards compatibility - run both movie and group hooks + r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.GroupUpdatePost, input, translator.getFields()) + r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.MovieUpdatePost, input, translator.getFields()) + + movie, err = r.getMovie(ctx, movie.ID) + if err != nil { + return nil, err + } + + newRet = append(newRet, movie) + } + + return newRet, nil +} + +func (r *mutationResolver) GroupDestroy(ctx context.Context, input GroupDestroyInput) (bool, error) { + id, err := strconv.Atoi(input.ID) + if err != nil { + return false, fmt.Errorf("converting id: %w", err) + } + + if err := r.withTxn(ctx, func(ctx context.Context) error { + return r.repository.Movie.Destroy(ctx, id) + }); err != nil { + return false, err + } + + // for backwards compatibility - run both movie and group hooks + r.hookExecutor.ExecutePostHooks(ctx, id, hook.GroupDestroyPost, input, nil) + r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, input, nil) + + return true, nil +} + +func (r *mutationResolver) GroupsDestroy(ctx context.Context, groupIDs []string) (bool, error) { + ids, err := stringslice.StringSliceToIntSlice(groupIDs) + if err != nil { + return false, fmt.Errorf("converting ids: %w", err) + } + + if err := r.withTxn(ctx, func(ctx context.Context) error { + qb := r.repository.Movie + for _, id := range ids { + if err := qb.Destroy(ctx, id); err != nil { + return err + } + } + + return nil + }); err != nil { + return false, err + } + + for _, id := range ids { + // for backwards compatibility - run both movie and group hooks + r.hookExecutor.ExecutePostHooks(ctx, id, hook.GroupDestroyPost, groupIDs, nil) + r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, groupIDs, nil) + } + + return true, nil +} diff --git a/internal/api/resolver_mutation_movie.go b/internal/api/resolver_mutation_movie.go index c3fce71a6..3e73f32dd 100644 --- a/internal/api/resolver_mutation_movie.go +++ b/internal/api/resolver_mutation_movie.go @@ -112,6 +112,8 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInp return nil, err } + // for backwards compatibility - run both movie and group hooks + r.hookExecutor.ExecutePostHooks(ctx, newMovie.ID, hook.GroupCreatePost, input, nil) r.hookExecutor.ExecutePostHooks(ctx, newMovie.ID, hook.MovieCreatePost, input, nil) return r.getMovie(ctx, newMovie.ID) } @@ -197,6 +199,8 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInp return nil, err } + // for backwards compatibility - run both movie and group hooks + r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.GroupUpdatePost, input, translator.getFields()) r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.MovieUpdatePost, input, translator.getFields()) return r.getMovie(ctx, movie.ID) } @@ -250,6 +254,8 @@ func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input BulkMovieU var newRet []*models.Movie for _, movie := range ret { + // for backwards compatibility - run both movie and group hooks + r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.GroupUpdatePost, input, translator.getFields()) r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.MovieUpdatePost, input, translator.getFields()) movie, err = r.getMovie(ctx, movie.ID) @@ -275,6 +281,8 @@ func (r *mutationResolver) MovieDestroy(ctx context.Context, input MovieDestroyI return false, err } + // for backwards compatibility - run both movie and group hooks + r.hookExecutor.ExecutePostHooks(ctx, id, hook.GroupDestroyPost, input, nil) r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, input, nil) return true, nil @@ -300,6 +308,8 @@ func (r *mutationResolver) MoviesDestroy(ctx context.Context, movieIDs []string) } for _, id := range ids { + // for backwards compatibility - run both movie and group hooks + r.hookExecutor.ExecutePostHooks(ctx, id, hook.GroupDestroyPost, movieIDs, nil) r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, movieIDs, nil) } diff --git a/internal/api/resolver_mutation_scene.go b/internal/api/resolver_mutation_scene.go index 15bf45147..d3616cc4c 100644 --- a/internal/api/resolver_mutation_scene.go +++ b/internal/api/resolver_mutation_scene.go @@ -80,9 +80,17 @@ func (r *mutationResolver) SceneCreate(ctx context.Context, input models.SceneCr return nil, fmt.Errorf("converting gallery ids: %w", err) } - newScene.Movies, err = translator.relatedMovies(input.Movies) - if err != nil { - return nil, fmt.Errorf("converting movies: %w", err) + // prefer groups over movies + if len(input.Groups) > 0 { + newScene.Movies, err = translator.relatedMoviesFromGroups(input.Groups) + if err != nil { + return nil, fmt.Errorf("converting groups: %w", err) + } + } else if len(input.Movies) > 0 { + newScene.Movies, err = translator.relatedMovies(input.Movies) + if err != nil { + return nil, fmt.Errorf("converting movies: %w", err) + } } var coverImageData []byte @@ -216,9 +224,16 @@ func scenePartialFromInput(input models.SceneUpdateInput, translator changesetTr return nil, fmt.Errorf("converting gallery ids: %w", err) } - updatedScene.MovieIDs, err = translator.updateMovieIDs(input.Movies, "movies") - if err != nil { - return nil, fmt.Errorf("converting movies: %w", err) + if translator.hasField("groups") { + updatedScene.MovieIDs, err = translator.updateMovieIDsFromGroups(input.Groups, "groups") + if err != nil { + return nil, fmt.Errorf("converting movies: %w", err) + } + } else if translator.hasField("movies") { + updatedScene.MovieIDs, err = translator.updateMovieIDs(input.Movies, "movies") + if err != nil { + return nil, fmt.Errorf("converting movies: %w", err) + } } return &updatedScene, nil @@ -358,9 +373,16 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input BulkSceneU return nil, fmt.Errorf("converting gallery ids: %w", err) } - updatedScene.MovieIDs, err = translator.updateMovieIDsBulk(input.MovieIds, "movie_ids") - if err != nil { - return nil, fmt.Errorf("converting movie ids: %w", err) + if translator.hasField("groups") { + updatedScene.MovieIDs, err = translator.updateMovieIDsBulk(input.GroupIds, "group_ids") + if err != nil { + return nil, fmt.Errorf("converting group ids: %w", err) + } + } else if translator.hasField("movies") { + updatedScene.MovieIDs, err = translator.updateMovieIDsBulk(input.MovieIds, "movie_ids") + if err != nil { + return nil, fmt.Errorf("converting movie ids: %w", err) + } } ret := []*models.Scene{} diff --git a/internal/api/resolver_query_find_group.go b/internal/api/resolver_query_find_group.go new file mode 100644 index 000000000..f5fdde50a --- /dev/null +++ b/internal/api/resolver_query_find_group.go @@ -0,0 +1,59 @@ +package api + +import ( + "context" + "strconv" + + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/sliceutil/stringslice" +) + +func (r *queryResolver) FindGroup(ctx context.Context, id string) (ret *models.Movie, err error) { + idInt, err := strconv.Atoi(id) + if err != nil { + return nil, err + } + + if err := r.withReadTxn(ctx, func(ctx context.Context) error { + ret, err = r.repository.Movie.Find(ctx, idInt) + return err + }); err != nil { + return nil, err + } + + return ret, nil +} + +func (r *queryResolver) FindGroups(ctx context.Context, movieFilter *models.MovieFilterType, filter *models.FindFilterType, ids []string) (ret *FindGroupsResultType, err error) { + idInts, err := stringslice.StringSliceToIntSlice(ids) + if err != nil { + return nil, err + } + + if err := r.withReadTxn(ctx, func(ctx context.Context) error { + var movies []*models.Movie + var err error + var total int + + if len(idInts) > 0 { + movies, err = r.repository.Movie.FindMany(ctx, idInts) + total = len(movies) + } else { + movies, total, err = r.repository.Movie.Query(ctx, movieFilter, filter) + } + + if err != nil { + return err + } + + ret = &FindGroupsResultType{ + Count: total, + Groups: movies, + } + return nil + }); err != nil { + return nil, err + } + + return ret, nil +} diff --git a/internal/api/resolver_query_scraper.go b/internal/api/resolver_query_scraper.go index 503f73b7e..9d69843bc 100644 --- a/internal/api/resolver_query_scraper.go +++ b/internal/api/resolver_query_scraper.go @@ -213,6 +213,39 @@ func (r *queryResolver) ScrapeMovieURL(ctx context.Context, url string) (*models return ret, nil } +func (r *queryResolver) ScrapeGroupURL(ctx context.Context, url string) (*models.ScrapedGroup, error) { + content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypeMovie) + if err != nil { + return nil, err + } + + ret, err := marshalScrapedMovie(content) + if err != nil { + return nil, err + } + + filterMovieTags([]*models.ScrapedMovie{ret}) + + // convert to scraped group + group := &models.ScrapedGroup{ + StoredID: ret.StoredID, + Name: ret.Name, + Aliases: ret.Aliases, + Duration: ret.Duration, + Date: ret.Date, + Rating: ret.Rating, + Director: ret.Director, + URLs: ret.URLs, + Synopsis: ret.Synopsis, + Studio: ret.Studio, + Tags: ret.Tags, + FrontImage: ret.FrontImage, + BackImage: ret.BackImage, + } + + return group, nil +} + func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source scraper.Source, input ScrapeSingleSceneInput) ([]*scraper.ScrapedScene, error) { var ret []*scraper.ScrapedScene @@ -461,3 +494,7 @@ func (r *queryResolver) ScrapeSingleGallery(ctx context.Context, source scraper. func (r *queryResolver) ScrapeSingleMovie(ctx context.Context, source scraper.Source, input ScrapeSingleMovieInput) ([]*models.ScrapedMovie, error) { return nil, ErrNotSupported } + +func (r *queryResolver) ScrapeSingleGroup(ctx context.Context, source scraper.Source, input ScrapeSingleGroupInput) ([]*models.ScrapedGroup, error) { + return nil, ErrNotSupported +} diff --git a/internal/manager/task_export.go b/internal/manager/task_export.go index 0a294e70e..cbf304fb6 100644 --- a/internal/manager/task_export.go +++ b/internal/manager/task_export.go @@ -42,7 +42,7 @@ type ExportTask struct { scenes *exportSpec images *exportSpec performers *exportSpec - movies *exportSpec + groups *exportSpec tags *exportSpec studios *exportSpec galleries *exportSpec @@ -63,7 +63,8 @@ type ExportObjectsInput struct { Studios *ExportObjectTypeInput `json:"studios"` Performers *ExportObjectTypeInput `json:"performers"` Tags *ExportObjectTypeInput `json:"tags"` - Movies *ExportObjectTypeInput `json:"movies"` + Groups *ExportObjectTypeInput `json:"groups"` + Movies *ExportObjectTypeInput `json:"movies"` // deprecated Galleries *ExportObjectTypeInput `json:"galleries"` IncludeDependencies *bool `json:"includeDependencies"` } @@ -97,13 +98,19 @@ func CreateExportTask(a models.HashAlgorithm, input ExportObjectsInput) *ExportT includeDeps = *input.IncludeDependencies } + // handle deprecated Movies field + groupSpec := input.Groups + if groupSpec == nil && input.Movies != nil { + groupSpec = input.Movies + } + return &ExportTask{ repository: GetInstance().Repository, fileNamingAlgorithm: a, scenes: newExportSpec(input.Scenes), images: newExportSpec(input.Images), performers: newExportSpec(input.Performers), - movies: newExportSpec(input.Movies), + groups: newExportSpec(groupSpec), tags: newExportSpec(input.Tags), studios: newExportSpec(input.Studios), galleries: newExportSpec(input.Galleries), @@ -282,11 +289,11 @@ func (t *ExportTask) populateMovieScenes(ctx context.Context) { var movies []*models.Movie var err error - all := t.full || (t.movies != nil && t.movies.all) + all := t.full || (t.groups != nil && t.groups.all) if all { movies, err = reader.All(ctx) - } else if t.movies != nil && len(t.movies.IDs) > 0 { - movies, err = reader.FindMany(ctx, t.movies.IDs) + } else if t.groups != nil && len(t.groups.IDs) > 0 { + movies, err = reader.FindMany(ctx, t.groups.IDs) } if err != nil { @@ -574,7 +581,7 @@ func (t *ExportTask) exportScene(ctx context.Context, wg *sync.WaitGroup, jobCha logger.Errorf("[scenes] <%s> error getting scene movies: %v", sceneHash, err) continue } - t.movies.IDs = sliceutil.AppendUniques(t.movies.IDs, movieIDs) + t.groups.IDs = sliceutil.AppendUniques(t.groups.IDs, movieIDs) t.performers.IDs = sliceutil.AppendUniques(t.performers.IDs, performer.GetIDs(performers)) } @@ -1080,11 +1087,11 @@ func (t *ExportTask) ExportMovies(ctx context.Context, workers int) { reader := t.repository.Movie var movies []*models.Movie var err error - all := t.full || (t.movies != nil && t.movies.all) + all := t.full || (t.groups != nil && t.groups.all) if all { movies, err = reader.All(ctx) - } else if t.movies != nil && len(t.movies.IDs) > 0 { - movies, err = reader.FindMany(ctx, t.movies.IDs) + } else if t.groups != nil && len(t.groups.IDs) > 0 { + movies, err = reader.FindMany(ctx, t.groups.IDs) } if err != nil { diff --git a/pkg/models/model_saved_filter.go b/pkg/models/model_saved_filter.go index d680e7c95..8c9e7b18d 100644 --- a/pkg/models/model_saved_filter.go +++ b/pkg/models/model_saved_filter.go @@ -15,6 +15,7 @@ const ( FilterModeGalleries FilterMode = "GALLERIES" FilterModeSceneMarkers FilterMode = "SCENE_MARKERS" FilterModeMovies FilterMode = "MOVIES" + FilterModeGroups FilterMode = "GROUPS" FilterModeTags FilterMode = "TAGS" FilterModeImages FilterMode = "IMAGES" ) @@ -25,6 +26,7 @@ var AllFilterMode = []FilterMode{ FilterModeStudios, FilterModeGalleries, FilterModeSceneMarkers, + FilterModeGroups, FilterModeMovies, FilterModeTags, FilterModeImages, @@ -32,7 +34,7 @@ var AllFilterMode = []FilterMode{ func (e FilterMode) IsValid() bool { switch e { - case FilterModeScenes, FilterModePerformers, FilterModeStudios, FilterModeGalleries, FilterModeSceneMarkers, FilterModeMovies, FilterModeTags, FilterModeImages: + case FilterModeScenes, FilterModePerformers, FilterModeStudios, FilterModeGalleries, FilterModeSceneMarkers, FilterModeMovies, FilterModeGroups, FilterModeTags, FilterModeImages: return true } return false diff --git a/pkg/models/model_scraped_item.go b/pkg/models/model_scraped_item.go index 84c69d7e4..e95fc6df4 100644 --- a/pkg/models/model_scraped_item.go +++ b/pkg/models/model_scraped_item.go @@ -414,3 +414,24 @@ type ScrapedMovie struct { } func (ScrapedMovie) IsScrapedContent() {} + +// ScrapedGroup is a group from a scraping operation +type ScrapedGroup struct { + StoredID *string `json:"stored_id"` + Name *string `json:"name"` + Aliases *string `json:"aliases"` + Duration *string `json:"duration"` + Date *string `json:"date"` + Rating *string `json:"rating"` + Director *string `json:"director"` + URLs []string `json:"urls"` + Synopsis *string `json:"synopsis"` + Studio *ScrapedStudio `json:"studio"` + Tags []*ScrapedTag `json:"tags"` + // This should be a base64 encoded data URL + FrontImage *string `json:"front_image"` + // This should be a base64 encoded data URL + BackImage *string `json:"back_image"` +} + +func (ScrapedGroup) IsScrapedContent() {} diff --git a/pkg/models/scene.go b/pkg/models/scene.go index 8a2ffde8d..5c5df87db 100644 --- a/pkg/models/scene.go +++ b/pkg/models/scene.go @@ -55,6 +55,8 @@ type SceneFilterType struct { IsMissing *string `json:"is_missing"` // Filter to only include scenes with this studio Studios *HierarchicalMultiCriterionInput `json:"studios"` + // Filter to only include scenes with this group + Groups *MultiCriterionInput `json:"groups"` // Filter to only include scenes with this movie Movies *MultiCriterionInput `json:"movies"` // Filter to only include scenes with this gallery @@ -103,6 +105,8 @@ type SceneFilterType struct { StudiosFilter *StudioFilterType `json:"studios_filter"` // Filter by related tags that meet this criteria TagsFilter *TagFilterType `json:"tags_filter"` + // Filter by related groups that meet this criteria + GroupsFilter *MovieFilterType `json:"groups_filter"` // Filter by related movies that meet this criteria MoviesFilter *MovieFilterType `json:"movies_filter"` // Filter by related markers that meet this criteria @@ -131,11 +135,17 @@ type SceneQueryResult struct { resolveErr error } +// SceneMovieInput is used for groups and movies type SceneMovieInput struct { MovieID string `json:"movie_id"` SceneIndex *int `json:"scene_index"` } +type SceneGroupInput struct { + GroupID string `json:"group_id"` + SceneIndex *int `json:"scene_index"` +} + type SceneCreateInput struct { Title *string `json:"title"` Code *string `json:"code"` @@ -150,6 +160,7 @@ type SceneCreateInput struct { GalleryIds []string `json:"gallery_ids"` PerformerIds []string `json:"performer_ids"` Movies []SceneMovieInput `json:"movies"` + Groups []SceneGroupInput `json:"groups"` TagIds []string `json:"tag_ids"` // This should be a URL or a base64 encoded data URL CoverImage *string `json:"cover_image"` @@ -177,6 +188,7 @@ type SceneUpdateInput struct { GalleryIds []string `json:"gallery_ids"` PerformerIds []string `json:"performer_ids"` Movies []SceneMovieInput `json:"movies"` + Groups []SceneGroupInput `json:"groups"` TagIds []string `json:"tag_ids"` // This should be a URL or a base64 encoded data URL CoverImage *string `json:"cover_image"` diff --git a/pkg/models/tag.go b/pkg/models/tag.go index cc32a6ce2..ddab8baf5 100644 --- a/pkg/models/tag.go +++ b/pkg/models/tag.go @@ -22,6 +22,8 @@ type TagFilterType struct { PerformerCount *IntCriterionInput `json:"performer_count"` // Filter by number of studios with this tag StudioCount *IntCriterionInput `json:"studio_count"` + // Filter by number of groups with this tag + GroupCount *IntCriterionInput `json:"group_count"` // Filter by number of movies with this tag MovieCount *IntCriterionInput `json:"movie_count"` // Filter by number of markers with this tag diff --git a/pkg/plugin/hook/hooks.go b/pkg/plugin/hook/hooks.go index 1b7d93be4..a8235b183 100644 --- a/pkg/plugin/hook/hooks.go +++ b/pkg/plugin/hook/hooks.go @@ -26,10 +26,16 @@ const ( GalleryChapterUpdatePost TriggerEnum = "GalleryChapter.Update.Post" GalleryChapterDestroyPost TriggerEnum = "GalleryChapter.Destroy.Post" + // deprecated - use Group hooks instead + // for now, both movie and group hooks will be executed MovieCreatePost TriggerEnum = "Movie.Create.Post" MovieUpdatePost TriggerEnum = "Movie.Update.Post" MovieDestroyPost TriggerEnum = "Movie.Destroy.Post" + GroupCreatePost TriggerEnum = "Group.Create.Post" + GroupUpdatePost TriggerEnum = "Group.Update.Post" + GroupDestroyPost TriggerEnum = "Group.Destroy.Post" + PerformerCreatePost TriggerEnum = "Performer.Create.Post" PerformerUpdatePost TriggerEnum = "Performer.Update.Post" PerformerDestroyPost TriggerEnum = "Performer.Destroy.Post" diff --git a/pkg/scraper/config.go b/pkg/scraper/config.go index 3a0aadf51..19545a08d 100644 --- a/pkg/scraper/config.go +++ b/pkg/scraper/config.go @@ -299,6 +299,7 @@ func (c config) spec() Scraper { if len(movie.SupportedScrapes) > 0 { ret.Movie = &movie + ret.Group = &movie } return ret @@ -312,7 +313,7 @@ func (c config) supports(ty ScrapeContentType) bool { return (c.SceneByName != nil && c.SceneByQueryFragment != nil) || c.SceneByFragment != nil || len(c.SceneByURL) > 0 case ScrapeContentTypeGallery: return c.GalleryByFragment != nil || len(c.GalleryByURL) > 0 - case ScrapeContentTypeMovie: + case ScrapeContentTypeMovie, ScrapeContentTypeGroup: return len(c.MovieByURL) > 0 } @@ -339,7 +340,7 @@ func (c config) matchesURL(url string, ty ScrapeContentType) bool { return true } } - case ScrapeContentTypeMovie: + case ScrapeContentTypeMovie, ScrapeContentTypeGroup: for _, scraper := range c.MovieByURL { if scraper.matchesURL(url) { return true diff --git a/pkg/scraper/group.go b/pkg/scraper/group.go index bbf0a680a..94cc05b96 100644 --- a/pkg/scraper/group.go +++ b/pkg/scraper/group.go @@ -81,7 +81,7 @@ func loadUrlCandidates(c config, ty ScrapeContentType) []*scrapeByURLConfig { return c.PerformerByURL case ScrapeContentTypeScene: return c.SceneByURL - case ScrapeContentTypeMovie: + case ScrapeContentTypeMovie, ScrapeContentTypeGroup: return c.MovieByURL case ScrapeContentTypeGallery: return c.GalleryByURL diff --git a/pkg/scraper/json.go b/pkg/scraper/json.go index ae96ecb06..0da20a827 100644 --- a/pkg/scraper/json.go +++ b/pkg/scraper/json.go @@ -102,7 +102,7 @@ func (s *jsonScraper) scrapeByURL(ctx context.Context, url string, ty ScrapeCont return nil, err } return ret, nil - case ScrapeContentTypeMovie: + case ScrapeContentTypeMovie, ScrapeContentTypeGroup: ret, err := scraper.scrapeMovie(ctx, q) if err != nil || ret == nil { return nil, err diff --git a/pkg/scraper/scene.go b/pkg/scraper/scene.go index e5de74a23..1ffc20996 100644 --- a/pkg/scraper/scene.go +++ b/pkg/scraper/scene.go @@ -18,6 +18,7 @@ type ScrapedScene struct { Studio *models.ScrapedStudio `json:"studio"` Tags []*models.ScrapedTag `json:"tags"` Performers []*models.ScrapedPerformer `json:"performers"` + Groups []*models.ScrapedGroup `json:"groups"` Movies []*models.ScrapedMovie `json:"movies"` RemoteSiteID *string `json:"remote_site_id"` Duration *int `json:"duration"` diff --git a/pkg/scraper/scraper.go b/pkg/scraper/scraper.go index 4eb67dcf4..1e814bd8a 100644 --- a/pkg/scraper/scraper.go +++ b/pkg/scraper/scraper.go @@ -31,6 +31,7 @@ type ScrapeContentType string const ( ScrapeContentTypeGallery ScrapeContentType = "GALLERY" ScrapeContentTypeMovie ScrapeContentType = "MOVIE" + ScrapeContentTypeGroup ScrapeContentType = "GROUP" ScrapeContentTypePerformer ScrapeContentType = "PERFORMER" ScrapeContentTypeScene ScrapeContentType = "SCENE" ) @@ -38,13 +39,14 @@ const ( var AllScrapeContentType = []ScrapeContentType{ ScrapeContentTypeGallery, ScrapeContentTypeMovie, + ScrapeContentTypeGroup, ScrapeContentTypePerformer, ScrapeContentTypeScene, } func (e ScrapeContentType) IsValid() bool { switch e { - case ScrapeContentTypeGallery, ScrapeContentTypeMovie, ScrapeContentTypePerformer, ScrapeContentTypeScene: + case ScrapeContentTypeGallery, ScrapeContentTypeMovie, ScrapeContentTypeGroup, ScrapeContentTypePerformer, ScrapeContentTypeScene: return true } return false @@ -81,6 +83,8 @@ type Scraper struct { // Details for gallery scraper Gallery *ScraperSpec `json:"gallery"` // Details for movie scraper + Group *ScraperSpec `json:"group"` + // Details for movie scraper Movie *ScraperSpec `json:"movie"` } diff --git a/pkg/scraper/script.go b/pkg/scraper/script.go index 51ee85262..bff78ac79 100644 --- a/pkg/scraper/script.go +++ b/pkg/scraper/script.go @@ -384,7 +384,7 @@ func (s *scriptScraper) scrape(ctx context.Context, input string, ty ScrapeConte var scene *ScrapedScene err := s.runScraperScript(ctx, input, &scene) return scene, err - case ScrapeContentTypeMovie: + case ScrapeContentTypeMovie, ScrapeContentTypeGroup: var movie *models.ScrapedMovie err := s.runScraperScript(ctx, input, &movie) return movie, err diff --git a/pkg/scraper/xpath.go b/pkg/scraper/xpath.go index d13c8e4c0..9eab9c67f 100644 --- a/pkg/scraper/xpath.go +++ b/pkg/scraper/xpath.go @@ -83,7 +83,7 @@ func (s *xpathScraper) scrapeByURL(ctx context.Context, url string, ty ScrapeCon return nil, err } return ret, nil - case ScrapeContentTypeMovie: + case ScrapeContentTypeMovie, ScrapeContentTypeGroup: ret, err := scraper.scrapeMovie(ctx, q) if err != nil || ret == nil { return nil, err diff --git a/pkg/sqlite/gallery_test.go b/pkg/sqlite/gallery_test.go index 08908220b..fcc10aece 100644 --- a/pkg/sqlite/gallery_test.go +++ b/pkg/sqlite/gallery_test.go @@ -1881,7 +1881,7 @@ func TestGalleryQueryIsMissingPerformers(t *testing.T) { assert.True(t, len(galleries) > 0) - // ensure non of the ids equal the one with movies + // ensure non of the ids equal the one with galleries for _, gallery := range galleries { assert.NotEqual(t, galleryIDs[galleryIdxWithPerformer], gallery.ID) } diff --git a/pkg/sqlite/image_test.go b/pkg/sqlite/image_test.go index e1246ebbe..aa4ed3b99 100644 --- a/pkg/sqlite/image_test.go +++ b/pkg/sqlite/image_test.go @@ -2053,7 +2053,7 @@ func TestImageQueryIsMissingPerformers(t *testing.T) { assert.True(t, len(images) > 0) - // ensure non of the ids equal the one with movies + // ensure non of the ids equal the one with performers for _, image := range images { assert.NotEqual(t, imageIDs[imageIdxWithPerformer], image.ID) } diff --git a/pkg/sqlite/performer_test.go b/pkg/sqlite/performer_test.go index c0124d09d..e0294f3e4 100644 --- a/pkg/sqlite/performer_test.go +++ b/pkg/sqlite/performer_test.go @@ -1330,7 +1330,7 @@ func verifyPerformerQuery(t *testing.T, filter models.PerformerFilterType, verif for _, performer := range performers { if err := performer.LoadURLs(ctx, db.Performer); err != nil { - t.Errorf("Error loading movie relationships: %v", err) + t.Errorf("Error loading url relationships: %v", err) } } diff --git a/pkg/sqlite/saved_filter.go b/pkg/sqlite/saved_filter.go index 49b1f45ed..8f58b05e7 100644 --- a/pkg/sqlite/saved_filter.go +++ b/pkg/sqlite/saved_filter.go @@ -228,10 +228,21 @@ func (qb *SavedFilterStore) getMany(ctx context.Context, q *goqu.SelectDataset) func (qb *SavedFilterStore) FindByMode(ctx context.Context, mode models.FilterMode) ([]*models.SavedFilter, error) { // SELECT * FROM %s WHERE mode = ? AND name != ? ORDER BY name ASC table := qb.table() - sq := qb.selectDataset().Prepared(true).Where( - table.Col("mode").Eq(mode), - table.Col("name").Neq(savedFilterDefaultName), - ).Order(table.Col("name").Asc()) + + // TODO - querying on groups needs to include movies + // remove this when we migrate to remove the movies filter mode in the database + var whereClause exp.Expression + + if mode == models.FilterModeGroups || mode == models.FilterModeMovies { + whereClause = goqu.Or( + table.Col("mode").Eq(models.FilterModeGroups), + table.Col("mode").Eq(models.FilterModeMovies), + ) + } else { + whereClause = table.Col("mode").Eq(mode) + } + + sq := qb.selectDataset().Prepared(true).Where(whereClause).Order(table.Col("name").Asc()) ret, err := qb.getMany(ctx, sq) if err != nil { diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index a6b73ac2e..fa0cb5e63 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -1074,6 +1074,7 @@ var sceneSortOptions = sortOptions{ "duration", "file_mod_time", "framerate", + "group_scene_number", "id", "interactive", "interactive_speed", @@ -1140,7 +1141,7 @@ func (qb *SceneStore) setSceneSort(query *queryBuilder, findFilter *models.FindF direction := findFilter.GetDirection() switch sort { - case "movie_scene_number": + case "movie_scene_number", "group_scene_number": query.join(moviesScenesTable, "", "scenes.id = movies_scenes.scene_id") query.sortAndPagination += getSort("scene_index", direction, moviesScenesTable) case "tag_count": diff --git a/pkg/sqlite/scene_filter.go b/pkg/sqlite/scene_filter.go index b9c219695..7c6e3f634 100644 --- a/pkg/sqlite/scene_filter.go +++ b/pkg/sqlite/scene_filter.go @@ -147,7 +147,10 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler { qb.performersCriterionHandler(sceneFilter.Performers), qb.performerCountCriterionHandler(sceneFilter.PerformerCount), studioCriterionHandler(sceneTable, sceneFilter.Studios), - qb.moviesCriterionHandler(sceneFilter.Movies), + + qb.groupsCriterionHandler(sceneFilter.Groups), + qb.groupsCriterionHandler(sceneFilter.Movies), + qb.galleriesCriterionHandler(sceneFilter.Galleries), qb.performerTagsCriterionHandler(sceneFilter.PerformerTags), qb.performerFavoriteCriterionHandler(sceneFilter.PerformerFavorite), @@ -480,7 +483,7 @@ func (qb *sceneFilterHandler) performerAgeCriterionHandler(performerAge *models. } } -func (qb *sceneFilterHandler) moviesCriterionHandler(movies *models.MultiCriterionInput) criterionHandlerFunc { +func (qb *sceneFilterHandler) groupsCriterionHandler(movies *models.MultiCriterionInput) criterionHandlerFunc { addJoinsFunc := func(f *filterBuilder) { sceneRepository.movies.join(f, "", "scenes.id") f.addLeftJoin("movies", "", "movies_scenes.movie_id = movies.id") diff --git a/pkg/sqlite/setup_test.go b/pkg/sqlite/setup_test.go index 4a6e3edb4..b8ce23cd6 100644 --- a/pkg/sqlite/setup_test.go +++ b/pkg/sqlite/setup_test.go @@ -278,9 +278,7 @@ const ( ) const ( - savedFilterIdxDefaultScene = iota - savedFilterIdxDefaultImage - savedFilterIdxScene + savedFilterIdxScene = iota savedFilterIdxImage // new indexes above @@ -1777,9 +1775,9 @@ func createChapter(ctx context.Context, mqb models.GalleryChapterReaderWriter, c func getSavedFilterMode(index int) models.FilterMode { switch index { - case savedFilterIdxScene, savedFilterIdxDefaultScene: + case savedFilterIdxScene: return models.FilterModeScenes - case savedFilterIdxImage, savedFilterIdxDefaultImage: + case savedFilterIdxImage: return models.FilterModeImages default: return models.FilterModeScenes @@ -1787,11 +1785,6 @@ func getSavedFilterMode(index int) models.FilterMode { } func getSavedFilterName(index int) string { - if index <= savedFilterIdxDefaultImage { - // empty string for default filters - return "" - } - if index <= savedFilterIdxImage { // use the same name for the first two - should be possible return firstSavedFilterName diff --git a/pkg/sqlite/tag.go b/pkg/sqlite/tag.go index c6494f38b..69dc086ea 100644 --- a/pkg/sqlite/tag.go +++ b/pkg/sqlite/tag.go @@ -683,7 +683,7 @@ func (qb *TagStore) getTagSort(query *queryBuilder, findFilter *models.FindFilte sortQuery += getCountSort(tagTable, performersTagsTable, tagIDColumn, direction) case "studios_count": sortQuery += getCountSort(tagTable, studiosTagsTable, tagIDColumn, direction) - case "movies_count": + case "movies_count", "groups_count": sortQuery += getCountSort(tagTable, moviesTagsTable, tagIDColumn, direction) default: sortQuery += getSort(sort, direction, "tags") diff --git a/pkg/sqlite/tag_filter.go b/pkg/sqlite/tag_filter.go index 5bae18c00..c2fd1723f 100644 --- a/pkg/sqlite/tag_filter.go +++ b/pkg/sqlite/tag_filter.go @@ -67,7 +67,10 @@ func (qb *tagFilterHandler) criterionHandler() criterionHandler { qb.galleryCountCriterionHandler(tagFilter.GalleryCount), qb.performerCountCriterionHandler(tagFilter.PerformerCount), qb.studioCountCriterionHandler(tagFilter.StudioCount), - qb.movieCountCriterionHandler(tagFilter.MovieCount), + + qb.groupCountCriterionHandler(tagFilter.GroupCount), + qb.groupCountCriterionHandler(tagFilter.MovieCount), + qb.markerCountCriterionHandler(tagFilter.MarkerCount), qb.parentsCriterionHandler(tagFilter.Parents), qb.childrenCriterionHandler(tagFilter.Children), @@ -187,7 +190,7 @@ func (qb *tagFilterHandler) studioCountCriterionHandler(studioCount *models.IntC } } -func (qb *tagFilterHandler) movieCountCriterionHandler(movieCount *models.IntCriterionInput) criterionHandlerFunc { +func (qb *tagFilterHandler) groupCountCriterionHandler(movieCount *models.IntCriterionInput) criterionHandlerFunc { return func(ctx context.Context, f *filterBuilder) { if movieCount != nil { f.addLeftJoin("movies_tags", "", "movies_tags.tag_id = tags.id") diff --git a/ui/v2.5/graphql/data/movie-slim.graphql b/ui/v2.5/graphql/data/group-slim.graphql similarity index 63% rename from ui/v2.5/graphql/data/movie-slim.graphql rename to ui/v2.5/graphql/data/group-slim.graphql index 2db5b80bd..ddb18d4e2 100644 --- a/ui/v2.5/graphql/data/movie-slim.graphql +++ b/ui/v2.5/graphql/data/group-slim.graphql @@ -1,11 +1,11 @@ -fragment SlimMovieData on Movie { +fragment SlimGroupData on Group { id name front_image_path rating100 } -fragment SelectMovieData on Movie { +fragment SelectGroupData on Group { id name aliases diff --git a/ui/v2.5/graphql/data/movie.graphql b/ui/v2.5/graphql/data/group.graphql similarity index 88% rename from ui/v2.5/graphql/data/movie.graphql rename to ui/v2.5/graphql/data/group.graphql index b94450f28..60f55e309 100644 --- a/ui/v2.5/graphql/data/movie.graphql +++ b/ui/v2.5/graphql/data/group.graphql @@ -1,4 +1,4 @@ -fragment MovieData on Movie { +fragment GroupData on Group { id name aliases diff --git a/ui/v2.5/graphql/data/performer.graphql b/ui/v2.5/graphql/data/performer.graphql index 91393f39e..144382a45 100644 --- a/ui/v2.5/graphql/data/performer.graphql +++ b/ui/v2.5/graphql/data/performer.graphql @@ -23,7 +23,7 @@ fragment PerformerData on Performer { scene_count image_count gallery_count - movie_count + group_count performer_count o_counter diff --git a/ui/v2.5/graphql/data/scene-slim.graphql b/ui/v2.5/graphql/data/scene-slim.graphql index c24eb9752..7e2a4ffad 100644 --- a/ui/v2.5/graphql/data/scene-slim.graphql +++ b/ui/v2.5/graphql/data/scene-slim.graphql @@ -58,8 +58,8 @@ fragment SlimSceneData on Scene { image_path } - movies { - movie { + groups { + group { id name front_image_path diff --git a/ui/v2.5/graphql/data/scene.graphql b/ui/v2.5/graphql/data/scene.graphql index 2b9ef76a3..ef5892229 100644 --- a/ui/v2.5/graphql/data/scene.graphql +++ b/ui/v2.5/graphql/data/scene.graphql @@ -53,9 +53,9 @@ fragment SceneData on Scene { ...SlimStudioData } - movies { - movie { - ...MovieData + groups { + group { + ...GroupData } scene_index } diff --git a/ui/v2.5/graphql/data/scrapers.graphql b/ui/v2.5/graphql/data/scrapers.graphql index a68bb5c70..7e12610a0 100644 --- a/ui/v2.5/graphql/data/scrapers.graphql +++ b/ui/v2.5/graphql/data/scrapers.graphql @@ -73,13 +73,13 @@ fragment ScrapedScenePerformerData on ScrapedPerformer { weight } -fragment ScrapedMovieStudioData on ScrapedStudio { +fragment ScrapedGroupStudioData on ScrapedStudio { stored_id name url } -fragment ScrapedMovieData on ScrapedMovie { +fragment ScrapedGroupData on ScrapedGroup { name aliases duration @@ -92,14 +92,14 @@ fragment ScrapedMovieData on ScrapedMovie { back_image studio { - ...ScrapedMovieStudioData + ...ScrapedGroupStudioData } tags { ...ScrapedSceneTagData } } -fragment ScrapedSceneMovieData on ScrapedMovie { +fragment ScrapedSceneGroupData on ScrapedGroup { stored_id name aliases @@ -113,7 +113,7 @@ fragment ScrapedSceneMovieData on ScrapedMovie { back_image studio { - ...ScrapedMovieStudioData + ...ScrapedGroupStudioData } tags { ...ScrapedSceneTagData @@ -173,8 +173,8 @@ fragment ScrapedSceneData on ScrapedScene { ...ScrapedScenePerformerData } - movies { - ...ScrapedSceneMovieData + groups { + ...ScrapedSceneGroupData } fingerprints { @@ -245,8 +245,8 @@ fragment ScrapedStashBoxSceneData on ScrapedScene { ...ScrapedScenePerformerData } - movies { - ...ScrapedSceneMovieData + groups { + ...ScrapedSceneGroupData } } diff --git a/ui/v2.5/graphql/data/studio.graphql b/ui/v2.5/graphql/data/studio.graphql index afd254d22..feb35136f 100644 --- a/ui/v2.5/graphql/data/studio.graphql +++ b/ui/v2.5/graphql/data/studio.graphql @@ -23,8 +23,8 @@ fragment StudioData on Studio { gallery_count_all: gallery_count(depth: -1) performer_count performer_count_all: performer_count(depth: -1) - movie_count - movie_count_all: movie_count(depth: -1) + group_count + group_count_all: group_count(depth: -1) stash_ids { stash_id endpoint diff --git a/ui/v2.5/graphql/data/tag.graphql b/ui/v2.5/graphql/data/tag.graphql index 695bb5de6..b0501de69 100644 --- a/ui/v2.5/graphql/data/tag.graphql +++ b/ui/v2.5/graphql/data/tag.graphql @@ -18,8 +18,8 @@ fragment TagData on Tag { performer_count_all: performer_count(depth: -1) studio_count studio_count_all: studio_count(depth: -1) - movie_count - movie_count_all: movie_count(depth: -1) + group_count + group_count_all: group_count(depth: -1) parents { ...SlimTagData diff --git a/ui/v2.5/graphql/mutations/group.graphql b/ui/v2.5/graphql/mutations/group.graphql new file mode 100644 index 000000000..fb739e840 --- /dev/null +++ b/ui/v2.5/graphql/mutations/group.graphql @@ -0,0 +1,25 @@ +mutation GroupCreate($input: GroupCreateInput!) { + groupCreate(input: $input) { + ...GroupData + } +} + +mutation GroupUpdate($input: GroupUpdateInput!) { + groupUpdate(input: $input) { + ...GroupData + } +} + +mutation BulkGroupUpdate($input: BulkGroupUpdateInput!) { + bulkGroupUpdate(input: $input) { + ...GroupData + } +} + +mutation GroupDestroy($id: ID!) { + groupDestroy(input: { id: $id }) +} + +mutation GroupsDestroy($ids: [ID!]!) { + groupsDestroy(ids: $ids) +} diff --git a/ui/v2.5/graphql/mutations/movie.graphql b/ui/v2.5/graphql/mutations/movie.graphql deleted file mode 100644 index 1eebae15c..000000000 --- a/ui/v2.5/graphql/mutations/movie.graphql +++ /dev/null @@ -1,25 +0,0 @@ -mutation MovieCreate($input: MovieCreateInput!) { - movieCreate(input: $input) { - ...MovieData - } -} - -mutation MovieUpdate($input: MovieUpdateInput!) { - movieUpdate(input: $input) { - ...MovieData - } -} - -mutation BulkMovieUpdate($input: BulkMovieUpdateInput!) { - bulkMovieUpdate(input: $input) { - ...MovieData - } -} - -mutation MovieDestroy($id: ID!) { - movieDestroy(input: { id: $id }) -} - -mutation MoviesDestroy($ids: [ID!]!) { - moviesDestroy(ids: $ids) -} diff --git a/ui/v2.5/graphql/queries/misc.graphql b/ui/v2.5/graphql/queries/misc.graphql index 9367f0cc2..91aa5f15d 100644 --- a/ui/v2.5/graphql/queries/misc.graphql +++ b/ui/v2.5/graphql/queries/misc.graphql @@ -16,7 +16,7 @@ query Stats { gallery_count performer_count studio_count - movie_count + group_count tag_count total_o_count total_play_duration diff --git a/ui/v2.5/graphql/queries/movie.graphql b/ui/v2.5/graphql/queries/movie.graphql index 088629b87..ad47e908d 100644 --- a/ui/v2.5/graphql/queries/movie.graphql +++ b/ui/v2.5/graphql/queries/movie.graphql @@ -1,27 +1,27 @@ -query FindMovies($filter: FindFilterType, $movie_filter: MovieFilterType) { - findMovies(filter: $filter, movie_filter: $movie_filter) { +query FindGroups($filter: FindFilterType, $group_filter: GroupFilterType) { + findGroups(filter: $filter, group_filter: $group_filter) { count - movies { - ...MovieData + groups { + ...GroupData } } } -query FindMovie($id: ID!) { - findMovie(id: $id) { - ...MovieData +query FindGroup($id: ID!) { + findGroup(id: $id) { + ...GroupData } } -query FindMoviesForSelect( +query FindGroupsForSelect( $filter: FindFilterType - $movie_filter: MovieFilterType + $group_filter: GroupFilterType $ids: [ID!] ) { - findMovies(filter: $filter, movie_filter: $movie_filter, ids: $ids) { + findGroups(filter: $filter, group_filter: $group_filter, ids: $ids) { count - movies { - ...SelectMovieData + groups { + ...SelectGroupData } } } diff --git a/ui/v2.5/graphql/queries/scrapers/scrapers.graphql b/ui/v2.5/graphql/queries/scrapers/scrapers.graphql index 366938fd4..37e5a3a4a 100644 --- a/ui/v2.5/graphql/queries/scrapers/scrapers.graphql +++ b/ui/v2.5/graphql/queries/scrapers/scrapers.graphql @@ -31,11 +31,11 @@ query ListGalleryScrapers { } } -query ListMovieScrapers { - listScrapers(types: [MOVIE]) { +query ListGroupScrapers { + listScrapers(types: [GROUP]) { id name - movie { + group { urls supported_scrapes } @@ -114,9 +114,9 @@ query ScrapeGalleryURL($url: String!) { } } -query ScrapeMovieURL($url: String!) { - scrapeMovieURL(url: $url) { - ...ScrapedMovieData +query ScrapeGroupURL($url: String!) { + scrapeGroupURL(url: $url) { + ...ScrapedGroupData } } diff --git a/ui/v2.5/src/components/FrontPage/Control.tsx b/ui/v2.5/src/components/FrontPage/Control.tsx index f06fbb756..495fbc852 100644 --- a/ui/v2.5/src/components/FrontPage/Control.tsx +++ b/ui/v2.5/src/components/FrontPage/Control.tsx @@ -44,6 +44,7 @@ const RecommendationRow: React.FC = ({ mode, filter, header }) => { /> ); case GQL.FilterMode.Movies: + case GQL.FilterMode.Groups: return ( void; } @@ -39,32 +39,32 @@ export const EditGroupsDialog: React.FC = ( const [tagIds, setTagIds] = useState(); const [existingTagIds, setExistingTagIds] = useState(); - const [updateMovies] = useBulkMovieUpdate(getMovieInput()); + const [updateGroups] = useBulkGroupUpdate(getGroupInput()); const [isUpdating, setIsUpdating] = useState(false); - function getMovieInput(): GQL.BulkMovieUpdateInput { + function getGroupInput(): GQL.BulkGroupUpdateInput { const aggregateRating = getAggregateRating(props.selected); const aggregateStudioId = getAggregateStudioId(props.selected); const aggregateTagIds = getAggregateTagIds(props.selected); - const movieInput: GQL.BulkMovieUpdateInput = { - ids: props.selected.map((movie) => movie.id), + const groupInput: GQL.BulkGroupUpdateInput = { + ids: props.selected.map((group) => group.id), director, }; // if rating is undefined - movieInput.rating100 = getAggregateInputValue(rating100, aggregateRating); - movieInput.studio_id = getAggregateInputValue(studioId, aggregateStudioId); - movieInput.tag_ids = getAggregateInputIDs(tagMode, tagIds, aggregateTagIds); + groupInput.rating100 = getAggregateInputValue(rating100, aggregateRating); + groupInput.studio_id = getAggregateInputValue(studioId, aggregateStudioId); + groupInput.tag_ids = getAggregateInputIDs(tagMode, tagIds, aggregateTagIds); - return movieInput; + return groupInput; } async function onSave() { setIsUpdating(true); try { - await updateMovies(); + await updateGroups(); Toast.success( intl.formatMessage( { id: "toast.updated_entity" }, @@ -88,26 +88,26 @@ export const EditGroupsDialog: React.FC = ( let updateDirector: string | undefined; let first = true; - state.forEach((movie: GQL.MovieDataFragment) => { - const movieTagIDs = (movie.tags ?? []).map((p) => p.id).sort(); + state.forEach((group: GQL.GroupDataFragment) => { + const groupTagIDs = (group.tags ?? []).map((p) => p.id).sort(); if (first) { first = false; - updateRating = movie.rating100 ?? undefined; - updateStudioId = movie.studio?.id ?? undefined; - updateTagIds = movieTagIDs; - updateDirector = movie.director ?? undefined; + updateRating = group.rating100 ?? undefined; + updateStudioId = group.studio?.id ?? undefined; + updateTagIds = groupTagIDs; + updateDirector = group.director ?? undefined; } else { - if (movie.rating100 !== updateRating) { + if (group.rating100 !== updateRating) { updateRating = undefined; } - if (movie.studio?.id !== updateStudioId) { + if (group.studio?.id !== updateStudioId) { updateStudioId = undefined; } - if (movie.director !== updateDirector) { + if (group.director !== updateDirector) { updateDirector = undefined; } - if (!isEqual(movieTagIDs, updateTagIds)) { + if (!isEqual(groupTagIDs, updateTagIds)) { updateTagIds = []; } } diff --git a/ui/v2.5/src/components/Movies/MovieCard.tsx b/ui/v2.5/src/components/Movies/MovieCard.tsx index 739761251..ff8426253 100644 --- a/ui/v2.5/src/components/Movies/MovieCard.tsx +++ b/ui/v2.5/src/components/Movies/MovieCard.tsx @@ -12,7 +12,7 @@ import { faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons"; import ScreenUtils from "src/utils/screen"; interface IProps { - group: GQL.MovieDataFragment; + group: GQL.GroupDataFragment; containerWidth?: number; sceneIndex?: number; selecting?: boolean; diff --git a/ui/v2.5/src/components/Movies/MovieCardGrid.tsx b/ui/v2.5/src/components/Movies/MovieCardGrid.tsx index 52cbc0f53..a08ec58fe 100644 --- a/ui/v2.5/src/components/Movies/MovieCardGrid.tsx +++ b/ui/v2.5/src/components/Movies/MovieCardGrid.tsx @@ -4,7 +4,7 @@ import { GroupCard } from "./MovieCard"; import { useContainerDimensions } from "../Shared/GridCard/GridCard"; interface IGroupCardGrid { - groups: GQL.MovieDataFragment[]; + groups: GQL.GroupDataFragment[]; selectedIds: Set; onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void; } diff --git a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx index 686a92b39..731818da4 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx @@ -6,9 +6,9 @@ import cx from "classnames"; import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; import { - useFindMovie, - useMovieUpdate, - useMovieDestroy, + useFindGroup, + useGroupUpdate, + useGroupDestroy, } from "src/core/StashService"; import { useHistory, RouteComponentProps } from "react-router-dom"; import { DetailsEditNavbar } from "src/components/Shared/DetailsEditNavbar"; @@ -19,7 +19,7 @@ import { ModalComponent } from "src/components/Shared/Modal"; import { useToast } from "src/hooks/Toast"; import { GroupScenesPanel } from "./MovieScenesPanel"; import { - CompressedMovieDetailsPanel, + CompressedGroupDetailsPanel, GroupDetailsPanel, } from "./MovieDetailsPanel"; import { GroupEditPanel } from "./MovieEditPanel"; @@ -38,7 +38,7 @@ import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; import { ExternalLinksButton } from "src/components/Shared/ExternalLinksButton"; interface IProps { - group: GQL.MovieDataFragment; + group: GQL.GroupDataFragment; } interface IGroupParams { @@ -64,7 +64,7 @@ const GroupPage: React.FC = ({ group }) => { const [isEditing, setIsEditing] = useState(false); const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); - // Editing movie state + // Editing group state const [frontImage, setFrontImage] = useState(); const [backImage, setBackImage] = useState(); const [encodingImage, setEncodingImage] = useState(false); @@ -106,8 +106,8 @@ const GroupPage: React.FC = ({ group }) => { images: lightboxImages, }); - const [updateMovie, { loading: updating }] = useMovieUpdate(); - const [deleteMovie, { loading: deleting }] = useMovieDestroy({ + const [updateGroup, { loading: updating }] = useGroupUpdate(); + const [deleteGroup, { loading: deleting }] = useGroupDestroy({ id: group.id, }); @@ -131,8 +131,8 @@ const GroupPage: React.FC = ({ group }) => { setRating ); - async function onSave(input: GQL.MovieCreateInput) { - await updateMovie({ + async function onSave(input: GQL.GroupCreateInput) { + await updateGroup({ variables: { input: { id: group.id, @@ -151,12 +151,12 @@ const GroupPage: React.FC = ({ group }) => { async function onDelete() { try { - await deleteMovie(); + await deleteGroup(); } catch (e) { Toast.error(e); } - // redirect to movies page + // redirect to groups page history.push(`/groups`); } @@ -287,7 +287,7 @@ const GroupPage: React.FC = ({ group }) => { function setRating(v: number | null) { if (group.id) { - updateMovie({ + updateGroup({ variables: { input: { id: group.id, @@ -343,7 +343,7 @@ const GroupPage: React.FC = ({ group }) => { function maybeRenderCompressedDetails() { if (!isEditing && loadStickyHeader) { - return ; + return ; } } @@ -441,16 +441,16 @@ const GroupLoader: React.FC> = ({ match, }) => { const { id } = match.params; - const { data, loading, error } = useFindMovie(id); + const { data, loading, error } = useFindGroup(id); useScrollToTopOnMount(); if (loading) return ; if (error) return ; - if (!data?.findMovie) - return ; + if (!data?.findGroup) + return ; - return ; + return ; }; export default GroupLoader; diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieCreate.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieCreate.tsx index 2f65463c9..5d15afbd0 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieCreate.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieCreate.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from "react"; import * as GQL from "src/core/generated-graphql"; -import { useMovieCreate } from "src/core/StashService"; +import { useGroupCreate } from "src/core/StashService"; import { useHistory, useLocation } from "react-router-dom"; import { useIntl } from "react-intl"; import { LoadingIndicator } from "src/components/Shared/LoadingIndicator"; @@ -18,23 +18,23 @@ const GroupCreate: React.FC = () => { name: query.get("q") ?? undefined, }; - // Editing movie state + // Editing group state const [frontImage, setFrontImage] = useState(); const [backImage, setBackImage] = useState(); const [encodingImage, setEncodingImage] = useState(false); - const [createMovie] = useMovieCreate(); + const [createGroup] = useGroupCreate(); - async function onSave(input: GQL.MovieCreateInput) { - const result = await createMovie({ + async function onSave(input: GQL.GroupCreateInput) { + const result = await createGroup({ variables: { input }, }); - if (result.data?.movieCreate?.id) { - history.push(`/groups/${result.data.movieCreate.id}`); + if (result.data?.groupCreate?.id) { + history.push(`/groups/${result.data.groupCreate.id}`); Toast.success( intl.formatMessage( { id: "toast.created_entity" }, - { entity: intl.formatMessage({ id: "gallery" }).toLocaleLowerCase() } + { entity: intl.formatMessage({ id: "group" }).toLocaleLowerCase() } ) ); } diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx index 8f911c08c..eb3696550 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx @@ -8,7 +8,7 @@ import { DirectorLink } from "src/components/Shared/Link"; import { TagLink } from "src/components/Shared/TagLink"; interface IGroupDetailsPanel { - group: GQL.MovieDataFragment; + group: GQL.GroupDataFragment; collapsed?: boolean; fullWidth?: boolean; } @@ -97,7 +97,7 @@ export const GroupDetailsPanel: React.FC = ({ ); }; -export const CompressedMovieDetailsPanel: React.FC = ({ +export const CompressedGroupDetailsPanel: React.FC = ({ group, }) => { function scrollToTop() { diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx index 0a281df51..5b8584f6a 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx @@ -4,8 +4,8 @@ import * as GQL from "src/core/generated-graphql"; import * as yup from "yup"; import Mousetrap from "mousetrap"; import { - queryScrapeMovieURL, - useListMovieScrapers, + queryScrapeGroupURL, + useListGroupScrapers, } from "src/core/StashService"; import { LoadingIndicator } from "src/components/Shared/LoadingIndicator"; import { DetailsEditNavbar } from "src/components/Shared/DetailsEditNavbar"; @@ -28,8 +28,8 @@ import { Studio, StudioSelect } from "src/components/Studios/StudioSelect"; import { useTagsEdit } from "src/hooks/tagsEdit"; interface IGroupEditPanel { - group: Partial; - onSubmit: (movie: GQL.MovieCreateInput) => Promise; + group: Partial; + onSubmit: (group: GQL.GroupCreateInput) => Promise; onCancel: () => void; onDelete: () => void; setFrontImage: (image?: string | null) => void; @@ -56,8 +56,8 @@ export const GroupEditPanel: React.FC = ({ const [imageClipboard, setImageClipboard] = useState(); - const Scrapers = useListMovieScrapers(); - const [scrapedGroup, setScrapedGroup] = useState(); + const Scrapers = useListGroupScrapers(); + const [scrapedGroup, setScrapedGroup] = useState(); const [studio, setStudio] = useState(null); @@ -129,7 +129,7 @@ export const GroupEditPanel: React.FC = ({ }); function updateGroupEditStateFromScraper( - state: Partial + state: Partial ) { if (state.name) { formik.setFieldValue("name", state.name); @@ -190,21 +190,21 @@ export const GroupEditPanel: React.FC = ({ setIsLoading(false); } - async function onScrapeMovieURL(url: string) { + async function onScrapeGroupURL(url: string) { if (!url) return; setIsLoading(true); try { - const result = await queryScrapeMovieURL(url); - if (!result.data || !result.data.scrapeMovieURL) { + const result = await queryScrapeGroupURL(url); + if (!result.data || !result.data.scrapeGroupURL) { return; } // if this is a new group, just dump the data if (isNew) { - updateGroupEditStateFromScraper(result.data.scrapeMovieURL); + updateGroupEditStateFromScraper(result.data.scrapeGroupURL); } else { - setScrapedGroup(result.data.scrapeMovieURL); + setScrapedGroup(result.data.scrapeGroupURL); } } catch (e) { Toast.error(e); @@ -217,7 +217,7 @@ export const GroupEditPanel: React.FC = ({ return ( !!scrapedUrl && (Scrapers?.data?.listScrapers ?? []).some((s) => - (s?.movie?.urls ?? []).some((u) => scrapedUrl.includes(u)) + (s?.group?.urls ?? []).some((u) => scrapedUrl.includes(u)) ) ); } @@ -249,7 +249,7 @@ export const GroupEditPanel: React.FC = ({ ); } - function onScrapeDialogClosed(p?: GQL.ScrapedMovieDataFragment) { + function onScrapeDialogClosed(p?: GQL.ScrapedGroupDataFragment) { if (p) { updateGroupEditStateFromScraper(p); } @@ -381,7 +381,7 @@ export const GroupEditPanel: React.FC = ({ { - // Check if it's a redirect after movie creation + // Check if it's a redirect after group creation if (action === "PUSH" && location.pathname.startsWith("/groups/")) return true; @@ -396,7 +396,7 @@ export const GroupEditPanel: React.FC = ({ {renderDateField("date")} {renderStudioField()} {renderInputField("director")} - {renderURLListField("urls", onScrapeMovieURL, urlScrapable)} + {renderURLListField("urls", onScrapeGroupURL, urlScrapable)} {renderInputField("synopsis", "textarea")} {renderTagsField()} diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieScenesPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieScenesPanel.tsx index 6ab34bfca..deb1f31a7 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieScenesPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieScenesPanel.tsx @@ -1,13 +1,13 @@ import React from "react"; import * as GQL from "src/core/generated-graphql"; -import { MoviesCriterion } from "src/models/list-filter/criteria/movies"; +import { GroupsCriterion as GroupsCriterion } from "src/models/list-filter/criteria/movies"; import { ListFilterModel } from "src/models/list-filter/filter"; import { SceneList } from "src/components/Scenes/SceneList"; import { View } from "src/components/List/views"; interface IGroupScenesPanel { active: boolean; - group: GQL.MovieDataFragment; + group: GQL.GroupDataFragment; } export const GroupScenesPanel: React.FC = ({ @@ -15,32 +15,32 @@ export const GroupScenesPanel: React.FC = ({ group, }) => { function filterHook(filter: ListFilterModel) { - const movieValue = { id: group.id, label: group.name }; - // if movie is already present, then we modify it, otherwise add - let movieCriterion = filter.criteria.find((c) => { - return c.criterionOption.type === "movies"; - }) as MoviesCriterion | undefined; + const groupValue = { id: group.id, label: group.name }; + // if group is already present, then we modify it, otherwise add + let groupCriterion = filter.criteria.find((c) => { + return c.criterionOption.type === "groups"; + }) as GroupsCriterion | undefined; if ( - movieCriterion && - (movieCriterion.modifier === GQL.CriterionModifier.IncludesAll || - movieCriterion.modifier === GQL.CriterionModifier.Includes) + groupCriterion && + (groupCriterion.modifier === GQL.CriterionModifier.IncludesAll || + groupCriterion.modifier === GQL.CriterionModifier.Includes) ) { - // add the movie if not present + // add the group if not present if ( - !movieCriterion.value.find((p) => { + !groupCriterion.value.find((p) => { return p.id === group.id; }) ) { - movieCriterion.value.push(movieValue); + groupCriterion.value.push(groupValue); } - movieCriterion.modifier = GQL.CriterionModifier.IncludesAll; + groupCriterion.modifier = GQL.CriterionModifier.IncludesAll; } else { // overwrite - movieCriterion = new MoviesCriterion(); - movieCriterion.value = [movieValue]; - filter.criteria.push(movieCriterion); + groupCriterion = new GroupsCriterion(); + groupCriterion.value = [groupValue]; + filter.criteria.push(groupCriterion); } return filter; @@ -50,7 +50,7 @@ export const GroupScenesPanel: React.FC = ({ return ( diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieScrapeDialog.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieScrapeDialog.tsx index 644564112..bdb5d6ad5 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieScrapeDialog.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieScrapeDialog.tsx @@ -21,12 +21,12 @@ import { Tag } from "src/components/Tags/TagSelect"; import { useScrapedTags } from "src/components/Shared/ScrapeDialog/scrapedTags"; interface IGroupScrapeDialogProps { - group: Partial; + group: Partial; groupStudio: Studio | null; groupTags: Tag[]; - scraped: GQL.ScrapedMovie; + scraped: GQL.ScrapedGroup; - onClose: (scrapedMovie?: GQL.ScrapedMovie) => void; + onClose: (scrapedGroup?: GQL.ScrapedGroup) => void; } export const GroupScrapeDialog: React.FC = ({ @@ -126,7 +126,7 @@ export const GroupScrapeDialog: React.FC = ({ return <>; } - function makeNewScrapedItem(): GQL.ScrapedMovie { + function makeNewScrapedItem(): GQL.ScrapedGroup { const newStudioValue = studio.getNewValue(); const durationString = duration.getNewValue(); diff --git a/ui/v2.5/src/components/Movies/MovieList.tsx b/ui/v2.5/src/components/Movies/MovieList.tsx index d4d4bf1c9..28c4baadd 100644 --- a/ui/v2.5/src/components/Movies/MovieList.tsx +++ b/ui/v2.5/src/components/Movies/MovieList.tsx @@ -7,9 +7,9 @@ import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; import * as GQL from "src/core/generated-graphql"; import { - queryFindMovies, - useFindMovies, - useMoviesDestroy, + queryFindGroups, + useFindGroups, + useGroupsDestroy, } from "src/core/StashService"; import { makeItemList, showWhenSelected } from "../List/ItemList"; import { ExportDialog } from "../Shared/ExportDialog"; @@ -19,13 +19,13 @@ import { EditGroupsDialog } from "./EditMoviesDialog"; import { View } from "../List/views"; const GroupItemList = makeItemList({ - filterMode: GQL.FilterMode.Movies, - useResult: useFindMovies, - getItems(result: GQL.FindMoviesQueryResult) { - return result?.data?.findMovies?.movies ?? []; + filterMode: GQL.FilterMode.Groups, + useResult: useFindGroups, + getItems(result: GQL.FindGroupsQueryResult) { + return result?.data?.findGroups?.groups ?? []; }, - getCount(result: GQL.FindMoviesQueryResult) { - return result?.data?.findMovies?.count ?? 0; + getCount(result: GQL.FindGroupsQueryResult) { + return result?.data?.findGroups?.count ?? 0; }, }); @@ -62,7 +62,7 @@ export const GroupList: React.FC = ({ ]; function addKeybinds( - result: GQL.FindMoviesQueryResult, + result: GQL.FindGroupsQueryResult, filter: ListFilterModel ) { Mousetrap.bind("p r", () => { @@ -75,21 +75,21 @@ export const GroupList: React.FC = ({ } async function viewRandom( - result: GQL.FindMoviesQueryResult, + result: GQL.FindGroupsQueryResult, filter: ListFilterModel ) { // query for a random image - if (result.data?.findMovies) { - const { count } = result.data.findMovies; + if (result.data?.findGroups) { + const { count } = result.data.findGroups; const index = Math.floor(Math.random() * count); const filterCopy = cloneDeep(filter); filterCopy.itemsPerPage = 1; filterCopy.currentPage = index + 1; - const singleResult = await queryFindMovies(filterCopy); - if (singleResult.data.findMovies.movies.length === 1) { - const { id } = singleResult.data.findMovies.movies[0]; - // navigate to the movie page + const singleResult = await queryFindGroups(filterCopy); + if (singleResult.data.findGroups.groups.length === 1) { + const { id } = singleResult.data.findGroups.groups[0]; + // navigate to the group page history.push(`/groups/${id}`); } } @@ -106,7 +106,7 @@ export const GroupList: React.FC = ({ } function renderContent( - result: GQL.FindMoviesQueryResult, + result: GQL.FindGroupsQueryResult, filter: ListFilterModel, selectedIds: Set, onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void @@ -116,7 +116,7 @@ export const GroupList: React.FC = ({ return ( = ({ } function renderGroups() { - if (!result.data?.findMovies) return; + if (!result.data?.findGroups) return; if (filter.displayMode === DisplayMode.Grid) { return ( @@ -152,14 +152,14 @@ export const GroupList: React.FC = ({ } function renderEditDialog( - selectedGroups: GQL.MovieDataFragment[], + selectedGroups: GQL.GroupDataFragment[], onClose: (applied: boolean) => void ) { return ; } function renderDeleteDialog( - selectedGroups: GQL.SlimMovieDataFragment[], + selectedGroups: GQL.SlimGroupDataFragment[], onClose: (confirmed: boolean) => void ) { return ( @@ -168,7 +168,7 @@ export const GroupList: React.FC = ({ onClose={onClose} singularEntity={intl.formatMessage({ id: "group" })} pluralEntity={intl.formatMessage({ id: "groups" })} - destroyMutation={useMoviesDestroy} + destroyMutation={useGroupsDestroy} /> ); } diff --git a/ui/v2.5/src/components/Movies/MovieRecommendationRow.tsx b/ui/v2.5/src/components/Movies/MovieRecommendationRow.tsx index 9247d4ed9..5ecf36161 100644 --- a/ui/v2.5/src/components/Movies/MovieRecommendationRow.tsx +++ b/ui/v2.5/src/components/Movies/MovieRecommendationRow.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Link } from "react-router-dom"; -import { useFindMovies } from "src/core/StashService"; +import { useFindGroups } from "src/core/StashService"; import Slider from "@ant-design/react-slick"; import { GroupCard } from "./MovieCard"; import { ListFilterModel } from "src/models/list-filter/filter"; @@ -15,8 +15,8 @@ interface IProps { } export const GroupRecommendationRow: React.FC = (props: IProps) => { - const result = useFindMovies(props.filter); - const cardCount = result.data?.findMovies.count; + const result = useFindGroups(props.filter); + const cardCount = result.data?.findGroups.count; if (!result.loading && !cardCount) { return null; @@ -42,8 +42,8 @@ export const GroupRecommendationRow: React.FC = (props: IProps) => { ? [...Array(props.filter.itemsPerPage)].map((i) => (
)) - : result.data?.findMovies.movies.map((m) => ( - + : result.data?.findGroups.groups.map((g) => ( + ))} diff --git a/ui/v2.5/src/components/Movies/MovieSelect.tsx b/ui/v2.5/src/components/Movies/MovieSelect.tsx index 1aa791235..4f611e5e3 100644 --- a/ui/v2.5/src/components/Movies/MovieSelect.tsx +++ b/ui/v2.5/src/components/Movies/MovieSelect.tsx @@ -9,9 +9,9 @@ import cx from "classnames"; import * as GQL from "src/core/generated-graphql"; import { - queryFindMoviesForSelect, - queryFindMoviesByIDForSelect, - useMovieCreate, + queryFindGroupsForSelect, + queryFindGroupsByIDForSelect, + useGroupCreate, } from "src/core/StashService"; import { ConfigurationContext } from "src/hooks/Config"; import { useIntl } from "react-intl"; @@ -31,29 +31,29 @@ import { PatchComponent, PatchFunction } from "src/patch"; import { TruncatedText } from "../Shared/TruncatedText"; export type Group = Pick< - GQL.Movie, + GQL.Group, "id" | "name" | "date" | "front_image_path" | "aliases" > & { studio?: Pick | null; }; type Option = SelectOption; -type FindMoviesResult = Awaited< - ReturnType ->["data"]["findMovies"]["movies"]; +type FindGroupsResult = Awaited< + ReturnType +>["data"]["findGroups"]["groups"]; -function sortMoviesByRelevance(input: string, movies: FindMoviesResult) { +function sortGroupsByRelevance(input: string, groups: FindGroupsResult) { return sortByRelevance( input, - movies, + groups, (m) => m.name, (m) => (m.aliases ? [m.aliases] : []) ); } -const movieSelectSort = PatchFunction( - "MovieSelect.sort", - sortMoviesByRelevance +const groupSelectSort = PatchFunction( + "GroupSelect.sort", + sortGroupsByRelevance ); const _GroupSelect: React.FC< @@ -63,7 +63,7 @@ const _GroupSelect: React.FC< excludeIds?: string[]; } > = (props) => { - const [createMovie] = useMovieCreate(); + const [createGroup] = useGroupCreate(); const { configuration } = React.useContext(ConfigurationContext); const intl = useIntl(); @@ -74,23 +74,23 @@ const _GroupSelect: React.FC< const exclude = useMemo(() => props.excludeIds ?? [], [props.excludeIds]); - async function loadMovies(input: string): Promise { - const filter = new ListFilterModel(GQL.FilterMode.Movies); + async function loadGroups(input: string): Promise { + const filter = new ListFilterModel(GQL.FilterMode.Groups); filter.searchTerm = input; filter.currentPage = 1; filter.itemsPerPage = maxOptionsShown; filter.sortBy = "name"; filter.sortDirection = GQL.SortDirectionEnum.Asc; - const query = await queryFindMoviesForSelect(filter); - let ret = query.data.findMovies.movies.filter((movie) => { + const query = await queryFindGroupsForSelect(filter); + let ret = query.data.findGroups.groups.filter((group) => { // HACK - we should probably exclude these in the backend query, but // this will do in the short-term - return !exclude.includes(movie.id.toString()); + return !exclude.includes(group.id.toString()); }); - return movieSelectSort(input, ret).map((movie) => ({ - value: movie.id, - object: movie, + return groupSelectSort(input, ret).map((group) => ({ + value: group.id, + object: group, })); } @@ -184,12 +184,12 @@ const _GroupSelect: React.FC< }; const onCreate = async (name: string) => { - const result = await createMovie({ + const result = await createGroup({ variables: { input: { name } }, }); return { - value: result.data!.movieCreate!.id, - item: result.data!.movieCreate!, + value: result.data!.groupCreate!.id, + item: result.data!.groupCreate!, message: "Created group", }; }; @@ -230,7 +230,7 @@ const _GroupSelect: React.FC< }, props.className )} - loadOptions={loadMovies} + loadOptions={loadGroups} getNamedObject={getNamedObject} isValidNewOption={isValidNewOption} components={{ @@ -273,10 +273,10 @@ const _GroupIDSelect: React.FC> = ( } async function loadObjectsByID(idsToLoad: string[]): Promise { - const query = await queryFindMoviesByIDForSelect(idsToLoad); - const { movies: loadedMovies } = query.data.findMovies; + const query = await queryFindGroupsByIDForSelect(idsToLoad); + const { groups: loadedGroups } = query.data.findGroups; - return loadedMovies; + return loadedGroups; } useEffect(() => { diff --git a/ui/v2.5/src/components/Performers/PerformerCard.tsx b/ui/v2.5/src/components/Performers/PerformerCard.tsx index 3d5765ada..eae5d121c 100644 --- a/ui/v2.5/src/components/Performers/PerformerCard.tsx +++ b/ui/v2.5/src/components/Performers/PerformerCard.tsx @@ -28,7 +28,7 @@ export interface IPerformerCardExtraCriteria { scenes?: Criterion[]; images?: Criterion[]; galleries?: Criterion[]; - movies?: Criterion[]; + groups?: Criterion[]; performer?: ILabeledId; } @@ -179,17 +179,17 @@ export const PerformerCard: React.FC = ({ } function maybeRenderGroupsPopoverButton() { - if (!performer.movie_count) return; + if (!performer.group_count) return; return ( ); @@ -202,7 +202,7 @@ export const PerformerCard: React.FC = ({ performer.gallery_count || performer.tags.length > 0 || performer.o_counter || - performer.movie_count + performer.group_count ) { return ( <> diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 2c19fa775..6d67ec888 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -145,7 +145,7 @@ const PerformerPage: React.FC = ({ performer, tabKey }) => { ret = "galleries"; } else if (performer.image_count != 0) { ret = "images"; - } else if (performer.movie_count != 0) { + } else if (performer.group_count != 0) { ret = "groups"; } } @@ -325,7 +325,7 @@ const PerformerPage: React.FC = ({ performer, tabKey }) => { {intl.formatMessage({ id: "groups" })} diff --git a/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx b/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx index 9ee04ac68..5f4f5e44d 100644 --- a/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx +++ b/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx @@ -387,23 +387,23 @@ export const SceneDuplicateChecker: React.FC = () => { } function maybeRenderGroupPopoverButton(scene: GQL.SlimSceneDataFragment) { - if (scene.movies.length <= 0) return; + if (scene.groups.length <= 0) return; - const popoverContent = scene.movies.map((sceneMovie) => ( -
+ const popoverContent = scene.groups.map((sceneGroup) => ( +
{sceneMovie.movie.name
@@ -417,7 +417,7 @@ export const SceneDuplicateChecker: React.FC = () => { > ); @@ -511,7 +511,7 @@ export const SceneDuplicateChecker: React.FC = () => { if ( scene.tags.length > 0 || scene.performers.length > 0 || - scene.movies.length > 0 || + scene.groups.length > 0 || scene.scene_markers.length > 0 || scene?.o_counter || scene.galleries.length > 0 || diff --git a/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx b/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx index f4fc8a1e2..06f3549a3 100644 --- a/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx +++ b/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx @@ -79,7 +79,7 @@ export const EditScenesDialog: React.FC = ( aggregatePerformerIds ); sceneInput.tag_ids = getAggregateInputIDs(tagMode, tagIds, aggregateTagIds); - sceneInput.movie_ids = getAggregateInputIDs( + sceneInput.group_ids = getAggregateInputIDs( groupMode, groupIds, aggregateGroupIds @@ -126,7 +126,7 @@ export const EditScenesDialog: React.FC = ( .map((p) => p.id) .sort(); const sceneTagIDs = (scene.tags ?? []).map((p) => p.id).sort(); - const sceneGroupIDs = (scene.movies ?? []).map((m) => m.movie.id).sort(); + const sceneGroupIDs = (scene.groups ?? []).map((m) => m.group.id).sort(); if (first) { updateRating = sceneRating ?? undefined; diff --git a/ui/v2.5/src/components/Scenes/SceneCard.tsx b/ui/v2.5/src/components/Scenes/SceneCard.tsx index 694d1bdcc..e2f773626 100644 --- a/ui/v2.5/src/components/Scenes/SceneCard.tsx +++ b/ui/v2.5/src/components/Scenes/SceneCard.tsx @@ -144,23 +144,23 @@ const SceneCardPopovers = PatchComponent( } function maybeRenderGroupPopoverButton() { - if (props.scene.movies.length <= 0) return; + if (props.scene.groups.length <= 0) return; - const popoverContent = props.scene.movies.map((sceneGroup) => ( -
+ const popoverContent = props.scene.groups.map((sceneGroup) => ( +
{sceneGroup.movie.name
@@ -174,7 +174,7 @@ const SceneCardPopovers = PatchComponent( > ); @@ -279,7 +279,7 @@ const SceneCardPopovers = PatchComponent( !props.compact && (props.scene.tags.length > 0 || props.scene.performers.length > 0 || - props.scene.movies.length > 0 || + props.scene.groups.length > 0 || props.scene.scene_markers.length > 0 || props.scene?.o_counter || props.scene.galleries.length > 0 || diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx index ee599bee4..84754f184 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx @@ -441,12 +441,12 @@ const ScenePage: React.FC = ({ - {scene.movies.length > 0 ? ( + {scene.groups.length > 0 ? ( diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx index 7d23d8237..7b853b98c 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx @@ -104,8 +104,8 @@ export const SceneEditPanel: React.FC = ({ }, [scene.performers]); useEffect(() => { - setGroups(scene.movies?.map((m) => m.movie) ?? []); - }, [scene.movies]); + setGroups(scene.groups?.map((m) => m.group) ?? []); + }, [scene.groups]); useEffect(() => { setStudio(scene.studio ?? null); @@ -125,10 +125,10 @@ export const SceneEditPanel: React.FC = ({ gallery_ids: yup.array(yup.string().required()).defined(), studio_id: yup.string().required().nullable(), performer_ids: yup.array(yup.string().required()).defined(), - movies: yup + groups: yup .array( yup.object({ - movie_id: yup.string().required(), + group_id: yup.string().required(), scene_index: yup.number().integer().nullable().defined(), }) ) @@ -149,8 +149,8 @@ export const SceneEditPanel: React.FC = ({ gallery_ids: (scene.galleries ?? []).map((g) => g.id), studio_id: scene.studio?.id ?? null, performer_ids: (scene.performers ?? []).map((p) => p.id), - movies: (scene.movies ?? []).map((m) => { - return { movie_id: m.movie.id, scene_index: m.scene_index ?? null }; + groups: (scene.groups ?? []).map((m) => { + return { group_id: m.group.id, scene_index: m.scene_index ?? null }; }), tag_ids: (scene.tags ?? []).map((t) => t.id), stash_ids: getStashIDs(scene.stash_ids), @@ -187,16 +187,16 @@ export const SceneEditPanel: React.FC = ({ return sceneImage; }, [formik.values.cover_image, scene.paths?.screenshot]); - const movieEntries = useMemo(() => { - return formik.values.movies + const groupEntries = useMemo(() => { + return formik.values.groups .map((m) => { return { - movie: groups.find((mm) => mm.id === m.movie_id), + group: groups.find((mm) => mm.id === m.group_id), scene_index: m.scene_index, }; }) - .filter((m) => m.movie !== undefined) as IGroupEntry[]; - }, [formik.values.movies, groups]); + .filter((m) => m.group !== undefined) as IGroupEntry[]; + }, [formik.values.groups, groups]); function onSetGalleries(items: Gallery[]) { setGalleries(items); @@ -256,21 +256,21 @@ export const SceneEditPanel: React.FC = ({ function onSetGroups(items: Group[]) { setGroups(items); - const existingMovies = formik.values.movies; + const existingGroups = formik.values.groups; - const newMovies = items.map((m) => { - const existing = existingMovies.find((mm) => mm.movie_id === m.id); + const newGroups = items.map((m) => { + const existing = existingGroups.find((mm) => mm.group_id === m.id); if (existing) { return existing; } return { - movie_id: m.id, + group_id: m.id, scene_index: null, }; }); - formik.setFieldValue("movies", newMovies); + formik.setFieldValue("groups", newGroups); } async function onSave(input: InputValues) { @@ -568,8 +568,8 @@ export const SceneEditPanel: React.FC = ({ } } - if (updatedScene.movies && updatedScene.movies.length > 0) { - const idMovis = updatedScene.movies.filter((p) => { + if (updatedScene.groups && updatedScene.groups.length > 0) { + const idMovis = updatedScene.groups.filter((p) => { return p.stored_id !== undefined && p.stored_id !== null; }); @@ -725,24 +725,24 @@ export const SceneEditPanel: React.FC = ({ return renderField("performer_ids", title, control, fullWidthProps); } - function onSetMovieEntries(input: IGroupEntry[]) { - setGroups(input.map((m) => m.movie)); + function onSetGroupEntries(input: IGroupEntry[]) { + setGroups(input.map((m) => m.group)); - const newMovies = input.map((m) => ({ - movie_id: m.movie.id, + const newGroups = input.map((m) => ({ + group_id: m.group.id, scene_index: m.scene_index, })); - formik.setFieldValue("movies", newMovies); + formik.setFieldValue("groups", newGroups); } - function renderMoviesField() { + function renderGroupsField() { const title = intl.formatMessage({ id: "groups" }); const control = ( - + ); - return renderField("movies", title, control, fullWidthProps); + return renderField("groups", title, control, fullWidthProps); } function renderTagsField() { @@ -820,7 +820,7 @@ export const SceneEditPanel: React.FC = ({ {renderGalleriesField()} {renderStudioField()} {renderPerformersField()} - {renderMoviesField()} + {renderGroupsField()} {renderTagsField()} {renderStashIDsField( diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMoviePanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMoviePanel.tsx index 50febd074..78fc7875a 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMoviePanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMoviePanel.tsx @@ -9,10 +9,10 @@ interface ISceneGroupPanelProps { export const SceneGroupPanel: React.FC = ( props: ISceneGroupPanelProps ) => { - const cards = props.scene.movies.map((sceneGroup) => ( + const cards = props.scene.groups.map((sceneGroup) => ( )); diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMovieTable.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMovieTable.tsx index a851d5330..5b85c8352 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMovieTable.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMovieTable.tsx @@ -5,10 +5,10 @@ import { Form, Row, Col } from "react-bootstrap"; import { Group, GroupSelect } from "src/components/Movies/MovieSelect"; import cx from "classnames"; -export type MovieSceneIndexMap = Map; +export type GroupSceneIndexMap = Map; export interface IGroupEntry { - movie: Group; + group: Group; scene_index?: GQL.InputMaybe | undefined; } @@ -22,7 +22,7 @@ export const SceneGroupTable: React.FC = (props) => { const intl = useIntl(); - const groupIDs = useMemo(() => value.map((m) => m.movie.id), [value]); + const groupIDs = useMemo(() => value.map((m) => m.group.id), [value]); const updateFieldChanged = (index: number, sceneIndex: number | null) => { const newValues = value.map((existing, i) => { @@ -52,7 +52,7 @@ export const SceneGroupTable: React.FC = (props) => { if (i === index) { return { ...existing, - movie: group, + group: group, }; } return existing; @@ -71,7 +71,7 @@ export const SceneGroupTable: React.FC = (props) => { const newValues = [ ...value, { - movie: group, + group: group, scene_index: null, }, ]; @@ -83,11 +83,11 @@ export const SceneGroupTable: React.FC = (props) => { return ( <> {value.map((m, i) => ( - + onGroupSet(i, items)} - values={[m.movie!]} + values={[m.group!]} excludeIds={groupIDs} /> diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx index 5cdbd6d99..59f13b1ec 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx @@ -115,20 +115,20 @@ export const SceneScrapeDialog: React.FC = ({ ); const [groups, setGroups] = useState< - ObjectListScrapeResult + ObjectListScrapeResult >( - new ObjectListScrapeResult( + new ObjectListScrapeResult( sortStoredIdObjects( sceneGroups.map((p) => ({ stored_id: p.id, name: p.name, })) ), - sortStoredIdObjects(scraped.movies ?? undefined) + sortStoredIdObjects(scraped.groups ?? undefined) ) ); - const [newGroups, setNewGroups] = useState( - scraped.movies?.filter((t) => !t.stored_id) ?? [] + const [newGroups, setNewGroups] = useState( + scraped.groups?.filter((t) => !t.stored_id) ?? [] ); const { tags, newTags, scrapedTagsRow } = useScrapedTags( @@ -202,7 +202,7 @@ export const SceneScrapeDialog: React.FC = ({ director: director.getNewValue(), studio: newStudioValue, performers: performers.getNewValue(), - movies: groups.getNewValue(), + groups: groups.getNewValue(), tags: tags.getNewValue(), details: details.getNewValue(), image: image.getNewValue(), diff --git a/ui/v2.5/src/components/Scenes/SceneListTable.tsx b/ui/v2.5/src/components/Scenes/SceneListTable.tsx index d6a5fda64..3e68d27c2 100644 --- a/ui/v2.5/src/components/Scenes/SceneListTable.tsx +++ b/ui/v2.5/src/components/Scenes/SceneListTable.tsx @@ -126,10 +126,10 @@ export const SceneListTable: React.FC = ( const GroupCell = (scene: GQL.SlimSceneDataFragment) => (
    - {scene.movies.map((sceneGroup) => ( -
  • - - {sceneGroup.movie.name} + {scene.groups.map((sceneGroup) => ( +
  • + + {sceneGroup.group.name}
  • ))} diff --git a/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx b/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx index 314997363..c019abcea 100644 --- a/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx +++ b/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx @@ -100,10 +100,10 @@ const SceneMergeDetails: React.FC = ({ }; } - function groupToStoredID(o: { movie: { id: string; name: string } }) { + function groupToStoredID(o: { group: { id: string; name: string } }) { return { - stored_id: o.movie.id, - name: o.movie.name, + stored_id: o.group.id, + name: o.group.name, }; } @@ -142,10 +142,10 @@ const SceneMergeDetails: React.FC = ({ ); const [groups, setGroups] = useState< - ObjectListScrapeResult + ObjectListScrapeResult >( - new ObjectListScrapeResult( - sortStoredIdObjects(dest.movies.map(groupToStoredID)) + new ObjectListScrapeResult( + sortStoredIdObjects(dest.groups.map(groupToStoredID)) ) ); @@ -253,9 +253,9 @@ const SceneMergeDetails: React.FC = ({ ); setGroups( - new ObjectListScrapeResult( - sortStoredIdObjects(dest.movies.map(groupToStoredID)), - uniqIDStoredIDs(all.map((s) => s.movies.map(groupToStoredID)).flat()) + new ObjectListScrapeResult( + sortStoredIdObjects(dest.groups.map(groupToStoredID)), + uniqIDStoredIDs(all.map((s) => s.groups.map(groupToStoredID)).flat()) ) ); @@ -585,14 +585,14 @@ const SceneMergeDetails: React.FC = ({ gallery_ids: galleries.getNewValue(), studio_id: studio.getNewValue()?.stored_id, performer_ids: performers.getNewValue()?.map((p) => p.stored_id!), - movies: groups.getNewValue()?.map((m) => { - // find the equivalent movie in the original scenes + groups: groups.getNewValue()?.map((m) => { + // find the equivalent group in the original scenes const found = all - .map((s) => s.movies) + .map((s) => s.groups) .flat() - .find((mm) => mm.movie.id === m.stored_id); + .find((mm) => mm.group.id === m.stored_id); return { - movie_id: m.stored_id!, + group_id: m.stored_id!, scene_index: found!.scene_index, }; }), diff --git a/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx b/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx index caf8147f3..4b38100b5 100644 --- a/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx @@ -3,7 +3,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import { Button } from "react-bootstrap"; import { mutateReloadScrapers, - useListMovieScrapers, + useListGroupScrapers, useListPerformerScrapers, useListSceneScrapers, useListGalleryScrapers, @@ -80,7 +80,7 @@ export const SettingsScrapingPanel: React.FC = () => { const { data: galleryScrapers, loading: loadingGalleries } = useListGalleryScrapers(); const { data: groupScrapers, loading: loadingGroups } = - useListMovieScrapers(); + useListGroupScrapers(); const { general, scraping, loading, error, saveGeneral, saveScraping } = useSettings(); @@ -251,9 +251,9 @@ export const SettingsScrapingPanel: React.FC = () => { {scraper.name} - {renderGroupScrapeTypes(scraper.movie?.supported_scrapes ?? [])} + {renderGroupScrapeTypes(scraper.group?.supported_scrapes ?? [])} - {renderURLs(scraper.movie?.urls ?? [])} + {renderURLs(scraper.group?.urls ?? [])} )); diff --git a/ui/v2.5/src/components/Shared/ScrapeDialog/ScrapedObjectsRow.tsx b/ui/v2.5/src/components/Shared/ScrapeDialog/ScrapedObjectsRow.tsx index 73589ce92..1ec41386b 100644 --- a/ui/v2.5/src/components/Shared/ScrapeDialog/ScrapedObjectsRow.tsx +++ b/ui/v2.5/src/components/Shared/ScrapeDialog/ScrapedObjectsRow.tsx @@ -197,7 +197,7 @@ export const ScrapedPerformersRow: React.FC< }; export const ScrapedGroupsRow: React.FC< - IScrapedObjectRowImpl + IScrapedObjectRowImpl > = ({ title, result, onChange, newObjects, onCreateNew }) => { const groupsCopy = useMemo(() => { return ( @@ -209,9 +209,9 @@ export const ScrapedGroupsRow: React.FC< }, [newObjects]); function renderScrapedGroups( - scrapeResult: ScrapeResult, + scrapeResult: ScrapeResult, isNew?: boolean, - onChangeFn?: (value: GQL.ScrapedMovie[]) => void + onChangeFn?: (value: GQL.ScrapedGroup[]) => void ) { const resultValue = isNew ? scrapeResult.newValue @@ -244,7 +244,7 @@ export const ScrapedGroupsRow: React.FC< } return ( - + title={title} result={result} renderObjects={renderScrapedGroups} diff --git a/ui/v2.5/src/components/Shared/ScrapeDialog/createObjects.ts b/ui/v2.5/src/components/Shared/ScrapeDialog/createObjects.ts index 397681483..61311e546 100644 --- a/ui/v2.5/src/components/Shared/ScrapeDialog/createObjects.ts +++ b/ui/v2.5/src/components/Shared/ScrapeDialog/createObjects.ts @@ -1,7 +1,7 @@ import { useToast } from "src/hooks/Toast"; import * as GQL from "src/core/generated-graphql"; import { - useMovieCreate, + useGroupCreate, usePerformerCreate, useStudioCreate, useTagCreate, @@ -124,12 +124,12 @@ export function useCreateScrapedPerformer( } export function useCreateScrapedGroup( - props: IUseCreateNewObjectProps + props: IUseCreateNewObjectProps ) { const { scrapeResult, setScrapeResult, newObjects, setNewObjects } = props; - const [createGroup] = useMovieCreate(); + const [createGroup] = useGroupCreate(); - async function createNewGroup(toCreate: GQL.ScrapedMovie) { + async function createNewGroup(toCreate: GQL.ScrapedGroup) { const input = scrapedGroupToCreateInput(toCreate); const result = await createGroup({ @@ -137,10 +137,10 @@ export function useCreateScrapedGroup( }); const newValue = [...(scrapeResult.newValue ?? [])]; - if (result.data?.movieCreate) + if (result.data?.groupCreate) newValue.push({ - stored_id: result.data.movieCreate.id, - name: result.data.movieCreate.name, + stored_id: result.data.groupCreate.id, + name: result.data.groupCreate.name, }); // add the new object to the new object value diff --git a/ui/v2.5/src/components/Stats.tsx b/ui/v2.5/src/components/Stats.tsx index 608afc0e2..87d022be7 100644 --- a/ui/v2.5/src/components/Stats.tsx +++ b/ui/v2.5/src/components/Stats.tsx @@ -50,7 +50,7 @@ export const Stats: React.FC = () => {

- +

diff --git a/ui/v2.5/src/components/Studios/StudioCard.tsx b/ui/v2.5/src/components/Studios/StudioCard.tsx index 62604555e..e2a7bada2 100644 --- a/ui/v2.5/src/components/Studios/StudioCard.tsx +++ b/ui/v2.5/src/components/Studios/StudioCard.tsx @@ -143,13 +143,13 @@ export const StudioCard: React.FC = ({ } function maybeRenderGroupsPopoverButton() { - if (!studio.movie_count) return; + if (!studio.group_count) return; return ( ); @@ -190,7 +190,7 @@ export const StudioCard: React.FC = ({ studio.scene_count || studio.image_count || studio.gallery_count || - studio.movie_count || + studio.group_count || studio.performer_count || studio.tags.length > 0 ) { diff --git a/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx b/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx index 870db812c..20291e2a7 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx @@ -109,7 +109,7 @@ const StudioPage: React.FC = ({ studio, tabKey }) => { const performerCount = (showAllCounts ? studio.performer_count_all : studio.performer_count) ?? 0; const groupCount = - (showAllCounts ? studio.movie_count_all : studio.movie_count) ?? 0; + (showAllCounts ? studio.group_count_all : studio.group_count) ?? 0; const populatedDefaultTab = useMemo(() => { let ret: TabKey = "scenes"; diff --git a/ui/v2.5/src/components/Studios/StudioDetails/StudioPerformersPanel.tsx b/ui/v2.5/src/components/Studios/StudioDetails/StudioPerformersPanel.tsx index 0eb146a1b..33e98072f 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/StudioPerformersPanel.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/StudioPerformersPanel.tsx @@ -25,7 +25,7 @@ export const StudioPerformersPanel: React.FC = ({ scenes: [studioCriterion], images: [studioCriterion], galleries: [studioCriterion], - movies: [studioCriterion], + groups: [studioCriterion], }; const filterHook = useStudioFilterHook(studio); diff --git a/ui/v2.5/src/components/Tags/TagCard.tsx b/ui/v2.5/src/components/Tags/TagCard.tsx index 770e0bb91..f95144537 100644 --- a/ui/v2.5/src/components/Tags/TagCard.tsx +++ b/ui/v2.5/src/components/Tags/TagCard.tsx @@ -237,13 +237,13 @@ export const TagCard: React.FC = ({ } function maybeRenderGroupsPopoverButton() { - if (!tag.movie_count) return; + if (!tag.group_count) return; return ( ); diff --git a/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx b/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx index 51a334c11..7b794e9bf 100644 --- a/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx +++ b/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx @@ -106,7 +106,7 @@ const TagPage: React.FC = ({ tag, tabKey }) => { const galleryCount = (showAllCounts ? tag.gallery_count_all : tag.gallery_count) ?? 0; const groupCount = - (showAllCounts ? tag.movie_count_all : tag.movie_count) ?? 0; + (showAllCounts ? tag.group_count_all : tag.group_count) ?? 0; const sceneMarkerCount = (showAllCounts ? tag.scene_marker_count_all : tag.scene_marker_count) ?? 0; const performerCount = diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index d7518b6c7..949a061c1 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -210,43 +210,43 @@ export const queryFindImages = (filter: ListFilterModel) => }, }); -export const useFindMovie = (id: string) => { +export const useFindGroup = (id: string) => { const skip = id === "new" || id === ""; - return GQL.useFindMovieQuery({ variables: { id }, skip }); + return GQL.useFindGroupQuery({ variables: { id }, skip }); }; -export const useFindMovies = (filter?: ListFilterModel) => - GQL.useFindMoviesQuery({ +export const useFindGroups = (filter?: ListFilterModel) => + GQL.useFindGroupsQuery({ skip: filter === undefined, variables: { filter: filter?.makeFindFilter(), - movie_filter: filter?.makeFilter(), + group_filter: filter?.makeFilter(), }, }); -export const queryFindMovies = (filter: ListFilterModel) => - client.query({ - query: GQL.FindMoviesDocument, +export const queryFindGroups = (filter: ListFilterModel) => + client.query({ + query: GQL.FindGroupsDocument, variables: { filter: filter.makeFindFilter(), - movie_filter: filter.makeFilter(), + group_filter: filter.makeFilter(), }, }); -export const queryFindMoviesByIDForSelect = (movieIDs: string[]) => - client.query({ - query: GQL.FindMoviesForSelectDocument, +export const queryFindGroupsByIDForSelect = (groupIDs: string[]) => + client.query({ + query: GQL.FindGroupsForSelectDocument, variables: { - ids: movieIDs, + ids: groupIDs, }, }); -export const queryFindMoviesForSelect = (filter: ListFilterModel) => - client.query({ - query: GQL.FindMoviesForSelectDocument, +export const queryFindGroupsForSelect = (filter: ListFilterModel) => + client.query({ + query: GQL.FindGroupsForSelectDocument, variables: { filter: filter.makeFindFilter(), - movie_filter: filter.makeFilter(), + group_filter: filter.makeFilter(), }, }); @@ -485,13 +485,13 @@ function updateO( } const sceneMutationImpactedTypeFields = { - Movie: ["scenes", "scene_count"], + Group: ["scenes", "scene_count"], Gallery: ["scenes"], Performer: [ "scenes", "scene_count", - "movies", - "movie_count", + "groups", + "group_count", "performer_count", ], Studio: ["scene_count", "performer_count"], @@ -500,7 +500,7 @@ const sceneMutationImpactedTypeFields = { const sceneMutationImpactedQueries = [ GQL.FindScenesDocument, // various filters - GQL.FindMoviesDocument, // is missing scenes + GQL.FindGroupsDocument, // is missing scenes GQL.FindGalleriesDocument, // is missing scenes GQL.FindPerformersDocument, // filter by scene count GQL.FindStudiosDocument, // filter by scene count @@ -1273,98 +1273,98 @@ export const mutateImageSetPrimaryFile = (id: string, fileID: string) => }, }); -const movieMutationImpactedTypeFields = { - Performer: ["movie_count"], - Studio: ["movie_count"], +const groupMutationImpactedTypeFields = { + Performer: ["group_count"], + Studio: ["group_count"], }; -const movieMutationImpactedQueries = [ - GQL.FindMoviesDocument, // various filters +const groupMutationImpactedQueries = [ + GQL.FindGroupsDocument, // various filters ]; -export const useMovieCreate = () => - GQL.useMovieCreateMutation({ +export const useGroupCreate = () => + GQL.useGroupCreateMutation({ update(cache, result) { - const movie = result.data?.movieCreate; - if (!movie) return; + const group = result.data?.groupCreate; + if (!group) return; // update stats - updateStats(cache, "movie_count", 1); + updateStats(cache, "group_count", 1); - evictTypeFields(cache, movieMutationImpactedTypeFields); - evictQueries(cache, movieMutationImpactedQueries); + evictTypeFields(cache, groupMutationImpactedTypeFields); + evictQueries(cache, groupMutationImpactedQueries); }, }); -export const useMovieUpdate = () => - GQL.useMovieUpdateMutation({ +export const useGroupUpdate = () => + GQL.useGroupUpdateMutation({ update(cache, result) { - if (!result.data?.movieUpdate) return; + if (!result.data?.groupUpdate) return; - evictTypeFields(cache, movieMutationImpactedTypeFields); - evictQueries(cache, movieMutationImpactedQueries); + evictTypeFields(cache, groupMutationImpactedTypeFields); + evictQueries(cache, groupMutationImpactedQueries); }, }); -export const useBulkMovieUpdate = (input: GQL.BulkMovieUpdateInput) => - GQL.useBulkMovieUpdateMutation({ +export const useBulkGroupUpdate = (input: GQL.BulkGroupUpdateInput) => + GQL.useBulkGroupUpdateMutation({ variables: { input }, update(cache, result) { - if (!result.data?.bulkMovieUpdate) return; + if (!result.data?.bulkGroupUpdate) return; - evictTypeFields(cache, movieMutationImpactedTypeFields); - evictQueries(cache, movieMutationImpactedQueries); + evictTypeFields(cache, groupMutationImpactedTypeFields); + evictQueries(cache, groupMutationImpactedQueries); }, }); -export const useMovieDestroy = (input: GQL.MovieDestroyInput) => - GQL.useMovieDestroyMutation({ +export const useGroupDestroy = (input: GQL.GroupDestroyInput) => + GQL.useGroupDestroyMutation({ variables: input, update(cache, result) { - if (!result.data?.movieDestroy) return; + if (!result.data?.groupDestroy) return; - const obj = { __typename: "Movie", id: input.id }; - deleteObject(cache, obj, GQL.FindMovieDocument); + const obj = { __typename: "Group", id: input.id }; + deleteObject(cache, obj, GQL.FindGroupDocument); // update stats - updateStats(cache, "movie_count", -1); + updateStats(cache, "group_count", -1); evictTypeFields(cache, { - Scene: ["movies"], - Performer: ["movie_count"], - Studio: ["movie_count"], + Scene: ["groups"], + Performer: ["group_count"], + Studio: ["group_count"], }); evictQueries(cache, [ - ...movieMutationImpactedQueries, - GQL.FindScenesDocument, // filter by movie + ...groupMutationImpactedQueries, + GQL.FindScenesDocument, // filter by group ]); }, }); -export const useMoviesDestroy = (input: GQL.MoviesDestroyMutationVariables) => - GQL.useMoviesDestroyMutation({ +export const useGroupsDestroy = (input: GQL.GroupsDestroyMutationVariables) => + GQL.useGroupsDestroyMutation({ variables: input, update(cache, result) { - if (!result.data?.moviesDestroy) return; + if (!result.data?.groupsDestroy) return; const { ids } = input; for (const id of ids) { - const obj = { __typename: "Movie", id }; - deleteObject(cache, obj, GQL.FindMovieDocument); + const obj = { __typename: "Group", id }; + deleteObject(cache, obj, GQL.FindGroupDocument); } // update stats - updateStats(cache, "movie_count", -ids.length); + updateStats(cache, "group_count", -ids.length); evictTypeFields(cache, { - Scene: ["movies"], - Performer: ["movie_count"], - Studio: ["movie_count"], + Scene: ["groups"], + Performer: ["group_count"], + Studio: ["group_count"], }); evictQueries(cache, [ - ...movieMutationImpactedQueries, - GQL.FindScenesDocument, // filter by movie + ...groupMutationImpactedQueries, + GQL.FindScenesDocument, // filter by group ]); }, }); @@ -1678,7 +1678,7 @@ export const usePerformerDestroy = () => evictQueries(cache, [ ...performerMutationImpactedQueries, GQL.FindPerformersDocument, // appears with - GQL.FindMoviesDocument, // filter by performers + GQL.FindGroupsDocument, // filter by performers GQL.FindSceneMarkersDocument, // filter by performers ]); }, @@ -1718,7 +1718,7 @@ export const usePerformersDestroy = ( evictQueries(cache, [ ...performerMutationImpactedQueries, GQL.FindPerformersDocument, // appears with - GQL.FindMoviesDocument, // filter by performers + GQL.FindGroupsDocument, // filter by performers GQL.FindSceneMarkersDocument, // filter by performers ]); }, @@ -1731,7 +1731,7 @@ const studioMutationImpactedTypeFields = { export const studioMutationImpactedQueries = [ GQL.FindScenesDocument, // filter by studio GQL.FindImagesDocument, // filter by studio - GQL.FindMoviesDocument, // filter by studio + GQL.FindGroupsDocument, // filter by studio GQL.FindGalleriesDocument, // filter by studio GQL.FindPerformersDocument, // filter by studio GQL.FindStudiosDocument, // various filters @@ -2161,11 +2161,11 @@ export const mutateStashBoxBatchStudioTag = ( variables: { input }, }); -export const useListMovieScrapers = () => GQL.useListMovieScrapersQuery(); +export const useListGroupScrapers = () => GQL.useListGroupScrapersQuery(); -export const queryScrapeMovieURL = (url: string) => - client.query({ - query: GQL.ScrapeMovieUrlDocument, +export const queryScrapeGroupURL = (url: string) => + client.query({ + query: GQL.ScrapeGroupUrlDocument, variables: { url }, fetchPolicy: "network-only", }); @@ -2261,7 +2261,7 @@ export const useLoggingSubscribe = () => GQL.useLoggingSubscribeSubscription(); // all scraper-related queries export const scraperMutationImpactedQueries = [ - GQL.ListMovieScrapersDocument, + GQL.ListGroupScrapersDocument, GQL.ListPerformerScrapersDocument, GQL.ListSceneScrapersDocument, GQL.InstalledScraperPackagesDocument, diff --git a/ui/v2.5/src/core/config.ts b/ui/v2.5/src/core/config.ts index e8f829061..213d8c113 100644 --- a/ui/v2.5/src/core/config.ts +++ b/ui/v2.5/src/core/config.ts @@ -143,7 +143,7 @@ export function generateDefaultFrontPageContent(intl: IntlShape) { return [ recentlyReleased(intl, FilterMode.Scenes, "scenes"), recentlyAdded(intl, FilterMode.Studios, "studios"), - recentlyReleased(intl, FilterMode.Movies, "groups"), + recentlyReleased(intl, FilterMode.Groups, "groups"), recentlyAdded(intl, FilterMode.Performers, "performers"), recentlyReleased(intl, FilterMode.Galleries, "galleries"), ]; @@ -156,8 +156,8 @@ export function generatePremadeFrontPageContent(intl: IntlShape) { recentlyReleased(intl, FilterMode.Galleries, "galleries"), recentlyAdded(intl, FilterMode.Galleries, "galleries"), recentlyAdded(intl, FilterMode.Images, "images"), - recentlyReleased(intl, FilterMode.Movies, "groups"), - recentlyAdded(intl, FilterMode.Movies, "groups"), + recentlyReleased(intl, FilterMode.Groups, "groups"), + recentlyAdded(intl, FilterMode.Groups, "groups"), recentlyAdded(intl, FilterMode.Studios, "studios"), recentlyAdded(intl, FilterMode.Performers, "performers"), ]; diff --git a/ui/v2.5/src/core/createClient.ts b/ui/v2.5/src/core/createClient.ts index e5f502cca..4fbcd9183 100644 --- a/ui/v2.5/src/core/createClient.ts +++ b/ui/v2.5/src/core/createClient.ts @@ -46,8 +46,8 @@ const typePolicies: TypePolicies = { findStudio: { read: readReference("Studio"), }, - findMovie: { - read: readReference("Movie"), + findGroup: { + read: readReference("Group"), }, findGallery: { read: readReference("Gallery"), @@ -80,7 +80,7 @@ const typePolicies: TypePolicies = { }, }, }, - Movie: { + Group: { fields: { studio: { read: readDanglingNull, diff --git a/ui/v2.5/src/core/movies.ts b/ui/v2.5/src/core/movies.ts index 1183785f1..8a741b750 100644 --- a/ui/v2.5/src/core/movies.ts +++ b/ui/v2.5/src/core/movies.ts @@ -1,10 +1,10 @@ import * as GQL from "src/core/generated-graphql"; import TextUtils from "src/utils/text"; -export const scrapedGroupToCreateInput = (toCreate: GQL.ScrapedMovie) => { - const input: GQL.MovieCreateInput = { +export const scrapedGroupToCreateInput = (toCreate: GQL.ScrapedGroup) => { + const input: GQL.GroupCreateInput = { name: toCreate.name ?? "", - url: toCreate.url, + urls: toCreate.urls, aliases: toCreate.aliases, front_image: toCreate.front_image, back_image: toCreate.back_image, diff --git a/ui/v2.5/src/models/list-filter/criteria/is-missing.ts b/ui/v2.5/src/models/list-filter/criteria/is-missing.ts index 15272d756..99b53bc7d 100644 --- a/ui/v2.5/src/models/list-filter/criteria/is-missing.ts +++ b/ui/v2.5/src/models/list-filter/criteria/is-missing.ts @@ -87,7 +87,7 @@ export const StudioIsMissingCriterionOption = new IsMissingCriterionOption( ["image", "stash_id", "details"] ); -export const MovieIsMissingCriterionOption = new IsMissingCriterionOption( +export const GroupIsMissingCriterionOption = new IsMissingCriterionOption( "isMissing", "is_missing", ["front_image", "back_image", "scenes"] diff --git a/ui/v2.5/src/models/list-filter/criteria/movies.ts b/ui/v2.5/src/models/list-filter/criteria/movies.ts index 391fdb1d2..1daffeab9 100644 --- a/ui/v2.5/src/models/list-filter/criteria/movies.ts +++ b/ui/v2.5/src/models/list-filter/criteria/movies.ts @@ -2,16 +2,16 @@ import { ILabeledIdCriterion, ILabeledIdCriterionOption } from "./criterion"; const inputType = "groups"; -export const MoviesCriterionOption = new ILabeledIdCriterionOption( +export const GroupsCriterionOption = new ILabeledIdCriterionOption( + "groups", "groups", - "movies", false, inputType, - () => new MoviesCriterion() + () => new GroupsCriterion() ); -export class MoviesCriterion extends ILabeledIdCriterion { +export class GroupsCriterion extends ILabeledIdCriterion { constructor() { - super(MoviesCriterionOption); + super(GroupsCriterionOption); } } diff --git a/ui/v2.5/src/models/list-filter/factory.ts b/ui/v2.5/src/models/list-filter/factory.ts index 36b9d66db..1677358e3 100644 --- a/ui/v2.5/src/models/list-filter/factory.ts +++ b/ui/v2.5/src/models/list-filter/factory.ts @@ -2,7 +2,7 @@ import { FilterMode } from "src/core/generated-graphql"; import { ListFilterOptions } from "./filter-options"; import { GalleryListFilterOptions } from "./galleries"; import { ImageListFilterOptions } from "./images"; -import { MovieListFilterOptions } from "./movies"; +import { GroupListFilterOptions } from "./movies"; import { PerformerListFilterOptions } from "./performers"; import { SceneMarkerListFilterOptions } from "./scene-markers"; import { SceneListFilterOptions } from "./scenes"; @@ -22,7 +22,8 @@ export function getFilterOptions(mode: FilterMode): ListFilterOptions { case FilterMode.SceneMarkers: return SceneMarkerListFilterOptions; case FilterMode.Movies: - return MovieListFilterOptions; + case FilterMode.Groups: + return GroupListFilterOptions; case FilterMode.Tags: return TagListFilterOptions; case FilterMode.Images: diff --git a/ui/v2.5/src/models/list-filter/movies.ts b/ui/v2.5/src/models/list-filter/movies.ts index 7e89d5939..43439a62f 100644 --- a/ui/v2.5/src/models/list-filter/movies.ts +++ b/ui/v2.5/src/models/list-filter/movies.ts @@ -5,7 +5,7 @@ import { createDurationCriterionOption, createMandatoryNumberCriterionOption, } from "./criteria/criterion"; -import { MovieIsMissingCriterionOption } from "./criteria/is-missing"; +import { GroupIsMissingCriterionOption } from "./criteria/is-missing"; import { StudiosCriterionOption } from "./criteria/studios"; import { PerformersCriterionOption } from "./criteria/performers"; import { ListFilterOptions } from "./filter-options"; @@ -35,7 +35,7 @@ const displayModeOptions = [DisplayMode.Grid]; const criterionOptions = [ // StudioTagsCriterionOption, StudiosCriterionOption, - MovieIsMissingCriterionOption, + GroupIsMissingCriterionOption, createStringCriterionOption("url"), createStringCriterionOption("name"), createStringCriterionOption("director"), @@ -50,7 +50,7 @@ const criterionOptions = [ createMandatoryTimestampCriterionOption("updated_at"), ]; -export const MovieListFilterOptions = new ListFilterOptions( +export const GroupListFilterOptions = new ListFilterOptions( defaultSortBy, sortByOptions, displayModeOptions, diff --git a/ui/v2.5/src/models/list-filter/scenes.ts b/ui/v2.5/src/models/list-filter/scenes.ts index 9a8e3f5e1..92b9eadfa 100644 --- a/ui/v2.5/src/models/list-filter/scenes.ts +++ b/ui/v2.5/src/models/list-filter/scenes.ts @@ -8,7 +8,7 @@ import { } from "./criteria/criterion"; import { HasMarkersCriterionOption } from "./criteria/has-markers"; import { SceneIsMissingCriterionOption } from "./criteria/is-missing"; -import { MoviesCriterionOption } from "./criteria/movies"; +import { GroupsCriterionOption } from "./criteria/movies"; import { GalleriesCriterionOption } from "./criteria/galleries"; import { OrganizedCriterionOption } from "./criteria/organized"; import { PerformersCriterionOption } from "./criteria/performers"; @@ -105,7 +105,7 @@ const criterionOptions = [ PerformerFavoriteCriterionOption, // StudioTagsCriterionOption, StudiosCriterionOption, - MoviesCriterionOption, + GroupsCriterionOption, GalleriesCriterionOption, createStringCriterionOption("url"), StashIDCriterionOption, diff --git a/ui/v2.5/src/models/list-filter/tags.ts b/ui/v2.5/src/models/list-filter/tags.ts index dc84a9676..9aa4c80e6 100644 --- a/ui/v2.5/src/models/list-filter/tags.ts +++ b/ui/v2.5/src/models/list-filter/tags.ts @@ -62,7 +62,7 @@ const criterionOptions = [ createMandatoryNumberCriterionOption("gallery_count"), createMandatoryNumberCriterionOption("performer_count"), createMandatoryNumberCriterionOption("studio_count"), - createMandatoryNumberCriterionOption("movie_count", "group_count"), + createMandatoryNumberCriterionOption("group_count"), createMandatoryNumberCriterionOption("marker_count"), ParentTagsCriterionOption, new MandatoryNumberCriterionOption("parent_tag_count", "parent_count"), diff --git a/ui/v2.5/src/models/list-filter/types.ts b/ui/v2.5/src/models/list-filter/types.ts index 5a63179ad..9884c63c8 100644 --- a/ui/v2.5/src/models/list-filter/types.ts +++ b/ui/v2.5/src/models/list-filter/types.ts @@ -147,7 +147,7 @@ export type CriterionType = | "performers" | "studios" | "scenes" - | "movies" + | "groups" | "galleries" | "birth_year" | "age" @@ -174,7 +174,7 @@ export type CriterionType = | "gallery_count" | "performer_count" | "studio_count" - | "movie_count" + | "group_count" | "death_year" | "url" | "interactive" diff --git a/ui/v2.5/src/pluginApi.d.ts b/ui/v2.5/src/pluginApi.d.ts index 9d3aad40f..bbdc9fcff 100644 --- a/ui/v2.5/src/pluginApi.d.ts +++ b/ui/v2.5/src/pluginApi.d.ts @@ -11,7 +11,7 @@ declare namespace PluginApi { const BlobsStorageType: { [key: string]: any }; const BulkGalleryUpdateDocument: { [key: string]: any }; const BulkImageUpdateDocument: { [key: string]: any }; - const BulkMovieUpdateDocument: { [key: string]: any }; + const BulkGroupUpdateDocument: { [key: string]: any }; const BulkPerformerUpdateDocument: { [key: string]: any }; const BulkSceneUpdateDocument: { [key: string]: any }; const BulkUpdateIdMode: { [key: string]: any }; @@ -46,9 +46,9 @@ declare namespace PluginApi { const FindImageDocument: { [key: string]: any }; const FindImagesDocument: { [key: string]: any }; const FindJobDocument: { [key: string]: any }; - const FindMovieDocument: { [key: string]: any }; - const FindMoviesDocument: { [key: string]: any }; - const FindMoviesForSelectDocument: { [key: string]: any }; + const FindGroupDocument: { [key: string]: any }; + const FindGroupsDocument: { [key: string]: any }; + const FindGroupsForSelectDocument: { [key: string]: any }; const FindPerformerDocument: { [key: string]: any }; const FindPerformersDocument: { [key: string]: any }; const FindPerformersForSelectDocument: { [key: string]: any }; @@ -109,7 +109,7 @@ declare namespace PluginApi { const JobsSubscribeDocument: { [key: string]: any }; const LatestVersionDocument: { [key: string]: any }; const ListGalleryScrapersDocument: { [key: string]: any }; - const ListMovieScrapersDocument: { [key: string]: any }; + const ListGroupScrapersDocument: { [key: string]: any }; const ListPerformerScrapersDocument: { [key: string]: any }; const ListSceneScrapersDocument: { [key: string]: any }; const LogEntryDataFragmentDoc: { [key: string]: any }; @@ -129,11 +129,11 @@ declare namespace PluginApi { const MigrateDocument: { [key: string]: any }; const MigrateHashNamingDocument: { [key: string]: any }; const MigrateSceneScreenshotsDocument: { [key: string]: any }; - const MovieCreateDocument: { [key: string]: any }; - const MovieDataFragmentDoc: { [key: string]: any }; - const MovieDestroyDocument: { [key: string]: any }; - const MovieUpdateDocument: { [key: string]: any }; - const MoviesDestroyDocument: { [key: string]: any }; + const GroupCreateDocument: { [key: string]: any }; + const GroupDataFragmentDoc: { [key: string]: any }; + const GroupDestroyDocument: { [key: string]: any }; + const GroupUpdateDocument: { [key: string]: any }; + const GroupsDestroyDocument: { [key: string]: any }; const OptimiseDatabaseDocument: { [key: string]: any }; const OrientationEnum: { [key: string]: any }; const PackageDataFragmentDoc: { [key: string]: any }; @@ -179,7 +179,7 @@ declare namespace PluginApi { const ScenesUpdateDocument: { [key: string]: any }; const ScrapeContentType: { [key: string]: any }; const ScrapeGalleryUrlDocument: { [key: string]: any }; - const ScrapeMovieUrlDocument: { [key: string]: any }; + const ScrapeGroupUrlDocument: { [key: string]: any }; const ScrapeMultiPerformersDocument: { [key: string]: any }; const ScrapeMultiScenesDocument: { [key: string]: any }; const ScrapePerformerUrlDocument: { [key: string]: any }; @@ -190,11 +190,11 @@ declare namespace PluginApi { const ScrapeSingleStudioDocument: { [key: string]: any }; const ScrapeType: { [key: string]: any }; const ScrapedGalleryDataFragmentDoc: { [key: string]: any }; - const ScrapedMovieDataFragmentDoc: { [key: string]: any }; - const ScrapedMovieStudioDataFragmentDoc: { [key: string]: any }; + const ScrapedGroupDataFragmentDoc: { [key: string]: any }; + const ScrapedGroupStudioDataFragmentDoc: { [key: string]: any }; const ScrapedPerformerDataFragmentDoc: { [key: string]: any }; const ScrapedSceneDataFragmentDoc: { [key: string]: any }; - const ScrapedSceneMovieDataFragmentDoc: { [key: string]: any }; + const ScrapedSceneGroupDataFragmentDoc: { [key: string]: any }; const ScrapedScenePerformerDataFragmentDoc: { [key: string]: any }; const ScrapedSceneStudioDataFragmentDoc: { [key: string]: any }; const ScrapedSceneTagDataFragmentDoc: { [key: string]: any }; @@ -203,7 +203,7 @@ declare namespace PluginApi { const ScrapedStudioDataFragmentDoc: { [key: string]: any }; const ScraperSourceDataFragmentDoc: { [key: string]: any }; const SelectGalleryDataFragmentDoc: { [key: string]: any }; - const SelectMovieDataFragmentDoc: { [key: string]: any }; + const SelectGroupDataFragmentDoc: { [key: string]: any }; const SelectPerformerDataFragmentDoc: { [key: string]: any }; const SelectStudioDataFragmentDoc: { [key: string]: any }; const SelectTagDataFragmentDoc: { [key: string]: any }; @@ -211,7 +211,7 @@ declare namespace PluginApi { const SetupDocument: { [key: string]: any }; const SlimGalleryDataFragmentDoc: { [key: string]: any }; const SlimImageDataFragmentDoc: { [key: string]: any }; - const SlimMovieDataFragmentDoc: { [key: string]: any }; + const SlimGroupDataFragmentDoc: { [key: string]: any }; const SlimPerformerDataFragmentDoc: { [key: string]: any }; const SlimSceneDataFragmentDoc: { [key: string]: any }; const SlimStudioDataFragmentDoc: { [key: string]: any }; @@ -259,9 +259,9 @@ declare namespace PluginApi { function refetchFindImageQuery(...args: any[]): any; function refetchFindImagesQuery(...args: any[]): any; function refetchFindJobQuery(...args: any[]): any; - function refetchFindMovieQuery(...args: any[]): any; - function refetchFindMoviesForSelectQuery(...args: any[]): any; - function refetchFindMoviesQuery(...args: any[]): any; + function refetchFindGroupQuery(...args: any[]): any; + function refetchFindGroupsForSelectQuery(...args: any[]): any; + function refetchFindGroupsQuery(...args: any[]): any; function refetchFindPerformerQuery(...args: any[]): any; function refetchFindPerformersForSelectQuery(...args: any[]): any; function refetchFindPerformersQuery(...args: any[]): any; @@ -285,7 +285,7 @@ declare namespace PluginApi { function refetchJobQueueQuery(...args: any[]): any; function refetchLatestVersionQuery(...args: any[]): any; function refetchListGalleryScrapersQuery(...args: any[]): any; - function refetchListMovieScrapersQuery(...args: any[]): any; + function refetchListGroupScrapersQuery(...args: any[]): any; function refetchListPerformerScrapersQuery(...args: any[]): any; function refetchListSceneScrapersQuery(...args: any[]): any; function refetchLogsQuery(...args: any[]): any; @@ -297,7 +297,7 @@ declare namespace PluginApi { function refetchSceneStreamsQuery(...args: any[]): any; function refetchSceneWallQuery(...args: any[]): any; function refetchScrapeGalleryUrlQuery(...args: any[]): any; - function refetchScrapeMovieUrlQuery(...args: any[]): any; + function refetchScrapeGroupUrlQuery(...args: any[]): any; function refetchScrapeMultiPerformersQuery(...args: any[]): any; function refetchScrapeMultiScenesQuery(...args: any[]): any; function refetchScrapePerformerUrlQuery(...args: any[]): any; @@ -322,7 +322,7 @@ declare namespace PluginApi { function useBackupDatabaseMutation(...args: any[]): any; function useBulkGalleryUpdateMutation(...args: any[]): any; function useBulkImageUpdateMutation(...args: any[]): any; - function useBulkMovieUpdateMutation(...args: any[]): any; + function useBulkGroupUpdateMutation(...args: any[]): any; function useBulkPerformerUpdateMutation(...args: any[]): any; function useBulkSceneUpdateMutation(...args: any[]): any; function useConfigurationLazyQuery(...args: any[]): any; @@ -367,15 +367,15 @@ declare namespace PluginApi { function useFindJobLazyQuery(...args: any[]): any; function useFindJobQuery(...args: any[]): any; function useFindJobSuspenseQuery(...args: any[]): any; - function useFindMovieLazyQuery(...args: any[]): any; - function useFindMovieQuery(...args: any[]): any; - function useFindMovieSuspenseQuery(...args: any[]): any; - function useFindMoviesForSelectLazyQuery(...args: any[]): any; - function useFindMoviesForSelectQuery(...args: any[]): any; - function useFindMoviesForSelectSuspenseQuery(...args: any[]): any; - function useFindMoviesLazyQuery(...args: any[]): any; - function useFindMoviesQuery(...args: any[]): any; - function useFindMoviesSuspenseQuery(...args: any[]): any; + function useFindGroupLazyQuery(...args: any[]): any; + function useFindGroupQuery(...args: any[]): any; + function useFindGroupSuspenseQuery(...args: any[]): any; + function useFindGroupsForSelectLazyQuery(...args: any[]): any; + function useFindGroupsForSelectQuery(...args: any[]): any; + function useFindGroupsForSelectSuspenseQuery(...args: any[]): any; + function useFindGroupsLazyQuery(...args: any[]): any; + function useFindGroupsQuery(...args: any[]): any; + function useFindGroupsSuspenseQuery(...args: any[]): any; function useFindPerformerLazyQuery(...args: any[]): any; function useFindPerformerQuery(...args: any[]): any; function useFindPerformerSuspenseQuery(...args: any[]): any; @@ -466,9 +466,9 @@ declare namespace PluginApi { function useListGalleryScrapersLazyQuery(...args: any[]): any; function useListGalleryScrapersQuery(...args: any[]): any; function useListGalleryScrapersSuspenseQuery(...args: any[]): any; - function useListMovieScrapersLazyQuery(...args: any[]): any; - function useListMovieScrapersQuery(...args: any[]): any; - function useListMovieScrapersSuspenseQuery(...args: any[]): any; + function useListGroupScrapersLazyQuery(...args: any[]): any; + function useListGroupScrapersQuery(...args: any[]): any; + function useListGroupScrapersSuspenseQuery(...args: any[]): any; function useListPerformerScrapersLazyQuery(...args: any[]): any; function useListPerformerScrapersQuery(...args: any[]): any; function useListPerformerScrapersSuspenseQuery(...args: any[]): any; @@ -496,10 +496,10 @@ declare namespace PluginApi { function useMigrateHashNamingMutation(...args: any[]): any; function useMigrateMutation(...args: any[]): any; function useMigrateSceneScreenshotsMutation(...args: any[]): any; - function useMovieCreateMutation(...args: any[]): any; - function useMovieDestroyMutation(...args: any[]): any; - function useMovieUpdateMutation(...args: any[]): any; - function useMoviesDestroyMutation(...args: any[]): any; + function useGroupCreateMutation(...args: any[]): any; + function useGroupDestroyMutation(...args: any[]): any; + function useGroupUpdateMutation(...args: any[]): any; + function useGroupsDestroyMutation(...args: any[]): any; function useOptimiseDatabaseMutation(...args: any[]): any; function useParseSceneFilenamesLazyQuery(...args: any[]): any; function useParseSceneFilenamesQuery(...args: any[]): any; @@ -546,9 +546,9 @@ declare namespace PluginApi { function useScrapeGalleryUrlLazyQuery(...args: any[]): any; function useScrapeGalleryUrlQuery(...args: any[]): any; function useScrapeGalleryUrlSuspenseQuery(...args: any[]): any; - function useScrapeMovieUrlLazyQuery(...args: any[]): any; - function useScrapeMovieUrlQuery(...args: any[]): any; - function useScrapeMovieUrlSuspenseQuery(...args: any[]): any; + function useScrapeGroupUrlLazyQuery(...args: any[]): any; + function useScrapeGroupUrlQuery(...args: any[]): any; + function useScrapeGroupUrlSuspenseQuery(...args: any[]): any; function useScrapeMultiPerformersLazyQuery(...args: any[]): any; function useScrapeMultiPerformersQuery(...args: any[]): any; function useScrapeMultiPerformersSuspenseQuery(...args: any[]): any; @@ -664,8 +664,8 @@ declare namespace PluginApi { StudioIDSelect: React.FC; GallerySelect: React.FC; GalleryIDSelect: React.FC; - MovieSelect: React.FC; - MovieIDSelect: React.FC; + GroupSelect: React.FC; + GroupIDSelect: React.FC; SceneSelect: React.FC; SceneIDSelect: React.FC; DateInput: React.FC; @@ -767,9 +767,9 @@ declare namespace PluginApi { function queryFindGalleriesByIDForSelect(...args: any[]): any; function queryFindGalleriesForSelect(...args: any[]): any; function queryFindImages(...args: any[]): any; - function queryFindMovies(...args: any[]): any; - function queryFindMoviesByIDForSelect(...args: any[]): any; - function queryFindMoviesForSelect(...args: any[]): any; + function queryFindGroups(...args: any[]): any; + function queryFindGroupsByIDForSelect(...args: any[]): any; + function queryFindGroupsForSelect(...args: any[]): any; function queryFindPerformer(...args: any[]): any; function queryFindPerformers(...args: any[]): any; function queryFindPerformersByIDForSelect(...args: any[]): any; @@ -789,7 +789,7 @@ declare namespace PluginApi { function querySceneByPathRegex(...args: any[]): any; function queryScrapeGallery(...args: any[]): any; function queryScrapeGalleryURL(...args: any[]): any; - function queryScrapeMovieURL(...args: any[]): any; + function queryScrapeGroupURL(...args: any[]): any; function queryScrapePerformer(...args: any[]): any; function queryScrapePerformerURL(...args: any[]): any; function queryScrapeScene(...args: any[]): any; @@ -802,7 +802,7 @@ declare namespace PluginApi { function useAddTempDLNAIP(...args: any[]): any; function useBulkGalleryUpdate(...args: any[]): any; function useBulkImageUpdate(...args: any[]): any; - function useBulkMovieUpdate(...args: any[]): any; + function useBulkGroupUpdate(...args: any[]): any; function useBulkPerformerUpdate(...args: any[]): any; function useBulkSceneUpdate(...args: any[]): any; function useConfiguration(...args: any[]): any; @@ -822,8 +822,8 @@ declare namespace PluginApi { function useFindGallery(...args: any[]): any; function useFindImage(...args: any[]): any; function useFindImages(...args: any[]): any; - function useFindMovie(...args: any[]): any; - function useFindMovies(...args: any[]): any; + function useFindGroup(...args: any[]): any; + function useFindGroups(...args: any[]): any; function useFindPerformer(...args: any[]): any; function useFindPerformers(...args: any[]): any; function useFindSavedFilter(...args: any[]): any; @@ -853,16 +853,16 @@ declare namespace PluginApi { function useJobsSubscribe(...args: any[]): any; function useLatestVersion(...args: any[]): any; function useListGalleryScrapers(...args: any[]): any; - function useListMovieScrapers(...args: any[]): any; + function useListGroupScrapers(...args: any[]): any; function useListPerformerScrapers(...args: any[]): any; function useListSceneScrapers(...args: any[]): any; function useLoggingSubscribe(...args: any[]): any; function useLogs(...args: any[]): any; function useMarkerStrings(...args: any[]): any; - function useMovieCreate(...args: any[]): any; - function useMovieDestroy(...args: any[]): any; - function useMovieUpdate(...args: any[]): any; - function useMoviesDestroy(...args: any[]): any; + function useGroupCreate(...args: any[]): any; + function useGroupDestroy(...args: any[]): any; + function useGroupUpdate(...args: any[]): any; + function useGroupsDestroy(...args: any[]): any; function usePerformerCreate(...args: any[]): any; function usePerformerDestroy(...args: any[]): any; function usePerformerUpdate(...args: any[]): any; diff --git a/ui/v2.5/src/utils/bulkUpdate.ts b/ui/v2.5/src/utils/bulkUpdate.ts index 9856c9336..70a5b38fc 100644 --- a/ui/v2.5/src/utils/bulkUpdate.ts +++ b/ui/v2.5/src/utils/bulkUpdate.ts @@ -82,12 +82,12 @@ export function getAggregateTagIds(state: { tags: IHasID[] }[]) { } interface IGroup { - movie: IHasID; + group: IHasID; } -export function getAggregateGroupIds(state: { movies: IGroup[] }[]) { +export function getAggregateGroupIds(state: { groups: IGroup[] }[]) { const sortedLists = state.map((o) => - o.movies.map((oo) => oo.movie.id).sort() + o.groups.map((oo) => oo.group.id).sort() ); return getAggregateIds(sortedLists); } diff --git a/ui/v2.5/src/utils/navigation.ts b/ui/v2.5/src/utils/navigation.ts index c246d699a..d30da8d7a 100644 --- a/ui/v2.5/src/utils/navigation.ts +++ b/ui/v2.5/src/utils/navigation.ts @@ -12,7 +12,7 @@ import { TagsCriterionOption, } from "src/models/list-filter/criteria/tags"; import { ListFilterModel } from "src/models/list-filter/filter"; -import { MoviesCriterion } from "src/models/list-filter/criteria/movies"; +import { GroupsCriterion } from "src/models/list-filter/criteria/movies"; import { Criterion, CriterionOption, @@ -109,7 +109,7 @@ const makePerformerGroupsUrl = ( extraCriteria?: Criterion[] ) => { if (!performer.id) return "#"; - const filter = new ListFilterModel(GQL.FilterMode.Movies, undefined); + const filter = new ListFilterModel(GQL.FilterMode.Groups, undefined); const criterion = new PerformersCriterion(); criterion.value.items = [ { id: performer.id, label: performer.name || `Performer ${performer.id}` }, @@ -176,7 +176,7 @@ const makeStudioGalleriesUrl = (studio: Partial) => { const makeStudioGroupsUrl = (studio: Partial) => { if (!studio.id) return "#"; - const filter = new ListFilterModel(GQL.FilterMode.Movies, undefined); + const filter = new ListFilterModel(GQL.FilterMode.Groups, undefined); const criterion = new StudiosCriterion(); criterion.value = { items: [{ id: studio.id, label: studio.name || `Studio ${studio.id}` }], @@ -211,10 +211,10 @@ const makeChildStudiosUrl = (studio: Partial) => { return `/studios?${filter.makeQueryParameters()}`; }; -const makeGroupScenesUrl = (group: Partial) => { +const makeGroupScenesUrl = (group: Partial) => { if (!group.id) return "#"; const filter = new ListFilterModel(GQL.FilterMode.Scenes, undefined); - const criterion = new MoviesCriterion(); + const criterion = new GroupsCriterion(); criterion.value = [ { id: group.id, label: group.name || `Group ${group.id}` }, ]; @@ -299,7 +299,7 @@ const makeTagImagesUrl = (tag: INamedObject) => { }; const makeTagGroupsUrl = (tag: INamedObject) => { - return `/groups?${makeTagFilter(GQL.FilterMode.Movies, tag)}`; + return `/groups?${makeTagFilter(GQL.FilterMode.Groups, tag)}`; }; type SceneMarkerDataFragment = Pick & { @@ -351,7 +351,7 @@ const makeDirectorScenesUrl = (director: string) => { const makeDirectorGroupsUrl = (director: string) => { if (director.length == 0) return "#"; - const filter = new ListFilterModel(GQL.FilterMode.Movies, undefined); + const filter = new ListFilterModel(GQL.FilterMode.Groups, undefined); filter.criteria.push( stringEqualsCriterion(createStringCriterionOption("director"), director) );