This commit is contained in:
Infinite 2020-01-30 23:34:20 +01:00
parent c1ce6d539d
commit 4a32f90382
30 changed files with 321 additions and 397 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'
})
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -62,6 +62,7 @@
color: #444;
font-weight: 700;
left: 0;
line-height: 1;
overflow: hidden;
padding: 5px;
position: absolute;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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%;
}
}
}

View File

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

View File

@ -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";
}

View File

@ -99,6 +99,16 @@ hr {
}
}
.table {
td {
padding: .25rem .75rem;
}
}
.popover {
max-width: inherit;
}
.card {
border: none;
}