Show O Counter in Studio card (#5982)

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
Slick Daddy 2025-11-25 02:06:36 +03:00 committed by GitHub
parent ca8ee6bc2a
commit ecd9c6ec5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 138 additions and 6 deletions

View file

@ -25,6 +25,7 @@ type Studio {
updated_at: Time!
groups: [Group!]!
movies: [Movie!]! @deprecated(reason: "use groups instead")
o_counter: Int
}
input StudioCreateInput {

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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.

View file

@ -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.

View file

@ -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()

View file

@ -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),

View file

@ -17,4 +17,5 @@ fragment SlimStudioData on Studio {
id
name
}
o_counter
}

View file

@ -39,6 +39,7 @@ fragment StudioData on Studio {
tags {
...SlimTagData
}
o_counter
}
fragment SelectStudioData on Studio {

View file

@ -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>
</>
);

View file

@ -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} />
<RatingSystem
value={studio.rating100}
onSetRating={(value) => setRating(value)}
clickToRate
withoutContext
/>
<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}

View file

@ -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 */