Allow unsetting of rating, studio, gallery

This commit is contained in:
WithoutPants
2019-10-12 22:32:01 +11:00
parent d8b566250e
commit 470c64b840
8 changed files with 78 additions and 30 deletions

View File

@@ -3,10 +3,11 @@ package api
import ( import (
"context" "context"
"database/sql" "database/sql"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/models"
"strconv" "strconv"
"time" "time"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/models"
) )
func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUpdateInput) (*models.Scene, error) { func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUpdateInput) (*models.Scene, error) {
@@ -29,12 +30,20 @@ func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUp
if input.Date != nil { if input.Date != nil {
updatedScene.Date = models.SQLiteDate{String: *input.Date, Valid: true} updatedScene.Date = models.SQLiteDate{String: *input.Date, Valid: true}
} }
if input.Rating != nil { if input.Rating != nil {
updatedScene.Rating = sql.NullInt64{Int64: int64(*input.Rating), Valid: true} updatedScene.Rating = sql.NullInt64{Int64: int64(*input.Rating), Valid: true}
} else {
// rating must be nullable
updatedScene.Rating = sql.NullInt64{Valid: false}
} }
if input.StudioID != nil { if input.StudioID != nil {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64) studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
updatedScene.StudioID = sql.NullInt64{Int64: studioID, Valid: true} updatedScene.StudioID = sql.NullInt64{Int64: studioID, Valid: true}
} else {
// studio must be nullable
updatedScene.StudioID = sql.NullInt64{Valid: false}
} }
// Start the transaction and save the scene marker // Start the transaction and save the scene marker
@@ -47,6 +56,14 @@ func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUp
return nil, err return nil, err
} }
// Clear the existing gallery value
gqb := models.NewGalleryQueryBuilder()
err = gqb.ClearGalleryId(sceneID, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
if input.GalleryID != nil { if input.GalleryID != nil {
// Save the gallery // Save the gallery
galleryID, _ := strconv.Atoi(*input.GalleryID) galleryID, _ := strconv.Atoi(*input.GalleryID)

View File

@@ -2,9 +2,10 @@ package models
import ( import (
"database/sql" "database/sql"
"path/filepath"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/database"
"path/filepath"
) )
type GalleryQueryBuilder struct{} type GalleryQueryBuilder struct{}
@@ -50,6 +51,24 @@ func (qb *GalleryQueryBuilder) Update(updatedGallery Gallery, tx *sqlx.Tx) (*Gal
return &updatedGallery, nil return &updatedGallery, nil
} }
type GalleryNullSceneID struct {
SceneID sql.NullInt64
}
func (qb *GalleryQueryBuilder) ClearGalleryId(sceneID int, tx *sqlx.Tx) error {
ensureTx(tx)
_, err := tx.NamedExec(
`UPDATE galleries SET scene_id = null WHERE scene_id = :sceneid`,
GalleryNullSceneID{
SceneID: sql.NullInt64{
Int64: int64(sceneID),
Valid: true,
},
},
)
return err
}
func (qb *GalleryQueryBuilder) Find(id int) (*Gallery, error) { func (qb *GalleryQueryBuilder) Find(id int) (*Gallery, error) {
query := "SELECT * FROM galleries WHERE id = ? LIMIT 1" query := "SELECT * FROM galleries WHERE id = ? LIMIT 1"
args := []interface{}{id} args := []interface{}{id}

View File

@@ -3,13 +3,14 @@ package models
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/logger"
"math/rand" "math/rand"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/logger"
) )
var randomSortFloat = rand.Float64() var randomSortFloat = rand.Float64()
@@ -266,21 +267,17 @@ func SQLGenKeys(i interface{}) string {
query = append(query, fmt.Sprintf("%s=:%s", key, key)) query = append(query, fmt.Sprintf("%s=:%s", key, key))
} }
case sql.NullString: case sql.NullString:
if t.Valid { // should be nullable
query = append(query, fmt.Sprintf("%s=:%s", key, key)) query = append(query, fmt.Sprintf("%s=:%s", key, key))
}
case sql.NullBool: case sql.NullBool:
if t.Valid { // should be nullable
query = append(query, fmt.Sprintf("%s=:%s", key, key)) query = append(query, fmt.Sprintf("%s=:%s", key, key))
}
case sql.NullInt64: case sql.NullInt64:
if t.Valid { // should be nullable
query = append(query, fmt.Sprintf("%s=:%s", key, key)) query = append(query, fmt.Sprintf("%s=:%s", key, key))
}
case sql.NullFloat64: case sql.NullFloat64:
if t.Valid { // should be nullable
query = append(query, fmt.Sprintf("%s=:%s", key, key)) query = append(query, fmt.Sprintf("%s=:%s", key, key))
}
default: default:
reflectValue := reflect.ValueOf(t) reflectValue := reflect.ValueOf(t)
kind := reflectValue.Kind() kind := reflectValue.Kind()

View File

@@ -46,7 +46,7 @@ export const SceneEditPanel: FunctionComponent<IProps> = (props: IProps) => {
setDetails(state.details); setDetails(state.details);
setUrl(state.url); setUrl(state.url);
setDate(state.date); setDate(state.date);
setRating(state.rating); setRating(state.rating == null ? NaN : state.rating);
setGalleryId(state.gallery ? state.gallery.id : undefined); setGalleryId(state.gallery ? state.gallery.id : undefined);
setStudioId(state.studio ? state.studio.id : undefined); setStudioId(state.studio ? state.studio.id : undefined);
setPerformerIds(perfIds); setPerformerIds(perfIds);
@@ -150,14 +150,14 @@ export const SceneEditPanel: FunctionComponent<IProps> = (props: IProps) => {
<ValidGalleriesSelect <ValidGalleriesSelect
sceneId={props.scene.id} sceneId={props.scene.id}
initialId={galleryId} initialId={galleryId}
onSelectItem={(item) => setGalleryId(item.id)} onSelectItem={(item) => setGalleryId(item ? item.id : undefined)}
/> />
</FormGroup> </FormGroup>
<FormGroup label="Studio"> <FormGroup label="Studio">
<FilterSelect <FilterSelect
type="studios" type="studios"
onSelectItem={(item) => setStudioId(item.id)} onSelectItem={(item) => setStudioId(item ? item.id : undefined)}
initialId={studioId} initialId={studioId}
/> />
</FormGroup> </FormGroup>

View File

@@ -163,7 +163,7 @@ export const SceneMarkersPanel: FunctionComponent<ISceneMarkersPanelProps> = (pr
return ( return (
<FilterSelect <FilterSelect
type="tags" type="tags"
onSelectItem={(tag) => fieldProps.form.setFieldValue("primaryTagId", tag.id)} onSelectItem={(tag) => fieldProps.form.setFieldValue("primaryTagId", tag ? tag.id : undefined)}
initialId={!!editingMarker ? editingMarker.primary_tag.id : undefined} initialId={!!editingMarker ? editingMarker.primary_tag.id : undefined}
/> />
); );

View File

@@ -18,7 +18,12 @@ type ValidTypes =
interface IProps extends HTMLInputProps { interface IProps extends HTMLInputProps {
type: "performers" | "studios" | "tags"; type: "performers" | "studios" | "tags";
initialId?: string; initialId?: string;
onSelectItem: (item: ValidTypes) => void; onSelectItem: (item: ValidTypes | undefined) => void;
}
function addNoneOption(items: ValidTypes[]) {
// Add a none option to clear the gallery
if (!items.find((item) => item.id === "0")) { items.unshift({id: "0", name: "None"}); }
} }
export const FilterSelect: React.FunctionComponent<IProps> = (props: IProps) => { export const FilterSelect: React.FunctionComponent<IProps> = (props: IProps) => {
@@ -28,12 +33,14 @@ export const FilterSelect: React.FunctionComponent<IProps> = (props: IProps) =>
case "performers": { case "performers": {
const { data } = StashService.useAllPerformersForFilter(); const { data } = StashService.useAllPerformersForFilter();
items = !!data && !!data.allPerformers ? data.allPerformers : []; items = !!data && !!data.allPerformers ? data.allPerformers : [];
addNoneOption(items);
InternalSelect = InternalPerformerSelect; InternalSelect = InternalPerformerSelect;
break; break;
} }
case "studios": { case "studios": {
const { data } = StashService.useAllStudiosForFilter(); const { data } = StashService.useAllStudiosForFilter();
items = !!data && !!data.allStudios ? data.allStudios : []; items = !!data && !!data.allStudios ? data.allStudios : [];
addNoneOption(items);
InternalSelect = InternalStudioSelect; InternalSelect = InternalStudioSelect;
break; break;
} }
@@ -50,7 +57,7 @@ export const FilterSelect: React.FunctionComponent<IProps> = (props: IProps) =>
} }
/* eslint-disable react-hooks/rules-of-hooks */ /* eslint-disable react-hooks/rules-of-hooks */
const [selectedItem, setSelectedItem] = React.useState<ValidTypes | null>(null); const [selectedItem, setSelectedItem] = React.useState<ValidTypes | undefined>(undefined);
const [isInitialized, setIsInitialized] = React.useState<boolean>(false); const [isInitialized, setIsInitialized] = React.useState<boolean>(false);
/* eslint-enable */ /* eslint-enable */
@@ -80,7 +87,11 @@ export const FilterSelect: React.FunctionComponent<IProps> = (props: IProps) =>
return item.name!.toLowerCase().indexOf(query.toLowerCase()) >= 0; return item.name!.toLowerCase().indexOf(query.toLowerCase()) >= 0;
}; };
function onItemSelect(item: ValidTypes) { function onItemSelect(item: ValidTypes | undefined) {
if (item && item.id == "0") {
item = undefined;
}
props.onSelectItem(item); props.onSelectItem(item);
setSelectedItem(item); setSelectedItem(item);
} }

View File

@@ -11,7 +11,7 @@ const InternalSelect = Select.ofType<GQL.ValidGalleriesForSceneValidGalleriesFor
interface IProps extends HTMLInputProps { interface IProps extends HTMLInputProps {
initialId?: string; initialId?: string;
sceneId: string; sceneId: string;
onSelectItem: (item: GQL.ValidGalleriesForSceneValidGalleriesForScene) => void; onSelectItem: (item: GQL.ValidGalleriesForSceneValidGalleriesForScene | undefined) => void;
} }
export const ValidGalleriesSelect: React.FunctionComponent<IProps> = (props: IProps) => { export const ValidGalleriesSelect: React.FunctionComponent<IProps> = (props: IProps) => {
@@ -20,7 +20,7 @@ export const ValidGalleriesSelect: React.FunctionComponent<IProps> = (props: IPr
// Add a none option to clear the gallery // Add a none option to clear the gallery
if (!items.find((item) => item.id === "0")) { items.unshift({id: "0", path: "None"}); } if (!items.find((item) => item.id === "0")) { items.unshift({id: "0", path: "None"}); }
const [selectedItem, setSelectedItem] = React.useState<GQL.ValidGalleriesForSceneValidGalleriesForScene | null>(null); const [selectedItem, setSelectedItem] = React.useState<GQL.ValidGalleriesForSceneValidGalleriesForScene | undefined>(undefined);
const [isInitialized, setIsInitialized] = React.useState<boolean>(false); const [isInitialized, setIsInitialized] = React.useState<boolean>(false);
if (!!props.initialId && !selectedItem && !isInitialized) { if (!!props.initialId && !selectedItem && !isInitialized) {
@@ -49,7 +49,11 @@ export const ValidGalleriesSelect: React.FunctionComponent<IProps> = (props: IPr
return item.path!.toLowerCase().indexOf(query.toLowerCase()) >= 0; return item.path!.toLowerCase().indexOf(query.toLowerCase()) >= 0;
}; };
function onItemSelect(item: GQL.ValidGalleriesForSceneValidGalleriesForScene) { function onItemSelect(item: GQL.ValidGalleriesForSceneValidGalleriesForScene | undefined) {
if (item && item.id == "0") {
item = undefined;
}
props.onSelectItem(item); props.onSelectItem(item);
setSelectedItem(item); setSelectedItem(item);
} }

View File

@@ -124,7 +124,7 @@ export class TableUtils {
title: string, title: string,
type: "performers" | "studios" | "tags", type: "performers" | "studios" | "tags",
initialId: string | undefined, initialId: string | undefined,
onChange: ((id: string) => void), onChange: ((id: string | undefined) => void),
}) { }) {
return ( return (
<tr> <tr>
@@ -132,7 +132,7 @@ export class TableUtils {
<td> <td>
<FilterSelect <FilterSelect
type={options.type} type={options.type}
onSelectItem={(item) => options.onChange(item.id)} onSelectItem={(item) => options.onChange(item ? item.id : undefined)}
initialId={options.initialId} initialId={options.initialId}
/> />
</td> </td>