mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Show O Counter in Studio card (#5982)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
parent
ca8ee6bc2a
commit
ecd9c6ec5b
13 changed files with 138 additions and 6 deletions
|
|
@ -25,6 +25,7 @@ type Studio {
|
|||
updated_at: Time!
|
||||
groups: [Group!]!
|
||||
movies: [Movie!]! @deprecated(reason: "use groups instead")
|
||||
o_counter: Int
|
||||
}
|
||||
|
||||
input StudioCreateInput {
|
||||
|
|
|
|||
|
|
@ -143,6 +143,24 @@ func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio, dep
|
|||
return r.GroupCount(ctx, obj, depth)
|
||||
}
|
||||
|
||||
func (r *studioResolver) OCounter(ctx context.Context, obj *models.Studio) (ret *int, err error) {
|
||||
var res_scene int
|
||||
var res_image int
|
||||
var res int
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res_scene, err = r.repository.Scene.OCountByStudioID(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res_image, err = r.repository.Image.OCountByStudioID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = res_scene + res_image
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (r *studioResolver) ParentStudio(ctx context.Context, obj *models.Studio) (ret *models.Studio, err error) {
|
||||
if obj.ParentID == nil {
|
||||
return nil, nil
|
||||
|
|
|
|||
|
|
@ -594,6 +594,27 @@ func (_m *ImageReaderWriter) OCountByPerformerID(ctx context.Context, performerI
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// OCountByStudioID provides a mock function with given fields: ctx, studioID
|
||||
func (_m *ImageReaderWriter) OCountByStudioID(ctx context.Context, studioID int) (int, error) {
|
||||
ret := _m.Called(ctx, studioID)
|
||||
|
||||
var r0 int
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int) int); ok {
|
||||
r0 = rf(ctx, studioID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
||||
r1 = rf(ctx, studioID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Query provides a mock function with given fields: ctx, options
|
||||
func (_m *ImageReaderWriter) Query(ctx context.Context, options models.ImageQueryOptions) (*models.ImageQueryResult, error) {
|
||||
ret := _m.Called(ctx, options)
|
||||
|
|
|
|||
|
|
@ -1183,6 +1183,27 @@ func (_m *SceneReaderWriter) OCountByPerformerID(ctx context.Context, performerI
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// OCountByStudioID provides a mock function with given fields: ctx, studioID
|
||||
func (_m *SceneReaderWriter) OCountByStudioID(ctx context.Context, studioID int) (int, error) {
|
||||
ret := _m.Called(ctx, studioID)
|
||||
|
||||
var r0 int
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int) int); ok {
|
||||
r0 = rf(ctx, studioID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
||||
r1 = rf(ctx, studioID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// PlayDuration provides a mock function with given fields: ctx
|
||||
func (_m *SceneReaderWriter) PlayDuration(ctx context.Context) (float64, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ type ImageCounter interface {
|
|||
CountByGalleryID(ctx context.Context, galleryID int) (int, error)
|
||||
OCount(ctx context.Context) (int, error)
|
||||
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
|
||||
OCountByStudioID(ctx context.Context, studioID int) (int, error)
|
||||
}
|
||||
|
||||
// ImageCreator provides methods to create images.
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ type SceneCounter interface {
|
|||
CountMissingOSHash(ctx context.Context) (int, error)
|
||||
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
|
||||
OCountByGroupID(ctx context.Context, groupID int) (int, error)
|
||||
OCountByStudioID(ctx context.Context, studioID int) (int, error)
|
||||
}
|
||||
|
||||
// SceneCreator provides methods to create scenes.
|
||||
|
|
|
|||
|
|
@ -682,6 +682,20 @@ func (qb *ImageStore) OCountByPerformerID(ctx context.Context, performerID int)
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (qb *ImageStore) OCountByStudioID(ctx context.Context, studioID int) (int, error) {
|
||||
table := qb.table()
|
||||
q := dialect.Select(goqu.COALESCE(goqu.SUM("o_counter"), 0)).From(table).Where(
|
||||
table.Col(studioIDColumn).Eq(studioID),
|
||||
)
|
||||
|
||||
var ret int
|
||||
if err := querySimple(ctx, q, &ret); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (qb *ImageStore) OCount(ctx context.Context) (int, error) {
|
||||
table := qb.table()
|
||||
|
||||
|
|
|
|||
|
|
@ -818,6 +818,23 @@ func (qb *SceneStore) OCountByGroupID(ctx context.Context, groupID int) (int, er
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (qb *SceneStore) OCountByStudioID(ctx context.Context, studioID int) (int, error) {
|
||||
table := qb.table()
|
||||
oHistoryTable := goqu.T(scenesODatesTable)
|
||||
|
||||
q := dialect.Select(goqu.COUNT("*")).From(table).InnerJoin(
|
||||
oHistoryTable,
|
||||
goqu.On(table.Col(idColumn).Eq(oHistoryTable.Col(sceneIDColumn))),
|
||||
).Where(table.Col(studioIDColumn).Eq(studioID))
|
||||
|
||||
var ret int
|
||||
if err := querySimple(ctx, q, &ret); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (qb *SceneStore) FindByGroupID(ctx context.Context, groupID int) ([]*models.Scene, error) {
|
||||
sq := dialect.From(scenesGroupsJoinTable).Select(scenesGroupsJoinTable.Col(sceneIDColumn)).Where(
|
||||
scenesGroupsJoinTable.Col(groupIDColumn).Eq(groupID),
|
||||
|
|
|
|||
|
|
@ -17,4 +17,5 @@ fragment SlimStudioData on Studio {
|
|||
id
|
||||
name
|
||||
}
|
||||
o_counter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ fragment StudioData on Studio {
|
|||
tags {
|
||||
...SlimTagData
|
||||
}
|
||||
o_counter
|
||||
}
|
||||
|
||||
fragment SelectStudioData on Studio {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { RatingBanner } from "../Shared/RatingBanner";
|
|||
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
||||
import { useStudioUpdate } from "src/core/StashService";
|
||||
import { faTag } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OCounterButton } from "../Shared/CountButton";
|
||||
|
||||
interface IProps {
|
||||
studio: GQL.StudioDataFragment;
|
||||
|
|
@ -175,6 +176,12 @@ export const StudioCard: React.FC<IProps> = ({
|
|||
);
|
||||
}
|
||||
|
||||
function maybeRenderOCounter() {
|
||||
if (!studio.o_counter) return;
|
||||
|
||||
return <OCounterButton value={studio.o_counter} />;
|
||||
}
|
||||
|
||||
function maybeRenderPopoverButtonGroup() {
|
||||
if (
|
||||
studio.scene_count ||
|
||||
|
|
@ -182,6 +189,7 @@ export const StudioCard: React.FC<IProps> = ({
|
|||
studio.gallery_count ||
|
||||
studio.group_count ||
|
||||
studio.performer_count ||
|
||||
studio.o_counter ||
|
||||
studio.tags.length > 0
|
||||
) {
|
||||
return (
|
||||
|
|
@ -194,6 +202,7 @@ export const StudioCard: React.FC<IProps> = ({
|
|||
{maybeRenderGalleriesPopoverButton()}
|
||||
{maybeRenderPerformersPopoverButton()}
|
||||
{maybeRenderTagPopoverButton()}
|
||||
{maybeRenderOCounter()}
|
||||
</ButtonGroup>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import { ExternalLinkButtons } from "src/components/Shared/ExternalLinksButton";
|
|||
import { AliasList } from "src/components/Shared/DetailsPage/AliasList";
|
||||
import { HeaderImage } from "src/components/Shared/DetailsPage/HeaderImage";
|
||||
import { goBackOrReplace } from "src/utils/history";
|
||||
import { OCounterButton } from "src/components/Shared/CountButton";
|
||||
|
||||
interface IProps {
|
||||
studio: GQL.StudioDataFragment;
|
||||
|
|
@ -471,12 +472,17 @@ const StudioPage: React.FC<IProps> = ({ studio, tabKey }) => {
|
|||
</DetailTitle>
|
||||
|
||||
<AliasList aliases={studio.aliases} />
|
||||
<div className="quality-group">
|
||||
<RatingSystem
|
||||
value={studio.rating100}
|
||||
onSetRating={(value) => setRating(value)}
|
||||
clickToRate
|
||||
withoutContext
|
||||
/>
|
||||
{!!studio.o_counter && (
|
||||
<OCounterButton value={studio.o_counter} />
|
||||
)}
|
||||
</div>
|
||||
{!isEditing && (
|
||||
<StudioDetailsPanel
|
||||
studio={studio}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,27 @@
|
|||
width: auto;
|
||||
}
|
||||
|
||||
.quality-group {
|
||||
display: inline-flex;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
// The following min-width declarations prevent
|
||||
// the O-Count from moving around
|
||||
// when hovering over rating stars
|
||||
.rating-stars-precision-full .star-rating-number {
|
||||
min-width: 0.75rem;
|
||||
}
|
||||
|
||||
.rating-stars-precision-half .star-rating-number,
|
||||
.rating-stars-precision-tenth .star-rating-number {
|
||||
min-width: 1.45rem;
|
||||
}
|
||||
|
||||
.rating-stars-precision-quarter .star-rating-number {
|
||||
min-width: 2rem;
|
||||
}
|
||||
|
||||
// the detail element ids are the same as field type name
|
||||
// which don't follow the correct convention
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
|
|
|
|||
Loading…
Reference in a new issue