mirror of https://github.com/stashapp/stash.git
Styles
This commit is contained in:
parent
c1ce6d539d
commit
4a32f90382
|
@ -19,7 +19,7 @@ export class ErrorBoundary extends React.Component<any, any> {
|
|||
return (
|
||||
<div>
|
||||
<h2>Something went wrong.</h2>
|
||||
<details style={{ whiteSpace: "pre-wrap" }}>
|
||||
<details className="error-message">
|
||||
{this.state.error && this.state.error.toString()}
|
||||
<br />
|
||||
{this.state.errorInfo.componentStack}
|
||||
|
|
|
@ -14,7 +14,7 @@ export const Gallery: React.FC = () => {
|
|||
if (error) return <div>{error.message}</div>;
|
||||
|
||||
return (
|
||||
<div style={{ width: "75vw", margin: "0 auto" }}>
|
||||
<div className="col-9 m-auto">
|
||||
<GalleryViewer gallery={gallery as any} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -23,7 +23,7 @@ export const GalleryList: React.FC = () => {
|
|||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
return (
|
||||
<Table style={{ margin: "0 auto" }}>
|
||||
<Table className="m-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Preview</th>
|
||||
|
|
|
@ -156,11 +156,12 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||
|
||||
<Form.Group className="row" controlId="ignored-words">
|
||||
<Form.Label className="col-2">Ignored words</Form.Label>
|
||||
<Form.Control
|
||||
className="col-8"
|
||||
onChange={(newValue: any) => setIgnoreWords(newValue.target.value)}
|
||||
value={ignoreWords}
|
||||
/>
|
||||
<InputGroup className="col-8">
|
||||
<Form.Control
|
||||
onChange={(newValue: any) => setIgnoreWords(newValue.target.value)}
|
||||
value={ignoreWords}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Form.Text className="text-muted col-10 offset-2">
|
||||
Matches with {"{i}"}
|
||||
</Form.Text>
|
||||
|
@ -171,27 +172,29 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||
<Form.Label htmlFor="whitespace-characters" className="col-2">
|
||||
Whitespace characters:
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="col-8"
|
||||
onChange={(newValue: any) =>
|
||||
setWhitespaceCharacters(newValue.target.value)
|
||||
}
|
||||
value={whitespaceCharacters}
|
||||
/>
|
||||
<InputGroup className="col-8">
|
||||
<Form.Control
|
||||
onChange={(newValue: any) =>
|
||||
setWhitespaceCharacters(newValue.target.value)
|
||||
}
|
||||
value={whitespaceCharacters}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Form.Text className="text-muted col-10 offset-2">
|
||||
These characters will be replaced with whitespace in the title
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
<Form.Group className="row">
|
||||
<Form.Label htmlFor="capitalize-title" className="col-2">
|
||||
Capitalize title
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="col-8"
|
||||
type="checkbox"
|
||||
<Form.Group>
|
||||
<Form.Check
|
||||
inline
|
||||
className="m-0"
|
||||
id="capitalize-title"
|
||||
checked={capitalizeTitle}
|
||||
onChange={() => setCapitalizeTitle(!capitalizeTitle)}
|
||||
/>
|
||||
<Form.Label htmlFor="capitalize-title">
|
||||
Capitalize title
|
||||
</Form.Label>
|
||||
</Form.Group>
|
||||
|
||||
{/* TODO - mapping stuff will go here */}
|
||||
|
@ -222,12 +225,11 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||
</Form.Group>
|
||||
|
||||
<Form.Group className="row">
|
||||
<Button variant="secondary" className="col-1" onClick={onFind}>
|
||||
<Button variant="secondary" className="ml-3 col-1" onClick={onFind}>
|
||||
Find
|
||||
</Button>
|
||||
<Form.Control
|
||||
as="select"
|
||||
style={{ flexBasis: "min-content" }}
|
||||
options={PAGE_SIZE_OPTIONS}
|
||||
onChange={(event: any) =>
|
||||
props.onPageSizeChanged(parseInt(event.target.value, 10))
|
||||
|
@ -236,7 +238,7 @@ export const ParserInput: React.FC<IParserInputProps> = (
|
|||
className="col-1 filter-item"
|
||||
>
|
||||
{PAGE_SIZE_OPTIONS.map(val => (
|
||||
<option value="val">{val}</option>
|
||||
<option key={val} value={val}>{val}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
|
|
|
@ -414,8 +414,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||
return (
|
||||
<>
|
||||
<td>
|
||||
<Form.Control
|
||||
type="checkbox"
|
||||
<Form.Check
|
||||
checked={props.parserResult.set}
|
||||
onChange={() => {
|
||||
props.onSetChanged(!props.parserResult.set);
|
||||
|
@ -458,7 +457,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||
disabled={!props.parserResult.set}
|
||||
className={props.className}
|
||||
value={props.parserResult.value || ""}
|
||||
onBlur={(event: any) => props.onChange(event.target.value)}
|
||||
onChange={(event: any) => props.onChange(event.target.value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -494,7 +493,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||
return (
|
||||
<div>
|
||||
{elements.map((name: string) => (
|
||||
<Badge variant="secondary">{name}</Badge>
|
||||
<Badge key={name} variant="secondary">{name}</Badge>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
@ -592,7 +591,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||
|
||||
return (
|
||||
<tr className="scene-parser-row">
|
||||
<td style={{ textAlign: "left" }}>{props.scene.filename}</td>
|
||||
<td className="text-left">{props.scene.filename}</td>
|
||||
<SceneParserField
|
||||
key="title"
|
||||
fieldName="Title"
|
||||
|
@ -689,9 +688,8 @@ export const SceneFilenameParser: React.FC = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<td>
|
||||
<Form.Control
|
||||
type="checkbox"
|
||||
<td className="w-15">
|
||||
<Form.Check
|
||||
checked={allSet}
|
||||
onChange={() => {
|
||||
onAllSet(!allSet);
|
||||
|
@ -715,7 +713,7 @@ export const SceneFilenameParser: React.FC = () => {
|
|||
<Table>
|
||||
<thead>
|
||||
<tr className="scene-parser-row">
|
||||
<th>Filename</th>
|
||||
<th className="w-25">Filename</th>
|
||||
{renderHeader("Title", allTitleSet, onSelectAllTitleSet)}
|
||||
{renderHeader("Date", allDateSet, onSelectAllDateSet)}
|
||||
{renderHeader(
|
||||
|
|
|
@ -7,13 +7,13 @@ import { useToast } from "src/hooks";
|
|||
export const SettingsInterfacePanel: React.FC = () => {
|
||||
const Toast = useToast();
|
||||
const config = StashService.useConfiguration();
|
||||
const [soundOnPreview, setSoundOnPreview] = useState<boolean>();
|
||||
const [wallShowTitle, setWallShowTitle] = useState<boolean>();
|
||||
const [soundOnPreview, setSoundOnPreview] = useState<boolean>(true);
|
||||
const [wallShowTitle, setWallShowTitle] = useState<boolean>(true);
|
||||
const [maximumLoopDuration, setMaximumLoopDuration] = useState<number>(0);
|
||||
const [autostartVideo, setAutostartVideo] = useState<boolean>();
|
||||
const [showStudioAsText, setShowStudioAsText] = useState<boolean>();
|
||||
const [autostartVideo, setAutostartVideo] = useState<boolean>(false);
|
||||
const [showStudioAsText, setShowStudioAsText] = useState<boolean>(false);
|
||||
const [css, setCSS] = useState<string>();
|
||||
const [cssEnabled, setCSSEnabled] = useState<boolean>();
|
||||
const [cssEnabled, setCSSEnabled] = useState<boolean>(false);
|
||||
|
||||
const [updateInterfaceConfig] = StashService.useConfigureInterface({
|
||||
soundOnPreview,
|
||||
|
|
|
@ -340,6 +340,10 @@ const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
|
|||
container: (base: CSSProperties, state: any) => ({
|
||||
...base,
|
||||
zIndex: state.isFocused ? 10 : base.zIndex
|
||||
}),
|
||||
multiValueRemove: (base: CSSProperties, state: any) => ({
|
||||
...base,
|
||||
color: state.isFocused ? base.color: '#333333'
|
||||
})
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ interface IProps {
|
|||
tag?: Partial<TagDataFragment>;
|
||||
performer?: Partial<PerformerDataFragment>;
|
||||
marker?: Partial<SceneMarkerDataFragment>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TagLink: React.FC<IProps> = (props: IProps) => {
|
||||
|
@ -30,7 +31,7 @@ export const TagLink: React.FC<IProps> = (props: IProps) => {
|
|||
)}`;
|
||||
}
|
||||
return (
|
||||
<Badge className="tag-item" variant="secondary">
|
||||
<Badge className={`tag-item ${props.className}`} variant="secondary">
|
||||
<Link to={link}>{title}</Link>
|
||||
</Badge>
|
||||
);
|
||||
|
|
|
@ -10,8 +10,8 @@ interface IProps {
|
|||
export const StudioCard: React.FC<IProps> = ({ studio }) => {
|
||||
return (
|
||||
<Card className="studio-card">
|
||||
<Link to={`/studios/${studio.id}`} className="studio-image">
|
||||
<img alt={studio.name} src={studio.image_path ?? ""} />
|
||||
<Link to={`/studios/${studio.id}`} className="studio-card-header">
|
||||
<img className="studio-card-image" alt={studio.name} src={studio.image_path ?? ""} />
|
||||
</Link>
|
||||
<div className="card-section">
|
||||
<h5 className="text-truncate">{studio.name}</h5>
|
||||
|
|
|
@ -160,7 +160,7 @@ export const Studio: React.FC = () => {
|
|||
>
|
||||
{isNew && <h2>Add Studio</h2>}
|
||||
<img className="logo" alt={name} src={imagePreview} />
|
||||
<Table id="performer-details" style={{ width: "100%" }}>
|
||||
<Table>
|
||||
<tbody>
|
||||
{TableUtils.renderInputGroup({
|
||||
title: "Name",
|
||||
|
|
|
@ -18,7 +18,7 @@ export const StudioList: React.FC = () => {
|
|||
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<div className="grid">
|
||||
<div className="row px-xl-5 justify-content-center">
|
||||
{result.data.findStudios.studios.map(studio => (
|
||||
<StudioCard key={studio.id} studio={studio} />
|
||||
))}
|
||||
|
|
|
@ -98,11 +98,11 @@ export const TagList: React.FC = () => {
|
|||
|
||||
const tagElements = data.allTags.map(tag => {
|
||||
return (
|
||||
<div key={tag.id} className="tag-list-row">
|
||||
<div key={tag.id} className="tag-list-row row">
|
||||
<Button variant="link" onClick={() => setEditingTag(tag)}>
|
||||
{tag.name}
|
||||
</Button>
|
||||
<div style={{ float: "right" }}>
|
||||
<div className="ml-auto">
|
||||
<Button variant="secondary" onClick={() => onAutoTag(tag)}>
|
||||
Auto Tag
|
||||
</Button>
|
||||
|
@ -131,7 +131,7 @@ export const TagList: React.FC = () => {
|
|||
<div id="tag-list-container">
|
||||
<Button
|
||||
variant="primary"
|
||||
style={{ marginTop: "20px" }}
|
||||
className="mt-2"
|
||||
onClick={() => setEditingTag({})}
|
||||
>
|
||||
New Tag
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
color: #444;
|
||||
font-weight: 700;
|
||||
left: 0;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
|
|
|
@ -142,7 +142,7 @@ export const WallItem: React.FC<IWallItemProps> = (props: IWallItemProps) => {
|
|||
/>
|
||||
{showTextContainer ? (
|
||||
<div className="scene-wall-item-text-container">
|
||||
<div style={{ lineHeight: 1 }}>{title}</div>
|
||||
<div>{title}</div>
|
||||
{tags}
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
@ -257,7 +257,6 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
|||
defaultValue={props.filter.searchTerm}
|
||||
onChange={onChangeQuery}
|
||||
className="filter-item col-5 col-sm-2"
|
||||
style={{ width: "inherit" }}
|
||||
/>
|
||||
<Form.Control
|
||||
as="select"
|
||||
|
@ -316,13 +315,7 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
|||
{renderMore()}
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
margin: "10px auto"
|
||||
}}
|
||||
>
|
||||
<div className="d-flex justify-content-center">
|
||||
{renderFilterTags()}
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -10,35 +10,36 @@ interface IPerformerCardProps {
|
|||
}
|
||||
|
||||
export const PerformerCard: React.FC<IPerformerCardProps> = (
|
||||
props: IPerformerCardProps
|
||||
{ performer, ageFromDate }
|
||||
) => {
|
||||
const age = TextUtils.age(props.performer.birthdate, props.ageFromDate);
|
||||
const age = TextUtils.age(performer.birthdate, ageFromDate);
|
||||
const ageString = `${age} years old${
|
||||
props.ageFromDate ? " in this scene." : "."
|
||||
ageFromDate ? " in this scene." : "."
|
||||
}`;
|
||||
|
||||
function maybeRenderFavoriteBanner() {
|
||||
if (props.performer.favorite === false) {
|
||||
if (performer.favorite === false) {
|
||||
return;
|
||||
}
|
||||
return <div className="rating-banner rating-5">FAVORITE</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="performer-card">
|
||||
<Card>
|
||||
<Link
|
||||
to={`/performers/${props.performer.id}`}
|
||||
className="performer previewable image"
|
||||
style={{ backgroundImage: `url(${props.performer.image_path})` }}
|
||||
to={`/performers/${performer.id}`}
|
||||
>
|
||||
<img
|
||||
className="image-thumbnail card-image"
|
||||
alt={performer.name ?? ''} src={performer.image_path ?? ''} />
|
||||
{maybeRenderFavoriteBanner()}
|
||||
</Link>
|
||||
<div className="card-section">
|
||||
<h5 className="text-truncate">{props.performer.name}</h5>
|
||||
<h5 className="text-truncate">{performer.name}</h5>
|
||||
{age !== 0 ? <div className="text-muted">{ageString}</div> : ""}
|
||||
<div className="text-muted">
|
||||
Stars in {props.performer.scene_count}{" "}
|
||||
<Link to={NavUtils.makePerformerScenesUrl(props.performer)}>
|
||||
Stars in {performer.scene_count}{" "}
|
||||
<Link to={NavUtils.makePerformerScenesUrl(performer)}>
|
||||
scenes
|
||||
</Link>
|
||||
.
|
||||
|
|
|
@ -222,6 +222,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
{queryableScrapers
|
||||
? queryableScrapers.map(s => (
|
||||
<Button
|
||||
key={s.name}
|
||||
variant="link"
|
||||
onClick={() => onDisplayFreeOnesDialog(s)}
|
||||
>
|
||||
|
@ -294,7 +295,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
</td>
|
||||
<td>
|
||||
<Form.Control
|
||||
value={url}
|
||||
value={url ?? ''}
|
||||
readOnly={!isEditing}
|
||||
plaintext={!isEditing}
|
||||
placeholder="URL"
|
||||
|
@ -310,7 +311,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
function maybeRenderButtons() {
|
||||
if (isEditing) {
|
||||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
<Button
|
||||
className="edit-button"
|
||||
variant="primary"
|
||||
|
@ -330,7 +331,11 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
""
|
||||
)}
|
||||
{renderScraperMenu()}
|
||||
</>
|
||||
<ImageInput
|
||||
isEditing={!!isEditing}
|
||||
onImageChange={onImageChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -377,7 +382,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
{renderDeleteAlert()}
|
||||
{renderScraperDialog()}
|
||||
|
||||
<Table id="performer-details" style={{ width: "100%" }}>
|
||||
<Table id="performer-details" className="w-100">
|
||||
<tbody>
|
||||
{maybeRenderName()}
|
||||
{maybeRenderAliases()}
|
||||
|
@ -449,10 +454,6 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
isEditing: !!isEditing,
|
||||
onChange: setInstagram
|
||||
})}
|
||||
<ImageInput
|
||||
isEditing={!!isEditing}
|
||||
onImageChange={onImageChangeHandler}
|
||||
/>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
|
|
|
@ -14,78 +14,55 @@ interface IPerformerListTableProps {
|
|||
export const PerformerListTable: React.FC<IPerformerListTableProps> = (
|
||||
props: IPerformerListTableProps
|
||||
) => {
|
||||
function maybeRenderFavoriteHeart(performer: GQL.PerformerDataFragment) {
|
||||
if (!performer.favorite) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<Button disabled className="favorite">
|
||||
<Icon icon="heart" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function renderPerformerImage(performer: GQL.PerformerDataFragment) {
|
||||
const style: React.CSSProperties = {
|
||||
backgroundImage: `url('${performer.image_path}')`,
|
||||
lineHeight: 5,
|
||||
backgroundSize: "contain",
|
||||
display: "inline-block",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat"
|
||||
};
|
||||
|
||||
return (
|
||||
<Link
|
||||
className="performer-list-thumbnail"
|
||||
to={`/performers/${performer.id}`}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderPerformerRow(performer: GQL.PerformerDataFragment) {
|
||||
return (
|
||||
<>
|
||||
<tr>
|
||||
<td>{renderPerformerImage(performer)}</td>
|
||||
<td style={{ textAlign: "left" }}>
|
||||
<Link to={`/performers/${performer.id}`}>
|
||||
<h5 className="text-truncate">{performer.name}</h5>
|
||||
</Link>
|
||||
</td>
|
||||
<td>{performer.aliases ? performer.aliases : ""}</td>
|
||||
<td>{maybeRenderFavoriteHeart(performer)}</td>
|
||||
<td>
|
||||
<Link to={NavUtils.makePerformerScenesUrl(performer)}>
|
||||
<h6>{performer.scene_count}</h6>
|
||||
</Link>
|
||||
</td>
|
||||
<td>{performer.birthdate}</td>
|
||||
<td>{performer.height}</td>
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
}
|
||||
const renderPerformerRow = (performer: GQL.PerformerDataFragment) => (
|
||||
<tr key={performer.id}>
|
||||
<td>
|
||||
<Link
|
||||
to={`/performers/${performer.id}`}
|
||||
>
|
||||
<img className="image-thumbnail" alt={performer.name ?? ""} src={performer.image_path ?? ''} />
|
||||
</Link>
|
||||
</td>
|
||||
<td className="text-left">
|
||||
<Link to={`/performers/${performer.id}`}>
|
||||
<h5 className="text-truncate">{performer.name}</h5>
|
||||
</Link>
|
||||
</td>
|
||||
<td>{performer.aliases ? performer.aliases : ""}</td>
|
||||
<td>{
|
||||
performer.favorite && (
|
||||
<Button disabled className="favorite">
|
||||
<Icon icon="heart" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<Link to={NavUtils.makePerformerScenesUrl(performer)}>
|
||||
<h6>{performer.scene_count}</h6>
|
||||
</Link>
|
||||
</td>
|
||||
<td>{performer.birthdate}</td>
|
||||
<td>{performer.height}</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid">
|
||||
<Table bordered striped>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>Name</th>
|
||||
<th>Aliases</th>
|
||||
<th>Favourite</th>
|
||||
<th>Scene Count</th>
|
||||
<th>Birthdate</th>
|
||||
<th>Height</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{props.performers.map(renderPerformerRow)}</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
</>
|
||||
<div className="grid">
|
||||
<Table bordered striped>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>Name</th>
|
||||
<th>Aliases</th>
|
||||
<th>Favourite</th>
|
||||
<th>Scene Count</th>
|
||||
<th>Birthdate</th>
|
||||
<th>Height</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{props.performers.map(renderPerformerRow)}</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
.performer.image {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
height: 50vh;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
#performer-details {
|
||||
td {
|
||||
padding: 2px 0;
|
||||
|
|
|
@ -63,20 +63,13 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||
function maybeRenderSceneStudioOverlay() {
|
||||
if (!props.scene.studio) return;
|
||||
|
||||
let style: React.CSSProperties = {
|
||||
backgroundImage: `url('${props.scene.studio.image_path}')`
|
||||
};
|
||||
|
||||
let text = "";
|
||||
if (showStudioAsText) {
|
||||
style = {};
|
||||
text = props.scene.studio.name;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="scene-studio-overlay">
|
||||
<Link to={`/studios/${props.scene.studio.id}`} style={style}>
|
||||
{text}
|
||||
<Link to={`/studios/${props.scene.studio.id}`}>
|
||||
{ showStudioAsText
|
||||
? props.scene.studio.name
|
||||
: <img className="image-thumbnail" alt={props.scene.studio.name} src={props.scene.studio.image_path ?? ''} />
|
||||
}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
@ -103,13 +96,14 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||
if (props.scene.performers.length <= 0) return;
|
||||
|
||||
const popoverContent = props.scene.performers.map(performer => (
|
||||
<div className="performer-tag-container" key="performer">
|
||||
<div className="performer-tag-container row" key="performer">
|
||||
<Link
|
||||
to={`/performers/${performer.id}`}
|
||||
className="performer-tag previewable image"
|
||||
style={{ backgroundImage: `url(${performer.image_path})` }}
|
||||
/>
|
||||
<TagLink key={performer.id} performer={performer} />
|
||||
className="performer-tag col m-auto zoom-2"
|
||||
>
|
||||
<img className="image-thumbnail" alt={performer.name ?? ''} src={performer.image_path ?? ''} />
|
||||
</Link>
|
||||
<TagLink key={performer.id} performer={performer} className="d-block" />
|
||||
</div>
|
||||
));
|
||||
|
||||
|
@ -182,7 +176,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||
|
||||
return (
|
||||
<Card
|
||||
className={`zoom-${props.zoomIndex} scene-card`}
|
||||
className={`scene-card zoom-${props.zoomIndex}`}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
|
@ -200,20 +194,18 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||
{maybeRenderSceneStudioOverlay()}
|
||||
<Link
|
||||
to={`/scenes/${props.scene.id}`}
|
||||
className={cx("image", "previewable", { portrait: isPortrait() })}
|
||||
className="scene-card-link"
|
||||
>
|
||||
<div className="video-container">
|
||||
{maybeRenderRatingBanner()}
|
||||
{maybeRenderSceneSpecsOverlay()}
|
||||
<video
|
||||
loop
|
||||
className={cx("preview", { portrait: isPortrait() })}
|
||||
poster={props.scene.paths.screenshot || ""}
|
||||
ref={videoHoverHook.videoEl}
|
||||
>
|
||||
{previewPath ? <source src={previewPath} /> : ""}
|
||||
</video>
|
||||
</div>
|
||||
{maybeRenderRatingBanner()}
|
||||
{maybeRenderSceneSpecsOverlay()}
|
||||
<video
|
||||
loop
|
||||
className={cx('scene-card-video', { portrait: isPortrait() })}
|
||||
poster={props.scene.paths.screenshot || ""}
|
||||
ref={videoHoverHook.videoEl}
|
||||
>
|
||||
{previewPath ? <source src={previewPath} /> : ""}
|
||||
</video>
|
||||
</Link>
|
||||
<div className="card-section">
|
||||
<h5 className="text-truncate">
|
||||
|
@ -222,13 +214,15 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||
: TextUtils.fileNameFromPath(props.scene.path)}
|
||||
</h5>
|
||||
<span>{props.scene.date}</span>
|
||||
<p>
|
||||
{TextUtils.truncate(
|
||||
props.scene.details ?? "",
|
||||
100,
|
||||
"... (continued)"
|
||||
)}
|
||||
</p>
|
||||
{ props.scene.details && (
|
||||
<p>
|
||||
{TextUtils.truncate(
|
||||
props.scene.details,
|
||||
100,
|
||||
"... (continued)"
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{maybeRenderPopoverButtonGroup()}
|
||||
|
|
|
@ -37,13 +37,13 @@ export const PrimaryTags: React.FC<IPrimaryTags> = ({
|
|||
return (
|
||||
<div key={marker.id}>
|
||||
<hr />
|
||||
<div>
|
||||
<div className="row">
|
||||
<Button variant="link" onClick={() => onClickMarker(marker)}>
|
||||
{marker.title}
|
||||
</Button>
|
||||
<Button
|
||||
variant="link"
|
||||
style={{ float: "right" }}
|
||||
className="ml-auto"
|
||||
onClick={() => onEdit(marker)}
|
||||
>
|
||||
Edit
|
||||
|
|
|
@ -153,17 +153,17 @@ export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({
|
|||
</div>
|
||||
</Form.Group>
|
||||
</div>
|
||||
<div className="buttons-container">
|
||||
<div className="buttons-container row">
|
||||
<Button variant="primary" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
<Button type="button" onClick={onClose}>
|
||||
<Button variant="secondary" type="button" onClick={onClose} className="ml-2">
|
||||
Cancel
|
||||
</Button>
|
||||
{editingMarker && (
|
||||
<Button
|
||||
variant="danger"
|
||||
style={{ float: "right", marginRight: "10px" }}
|
||||
className="ml-auto"
|
||||
onClick={() => onDelete()}
|
||||
>
|
||||
Delete
|
||||
|
|
|
@ -125,7 +125,7 @@ export const SceneList: React.FC<ISceneList> = ({
|
|||
}
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return (
|
||||
<div className="grid">
|
||||
<div className="row justify-content-center">
|
||||
{result.data.findScenes.scenes.map(scene =>
|
||||
renderSceneCard(scene, selectedIds, zoomIndex)
|
||||
)}
|
||||
|
|
|
@ -11,77 +11,48 @@ interface ISceneListTableProps {
|
|||
export const SceneListTable: React.FC<ISceneListTableProps> = (
|
||||
props: ISceneListTableProps
|
||||
) => {
|
||||
function renderSceneImage(scene: GQL.SlimSceneDataFragment) {
|
||||
const style: React.CSSProperties = {
|
||||
backgroundImage: `url('${scene.paths.screenshot}')`,
|
||||
lineHeight: 5,
|
||||
backgroundSize: "contain",
|
||||
display: "inline-block",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat"
|
||||
};
|
||||
|
||||
return (
|
||||
<Link
|
||||
className="scene-list-thumbnail"
|
||||
to={`/scenes/${scene.id}`}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderDuration(scene: GQL.SlimSceneDataFragment) {
|
||||
if (scene.file.duration === undefined) {
|
||||
return;
|
||||
}
|
||||
return TextUtils.secondsToTimestamp(scene.file.duration ?? 0);
|
||||
}
|
||||
|
||||
function renderTags(tags: GQL.Tag[]) {
|
||||
return tags.map(tag => (
|
||||
const renderTags = (tags: GQL.Tag[]) => (
|
||||
tags.map(tag => (
|
||||
<Link key={tag.id} to={NavUtils.makeTagScenesUrl(tag)}>
|
||||
<h6>{tag.name}</h6>
|
||||
</Link>
|
||||
));
|
||||
}
|
||||
))
|
||||
);
|
||||
|
||||
function renderPerformers(performers: Partial<GQL.Performer>[]) {
|
||||
return performers.map(performer => (
|
||||
<Link key={performer.id} to={NavUtils.makePerformerScenesUrl(performer)}>
|
||||
<h6>{performer.name}</h6>
|
||||
</Link>
|
||||
));
|
||||
}
|
||||
const renderPerformers = (performers: Partial<GQL.Performer>[]) => (
|
||||
performers.map(performer => (
|
||||
<Link key={performer.id} to={NavUtils.makePerformerScenesUrl(performer)} />
|
||||
))
|
||||
);
|
||||
|
||||
function renderStudio(studio: Partial<GQL.Studio> | undefined) {
|
||||
if (studio) {
|
||||
return (
|
||||
<Link to={NavUtils.makeStudioScenesUrl(studio)}>
|
||||
<h6>{studio.name}</h6>
|
||||
const renderSceneRow = (scene: GQL.SlimSceneDataFragment) => (
|
||||
<tr key={scene.id}>
|
||||
<td>
|
||||
<Link
|
||||
to={`/scenes/${scene.id}`}
|
||||
>
|
||||
<img className="image-thumbnail" alt={scene.title ?? ''} src={scene.paths.screenshot ?? ''} />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSceneRow(scene: GQL.SlimSceneDataFragment) {
|
||||
return (
|
||||
<tr key={scene.id}>
|
||||
<td>{renderSceneImage(scene)}</td>
|
||||
<td style={{ textAlign: "left" }}>
|
||||
<Link to={`/scenes/${scene.id}`}>
|
||||
<h5 className="text-truncate">
|
||||
{scene.title ?? TextUtils.fileNameFromPath(scene.path)}
|
||||
</h5>
|
||||
</Link>
|
||||
</td>
|
||||
<td>{scene.rating ? scene.rating : ""}</td>
|
||||
<td>{renderDuration(scene)}</td>
|
||||
<td>{renderTags(scene.tags)}</td>
|
||||
<td>{renderPerformers(scene.performers)}</td>
|
||||
<td>{renderStudio(scene.studio ?? undefined)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
</td>
|
||||
<td className="text-left">
|
||||
<Link to={`/scenes/${scene.id}`}>
|
||||
<h5 className="text-truncate">
|
||||
{scene.title ?? TextUtils.fileNameFromPath(scene.path)}
|
||||
</h5>
|
||||
</Link>
|
||||
</td>
|
||||
<td>{scene.rating ? scene.rating : ""}</td>
|
||||
<td>{scene.file.duration && TextUtils.secondsToTimestamp(scene.file.duration) }</td>
|
||||
<td>{renderTags(scene.tags)}</td>
|
||||
<td>{renderPerformers(scene.performers)}</td>
|
||||
<td>{ scene.studio && (
|
||||
<Link to={NavUtils.makeStudioScenesUrl(scene.studio)}>
|
||||
<h6>{scene.studio.name}</h6>
|
||||
</Link>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="grid">
|
||||
|
|
|
@ -194,7 +194,7 @@ export class ScenePlayerImpl extends React.Component<
|
|||
return (
|
||||
<ReactJWPlayer
|
||||
playerId={JWUtils.playerID}
|
||||
playerScript="http://192.168.1.65:9999/jwplayer/jwplayer.js"
|
||||
playerScript="http://localhost:9999/jwplayer/jwplayer.js"
|
||||
customProps={config}
|
||||
onReady={this.onReady}
|
||||
onSeeked={this.onSeeked}
|
||||
|
|
|
@ -264,10 +264,11 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (
|
|||
<Form.Label>Rating</Form.Label>
|
||||
<Form.Control
|
||||
as="select"
|
||||
value={rating}
|
||||
onChange={(event: any) => setRating(event.target.value)}
|
||||
>
|
||||
{["", "1", "2", "3", "4", "5"].map(opt => (
|
||||
<option selected={opt === rating} value={opt}>
|
||||
<option key={opt} value={opt}>
|
||||
{opt}
|
||||
</option>
|
||||
))}
|
||||
|
|
|
@ -13,12 +13,33 @@
|
|||
}
|
||||
}
|
||||
|
||||
.grid {
|
||||
.row {
|
||||
.scene-card {
|
||||
padding-bottom: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.scene-card-link {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card-section {
|
||||
margin-bottom: 0;
|
||||
padding: .5rem 1rem 0 1rem;
|
||||
}
|
||||
|
||||
.card-select {
|
||||
left: .5rem;
|
||||
margin-top: -12px;
|
||||
opacity: .5;
|
||||
padding-left: 15px;
|
||||
position: absolute;
|
||||
top: .7rem;
|
||||
width: 1.2rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.performer-tag-container {
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
|
@ -105,3 +126,24 @@
|
|||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-card {
|
||||
padding: .5rem;
|
||||
|
||||
&-header {
|
||||
height: 150px;
|
||||
line-height: 150px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-image {
|
||||
max-height: 150px;
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
width: 320px;
|
||||
|
||||
@media (max-width: 576px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
.studio-card {
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.studio-image {
|
||||
height: 150px;
|
||||
line-height: 150px;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
|
@ -25,126 +25,71 @@ code {
|
|||
margin: $pt-grid-size $pt-grid-size 0 0;
|
||||
padding: 0;
|
||||
|
||||
.performer-card,
|
||||
.studio-card {
|
||||
min-width: 185px;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
&.wall {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
& .performer-list-thumbnail {
|
||||
height: 100px;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
& .scene-list-thumbnail {
|
||||
min-height: 50px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
table td {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 0 0 10px 10px;
|
||||
overflow: hidden;
|
||||
.card {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
&.zoom-0 {
|
||||
width: 15rem;
|
||||
@media (min-width: 576px) {
|
||||
.zoom-0 {
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
.previewable {
|
||||
max-height: 11.25rem;
|
||||
}
|
||||
.zoom-1 {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.previewable.portrait {
|
||||
max-height: 11.25rem;
|
||||
}
|
||||
}
|
||||
.zoom-2 {
|
||||
width: 30rem;
|
||||
}
|
||||
|
||||
&.zoom-1 {
|
||||
width: 20rem;
|
||||
|
||||
.previewable {
|
||||
max-height: 15rem;
|
||||
}
|
||||
|
||||
.previewable.portrait {
|
||||
height: 15rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.zoom-2 {
|
||||
width: 30rem;
|
||||
|
||||
.previewable {
|
||||
max-height: 22.5rem;
|
||||
}
|
||||
|
||||
.previewable.portrait {
|
||||
height: 22.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.zoom-3 {
|
||||
width: 40rem;
|
||||
|
||||
.previewable {
|
||||
max-height: 30rem;
|
||||
}
|
||||
|
||||
.previewable.portrait {
|
||||
height: 30rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-select {
|
||||
margin-top: -12px;
|
||||
opacity: .5;
|
||||
padding-left: 15px;
|
||||
position: absolute;
|
||||
width: 1.2rem;
|
||||
z-index: 1;
|
||||
}
|
||||
.zoom-3 {
|
||||
width: 40rem;
|
||||
}
|
||||
}
|
||||
|
||||
.previewable {
|
||||
display: block;
|
||||
line-height: 0;
|
||||
margin: -20px 0 0 -20px;
|
||||
max-height: 240px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: calc(100% + 40px);
|
||||
}
|
||||
|
||||
.previewable.portrait {
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
height: 100%;
|
||||
.scene-card-video {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
|
||||
.zoom-0 {
|
||||
height: 11.25rem;
|
||||
}
|
||||
|
||||
.zoom-1 {
|
||||
height: 15rem;
|
||||
}
|
||||
|
||||
.zoom-2 {
|
||||
height: 22.5rem;
|
||||
}
|
||||
|
||||
.zoom-3 {
|
||||
height: 30rem;
|
||||
}
|
||||
}
|
||||
|
||||
video.preview {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
.image-thumbnail {
|
||||
height: 100px;
|
||||
min-width: 50px;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
object-position: center 20%;
|
||||
}
|
||||
|
||||
video.preview.portrait {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
.card-image {
|
||||
height: 30rem;
|
||||
min-width: 11.25rem;
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.filter-item,
|
||||
|
@ -204,16 +149,6 @@ video.preview.portrait {
|
|||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.card-section {
|
||||
padding: 10px 0 0 0;
|
||||
|
||||
&.centered {
|
||||
display: flex;
|
||||
flex-flow: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.rating-5 {
|
||||
background: #ff2f39;
|
||||
}
|
||||
|
@ -264,26 +199,27 @@ video.preview.portrait {
|
|||
.scene-studio-overlay {
|
||||
display: block;
|
||||
font-weight: 900;
|
||||
height: 20%;
|
||||
height: 10%;
|
||||
max-width: 40%;
|
||||
opacity: .75;
|
||||
position: absolute;
|
||||
right: .7rem;
|
||||
top: .7rem;
|
||||
width: 40%;
|
||||
z-index: 9;
|
||||
|
||||
.image-thumbnail {
|
||||
height: auto;
|
||||
max-height: 50px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
background-position: right top;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
color: $text-color;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
letter-spacing: -.03rem;
|
||||
text-align: right;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 0 3px #000;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -468,6 +404,12 @@ video.preview.portrait {
|
|||
.form-control {
|
||||
&-plaintext {
|
||||
color: $text-color;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
option {
|
||||
color: black;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: transparent;
|
||||
|
@ -496,6 +438,7 @@ video.preview.portrait {
|
|||
|
||||
.image-input {
|
||||
margin-bottom: 0;
|
||||
margin-left: .5rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
|
@ -504,7 +447,6 @@ video.preview.portrait {
|
|||
}
|
||||
|
||||
[type=file] {
|
||||
cursor: inherit;
|
||||
display: block;
|
||||
filter: alpha(opacity=0);
|
||||
font-size: 999px;
|
||||
|
@ -515,6 +457,11 @@ video.preview.portrait {
|
|||
right: 0;
|
||||
text-align: right;
|
||||
top: 0;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -565,3 +512,7 @@ video.preview.portrait {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
white-space: "pre-wrap";
|
||||
}
|
||||
|
|
|
@ -99,6 +99,16 @@ hr {
|
|||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
td {
|
||||
padding: .25rem .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
max-width: inherit;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue