diff --git a/pkg/sqlite/gallery.go b/pkg/sqlite/gallery.go index d2e475ffe..445baa46a 100644 --- a/pkg/sqlite/gallery.go +++ b/pkg/sqlite/gallery.go @@ -199,6 +199,7 @@ func (qb *galleryQueryBuilder) makeQuery(galleryFilter *models.GalleryFilterType query.handleStringCriterionInput(galleryFilter.Path, "galleries.path") query.handleIntCriterionInput(galleryFilter.Rating, "galleries.rating") query.handleStringCriterionInput(galleryFilter.URL, "galleries.url") + query.handleCountCriterion(galleryFilter.ImageCount, galleryTable, galleriesImagesTable, galleryIDColumn) qb.handleAverageResolutionFilter(&query, galleryFilter.AverageResolution) if Organized := galleryFilter.Organized; Organized != nil { diff --git a/pkg/sqlite/gallery_test.go b/pkg/sqlite/gallery_test.go index 999224bdc..7b42133a4 100644 --- a/pkg/sqlite/gallery_test.go +++ b/pkg/sqlite/gallery_test.go @@ -712,6 +712,56 @@ func verifyGalleriesPerformerCount(t *testing.T, performerCountCriterion models. }) } +func TestGalleryQueryImageCount(t *testing.T) { + const imageCount = 0 + imageCountCriterion := models.IntCriterionInput{ + Value: imageCount, + Modifier: models.CriterionModifierEquals, + } + + verifyGalleriesImageCount(t, imageCountCriterion) + + imageCountCriterion.Modifier = models.CriterionModifierNotEquals + verifyGalleriesImageCount(t, imageCountCriterion) + + imageCountCriterion.Modifier = models.CriterionModifierGreaterThan + verifyGalleriesImageCount(t, imageCountCriterion) + + imageCountCriterion.Modifier = models.CriterionModifierLessThan + verifyGalleriesImageCount(t, imageCountCriterion) +} + +func verifyGalleriesImageCount(t *testing.T, imageCountCriterion models.IntCriterionInput) { + withTxn(func(r models.Repository) error { + sqb := r.Gallery() + galleryFilter := models.GalleryFilterType{ + ImageCount: &imageCountCriterion, + } + + galleries := queryGallery(t, sqb, &galleryFilter, nil) + assert.Greater(t, len(galleries), -1) + + for _, gallery := range galleries { + pp := 0 + + _, count, err := r.Image().Query(&models.ImageFilterType{ + Galleries: &models.MultiCriterionInput{ + Value: []string{strconv.Itoa(gallery.ID)}, + Modifier: models.CriterionModifierIncludes, + }, + }, &models.FindFilterType{ + PerPage: &pp, + }) + if err != nil { + return err + } + verifyInt(t, count, imageCountCriterion) + } + + return nil + }) +} + // TODO Count // TODO All // TODO Query diff --git a/ui/v2.5/src/models/list-filter/filter.ts b/ui/v2.5/src/models/list-filter/filter.ts index acb6b4b5d..64b282b3e 100644 --- a/ui/v2.5/src/models/list-filter/filter.ts +++ b/ui/v2.5/src/models/list-filter/filter.ts @@ -281,11 +281,13 @@ export class ListFilterModel { case FilterMode.Galleries: this.sortBy = defaultSort ?? "path"; this.sortByOptions = [ + "date", "path", "file_mod_time", "images_count", "tag_count", "performer_count", + "title", "random", ]; this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List]; @@ -301,6 +303,7 @@ export class ListFilterModel { new PerformerTagsCriterionOption(), new PerformersCriterionOption(), ListFilterModel.createCriterionOption("performer_count"), + ListFilterModel.createCriterionOption("image_count"), new StudiosCriterionOption(), ListFilterModel.createCriterionOption("url"), ]; @@ -463,7 +466,7 @@ export class ListFilterModel { this.itemsPerPage !== DEFAULT_PARAMS.itemsPerPage ? this.itemsPerPage : undefined, - sortby: this.sortBy !== "date" ? this.getSortBy() : undefined, + sortby: this.getSortBy() ?? undefined, sortdir: this.sortDirection === SortDirectionEnum.Desc ? "desc" : undefined, disp: @@ -1215,6 +1218,14 @@ export class ListFilterModel { }; break; } + case "image_count": { + const countCrit = criterion as NumberCriterion; + result.image_count = { + value: countCrit.value, + modifier: countCrit.modifier, + }; + break; + } case "studios": { const studCrit = criterion as StudiosCriterion; result.studios = {