From 413311711fba25a67e6aa2f9eeeefef97e641567 Mon Sep 17 00:00:00 2001 From: bob123491234 <54259225+bob123491234@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:45:07 -0600 Subject: [PATCH] Add Details, Studio Code, and Photographer to Images (#4217) * Add Details, Code, and Photographer to Images * Add date and details to image card --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com> --- graphql/documents/data/image-slim.graphql | 3 + graphql/documents/data/image.graphql | 3 + graphql/schema/types/filters.graphql | 5 + graphql/schema/types/image.graphql | 9 ++ internal/api/resolver_mutation_image.go | 6 + pkg/image/export.go | 11 +- pkg/image/import.go | 9 ++ pkg/models/image.go | 13 ++- pkg/models/jsonschema/image.go | 23 ++-- pkg/models/model_image.go | 24 ++-- pkg/sqlite/database.go | 2 +- pkg/sqlite/image.go | 43 +++++--- pkg/sqlite/image_test.go | 103 +++++++++++------- .../54_image_code_details_photographer.up.sql | 3 + ui/v2.5/src/components/Images/ImageCard.tsx | 11 ++ .../Images/ImageDetails/ImageDetailPanel.tsx | 23 ++++ .../Images/ImageDetails/ImageEditPanel.tsx | 27 +++++ ui/v2.5/src/components/Images/styles.scss | 4 + ui/v2.5/src/models/list-filter/images.ts | 3 + 19 files changed, 244 insertions(+), 81 deletions(-) create mode 100644 pkg/sqlite/migrations/54_image_code_details_photographer.up.sql diff --git a/graphql/documents/data/image-slim.graphql b/graphql/documents/data/image-slim.graphql index 1c7784c9ede..e45f1993853 100644 --- a/graphql/documents/data/image-slim.graphql +++ b/graphql/documents/data/image-slim.graphql @@ -1,8 +1,11 @@ fragment SlimImageData on Image { id title + code date urls + details + photographer rating100 organized o_counter diff --git a/graphql/documents/data/image.graphql b/graphql/documents/data/image.graphql index 64c801401e7..648d03d289d 100644 --- a/graphql/documents/data/image.graphql +++ b/graphql/documents/data/image.graphql @@ -1,9 +1,12 @@ fragment ImageData on Image { id title + code rating100 date urls + details + photographer organized o_counter created_at diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index f0c8e4988eb..fa646d345d2 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -443,6 +443,7 @@ input ImageFilterType { NOT: ImageFilterType title: StringCriterionInput + details: StringCriterionInput " Filter by image id" id: IntCriterionInput @@ -486,6 +487,10 @@ input ImageFilterType { created_at: TimestampCriterionInput "Filter by last update time" updated_at: TimestampCriterionInput + "Filter by studio code" + code: StringCriterionInput + "Filter by photographer" + photographer: StringCriterionInput } enum CriterionModifier { diff --git a/graphql/schema/types/image.graphql b/graphql/schema/types/image.graphql index 4c68ef7fecf..fb95556f57d 100644 --- a/graphql/schema/types/image.graphql +++ b/graphql/schema/types/image.graphql @@ -1,11 +1,14 @@ type Image { id: ID! title: String + code: String # rating expressed as 1-100 rating100: Int url: String @deprecated(reason: "Use urls") urls: [String!]! date: String + details: String + photographer: String o_counter: Int organized: Boolean! created_at: Time! @@ -37,12 +40,15 @@ input ImageUpdateInput { clientMutationId: String id: ID! title: String + code: String # rating expressed as 1-100 rating100: Int organized: Boolean url: String @deprecated(reason: "Use urls") urls: [String!] date: String + details: String + photographer: String studio_id: ID performer_ids: [ID!] @@ -56,12 +62,15 @@ input BulkImageUpdateInput { clientMutationId: String ids: [ID!] title: String + code: String # rating expressed as 1-100 rating100: Int organized: Boolean url: String @deprecated(reason: "Use urls") urls: BulkUpdateStrings date: String + details: String + photographer: String studio_id: ID performer_ids: BulkUpdateIds diff --git a/internal/api/resolver_mutation_image.go b/internal/api/resolver_mutation_image.go index e465da33f70..0cd5d34874b 100644 --- a/internal/api/resolver_mutation_image.go +++ b/internal/api/resolver_mutation_image.go @@ -107,6 +107,9 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp updatedImage := models.NewImagePartial() updatedImage.Title = translator.optionalString(input.Title, "title") + updatedImage.Code = translator.optionalString(input.Code, "code") + updatedImage.Details = translator.optionalString(input.Details, "details") + updatedImage.Photographer = translator.optionalString(input.Photographer, "photographer") updatedImage.Rating = translator.optionalInt(input.Rating100, "rating100") updatedImage.Organized = translator.optionalBool(input.Organized, "organized") @@ -203,6 +206,9 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input BulkImageU updatedImage := models.NewImagePartial() updatedImage.Title = translator.optionalString(input.Title, "title") + updatedImage.Code = translator.optionalString(input.Code, "code") + updatedImage.Details = translator.optionalString(input.Details, "details") + updatedImage.Photographer = translator.optionalString(input.Photographer, "photographer") updatedImage.Rating = translator.optionalInt(input.Rating100, "rating100") updatedImage.Organized = translator.optionalBool(input.Organized, "organized") diff --git a/pkg/image/export.go b/pkg/image/export.go index 41eac446fe2..fdba6165cb0 100644 --- a/pkg/image/export.go +++ b/pkg/image/export.go @@ -13,10 +13,13 @@ import ( // of cover image. func ToBasicJSON(image *models.Image) *jsonschema.Image { newImageJSON := jsonschema.Image{ - Title: image.Title, - URLs: image.URLs.List(), - CreatedAt: json.JSONTime{Time: image.CreatedAt}, - UpdatedAt: json.JSONTime{Time: image.UpdatedAt}, + Title: image.Title, + Code: image.Code, + URLs: image.URLs.List(), + Details: image.Details, + Photographer: image.Photographer, + CreatedAt: json.JSONTime{Time: image.CreatedAt}, + UpdatedAt: json.JSONTime{Time: image.UpdatedAt}, } if image.Rating != nil { diff --git a/pkg/image/import.go b/pkg/image/import.go index 8a68c49c7f0..fa8fe21610e 100644 --- a/pkg/image/import.go +++ b/pkg/image/import.go @@ -76,6 +76,15 @@ func (i *Importer) imageJSONToImage(imageJSON jsonschema.Image) models.Image { if imageJSON.Title != "" { newImage.Title = imageJSON.Title } + if imageJSON.Code != "" { + newImage.Code = imageJSON.Code + } + if imageJSON.Details != "" { + newImage.Details = imageJSON.Details + } + if imageJSON.Photographer != "" { + newImage.Photographer = imageJSON.Photographer + } if imageJSON.Rating != 0 { newImage.Rating = &imageJSON.Rating } diff --git a/pkg/models/image.go b/pkg/models/image.go index 25d227f329d..8a8b5ba5047 100644 --- a/pkg/models/image.go +++ b/pkg/models/image.go @@ -3,11 +3,14 @@ package models import "context" type ImageFilterType struct { - And *ImageFilterType `json:"AND"` - Or *ImageFilterType `json:"OR"` - Not *ImageFilterType `json:"NOT"` - ID *IntCriterionInput `json:"id"` - Title *StringCriterionInput `json:"title"` + And *ImageFilterType `json:"AND"` + Or *ImageFilterType `json:"OR"` + Not *ImageFilterType `json:"NOT"` + ID *IntCriterionInput `json:"id"` + Title *StringCriterionInput `json:"title"` + Code *StringCriterionInput `json:"code"` + Details *StringCriterionInput `json:"details"` + Photographer *StringCriterionInput `json:"photographer"` // Filter by file checksum Checksum *StringCriterionInput `json:"checksum"` // Filter by path diff --git a/pkg/models/jsonschema/image.go b/pkg/models/jsonschema/image.go index 7ff0b21621f..1bdac87705d 100644 --- a/pkg/models/jsonschema/image.go +++ b/pkg/models/jsonschema/image.go @@ -11,22 +11,25 @@ import ( type Image struct { Title string `json:"title,omitempty"` + Code string `json:"code,omitempty"` Studio string `json:"studio,omitempty"` Rating int `json:"rating,omitempty"` // deprecated - for import only URL string `json:"url,omitempty"` - URLs []string `json:"urls,omitempty"` - Date string `json:"date,omitempty"` - Organized bool `json:"organized,omitempty"` - OCounter int `json:"o_counter,omitempty"` - Galleries []GalleryRef `json:"galleries,omitempty"` - Performers []string `json:"performers,omitempty"` - Tags []string `json:"tags,omitempty"` - Files []string `json:"files,omitempty"` - CreatedAt json.JSONTime `json:"created_at,omitempty"` - UpdatedAt json.JSONTime `json:"updated_at,omitempty"` + URLs []string `json:"urls,omitempty"` + Date string `json:"date,omitempty"` + Details string `json:"details,omitempty"` + Photographer string `json:"photographer,omitempty"` + Organized bool `json:"organized,omitempty"` + OCounter int `json:"o_counter,omitempty"` + Galleries []GalleryRef `json:"galleries,omitempty"` + Performers []string `json:"performers,omitempty"` + Tags []string `json:"tags,omitempty"` + Files []string `json:"files,omitempty"` + CreatedAt json.JSONTime `json:"created_at,omitempty"` + UpdatedAt json.JSONTime `json:"updated_at,omitempty"` } func (s Image) Filename(basename string, hash string) string { diff --git a/pkg/models/model_image.go b/pkg/models/model_image.go index 8f3211dc7ac..1d0993536bb 100644 --- a/pkg/models/model_image.go +++ b/pkg/models/model_image.go @@ -11,7 +11,10 @@ import ( type Image struct { ID int `json:"id"` - Title string `json:"title"` + Title string `json:"title"` + Code string `json:"code"` + Details string `json:"details"` + Photographer string `json:"photographer"` // Rating expressed in 1-100 scale Rating *int `json:"rating"` Organized bool `json:"organized"` @@ -46,15 +49,18 @@ func NewImage() Image { type ImagePartial struct { Title OptionalString + Code OptionalString // Rating expressed in 1-100 scale - Rating OptionalInt - URLs *UpdateStrings - Date OptionalDate - Organized OptionalBool - OCounter OptionalInt - StudioID OptionalInt - CreatedAt OptionalTime - UpdatedAt OptionalTime + Rating OptionalInt + URLs *UpdateStrings + Date OptionalDate + Details OptionalString + Photographer OptionalString + Organized OptionalBool + OCounter OptionalInt + StudioID OptionalInt + CreatedAt OptionalTime + UpdatedAt OptionalTime GalleryIDs *UpdateIDs TagIDs *UpdateIDs diff --git a/pkg/sqlite/database.go b/pkg/sqlite/database.go index 727c1238946..bf8f15a6843 100644 --- a/pkg/sqlite/database.go +++ b/pkg/sqlite/database.go @@ -33,7 +33,7 @@ const ( dbConnTimeout = 30 ) -var appSchemaVersion uint = 53 +var appSchemaVersion uint = 54 //go:embed migrations/*.sql var migrationsBox embed.FS diff --git a/pkg/sqlite/image.go b/pkg/sqlite/image.go index 45de02e6e51..64a7f08e6a5 100644 --- a/pkg/sqlite/image.go +++ b/pkg/sqlite/image.go @@ -31,21 +31,27 @@ const ( type imageRow struct { ID int `db:"id" goqu:"skipinsert"` Title zero.String `db:"title"` + Code zero.String `db:"code"` // expressed as 1-100 - Rating null.Int `db:"rating"` - Date NullDate `db:"date"` - Organized bool `db:"organized"` - OCounter int `db:"o_counter"` - StudioID null.Int `db:"studio_id,omitempty"` - CreatedAt Timestamp `db:"created_at"` - UpdatedAt Timestamp `db:"updated_at"` + Rating null.Int `db:"rating"` + Date NullDate `db:"date"` + Details zero.String `db:"details"` + Photographer zero.String `db:"photographer"` + Organized bool `db:"organized"` + OCounter int `db:"o_counter"` + StudioID null.Int `db:"studio_id,omitempty"` + CreatedAt Timestamp `db:"created_at"` + UpdatedAt Timestamp `db:"updated_at"` } func (r *imageRow) fromImage(i models.Image) { r.ID = i.ID r.Title = zero.StringFrom(i.Title) + r.Code = zero.StringFrom(i.Code) r.Rating = intFromPtr(i.Rating) r.Date = NullDateFromDatePtr(i.Date) + r.Details = zero.StringFrom(i.Details) + r.Photographer = zero.StringFrom(i.Photographer) r.Organized = i.Organized r.OCounter = i.OCounter r.StudioID = intFromPtr(i.StudioID) @@ -63,13 +69,16 @@ type imageQueryRow struct { func (r *imageQueryRow) resolve() *models.Image { ret := &models.Image{ - ID: r.ID, - Title: r.Title.String, - Rating: nullIntPtr(r.Rating), - Date: r.Date.DatePtr(), - Organized: r.Organized, - OCounter: r.OCounter, - StudioID: nullIntPtr(r.StudioID), + ID: r.ID, + Title: r.Title.String, + Code: r.Code.String, + Rating: nullIntPtr(r.Rating), + Date: r.Date.DatePtr(), + Details: r.Details.String, + Photographer: r.Photographer.String, + Organized: r.Organized, + OCounter: r.OCounter, + StudioID: nullIntPtr(r.StudioID), PrimaryFileID: nullIntFileIDPtr(r.PrimaryFileID), Checksum: r.PrimaryFileChecksum.String, @@ -91,8 +100,11 @@ type imageRowRecord struct { func (r *imageRowRecord) fromPartial(i models.ImagePartial) { r.setNullString("title", i.Title) + r.setNullString("code", i.Code) r.setNullInt("rating", i.Rating) r.setNullDate("date", i.Date) + r.setNullString("details", i.Details) + r.setNullString("photographer", i.Photographer) r.setBool("organized", i.Organized) r.setInt("o_counter", i.OCounter) r.setNullInt("studio_id", i.StudioID) @@ -672,6 +684,9 @@ func (qb *ImageStore) makeFilter(ctx context.Context, imageFilter *models.ImageF stringCriterionHandler(imageFilter.Checksum, "fingerprints_md5.fingerprint")(ctx, f) })) query.handleCriterion(ctx, stringCriterionHandler(imageFilter.Title, "images.title")) + query.handleCriterion(ctx, stringCriterionHandler(imageFilter.Code, "images.code")) + query.handleCriterion(ctx, stringCriterionHandler(imageFilter.Details, "images.details")) + query.handleCriterion(ctx, stringCriterionHandler(imageFilter.Photographer, "images.photographer")) query.handleCriterion(ctx, pathCriterionHandler(imageFilter.Path, "folders.path", "files.basename", qb.addFoldersTable)) query.handleCriterion(ctx, imageFileCountCriterionHandler(qb, imageFilter.FileCount)) diff --git a/pkg/sqlite/image_test.go b/pkg/sqlite/image_test.go index 5f06dd401cd..7a5b9ce1e1a 100644 --- a/pkg/sqlite/image_test.go +++ b/pkg/sqlite/image_test.go @@ -57,13 +57,16 @@ func loadImageRelationships(ctx context.Context, expected models.Image, actual * func Test_imageQueryBuilder_Create(t *testing.T) { var ( - title = "title" - rating = 60 - ocounter = 5 - url = "url" - date, _ = models.ParseDate("2003-02-01") - createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) - updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + title = "title" + code = "code" + rating = 60 + details = "details" + photographer = "photographer" + ocounter = 5 + url = "url" + date, _ = models.ParseDate("2003-02-01") + createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) imageFile = makeFileWithID(fileIdxStartImageFiles) ) @@ -77,8 +80,11 @@ func Test_imageQueryBuilder_Create(t *testing.T) { "full", models.Image{ Title: title, + Code: code, Rating: &rating, Date: &date, + Details: details, + Photographer: photographer, URLs: models.NewRelatedStrings([]string{url}), Organized: true, OCounter: ocounter, @@ -94,13 +100,16 @@ func Test_imageQueryBuilder_Create(t *testing.T) { { "with file", models.Image{ - Title: title, - Rating: &rating, - Date: &date, - URLs: models.NewRelatedStrings([]string{url}), - Organized: true, - OCounter: ocounter, - StudioID: &studioIDs[studioIdxWithImage], + Title: title, + Code: code, + Rating: &rating, + Date: &date, + Details: details, + Photographer: photographer, + URLs: models.NewRelatedStrings([]string{url}), + Organized: true, + OCounter: ocounter, + StudioID: &studioIDs[studioIdxWithImage], Files: models.NewRelatedFiles([]models.File{ imageFile.(*models.ImageFile), }), @@ -214,13 +223,16 @@ func makeImageFileWithID(i int) *models.ImageFile { func Test_imageQueryBuilder_Update(t *testing.T) { var ( - title = "title" - rating = 60 - url = "url" - date, _ = models.ParseDate("2003-02-01") - ocounter = 5 - createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) - updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + title = "title" + code = "code" + rating = 60 + url = "url" + details = "details" + photographer = "photographer" + date, _ = models.ParseDate("2003-02-01") + ocounter = 5 + createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) ) tests := []struct { @@ -233,9 +245,12 @@ func Test_imageQueryBuilder_Update(t *testing.T) { &models.Image{ ID: imageIDs[imageIdxWithGallery], Title: title, + Code: code, Rating: &rating, URLs: models.NewRelatedStrings([]string{url}), Date: &date, + Details: details, + Photographer: photographer, Organized: true, OCounter: ocounter, StudioID: &studioIDs[studioIdxWithImage], @@ -382,6 +397,9 @@ func clearImagePartial() models.ImagePartial { // leave mandatory fields return models.ImagePartial{ Title: models.OptionalString{Set: true, Null: true}, + Code: models.OptionalString{Set: true, Null: true}, + Details: models.OptionalString{Set: true, Null: true}, + Photographer: models.OptionalString{Set: true, Null: true}, Rating: models.OptionalInt{Set: true, Null: true}, URLs: &models.UpdateStrings{Mode: models.RelationshipUpdateModeSet}, Date: models.OptionalDate{Set: true, Null: true}, @@ -394,13 +412,16 @@ func clearImagePartial() models.ImagePartial { func Test_imageQueryBuilder_UpdatePartial(t *testing.T) { var ( - title = "title" - rating = 60 - url = "url" - date, _ = models.ParseDate("2003-02-01") - ocounter = 5 - createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) - updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + title = "title" + code = "code" + details = "details" + photographer = "photographer" + rating = 60 + url = "url" + date, _ = models.ParseDate("2003-02-01") + ocounter = 5 + createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) ) tests := []struct { @@ -414,8 +435,11 @@ func Test_imageQueryBuilder_UpdatePartial(t *testing.T) { "full", imageIDs[imageIdx1WithGallery], models.ImagePartial{ - Title: models.NewOptionalString(title), - Rating: models.NewOptionalInt(rating), + Title: models.NewOptionalString(title), + Code: models.NewOptionalString(code), + Details: models.NewOptionalString(details), + Photographer: models.NewOptionalString(photographer), + Rating: models.NewOptionalInt(rating), URLs: &models.UpdateStrings{ Values: []string{url}, Mode: models.RelationshipUpdateModeSet, @@ -440,14 +464,17 @@ func Test_imageQueryBuilder_UpdatePartial(t *testing.T) { }, }, models.Image{ - ID: imageIDs[imageIdx1WithGallery], - Title: title, - Rating: &rating, - URLs: models.NewRelatedStrings([]string{url}), - Date: &date, - Organized: true, - OCounter: ocounter, - StudioID: &studioIDs[studioIdxWithImage], + ID: imageIDs[imageIdx1WithGallery], + Title: title, + Code: code, + Details: details, + Photographer: photographer, + Rating: &rating, + URLs: models.NewRelatedStrings([]string{url}), + Date: &date, + Organized: true, + OCounter: ocounter, + StudioID: &studioIDs[studioIdxWithImage], Files: models.NewRelatedFiles([]models.File{ makeImageFile(imageIdx1WithGallery), }), diff --git a/pkg/sqlite/migrations/54_image_code_details_photographer.up.sql b/pkg/sqlite/migrations/54_image_code_details_photographer.up.sql new file mode 100644 index 00000000000..12e186f2ad7 --- /dev/null +++ b/pkg/sqlite/migrations/54_image_code_details_photographer.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE `images` ADD COLUMN `code` text; +ALTER TABLE `images` ADD COLUMN `photographer` text; +ALTER TABLE `images` ADD COLUMN `details` text; \ No newline at end of file diff --git a/ui/v2.5/src/components/Images/ImageCard.tsx b/ui/v2.5/src/components/Images/ImageCard.tsx index 5f8c57a53bf..47f7eac7a79 100644 --- a/ui/v2.5/src/components/Images/ImageCard.tsx +++ b/ui/v2.5/src/components/Images/ImageCard.tsx @@ -16,6 +16,7 @@ import { faTag, } from "@fortawesome/free-solid-svg-icons"; import { objectTitle } from "src/core/files"; +import { TruncatedText } from "../Shared/TruncatedText"; interface IImageCardProps { image: GQL.SlimImageDataFragment; @@ -175,6 +176,16 @@ export const ImageCard: React.FC = ( } + details={ +
+ {props.image.date} + +
+ } popovers={maybeRenderPopoverButtonGroup()} selected={props.selected} selecting={props.selecting} diff --git a/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx b/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx index 417d425cc7b..d8b84c79cdb 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx @@ -21,6 +21,18 @@ export const ImageDetailPanel: React.FC = (props) => { [props.image] ); + function renderDetails() { + if (!props.image.details) return; + return ( + <> +
+ :{" "} +
+

{props.image.details}

+ + ); + } + function renderTags() { if (props.image.tags.length === 0) return; const tags = props.image.tags.map((tag) => ( @@ -135,6 +147,16 @@ export const ImageDetailPanel: React.FC = (props) => { {TextUtils.formatDateTime(intl, props.image.updated_at)}{" "} } + {props.image.code && ( +
+ : {props.image.code}{" "} +
+ )} + {props.image.photographer && ( +
+ : {props.image.photographer}{" "} +
+ )} {props.image.studio && (
@@ -150,6 +172,7 @@ export const ImageDetailPanel: React.FC = (props) => {
+ {renderDetails()} {renderTags()} {renderPerformers()}
diff --git a/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx b/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx index 5344d105d95..a362f8ca1e6 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx @@ -48,8 +48,11 @@ export const ImageEditPanel: React.FC = ({ const schema = yup.object({ title: yup.string().ensure(), + code: yup.string().ensure(), urls: yupUniqueStringList("urls"), date: yupDateString(intl), + details: yup.string().ensure(), + photographer: yup.string().ensure(), rating100: yup.number().integer().nullable().defined(), studio_id: yup.string().required().nullable(), performer_ids: yup.array(yup.string().required()).defined(), @@ -58,8 +61,11 @@ export const ImageEditPanel: React.FC = ({ const initialValues = { title: image.title ?? "", + code: image.code ?? "", urls: image?.urls ?? [], date: image?.date ?? "", + details: image.details ?? "", + photographer: image.photographer ?? "", rating100: image.rating100 ?? null, studio_id: image.studio?.id ?? null, performer_ids: (image.performers ?? []).map((p) => p.id), @@ -204,6 +210,22 @@ export const ImageEditPanel: React.FC = ({ return renderField("tag_ids", title, control, fullWidthProps); } + function renderDetailsField() { + const props = { + labelProps: { + column: true, + sm: 3, + lg: 12, + }, + fieldProps: { + sm: 9, + lg: 12, + }, + }; + + return renderInputField("details", "textarea", "details", props); + } + return (
= ({ {renderInputField("title")} + {renderInputField("code", "text", "scene_code")} {renderURLListField("urls", "validation.urls_must_be_unique")} {renderDateField("date")} + {renderInputField("photographer")} {renderRatingField("rating100", "rating")} {renderStudioField()} {renderPerformersField()} {renderTagsField()} + + {renderDetailsField()} +
diff --git a/ui/v2.5/src/components/Images/styles.scss b/ui/v2.5/src/components/Images/styles.scss index 891943b151f..ed80c0ffa81 100644 --- a/ui/v2.5/src/components/Images/styles.scss +++ b/ui/v2.5/src/components/Images/styles.scss @@ -131,3 +131,7 @@ $imageTabWidth: 450px; margin-bottom: 0; } } + +.col-form-label { + padding-right: 2px; +} diff --git a/ui/v2.5/src/models/list-filter/images.ts b/ui/v2.5/src/models/list-filter/images.ts index fdea73952c7..d92f466d36b 100644 --- a/ui/v2.5/src/models/list-filter/images.ts +++ b/ui/v2.5/src/models/list-filter/images.ts @@ -33,6 +33,9 @@ const sortByOptions = [ const displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall]; const criterionOptions = [ createStringCriterionOption("title"), + createStringCriterionOption("code", "scene_code"), + createStringCriterionOption("details"), + createStringCriterionOption("photographer"), createMandatoryStringCriterionOption("checksum", "media_info.checksum"), PathCriterionOption, OrganizedCriterionOption,