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!
|
updated_at: Time!
|
||||||
groups: [Group!]!
|
groups: [Group!]!
|
||||||
movies: [Movie!]! @deprecated(reason: "use groups instead")
|
movies: [Movie!]! @deprecated(reason: "use groups instead")
|
||||||
|
o_counter: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
input StudioCreateInput {
|
input StudioCreateInput {
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,24 @@ func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio, dep
|
||||||
return r.GroupCount(ctx, obj, depth)
|
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) {
|
func (r *studioResolver) ParentStudio(ctx context.Context, obj *models.Studio) (ret *models.Studio, err error) {
|
||||||
if obj.ParentID == nil {
|
if obj.ParentID == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
||||||
|
|
@ -594,6 +594,27 @@ func (_m *ImageReaderWriter) OCountByPerformerID(ctx context.Context, performerI
|
||||||
return r0, r1
|
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
|
// Query provides a mock function with given fields: ctx, options
|
||||||
func (_m *ImageReaderWriter) Query(ctx context.Context, options models.ImageQueryOptions) (*models.ImageQueryResult, error) {
|
func (_m *ImageReaderWriter) Query(ctx context.Context, options models.ImageQueryOptions) (*models.ImageQueryResult, error) {
|
||||||
ret := _m.Called(ctx, options)
|
ret := _m.Called(ctx, options)
|
||||||
|
|
|
||||||
|
|
@ -1183,6 +1183,27 @@ func (_m *SceneReaderWriter) OCountByPerformerID(ctx context.Context, performerI
|
||||||
return r0, r1
|
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
|
// PlayDuration provides a mock function with given fields: ctx
|
||||||
func (_m *SceneReaderWriter) PlayDuration(ctx context.Context) (float64, error) {
|
func (_m *SceneReaderWriter) PlayDuration(ctx context.Context) (float64, error) {
|
||||||
ret := _m.Called(ctx)
|
ret := _m.Called(ctx)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ type ImageCounter interface {
|
||||||
CountByGalleryID(ctx context.Context, galleryID int) (int, error)
|
CountByGalleryID(ctx context.Context, galleryID int) (int, error)
|
||||||
OCount(ctx context.Context) (int, error)
|
OCount(ctx context.Context) (int, error)
|
||||||
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
|
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
|
||||||
|
OCountByStudioID(ctx context.Context, studioID int) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageCreator provides methods to create images.
|
// ImageCreator provides methods to create images.
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ type SceneCounter interface {
|
||||||
CountMissingOSHash(ctx context.Context) (int, error)
|
CountMissingOSHash(ctx context.Context) (int, error)
|
||||||
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
|
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
|
||||||
OCountByGroupID(ctx context.Context, groupID 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.
|
// SceneCreator provides methods to create scenes.
|
||||||
|
|
|
||||||
|
|
@ -682,6 +682,20 @@ func (qb *ImageStore) OCountByPerformerID(ctx context.Context, performerID int)
|
||||||
return ret, nil
|
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) {
|
func (qb *ImageStore) OCount(ctx context.Context) (int, error) {
|
||||||
table := qb.table()
|
table := qb.table()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -818,6 +818,23 @@ func (qb *SceneStore) OCountByGroupID(ctx context.Context, groupID int) (int, er
|
||||||
return ret, nil
|
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) {
|
func (qb *SceneStore) FindByGroupID(ctx context.Context, groupID int) ([]*models.Scene, error) {
|
||||||
sq := dialect.From(scenesGroupsJoinTable).Select(scenesGroupsJoinTable.Col(sceneIDColumn)).Where(
|
sq := dialect.From(scenesGroupsJoinTable).Select(scenesGroupsJoinTable.Col(sceneIDColumn)).Where(
|
||||||
scenesGroupsJoinTable.Col(groupIDColumn).Eq(groupID),
|
scenesGroupsJoinTable.Col(groupIDColumn).Eq(groupID),
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,5 @@ fragment SlimStudioData on Studio {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
o_counter
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ fragment StudioData on Studio {
|
||||||
tags {
|
tags {
|
||||||
...SlimTagData
|
...SlimTagData
|
||||||
}
|
}
|
||||||
|
o_counter
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment SelectStudioData on Studio {
|
fragment SelectStudioData on Studio {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
||||||
import { useStudioUpdate } from "src/core/StashService";
|
import { useStudioUpdate } from "src/core/StashService";
|
||||||
import { faTag } from "@fortawesome/free-solid-svg-icons";
|
import { faTag } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { OCounterButton } from "../Shared/CountButton";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
studio: GQL.StudioDataFragment;
|
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() {
|
function maybeRenderPopoverButtonGroup() {
|
||||||
if (
|
if (
|
||||||
studio.scene_count ||
|
studio.scene_count ||
|
||||||
|
|
@ -182,6 +189,7 @@ export const StudioCard: React.FC<IProps> = ({
|
||||||
studio.gallery_count ||
|
studio.gallery_count ||
|
||||||
studio.group_count ||
|
studio.group_count ||
|
||||||
studio.performer_count ||
|
studio.performer_count ||
|
||||||
|
studio.o_counter ||
|
||||||
studio.tags.length > 0
|
studio.tags.length > 0
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -194,6 +202,7 @@ export const StudioCard: React.FC<IProps> = ({
|
||||||
{maybeRenderGalleriesPopoverButton()}
|
{maybeRenderGalleriesPopoverButton()}
|
||||||
{maybeRenderPerformersPopoverButton()}
|
{maybeRenderPerformersPopoverButton()}
|
||||||
{maybeRenderTagPopoverButton()}
|
{maybeRenderTagPopoverButton()}
|
||||||
|
{maybeRenderOCounter()}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import { ExternalLinkButtons } from "src/components/Shared/ExternalLinksButton";
|
||||||
import { AliasList } from "src/components/Shared/DetailsPage/AliasList";
|
import { AliasList } from "src/components/Shared/DetailsPage/AliasList";
|
||||||
import { HeaderImage } from "src/components/Shared/DetailsPage/HeaderImage";
|
import { HeaderImage } from "src/components/Shared/DetailsPage/HeaderImage";
|
||||||
import { goBackOrReplace } from "src/utils/history";
|
import { goBackOrReplace } from "src/utils/history";
|
||||||
|
import { OCounterButton } from "src/components/Shared/CountButton";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
studio: GQL.StudioDataFragment;
|
studio: GQL.StudioDataFragment;
|
||||||
|
|
@ -471,12 +472,17 @@ const StudioPage: React.FC<IProps> = ({ studio, tabKey }) => {
|
||||||
</DetailTitle>
|
</DetailTitle>
|
||||||
|
|
||||||
<AliasList aliases={studio.aliases} />
|
<AliasList aliases={studio.aliases} />
|
||||||
|
<div className="quality-group">
|
||||||
<RatingSystem
|
<RatingSystem
|
||||||
value={studio.rating100}
|
value={studio.rating100}
|
||||||
onSetRating={(value) => setRating(value)}
|
onSetRating={(value) => setRating(value)}
|
||||||
clickToRate
|
clickToRate
|
||||||
withoutContext
|
withoutContext
|
||||||
/>
|
/>
|
||||||
|
{!!studio.o_counter && (
|
||||||
|
<OCounterButton value={studio.o_counter} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{!isEditing && (
|
{!isEditing && (
|
||||||
<StudioDetailsPanel
|
<StudioDetailsPanel
|
||||||
studio={studio}
|
studio={studio}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,27 @@
|
||||||
width: auto;
|
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
|
// the detail element ids are the same as field type name
|
||||||
// which don't follow the correct convention
|
// which don't follow the correct convention
|
||||||
/* stylelint-disable selector-class-pattern */
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue