diff --git a/pkg/models/model_scene_marker.go b/pkg/models/model_scene_marker.go index 434c0c2d9..1e9ac6115 100644 --- a/pkg/models/model_scene_marker.go +++ b/pkg/models/model_scene_marker.go @@ -14,8 +14,8 @@ type SceneMarker struct { UpdatedAt time.Time `json:"updated_at"` } -// SceneMarkerPartial represents part of a SceneMarker object. It is used to update -// the database entry. +// SceneMarkerPartial represents part of a SceneMarker object. +// It is used to update the database entry. type SceneMarkerPartial struct { Title OptionalString Seconds OptionalFloat64 diff --git a/pkg/sqlite/gallery_chapter.go b/pkg/sqlite/gallery_chapter.go index 82b997358..c8999cd43 100644 --- a/pkg/sqlite/gallery_chapter.go +++ b/pkg/sqlite/gallery_chapter.go @@ -20,7 +20,7 @@ const ( type galleryChapterRow struct { ID int `db:"id" goqu:"skipinsert"` - Title string `db:"title"` + Title string `db:"title"` // TODO: make db schema (and gql schema) nullable ImageIndex int `db:"image_index"` GalleryID int `db:"gallery_id"` CreatedAt Timestamp `db:"created_at"` @@ -54,7 +54,12 @@ type galleryChapterRowRecord struct { } func (r *galleryChapterRowRecord) fromPartial(o models.GalleryChapterPartial) { - r.setString("title", o.Title) + // TODO: replace with setNullString after schema is made nullable + // r.setNullString("title", o.Title) + // saves a null input as the empty string + if o.Title.Set { + r.set("title", o.Title.Value) + } r.setInt("image_index", o.ImageIndex) r.setInt("gallery_id", o.GalleryID) r.setTimestamp("created_at", o.CreatedAt) diff --git a/pkg/sqlite/performer.go b/pkg/sqlite/performer.go index cf6705e0c..e4bd7bb9a 100644 --- a/pkg/sqlite/performer.go +++ b/pkg/sqlite/performer.go @@ -30,7 +30,7 @@ const ( type performerRow struct { ID int `db:"id" goqu:"skipinsert"` - Name string `db:"name"` + Name null.String `db:"name"` // TODO: make schema non-nullable Disambigation zero.String `db:"disambiguation"` Gender zero.String `db:"gender"` URL zero.String `db:"url"` @@ -65,7 +65,7 @@ type performerRow struct { func (r *performerRow) fromPerformer(o models.Performer) { r.ID = o.ID - r.Name = o.Name + r.Name = null.StringFrom(o.Name) r.Disambigation = zero.StringFrom(o.Disambiguation) if o.Gender != nil && o.Gender.IsValid() { r.Gender = zero.StringFrom(o.Gender.String()) @@ -101,7 +101,7 @@ func (r *performerRow) fromPerformer(o models.Performer) { func (r *performerRow) resolve() *models.Performer { ret := &models.Performer{ ID: r.ID, - Name: r.Name, + Name: r.Name.String, Disambiguation: r.Disambigation.String, URL: r.URL.String, Twitter: r.Twitter.String, diff --git a/pkg/sqlite/scene_marker.go b/pkg/sqlite/scene_marker.go index 317a90995..e36c17c4f 100644 --- a/pkg/sqlite/scene_marker.go +++ b/pkg/sqlite/scene_marker.go @@ -25,7 +25,7 @@ GROUP BY scene_markers.id type sceneMarkerRow struct { ID int `db:"id" goqu:"skipinsert"` - Title string `db:"title"` + Title string `db:"title"` // TODO: make db schema (and gql schema) nullable Seconds float64 `db:"seconds"` PrimaryTagID int `db:"primary_tag_id"` SceneID int `db:"scene_id"` @@ -62,7 +62,12 @@ type sceneMarkerRowRecord struct { } func (r *sceneMarkerRowRecord) fromPartial(o models.SceneMarkerPartial) { - r.setNullString("title", o.Title) + // TODO: replace with setNullString after schema is made nullable + // r.setNullString("title", o.Title) + // saves a null input as the empty string + if o.Title.Set { + r.set("title", o.Title.Value) + } r.setFloat64("seconds", o.Seconds) r.setInt("primary_tag_id", o.PrimaryTagID) r.setInt("scene_id", o.SceneID) diff --git a/pkg/sqlite/tag.go b/pkg/sqlite/tag.go index e39f6f8a1..ce09da446 100644 --- a/pkg/sqlite/tag.go +++ b/pkg/sqlite/tag.go @@ -10,6 +10,7 @@ import ( "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" "github.com/jmoiron/sqlx" + "gopkg.in/guregu/null.v4" "gopkg.in/guregu/null.v4/zero" "github.com/stashapp/stash/pkg/models" @@ -27,7 +28,7 @@ const ( type tagRow struct { ID int `db:"id" goqu:"skipinsert"` - Name string `db:"name"` + Name null.String `db:"name"` // TODO: make schema non-nullable Description zero.String `db:"description"` IgnoreAutoTag bool `db:"ignore_auto_tag"` CreatedAt Timestamp `db:"created_at"` @@ -39,7 +40,7 @@ type tagRow struct { func (r *tagRow) fromTag(o models.Tag) { r.ID = o.ID - r.Name = o.Name + r.Name = null.StringFrom(o.Name) r.Description = zero.StringFrom(o.Description) r.IgnoreAutoTag = o.IgnoreAutoTag r.CreatedAt = Timestamp{Timestamp: o.CreatedAt} @@ -49,7 +50,7 @@ func (r *tagRow) fromTag(o models.Tag) { func (r *tagRow) resolve() *models.Tag { ret := &models.Tag{ ID: r.ID, - Name: r.Name, + Name: r.Name.String, Description: r.Description.String, IgnoreAutoTag: r.IgnoreAutoTag, CreatedAt: r.CreatedAt.Timestamp, diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryChapterForm.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryChapterForm.tsx index 0b8b711b0..ef8bfda0a 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryChapterForm.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryChapterForm.tsx @@ -14,13 +14,13 @@ import isEqual from "lodash-es/isEqual"; interface IGalleryChapterForm { galleryID: string; - editingChapter?: GQL.GalleryChapterDataFragment; + chapter?: GQL.GalleryChapterDataFragment; onClose: () => void; } export const GalleryChapterForm: React.FC = ({ galleryID, - editingChapter, + chapter, onClose, }) => { const intl = useIntl(); @@ -30,6 +30,8 @@ export const GalleryChapterForm: React.FC = ({ const [galleryChapterDestroy] = useGalleryChapterDestroy(); const Toast = useToast(); + const isNew = chapter === undefined; + const schema = yup.object({ title: yup.string().ensure(), image_index: yup @@ -41,8 +43,8 @@ export const GalleryChapterForm: React.FC = ({ }); const initialValues = { - title: editingChapter?.title ?? "", - image_index: editingChapter?.image_index ?? 1, + title: chapter?.title ?? "", + image_index: chapter?.image_index ?? 1, }; type InputValues = yup.InferType; @@ -56,7 +58,7 @@ export const GalleryChapterForm: React.FC = ({ async function onSave(input: InputValues) { try { - if (!editingChapter) { + if (isNew) { await galleryChapterCreate({ variables: { gallery_id: galleryID, @@ -66,7 +68,7 @@ export const GalleryChapterForm: React.FC = ({ } else { await galleryChapterUpdate({ variables: { - id: editingChapter.id, + id: chapter.id, gallery_id: galleryID, ...input, }, @@ -80,10 +82,10 @@ export const GalleryChapterForm: React.FC = ({ } async function onDelete() { - if (!editingChapter) return; + if (isNew) return; try { - await galleryChapterDestroy({ variables: { id: editingChapter.id } }); + await galleryChapterDestroy({ variables: { id: chapter.id } }); } catch (e) { Toast.error(e); } finally { @@ -130,9 +132,7 @@ export const GalleryChapterForm: React.FC = ({
- {editingChapter && ( + {!isNew && (
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx index e73ce7cc9..37650954b 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import { Button, Form } from "react-bootstrap"; import { FormattedMessage } from "react-intl"; import { useFormik } from "formik"; @@ -17,13 +17,13 @@ import isEqual from "lodash-es/isEqual"; interface ISceneMarkerForm { sceneID: string; - editingMarker?: GQL.SceneMarkerDataFragment; + marker?: GQL.SceneMarkerDataFragment; onClose: () => void; } export const SceneMarkerForm: React.FC = ({ sceneID, - editingMarker, + marker, onClose, }) => { const [sceneMarkerCreate] = useSceneMarkerCreate(); @@ -31,6 +31,8 @@ export const SceneMarkerForm: React.FC = ({ const [sceneMarkerDestroy] = useSceneMarkerDestroy(); const Toast = useToast(); + const isNew = marker === undefined; + const schema = yup.object({ title: yup.string().ensure(), seconds: yup.number().required().integer(), @@ -38,12 +40,16 @@ export const SceneMarkerForm: React.FC = ({ tag_ids: yup.array(yup.string().required()).defined(), }); - const initialValues = { - title: editingMarker?.title ?? "", - seconds: editingMarker?.seconds ?? Math.round(getPlayerPosition() ?? 0), - primary_tag_id: editingMarker?.primary_tag.id ?? "", - tag_ids: editingMarker?.tags.map((tag) => tag.id) ?? [], - }; + // useMemo to only run getPlayerPosition when the input marker actually changes + const initialValues = useMemo( + () => ({ + title: marker?.title ?? "", + seconds: marker?.seconds ?? Math.round(getPlayerPosition() ?? 0), + primary_tag_id: marker?.primary_tag.id ?? "", + tag_ids: marker?.tags.map((tag) => tag.id) ?? [], + }), + [marker] + ); type InputValues = yup.InferType; @@ -56,7 +62,7 @@ export const SceneMarkerForm: React.FC = ({ async function onSave(input: InputValues) { try { - if (!editingMarker) { + if (isNew) { await sceneMarkerCreate({ variables: { scene_id: sceneID, @@ -66,7 +72,7 @@ export const SceneMarkerForm: React.FC = ({ } else { await sceneMarkerUpdate({ variables: { - id: editingMarker.id, + id: marker.id, scene_id: sceneID, ...input, }, @@ -80,10 +86,10 @@ export const SceneMarkerForm: React.FC = ({ } async function onDelete() { - if (!editingMarker) return; + if (isNew) return; try { - await sceneMarkerDestroy({ variables: { id: editingMarker.id } }); + await sceneMarkerDestroy({ variables: { id: marker.id } }); } catch (e) { Toast.error(e); } finally { @@ -169,9 +175,7 @@ export const SceneMarkerForm: React.FC = ({
- {editingMarker && ( + {!isNew && (