diff --git a/api/graphql/generated.go b/api/graphql/generated.go index 88b97b79..8bef0a74 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -170,7 +170,7 @@ type ComplexityRoot struct { SetFaceGroupLabel func(childComplexity int, faceGroupID int, label *string) int SetPeriodicScanInterval func(childComplexity int, interval int) int SetScannerConcurrentWorkers func(childComplexity int, workers int) int - SetThumbnailDownsampleMethod func(childComplexity int, method int) int + SetThumbnailDownsampleMethod func(childComplexity int, method models.ThumbnailFilter) int ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int ShareMedia func(childComplexity int, mediaID int, expire *time.Time, password *string) int UpdateUser func(childComplexity int, id int, username *string, password *string, admin *bool) int @@ -328,7 +328,7 @@ type MutationResolver interface { UserRemoveRootAlbum(ctx context.Context, userID int, albumID int) (*models.Album, error) SetPeriodicScanInterval(ctx context.Context, interval int) (int, error) SetScannerConcurrentWorkers(ctx context.Context, workers int) (int, error) - SetThumbnailDownsampleMethod(ctx context.Context, method int) (int, error) + SetThumbnailDownsampleMethod(ctx context.Context, method models.ThumbnailFilter) (models.ThumbnailFilter, error) ChangeUserPreferences(ctx context.Context, language *string) (*models.UserPreferences, error) ResetAlbumCover(ctx context.Context, albumID int) (*models.Album, error) SetAlbumCover(ctx context.Context, coverID int) (*models.Album, error) @@ -1070,7 +1070,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.SetThumbnailDownsampleMethod(childComplexity, args["method"].(int)), true + return e.complexity.Mutation.SetThumbnailDownsampleMethod(childComplexity, args["method"].(models.ThumbnailFilter)), true case "Mutation.shareAlbum": if e.complexity.Mutation.ShareAlbum == nil { @@ -2181,10 +2181,10 @@ func (ec *executionContext) field_Mutation_setScannerConcurrentWorkers_args(ctx func (ec *executionContext) field_Mutation_setThumbnailDownsampleMethod_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} - var arg0 int + var arg0 models.ThumbnailFilter if tmp, ok := rawArgs["method"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("method")) - arg0, err = ec.unmarshalNInt2int(ctx, tmp) + arg0, err = ec.unmarshalNThumbnailFilter2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐThumbnailFilter(ctx, tmp) if err != nil { return nil, err } @@ -7210,7 +7210,7 @@ func (ec *executionContext) _Mutation_setThumbnailDownsampleMethod(ctx context.C resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().SetThumbnailDownsampleMethod(rctx, fc.Args["method"].(int)) + return ec.resolvers.Mutation().SetThumbnailDownsampleMethod(rctx, fc.Args["method"].(models.ThumbnailFilter)) } directive1 := func(ctx context.Context) (interface{}, error) { if ec.directives.IsAdmin == nil { @@ -7226,10 +7226,10 @@ func (ec *executionContext) _Mutation_setThumbnailDownsampleMethod(ctx context.C if tmp == nil { return nil, nil } - if data, ok := tmp.(int); ok { + if data, ok := tmp.(models.ThumbnailFilter); ok { return data, nil } - return nil, fmt.Errorf(`unexpected type %T from directive, should be int`, tmp) + return nil, fmt.Errorf(`unexpected type %T from directive, should be github.com/photoview/photoview/api/graphql/models.ThumbnailFilter`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -7241,9 +7241,9 @@ func (ec *executionContext) _Mutation_setThumbnailDownsampleMethod(ctx context.C } return graphql.Null } - res := resTmp.(int) + res := resTmp.(models.ThumbnailFilter) fc.Result = res - return ec.marshalNInt2int(ctx, field.Selections, res) + return ec.marshalNThumbnailFilter2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐThumbnailFilter(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Mutation_setThumbnailDownsampleMethod(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -7253,7 +7253,7 @@ func (ec *executionContext) fieldContext_Mutation_setThumbnailDownsampleMethod(c IsMethod: true, IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Int does not have child fields") + return nil, errors.New("field of type ThumbnailFilter does not have child fields") }, } defer func() { @@ -10729,10 +10729,10 @@ func (ec *executionContext) _SiteInfo_thumbnailMethod(ctx context.Context, field if tmp == nil { return nil, nil } - if data, ok := tmp.(int); ok { + if data, ok := tmp.(models.ThumbnailFilter); ok { return data, nil } - return nil, fmt.Errorf(`unexpected type %T from directive, should be int`, tmp) + return nil, fmt.Errorf(`unexpected type %T from directive, should be github.com/photoview/photoview/api/graphql/models.ThumbnailFilter`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -10744,9 +10744,9 @@ func (ec *executionContext) _SiteInfo_thumbnailMethod(ctx context.Context, field } return graphql.Null } - res := resTmp.(int) + res := resTmp.(models.ThumbnailFilter) fc.Result = res - return ec.marshalNInt2int(ctx, field.Selections, res) + return ec.marshalNThumbnailFilter2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐThumbnailFilter(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_SiteInfo_thumbnailMethod(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -10756,7 +10756,7 @@ func (ec *executionContext) fieldContext_SiteInfo_thumbnailMethod(ctx context.Co IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Int does not have child fields") + return nil, errors.New("field of type ThumbnailFilter does not have child fields") }, } return fc, nil @@ -16802,6 +16802,16 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S return res } +func (ec *executionContext) unmarshalNThumbnailFilter2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐThumbnailFilter(ctx context.Context, v interface{}) (models.ThumbnailFilter, error) { + var res models.ThumbnailFilter + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNThumbnailFilter2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐThumbnailFilter(ctx context.Context, sel ast.SelectionSet, v models.ThumbnailFilter) graphql.Marshaler { + return v +} + func (ec *executionContext) unmarshalNTime2timeᚐTime(ctx context.Context, v interface{}) (time.Time, error) { res, err := graphql.UnmarshalTime(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go index abd85898..23d47444 100644 --- a/api/graphql/models/generated.go +++ b/api/graphql/models/generated.go @@ -251,3 +251,53 @@ func (e *OrderDirection) UnmarshalGQL(v interface{}) error { func (e OrderDirection) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } + +// Supported downsampling filters for thumbnail generation +type ThumbnailFilter string + +const ( + ThumbnailFilterNearestNeighbor ThumbnailFilter = "NearestNeighbor" + ThumbnailFilterBox ThumbnailFilter = "Box" + ThumbnailFilterLinear ThumbnailFilter = "Linear" + ThumbnailFilterMitchellNetravali ThumbnailFilter = "MitchellNetravali" + ThumbnailFilterCatmullRom ThumbnailFilter = "CatmullRom" + ThumbnailFilterLanczos ThumbnailFilter = "Lanczos" +) + +var AllThumbnailFilter = []ThumbnailFilter{ + ThumbnailFilterNearestNeighbor, + ThumbnailFilterBox, + ThumbnailFilterLinear, + ThumbnailFilterMitchellNetravali, + ThumbnailFilterCatmullRom, + ThumbnailFilterLanczos, +} + +func (e ThumbnailFilter) IsValid() bool { + switch e { + case ThumbnailFilterNearestNeighbor, ThumbnailFilterBox, ThumbnailFilterLinear, ThumbnailFilterMitchellNetravali, ThumbnailFilterCatmullRom, ThumbnailFilterLanczos: + return true + } + return false +} + +func (e ThumbnailFilter) String() string { + return string(e) +} + +func (e *ThumbnailFilter) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = ThumbnailFilter(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid ThumbnailFilter", str) + } + return nil +} + +func (e ThumbnailFilter) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/api/graphql/models/site_info.go b/api/graphql/models/site_info.go index 1ea1c501..57b966f7 100644 --- a/api/graphql/models/site_info.go +++ b/api/graphql/models/site_info.go @@ -10,7 +10,7 @@ type SiteInfo struct { InitialSetup bool `gorm:"not null"` PeriodicScanInterval int `gorm:"not null"` ConcurrentWorkers int `gorm:"not null"` - ThumbnailMethod int `gorm:"not null"` + ThumbnailMethod ThumbnailFilter `gorm:"not null"` } func (SiteInfo) TableName() string { @@ -27,7 +27,7 @@ func DefaultSiteInfo(db *gorm.DB) SiteInfo { InitialSetup: true, PeriodicScanInterval: 0, ConcurrentWorkers: defaultConcurrentWorkers, - ThumbnailMethod: 0, + ThumbnailMethod: ThumbnailFilterNearestNeighbor, } } diff --git a/api/graphql/models/site_info_test.go b/api/graphql/models/site_info_test.go index 13472866..fb299be6 100644 --- a/api/graphql/models/site_info_test.go +++ b/api/graphql/models/site_info_test.go @@ -22,7 +22,7 @@ func TestSiteInfo(t *testing.T) { site_info.InitialSetup = false site_info.PeriodicScanInterval = 360 site_info.ConcurrentWorkers = 10 - site_info.ThumbnailMethod = 5 + site_info.ThumbnailMethod = ThumbnailFilterLanczos if !assert.NoError(t, db.Session(&gorm.Session{AllowGlobalUpdate: true}).Save(&site_info).Error) { return @@ -37,7 +37,7 @@ func TestSiteInfo(t *testing.T) { InitialSetup: false, PeriodicScanInterval: 360, ConcurrentWorkers: 10, - ThumbnailMethod: 5, + ThumbnailMethod: ThumbnailFilterLanczos, }, *site_info) } diff --git a/api/graphql/resolvers/thumbnails.go b/api/graphql/resolvers/thumbnails.go index 8f5fb02a..63051440 100644 --- a/api/graphql/resolvers/thumbnails.go +++ b/api/graphql/resolvers/thumbnails.go @@ -2,29 +2,35 @@ package resolvers import ( "context" - + "github.com/photoview/photoview/api/graphql/models" - "github.com/pkg/errors" + // "github.com/pkg/errors" "gorm.io/gorm" ) -func (r *mutationResolver) SetThumbnailDownsampleMethod(ctx context.Context, method int) (int, error) { +func (r *mutationResolver) SetThumbnailDownsampleMethod(ctx context.Context, method models.ThumbnailFilter) (models.ThumbnailFilter, error) { db := r.DB(ctx) - if method > 5 { - return 0, errors.New("The requested filter is unsupported, defaulting to nearest neighbor") - } + // if method > 5 { + // return 0, errors.New("The requested filter is unsupported, defaulting to nearest neighbor") + // } if err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&models.SiteInfo{}).Update("thumbnail_method", method).Error; err != nil { - return 0, err + return models.ThumbnailFilterNearestNeighbor, err } var siteInfo models.SiteInfo if err := db.First(&siteInfo).Error; err != nil { - return 0, err + return models.ThumbnailFilterNearestNeighbor, err } - // scanner_queue.ChangeScannerConcurrentWorkers(siteInfo.ConcurrentWorkers) - return siteInfo.ThumbnailMethod, nil + + // var langTrans *models.LanguageTranslation = nil + // if language != nil { + // lng := models.LanguageTranslation(*language) + // langTrans = &lng + // } + + } diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index 91c7dfb2..24fd737a 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -164,7 +164,7 @@ type Mutation { setScannerConcurrentWorkers(workers: Int!): Int! @isAdmin "Set the filter to be used when generating thumbnails" - setThumbnailDownsampleMethod(method: Int!): Int! @isAdmin + setThumbnailDownsampleMethod(method: ThumbnailFilter!): ThumbnailFilter! @isAdmin "Change user preferences for the logged in user" changeUserPreferences(language: String): UserPreferences! @isAuthorized @@ -250,6 +250,16 @@ type ShareToken { media: Media } +"Supported downsampling filters for thumbnail generation" +enum ThumbnailFilter { + NearestNeighbor, + Box, + Linear, + MitchellNetravali, + CatmullRom, + Lanczos, +} + "General information about the site" type SiteInfo { "Whether or not the initial setup wizard should be shown" @@ -261,7 +271,7 @@ type SiteInfo { "How many max concurrent scanner jobs that should run at once" concurrentWorkers: Int! @isAdmin "The filter to use when generating thumbnails" - thumbnailMethod: Int! @isAdmin + thumbnailMethod: ThumbnailFilter! @isAdmin } type User { diff --git a/api/scanner/media_encoding/encode_photo.go b/api/scanner/media_encoding/encode_photo.go index b7221d9b..adf4d8ec 100644 --- a/api/scanner/media_encoding/encode_photo.go +++ b/api/scanner/media_encoding/encode_photo.go @@ -21,13 +21,13 @@ import ( "gorm.io/gorm" ) -var thumbFilter = map[int]imaging.ResampleFilter{ - 0: imaging.NearestNeighbor, - 1: imaging.Box, - 2: imaging.Linear, - 3: imaging.MitchellNetravali, - 4: imaging.CatmullRom, - 5: imaging.Lanczos, +var thumbFilter = map[models.ThumbnailFilter]imaging.ResampleFilter{ + models.ThumbnailFilterNearestNeighbor: imaging.NearestNeighbor, + models.ThumbnailFilterBox: imaging.Box, + models.ThumbnailFilterLinear: imaging.Linear, + models.ThumbnailFilterMitchellNetravali: imaging.MitchellNetravali, + models.ThumbnailFilterCatmullRom: imaging.CatmullRom, + models.ThumbnailFilterLanczos: imaging.Lanczos, } func EncodeThumbnail(db *gorm.DB, inputPath string, outputPath string) (*media_utils.PhotoDimensions, error) { diff --git a/ui/src/Pages/SettingsPage/thumbnailPreferences.test.tsx b/ui/src/Pages/SettingsPage/ThumbnailPreferences.test.tsx similarity index 85% rename from ui/src/Pages/SettingsPage/thumbnailPreferences.test.tsx rename to ui/src/Pages/SettingsPage/ThumbnailPreferences.test.tsx index d725b2c0..879ba166 100644 --- a/ui/src/Pages/SettingsPage/thumbnailPreferences.test.tsx +++ b/ui/src/Pages/SettingsPage/ThumbnailPreferences.test.tsx @@ -3,6 +3,8 @@ import { MockedProvider } from '@apollo/client/testing' import { render, screen } from '@testing-library/react' +import { ThumbnailFilter } from '../../__generated__/globalTypes' + import ThumbnailPreferences, { THUMBNAIL_METHOD_QUERY, SET_THUMBNAIL_METHOD_MUTATION, @@ -16,7 +18,7 @@ test('load ThumbnailPreferences', () => { }, result: { data: { - siteInfo: { method: 0 }, + siteInfo: { method: ThumbnailFilter.NearestNeighbor }, }, }, }, @@ -24,7 +26,7 @@ test('load ThumbnailPreferences', () => { request: { query: SET_THUMBNAIL_METHOD_MUTATION, variables: { - method: '5', + method: ThumbnailFilter.Lanczos, }, }, result: { diff --git a/ui/src/Pages/SettingsPage/ThumbnailPreferences.tsx b/ui/src/Pages/SettingsPage/ThumbnailPreferences.tsx index 8f468324..520a794c 100644 --- a/ui/src/Pages/SettingsPage/ThumbnailPreferences.tsx +++ b/ui/src/Pages/SettingsPage/ThumbnailPreferences.tsx @@ -7,6 +7,7 @@ import { InputLabelTitle, } from './SettingsPage' import { useTranslation } from 'react-i18next' +import { ThumbnailFilter } from '../../__generated__/globalTypes' import { thumbnailMethodQuery } from './__generated__/thumbnailMethodQuery' import { setThumbnailMethodMutation, @@ -24,7 +25,7 @@ export const THUMBNAIL_METHOD_QUERY = gql` ` export const SET_THUMBNAIL_METHOD_MUTATION = gql` - mutation setThumbnailMethodMutation($method: Int!) { + mutation setThumbnailMethodMutation($method: ThumbnailFilter!) { setThumbnailDownsampleMethod(method: $method) } ` @@ -67,33 +68,33 @@ const ThumbnailPreferences = () => { 'settings.thumbnails.method.filter.nearest_neighbor', 'Nearest Neighbor (default)' ), - value: 0, + value: ThumbnailFilter.NearestNeighbor, }, { label: t('settings.thumbnails.method.filter.box', 'Box'), - value: 1, + value: ThumbnailFilter.Box, }, { label: t('settings.thumbnails.method.filter.linear', 'Linear'), - value: 2, + value: ThumbnailFilter.Linear, }, { label: t( 'settings.thumbnails.method.filter.mitchell_netravali', 'Mitchell-Netravali' ), - value: 3, + value: ThumbnailFilter.MitchellNetravali, }, { label: t('settings.thumbnails.method.filter.catmull_rom', 'Catmull-Rom'), - value: 4, + value: ThumbnailFilter.CatmullRom, }, { label: t( 'settings.thumbnails.method.filter.Lanczos', 'Lanczos (highest quality)' ), - value: 5, + value: ThumbnailFilter.Lanczos, }, ] diff --git a/ui/src/Pages/SettingsPage/__generated__/setThumbnailMethodMutation.ts b/ui/src/Pages/SettingsPage/__generated__/setThumbnailMethodMutation.ts index 15611ca6..bd7f061b 100644 --- a/ui/src/Pages/SettingsPage/__generated__/setThumbnailMethodMutation.ts +++ b/ui/src/Pages/SettingsPage/__generated__/setThumbnailMethodMutation.ts @@ -3,6 +3,8 @@ // @generated // This file was automatically generated and should not be edited. +import { ThumbnailFilter } from './../../../__generated__/globalTypes' + // ==================================================== // GraphQL mutation operation: setThumbnailMethodMutation // ==================================================== @@ -11,9 +13,9 @@ export interface setThumbnailMethodMutation { /** * Set the filter to be used when generating thumbnails */ - setThumbnailDownsampleMethod: number + setThumbnailDownsampleMethod: ThumbnailFilter } export interface setThumbnailMethodMutationVariables { - method: number + method: ThumbnailFilter } diff --git a/ui/src/Pages/SettingsPage/__generated__/thumbnailMethodQuery.ts b/ui/src/Pages/SettingsPage/__generated__/thumbnailMethodQuery.ts index ef0690a2..7c9cb00a 100644 --- a/ui/src/Pages/SettingsPage/__generated__/thumbnailMethodQuery.ts +++ b/ui/src/Pages/SettingsPage/__generated__/thumbnailMethodQuery.ts @@ -3,6 +3,8 @@ // @generated // This file was automatically generated and should not be edited. +import { ThumbnailFilter } from './../../../__generated__/globalTypes' + // ==================================================== // GraphQL query operation: thumbnailMethodQuery // ==================================================== @@ -12,7 +14,7 @@ export interface thumbnailMethodQuery_siteInfo { /** * The filter to use when generating thumbnails */ - thumbnailMethod: number + thumbnailMethod: ThumbnailFilter } export interface thumbnailMethodQuery { diff --git a/ui/src/__generated__/globalTypes.ts b/ui/src/__generated__/globalTypes.ts index 8fda05f5..89eae65f 100644 --- a/ui/src/__generated__/globalTypes.ts +++ b/ui/src/__generated__/globalTypes.ts @@ -48,6 +48,18 @@ export enum OrderDirection { DESC = 'DESC', } +/** + * Supported downsampling filters for thumbnail generation + */ +export enum ThumbnailFilter { + Box = 'Box', + CatmullRom = 'CatmullRom', + Lanczos = 'Lanczos', + Linear = 'Linear', + MitchellNetravali = 'MitchellNetravali', + NearestNeighbor = 'NearestNeighbor', +} + //============================================================== // END Enums and Input Objects //==============================================================