mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
Add Icons to tags if they have parent/child tags (#3931)
* Add Icons to tags if they have parent/child tags * Refactor TagLink --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
@@ -13,12 +13,10 @@ fragment SceneMarkerData on SceneMarker {
|
|||||||
primary_tag {
|
primary_tag {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
aliases
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tags {
|
tags {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
aliases
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,6 @@ fragment SlimTagData on Tag {
|
|||||||
name
|
name
|
||||||
aliases
|
aliases
|
||||||
image_path
|
image_path
|
||||||
|
parent_count
|
||||||
|
child_count
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ type Tag {
|
|||||||
performer_count(depth: Int): Int! # Resolver
|
performer_count(depth: Int): Int! # Resolver
|
||||||
parents: [Tag!]!
|
parents: [Tag!]!
|
||||||
children: [Tag!]!
|
children: [Tag!]!
|
||||||
|
|
||||||
|
parent_count: Int! # Resolver
|
||||||
|
child_count: Int! # Resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
input TagCreateInput {
|
input TagCreateInput {
|
||||||
|
|||||||
@@ -113,3 +113,25 @@ func (r *tagResolver) ImagePath(ctx context.Context, obj *models.Tag) (*string,
|
|||||||
imagePath := urlbuilders.NewTagURLBuilder(baseURL, obj).GetTagImageURL(hasImage)
|
imagePath := urlbuilders.NewTagURLBuilder(baseURL, obj).GetTagImageURL(hasImage)
|
||||||
return &imagePath, nil
|
return &imagePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *tagResolver) ParentCount(ctx context.Context, obj *models.Tag) (ret int, err error) {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
ret, err = r.repository.Tag.CountByParentTagID(ctx, obj.ID)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *tagResolver) ChildCount(ctx context.Context, obj *models.Tag) (ret int, err error) {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
ret, err = r.repository.Tag.CountByChildTagID(ctx, obj.ID)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,6 +58,48 @@ func (_m *TagReaderWriter) Count(ctx context.Context) (int, error) {
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountByChildTagID provides a mock function with given fields: ctx, childID
|
||||||
|
func (_m *TagReaderWriter) CountByChildTagID(ctx context.Context, childID int) (int, error) {
|
||||||
|
ret := _m.Called(ctx, childID)
|
||||||
|
|
||||||
|
var r0 int
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, int) int); ok {
|
||||||
|
r0 = rf(ctx, childID)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
||||||
|
r1 = rf(ctx, childID)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountByParentTagID provides a mock function with given fields: ctx, parentID
|
||||||
|
func (_m *TagReaderWriter) CountByParentTagID(ctx context.Context, parentID int) (int, error) {
|
||||||
|
ret := _m.Called(ctx, parentID)
|
||||||
|
|
||||||
|
var r0 int
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, int) int); ok {
|
||||||
|
r0 = rf(ctx, parentID)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
||||||
|
r1 = rf(ctx, parentID)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// Create provides a mock function with given fields: ctx, newTag
|
// Create provides a mock function with given fields: ctx, newTag
|
||||||
func (_m *TagReaderWriter) Create(ctx context.Context, newTag *models.Tag) error {
|
func (_m *TagReaderWriter) Create(ctx context.Context, newTag *models.Tag) error {
|
||||||
ret := _m.Called(ctx, newTag)
|
ret := _m.Called(ctx, newTag)
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ type TagAutoTagQueryer interface {
|
|||||||
// TagCounter provides methods to count tags.
|
// TagCounter provides methods to count tags.
|
||||||
type TagCounter interface {
|
type TagCounter interface {
|
||||||
Count(ctx context.Context) (int, error)
|
Count(ctx context.Context) (int, error)
|
||||||
|
CountByParentTagID(ctx context.Context, parentID int) (int, error)
|
||||||
|
CountByChildTagID(ctx context.Context, childID int) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TagCreator provides methods to create tags.
|
// TagCreator provides methods to create tags.
|
||||||
|
|||||||
@@ -396,6 +396,20 @@ func (qb *TagStore) FindByChildTagID(ctx context.Context, parentID int) ([]*mode
|
|||||||
return qb.queryTags(ctx, query, args)
|
return qb.queryTags(ctx, query, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (qb *TagStore) CountByParentTagID(ctx context.Context, parentID int) (int, error) {
|
||||||
|
q := dialect.Select(goqu.COUNT("*")).From(goqu.T("tags")).
|
||||||
|
InnerJoin(goqu.T("tags_relations"), goqu.On(goqu.I("tags_relations.parent_id").Eq(goqu.I("tags.id")))).
|
||||||
|
Where(goqu.I("tags_relations.child_id").Eq(goqu.V(parentID))) // Pass the parentID here
|
||||||
|
return count(ctx, q)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qb *TagStore) CountByChildTagID(ctx context.Context, childID int) (int, error) {
|
||||||
|
q := dialect.Select(goqu.COUNT("*")).From(goqu.T("tags")).
|
||||||
|
InnerJoin(goqu.T("tags_relations"), goqu.On(goqu.I("tags_relations.child_id").Eq(goqu.I("tags.id")))).
|
||||||
|
Where(goqu.I("tags_relations.parent_id").Eq(goqu.V(childID))) // Pass the childID here
|
||||||
|
return count(ctx, q)
|
||||||
|
}
|
||||||
|
|
||||||
func (qb *TagStore) Count(ctx context.Context) (int, error) {
|
func (qb *TagStore) Count(ctx context.Context) (int, error) {
|
||||||
q := dialect.Select(goqu.COUNT("*")).From(qb.table())
|
q := dialect.Select(goqu.COUNT("*")).From(qb.table())
|
||||||
return count(ctx, q)
|
return count(ctx, q)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { GridCard } from "../Shared/GridCard";
|
import { GridCard } from "../Shared/GridCard";
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { TagLink } from "../Shared/TagLink";
|
import { SceneLink, TagLink } from "../Shared/TagLink";
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||||
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
||||||
@@ -31,7 +31,7 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
|||||||
if (props.gallery.scenes.length === 0) return;
|
if (props.gallery.scenes.length === 0) return;
|
||||||
|
|
||||||
const popoverContent = props.gallery.scenes.map((scene) => (
|
const popoverContent = props.gallery.scenes.map((scene) => (
|
||||||
<TagLink key={scene.id} scene={scene} />
|
<SceneLink key={scene.id} scene={scene} />
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -52,7 +52,7 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
|||||||
if (props.gallery.tags.length <= 0) return;
|
if (props.gallery.tags.length <= 0) return;
|
||||||
|
|
||||||
const popoverContent = props.gallery.tags.map((tag) => (
|
const popoverContent = props.gallery.tags.map((tag) => (
|
||||||
<TagLink key={tag.id} tag={tag} tagType="gallery" />
|
<TagLink key={tag.id} tag={tag} linkType="gallery" />
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const GalleryDetailPanel: React.FC<IGalleryDetailProps> = ({
|
|||||||
function renderTags() {
|
function renderTags() {
|
||||||
if (gallery.tags.length === 0) return;
|
if (gallery.tags.length === 0) return;
|
||||||
const tags = gallery.tags.map((tag) => (
|
const tags = gallery.tags.map((tag) => (
|
||||||
<TagLink key={tag.id} tag={tag} tagType="gallery" />
|
<TagLink key={tag.id} tag={tag} linkType="gallery" />
|
||||||
));
|
));
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Button, ButtonGroup } from "react-bootstrap";
|
|||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import { TagLink } from "src/components/Shared/TagLink";
|
import { GalleryLink, TagLink } from "src/components/Shared/TagLink";
|
||||||
import { HoverPopover } from "src/components/Shared/HoverPopover";
|
import { HoverPopover } from "src/components/Shared/HoverPopover";
|
||||||
import { SweatDrops } from "src/components/Shared/SweatDrops";
|
import { SweatDrops } from "src/components/Shared/SweatDrops";
|
||||||
import { PerformerPopoverButton } from "src/components/Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "src/components/Shared/PerformerPopoverButton";
|
||||||
@@ -41,7 +41,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
|||||||
if (props.image.tags.length <= 0) return;
|
if (props.image.tags.length <= 0) return;
|
||||||
|
|
||||||
const popoverContent = props.image.tags.map((tag) => (
|
const popoverContent = props.image.tags.map((tag) => (
|
||||||
<TagLink key={tag.id} tag={tag} tagType="image" />
|
<TagLink key={tag.id} tag={tag} linkType="image" />
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -83,7 +83,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
|||||||
if (props.image.galleries.length <= 0) return;
|
if (props.image.galleries.length <= 0) return;
|
||||||
|
|
||||||
const popoverContent = props.image.galleries.map((gallery) => (
|
const popoverContent = props.image.galleries.map((gallery) => (
|
||||||
<TagLink key={gallery.id} gallery={gallery} />
|
<GalleryLink key={gallery.id} gallery={gallery} />
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useMemo } from "react";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { TagLink } from "src/components/Shared/TagLink";
|
import { GalleryLink, TagLink } from "src/components/Shared/TagLink";
|
||||||
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
||||||
import { PerformerCard } from "src/components/Performers/PerformerCard";
|
import { PerformerCard } from "src/components/Performers/PerformerCard";
|
||||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
@@ -24,7 +24,7 @@ export const ImageDetailPanel: React.FC<IImageDetailProps> = (props) => {
|
|||||||
function renderTags() {
|
function renderTags() {
|
||||||
if (props.image.tags.length === 0) return;
|
if (props.image.tags.length === 0) return;
|
||||||
const tags = props.image.tags.map((tag) => (
|
const tags = props.image.tags.map((tag) => (
|
||||||
<TagLink key={tag.id} tag={tag} tagType="image" />
|
<TagLink key={tag.id} tag={tag} linkType="image" />
|
||||||
));
|
));
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -67,8 +67,8 @@ export const ImageDetailPanel: React.FC<IImageDetailProps> = (props) => {
|
|||||||
|
|
||||||
function renderGalleries() {
|
function renderGalleries() {
|
||||||
if (props.image.galleries.length === 0) return;
|
if (props.image.galleries.length === 0) return;
|
||||||
const tags = props.image.galleries.map((gallery) => (
|
const galleries = props.image.galleries.map((gallery) => (
|
||||||
<TagLink key={gallery.id} gallery={gallery} />
|
<GalleryLink key={gallery.id} gallery={gallery} />
|
||||||
));
|
));
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -78,7 +78,7 @@ export const ImageDetailPanel: React.FC<IImageDetailProps> = (props) => {
|
|||||||
values={{ count: props.image.galleries.length }}
|
values={{ count: props.image.galleries.length }}
|
||||||
/>
|
/>
|
||||||
</h6>
|
</h6>
|
||||||
{tags}
|
{galleries}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { GridCard } from "../Shared/GridCard";
|
import { GridCard } from "../Shared/GridCard";
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { TagLink } from "../Shared/TagLink";
|
import { SceneLink } from "../Shared/TagLink";
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
@@ -36,7 +36,7 @@ export const MovieCard: React.FC<IProps> = (props: IProps) => {
|
|||||||
if (props.movie.scenes.length === 0) return;
|
if (props.movie.scenes.length === 0) return;
|
||||||
|
|
||||||
const popoverContent = props.movie.scenes.map((scene) => (
|
const popoverContent = props.movie.scenes.map((scene) => (
|
||||||
<TagLink key={scene.id} scene={scene} />
|
<SceneLink key={scene.id} scene={scene} />
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||||||
if (performer.tags.length <= 0) return;
|
if (performer.tags.length <= 0) return;
|
||||||
|
|
||||||
const popoverContent = performer.tags.map((tag) => (
|
const popoverContent = performer.tags.map((tag) => (
|
||||||
<TagLink key={tag.id} tagType="performer" tag={tag} />
|
<TagLink key={tag.id} linkType="performer" tag={tag} />
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||||||
return (
|
return (
|
||||||
<ul className="pl-0">
|
<ul className="pl-0">
|
||||||
{(performer.tags ?? []).map((tag) => (
|
{(performer.tags ?? []).map((tag) => (
|
||||||
<TagLink key={tag.id} tagType="performer" tag={tag} />
|
<TagLink key={tag.id} linkType="performer" tag={tag} />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
|||||||
import { ErrorMessage } from "../Shared/ErrorMessage";
|
import { ErrorMessage } from "../Shared/ErrorMessage";
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { TagLink } from "../Shared/TagLink";
|
import {
|
||||||
|
GalleryLink,
|
||||||
|
MovieLink,
|
||||||
|
SceneMarkerLink,
|
||||||
|
TagLink,
|
||||||
|
} from "../Shared/TagLink";
|
||||||
import { SweatDrops } from "../Shared/SweatDrops";
|
import { SweatDrops } from "../Shared/SweatDrops";
|
||||||
import { Pagination } from "src/components/List/Pagination";
|
import { Pagination } from "src/components/List/Pagination";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
@@ -349,7 +354,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
|||||||
src={sceneMovie.movie.front_image_path ?? ""}
|
src={sceneMovie.movie.front_image_path ?? ""}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<TagLink
|
<MovieLink
|
||||||
key={sceneMovie.movie.id}
|
key={sceneMovie.movie.id}
|
||||||
movie={sceneMovie.movie}
|
movie={sceneMovie.movie}
|
||||||
className="d-block"
|
className="d-block"
|
||||||
@@ -377,8 +382,8 @@ export const SceneDuplicateChecker: React.FC = () => {
|
|||||||
if (scene.scene_markers.length <= 0) return;
|
if (scene.scene_markers.length <= 0) return;
|
||||||
|
|
||||||
const popoverContent = scene.scene_markers.map((marker) => {
|
const popoverContent = scene.scene_markers.map((marker) => {
|
||||||
const markerPopover = { ...marker, scene: { id: scene.id } };
|
const markerWithScene = { ...marker, scene: { id: scene.id } };
|
||||||
return <TagLink key={marker.id} marker={markerPopover} />;
|
return <SceneMarkerLink key={marker.id} marker={markerWithScene} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -410,7 +415,7 @@ export const SceneDuplicateChecker: React.FC = () => {
|
|||||||
if (scene.galleries.length <= 0) return;
|
if (scene.galleries.length <= 0) return;
|
||||||
|
|
||||||
const popoverContent = scene.galleries.map((gallery) => (
|
const popoverContent = scene.galleries.map((gallery) => (
|
||||||
<TagLink key={gallery.id} gallery={gallery} />
|
<GalleryLink key={gallery.id} gallery={gallery} />
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import { Link, useHistory } from "react-router-dom";
|
|||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { TagLink } from "../Shared/TagLink";
|
import {
|
||||||
|
GalleryLink,
|
||||||
|
TagLink,
|
||||||
|
MovieLink,
|
||||||
|
SceneMarkerLink,
|
||||||
|
} from "../Shared/TagLink";
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { SweatDrops } from "../Shared/SweatDrops";
|
import { SweatDrops } from "../Shared/SweatDrops";
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
@@ -219,7 +224,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
src={sceneMovie.movie.front_image_path ?? ""}
|
src={sceneMovie.movie.front_image_path ?? ""}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<TagLink
|
<MovieLink
|
||||||
key={sceneMovie.movie.id}
|
key={sceneMovie.movie.id}
|
||||||
movie={sceneMovie.movie}
|
movie={sceneMovie.movie}
|
||||||
className="d-block"
|
className="d-block"
|
||||||
@@ -245,8 +250,8 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
if (props.scene.scene_markers.length <= 0) return;
|
if (props.scene.scene_markers.length <= 0) return;
|
||||||
|
|
||||||
const popoverContent = props.scene.scene_markers.map((marker) => {
|
const popoverContent = props.scene.scene_markers.map((marker) => {
|
||||||
const markerPopover = { ...marker, scene: { id: props.scene.id } };
|
const markerWithScene = { ...marker, scene: { id: props.scene.id } };
|
||||||
return <TagLink key={marker.id} marker={markerPopover} />;
|
return <SceneMarkerLink key={marker.id} marker={markerWithScene} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -282,7 +287,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
if (props.scene.galleries.length <= 0) return;
|
if (props.scene.galleries.length <= 0) return;
|
||||||
|
|
||||||
const popoverContent = props.scene.galleries.map((gallery) => (
|
const popoverContent = props.scene.galleries.map((gallery) => (
|
||||||
<TagLink key={gallery.id} gallery={gallery} />
|
<GalleryLink key={gallery.id} gallery={gallery} />
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -18,18 +18,19 @@ export const PrimaryTags: React.FC<IPrimaryTags> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
if (!sceneMarkers?.length) return <div />;
|
if (!sceneMarkers?.length) return <div />;
|
||||||
|
|
||||||
const primaries: Record<string, GQL.SlimTagDataFragment> = {};
|
const primaryTagNames: Record<string, string> = {};
|
||||||
const primaryTags: Record<string, GQL.SceneMarkerDataFragment[]> = {};
|
const markersByTag: Record<string, GQL.SceneMarkerDataFragment[]> = {};
|
||||||
sceneMarkers.forEach((m) => {
|
sceneMarkers.forEach((m) => {
|
||||||
if (primaryTags[m.primary_tag.id]) primaryTags[m.primary_tag.id].push(m);
|
if (primaryTagNames[m.primary_tag.id]) {
|
||||||
else {
|
markersByTag[m.primary_tag.id].push(m);
|
||||||
primaryTags[m.primary_tag.id] = [m];
|
} else {
|
||||||
primaries[m.primary_tag.id] = m.primary_tag;
|
primaryTagNames[m.primary_tag.id] = m.primary_tag.name;
|
||||||
|
markersByTag[m.primary_tag.id] = [m];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const primaryCards = Object.keys(primaryTags).map((id) => {
|
const primaryCards = Object.keys(markersByTag).map((id) => {
|
||||||
const markers = primaryTags[id].map((marker) => {
|
const markers = markersByTag[id].map((marker) => {
|
||||||
const tags = marker.tags.map((tag) => (
|
const tags = marker.tags.map((tag) => (
|
||||||
<Badge key={tag.id} variant="secondary" className="tag-item">
|
<Badge key={tag.id} variant="secondary" className="tag-item">
|
||||||
{tag.name}
|
{tag.name}
|
||||||
@@ -59,7 +60,7 @@ export const PrimaryTags: React.FC<IPrimaryTags> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="primary-card col-12 col-sm-6 col-xl-6" key={id}>
|
<Card className="primary-card col-12 col-sm-6 col-xl-6" key={id}>
|
||||||
<h3>{primaries[id].name}</h3>
|
<h3>{primaryTagNames[id]}</h3>
|
||||||
<Card.Body className="primary-card-body">{markers}</Card.Body>
|
<Card.Body className="primary-card-body">{markers}</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { sortPerformers } from "src/core/performers";
|
import { sortPerformers } from "src/core/performers";
|
||||||
import { HoverPopover } from "./HoverPopover";
|
import { HoverPopover } from "./HoverPopover";
|
||||||
import { Icon } from "./Icon";
|
import { Icon } from "./Icon";
|
||||||
import { TagLink } from "./TagLink";
|
import { PerformerLink } from "./TagLink";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
performers: Partial<GQL.PerformerDataFragment>[];
|
performers: Partial<GQL.PerformerDataFragment>[];
|
||||||
@@ -26,7 +26,11 @@ export const PerformerPopoverButton: React.FC<IProps> = ({ performers }) => {
|
|||||||
src={performer.image_path ?? ""}
|
src={performer.image_path ?? ""}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<TagLink key={performer.id} performer={performer} className="d-block" />
|
<PerformerLink
|
||||||
|
key={performer.id}
|
||||||
|
performer={performer}
|
||||||
|
className="d-block"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -767,12 +767,12 @@ export const TagSelect: React.FC<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = (optionProps.data as Option & { __isNew__: boolean }).__isNew__
|
const id = optionProps.data.value;
|
||||||
? ""
|
const hide = (optionProps.data as Option & { __isNew__: boolean })
|
||||||
: optionProps.data.value;
|
.__isNew__;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TagPopover id={id} placement={props.hoverPlacement}>
|
<TagPopover id={id} hide={hide} placement={props.hoverPlacement}>
|
||||||
<reactSelectComponents.Option {...thisOptionProps} />
|
<reactSelectComponents.Option {...thisOptionProps} />
|
||||||
</TagPopover>
|
</TagPopover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,97 +1,252 @@
|
|||||||
import { Badge } from "react-bootstrap";
|
import { Badge, OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import {
|
import NavUtils, { INamedObject } from "src/utils/navigation";
|
||||||
PerformerDataFragment,
|
|
||||||
TagDataFragment,
|
|
||||||
MovieDataFragment,
|
|
||||||
SceneDataFragment,
|
|
||||||
} from "src/core/generated-graphql";
|
|
||||||
import NavUtils from "src/utils/navigation";
|
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { objectTitle } from "src/core/files";
|
import { IFile, IObjectWithTitleFiles, objectTitle } from "src/core/files";
|
||||||
import { galleryTitle } from "src/core/galleries";
|
import { galleryTitle } from "src/core/galleries";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { TagPopover } from "../Tags/TagPopover";
|
import { TagPopover } from "../Tags/TagPopover";
|
||||||
import { markerTitle } from "src/core/markers";
|
import { markerTitle } from "src/core/markers";
|
||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
|
import { faFolderTree } from "@fortawesome/free-solid-svg-icons";
|
||||||
interface IFile {
|
import { Icon } from "../Shared/Icon";
|
||||||
path: string;
|
import { FormattedMessage } from "react-intl";
|
||||||
}
|
|
||||||
interface IGallery {
|
|
||||||
id: string;
|
|
||||||
files: IFile[];
|
|
||||||
folder?: GQL.Maybe<IFile>;
|
|
||||||
title: GQL.Maybe<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type SceneMarkerFragment = Pick<GQL.SceneMarker, "id" | "title" | "seconds"> & {
|
type SceneMarkerFragment = Pick<GQL.SceneMarker, "id" | "title" | "seconds"> & {
|
||||||
scene: Pick<GQL.Scene, "id">;
|
scene: Pick<GQL.Scene, "id">;
|
||||||
primary_tag: Pick<GQL.Tag, "id" | "name">;
|
primary_tag: Pick<GQL.Tag, "id" | "name">;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IProps {
|
interface ICommonLinkProps {
|
||||||
tag?: Partial<TagDataFragment>;
|
link: string;
|
||||||
tagType?: "performer" | "scene" | "gallery" | "image" | "details";
|
|
||||||
performer?: Partial<PerformerDataFragment>;
|
|
||||||
marker?: SceneMarkerFragment;
|
|
||||||
movie?: Partial<MovieDataFragment>;
|
|
||||||
scene?: Partial<Pick<SceneDataFragment, "id" | "title" | "files">>;
|
|
||||||
gallery?: Partial<IGallery>;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
hoverPlacement?: Placement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TagLink: React.FC<IProps> = (props: IProps) => {
|
const CommonLinkComponent: React.FC<ICommonLinkProps> = ({
|
||||||
let id: string = "";
|
link,
|
||||||
let link: string = "#";
|
className,
|
||||||
let title: string = "";
|
children,
|
||||||
if (props.tag) {
|
}) => {
|
||||||
id = props.tag.id || "";
|
|
||||||
switch (props.tagType) {
|
|
||||||
case "scene":
|
|
||||||
case undefined:
|
|
||||||
link = NavUtils.makeTagScenesUrl(props.tag);
|
|
||||||
break;
|
|
||||||
case "performer":
|
|
||||||
link = NavUtils.makeTagPerformersUrl(props.tag);
|
|
||||||
break;
|
|
||||||
case "gallery":
|
|
||||||
link = NavUtils.makeTagGalleriesUrl(props.tag);
|
|
||||||
break;
|
|
||||||
case "image":
|
|
||||||
link = NavUtils.makeTagImagesUrl(props.tag);
|
|
||||||
break;
|
|
||||||
case "details":
|
|
||||||
link = NavUtils.makeTagUrl(id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
title = props.tag.name || "";
|
|
||||||
} else if (props.performer) {
|
|
||||||
link = NavUtils.makePerformerScenesUrl(props.performer);
|
|
||||||
title = props.performer.name || "";
|
|
||||||
} else if (props.movie) {
|
|
||||||
link = NavUtils.makeMovieScenesUrl(props.movie);
|
|
||||||
title = props.movie.name || "";
|
|
||||||
} else if (props.marker) {
|
|
||||||
link = NavUtils.makeSceneMarkerUrl(props.marker);
|
|
||||||
title = `${markerTitle(props.marker)} - ${TextUtils.secondsToTimestamp(
|
|
||||||
props.marker.seconds || 0
|
|
||||||
)}`;
|
|
||||||
} else if (props.gallery) {
|
|
||||||
link = `/galleries/${props.gallery.id}`;
|
|
||||||
title = galleryTitle(props.gallery);
|
|
||||||
} else if (props.scene) {
|
|
||||||
link = `/scenes/${props.scene.id}`;
|
|
||||||
title = objectTitle(props.scene);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Badge className={cx("tag-item", props.className)} variant="secondary">
|
<Badge className={cx("tag-item", className)} variant="secondary">
|
||||||
<TagPopover id={id} placement={props.hoverPlacement}>
|
<Link to={link}>{children}</Link>
|
||||||
<Link to={link}>{title}</Link>
|
|
||||||
</TagPopover>
|
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IPerformerLinkProps {
|
||||||
|
performer: INamedObject;
|
||||||
|
linkType?: "scene" | "gallery" | "image";
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PerformerLink: React.FC<IPerformerLinkProps> = ({
|
||||||
|
performer,
|
||||||
|
linkType = "scene",
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const link = useMemo(() => {
|
||||||
|
switch (linkType) {
|
||||||
|
case "gallery":
|
||||||
|
return NavUtils.makePerformerGalleriesUrl(performer);
|
||||||
|
case "image":
|
||||||
|
return NavUtils.makePerformerImagesUrl(performer);
|
||||||
|
case "scene":
|
||||||
|
default:
|
||||||
|
return NavUtils.makePerformerScenesUrl(performer);
|
||||||
|
}
|
||||||
|
}, [performer, linkType]);
|
||||||
|
|
||||||
|
const title = performer.name || "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonLinkComponent link={link} className={className}>
|
||||||
|
{title}
|
||||||
|
</CommonLinkComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IMovieLinkProps {
|
||||||
|
movie: INamedObject;
|
||||||
|
linkType?: "scene";
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MovieLink: React.FC<IMovieLinkProps> = ({
|
||||||
|
movie,
|
||||||
|
linkType = "scene",
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const link = useMemo(() => {
|
||||||
|
switch (linkType) {
|
||||||
|
case "scene":
|
||||||
|
return NavUtils.makeMovieScenesUrl(movie);
|
||||||
|
}
|
||||||
|
}, [movie, linkType]);
|
||||||
|
|
||||||
|
const title = movie.name || "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonLinkComponent link={link} className={className}>
|
||||||
|
{title}
|
||||||
|
</CommonLinkComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ISceneMarkerLinkProps {
|
||||||
|
marker: SceneMarkerFragment;
|
||||||
|
linkType?: "scene";
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SceneMarkerLink: React.FC<ISceneMarkerLinkProps> = ({
|
||||||
|
marker,
|
||||||
|
linkType = "scene",
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const link = useMemo(() => {
|
||||||
|
switch (linkType) {
|
||||||
|
case "scene":
|
||||||
|
return NavUtils.makeSceneMarkerUrl(marker);
|
||||||
|
}
|
||||||
|
}, [marker, linkType]);
|
||||||
|
|
||||||
|
const title = `${markerTitle(marker)} - ${TextUtils.secondsToTimestamp(
|
||||||
|
marker.seconds || 0
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonLinkComponent link={link} className={className}>
|
||||||
|
{title}
|
||||||
|
</CommonLinkComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IObjectWithIDTitleFiles extends IObjectWithTitleFiles {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISceneLinkProps {
|
||||||
|
scene: IObjectWithIDTitleFiles;
|
||||||
|
linkType?: "details";
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SceneLink: React.FC<ISceneLinkProps> = ({
|
||||||
|
scene,
|
||||||
|
linkType = "details",
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const link = useMemo(() => {
|
||||||
|
switch (linkType) {
|
||||||
|
case "details":
|
||||||
|
return `/scenes/${scene.id}`;
|
||||||
|
}
|
||||||
|
}, [scene, linkType]);
|
||||||
|
|
||||||
|
const title = objectTitle(scene);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonLinkComponent link={link} className={className}>
|
||||||
|
{title}
|
||||||
|
</CommonLinkComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IGallery extends IObjectWithIDTitleFiles {
|
||||||
|
folder?: GQL.Maybe<IFile>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGalleryLinkProps {
|
||||||
|
gallery: IGallery;
|
||||||
|
linkType?: "details";
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GalleryLink: React.FC<IGalleryLinkProps> = ({
|
||||||
|
gallery,
|
||||||
|
linkType = "details",
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const link = useMemo(() => {
|
||||||
|
switch (linkType) {
|
||||||
|
case "details":
|
||||||
|
return `/galleries/${gallery.id}`;
|
||||||
|
}
|
||||||
|
}, [gallery, linkType]);
|
||||||
|
|
||||||
|
const title = galleryTitle(gallery);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonLinkComponent link={link} className={className}>
|
||||||
|
{title}
|
||||||
|
</CommonLinkComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ITagLinkProps {
|
||||||
|
tag: INamedObject;
|
||||||
|
linkType?: "scene" | "gallery" | "image" | "details" | "performer";
|
||||||
|
className?: string;
|
||||||
|
hoverPlacement?: Placement;
|
||||||
|
showHierarchyIcon?: boolean;
|
||||||
|
hierarchyTooltipID?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TagLink: React.FC<ITagLinkProps> = ({
|
||||||
|
tag,
|
||||||
|
linkType = "scene",
|
||||||
|
className,
|
||||||
|
hoverPlacement,
|
||||||
|
showHierarchyIcon = false,
|
||||||
|
hierarchyTooltipID,
|
||||||
|
}) => {
|
||||||
|
const link = useMemo(() => {
|
||||||
|
switch (linkType) {
|
||||||
|
case "scene":
|
||||||
|
return NavUtils.makeTagScenesUrl(tag);
|
||||||
|
case "performer":
|
||||||
|
return NavUtils.makeTagPerformersUrl(tag);
|
||||||
|
case "gallery":
|
||||||
|
return NavUtils.makeTagGalleriesUrl(tag);
|
||||||
|
case "image":
|
||||||
|
return NavUtils.makeTagImagesUrl(tag);
|
||||||
|
case "details":
|
||||||
|
return NavUtils.makeTagUrl(tag.id ?? "");
|
||||||
|
}
|
||||||
|
}, [tag, linkType]);
|
||||||
|
|
||||||
|
const title = tag.name || "";
|
||||||
|
|
||||||
|
const tooltip = useMemo(() => {
|
||||||
|
if (!hierarchyTooltipID) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip id="tag-hierarchy-tooltip">
|
||||||
|
<FormattedMessage id={hierarchyTooltipID} />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}, [hierarchyTooltipID]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonLinkComponent link={link} className={className}>
|
||||||
|
<TagPopover id={tag.id ?? ""} placement={hoverPlacement}>
|
||||||
|
<Link to={link}>
|
||||||
|
{title}
|
||||||
|
{showHierarchyIcon && (
|
||||||
|
<OverlayTrigger placement="top" overlay={tooltip}>
|
||||||
|
<span className="icon-wrapper">
|
||||||
|
<span className="vertical-line">|</span>
|
||||||
|
<Icon icon={faFolderTree} className="tag-icon" />
|
||||||
|
</span>
|
||||||
|
</OverlayTrigger>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</TagPopover>
|
||||||
|
</CommonLinkComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { FormattedMessage } from "react-intl";
|
|||||||
import { sortPerformers } from "src/core/performers";
|
import { sortPerformers } from "src/core/performers";
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import { OperationButton } from "src/components/Shared/OperationButton";
|
import { OperationButton } from "src/components/Shared/OperationButton";
|
||||||
import { TagLink } from "src/components/Shared/TagLink";
|
import { PerformerLink, TagLink } from "src/components/Shared/TagLink";
|
||||||
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
||||||
import { parsePath, prepareQueryString } from "src/components/Tagger/utils";
|
import { parsePath, prepareQueryString } from "src/components/Tagger/utils";
|
||||||
import { ScenePreview } from "src/components/Scenes/SceneCard";
|
import { ScenePreview } from "src/components/Scenes/SceneCard";
|
||||||
@@ -54,7 +54,7 @@ const TaggerSceneDetails: React.FC<ITaggerSceneDetails> = ({ scene }) => {
|
|||||||
src={performer.image_path ?? ""}
|
src={performer.image_path ?? ""}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<TagLink
|
<PerformerLink
|
||||||
key={performer.id}
|
key={performer.id}
|
||||||
performer={performer}
|
performer={performer}
|
||||||
className="d-block"
|
className="d-block"
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ export const TagDetailsPanel: React.FC<ITagDetails> = ({ tag, fullWidth }) => {
|
|||||||
key={p.id}
|
key={p.id}
|
||||||
tag={p}
|
tag={p}
|
||||||
hoverPlacement="bottom"
|
hoverPlacement="bottom"
|
||||||
tagType="details"
|
linkType="details"
|
||||||
|
showHierarchyIcon={p.parent_count !== 0}
|
||||||
|
hierarchyTooltipID="tag_parent_tooltip"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
@@ -40,7 +42,9 @@ export const TagDetailsPanel: React.FC<ITagDetails> = ({ tag, fullWidth }) => {
|
|||||||
key={c.id}
|
key={c.id}
|
||||||
tag={c}
|
tag={c}
|
||||||
hoverPlacement="bottom"
|
hoverPlacement="bottom"
|
||||||
tagType="details"
|
linkType="details"
|
||||||
|
showHierarchyIcon={c.child_count !== 0}
|
||||||
|
hierarchyTooltipID="tag_sub_tag_tooltip"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -8,13 +8,12 @@ import { ConfigurationContext } from "../../hooks/Config";
|
|||||||
import { IUIConfig } from "src/core/config";
|
import { IUIConfig } from "src/core/config";
|
||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
|
|
||||||
interface ITagPopoverProps {
|
interface ITagPopoverCardProps {
|
||||||
id?: string;
|
id: string;
|
||||||
placement?: Placement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TagPopoverCard: React.FC<ITagPopoverCardProps> = ({ id }) => {
|
export const TagPopoverCard: React.FC<ITagPopoverCardProps> = ({ id }) => {
|
||||||
const { data, loading, error } = useFindTag(id ?? "");
|
const { data, loading, error } = useFindTag(id);
|
||||||
|
|
||||||
if (loading)
|
if (loading)
|
||||||
return (
|
return (
|
||||||
@@ -35,8 +34,15 @@ export const TagPopoverCard: React.FC<ITagPopoverCardProps> = ({ id }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface ITagPopoverProps {
|
||||||
|
id: string;
|
||||||
|
hide?: boolean;
|
||||||
|
placement?: Placement;
|
||||||
|
}
|
||||||
|
|
||||||
export const TagPopover: React.FC<ITagPopoverProps> = ({
|
export const TagPopover: React.FC<ITagPopoverProps> = ({
|
||||||
id,
|
id,
|
||||||
|
hide,
|
||||||
children,
|
children,
|
||||||
placement = "top",
|
placement = "top",
|
||||||
}) => {
|
}) => {
|
||||||
@@ -45,7 +51,7 @@ export const TagPopover: React.FC<ITagPopoverProps> = ({
|
|||||||
const showTagCardOnHover =
|
const showTagCardOnHover =
|
||||||
(config?.ui as IUIConfig)?.showTagCardOnHover ?? true;
|
(config?.ui as IUIConfig)?.showTagCardOnHover ?? true;
|
||||||
|
|
||||||
if (!id || !showTagCardOnHover) {
|
if (hide || !showTagCardOnHover) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +66,3 @@ export const TagPopover: React.FC<ITagPopoverProps> = ({
|
|||||||
</HoverPopover>
|
</HoverPopover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ITagPopoverCardProps {
|
|
||||||
id?: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -72,3 +72,21 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-item {
|
||||||
|
.icon-wrapper {
|
||||||
|
color: #202b33;
|
||||||
|
opacity: 0.5;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item {
|
||||||
|
.tag-icon {
|
||||||
|
color: #202b33;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0.5;
|
||||||
|
padding-left: 3px;
|
||||||
|
transform: scale(0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
|
||||||
interface IFile {
|
export interface IFile {
|
||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IObjectWithFiles {
|
interface IObjectWithFiles {
|
||||||
files: IFile[];
|
files?: IFile[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IObjectWithTitleFiles extends IObjectWithFiles {
|
export interface IObjectWithTitleFiles extends IObjectWithFiles {
|
||||||
title: GQL.Maybe<string>;
|
title?: GQL.Maybe<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function objectTitle(s: Partial<IObjectWithTitleFiles>) {
|
export function objectTitle(s: Partial<IObjectWithTitleFiles>) {
|
||||||
|
|||||||
@@ -1319,6 +1319,8 @@
|
|||||||
"synopsis": "Synopsis",
|
"synopsis": "Synopsis",
|
||||||
"tag": "Tag",
|
"tag": "Tag",
|
||||||
"tag_count": "Tag Count",
|
"tag_count": "Tag Count",
|
||||||
|
"tag_parent_tooltip": "Has parent tags",
|
||||||
|
"tag_sub_tag_tooltip": "Has sub-tags",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
"tattoos": "Tattoos",
|
"tattoos": "Tattoos",
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
|
|||||||
@@ -73,8 +73,13 @@ const makePerformerImagesUrl = (
|
|||||||
return `/images?${filter.makeQueryParameters()}`;
|
return `/images?${filter.makeQueryParameters()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface INamedObject {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const makePerformerGalleriesUrl = (
|
const makePerformerGalleriesUrl = (
|
||||||
performer: Partial<GQL.PerformerDataFragment>,
|
performer: INamedObject,
|
||||||
extraPerformer?: ILabeledId,
|
extraPerformer?: ILabeledId,
|
||||||
extraCriteria?: Criterion<CriterionValue>[]
|
extraCriteria?: Criterion<CriterionValue>[]
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user