mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 21:04:37 +03:00
@@ -8,7 +8,7 @@
|
|||||||
* Added [DLNA server](/settings?tab=dlna). ([#1364](https://github.com/stashapp/stash/pull/1364))
|
* Added [DLNA server](/settings?tab=dlna). ([#1364](https://github.com/stashapp/stash/pull/1364))
|
||||||
|
|
||||||
### 🎨 Improvements
|
### 🎨 Improvements
|
||||||
* Prompt when leaving scene edit page with unsaved changed. ([#1429](https://github.com/stashapp/stash/pull/1429))
|
* Prompt when leaving scene edit page with unsaved changes. ([#1429](https://github.com/stashapp/stash/pull/1429))
|
||||||
* Make multi-set mode buttons more obvious in multi-edit dialog. ([#1435](https://github.com/stashapp/stash/pull/1435))
|
* Make multi-set mode buttons more obvious in multi-edit dialog. ([#1435](https://github.com/stashapp/stash/pull/1435))
|
||||||
* Filter modifiers and sort by options are now sorted alphabetically. ([#1406](https://github.com/stashapp/stash/pull/1406))
|
* Filter modifiers and sort by options are now sorted alphabetically. ([#1406](https://github.com/stashapp/stash/pull/1406))
|
||||||
* Add `CreatedAt` and `UpdatedAt` (and `FileModTime` where applicable) to API objects. ([#1421](https://github.com/stashapp/stash/pull/1421))
|
* Add `CreatedAt` and `UpdatedAt` (and `FileModTime` where applicable) to API objects. ([#1421](https://github.com/stashapp/stash/pull/1421))
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
* Add button to remove studio stash ID. ([#1378](https://github.com/stashapp/stash/pull/1378))
|
* Add button to remove studio stash ID. ([#1378](https://github.com/stashapp/stash/pull/1378))
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
### 🐛 Bug fixes
|
||||||
|
* Fix click/drag to select scenes. ([#1476](https://github.com/stashapp/stash/pull/1476))
|
||||||
* Fix clearing Performer and Movie ratings not working. ([#1429](https://github.com/stashapp/stash/pull/1429))
|
* Fix clearing Performer and Movie ratings not working. ([#1429](https://github.com/stashapp/stash/pull/1429))
|
||||||
* Fix scraper date parser failing when parsing time. ([#1431](https://github.com/stashapp/stash/pull/1431))
|
* Fix scraper date parser failing when parsing time. ([#1431](https://github.com/stashapp/stash/pull/1431))
|
||||||
* Fix quotes in filter labels causing UI errors. ([#1425](https://github.com/stashapp/stash/pull/1425))
|
* Fix quotes in filter labels causing UI errors. ([#1425](https://github.com/stashapp/stash/pull/1425))
|
||||||
|
|||||||
@@ -4,13 +4,7 @@ import { Link } from "react-router-dom";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { FormattedPlural } from "react-intl";
|
import { FormattedPlural } from "react-intl";
|
||||||
import { useConfiguration } from "src/core/StashService";
|
import { useConfiguration } from "src/core/StashService";
|
||||||
import {
|
import { GridCard, HoverPopover, Icon, TagLink } from "src/components/Shared";
|
||||||
BasicCard,
|
|
||||||
HoverPopover,
|
|
||||||
Icon,
|
|
||||||
TagLink,
|
|
||||||
TruncatedText,
|
|
||||||
} from "src/components/Shared";
|
|
||||||
import { TextUtils } from "src/utils";
|
import { TextUtils } from "src/utils";
|
||||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||||
|
|
||||||
@@ -136,9 +130,14 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicCard
|
<GridCard
|
||||||
className={`gallery-card zoom-${props.zoomIndex}`}
|
className={`gallery-card zoom-${props.zoomIndex}`}
|
||||||
url={`/galleries/${props.gallery.id}`}
|
url={`/galleries/${props.gallery.id}`}
|
||||||
|
title={
|
||||||
|
props.gallery.title
|
||||||
|
? props.gallery.title
|
||||||
|
: TextUtils.fileNameFromPath(props.gallery.path ?? "")
|
||||||
|
}
|
||||||
linkClassName="gallery-card-header"
|
linkClassName="gallery-card-header"
|
||||||
image={
|
image={
|
||||||
<>
|
<>
|
||||||
@@ -155,18 +154,6 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
|||||||
overlays={maybeRenderSceneStudioOverlay()}
|
overlays={maybeRenderSceneStudioOverlay()}
|
||||||
details={
|
details={
|
||||||
<>
|
<>
|
||||||
<Link to={`/galleries/${props.gallery.id}`}>
|
|
||||||
<h5 className="card-section-title">
|
|
||||||
<TruncatedText
|
|
||||||
text={
|
|
||||||
props.gallery.title
|
|
||||||
? props.gallery.title
|
|
||||||
: TextUtils.fileNameFromPath(props.gallery.path ?? "")
|
|
||||||
}
|
|
||||||
lineCount={2}
|
|
||||||
/>
|
|
||||||
</h5>
|
|
||||||
</Link>
|
|
||||||
<span>
|
<span>
|
||||||
{props.gallery.image_count}
|
{props.gallery.image_count}
|
||||||
<FormattedPlural
|
<FormattedPlural
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button, ButtonGroup, Card, Form } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import { Link } 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 {
|
import { Icon, TagLink, HoverPopover, SweatDrops } from "src/components/Shared";
|
||||||
Icon,
|
|
||||||
TagLink,
|
|
||||||
HoverPopover,
|
|
||||||
SweatDrops,
|
|
||||||
TruncatedText,
|
|
||||||
} from "src/components/Shared";
|
|
||||||
import { TextUtils } from "src/utils";
|
import { TextUtils } from "src/utils";
|
||||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||||
|
import { GridCard } from "../Shared/GridCard";
|
||||||
|
|
||||||
interface IImageCardProps {
|
interface IImageCardProps {
|
||||||
image: GQL.SlimImageDataFragment;
|
image: GQL.SlimImageDataFragment;
|
||||||
@@ -110,36 +104,6 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleImageClick(
|
|
||||||
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
|
|
||||||
) {
|
|
||||||
const { shiftKey } = event;
|
|
||||||
|
|
||||||
if (props.selecting) {
|
|
||||||
props.onSelectedChanged(!props.selected, shiftKey);
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDrag(event: React.DragEvent<HTMLAnchorElement>) {
|
|
||||||
if (props.selecting) {
|
|
||||||
event.dataTransfer.setData("text/plain", "");
|
|
||||||
event.dataTransfer.setDragImage(new Image(), 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDragOver(event: React.DragEvent<HTMLAnchorElement>) {
|
|
||||||
const ev = event;
|
|
||||||
const shiftKey = false;
|
|
||||||
|
|
||||||
if (props.selecting && !props.selected) {
|
|
||||||
props.onSelectedChanged(true, shiftKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.dataTransfer.dropEffect = "move";
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPortrait() {
|
function isPortrait() {
|
||||||
const { file } = props.image;
|
const { file } = props.image;
|
||||||
const width = file.width ? file.width : 0;
|
const width = file.width ? file.width : 0;
|
||||||
@@ -147,31 +111,18 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
|||||||
return height > width;
|
return height > width;
|
||||||
}
|
}
|
||||||
|
|
||||||
let shiftKey = false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={`image-card zoom-${props.zoomIndex}`}>
|
<GridCard
|
||||||
<Form.Control
|
className={`image-card zoom-${props.zoomIndex}`}
|
||||||
type="checkbox"
|
url={`/images/${props.image.id}`}
|
||||||
className="image-card-check"
|
title={
|
||||||
checked={props.selected}
|
props.image.title
|
||||||
onChange={() => props.onSelectedChanged(!props.selected, shiftKey)}
|
? props.image.title
|
||||||
onClick={(event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
|
: TextUtils.fileNameFromPath(props.image.path)
|
||||||
// eslint-disable-next-line prefer-destructuring
|
}
|
||||||
shiftKey = event.shiftKey;
|
linkClassName="image-card-link"
|
||||||
event.stopPropagation();
|
image={
|
||||||
}}
|
<>
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="image-section">
|
|
||||||
<Link
|
|
||||||
to={`/images/${props.image.id}`}
|
|
||||||
className="image-card-link"
|
|
||||||
onClick={handleImageClick}
|
|
||||||
onDragStart={handleDrag}
|
|
||||||
onDragOver={handleDragOver}
|
|
||||||
draggable={props.selecting}
|
|
||||||
>
|
|
||||||
<div className={cx("image-card-preview", { portrait: isPortrait() })}>
|
<div className={cx("image-card-preview", { portrait: isPortrait() })}>
|
||||||
<img
|
<img
|
||||||
className="image-card-preview-image"
|
className="image-card-preview-image"
|
||||||
@@ -180,22 +131,12 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{maybeRenderRatingBanner()}
|
{maybeRenderRatingBanner()}
|
||||||
</Link>
|
</>
|
||||||
</div>
|
|
||||||
<div className="card-section">
|
|
||||||
<h5 className="card-section-title">
|
|
||||||
<TruncatedText
|
|
||||||
text={
|
|
||||||
props.image.title
|
|
||||||
? props.image.title
|
|
||||||
: TextUtils.fileNameFromPath(props.image.path)
|
|
||||||
}
|
}
|
||||||
lineCount={2}
|
popovers={maybeRenderPopoverButtonGroup()}
|
||||||
|
selected={props.selected}
|
||||||
|
selecting={props.selecting}
|
||||||
|
onSelectedChanged={props.onSelectedChanged}
|
||||||
/>
|
/>
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{maybeRenderPopoverButtonGroup()}
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,21 +19,6 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-check {
|
|
||||||
left: 0.5rem;
|
|
||||||
margin-top: -12px;
|
|
||||||
opacity: 0;
|
|
||||||
padding-left: 15px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0.7rem;
|
|
||||||
width: 1.2rem;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
&:checked {
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rating-banner {
|
.rating-banner {
|
||||||
transition: opacity 0.5s;
|
transition: opacity 0.5s;
|
||||||
}
|
}
|
||||||
@@ -63,11 +48,6 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.5s;
|
transition: opacity 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-card-check {
|
|
||||||
opacity: 0.75;
|
|
||||||
transition: opacity 0.5s;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { FunctionComponent } from "react";
|
import React, { FunctionComponent } from "react";
|
||||||
import { FormattedPlural } from "react-intl";
|
import { FormattedPlural } from "react-intl";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { BasicCard, TruncatedText } from "src/components/Shared";
|
import { GridCard } from "src/components/Shared";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
movie: GQL.MovieDataFragment;
|
movie: GQL.MovieDataFragment;
|
||||||
@@ -45,9 +45,10 @@ export const MovieCard: FunctionComponent<IProps> = (props: IProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicCard
|
<GridCard
|
||||||
className="movie-card"
|
className="movie-card"
|
||||||
url={`/movies/${props.movie.id}`}
|
url={`/movies/${props.movie.id}`}
|
||||||
|
title={props.movie.name}
|
||||||
linkClassName="movie-card-header"
|
linkClassName="movie-card-header"
|
||||||
image={
|
image={
|
||||||
<>
|
<>
|
||||||
@@ -59,14 +60,7 @@ export const MovieCard: FunctionComponent<IProps> = (props: IProps) => {
|
|||||||
{maybeRenderRatingBanner()}
|
{maybeRenderRatingBanner()}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
details={
|
details={maybeRenderSceneNumber()}
|
||||||
<>
|
|
||||||
<h5>
|
|
||||||
<TruncatedText text={props.movie.name} lineCount={2} />
|
|
||||||
</h5>
|
|
||||||
{maybeRenderSceneNumber()}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
selected={props.selected}
|
selected={props.selected}
|
||||||
selecting={props.selecting}
|
selecting={props.selecting}
|
||||||
onSelectedChanged={props.onSelectedChanged}
|
onSelectedChanged={props.onSelectedChanged}
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ import { Link } from "react-router-dom";
|
|||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { NavUtils, TextUtils } from "src/utils";
|
import { NavUtils, TextUtils } from "src/utils";
|
||||||
import {
|
import {
|
||||||
BasicCard,
|
GridCard,
|
||||||
CountryFlag,
|
CountryFlag,
|
||||||
HoverPopover,
|
HoverPopover,
|
||||||
Icon,
|
Icon,
|
||||||
TagLink,
|
TagLink,
|
||||||
TruncatedText,
|
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { Button, ButtonGroup } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import {
|
import {
|
||||||
@@ -150,9 +149,10 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicCard
|
<GridCard
|
||||||
className="performer-card"
|
className="performer-card"
|
||||||
url={`/performers/${performer.id}`}
|
url={`/performers/${performer.id}`}
|
||||||
|
title={performer.name ?? ""}
|
||||||
image={
|
image={
|
||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
@@ -166,9 +166,6 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||||||
}
|
}
|
||||||
details={
|
details={
|
||||||
<>
|
<>
|
||||||
<h5>
|
|
||||||
<TruncatedText text={performer.name} />
|
|
||||||
</h5>
|
|
||||||
{age !== 0 ? <div className="text-muted">{ageString}</div> : ""}
|
{age !== 0 ? <div className="text-muted">{ageString}</div> : ""}
|
||||||
<Link to={NavUtils.makePerformersCountryUrl(performer)}>
|
<Link to={NavUtils.makePerformersCountryUrl(performer)}>
|
||||||
<CountryFlag country={performer.country} />
|
<CountryFlag country={performer.country} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { Button, ButtonGroup, Card, Form } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } 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";
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { TextUtils } from "src/utils";
|
import { TextUtils } from "src/utils";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||||
|
import { GridCard } from "../Shared/GridCard";
|
||||||
|
|
||||||
interface IScenePreviewProps {
|
interface IScenePreviewProps {
|
||||||
isPortrait: boolean;
|
isPortrait: boolean;
|
||||||
@@ -294,36 +295,6 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSceneClick(
|
|
||||||
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
|
|
||||||
) {
|
|
||||||
const { shiftKey } = event;
|
|
||||||
|
|
||||||
if (props.selecting && props.onSelectedChanged) {
|
|
||||||
props.onSelectedChanged(!props.selected, shiftKey);
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDrag(event: React.DragEvent<HTMLAnchorElement>) {
|
|
||||||
if (props.selecting) {
|
|
||||||
event.dataTransfer.setData("text/plain", "");
|
|
||||||
event.dataTransfer.setDragImage(new Image(), 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDragOver(event: React.DragEvent<HTMLAnchorElement>) {
|
|
||||||
const ev = event;
|
|
||||||
const shiftKey = false;
|
|
||||||
|
|
||||||
if (props.selecting && props.onSelectedChanged && !props.selected) {
|
|
||||||
props.onSelectedChanged(true, shiftKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.dataTransfer.dropEffect = "move";
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPortrait() {
|
function isPortrait() {
|
||||||
const { file } = props.scene;
|
const { file } = props.scene;
|
||||||
const width = file.width ? file.width : 0;
|
const width = file.width ? file.width : 0;
|
||||||
@@ -337,35 +308,23 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let shiftKey = false;
|
|
||||||
|
|
||||||
const sceneLink = props.queue
|
const sceneLink = props.queue
|
||||||
? props.queue.makeLink(props.scene.id, { sceneIndex: props.index })
|
? props.queue.makeLink(props.scene.id, { sceneIndex: props.index })
|
||||||
: `/scenes/${props.scene.id}`;
|
: `/scenes/${props.scene.id}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={`scene-card ${zoomIndex()}`}>
|
<GridCard
|
||||||
<Form.Control
|
className={`scene-card ${zoomIndex()}`}
|
||||||
type="checkbox"
|
url={sceneLink}
|
||||||
className="scene-card-check"
|
title={
|
||||||
checked={props.selected}
|
props.scene.title
|
||||||
onChange={() => props.onSelectedChanged?.(!props.selected, shiftKey)}
|
? props.scene.title
|
||||||
onClick={(event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
|
: TextUtils.fileNameFromPath(props.scene.path)
|
||||||
// eslint-disable-next-line prefer-destructuring
|
}
|
||||||
shiftKey = event.shiftKey;
|
linkClassName="scene-card-link"
|
||||||
event.stopPropagation();
|
thumbnailSectionClassName="video-section"
|
||||||
}}
|
image={
|
||||||
/>
|
<>
|
||||||
|
|
||||||
<div className="video-section">
|
|
||||||
<Link
|
|
||||||
to={sceneLink}
|
|
||||||
className="scene-card-link"
|
|
||||||
onClick={handleSceneClick}
|
|
||||||
onDragStart={handleDrag}
|
|
||||||
onDragOver={handleDragOver}
|
|
||||||
draggable={props.selecting}
|
|
||||||
>
|
|
||||||
<ScenePreview
|
<ScenePreview
|
||||||
image={props.scene.paths.screenshot ?? undefined}
|
image={props.scene.paths.screenshot ?? undefined}
|
||||||
video={props.scene.paths.preview ?? undefined}
|
video={props.scene.paths.preview ?? undefined}
|
||||||
@@ -376,29 +335,21 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
/>
|
/>
|
||||||
{maybeRenderRatingBanner()}
|
{maybeRenderRatingBanner()}
|
||||||
{maybeRenderSceneSpecsOverlay()}
|
{maybeRenderSceneSpecsOverlay()}
|
||||||
</Link>
|
</>
|
||||||
{maybeRenderSceneStudioOverlay()}
|
|
||||||
</div>
|
|
||||||
<div className="card-section">
|
|
||||||
<h5 className="card-section-title">
|
|
||||||
<Link to={`/scenes/${props.scene.id}`}>
|
|
||||||
<TruncatedText
|
|
||||||
text={
|
|
||||||
props.scene.title
|
|
||||||
? props.scene.title
|
|
||||||
: TextUtils.fileNameFromPath(props.scene.path)
|
|
||||||
}
|
}
|
||||||
lineCount={2}
|
overlays={maybeRenderSceneStudioOverlay()}
|
||||||
/>
|
details={
|
||||||
</Link>
|
<>
|
||||||
</h5>
|
|
||||||
<span>{props.scene.date}</span>
|
<span>{props.scene.date}</span>
|
||||||
<p>
|
<p>
|
||||||
<TruncatedText text={props.scene.details} lineCount={3} />
|
<TruncatedText text={props.scene.details} lineCount={3} />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</>
|
||||||
|
}
|
||||||
{maybeRenderPopoverButtonGroup()}
|
popovers={maybeRenderPopoverButtonGroup()}
|
||||||
</Card>
|
selected={props.selected}
|
||||||
|
selecting={props.selecting}
|
||||||
|
onSelectedChanged={props.onSelectedChanged}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -164,21 +164,6 @@ textarea.scene-description {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-check {
|
|
||||||
left: 0.5rem;
|
|
||||||
margin-top: -12px;
|
|
||||||
opacity: 0;
|
|
||||||
padding-left: 15px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0.7rem;
|
|
||||||
width: 1.2rem;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
&:checked {
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-specs-overlay,
|
.scene-specs-overlay,
|
||||||
.rating-banner,
|
.rating-banner,
|
||||||
.scene-studio-overlay {
|
.scene-studio-overlay {
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Card, Form } from "react-bootstrap";
|
import { Card, Form } from "react-bootstrap";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import TruncatedText from "./TruncatedText";
|
||||||
|
|
||||||
interface IBasicCardProps {
|
interface ICardProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
linkClassName?: string;
|
linkClassName?: string;
|
||||||
|
thumbnailSectionClassName?: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
title: string;
|
||||||
image: JSX.Element;
|
image: JSX.Element;
|
||||||
details: JSX.Element;
|
details?: JSX.Element;
|
||||||
overlays?: JSX.Element;
|
overlays?: JSX.Element;
|
||||||
popovers?: JSX.Element;
|
popovers?: JSX.Element;
|
||||||
selecting?: boolean;
|
selecting?: boolean;
|
||||||
@@ -15,12 +18,8 @@ interface IBasicCardProps {
|
|||||||
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
|
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BasicCard: React.FC<IBasicCardProps> = (
|
export const GridCard: React.FC<ICardProps> = (props: ICardProps) => {
|
||||||
props: IBasicCardProps
|
function handleImageClick(event: React.MouseEvent<HTMLElement, MouseEvent>) {
|
||||||
) => {
|
|
||||||
function handleImageClick(
|
|
||||||
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
|
|
||||||
) {
|
|
||||||
const { shiftKey } = event;
|
const { shiftKey } = event;
|
||||||
|
|
||||||
if (!props.onSelectedChanged) {
|
if (!props.onSelectedChanged) {
|
||||||
@@ -33,14 +32,14 @@ export const BasicCard: React.FC<IBasicCardProps> = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDrag(event: React.DragEvent<HTMLAnchorElement>) {
|
function handleDrag(event: React.DragEvent<HTMLElement>) {
|
||||||
if (props.selecting) {
|
if (props.selecting) {
|
||||||
event.dataTransfer.setData("text/plain", "");
|
event.dataTransfer.setData("text/plain", "");
|
||||||
event.dataTransfer.setDragImage(new Image(), 0, 0);
|
event.dataTransfer.setDragImage(new Image(), 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragOver(event: React.DragEvent<HTMLAnchorElement>) {
|
function handleDragOver(event: React.DragEvent<HTMLElement>) {
|
||||||
const ev = event;
|
const ev = event;
|
||||||
const shiftKey = false;
|
const shiftKey = false;
|
||||||
|
|
||||||
@@ -77,23 +76,33 @@ export const BasicCard: React.FC<IBasicCardProps> = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={props.className}>
|
<Card
|
||||||
{maybeRenderCheckbox()}
|
className={`${props.className} grid-card`}
|
||||||
|
|
||||||
<div className="image-section">
|
|
||||||
<Link
|
|
||||||
to={props.url}
|
|
||||||
className={props.linkClassName}
|
|
||||||
onClick={handleImageClick}
|
onClick={handleImageClick}
|
||||||
onDragStart={handleDrag}
|
onDragStart={handleDrag}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
draggable={props.onSelectedChanged && props.selecting}
|
draggable={props.onSelectedChanged && props.selecting}
|
||||||
|
>
|
||||||
|
{maybeRenderCheckbox()}
|
||||||
|
|
||||||
|
<div className={`${props.thumbnailSectionClassName} thumbnail-section`}>
|
||||||
|
<Link
|
||||||
|
to={props.url}
|
||||||
|
className={props.linkClassName}
|
||||||
|
onClick={handleImageClick}
|
||||||
>
|
>
|
||||||
{props.image}
|
{props.image}
|
||||||
</Link>
|
</Link>
|
||||||
{props.overlays}
|
{props.overlays}
|
||||||
</div>
|
</div>
|
||||||
<div className="card-section">{props.details}</div>
|
<div className="card-section">
|
||||||
|
<Link to={props.url} onClick={handleImageClick}>
|
||||||
|
<h5 className="card-section-title">
|
||||||
|
<TruncatedText text={props.title} lineCount={2} />
|
||||||
|
</h5>
|
||||||
|
</Link>
|
||||||
|
{props.details}
|
||||||
|
</div>
|
||||||
|
|
||||||
{props.popovers}
|
{props.popovers}
|
||||||
</Card>
|
</Card>
|
||||||
@@ -13,7 +13,7 @@ export { default as CountryFlag } from "./CountryFlag";
|
|||||||
export { default as SuccessIcon } from "./SuccessIcon";
|
export { default as SuccessIcon } from "./SuccessIcon";
|
||||||
export { default as ErrorMessage } from "./ErrorMessage";
|
export { default as ErrorMessage } from "./ErrorMessage";
|
||||||
export { default as TruncatedText } from "./TruncatedText";
|
export { default as TruncatedText } from "./TruncatedText";
|
||||||
export { BasicCard } from "./BasicCard";
|
export { GridCard } from "./GridCard";
|
||||||
export { RatingStars } from "./RatingStars";
|
export { RatingStars } from "./RatingStars";
|
||||||
export { ExportDialog } from "./ExportDialog";
|
export { ExportDialog } from "./ExportDialog";
|
||||||
export { default as DeleteEntityDialog } from "./DeleteEntityDialog";
|
export { default as DeleteEntityDialog } from "./DeleteEntityDialog";
|
||||||
|
|||||||
@@ -151,7 +151,12 @@ button.collapse-button.btn-primary:not(:disabled):not(.disabled):active {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.grid-card {
|
||||||
|
a .card-section-title {
|
||||||
|
color: $text-color;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
.card-check {
|
.card-check {
|
||||||
left: 0.5rem;
|
left: 0.5rem;
|
||||||
margin-top: -12px;
|
margin-top: -12px;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React 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 { NavUtils } from "src/utils";
|
import { NavUtils } from "src/utils";
|
||||||
import { BasicCard, TruncatedText } from "src/components/Shared";
|
import { GridCard } from "src/components/Shared";
|
||||||
import { ButtonGroup } from "react-bootstrap";
|
import { ButtonGroup } from "react-bootstrap";
|
||||||
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
||||||
|
|
||||||
@@ -119,9 +119,10 @@ export const StudioCard: React.FC<IProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicCard
|
<GridCard
|
||||||
className="studio-card"
|
className="studio-card"
|
||||||
url={`/studios/${studio.id}`}
|
url={`/studios/${studio.id}`}
|
||||||
|
title={studio.name}
|
||||||
linkClassName="studio-card-header"
|
linkClassName="studio-card-header"
|
||||||
image={
|
image={
|
||||||
<img
|
<img
|
||||||
@@ -132,9 +133,6 @@ export const StudioCard: React.FC<IProps> = ({
|
|||||||
}
|
}
|
||||||
details={
|
details={
|
||||||
<>
|
<>
|
||||||
<h5>
|
|
||||||
<TruncatedText text={studio.name} />
|
|
||||||
</h5>
|
|
||||||
{maybeRenderParent(studio, hideParent)}
|
{maybeRenderParent(studio, hideParent)}
|
||||||
{maybeRenderChildren(studio)}
|
{maybeRenderChildren(studio)}
|
||||||
{maybeRenderRatingBanner(studio)}
|
{maybeRenderRatingBanner(studio)}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import React 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 { NavUtils } from "src/utils";
|
import { NavUtils } from "src/utils";
|
||||||
import { Icon, TruncatedText } from "../Shared";
|
import { Icon } from "../Shared";
|
||||||
import { BasicCard } from "../Shared/BasicCard";
|
import { GridCard } from "../Shared/GridCard";
|
||||||
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@@ -102,9 +102,10 @@ export const TagCard: React.FC<IProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicCard
|
<GridCard
|
||||||
className={`tag-card zoom-${zoomIndex}`}
|
className={`tag-card zoom-${zoomIndex}`}
|
||||||
url={`/tags/${tag.id}`}
|
url={`/tags/${tag.id}`}
|
||||||
|
title={tag.name ?? ""}
|
||||||
linkClassName="tag-card-header"
|
linkClassName="tag-card-header"
|
||||||
image={
|
image={
|
||||||
<img
|
<img
|
||||||
@@ -113,11 +114,6 @@ export const TagCard: React.FC<IProps> = ({
|
|||||||
src={tag.image_path ?? ""}
|
src={tag.image_path ?? ""}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
details={
|
|
||||||
<h5>
|
|
||||||
<TruncatedText text={tag.name} />
|
|
||||||
</h5>
|
|
||||||
}
|
|
||||||
popovers={maybeRenderPopoverButtonGroup()}
|
popovers={maybeRenderPopoverButtonGroup()}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
selecting={selecting}
|
selecting={selecting}
|
||||||
|
|||||||
Reference in New Issue
Block a user