mirror of https://github.com/stashapp/stash.git
Add group graphql interfaces (#5017)
* Deprecate movie and add group interfaces * UI changes
This commit is contained in:
parent
f477b996b5
commit
2739696813
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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!]!
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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!]!
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
fragment MovieData on Movie {
|
||||
fragment GroupData on Group {
|
||||
id
|
||||
name
|
||||
aliases
|
|
@ -23,7 +23,7 @@ fragment PerformerData on Performer {
|
|||
scene_count
|
||||
image_count
|
||||
gallery_count
|
||||
movie_count
|
||||
group_count
|
||||
performer_count
|
||||
o_counter
|
||||
|
||||
|
|
|
@ -58,8 +58,8 @@ fragment SlimSceneData on Scene {
|
|||
image_path
|
||||
}
|
||||
|
||||
movies {
|
||||
movie {
|
||||
groups {
|
||||
group {
|
||||
id
|
||||
name
|
||||
front_image_path
|
||||
|
|
|
@ -53,9 +53,9 @@ fragment SceneData on Scene {
|
|||
...SlimStudioData
|
||||
}
|
||||
|
||||
movies {
|
||||
movie {
|
||||
...MovieData
|
||||
groups {
|
||||
group {
|
||||
...GroupData
|
||||
}
|
||||
scene_index
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -16,7 +16,7 @@ query Stats {
|
|||
gallery_count
|
||||
performer_count
|
||||
studio_count
|
||||
movie_count
|
||||
group_count
|
||||
tag_count
|
||||
total_o_count
|
||||
total_play_duration
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ const RecommendationRow: React.FC<IFilter> = ({ mode, filter, header }) => {
|
|||
/>
|
||||
);
|
||||
case GQL.FilterMode.Movies:
|
||||
case GQL.FilterMode.Groups:
|
||||
return (
|
||||
<GroupRecommendationRow
|
||||
isTouch={isTouch}
|
||||
|
|
|
@ -23,6 +23,7 @@ const FilterModeToMessageID = {
|
|||
[GQL.FilterMode.Galleries]: "galleries",
|
||||
[GQL.FilterMode.Images]: "images",
|
||||
[GQL.FilterMode.Movies]: "groups",
|
||||
[GQL.FilterMode.Groups]: "groups",
|
||||
[GQL.FilterMode.Performers]: "performers",
|
||||
[GQL.FilterMode.SceneMarkers]: "markers",
|
||||
[GQL.FilterMode.Scenes]: "scenes",
|
||||
|
|
|
@ -194,6 +194,7 @@ const FilterModeToConfigKey = {
|
|||
[FilterMode.Galleries]: "galleries",
|
||||
[FilterMode.Images]: "images",
|
||||
[FilterMode.Movies]: "groups",
|
||||
[FilterMode.Groups]: "groups",
|
||||
[FilterMode.Performers]: "performers",
|
||||
[FilterMode.SceneMarkers]: "sceneMarkers",
|
||||
[FilterMode.Scenes]: "scenes",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Form, Col, Row } from "react-bootstrap";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useBulkMovieUpdate } from "src/core/StashService";
|
||||
import { useBulkGroupUpdate } from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { ModalComponent } from "../Shared/Modal";
|
||||
import { StudioSelect } from "../Shared/Select";
|
||||
|
@ -20,7 +20,7 @@ import { isEqual } from "lodash-es";
|
|||
import { MultiSet } from "../Shared/MultiSet";
|
||||
|
||||
interface IListOperationProps {
|
||||
selected: GQL.MovieDataFragment[];
|
||||
selected: GQL.GroupDataFragment[];
|
||||
onClose: (applied: boolean) => void;
|
||||
}
|
||||
|
||||
|
@ -39,32 +39,32 @@ export const EditGroupsDialog: React.FC<IListOperationProps> = (
|
|||
const [tagIds, setTagIds] = useState<string[]>();
|
||||
const [existingTagIds, setExistingTagIds] = useState<string[]>();
|
||||
|
||||
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<IListOperationProps> = (
|
|||
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 = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { GroupCard } from "./MovieCard";
|
|||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
||||
|
||||
interface IGroupCardGrid {
|
||||
groups: GQL.MovieDataFragment[];
|
||||
groups: GQL.GroupDataFragment[];
|
||||
selectedIds: Set<string>;
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
||||
}
|
||||
|
|
|
@ -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<IProps> = ({ group }) => {
|
|||
const [isEditing, setIsEditing] = useState<boolean>(false);
|
||||
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
|
||||
|
||||
// Editing movie state
|
||||
// Editing group state
|
||||
const [frontImage, setFrontImage] = useState<string | null>();
|
||||
const [backImage, setBackImage] = useState<string | null>();
|
||||
const [encodingImage, setEncodingImage] = useState<boolean>(false);
|
||||
|
@ -106,8 +106,8 @@ const GroupPage: React.FC<IProps> = ({ 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<IProps> = ({ 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<IProps> = ({ 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<IProps> = ({ group }) => {
|
|||
|
||||
function setRating(v: number | null) {
|
||||
if (group.id) {
|
||||
updateMovie({
|
||||
updateGroup({
|
||||
variables: {
|
||||
input: {
|
||||
id: group.id,
|
||||
|
@ -343,7 +343,7 @@ const GroupPage: React.FC<IProps> = ({ group }) => {
|
|||
|
||||
function maybeRenderCompressedDetails() {
|
||||
if (!isEditing && loadStickyHeader) {
|
||||
return <CompressedMovieDetailsPanel group={group} />;
|
||||
return <CompressedGroupDetailsPanel group={group} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,16 +441,16 @@ const GroupLoader: React.FC<RouteComponentProps<IGroupParams>> = ({
|
|||
match,
|
||||
}) => {
|
||||
const { id } = match.params;
|
||||
const { data, loading, error } = useFindMovie(id);
|
||||
const { data, loading, error } = useFindGroup(id);
|
||||
|
||||
useScrollToTopOnMount();
|
||||
|
||||
if (loading) return <LoadingIndicator />;
|
||||
if (error) return <ErrorMessage error={error.message} />;
|
||||
if (!data?.findMovie)
|
||||
return <ErrorMessage error={`No movie found with id ${id}.`} />;
|
||||
if (!data?.findGroup)
|
||||
return <ErrorMessage error={`No group found with id ${id}.`} />;
|
||||
|
||||
return <GroupPage group={data.findMovie} />;
|
||||
return <GroupPage group={data.findGroup} />;
|
||||
};
|
||||
|
||||
export default GroupLoader;
|
||||
|
|
|
@ -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<string | null>();
|
||||
const [backImage, setBackImage] = useState<string | null>();
|
||||
const [encodingImage, setEncodingImage] = useState<boolean>(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() }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<IGroupDetailsPanel> = ({
|
|||
);
|
||||
};
|
||||
|
||||
export const CompressedMovieDetailsPanel: React.FC<IGroupDetailsPanel> = ({
|
||||
export const CompressedGroupDetailsPanel: React.FC<IGroupDetailsPanel> = ({
|
||||
group,
|
||||
}) => {
|
||||
function scrollToTop() {
|
||||
|
|
|
@ -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<GQL.MovieDataFragment>;
|
||||
onSubmit: (movie: GQL.MovieCreateInput) => Promise<void>;
|
||||
group: Partial<GQL.GroupDataFragment>;
|
||||
onSubmit: (group: GQL.GroupCreateInput) => Promise<void>;
|
||||
onCancel: () => void;
|
||||
onDelete: () => void;
|
||||
setFrontImage: (image?: string | null) => void;
|
||||
|
@ -56,8 +56,8 @@ export const GroupEditPanel: React.FC<IGroupEditPanel> = ({
|
|||
|
||||
const [imageClipboard, setImageClipboard] = useState<string>();
|
||||
|
||||
const Scrapers = useListMovieScrapers();
|
||||
const [scrapedGroup, setScrapedGroup] = useState<GQL.ScrapedMovie>();
|
||||
const Scrapers = useListGroupScrapers();
|
||||
const [scrapedGroup, setScrapedGroup] = useState<GQL.ScrapedGroup>();
|
||||
|
||||
const [studio, setStudio] = useState<Studio | null>(null);
|
||||
|
||||
|
@ -129,7 +129,7 @@ export const GroupEditPanel: React.FC<IGroupEditPanel> = ({
|
|||
});
|
||||
|
||||
function updateGroupEditStateFromScraper(
|
||||
state: Partial<GQL.ScrapedMovieDataFragment>
|
||||
state: Partial<GQL.ScrapedGroupDataFragment>
|
||||
) {
|
||||
if (state.name) {
|
||||
formik.setFieldValue("name", state.name);
|
||||
|
@ -190,21 +190,21 @@ export const GroupEditPanel: React.FC<IGroupEditPanel> = ({
|
|||
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<IGroupEditPanel> = ({
|
|||
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<IGroupEditPanel> = ({
|
|||
);
|
||||
}
|
||||
|
||||
function onScrapeDialogClosed(p?: GQL.ScrapedMovieDataFragment) {
|
||||
function onScrapeDialogClosed(p?: GQL.ScrapedGroupDataFragment) {
|
||||
if (p) {
|
||||
updateGroupEditStateFromScraper(p);
|
||||
}
|
||||
|
@ -381,7 +381,7 @@ export const GroupEditPanel: React.FC<IGroupEditPanel> = ({
|
|||
<Prompt
|
||||
when={formik.dirty}
|
||||
message={(location, action) => {
|
||||
// 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<IGroupEditPanel> = ({
|
|||
{renderDateField("date")}
|
||||
{renderStudioField()}
|
||||
{renderInputField("director")}
|
||||
{renderURLListField("urls", onScrapeMovieURL, urlScrapable)}
|
||||
{renderURLListField("urls", onScrapeGroupURL, urlScrapable)}
|
||||
{renderInputField("synopsis", "textarea")}
|
||||
{renderTagsField()}
|
||||
</Form>
|
||||
|
|
|
@ -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<IGroupScenesPanel> = ({
|
||||
|
@ -15,32 +15,32 @@ export const GroupScenesPanel: React.FC<IGroupScenesPanel> = ({
|
|||
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<IGroupScenesPanel> = ({
|
|||
return (
|
||||
<SceneList
|
||||
filterHook={filterHook}
|
||||
defaultSort="movie_scene_number"
|
||||
defaultSort="group_scene_number"
|
||||
alterQuery={active}
|
||||
view={View.GroupScenes}
|
||||
/>
|
||||
|
|
|
@ -21,12 +21,12 @@ import { Tag } from "src/components/Tags/TagSelect";
|
|||
import { useScrapedTags } from "src/components/Shared/ScrapeDialog/scrapedTags";
|
||||
|
||||
interface IGroupScrapeDialogProps {
|
||||
group: Partial<GQL.MovieUpdateInput>;
|
||||
group: Partial<GQL.GroupUpdateInput>;
|
||||
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<IGroupScrapeDialogProps> = ({
|
||||
|
@ -126,7 +126,7 @@ export const GroupScrapeDialog: React.FC<IGroupScrapeDialogProps> = ({
|
|||
return <></>;
|
||||
}
|
||||
|
||||
function makeNewScrapedItem(): GQL.ScrapedMovie {
|
||||
function makeNewScrapedItem(): GQL.ScrapedGroup {
|
||||
const newStudioValue = studio.getNewValue();
|
||||
const durationString = duration.getNewValue();
|
||||
|
||||
|
|
|
@ -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<IGroupList> = ({
|
|||
];
|
||||
|
||||
function addKeybinds(
|
||||
result: GQL.FindMoviesQueryResult,
|
||||
result: GQL.FindGroupsQueryResult,
|
||||
filter: ListFilterModel
|
||||
) {
|
||||
Mousetrap.bind("p r", () => {
|
||||
|
@ -75,21 +75,21 @@ export const GroupList: React.FC<IGroupList> = ({
|
|||
}
|
||||
|
||||
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<IGroupList> = ({
|
|||
}
|
||||
|
||||
function renderContent(
|
||||
result: GQL.FindMoviesQueryResult,
|
||||
result: GQL.FindGroupsQueryResult,
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void
|
||||
|
@ -116,7 +116,7 @@ export const GroupList: React.FC<IGroupList> = ({
|
|||
return (
|
||||
<ExportDialog
|
||||
exportInput={{
|
||||
movies: {
|
||||
groups: {
|
||||
ids: Array.from(selectedIds.values()),
|
||||
all: isExportAll,
|
||||
},
|
||||
|
@ -128,12 +128,12 @@ export const GroupList: React.FC<IGroupList> = ({
|
|||
}
|
||||
|
||||
function renderGroups() {
|
||||
if (!result.data?.findMovies) return;
|
||||
if (!result.data?.findGroups) return;
|
||||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<GroupCardGrid
|
||||
groups={result.data.findMovies.movies}
|
||||
groups={result.data.findGroups.groups}
|
||||
selectedIds={selectedIds}
|
||||
onSelectChange={onSelectChange}
|
||||
/>
|
||||
|
@ -152,14 +152,14 @@ export const GroupList: React.FC<IGroupList> = ({
|
|||
}
|
||||
|
||||
function renderEditDialog(
|
||||
selectedGroups: GQL.MovieDataFragment[],
|
||||
selectedGroups: GQL.GroupDataFragment[],
|
||||
onClose: (applied: boolean) => void
|
||||
) {
|
||||
return <EditGroupsDialog selected={selectedGroups} onClose={onClose} />;
|
||||
}
|
||||
|
||||
function renderDeleteDialog(
|
||||
selectedGroups: GQL.SlimMovieDataFragment[],
|
||||
selectedGroups: GQL.SlimGroupDataFragment[],
|
||||
onClose: (confirmed: boolean) => void
|
||||
) {
|
||||
return (
|
||||
|
@ -168,7 +168,7 @@ export const GroupList: React.FC<IGroupList> = ({
|
|||
onClose={onClose}
|
||||
singularEntity={intl.formatMessage({ id: "group" })}
|
||||
pluralEntity={intl.formatMessage({ id: "groups" })}
|
||||
destroyMutation={useMoviesDestroy}
|
||||
destroyMutation={useGroupsDestroy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<IProps> = (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<IProps> = (props: IProps) => {
|
|||
? [...Array(props.filter.itemsPerPage)].map((i) => (
|
||||
<div key={`_${i}`} className="group-skeleton skeleton-card"></div>
|
||||
))
|
||||
: result.data?.findMovies.movies.map((m) => (
|
||||
<GroupCard key={m.id} group={m} />
|
||||
: result.data?.findGroups.groups.map((g) => (
|
||||
<GroupCard key={g.id} group={g} />
|
||||
))}
|
||||
</Slider>
|
||||
</RecommendationRow>
|
||||
|
|
|
@ -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<GQL.Studio, "name"> | null;
|
||||
};
|
||||
type Option = SelectOption<Group>;
|
||||
|
||||
type FindMoviesResult = Awaited<
|
||||
ReturnType<typeof queryFindMoviesForSelect>
|
||||
>["data"]["findMovies"]["movies"];
|
||||
type FindGroupsResult = Awaited<
|
||||
ReturnType<typeof queryFindGroupsForSelect>
|
||||
>["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<Option[]> {
|
||||
const filter = new ListFilterModel(GQL.FilterMode.Movies);
|
||||
async function loadGroups(input: string): Promise<Option[]> {
|
||||
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<IFilterProps & IFilterIDProps<Group>> = (
|
|||
}
|
||||
|
||||
async function loadObjectsByID(idsToLoad: string[]): Promise<Group[]> {
|
||||
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(() => {
|
||||
|
|
|
@ -28,7 +28,7 @@ export interface IPerformerCardExtraCriteria {
|
|||
scenes?: Criterion<CriterionValue>[];
|
||||
images?: Criterion<CriterionValue>[];
|
||||
galleries?: Criterion<CriterionValue>[];
|
||||
movies?: Criterion<CriterionValue>[];
|
||||
groups?: Criterion<CriterionValue>[];
|
||||
performer?: ILabeledId;
|
||||
}
|
||||
|
||||
|
@ -179,17 +179,17 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||
}
|
||||
|
||||
function maybeRenderGroupsPopoverButton() {
|
||||
if (!performer.movie_count) return;
|
||||
if (!performer.group_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="group-count"
|
||||
type="group"
|
||||
count={performer.movie_count}
|
||||
count={performer.group_count}
|
||||
url={NavUtils.makePerformerGroupsUrl(
|
||||
performer,
|
||||
extraCriteria?.performer,
|
||||
extraCriteria?.movies
|
||||
extraCriteria?.groups
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
@ -202,7 +202,7 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||
performer.gallery_count ||
|
||||
performer.tags.length > 0 ||
|
||||
performer.o_counter ||
|
||||
performer.movie_count
|
||||
performer.group_count
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -145,7 +145,7 @@ const PerformerPage: React.FC<IProps> = ({ 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<IProps> = ({ performer, tabKey }) => {
|
|||
{intl.formatMessage({ id: "groups" })}
|
||||
<Counter
|
||||
abbreviateCounter={abbreviateCounter}
|
||||
count={performer.movie_count}
|
||||
count={performer.group_count}
|
||||
hideZero
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -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) => (
|
||||
<div className="group-tag-container row" key={sceneMovie.movie.id}>
|
||||
const popoverContent = scene.groups.map((sceneGroup) => (
|
||||
<div className="group-tag-container row" key={sceneGroup.group.id}>
|
||||
<Link
|
||||
to={`/groups/${sceneMovie.movie.id}`}
|
||||
to={`/groups/${sceneGroup.group.id}`}
|
||||
className="group-tag col m-auto zoom-2"
|
||||
>
|
||||
<img
|
||||
className="image-thumbnail"
|
||||
alt={sceneMovie.movie.name ?? ""}
|
||||
src={sceneMovie.movie.front_image_path ?? ""}
|
||||
alt={sceneGroup.group.name ?? ""}
|
||||
src={sceneGroup.group.front_image_path ?? ""}
|
||||
/>
|
||||
</Link>
|
||||
<GroupLink
|
||||
key={sceneMovie.movie.id}
|
||||
group={sceneMovie.movie}
|
||||
key={sceneGroup.group.id}
|
||||
group={sceneGroup.group}
|
||||
className="d-block"
|
||||
/>
|
||||
</div>
|
||||
|
@ -417,7 +417,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
|||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon={faFilm} />
|
||||
<span>{scene.movies.length}</span>
|
||||
<span>{scene.groups.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
);
|
||||
|
@ -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 ||
|
||||
|
|
|
@ -79,7 +79,7 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
|
|||
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<IListOperationProps> = (
|
|||
.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;
|
||||
|
|
|
@ -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) => (
|
||||
<div className="group-tag-container row" key={sceneGroup.movie.id}>
|
||||
const popoverContent = props.scene.groups.map((sceneGroup) => (
|
||||
<div className="group-tag-container row" key={sceneGroup.group.id}>
|
||||
<Link
|
||||
to={`/groups/${sceneGroup.movie.id}`}
|
||||
to={`/groups/${sceneGroup.group.id}`}
|
||||
className="group-tag col m-auto zoom-2"
|
||||
>
|
||||
<img
|
||||
className="image-thumbnail"
|
||||
alt={sceneGroup.movie.name ?? ""}
|
||||
src={sceneGroup.movie.front_image_path ?? ""}
|
||||
alt={sceneGroup.group.name ?? ""}
|
||||
src={sceneGroup.group.front_image_path ?? ""}
|
||||
/>
|
||||
</Link>
|
||||
<GroupLink
|
||||
key={sceneGroup.movie.id}
|
||||
group={sceneGroup.movie}
|
||||
key={sceneGroup.group.id}
|
||||
group={sceneGroup.group}
|
||||
className="d-block"
|
||||
/>
|
||||
</div>
|
||||
|
@ -174,7 +174,7 @@ const SceneCardPopovers = PatchComponent(
|
|||
>
|
||||
<Button className="minimal">
|
||||
<Icon icon={faFilm} />
|
||||
<span>{props.scene.movies.length}</span>
|
||||
<span>{props.scene.groups.length}</span>
|
||||
</Button>
|
||||
</HoverPopover>
|
||||
);
|
||||
|
@ -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 ||
|
||||
|
|
|
@ -441,12 +441,12 @@ const ScenePage: React.FC<IProps> = ({
|
|||
<FormattedMessage id="markers" />
|
||||
</Nav.Link>
|
||||
</Nav.Item>
|
||||
{scene.movies.length > 0 ? (
|
||||
{scene.groups.length > 0 ? (
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="scene-group-panel">
|
||||
<FormattedMessage
|
||||
id="countables.groups"
|
||||
values={{ count: scene.movies.length }}
|
||||
values={{ count: scene.groups.length }}
|
||||
/>
|
||||
</Nav.Link>
|
||||
</Nav.Item>
|
||||
|
|
|
@ -104,8 +104,8 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||
}, [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<IProps> = ({
|
|||
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<IProps> = ({
|
|||
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<IProps> = ({
|
|||
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<IProps> = ({
|
|||
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<IProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
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<IProps> = ({
|
|||
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 = (
|
||||
<SceneGroupTable value={movieEntries} onUpdate={onSetMovieEntries} />
|
||||
<SceneGroupTable value={groupEntries} onUpdate={onSetGroupEntries} />
|
||||
);
|
||||
|
||||
return renderField("movies", title, control, fullWidthProps);
|
||||
return renderField("groups", title, control, fullWidthProps);
|
||||
}
|
||||
|
||||
function renderTagsField() {
|
||||
|
@ -820,7 +820,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||
{renderGalleriesField()}
|
||||
{renderStudioField()}
|
||||
{renderPerformersField()}
|
||||
{renderMoviesField()}
|
||||
{renderGroupsField()}
|
||||
{renderTagsField()}
|
||||
|
||||
{renderStashIDsField(
|
||||
|
|
|
@ -9,10 +9,10 @@ interface ISceneGroupPanelProps {
|
|||
export const SceneGroupPanel: React.FC<ISceneGroupPanelProps> = (
|
||||
props: ISceneGroupPanelProps
|
||||
) => {
|
||||
const cards = props.scene.movies.map((sceneGroup) => (
|
||||
const cards = props.scene.groups.map((sceneGroup) => (
|
||||
<GroupCard
|
||||
key={sceneGroup.movie.id}
|
||||
group={sceneGroup.movie}
|
||||
key={sceneGroup.group.id}
|
||||
group={sceneGroup.group}
|
||||
sceneIndex={sceneGroup.scene_index ?? undefined}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -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<string, number | undefined>;
|
||||
export type GroupSceneIndexMap = Map<string, number | undefined>;
|
||||
|
||||
export interface IGroupEntry {
|
||||
movie: Group;
|
||||
group: Group;
|
||||
scene_index?: GQL.InputMaybe<number> | undefined;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ export const SceneGroupTable: React.FC<IProps> = (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<IProps> = (props) => {
|
|||
if (i === index) {
|
||||
return {
|
||||
...existing,
|
||||
movie: group,
|
||||
group: group,
|
||||
};
|
||||
}
|
||||
return existing;
|
||||
|
@ -71,7 +71,7 @@ export const SceneGroupTable: React.FC<IProps> = (props) => {
|
|||
const newValues = [
|
||||
...value,
|
||||
{
|
||||
movie: group,
|
||||
group: group,
|
||||
scene_index: null,
|
||||
},
|
||||
];
|
||||
|
@ -83,11 +83,11 @@ export const SceneGroupTable: React.FC<IProps> = (props) => {
|
|||
return (
|
||||
<>
|
||||
{value.map((m, i) => (
|
||||
<Row key={m.movie.id} className="group-row">
|
||||
<Row key={m.group.id} className="group-row">
|
||||
<Col xs={9}>
|
||||
<GroupSelect
|
||||
onSelect={(items) => onGroupSet(i, items)}
|
||||
values={[m.movie!]}
|
||||
values={[m.group!]}
|
||||
excludeIds={groupIDs}
|
||||
/>
|
||||
</Col>
|
||||
|
|
|
@ -115,20 +115,20 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
|||
);
|
||||
|
||||
const [groups, setGroups] = useState<
|
||||
ObjectListScrapeResult<GQL.ScrapedMovie>
|
||||
ObjectListScrapeResult<GQL.ScrapedGroup>
|
||||
>(
|
||||
new ObjectListScrapeResult<GQL.ScrapedMovie>(
|
||||
new ObjectListScrapeResult<GQL.ScrapedGroup>(
|
||||
sortStoredIdObjects(
|
||||
sceneGroups.map((p) => ({
|
||||
stored_id: p.id,
|
||||
name: p.name,
|
||||
}))
|
||||
),
|
||||
sortStoredIdObjects(scraped.movies ?? undefined)
|
||||
sortStoredIdObjects(scraped.groups ?? undefined)
|
||||
)
|
||||
);
|
||||
const [newGroups, setNewGroups] = useState<GQL.ScrapedMovie[]>(
|
||||
scraped.movies?.filter((t) => !t.stored_id) ?? []
|
||||
const [newGroups, setNewGroups] = useState<GQL.ScrapedGroup[]>(
|
||||
scraped.groups?.filter((t) => !t.stored_id) ?? []
|
||||
);
|
||||
|
||||
const { tags, newTags, scrapedTagsRow } = useScrapedTags(
|
||||
|
@ -202,7 +202,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
|||
director: director.getNewValue(),
|
||||
studio: newStudioValue,
|
||||
performers: performers.getNewValue(),
|
||||
movies: groups.getNewValue(),
|
||||
groups: groups.getNewValue(),
|
||||
tags: tags.getNewValue(),
|
||||
details: details.getNewValue(),
|
||||
image: image.getNewValue(),
|
||||
|
|
|
@ -126,10 +126,10 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
|
|||
|
||||
const GroupCell = (scene: GQL.SlimSceneDataFragment) => (
|
||||
<ul className="comma-list overflowable">
|
||||
{scene.movies.map((sceneGroup) => (
|
||||
<li key={sceneGroup.movie.id}>
|
||||
<Link to={NavUtils.makeGroupScenesUrl(sceneGroup.movie)}>
|
||||
<span className="ellips-data">{sceneGroup.movie.name}</span>
|
||||
{scene.groups.map((sceneGroup) => (
|
||||
<li key={sceneGroup.group.id}>
|
||||
<Link to={NavUtils.makeGroupScenesUrl(sceneGroup.group)}>
|
||||
<span className="ellips-data">{sceneGroup.group.name}</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
|
|
|
@ -100,10 +100,10 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||
};
|
||||
}
|
||||
|
||||
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<ISceneMergeDetailsProps> = ({
|
|||
);
|
||||
|
||||
const [groups, setGroups] = useState<
|
||||
ObjectListScrapeResult<GQL.ScrapedMovie>
|
||||
ObjectListScrapeResult<GQL.ScrapedGroup>
|
||||
>(
|
||||
new ObjectListScrapeResult<GQL.ScrapedMovie>(
|
||||
sortStoredIdObjects(dest.movies.map(groupToStoredID))
|
||||
new ObjectListScrapeResult<GQL.ScrapedGroup>(
|
||||
sortStoredIdObjects(dest.groups.map(groupToStoredID))
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -253,9 +253,9 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||
);
|
||||
|
||||
setGroups(
|
||||
new ObjectListScrapeResult<GQL.ScrapedMovie>(
|
||||
sortStoredIdObjects(dest.movies.map(groupToStoredID)),
|
||||
uniqIDStoredIDs(all.map((s) => s.movies.map(groupToStoredID)).flat())
|
||||
new ObjectListScrapeResult<GQL.ScrapedGroup>(
|
||||
sortStoredIdObjects(dest.groups.map(groupToStoredID)),
|
||||
uniqIDStoredIDs(all.map((s) => s.groups.map(groupToStoredID)).flat())
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -585,14 +585,14 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||
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,
|
||||
};
|
||||
}),
|
||||
|
|
|
@ -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 = () => {
|
|||
<tr key={scraper.id}>
|
||||
<td>{scraper.name}</td>
|
||||
<td>
|
||||
{renderGroupScrapeTypes(scraper.movie?.supported_scrapes ?? [])}
|
||||
{renderGroupScrapeTypes(scraper.group?.supported_scrapes ?? [])}
|
||||
</td>
|
||||
<td>{renderURLs(scraper.movie?.urls ?? [])}</td>
|
||||
<td>{renderURLs(scraper.group?.urls ?? [])}</td>
|
||||
</tr>
|
||||
));
|
||||
|
||||
|
|
|
@ -197,7 +197,7 @@ export const ScrapedPerformersRow: React.FC<
|
|||
};
|
||||
|
||||
export const ScrapedGroupsRow: React.FC<
|
||||
IScrapedObjectRowImpl<GQL.ScrapedMovie>
|
||||
IScrapedObjectRowImpl<GQL.ScrapedGroup>
|
||||
> = ({ title, result, onChange, newObjects, onCreateNew }) => {
|
||||
const groupsCopy = useMemo(() => {
|
||||
return (
|
||||
|
@ -209,9 +209,9 @@ export const ScrapedGroupsRow: React.FC<
|
|||
}, [newObjects]);
|
||||
|
||||
function renderScrapedGroups(
|
||||
scrapeResult: ScrapeResult<GQL.ScrapedMovie[]>,
|
||||
scrapeResult: ScrapeResult<GQL.ScrapedGroup[]>,
|
||||
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 (
|
||||
<ScrapedObjectsRow<GQL.ScrapedMovie>
|
||||
<ScrapedObjectsRow<GQL.ScrapedGroup>
|
||||
title={title}
|
||||
result={result}
|
||||
renderObjects={renderScrapedGroups}
|
||||
|
|
|
@ -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<GQL.ScrapedMovie>
|
||||
props: IUseCreateNewObjectProps<GQL.ScrapedGroup>
|
||||
) {
|
||||
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
|
||||
|
|
|
@ -50,7 +50,7 @@ export const Stats: React.FC = () => {
|
|||
</div>
|
||||
<div className="stats-element">
|
||||
<p className="title">
|
||||
<FormattedNumber value={data.stats.movie_count} />
|
||||
<FormattedNumber value={data.stats.group_count} />
|
||||
</p>
|
||||
<p className="heading">
|
||||
<FormattedMessage id="groups" />
|
||||
|
|
|
@ -143,13 +143,13 @@ export const StudioCard: React.FC<IProps> = ({
|
|||
}
|
||||
|
||||
function maybeRenderGroupsPopoverButton() {
|
||||
if (!studio.movie_count) return;
|
||||
if (!studio.group_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="group-count"
|
||||
type="group"
|
||||
count={studio.movie_count}
|
||||
count={studio.group_count}
|
||||
url={NavUtils.makeStudioGroupsUrl(studio)}
|
||||
/>
|
||||
);
|
||||
|
@ -190,7 +190,7 @@ export const StudioCard: React.FC<IProps> = ({
|
|||
studio.scene_count ||
|
||||
studio.image_count ||
|
||||
studio.gallery_count ||
|
||||
studio.movie_count ||
|
||||
studio.group_count ||
|
||||
studio.performer_count ||
|
||||
studio.tags.length > 0
|
||||
) {
|
||||
|
|
|
@ -109,7 +109,7 @@ const StudioPage: React.FC<IProps> = ({ 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";
|
||||
|
|
|
@ -25,7 +25,7 @@ export const StudioPerformersPanel: React.FC<IStudioPerformersPanel> = ({
|
|||
scenes: [studioCriterion],
|
||||
images: [studioCriterion],
|
||||
galleries: [studioCriterion],
|
||||
movies: [studioCriterion],
|
||||
groups: [studioCriterion],
|
||||
};
|
||||
|
||||
const filterHook = useStudioFilterHook(studio);
|
||||
|
|
|
@ -237,13 +237,13 @@ export const TagCard: React.FC<IProps> = ({
|
|||
}
|
||||
|
||||
function maybeRenderGroupsPopoverButton() {
|
||||
if (!tag.movie_count) return;
|
||||
if (!tag.group_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="group-count"
|
||||
type="group"
|
||||
count={tag.movie_count}
|
||||
count={tag.group_count}
|
||||
url={NavUtils.makeTagGroupsUrl(tag)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -106,7 +106,7 @@ const TagPage: React.FC<IProps> = ({ 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 =
|
||||
|
|
|
@ -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<GQL.FindMoviesQuery>({
|
||||
query: GQL.FindMoviesDocument,
|
||||
export const queryFindGroups = (filter: ListFilterModel) =>
|
||||
client.query<GQL.FindGroupsQuery>({
|
||||
query: GQL.FindGroupsDocument,
|
||||
variables: {
|
||||
filter: filter.makeFindFilter(),
|
||||
movie_filter: filter.makeFilter(),
|
||||
group_filter: filter.makeFilter(),
|
||||
},
|
||||
});
|
||||
|
||||
export const queryFindMoviesByIDForSelect = (movieIDs: string[]) =>
|
||||
client.query<GQL.FindMoviesForSelectQuery>({
|
||||
query: GQL.FindMoviesForSelectDocument,
|
||||
export const queryFindGroupsByIDForSelect = (groupIDs: string[]) =>
|
||||
client.query<GQL.FindGroupsForSelectQuery>({
|
||||
query: GQL.FindGroupsForSelectDocument,
|
||||
variables: {
|
||||
ids: movieIDs,
|
||||
ids: groupIDs,
|
||||
},
|
||||
});
|
||||
|
||||
export const queryFindMoviesForSelect = (filter: ListFilterModel) =>
|
||||
client.query<GQL.FindMoviesForSelectQuery>({
|
||||
query: GQL.FindMoviesForSelectDocument,
|
||||
export const queryFindGroupsForSelect = (filter: ListFilterModel) =>
|
||||
client.query<GQL.FindGroupsForSelectQuery>({
|
||||
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<GQL.ScrapeMovieUrlQuery>({
|
||||
query: GQL.ScrapeMovieUrlDocument,
|
||||
export const queryScrapeGroupURL = (url: string) =>
|
||||
client.query<GQL.ScrapeGroupUrlQuery>({
|
||||
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,
|
||||
|
|
|
@ -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"),
|
||||
];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue