mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Make mobile menu behavior more consistent, and stats styles responsive (#391)
This commit is contained in:
@@ -111,6 +111,7 @@ func Start() {
|
||||
r.Mount("/studio", studioRoutes{}.Routes())
|
||||
|
||||
r.HandleFunc("/css", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/css")
|
||||
if !config.GetCSSEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
"length-zero-no-unit": true,
|
||||
"max-empty-lines": 1,
|
||||
"max-nesting-depth": 4,
|
||||
"max-line-length": 100,
|
||||
"media-feature-colon-space-after": "always",
|
||||
"media-feature-colon-space-before": "never",
|
||||
"media-feature-range-operator-space-after": "always",
|
||||
|
||||
@@ -107,7 +107,11 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
|
||||
function renderSortByOptions() {
|
||||
return props.filter.sortByOptions.map(option => (
|
||||
<Dropdown.Item onClick={onChangeSortBy} key={option} className="bg-secondary text-white">
|
||||
<Dropdown.Item
|
||||
onClick={onChangeSortBy}
|
||||
key={option}
|
||||
className="bg-secondary text-white"
|
||||
>
|
||||
{option}
|
||||
</Dropdown.Item>
|
||||
));
|
||||
@@ -186,7 +190,11 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
function renderSelectAll() {
|
||||
if (props.onSelectAll) {
|
||||
return (
|
||||
<Dropdown.Item key="select-all" className="bg-secondary text-white" onClick={() => onSelectAll()}>
|
||||
<Dropdown.Item
|
||||
key="select-all"
|
||||
className="bg-secondary text-white"
|
||||
onClick={() => onSelectAll()}
|
||||
>
|
||||
Select All
|
||||
</Dropdown.Item>
|
||||
);
|
||||
@@ -196,7 +204,11 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
function renderSelectNone() {
|
||||
if (props.onSelectNone) {
|
||||
return (
|
||||
<Dropdown.Item key="select-none" className="bg-secondary text-white" onClick={() => onSelectNone()}>
|
||||
<Dropdown.Item
|
||||
key="select-none"
|
||||
className="bg-secondary text-white"
|
||||
onClick={() => onSelectNone()}
|
||||
>
|
||||
Select None
|
||||
</Dropdown.Item>
|
||||
);
|
||||
@@ -209,7 +221,11 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
if (props.otherOperations) {
|
||||
props.otherOperations.forEach(o => {
|
||||
options.push(
|
||||
<Dropdown.Item key={o.text} className="bg-secondary text-white" onClick={o.onClick}>
|
||||
<Dropdown.Item
|
||||
key={o.text}
|
||||
className="bg-secondary text-white"
|
||||
onClick={o.onClick}
|
||||
>
|
||||
{o.text}
|
||||
</Dropdown.Item>
|
||||
);
|
||||
@@ -222,7 +238,9 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
<Dropdown.Toggle variant="secondary" id="more-menu">
|
||||
<Icon icon="ellipsis-h" />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="bg-secondary text-white">{options}</Dropdown.Menu>
|
||||
<Dropdown.Menu className="bg-secondary text-white">
|
||||
{options}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
@@ -278,7 +296,9 @@ export const ListFilter: React.FC<IListFilterProps> = (
|
||||
<Dropdown.Toggle split variant="secondary" id="more-menu">
|
||||
{props.filter.sortBy}
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="bg-secondary text-white">{renderSortByOptions()}</Dropdown.Menu>
|
||||
<Dropdown.Menu className="bg-secondary text-white">
|
||||
{renderSortByOptions()}
|
||||
</Dropdown.Menu>
|
||||
<OverlayTrigger
|
||||
overlay={
|
||||
<Tooltip id="sort-direction-tooltip">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Nav, Navbar, Button } from "react-bootstrap";
|
||||
import { IconName } from "@fortawesome/fontawesome-svg-core";
|
||||
@@ -48,6 +48,31 @@ const menuItems: IMenuItem[] = [
|
||||
|
||||
export const MainNavbar: React.FC = () => {
|
||||
const location = useLocation();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
// react-bootstrap typing bug
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const navbarRef = useRef<any>();
|
||||
|
||||
const maybeCollapse = (event: Event) => {
|
||||
if (
|
||||
navbarRef.current &&
|
||||
event.target instanceof Node &&
|
||||
!navbarRef.current.contains(event.target)
|
||||
) {
|
||||
setExpanded(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (expanded) {
|
||||
document.addEventListener("click", maybeCollapse);
|
||||
document.addEventListener("touchstart", maybeCollapse);
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener("click", maybeCollapse);
|
||||
document.removeEventListener("touchstart", maybeCollapse);
|
||||
};
|
||||
}, [expanded]);
|
||||
|
||||
const path =
|
||||
location.pathname === "/performers"
|
||||
@@ -74,8 +99,15 @@ export const MainNavbar: React.FC = () => {
|
||||
bg="dark"
|
||||
className="top-nav"
|
||||
expand="md"
|
||||
expanded={expanded}
|
||||
onToggle={setExpanded}
|
||||
ref={navbarRef}
|
||||
>
|
||||
<Navbar.Brand
|
||||
as="div"
|
||||
className="order-1 order-md-0"
|
||||
onClick={() => setExpanded(false)}
|
||||
>
|
||||
<Navbar.Brand as="div" className="order-1 order-md-0">
|
||||
<Link to="/">
|
||||
<Button className="minimal brand-link d-none d-md-inline-block">
|
||||
Stash
|
||||
@@ -109,8 +141,8 @@ export const MainNavbar: React.FC = () => {
|
||||
</Navbar.Collapse>
|
||||
<Nav className="order-2">
|
||||
<div className="d-none d-sm-block">{newButton}</div>
|
||||
<LinkContainer exact to="/settings">
|
||||
<Button className="minimal">
|
||||
<LinkContainer exact to="/settings" onClick={() => setExpanded(false)}>
|
||||
<Button className="minimal settings-button">
|
||||
<Icon icon="cog" />
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
|
||||
@@ -236,7 +236,8 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
</h5>
|
||||
<span>{props.scene.date}</span>
|
||||
<p>
|
||||
{props.scene.details && TextUtils.truncate(props.scene.details, 100, "... (continued)")}
|
||||
{props.scene.details &&
|
||||
TextUtils.truncate(props.scene.details, 100, "... (continued)")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -137,10 +137,7 @@ export const Scene: React.FC = () => {
|
||||
>
|
||||
<SceneFileInfoPanel scene={scene} />
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey="scene-edit-panel"
|
||||
title="Edit"
|
||||
>
|
||||
<Tab eventKey="scene-edit-panel" title="Edit">
|
||||
<SceneEditPanel
|
||||
scene={scene}
|
||||
onUpdate={newScene => setScene(newScene)}
|
||||
|
||||
@@ -344,7 +344,9 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
||||
<td>Studio</td>
|
||||
<td>
|
||||
<StudioSelect
|
||||
onSelect={items => setStudioId(items.length > 0 ? items[0]?.id : undefined)}
|
||||
onSelect={items =>
|
||||
setStudioId(items.length > 0 ? items[0]?.id : undefined)
|
||||
}
|
||||
ids={studioId ? [studioId] : []}
|
||||
/>
|
||||
</td>
|
||||
|
||||
@@ -168,7 +168,11 @@ export const SettingsTasksPanel: React.FC = () => {
|
||||
<Form.Group>
|
||||
<h5>Status: {status}</h5>
|
||||
{!!status && status !== "Idle" ? (
|
||||
<ProgressBar animated now={progress} label={`${progress.toFixed(0)}%`} />
|
||||
<ProgressBar
|
||||
animated
|
||||
now={progress}
|
||||
label={`${progress.toFixed(0)}%`}
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
@@ -73,7 +73,9 @@ export const SceneGallerySelect: React.FC<ISceneGallerySelect> = props => {
|
||||
|
||||
const onChange = (selectedItems: ValueType<Option>) => {
|
||||
const selectedItem = getSelectedValues(selectedItems)[0];
|
||||
props.onSelect(selectedItem ? galleries.find(g => g.id === selectedItem) : undefined);
|
||||
props.onSelect(
|
||||
selectedItem ? galleries.find(g => g.id === selectedItem) : undefined
|
||||
);
|
||||
};
|
||||
|
||||
const selectedOptions: Option[] = props.initialId
|
||||
|
||||
@@ -13,7 +13,7 @@ export const Stats: React.FC = () => {
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="col col-sm-8 m-sm-auto row stats">
|
||||
<div className="flex-grow-1">
|
||||
<div className="stats-element">
|
||||
<p className="title">
|
||||
<FormattedNumber value={data.stats.scene_count} />
|
||||
</p>
|
||||
@@ -21,7 +21,7 @@ export const Stats: React.FC = () => {
|
||||
<FormattedMessage id="scenes" defaultMessage="Scenes" />
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-grow-1">
|
||||
<div className="stats-element">
|
||||
<p className="title">
|
||||
<FormattedNumber value={data.stats.gallery_count} />
|
||||
</p>
|
||||
@@ -29,7 +29,7 @@ export const Stats: React.FC = () => {
|
||||
<FormattedMessage id="galleries" defaultMessage="Galleries" />
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-grow-1">
|
||||
<div className="stats-element">
|
||||
<p className="title">
|
||||
<FormattedNumber value={data.stats.performer_count} />
|
||||
</p>
|
||||
@@ -37,7 +37,7 @@ export const Stats: React.FC = () => {
|
||||
<FormattedMessage id="performers" defaultMessage="Performers" />
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-grow-1">
|
||||
<div className="stats-element">
|
||||
<p className="title">
|
||||
<FormattedNumber value={data.stats.studio_count} />
|
||||
</p>
|
||||
@@ -45,7 +45,7 @@ export const Stats: React.FC = () => {
|
||||
<FormattedMessage id="studios" defaultMessage="Studios" />
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-grow-1">
|
||||
<div className="stats-element">
|
||||
<p className="title">
|
||||
<FormattedNumber value={data.stats.tag_count} />
|
||||
</p>
|
||||
|
||||
@@ -11,7 +11,7 @@ export class StashService {
|
||||
public static client: ApolloClient<NormalizedCacheObject>;
|
||||
private static cache: InMemoryCache;
|
||||
|
||||
public static getPlatformURL(ws? : boolean) {
|
||||
public static getPlatformURL(ws?: boolean) {
|
||||
const platformUrl = new URL(window.location.origin);
|
||||
|
||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
|
||||
|
||||
@@ -41,8 +41,7 @@ function useLocalForage(
|
||||
if (!Object.is(parsed, null)) {
|
||||
setData(parsed);
|
||||
Cache[key] = parsed;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setData({});
|
||||
Cache[key] = {};
|
||||
}
|
||||
|
||||
@@ -29,27 +29,31 @@ a {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
code, .code {
|
||||
code,
|
||||
.code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
.input-control, .text-input {
|
||||
color: $text-color;
|
||||
.input-control,
|
||||
.text-input {
|
||||
border: 0;
|
||||
box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);
|
||||
box-shadow: 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, .3), inset 0 1px 1px rgba(16, 22, 26, .4);
|
||||
color: $text-color;
|
||||
|
||||
&:focus {
|
||||
color: $text-color;
|
||||
border: 0;
|
||||
box-shadow: 0 0 0 1px $primary, 0 0 0 1px $primary, 0 0 0 3px rgba(19,124,189,.3), inset 0 0 0 1px rgba(16,22,26,.3), inset 0 1px 1px rgba(16,22,26,.4);
|
||||
box-shadow: 0 0 0 1px $primary, 0 0 0 1px $primary, 0 0 0 3px rgba(19, 124, 189, .3), inset 0 0 0 1px rgba(16, 22, 26, .3), inset 0 1px 1px rgba(16, 22, 26, .4);
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.text-input, .text-input:focus {
|
||||
.text-input,
|
||||
.text-input:focus {
|
||||
background-color: $textfield-bg;
|
||||
}
|
||||
|
||||
.input-control, .input-control:focus {
|
||||
.input-control,
|
||||
.input-control:focus {
|
||||
background-color: $secondary;
|
||||
}
|
||||
|
||||
@@ -85,6 +89,7 @@ code, .code {
|
||||
.scene-card-video {
|
||||
max-height: 180px;
|
||||
}
|
||||
|
||||
.previewable.portrait {
|
||||
height: 180px;
|
||||
}
|
||||
@@ -96,6 +101,7 @@ code, .code {
|
||||
.scene-card-video {
|
||||
max-height: 240px;
|
||||
}
|
||||
|
||||
.previewable.portrait {
|
||||
height: 240px;
|
||||
}
|
||||
@@ -107,6 +113,7 @@ code, .code {
|
||||
.scene-card-video {
|
||||
max-height: 360px;
|
||||
}
|
||||
|
||||
.previewable.portrait {
|
||||
height: 360px;
|
||||
}
|
||||
@@ -118,6 +125,7 @@ code, .code {
|
||||
.scene-card-video {
|
||||
max-height: 480px;
|
||||
}
|
||||
|
||||
.portrait {
|
||||
height: 480px;
|
||||
}
|
||||
@@ -131,13 +139,15 @@ code, .code {
|
||||
|
||||
/* this is a bit of a hack, because we can't supply direct class names
|
||||
to the react-select controls */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
div.react-select__control {
|
||||
background-color: $secondary;
|
||||
border-color: $secondary;
|
||||
color: $text-color;
|
||||
cursor: pointer;
|
||||
|
||||
.react-select__single-value, .react-select__input {
|
||||
.react-select__single-value,
|
||||
.react-select__input {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
@@ -160,6 +170,7 @@ div.react-select__menu {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
/* stylelint-enable selector-class-pattern */
|
||||
|
||||
.image-thumbnail {
|
||||
height: 100px;
|
||||
@@ -221,9 +232,9 @@ div.react-select__menu {
|
||||
|
||||
.filter-container,
|
||||
.operation-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
@@ -375,6 +386,11 @@ div.react-select__menu {
|
||||
.btn {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.settings-button {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,9 +399,18 @@ div.react-select__menu {
|
||||
}
|
||||
|
||||
.stats {
|
||||
&-element {
|
||||
flex-grow: 1;
|
||||
margin: auto .5rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 3vw;
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: 576px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
|
||||
@@ -7,16 +7,20 @@ import { StashService } from "./core/StashService";
|
||||
import "./index.scss";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
|
||||
ReactDOM.render((
|
||||
ReactDOM.render(
|
||||
<>
|
||||
<link rel="stylesheet" type="text/css" href={StashService.getPlatformURL() + "css"}/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href={`${StashService.getPlatformURL()}css`}
|
||||
/>
|
||||
<BrowserRouter>
|
||||
<ApolloProvider client={StashService.initialize()!}>
|
||||
<App />
|
||||
</ApolloProvider>
|
||||
</BrowserRouter>
|
||||
</>
|
||||
), document.getElementById("root")
|
||||
</>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
|
||||
@@ -25,7 +25,7 @@ $pre-color: $text-color;
|
||||
$navbar-dark-color: rgb(245, 248, 250);
|
||||
$popover-bg: $secondary;
|
||||
$dark-text: #182026;
|
||||
$textfield-bg: rgba(16,22,26,.3);
|
||||
$textfield-bg: rgba(16, 22, 26, .3);
|
||||
|
||||
@import "node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user