mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Allow unsetting of rating, studio, gallery
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user