Responsive styles for portrait orientation phones

This commit is contained in:
Infinite
2020-01-29 22:22:01 +01:00
parent 1ccf8d1586
commit 3f9f32c356
29 changed files with 273 additions and 206 deletions

View File

@@ -49,7 +49,7 @@
"function-whitespace-after": "always", "function-whitespace-after": "always",
"length-zero-no-unit": true, "length-zero-no-unit": true,
"max-empty-lines": 1, "max-empty-lines": 1,
"max-nesting-depth": 3, "max-nesting-depth": 4,
"max-line-length": 100, "max-line-length": 100,
"media-feature-colon-space-after": "always", "media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never", "media-feature-colon-space-before": "never",

View File

@@ -22,7 +22,7 @@ export const App: React.FC = () => (
<ErrorBoundary> <ErrorBoundary>
<ToastProvider> <ToastProvider>
<MainNavbar /> <MainNavbar />
<div className="main"> <div className="main container-fluid">
<Switch> <Switch>
<Route exact path="/" component={Stats} /> <Route exact path="/" component={Stats} />
<Route path="/scenes" component={Scenes} /> <Route path="/scenes" component={Scenes} />

View File

@@ -64,13 +64,18 @@ export const MainNavbar: React.FC = () => {
); );
return ( return (
<Navbar fixed="top" variant="dark" bg="dark"> <Navbar fixed="top" variant="dark" bg="dark" className="top-nav">
<Navbar.Brand as="div"> <Navbar.Brand as="div">
<Link to="/"> <Link to="/">
<Button className="minimal">Stash</Button> <Button className="minimal brand-link d-none d-sm-inline-block">
Stash
</Button>
<Button className="minimal brand-icon d-inline d-sm-none">
<img src="http://192.168.1.65:9999/favicon.ico" alt="" />
</Button>
</Link> </Link>
</Navbar.Brand> </Navbar.Brand>
<Nav className="mr-auto"> <Nav className="mr-md-auto">
{menuItems.map(i => ( {menuItems.map(i => (
<LinkContainer <LinkContainer
activeClassName="active" activeClassName="active"
@@ -80,13 +85,15 @@ export const MainNavbar: React.FC = () => {
> >
<Button className="minimal"> <Button className="minimal">
<Icon icon={i.icon} /> <Icon icon={i.icon} />
<span>{i.text}</span> <span className="d-none d-sm-inline">{i.text}</span>
</Button> </Button>
</LinkContainer> </LinkContainer>
))} ))}
</Nav> </Nav>
<Nav> <Nav>
{newButton} <div className="d-none d-sm-block">
{newButton}
</div>
<LinkContainer exact to="/settings"> <LinkContainer exact to="/settings">
<Button className="minimal"> <Button className="minimal">
<Icon icon="cog" /> <Icon icon="cog" />

View File

@@ -134,7 +134,7 @@ export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProp
<InputGroup.Append> <InputGroup.Append>
<DropdownButton id="parser-field-select" title="Add Field"> <DropdownButton id="parser-field-select" title="Add Field">
{validFields.map(item => ( {validFields.map(item => (
<Dropdown.Item onSelect={() => addParserField(item)}> <Dropdown.Item key={item.field} onSelect={() => addParserField(item)}>
<span>{item.field}</span> <span>{item.field}</span>
<span className="ml-auto">{item.helperText}</span> <span className="ml-auto">{item.helperText}</span>
</Dropdown.Item> </Dropdown.Item>
@@ -184,7 +184,7 @@ export const ParserInput: React.FC<IParserInputProps> = (props: IParserInputProp
<Form.Group> <Form.Group>
<DropdownButton variant="secondary" id="recipe-select" title="Select Parser Recipe"> <DropdownButton variant="secondary" id="recipe-select" title="Select Parser Recipe">
{builtInRecipes.map(item => ( {builtInRecipes.map(item => (
<Dropdown.Item onSelect={() => setParserRecipe(item)}> <Dropdown.Item key={item.pattern} onSelect={() => setParserRecipe(item)}>
<span>{item.pattern}</span> <span>{item.pattern}</span>
<span className="mr-auto">{item.description}</span> <span className="mr-auto">{item.description}</span>
</Dropdown.Item> </Dropdown.Item>

View File

@@ -20,7 +20,8 @@ export const ShowFields = (props: IShowFieldsProps) => {
} }
const fieldRows = [...props.fields.entries()].map(([label, enabled]) => ( const fieldRows = [...props.fields.entries()].map(([label, enabled]) => (
<div <Button
className="minimal d-block"
key={label} key={label}
onClick={() => { onClick={() => {
handleClick(label); handleClick(label);
@@ -28,7 +29,7 @@ export const ShowFields = (props: IShowFieldsProps) => {
> >
<Icon icon={enabled ? "check" : "times"} /> <Icon icon={enabled ? "check" : "times"} />
<span>{label}</span> <span>{label}</span>
</div> </Button>
)); ));
return ( return (

View File

@@ -174,7 +174,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
<Form.Group id="database-path"> <Form.Group id="database-path">
<h6>Database Path</h6> <h6>Database Path</h6>
<Form.Control <Form.Control
className="col-6" className="col col-sm-6"
defaultValue={databasePath} defaultValue={databasePath}
onChange={(e: any) => setDatabasePath(e.target.value)} onChange={(e: any) => setDatabasePath(e.target.value)}
/> />
@@ -186,7 +186,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
<Form.Group id="generated-path"> <Form.Group id="generated-path">
<h6>Generated Path</h6> <h6>Generated Path</h6>
<Form.Control <Form.Control
className="col-6" className="col col-sm-6"
defaultValue={generatedPath} defaultValue={generatedPath}
onChange={(e: any) => setGeneratedPath(e.target.value)} onChange={(e: any) => setGeneratedPath(e.target.value)}
/> />
@@ -203,7 +203,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
&& excludes.map((regexp, i) => ( && excludes.map((regexp, i) => (
<InputGroup> <InputGroup>
<Form.Control <Form.Control
className="col-6" className="col col-sm-6"
value={regexp} value={regexp}
onChange={(e: any) => onChange={(e: any) =>
excludeRegexChanged(i, e.target.value) excludeRegexChanged(i, e.target.value)
@@ -246,7 +246,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
<Form.Group id="transcode-size"> <Form.Group id="transcode-size">
<h6>Maximum transcode size</h6> <h6>Maximum transcode size</h6>
<Form.Control <Form.Control
className="col-6" className="col col-sm-6"
as="select" as="select"
onChange={(event: React.FormEvent<HTMLSelectElement>) => onChange={(event: React.FormEvent<HTMLSelectElement>) =>
setMaxTranscodeSize(translateQuality(event.currentTarget.value)) setMaxTranscodeSize(translateQuality(event.currentTarget.value))
@@ -266,7 +266,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
<Form.Group id="streaming-transcode-size"> <Form.Group id="streaming-transcode-size">
<h6>Maximum streaming transcode size</h6> <h6>Maximum streaming transcode size</h6>
<Form.Control <Form.Control
className="col-6" className="col col-sm-6"
as="select" as="select"
onChange={(event: React.FormEvent<HTMLSelectElement>) => onChange={(event: React.FormEvent<HTMLSelectElement>) =>
setMaxStreamingTranscodeSize( setMaxStreamingTranscodeSize(
@@ -294,7 +294,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
<Form.Group id="username"> <Form.Group id="username">
<h6>Username</h6> <h6>Username</h6>
<Form.Control <Form.Control
className="col-6" className="col col-sm-6"
defaultValue={username} defaultValue={username}
onChange={(e: React.FormEvent<HTMLInputElement>) => onChange={(e: React.FormEvent<HTMLInputElement>) =>
setUsername(e.currentTarget.value) setUsername(e.currentTarget.value)
@@ -307,7 +307,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
<Form.Group id="password"> <Form.Group id="password">
<h6>Password</h6> <h6>Password</h6>
<Form.Control <Form.Control
className="col-6" className="col col-sm-6"
type="password" type="password"
defaultValue={password} defaultValue={password}
onChange={(e: React.FormEvent<HTMLInputElement>) => onChange={(e: React.FormEvent<HTMLInputElement>) =>
@@ -326,7 +326,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
<Form.Group id="log-file"> <Form.Group id="log-file">
<h6>Log file</h6> <h6>Log file</h6>
<Form.Control <Form.Control
className="col-6" className="col col-sm-6"
defaultValue={logFile} defaultValue={logFile}
onChange={(e: React.FormEvent<HTMLInputElement>) => onChange={(e: React.FormEvent<HTMLInputElement>) =>
setLogFile(e.currentTarget.value) setLogFile(e.currentTarget.value)
@@ -354,7 +354,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
<Form.Group id="log-level"> <Form.Group id="log-level">
<h6>Log Level</h6> <h6>Log Level</h6>
<Form.Control <Form.Control
className="col-6" className="col col-sm-6"
as="select" as="select"
onChange={(event: React.FormEvent<HTMLSelectElement>) => onChange={(event: React.FormEvent<HTMLSelectElement>) =>
setLogLevel(event.currentTarget.value) setLogLevel(event.currentTarget.value)

View File

@@ -103,7 +103,7 @@ export const SettingsInterfacePanel: React.FC = () => {
<Form.Group id="max-loop-duration"> <Form.Group id="max-loop-duration">
<Form.Label>Maximum loop duration</Form.Label> <Form.Label>Maximum loop duration</Form.Label>
<DurationInput <DurationInput
className="col-4" className="col col-sm-4"
numericValue={maximumLoopDuration} numericValue={maximumLoopDuration}
onValueChange={duration => setMaximumLoopDuration(duration)} onValueChange={duration => setMaximumLoopDuration(duration)}
/> />
@@ -129,7 +129,7 @@ export const SettingsInterfacePanel: React.FC = () => {
value={css} value={css}
onChange={(e: any) => setCSS(e.target.value)} onChange={(e: any) => setCSS(e.target.value)}
rows={16} rows={16}
className="col-6" className="col col-sm-6"
></Form.Control> ></Form.Control>
<Form.Text className="text-muted"> <Form.Text className="text-muted">
Page must be reloaded for changes to take effect. Page must be reloaded for changes to take effect.

View File

@@ -40,7 +40,7 @@ const LogElement: React.FC<ILogElementProps> = ({ logEntry }) => {
<div className="row"> <div className="row">
<span className="log-time">{logEntry.time}</span> <span className="log-time">{logEntry.time}</span>
<span className={`${levelClass(logEntry.level)}`}>{level}</span> <span className={`${levelClass(logEntry.level)}`}>{level}</span>
<span className="col-9">{logEntry.message}</span> <span className="col col-sm-9">{logEntry.message}</span>
</div> </div>
); );
}; };
@@ -99,9 +99,9 @@ export const SettingsLogsPanel: React.FC = () => {
<> <>
<h4>Logs</h4> <h4>Logs</h4>
<Form.Row id="log-level"> <Form.Row id="log-level">
<Form.Label className="col-2">Log Level</Form.Label> <Form.Label className="col-6 col-sm-2">Log Level</Form.Label>
<Form.Control <Form.Control
className="col-2" className="col-6 col-sm-2"
as="select" as="select"
defaultValue={logLevel} defaultValue={logLevel}
onChange={event => setLogLevel(event.currentTarget.value)} onChange={event => setLogLevel(event.currentTarget.value)}

View File

@@ -43,7 +43,7 @@ export const DetailsEditNavbar: React.FC<IProps> = (props: IProps) => {
function renderDeleteButton() { function renderDeleteButton() {
if (props.isNew || props.isEditing) return; if (props.isNew || props.isEditing) return;
return ( return (
<Button variant="danger" className="delete" onClick={() => setIsDeleteAlertOpen(true)}> <Button variant="danger" className="delete d-none d-sm-block" onClick={() => setIsDeleteAlertOpen(true)}>
Delete Delete
</Button> </Button>
); );

View File

@@ -65,7 +65,7 @@ export const FolderSelect: React.FC<IProps> = (props: IProps) => {
<ul className="folder-list"> <ul className="folder-list">
{selectableDirectories.map(path => { {selectableDirectories.map(path => {
return ( return (
<li className="folder-item"> <li key={path} className="folder-item">
<Button <Button
variant="link" variant="link"
key={path} key={path}

View File

@@ -10,46 +10,44 @@ export const Stats: React.FC = () => {
if (error) return <span>error.message</span>; if (error) return <span>error.message</span>;
return ( return (
<div className="w-75 m-auto"> <div className="mt-5">
<nav className="w-75 m-auto d-flex flex-row"> <div className="col col-sm-8 m-sm-auto">
<div className="flex-grow-1"> <nav className="col col-sm-8 m-sm-auto row">
<div> <div className="flex-grow-1">
<p className="heading">Scenes</p> <div>
<p className="title">{data.stats.scene_count}</p> <p className="heading">Scenes</p>
<p className="title">{data.stats.scene_count}</p>
</div>
</div> </div>
</div> <div className="flex-grow-1">
<div className="flex-grow-1"> <div>
<div> <p className="heading">Galleries</p>
<p className="heading">Galleries</p> <p className="title">{data.stats.gallery_count}</p>
<p className="title">{data.stats.gallery_count}</p> </div>
</div> </div>
</div> <div className="flex-grow-1">
<div className="flex-grow-1"> <div>
<div> <p className="heading">Performers</p>
<p className="heading">Performers</p> <p className="title">{data.stats.performer_count}</p>
<p className="title">{data.stats.performer_count}</p> </div>
</div> </div>
</div> <div className="flex-grow-1">
<div className="flex-grow-1"> <div>
<div> <p className="heading">Studios</p>
<p className="heading">Studios</p> <p className="title">{data.stats.studio_count}</p>
<p className="title">{data.stats.studio_count}</p> </div>
</div> </div>
</div> <div className="flex-grow-1">
<div className="flex-grow-1"> <div>
<div> <p className="heading">Tags</p>
<p className="heading">Tags</p> <p className="title">{data.stats.tag_count}</p>
<p className="title">{data.stats.tag_count}</p> </div>
</div> </div>
</div> </nav>
</nav>
<h5>Notes</h5> <h5>Notes</h5>
<pre> <em>This is still an early version, some things are still a work in progress.</em>
{` </div>
This is still an early version, some things are still a work in progress.
`}
</pre>
</div> </div>
); );
}; };

View File

@@ -149,7 +149,7 @@ export const Studio: React.FC = () => {
return ( return (
<div className="row"> <div className="row">
<div className={cx('studio-details', { 'col-4': !isNew, 'col-8': isNew})}> <div className={cx('studio-details', { 'col ml-sm-5': !isNew, 'col-8': isNew})}>
{ isNew && <h2>Add Studio</h2> } { isNew && <h2>Add Studio</h2> }
<img className="logo" alt={name} src={imagePreview} /> <img className="logo" alt={name} src={imagePreview} />
<Table id="performer-details" style={{ width: "100%" }}> <Table id="performer-details" style={{ width: "100%" }}>
@@ -180,7 +180,7 @@ export const Studio: React.FC = () => {
/> />
</div> </div>
{ !isNew && ( { !isNew && (
<div className="col-8"> <div className="col-12 col-sm-8">
<StudioScenesPanel studio={studio} /> <StudioScenesPanel studio={studio} />
</div> </div>
)} )}

View File

@@ -1,6 +1,4 @@
.studio-details { .studio-details {
padding-left: 4rem;
.logo { .logo {
margin: 4rem 0; margin: 4rem 0;
width: 100%; width: 100%;

View File

@@ -93,6 +93,10 @@
position: relative; position: relative;
width: 20%; width: 20%;
@media (max-width: 576px) {
width: 100%;
}
video, video,
img { img {
height: 100%; height: 100%;

View File

@@ -104,7 +104,7 @@ export const WallPanel: React.FC<IWallPanelProps> = (
return ( return (
<> <>
<div className={`wall-overlay ${overlayClassName}`} /> <div className={`wall-overlay ${overlayClassName}`} />
<div className="wall grid"> <div className="wall grid row">
{maybeRenderScenes()} {maybeRenderScenes()}
{maybeRenderSceneMarkers()} {maybeRenderSceneMarkers()}
</div> </div>

View File

@@ -228,7 +228,7 @@ export const ListFilter: React.FC<IListFilterProps> = (
if (props.onChangeZoom) { if (props.onChangeZoom) {
return ( return (
<Form.Control <Form.Control
className="zoom-slider col-1" className="zoom-slider col-1 d-none d-sm-block"
type="range" type="range"
min={0} min={0}
max={3} max={3}
@@ -249,14 +249,14 @@ export const ListFilter: React.FC<IListFilterProps> = (
placeholder="Search..." placeholder="Search..."
defaultValue={props.filter.searchTerm} defaultValue={props.filter.searchTerm}
onChange={onChangeQuery} onChange={onChangeQuery}
className="filter-item" className="filter-item col-5 col-sm-2"
style={{ width: "inherit" }} style={{ width: "inherit" }}
/> />
<Form.Control <Form.Control
as="select" as="select"
onChange={onChangePageSize} onChange={onChangePageSize}
value={props.filter.itemsPerPage.toString()} value={props.filter.itemsPerPage.toString()}
className="filter-item col-1" className="filter-item col-1 d-none d-sm-inline"
> >
{PAGE_SIZE_OPTIONS.map(s => ( {PAGE_SIZE_OPTIONS.map(s => (
<option value={s} key={s}>{s}</option> <option value={s} key={s}>{s}</option>
@@ -298,13 +298,13 @@ export const ListFilter: React.FC<IListFilterProps> = (
editingCriterion={editingCriterion} editingCriterion={editingCriterion}
/> />
<ButtonGroup className="filter-item"> <ButtonGroup className="filter-item d-none d-sm-inline-flex">
{renderDisplayModeOptions()} {renderDisplayModeOptions()}
</ButtonGroup> </ButtonGroup>
{maybeRenderZoom()} {maybeRenderZoom()}
<ButtonGroup className="filter-item">{renderMore()}</ButtonGroup> <ButtonGroup className="filter-item d-none d-sm-inline-flex">{renderMore()}</ButtonGroup>
</div> </div>
<div <div
style={{ style={{

View File

@@ -37,9 +37,20 @@ export const Pagination: React.FC<IPaginationProps> = ({
i => startPage + i i => startPage + i
); );
const calculatePageClass = (buttonPage:number) => {
if(pages.length <= 4) return '';
if(currentPage === 1 && buttonPage <= 4) return '';
const maxPage = pages[pages.length - 1];
if(currentPage === maxPage && buttonPage > (maxPage - 3)) return '';
if(Math.abs(buttonPage - currentPage) <= 1) return '';
return 'd-none d-sm-block'
}
const pageButtons = pages.map((page: number) => ( const pageButtons = pages.map((page: number) => (
<Button <Button
variant="secondary" variant="secondary"
className={calculatePageClass(page)}
key={page} key={page}
active={currentPage === page} active={currentPage === page}
onClick={() => onChangePage(page)} onClick={() => onChangePage(page)}
@@ -54,9 +65,11 @@ export const Pagination: React.FC<IPaginationProps> = ({
return ( return (
<ButtonGroup className="filter-container pagination"> <ButtonGroup className="filter-container pagination">
<Button variant="secondary" disabled={currentPage === 1} onClick={() => onChangePage(1)}> <Button variant="secondary" disabled={currentPage === 1} onClick={() => onChangePage(1)}>
First <span className="d-none d-sm-inline">First</span>
<span className="d-inline d-sm-none">&#x300a;</span>
</Button> </Button>
<Button <Button
className="d-none d-sm-block"
variant="secondary" variant="secondary"
disabled={currentPage === 1} disabled={currentPage === 1}
onClick={() => onChangePage(currentPage - 1)} onClick={() => onChangePage(currentPage - 1)}
@@ -65,6 +78,7 @@ export const Pagination: React.FC<IPaginationProps> = ({
</Button> </Button>
{pageButtons} {pageButtons}
<Button <Button
className="d-none d-sm-block"
variant="secondary" variant="secondary"
disabled={currentPage === totalPages} disabled={currentPage === totalPages}
onClick={() => onChangePage(currentPage + 1)} onClick={() => onChangePage(currentPage + 1)}
@@ -76,7 +90,8 @@ export const Pagination: React.FC<IPaginationProps> = ({
disabled={currentPage === totalPages} disabled={currentPage === totalPages}
onClick={() => onChangePage(totalPages)} onClick={() => onChangePage(totalPages)}
> >
Last <span className="d-none d-sm-inline">Last</span>
<span className="d-inline d-sm-none">&#x300b;</span>
</Button> </Button>
</ButtonGroup> </ButtonGroup>
); );

View File

@@ -164,7 +164,7 @@ export const Performer: React.FC = () => {
} }
const renderIcons = () => ( const renderIcons = () => (
<span className="name-icons"> <span className="name-icons d-block d-sm-inline">
<Button <Button
className={cx('minimal', performer.favorite ? "favorite" : "not-favorite")} className={cx('minimal', performer.favorite ? "favorite" : "not-favorite")}
onClick={() => setFavorite(!performer.favorite)} onClick={() => setFavorite(!performer.favorite)}
@@ -217,19 +217,26 @@ export const Performer: React.FC = () => {
return ( return (
<div id="performer-page" className="row"> <div id="performer-page" className="row">
<div className="image-container col-4 offset-1"> <div className="image-container col-sm-4 offset-sm-1 d-none d-sm-block">
<Button variant="link" onClick={() => setLightboxIsOpen(true)}> <Button variant="link" onClick={() => setLightboxIsOpen(true)}>
<img className="performer" src={imagePreview} alt="Performer" /> <img className="performer" src={imagePreview} alt="Performer" />
</Button> </Button>
</div> </div>
<div className="col-6"> <div className="col col-sm-6">
<div className="performer-head"> <div className="row">
<h2> <div className="image-container col-6 d-block d-sm-none">
{performer.name} <Button variant="link" onClick={() => setLightboxIsOpen(true)}>
{renderIcons()} <img className="performer" src={imagePreview} alt="Performer" />
</h2> </Button>
{maybeRenderAliases()} </div>
{maybeRenderAge()} <div className="performer-head col-6 col-sm-12">
<h2>
{performer.name}
{renderIcons()}
</h2>
{maybeRenderAliases()}
{maybeRenderAge()}
</div>
</div> </div>
<div className="performer-body"> <div className="performer-body">
<div className="performer-tabs">{renderTabs()}</div> <div className="performer-tabs">{renderTabs()}</div>

View File

@@ -55,7 +55,7 @@ export const PerformerList: React.FC = () => {
} }
if (filter.displayMode === DisplayMode.Grid) { if (filter.displayMode === DisplayMode.Grid) {
return ( return (
<div className="grid"> <div className="grid row">
{result.data.findPerformers.performers.map(p => ( {result.data.findPerformers.performers.map(p => (
<PerformerCard key={p.id} performer={p} /> <PerformerCard key={p.id} performer={p} />
))} ))}

View File

@@ -188,7 +188,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
> >
<Form.Control <Form.Control
type="checkbox" type="checkbox"
className="card-select" className="card-select d-none d-sm-block"
checked={props.selected} checked={props.selected}
onChange={() => props.onSelectedChanged(!props.selected, shiftKey)} onChange={() => props.onSelectedChanged(!props.selected, shiftKey)}
onClick={(event: React.MouseEvent<HTMLInputElement, MouseEvent>) => { onClick={(event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {

View File

@@ -53,7 +53,7 @@ export const PrimaryTags: React.FC<IPrimaryTags> = ({ sceneMarkers, onClickMarke
}); });
return ( return (
<Card className="primary-card col-3" key={id}> <Card className="primary-card col-12 col-sm-3" key={id}>
<h3>{primaries[id].name}</h3> <h3>{primaries[id].name}</h3>
<Card.Body className="primary-card-body"> <Card.Body className="primary-card-body">
{ markers } { markers }

View File

@@ -55,7 +55,7 @@ export const Scene: React.FC = () => {
timestamp={timestamp} timestamp={timestamp}
autoplay={autoplay} autoplay={autoplay}
/> />
<div id="details-container"> <div id="details-container" className="col col-sm-9 m-sm-auto">
<Tabs id="scene-tabs" mountOnEnter> <Tabs id="scene-tabs" mountOnEnter>
<Tab eventKey="scene-details-panel" title="Details"> <Tab eventKey="scene-details-panel" title="Details">
<SceneDetailPanel scene={scene} /> <SceneDetailPanel scene={scene} />
@@ -83,7 +83,7 @@ export const Scene: React.FC = () => {
<Tab className="file-info-panel" eventKey="scene-file-info-panel" title="File Info"> <Tab className="file-info-panel" eventKey="scene-file-info-panel" title="File Info">
<SceneFileInfoPanel scene={scene} /> <SceneFileInfoPanel scene={scene} />
</Tab> </Tab>
<Tab eventKey="scene-edit-panel" title="Edit"> <Tab eventKey="scene-edit-panel" title="Edit" tabClassName="d-none d-sm-block">
<SceneEditPanel <SceneEditPanel
scene={scene} scene={scene}
onUpdate={newScene => setScene(newScene)} onUpdate={newScene => setScene(newScene)}

View File

@@ -34,9 +34,9 @@ export const SceneDetailPanel: React.FC<ISceneDetailProps> = props => {
return ( return (
<div className="row"> <div className="row">
<h1 className="col scene-header"> <h3 className="col scene-header text-truncate">
{props.scene.title ?? TextUtils.fileNameFromPath(props.scene.path)} {props.scene.title ?? TextUtils.fileNameFromPath(props.scene.path)}
</h1> </h3>
<div className="col-6 scene-details"> <div className="col-6 scene-details">
<h4>{props.scene.date ?? ''}</h4> <h4>{props.scene.date ?? ''}</h4>
{props.scene.rating ? <h6>Rating: {props.scene.rating}</h6> : ""} {props.scene.rating ? <h6>Rating: {props.scene.rating}</h6> : ""}

View File

@@ -1,5 +1,4 @@
import React from "react"; import React from "react";
import { Table } from "react-bootstrap";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { TextUtils } from "src/utils"; import { TextUtils } from "src/utils";
@@ -12,10 +11,10 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
) => { ) => {
function renderChecksum() { function renderChecksum() {
return ( return (
<tr> <div className="row">
<td>Checksum</td> <span className="col-4">Checksum</span>
<td>{props.scene.checksum}</td> <span className="col-8 text-truncate">{props.scene.checksum}</span>
</tr> </div>
); );
} }
@@ -24,25 +23,25 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
scene: { path } scene: { path }
} = props; } = props;
return ( return (
<tr> <div className="row">
<td>Path</td> <span className="col-4">Path</span>
<td> <span className="col-8 text-truncate">
<a href={`file://${path}`}>{`file://${props.scene.path}`}</a>{" "} <a href={`file://${path}`}>{`file://${props.scene.path}`}</a>{" "}
</td> </span>
</tr> </div>
); );
} }
function renderStream() { function renderStream() {
return ( return (
<tr> <div className="row">
<td>Stream</td> <span className="col-4">Stream</span>
<td> <span className="col-8 text-truncate">
<a href={props.scene.paths.stream ?? ""}> <a href={props.scene.paths.stream ?? ""}>
{props.scene.paths.stream} {props.scene.paths.stream}
</a>{" "} </a>{" "}
</td> </span>
</tr> </div>
); );
} }
@@ -51,12 +50,12 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
return; return;
} }
return ( return (
<tr> <div className="row">
<td>File Size</td> <span className="col-4">File Size</span>
<td> <span className="col-8 text-truncate">
{TextUtils.fileSize(parseInt(props.scene.file.size ?? "0", 10))} {TextUtils.fileSize(parseInt(props.scene.file.size ?? "0", 10))}
</td> </span>
</tr> </div>
); );
} }
@@ -65,10 +64,12 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
return; return;
} }
return ( return (
<tr> <div className="row">
<td>Duration</td> <span className="col-4">Duration</span>
<td>{TextUtils.secondsToTimestamp(props.scene.file.duration ?? 0)}</td> <span className="col-8 text-truncate">
</tr> {TextUtils.secondsToTimestamp(props.scene.file.duration ?? 0)}
</span>
</div>
); );
} }
@@ -77,12 +78,12 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
return; return;
} }
return ( return (
<tr> <div className="row">
<td>Dimensions</td> <span className="col-4">Dimensions</span>
<td> <span className="col-8 text-truncate">
{props.scene.file.width} x {props.scene.file.height} {props.scene.file.width} x {props.scene.file.height}
</td> </span>
</tr> </div>
); );
} }
@@ -91,22 +92,22 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
return; return;
} }
return ( return (
<tr> <div className="row">
<td>Frame Rate</td> <span className="col-4">Frame Rate</span>
<td>{props.scene.file.framerate} frames per second</td> <span className="col-8 text-truncate">{props.scene.file.framerate} frames per second</span>
</tr> </div>
); );
} }
function renderBitRate() { function renderbitrate() {
if (props.scene.file.bitrate === undefined) { if (props.scene.file.bitrate === undefined) {
return; return;
} }
return ( return (
<tr> <div className="row">
<td>Bit Rate</td> <span className="col-4">Bit Rate</span>
<td>{TextUtils.bitRate(props.scene.file.bitrate ?? 0)}</td> <span className="col-8 text-truncate">{TextUtils.bitRate(props.scene.file.bitrate ?? 0)}</span>
</tr> </div>
); );
} }
@@ -115,10 +116,10 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
return; return;
} }
return ( return (
<tr> <div className="row">
<td>Video Codec</td> <span className="col-4">Video Codec</span>
<td>{props.scene.file.video_codec}</td> <span className="col-8 text-truncate">{props.scene.file.video_codec}</span>
</tr> </div>
); );
} }
@@ -127,10 +128,10 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
return; return;
} }
return ( return (
<tr> <div className="row">
<td>Audio Codec</td> <span className="col-4">Audio Codec</span>
<td>{props.scene.file.audio_codec}</td> <span className="col-8 text-truncate">{props.scene.file.audio_codec}</span>
</tr> </div>
); );
} }
@@ -139,30 +140,26 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
return; return;
} }
return ( return (
<tr> <div className="row">
<td>Downloaded From</td> <span className="col-4">Downloaded From</span>
<td>{props.scene.url}</td> <span className="col-8 text-truncate">{props.scene.url}</span>
</tr> </div>
); );
} }
return ( return (
<> <div className="container scene-file-info">
<Table> {renderChecksum()}
<tbody> {renderPath()}
{renderChecksum()} {renderStream()}
{renderPath()} {renderFileSize()}
{renderStream()} {renderDuration()}
{renderFileSize()} {renderDimensions()}
{renderDuration()} {renderFrameRate()}
{renderDimensions()} {renderbitrate()}
{renderFrameRate()} {renderVideoCodec()}
{renderBitRate()} {renderAudioCodec()}
{renderVideoCodec()} {renderUrl()}
{renderAudioCodec()} </div>
{renderUrl()}
</tbody>
</Table>
</>
); );
}; };

View File

@@ -51,11 +51,13 @@ export const SceneMarkersPanel: React.FC<ISceneMarkersPanelProps> = (
return ( return (
<> <>
<Button onClick={() => onOpenEditor()}>Create Marker</Button> <Button onClick={() => onOpenEditor()}>Create Marker</Button>
<PrimaryTags <div className="container">
sceneMarkers={props.scene.scene_markers ?? []} <PrimaryTags
onClickMarker={onClickMarker} sceneMarkers={props.scene.scene_markers ?? []}
onEdit={onOpenEditor} onClickMarker={onClickMarker}
/> onEdit={onOpenEditor}
/>
</div>
<div className="row"> <div className="row">
<WallPanel <WallPanel
sceneMarkers={props.scene.scene_markers} sceneMarkers={props.scene.scene_markers}

View File

@@ -194,7 +194,7 @@ export class ScenePlayerImpl extends React.Component<
return ( return (
<ReactJWPlayer <ReactJWPlayer
playerId={JWUtils.playerID} playerId={JWUtils.playerID}
playerScript="http://localhost:9999/jwplayer/jwplayer.js" playerScript="http://192.168.1.65:9999/jwplayer/jwplayer.js"
customProps={config} customProps={config}
onReady={this.onReady} onReady={this.onReady}
onSeeked={this.onSeeked} onSeeked={this.onSeeked}
@@ -205,8 +205,8 @@ export class ScenePlayerImpl extends React.Component<
public render() { public render() {
return ( return (
<HotKeys keyMap={KeyMap} handlers={this.KeyHandlers}> <HotKeys keyMap={KeyMap} handlers={this.KeyHandlers} className="row">
<div id="jwplayer-container"> <div id="jwplayer-container" className="w-100 col-sm-9 m-sm-auto no-gutter" >
{this.renderPlayer()} {this.renderPlayer()}
<ScenePlayerScrubber <ScenePlayerScrubber
scene={this.props.scene} scene={this.props.scene}

View File

@@ -357,7 +357,7 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = (
} }
return ( return (
<div className="scrubber-wrapper"> <div className="scrubber-wrapper d-none d-sm-block">
<Button <Button
variant="link" variant="link"
className="scrubber-button" className="scrubber-button"

View File

@@ -78,8 +78,8 @@
} }
.file-info-panel { .file-info-panel {
td { div {
padding: .4rem; margin-bottom: .5rem;
} }
} }

View File

@@ -8,6 +8,10 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
margin: 0; margin: 0;
padding: $pt-navbar-height 0 0 0; padding: $pt-navbar-height 0 0 0;
@media (min-width: 600px) {
min-width: 845px;
}
} }
code { code {
@@ -21,6 +25,12 @@ code {
margin: $pt-grid-size $pt-grid-size 0 0; margin: $pt-grid-size $pt-grid-size 0 0;
padding: 0; padding: 0;
.performer-card,
.studio-card {
min-width: 185px;
width: 320px;
}
&.wall { &.wall {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -44,53 +54,54 @@ code {
.card { .card {
margin: 0 0 10px 10px; margin: 0 0 10px 10px;
overflow: hidden; overflow: hidden;
width: 20rem;
&.zoom-0 { @media (min-width: 576px) {
width: 15rem; &.zoom-0 {
width: 15rem;
.previewable { .previewable {
max-height: 11.25rem; max-height: 11.25rem;
}
.previewable.portrait {
max-height: 11.25rem;
}
} }
.previewable.portrait { &.zoom-1 {
max-height: 11.25rem; width: 20rem;
}
}
&.zoom-1 { .previewable {
width: 20rem; max-height: 15rem;
}
.previewable { .previewable.portrait {
max-height: 15rem; height: 15rem;
}
} }
.previewable.portrait { &.zoom-2 {
height: 15rem; width: 30rem;
}
}
&.zoom-2 { .previewable {
width: 30rem; max-height: 22.5rem;
}
.previewable { .previewable.portrait {
max-height: 22.5rem; height: 22.5rem;
}
} }
.previewable.portrait { &.zoom-3 {
height: 22.5rem; width: 40rem;
}
}
&.zoom-3 { .previewable {
width: 40rem; max-height: 30rem;
}
.previewable { .previewable.portrait {
max-height: 30rem; height: 30rem;
} }
.previewable.portrait {
height: 30rem;
} }
} }
@@ -314,11 +325,6 @@ video.preview.portrait {
width: 100%; width: 100%;
} }
#details-container {
margin: 10px auto;
width: 75%;
}
.pre { .pre {
white-space: pre-line; white-space: pre-line;
} }
@@ -527,3 +533,35 @@ video.preview.portrait {
} }
} }
/* stylelint-enable */ /* stylelint-enable */
.brand-icon {
padding: 3px 6px;
img {
height: 1.5rem;
}
}
.top-nav {
@media (max-width: 576px) {
a {
padding: 6px;
}
}
}
@media (max-width: 576px) {
.nav-tabs {
border: none;
margin: auto;
.nav-link {
border: none;
padding: 8px;
&.active {
border-bottom: 2px solid;
}
}
}
}