change(ui): extract some player components

This commit is contained in:
nick-delirium 2023-01-11 16:57:31 +01:00 committed by Delirium
parent a1ce424df9
commit 579586b56b
14 changed files with 432 additions and 106 deletions

View file

@ -1,22 +1,23 @@
import React from 'react';
import cn from 'classnames';
import { connect } from 'react-redux';
import { STORAGE_TYPES, selectStorageType } from 'Player';
import { selectStorageType, STORAGE_TYPES } from 'Player';
import { PlayButton, PlayingState, FullScreenButton } from 'Player/components'
import { Icon, Tooltip } from 'UI';
import {
fullscreenOn,
fullscreenOff,
toggleBottomBlock,
OVERVIEW,
CONSOLE,
NETWORK,
STACKEVENTS,
STORAGE,
PROFILER,
PERFORMANCE,
fullscreenOff,
fullscreenOn,
GRAPHQL,
INSPECTOR,
NETWORK,
OVERVIEW,
PERFORMANCE,
PROFILER,
STACKEVENTS,
STORAGE,
toggleBottomBlock,
} from 'Duck/components/player';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
@ -138,53 +139,13 @@ function Controls(props: any) {
};
const renderPlayBtn = () => {
let label;
let icon;
if (completed) {
icon = 'arrow-clockwise' as const;
label = 'Replay this session';
} else if (playing) {
icon = 'pause-fill' as const;
label = 'Pause';
} else {
icon = 'play-fill-new' as const;
label = 'Pause';
label = 'Play';
}
const state = completed ? PlayingState.Completed : playing ? PlayingState.Playing : PlayingState.Paused
return (
<Tooltip title={label} className="mr-4">
<div
onClick={() => player.togglePlay()}
className="hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade"
>
<Icon name={icon} size="36" color="inherit" />
</div>
</Tooltip>
<PlayButton state={state} togglePlay={player.togglePlay} iconSize={36} />
);
};
const controlIcon = (
icon: string,
size: number,
action: (args: any) => any,
isBackwards: boolean,
additionalClasses: string
) => (
<div
onClick={action}
className={cn('py-1 px-2 hover-main cursor-pointer bg-gray-lightest', additionalClasses)}
style={{ transform: isBackwards ? 'rotate(180deg)' : '' }}
>
<Icon
// @ts-ignore
name={icon}
size={size}
color="inherit"
/>
</div>
);
const toggleBottomTools = (blockName: number) => {
if (blockName === INSPECTOR) {
// player.toggleInspectorMode(false);
@ -210,7 +171,6 @@ function Controls(props: any) {
toggleSpeed={() => player.toggleSpeed()}
toggleSkip={() => player.toggleSkip()}
playButton={renderPlayBtn()}
controlIcon={controlIcon}
skipIntervals={SKIP_INTERVALS}
setSkipInterval={changeSkipInterval}
currentInterval={skipInterval}
@ -301,13 +261,11 @@ function Controls(props: any) {
)}
<Tooltip title="Fullscreen" delay={0} placement="top-start" className="mx-4">
{controlIcon(
'arrows-angle-extend',
16,
props.fullscreenOn,
false,
'rounded hover:bg-gray-light-shade color-gray-medium'
)}
<FullScreenButton
size={16}
onClick={props.fullscreenOn}
customClasses={'rounded hover:bg-gray-light-shade color-gray-medium'}
/>
</Tooltip>
</div>
</div>

View file

@ -1,25 +1,15 @@
import React from 'react';
import { Duration } from 'luxon';
import styles from './time.module.css';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
const Time = ({ time, isCustom, format = 'm:ss', }) => (
<div className={ !isCustom ? styles.time : undefined }>
{ Duration.fromMillis(time).toFormat(format) }
</div>
)
Time.displayName = "Time";
import { PlayTime } from 'Player/components'
const ReduxTime = observer(({ format, name, isCustom }) => {
const { store } = React.useContext(PlayerContext)
const time = store.get()[name]
return <Time format={format} time={time} isCustom={isCustom} />
return <PlayTime format={format} time={time} isCustom={isCustom} />
})
ReduxTime.displayName = "ReduxTime";
export default React.memo(Time);
export { ReduxTime };

View file

@ -1,20 +1,19 @@
import React from 'react';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
import styles from './timeTracker.module.css';
import cn from 'classnames'
import { ProgressBar } from 'Player/components'
const TimeTracker = ({ scale, live = false, left }) => {
const { store } = React.useContext(PlayerContext)
const time = store.get().time
return (
<React.Fragment>
<span
className={ cn(styles.playedTimeline, live && left > 99 ? styles.liveTime : null) }
style={ { width: `${ time * scale }%` } }
/>
</React.Fragment>
<ProgressBar
scale={scale}
live={live}
left={left}
time={time}
/>
);}
TimeTracker.displayName = 'TimeTracker';

View file

@ -2,7 +2,7 @@ import React, { memo, FC, useEffect, CSSProperties } from 'react';
import type { DragSourceMonitor } from 'react-dnd'
import { useDrag } from 'react-dnd'
import { getEmptyImage } from 'react-dnd-html5-backend'
import Circle from './Circle'
import { ProgressCircle } from 'Player/components'
function getStyles(
left: number,
@ -41,7 +41,7 @@ const DraggableCircle: FC<Props> = memo(function DraggableCircle({
live,
onDrop,
}) {
const [{ isDragging, item }, dragRef, preview] = useDrag(
const [{ isDragging }, dragRef, preview] = useDrag(
() => ({
type: ItemTypes.BOX,
item: { left },
@ -64,7 +64,7 @@ const DraggableCircle: FC<Props> = memo(function DraggableCircle({
style={getStyles(left, isDragging)}
role="DraggableBox"
>
<Circle isGreen={left > 99 && live} />
<ProgressCircle isGreen={left > 99 && live} />
</div>
);
})

View file

@ -4,6 +4,7 @@ import cn from 'classnames';
import { ReduxTime } from '../Time';
// @ts-ignore
import styles from '../controls.module.css';
import { SkipButton } from 'Player/components'
interface Props {
skip: boolean;
@ -17,13 +18,6 @@ interface Props {
forthTenSeconds: () => void;
toggleSpeed: () => void;
toggleSkip: () => void;
controlIcon: (
icon: string,
size: number,
action: () => void,
isBackwards: boolean,
additionalClasses: string
) => JSX.Element;
}
function PlayerControls(props: Props) {
@ -39,7 +33,6 @@ function PlayerControls(props: Props) {
skipIntervals,
setSkipInterval,
currentInterval,
controlIcon,
} = props;
const [showTooltip, setShowTooltip] = React.useState(false);
const speedRef = React.useRef<HTMLButtonElement>(null);
@ -98,13 +91,12 @@ function PlayerControls(props: Props) {
ref={arrowBackRef}
className="h-full bg-transparent"
>
{controlIcon(
'skip-forward-fill',
18,
backTenSeconds,
true,
'hover:bg-active-blue-border color-main h-full flex items-center'
)}
<SkipButton
size={18}
onClick={backTenSeconds}
isBackwards={true}
customClasses={'hover:bg-active-blue-border color-main h-full flex items-center'}
/>
</button>
</Tooltip>
@ -158,13 +150,11 @@ function PlayerControls(props: Props) {
ref={arrowForwardRef}
className="h-full bg-transparent"
>
{controlIcon(
'skip-forward-fill',
18,
forthTenSeconds,
false,
'hover:bg-active-blue-border color-main h-full flex items-center'
)}
<SkipButton
size={18}
onClick={forthTenSeconds}
customClasses={'hover:bg-active-blue-border color-main h-full flex items-center'}
/>
</button>
</Tooltip>
</div>

View file

@ -0,0 +1,25 @@
import React from 'react'
import { Icon } from 'UI'
import cn from 'classnames'
interface IProps {
size: number;
onClick: () => void;
customClasses: string;
}
export function FullScreenButton({ size = 18, onClick, customClasses }: IProps) {
return (
<div
onClick={onClick}
className={cn('py-1 px-2 hover-main cursor-pointer bg-gray-lightest', customClasses)}
>
<Icon
name="arrows-angle-extend"
size={size}
color="inherit"
/>
</div>
)
}

View file

@ -0,0 +1,44 @@
import React from 'react'
import { Icon, Tooltip } from "UI";
export enum PlayingState {
Playing,
Paused,
Completed
}
interface IProps {
togglePlay: () => void;
iconSize: number;
state: PlayingState;
}
const Values = {
[PlayingState.Playing]: {
icon: 'pause-fill' as const,
label: 'Pause'
},
[PlayingState.Completed]: {
icon: 'arrow-clockwise' as const,
label: 'Replay this session',
},
[PlayingState.Paused]: {
icon: 'play-fill-new' as const,
label: 'Play'
}
}
export function PlayButton({ togglePlay, iconSize, state }: IProps) {
const { icon, label } = Values[state];
return (
<Tooltip title={label} className="mr-4">
<div
onClick={togglePlay}
className="hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade"
>
<Icon name={icon} size={iconSize} color="inherit" />
</div>
</Tooltip>
)
}

View file

@ -0,0 +1,27 @@
import React from 'react'
import { Duration } from 'luxon';
const styles = {
padding: '0 12px',
width: '70px',
'text-align': 'center',
}
interface IProps {
/** current time in ms */
time: number;
isCustom?: boolean;
format?: string;
}
/** Play time timer */
export const PlayTime = ({ time, isCustom, format = 'm:ss', }: IProps) => (
<div
style={!isCustom ? styles : undefined}
className={!isCustom ? 'color-gray-medium' : undefined}
>
{Duration.fromMillis(time).toFormat(format)}
</div>
)
PlayTime.displayName = "PlayTime";

View file

@ -0,0 +1,31 @@
import React from 'react';
interface IProps {
scale: number;
live?: boolean;
left: number;
time: number;
}
const styles = {
display: 'block',
pointerEvents: 'none' as const,
height: '10px',
zIndex: 1,
}
const replayBg = '#d0d4f2' // active blue border
const liveBg = 'rgba(66, 174, 94, 0.3)' // light green shade
/** Playtime progress bar */
export const ProgressBar = ({ scale, live = false, left, time }: IProps) => {
return (
<div
style={{
...styles,
width: `${ time * scale }%`,
backgroundColor: live && left > 99 ? liveBg : replayBg
}}
/>
);}
ProgressBar.displayName = 'ProgressBar';

View file

@ -0,0 +1,16 @@
import React, { memo } from 'react';
import cn from 'classnames';
import styles from './styles.module.css';
interface IProps {
preview?: boolean;
isGreen?: boolean;
}
export const ProgressCircle = memo(({ preview, isGreen }: IProps) => (
<div
className={cn(styles.positionTracker, { [styles.greenTracker]: isGreen })}
role={preview ? 'BoxPreview' : 'Box'}
/>
)
)

View file

@ -0,0 +1,27 @@
import React from 'react'
import { Icon } from 'UI'
import cn from 'classnames'
interface IProps {
size: number;
onClick: () => void;
isBackwards?: boolean;
customClasses: string;
}
export function SkipButton({ size = 18, onClick, isBackwards, customClasses }: IProps) {
return (
<div
onClick={onClick}
className={cn('py-1 px-2 hover-main cursor-pointer bg-gray-lightest', customClasses)}
style={{ transform: isBackwards ? 'rotate(180deg)' : '' }}
>
<Icon
name="skip-forward-fill"
size={size}
color="inherit"
/>
</div>
)
}

View file

@ -0,0 +1,6 @@
export { PlayButton, PlayingState } from './PlayButton'
export { SkipButton } from './SkipButton'
export { FullScreenButton } from './FullScreenButton'
export { PlayTime } from './PlayTime'
export { ProgressBar } from './ProgressBar'
export { ProgressCircle } from './ProgressCircle'

View file

@ -0,0 +1,213 @@
.positionTracker {
width: 15px;
height: 15px;
box-shadow: 0 0 0 1px #2331A8;
margin-left: -7px;
border-radius: 50%;
background-color: $main;
position: absolute;
left: 0;
z-index: 98;
top: 3px;
transition: all 0.2s ease-out;
&:hover,
&:focus {
transition: all 0.1s ease-in;
width: 20px;
height: 20px;
top: 1px;
left: -2px;
}
}
.greenTracker {
background-color: #42AE5E!important;
box-shadow: 0 0 0 1px #42AE5E;
}
.progress {
height: 10px;
padding: 8px 0;
cursor: pointer;
width: 100%;
max-width: 100%;
position: relative;
display: flex;
align-items: center;
}
.skipInterval {
position: absolute;
top: 3px;
height: 10px;
bottom: 0;
background: repeating-linear-gradient( 125deg, #efefef, #efefef 3px, #ddd 3px, #efefef 5px );
pointer-events: none;
z-index: 2;
}
.event {
position: absolute;
width: 2px;
height: 10px;
background: $main;
z-index: 3;
pointer-events: none;
/* top: 0; */
/* bottom: 0; */
/* &:hover {
width: 10px;
height: 10px;
margin-left: -6px;
z-index: 1;
};*/
}
/* .event.click, .event.input {
background: $green;
}
.event.location {
background: $blue;
} */
.redEvent {
position: absolute;
width: 2px;
height: 10px;
background: $red;
z-index: 3;
pointer-events: none;
/* top: 0; */
/* bottom: 0; */
/* &:hover {
width: 10px;
height: 10px;
margin-left: -6px;
z-index: 1;
};*/
}
.markup {
position: absolute;
width: 2px;
height: 8px;
margin-left: -8px;
&:hover {
z-index: 9999;
}
}
/* .markup.log {
background: $blue;
}
.markup.error {
background: $red;
}
.markup.warning {
background: $orange;
} */
.markup.info {
background: $blue2;
}
.popup {
max-width: 300px !important;
/* max-height: 300px !important; */
overflow: hidden;
text-overflow: ellipsis;
& span {
display: block;
max-height: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.timeline {
overflow: hidden;
position: absolute;
left: 0;
right: 0;
height: 10px;
border: 1px solid $gray-light;
display: flex;
align-items: center;
}
.clickRage {
position: absolute;
width: 2px;
height: 8px;
margin-left: -1px;
/* background: $red; */
}
.returningLocation {
position: absolute;
height: 20%;
border-radius: 50%;
/* background: $red; */
width: 12px;
}
.feedbackIcon {
position: absolute;
margin-top: -20px;
margin-left: -9px;
background-color: $gray-lightest;
padding: 2px;
box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.1);
& .tooltipArrow {
width: 50px;
height: 25px;
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
overflow: hidden;
&::after {
content: "";
position: absolute;
width: 6px;
height: 6px;
background: $gray-lightest;
transform: translateX(-50%) translateY(50%) rotate(45deg);
bottom: 100%;
left: 50%;
box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.1);
}
}
}
.timeTooltip {
position: absolute;
padding: 0.25rem;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
background: black;
top: -35px;
color: white;
&:after {
content:'';
position: absolute;
top: 100%;
left: 0;
right: 0;
margin: 0 auto;
width: 0;
height: 0;
border-top: solid 5px black;
border-left: solid 5px transparent;
border-right: solid 5px transparent;
}
}

View file

@ -15,7 +15,7 @@ const borderStyles = {
left: 0,
top: 0,
position: 'fixed',
'pointer-events': 'none',
pointerEvents: 'none',
}
const buttonStyles = {