Mobile UI improvements (#1104)

* Use dropdown for o-counter instead of hover
* Always show previews on non-hoverable device
* Add IntersectionObserver polyfill
* Prevent video previews playing fullscreen
This commit is contained in:
WithoutPants
2021-02-26 16:13:34 +11:00
committed by GitHub
parent af6b21a428
commit a9ac176e91
9 changed files with 98 additions and 60 deletions

View File

@@ -46,6 +46,7 @@
"graphql": "^15.4.0", "graphql": "^15.4.0",
"graphql-tag": "^2.11.0", "graphql-tag": "^2.11.0",
"i18n-iso-countries": "^6.4.0", "i18n-iso-countries": "^6.4.0",
"intersection-observer": "^0.12.0",
"jimp": "^0.16.1", "jimp": "^0.16.1",
"localforage": "1.9.0", "localforage": "1.9.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",

View File

@@ -1,3 +1,8 @@
### 🎨 Improvements ### 🎨 Improvements
* Auto-play video previews on mobile devices.
* Replace hover menu with dropdown menu for O-Counter.
* Support random strings for scraper cookie values. * Support random strings for scraper cookie values.
* Added Rescan button to scene, image, gallery details overflow button. * Added Rescan button to scene, image, gallery details overflow button.
### 🐛 Bug fixes
* Prevent scene card previews playing in full-screen on iOS devices.

View File

@@ -29,17 +29,14 @@ export const ScenePreview: React.FC<IScenePreviewProps> = ({
const videoEl = useRef<HTMLVideoElement>(null); const videoEl = useRef<HTMLVideoElement>(null);
useEffect(() => { useEffect(() => {
const observer = new IntersectionObserver( const observer = new IntersectionObserver((entries) => {
(entries) => {
entries.forEach((entry) => { entries.forEach((entry) => {
if (entry.intersectionRatio > 0) if (entry.intersectionRatio > 0)
// Catch is necessary due to DOMException if user hovers before clicking on page // Catch is necessary due to DOMException if user hovers before clicking on page
videoEl.current?.play().catch(() => {}); videoEl.current?.play().catch(() => {});
else videoEl.current?.pause(); else videoEl.current?.pause();
}); });
}, });
{ root: document.documentElement }
);
if (videoEl.current) observer.observe(videoEl.current); if (videoEl.current) observer.observe(videoEl.current);
}); });
@@ -53,6 +50,8 @@ export const ScenePreview: React.FC<IScenePreviewProps> = ({
<div className={cx("scene-card-preview", { portrait: isPortrait })}> <div className={cx("scene-card-preview", { portrait: isPortrait })}>
<img className="scene-card-preview-image" src={image} alt="" /> <img className="scene-card-preview-image" src={image} alt="" />
<video <video
disableRemotePlayback
playsInline
className="scene-card-preview-video" className="scene-card-preview-video"
loop loop
preload="none" preload="none"

View File

@@ -1,6 +1,12 @@
import React from "react"; import React from "react";
import { Button, Spinner } from "react-bootstrap"; import {
import { Icon, HoverPopover, SweatDrops } from "src/components/Shared"; Button,
ButtonGroup,
Dropdown,
DropdownButton,
Spinner,
} from "react-bootstrap";
import { Icon, SweatDrops } from "src/components/Shared";
export interface IOCounterButtonProps { export interface IOCounterButtonProps {
loading: boolean; loading: boolean;
@@ -19,7 +25,7 @@ export const OCounterButton: React.FC<IOCounterButtonProps> = (
const renderButton = () => ( const renderButton = () => (
<Button <Button
className="minimal" className="minimal pr-1"
onClick={props.onIncrement} onClick={props.onIncrement}
variant="secondary" variant="secondary"
title="O-Counter" title="O-Counter"
@@ -29,41 +35,32 @@ export const OCounterButton: React.FC<IOCounterButtonProps> = (
</Button> </Button>
); );
const maybeRenderDropdown = () => {
if (props.value) { if (props.value) {
return ( return (
<HoverPopover <DropdownButton
content={ as={ButtonGroup}
<div> title=" "
<div>
<Button
className="minimal"
onClick={props.onDecrement}
variant="secondary" variant="secondary"
className="pl-0 show-carat"
> >
<Dropdown.Item onClick={props.onDecrement}>
<Icon icon="minus" /> <Icon icon="minus" />
<span>Decrement</span> <span>Decrement</span>
</Button> </Dropdown.Item>
</div> <Dropdown.Item onClick={props.onReset}>
<div>
<Button
className="minimal"
onClick={props.onReset}
variant="secondary"
>
<Icon icon="ban" /> <Icon icon="ban" />
<span>Reset</span> <span>Reset</span>
</Button> </Dropdown.Item>
</div> </DropdownButton>
</div>
}
enterDelay={1000}
placement="bottom"
onOpen={props.onMenuOpened}
onClose={props.onMenuClosed}
>
{renderButton()}
</HoverPopover>
); );
} }
return renderButton(); };
return (
<ButtonGroup className="o-counter">
{renderButton()}
{maybeRenderDropdown()}
</ButtonGroup>
);
}; };

View File

@@ -209,6 +209,7 @@ textarea.scene-description {
} }
} }
@media (pointer: fine) {
&:hover { &:hover {
.scene-specs-overlay, .scene-specs-overlay,
.rating-banner, .rating-banner,
@@ -227,6 +228,26 @@ textarea.scene-description {
transition: opacity 0.5s; transition: opacity 0.5s;
} }
.scene-card-preview-video {
top: 0;
transition-delay: 0.2s;
}
}
}
/* replicate hover for non-hoverable interfaces */
@media (hover: none), (pointer: coarse), (pointer: none) {
/* don't hide overlays */
.scene-studio-overlay {
opacity: 0.75;
transition: opacity 0.5s;
}
.scene-card-check {
opacity: 0.75;
transition: opacity 0.5s;
}
.scene-card-preview-video { .scene-card-preview-video {
top: 0; top: 0;
transition-delay: 0.2s; transition-delay: 0.2s;
@@ -545,3 +566,10 @@ input[type="range"].blue-slider {
color: #664c3f; color: #664c3f;
} }
} }
.o-counter .dropdown-toggle {
background-color: rgba(0, 0, 0, 0);
border: none;
padding-left: 0;
padding-right: 0.25rem;
}

View File

@@ -57,6 +57,8 @@ const Preview: React.FC<{
); );
const video = ( const video = (
<video <video
disableRemotePlayback
playsInline
src={previews.video} src={previews.video}
poster={previews.image} poster={previews.image}
autoPlay={previewType === "video"} autoPlay={previewType === "video"}

View File

@@ -3,6 +3,7 @@ import { shouldPolyfill as shouldPolyfillCanonicalLocales } from "@formatjs/intl
import { shouldPolyfill as shouldPolyfillLocale } from "@formatjs/intl-locale/should-polyfill"; import { shouldPolyfill as shouldPolyfillLocale } from "@formatjs/intl-locale/should-polyfill";
import { shouldPolyfill as shouldPolyfillNumberformat } from "@formatjs/intl-numberformat/should-polyfill"; import { shouldPolyfill as shouldPolyfillNumberformat } from "@formatjs/intl-numberformat/should-polyfill";
import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-pluralrules/should-polyfill"; import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-pluralrules/should-polyfill";
import "intersection-observer/intersection-observer";
// Required for browsers older than August 2020ish. Can be removed at some point. // Required for browsers older than August 2020ish. Can be removed at some point.
replaceAll.shim(); replaceAll.shim();

View File

@@ -62,7 +62,7 @@ hr {
margin: 5px 0; margin: 5px 0;
} }
.dropdown-toggle::after { :not(.show-carat) > .dropdown-toggle::after {
content: none; content: none;
} }

View File

@@ -8058,6 +8058,11 @@ internal-slot@^1.0.2:
has "^1.0.3" has "^1.0.3"
side-channel "^1.0.2" side-channel "^1.0.2"
intersection-observer@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.0.tgz#6c84628f67ce8698e5f9ccf857d97718745837aa"
integrity sha512-2Vkz8z46Dv401zTWudDGwO7KiGHNDkMv417T5ItcNYfmvHR/1qCTVBO9vwH8zZmQ0WkA/1ARwpysR9bsnop4NQ==
intl-messageformat-parser@6.1.3: intl-messageformat-parser@6.1.3:
version "6.1.3" version "6.1.3"
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.1.3.tgz#c333850f66d686eca5c9d87eff1ad46f8721b64d" resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.1.3.tgz#c333850f66d686eca5c9d87eff1ad46f8721b64d"