lbry-desktop/ui/component/fileRenderFloating/view.jsx
infiinte-persistence 5106ba59f6 FloatingPlayer: Run clamp-to-screen code when going into floating mode.
## Issue
The previous code only handled the clamping if the FP is already floating when the main window is resized.

1. In fresh Odysee session, the floating player is always clipped on the right.
2. If you resize the desktop while not floating, the FP could be clipped when you go into floating mode.

## Changes
- Factor out the clamping code.
- Add another effect to handle 'isFloating' changes.
2020-09-15 12:55:12 -04:00

256 lines
7.7 KiB
JavaScript

// @flow
import * as ICONS from 'constants/icons';
import * as RENDER_MODES from 'constants/file_render_modes';
import React, { useEffect, useState } from 'react';
import Button from 'component/button';
import classnames from 'classnames';
import LoadingScreen from 'component/common/loading-screen';
import FileRender from 'component/fileRender';
import UriIndicator from 'component/uriIndicator';
import usePersistedState from 'effects/use-persisted-state';
import { FILE_WRAPPER_CLASS } from 'page/file/view';
import Draggable from 'react-draggable';
import Tooltip from 'component/common/tooltip';
import { onFullscreenChange } from 'util/full-screen';
import { useIsMobile } from 'effects/use-screensize';
import debounce from 'util/debounce';
const DEBOUNCE_WINDOW_RESIZE_HANDLER_MS = 60;
type Props = {
isFloating: boolean,
fileInfo: FileListItem,
uri: string,
streamingUrl?: string,
title: ?string,
floatingPlayerEnabled: boolean,
closeFloatingPlayer: () => void,
renderMode: string,
};
export default function FileRenderFloating(props: Props) {
const {
fileInfo,
uri,
streamingUrl,
title,
isFloating,
closeFloatingPlayer,
floatingPlayerEnabled,
renderMode,
} = props;
const isMobile = useIsMobile();
const [fileViewerRect, setFileViewerRect] = useState();
const [desktopPlayStartTime, setDesktopPlayStartTime] = useState();
const [wasDragging, setWasDragging] = useState(false);
const [position, setPosition] = usePersistedState('floating-file-viewer:position', {
x: -25,
y: window.innerHeight - 400,
});
const [relativePos, setRelativePos] = useState({ x: 0, y: 0 });
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode);
const isReadyToPlay = isPlayable && (streamingUrl || (fileInfo && fileInfo.completed));
const loadingMessage =
fileInfo && fileInfo.blobs_completed >= 1 && (!fileInfo.download_path || !fileInfo.written_bytes)
? __("It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.")
: __('Loading');
function getScreenWidth() {
if (document && document.documentElement) {
return document.documentElement.clientWidth;
} else {
return window.innerWidth;
}
}
function getScreenHeight() {
if (document && document.documentElement) {
return document.documentElement.clientHeight;
} else {
return window.innerHeight;
}
}
function clampToScreen(pos) {
const GAP_PX = 10;
const ESTIMATED_SCROLL_BAR_PX = 50;
const FLOATING_PLAYER_CLASS = 'content__viewer--floating';
const fpPlayerElem = document.querySelector(`.${FLOATING_PLAYER_CLASS}`);
if (fpPlayerElem) {
if (pos.x + fpPlayerElem.getBoundingClientRect().width > getScreenWidth() - ESTIMATED_SCROLL_BAR_PX) {
pos.x = getScreenWidth() - fpPlayerElem.getBoundingClientRect().width - ESTIMATED_SCROLL_BAR_PX - GAP_PX;
}
if (pos.y + fpPlayerElem.getBoundingClientRect().height > getScreenHeight()) {
pos.y = getScreenHeight() - fpPlayerElem.getBoundingClientRect().height - GAP_PX * 2;
}
}
}
// Updated 'relativePos' based on persisted 'position':
useEffect(() => {
setRelativePos({
x: position.x / getScreenWidth(),
y: position.y / getScreenHeight(),
});
}, []);
// Ensure player is within screen when 'isFloating' changes.
useEffect(() => {
if (isFloating) {
let pos = { x: position.x, y: position.y };
clampToScreen(pos);
if (pos.x !== position.x || pos.y !== position.y) {
setPosition({ x: pos.x, y: pos.y });
}
}
}, [isFloating]);
// Listen to main-window resizing and adjust the fp position accordingly:
useEffect(() => {
const handleMainWindowResize = debounce(e => {
let newPos = {
x: Math.round(relativePos.x * getScreenWidth()),
y: Math.round(relativePos.y * getScreenHeight()),
};
clampToScreen(newPos);
setPosition({ x: newPos.x, y: newPos.y });
}, DEBOUNCE_WINDOW_RESIZE_HANDLER_MS);
window.addEventListener('resize', handleMainWindowResize);
return () => window.removeEventListener('resize', handleMainWindowResize);
// 'relativePos' is needed in the dependency list to avoid stale closure.
// Otherwise, this could just be changed to a one-time effect.
}, [relativePos]);
// Update 'fileViewerRect':
useEffect(() => {
function handleResize() {
const element = document.querySelector(`.${FILE_WRAPPER_CLASS}`);
if (!element) {
return;
}
const rect = element.getBoundingClientRect();
// $FlowFixMe
setFileViewerRect(rect);
}
handleResize();
window.addEventListener('resize', handleResize);
onFullscreenChange(window, 'add', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
onFullscreenChange(window, 'remove', handleResize);
};
}, [setFileViewerRect, isFloating]);
useEffect(() => {
// @if TARGET='app'
setDesktopPlayStartTime(Date.now());
// @endif
return () => {
// @if TARGET='app'
setDesktopPlayStartTime(undefined);
// @endif
};
}, [uri]);
if (!isPlayable || !uri || (isFloating && (isMobile || !floatingPlayerEnabled))) {
return null;
}
function handleDragStart(e, ui) {
// Not really necessary, but reset just in case 'handleStop' didn't fire.
setWasDragging(false);
}
function handleDragMove(e, ui) {
setWasDragging(true);
const { x, y } = position;
const newX = x + ui.deltaX;
const newY = y + ui.deltaY;
setPosition({
x: newX,
y: newY,
});
}
function handleDragStop(e, ui) {
if (wasDragging) {
e.stopPropagation();
setWasDragging(false);
setRelativePos({
x: position.x / getScreenWidth(),
y: position.y / getScreenHeight(),
});
}
}
return (
<Draggable
onDrag={handleDragMove}
onStart={handleDragStart}
onStop={handleDragStop}
defaultPosition={position}
position={isFloating ? position : { x: 0, y: 0 }}
bounds="parent"
disabled={!isFloating}
handle=".draggable"
cancel=".button"
>
<div
className={classnames('content__viewer', {
'content__viewer--floating': isFloating,
'content__viewer--inline': !isFloating,
})}
style={
!isFloating && fileViewerRect
? { width: fileViewerRect.width, height: fileViewerRect.height, left: fileViewerRect.x }
: {}
}
>
<div
className={classnames('content__wrapper', {
'content__wrapper--floating': isFloating,
})}
>
{isFloating && (
<Tooltip label={__('Close')}>
<Button
onClick={closeFloatingPlayer}
icon={ICONS.REMOVE}
button="primary"
className="content__floating-close"
/>
</Tooltip>
)}
{isReadyToPlay ? (
<FileRender
className="draggable"
uri={uri}
// @if TARGET='app'
desktopPlayStartTime={desktopPlayStartTime}
// @endif
/>
) : (
<LoadingScreen status={loadingMessage} />
)}
{isFloating && (
<div className="draggable content__info">
<div className="claim-preview__title" title={title || uri}>
<Button label={title || uri} navigate={uri} button="link" className="content__floating-link" />
</div>
<UriIndicator link addTooltip={false} uri={uri} />
</div>
)}
</div>
</div>
</Draggable>
);
}