From c49aec2ae0d5695dde17067e0c39c3be102b7875 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 4 Oct 2022 15:08:22 -0400 Subject: [PATCH 1/7] Refactor UI library types --- .../components/ui/button/useButtonStyles.ts | 50 +++++++++---------- app/soapbox/components/ui/card/card.tsx | 8 +-- app/soapbox/components/ui/hstack/hstack.tsx | 14 +++--- app/soapbox/components/ui/modal/modal.tsx | 4 +- app/soapbox/components/ui/stack/stack.tsx | 12 ++--- app/soapbox/components/ui/text/text.tsx | 26 ++++------ 6 files changed, 52 insertions(+), 62 deletions(-) diff --git a/app/soapbox/components/ui/button/useButtonStyles.ts b/app/soapbox/components/ui/button/useButtonStyles.ts index ecec3de1f..4dc38997d 100644 --- a/app/soapbox/components/ui/button/useButtonStyles.ts +++ b/app/soapbox/components/ui/button/useButtonStyles.ts @@ -1,12 +1,32 @@ import classNames from 'clsx'; -type ButtonThemes = 'primary' | 'secondary' | 'tertiary' | 'accent' | 'danger' | 'transparent' | 'outline' -type ButtonSizes = 'sm' | 'md' | 'lg' +const themes = { + primary: + 'bg-primary-500 hover:bg-primary-400 dark:hover:bg-primary-600 border-transparent focus:bg-primary-500 text-gray-100 focus:ring-primary-300', + secondary: + 'border-transparent bg-primary-100 dark:bg-primary-800 hover:bg-primary-50 dark:hover:bg-primary-700 focus:bg-primary-100 dark:focus:bg-primary-800 text-primary-500 dark:text-primary-200', + tertiary: + 'bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500', + accent: 'border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300', + danger: 'border-transparent bg-danger-100 dark:bg-danger-900 text-danger-600 dark:text-danger-200 hover:bg-danger-600 hover:text-gray-100 dark:hover:text-gray-100 dark:hover:bg-danger-500 focus:bg-danger-800 dark:focus:bg-danger-600', + transparent: 'border-transparent text-gray-800 backdrop-blur-sm bg-white/75 hover:bg-white/80', + outline: 'border-gray-100 border-2 bg-transparent text-gray-100 hover:bg-white/10', +}; + +const sizes = { + xs: 'px-3 py-1 text-xs', + sm: 'px-3 py-1.5 text-xs leading-4', + md: 'px-4 py-2 text-sm', + lg: 'px-6 py-3 text-base', +}; + +type ButtonSizes = keyof typeof sizes +type ButtonThemes = keyof typeof themes type IButtonStyles = { - theme: ButtonThemes, - block: boolean, - disabled: boolean, + theme: ButtonThemes + block: boolean + disabled: boolean size: ButtonSizes } @@ -17,26 +37,6 @@ const useButtonStyles = ({ disabled, size, }: IButtonStyles) => { - const themes = { - primary: - 'bg-primary-500 hover:bg-primary-400 dark:hover:bg-primary-600 border-transparent focus:bg-primary-500 text-gray-100 focus:ring-primary-300', - secondary: - 'border-transparent bg-primary-100 dark:bg-primary-800 hover:bg-primary-50 dark:hover:bg-primary-700 focus:bg-primary-100 dark:focus:bg-primary-800 text-primary-500 dark:text-primary-200', - tertiary: - 'bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500', - accent: 'border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300', - danger: 'border-transparent bg-danger-100 dark:bg-danger-900 text-danger-600 dark:text-danger-200 hover:bg-danger-600 hover:text-gray-100 dark:hover:text-gray-100 dark:hover:bg-danger-500 focus:bg-danger-800 dark:focus:bg-danger-600', - transparent: 'border-transparent text-gray-800 backdrop-blur-sm bg-white/75 hover:bg-white/80', - outline: 'border-gray-100 border-2 bg-transparent text-gray-100 hover:bg-white/10', - }; - - const sizes = { - xs: 'px-3 py-1 text-xs', - sm: 'px-3 py-1.5 text-xs leading-4', - md: 'px-4 py-2 text-sm', - lg: 'px-6 py-3 text-base', - }; - const buttonStyle = classNames({ 'inline-flex items-center border font-medium rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 appearance-none transition-all': true, 'select-none disabled:opacity-75 disabled:cursor-default': disabled, diff --git a/app/soapbox/components/ui/card/card.tsx b/app/soapbox/components/ui/card/card.tsx index 816627326..59f6ee1bc 100644 --- a/app/soapbox/components/ui/card/card.tsx +++ b/app/soapbox/components/ui/card/card.tsx @@ -18,13 +18,13 @@ const messages = defineMessages({ interface ICard { /** The type of card. */ - variant?: 'default' | 'rounded', + variant?: 'default' | 'rounded' /** Card size preset. */ - size?: 'md' | 'lg' | 'xl', + size?: keyof typeof sizes /** Extra classnames for the
element. */ - className?: string, + className?: string /** Elements inside the card. */ - children: React.ReactNode, + children: React.ReactNode } /** An opaque backdrop to hold a collection of related elements. */ diff --git a/app/soapbox/components/ui/hstack/hstack.tsx b/app/soapbox/components/ui/hstack/hstack.tsx index f959cdd51..994da6aff 100644 --- a/app/soapbox/components/ui/hstack/hstack.tsx +++ b/app/soapbox/components/ui/hstack/hstack.tsx @@ -29,21 +29,21 @@ const spaces = { interface IHStack { /** Vertical alignment of children. */ - alignItems?: 'top' | 'bottom' | 'center' | 'start', + alignItems?: keyof typeof alignItemsOptions /** Extra class names on the
element. */ - className?: string, + className?: string /** Children */ - children?: React.ReactNode, + children?: React.ReactNode /** Horizontal alignment of children. */ - justifyContent?: 'between' | 'center' | 'start' | 'end' | 'around', + justifyContent?: keyof typeof justifyContentOptions /** Size of the gap between elements. */ - space?: 0.5 | 1 | 1.5 | 2 | 3 | 4 | 6 | 8, + space?: keyof typeof spaces /** Whether to let the flexbox grow. */ - grow?: boolean, + grow?: boolean /** Extra CSS styles for the
*/ style?: React.CSSProperties /** Whether to let the flexbox wrap onto multiple lines. */ - wrap?: boolean, + wrap?: boolean } /** Horizontal row of child elements. */ diff --git a/app/soapbox/components/ui/modal/modal.tsx b/app/soapbox/components/ui/modal/modal.tsx index e203a1460..969f7ae65 100644 --- a/app/soapbox/components/ui/modal/modal.tsx +++ b/app/soapbox/components/ui/modal/modal.tsx @@ -10,8 +10,6 @@ const messages = defineMessages({ confirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, }); -type Widths = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' - const widths = { xs: 'max-w-xs', sm: 'max-w-sm', @@ -51,7 +49,7 @@ interface IModal { skipFocus?: boolean, /** Title text for the modal. */ title?: React.ReactNode, - width?: Widths, + width?: keyof typeof widths, } /** Displays a modal dialog box. */ diff --git a/app/soapbox/components/ui/stack/stack.tsx b/app/soapbox/components/ui/stack/stack.tsx index 64257ecf9..5f60f553f 100644 --- a/app/soapbox/components/ui/stack/stack.tsx +++ b/app/soapbox/components/ui/stack/stack.tsx @@ -1,8 +1,6 @@ import classNames from 'clsx'; import React from 'react'; -type SIZES = 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 10 - const spaces = { 0: 'space-y-0', '0.5': 'space-y-0.5', @@ -25,15 +23,15 @@ const alignItemsOptions = { interface IStack extends React.HTMLAttributes { /** Size of the gap between elements. */ - space?: SIZES, + space?: keyof typeof spaces /** Horizontal alignment of children. */ - alignItems?: 'center', + alignItems?: 'center' /** Vertical alignment of children. */ - justifyContent?: 'center', + justifyContent?: 'center' /** Extra class names on the
element. */ - className?: string, + className?: string /** Whether to let the flexbox grow. */ - grow?: boolean, + grow?: boolean } /** Vertical stack of child elements. */ diff --git a/app/soapbox/components/ui/text/text.tsx b/app/soapbox/components/ui/text/text.tsx index 2e0736809..7669f3d2a 100644 --- a/app/soapbox/components/ui/text/text.tsx +++ b/app/soapbox/components/ui/text/text.tsx @@ -1,16 +1,6 @@ import classNames from 'clsx'; import React from 'react'; -type Themes = 'default' | 'danger' | 'primary' | 'muted' | 'subtle' | 'success' | 'inherit' | 'white' -type Weights = 'normal' | 'medium' | 'semibold' | 'bold' -export type Sizes = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' -type Alignments = 'left' | 'center' | 'right' -type TrackingSizes = 'normal' | 'wide' -type TransformProperties = 'uppercase' | 'normal' -type Families = 'sans' | 'mono' -type Tags = 'abbr' | 'p' | 'span' | 'pre' | 'time' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' -type Directions = 'ltr' | 'rtl' - const themes = { default: 'text-gray-900 dark:text-gray-100', danger: 'text-danger-600', @@ -60,15 +50,19 @@ const families = { mono: 'font-mono', }; +export type Sizes = keyof typeof sizes +type Tags = 'abbr' | 'p' | 'span' | 'pre' | 'time' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' +type Directions = 'ltr' | 'rtl' + interface IText extends Pick, 'dangerouslySetInnerHTML'> { /** How to align the text. */ - align?: Alignments, + align?: keyof typeof alignments, /** Extra class names for the outer element. */ className?: string, /** Text direction. */ direction?: Directions, /** Typeface of the text. */ - family?: Families, + family?: keyof typeof families, /** The "for" attribute specifies which form element a label is bound to. */ htmlFor?: string, /** Font size of the text. */ @@ -76,15 +70,15 @@ interface IText extends Pick, 'danger /** HTML element name of the outer element. */ tag?: Tags, /** Theme for the text. */ - theme?: Themes, + theme?: keyof typeof themes, /** Letter-spacing of the text. */ - tracking?: TrackingSizes, + tracking?: keyof typeof trackingSizes, /** Transform (eg uppercase) for the text. */ - transform?: TransformProperties, + transform?: keyof typeof transformProperties, /** Whether to truncate the text if its container is too small. */ truncate?: boolean, /** Font weight of the text. */ - weight?: Weights, + weight?: keyof typeof weights, /** Tooltip title. */ title?: string, } From c960ad9d33ff48ce9d9f05fef94bbd2e0427a5d7 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 4 Oct 2022 15:17:26 -0400 Subject: [PATCH 2/7] Ensure space is number --- app/soapbox/components/ui/hstack/hstack.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/ui/hstack/hstack.tsx b/app/soapbox/components/ui/hstack/hstack.tsx index 994da6aff..a109da608 100644 --- a/app/soapbox/components/ui/hstack/hstack.tsx +++ b/app/soapbox/components/ui/hstack/hstack.tsx @@ -17,7 +17,7 @@ const alignItemsOptions = { }; const spaces = { - '0.5': 'space-x-0.5', + [0.5]: 'space-x-0.5', 1: 'space-x-1', 1.5: 'space-x-1.5', 2: 'space-x-2', From 1c55e60055abbdca49cb07617b04f4a8c8ce2fee Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 4 Oct 2022 15:17:51 -0400 Subject: [PATCH 3/7] Ensure space is number --- app/soapbox/components/ui/stack/stack.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/soapbox/components/ui/stack/stack.tsx b/app/soapbox/components/ui/stack/stack.tsx index 5f60f553f..b161d4949 100644 --- a/app/soapbox/components/ui/stack/stack.tsx +++ b/app/soapbox/components/ui/stack/stack.tsx @@ -3,9 +3,9 @@ import React from 'react'; const spaces = { 0: 'space-y-0', - '0.5': 'space-y-0.5', + [0.5]: 'space-y-0.5', 1: 'space-y-1', - '1.5': 'space-y-1.5', + [1.5]: 'space-y-1.5', 2: 'space-y-2', 3: 'space-y-3', 4: 'space-y-4', From 6dddaea73668c71ec481a07edd0b8c2e14151b28 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 12 Oct 2022 18:24:23 -0500 Subject: [PATCH 4/7] Audio: convert to TSX+FC --- app/soapbox/features/audio/index.js | 535 ------------------------ app/soapbox/features/audio/index.tsx | 600 +++++++++++++++++++++++++++ 2 files changed, 600 insertions(+), 535 deletions(-) delete mode 100644 app/soapbox/features/audio/index.js create mode 100644 app/soapbox/features/audio/index.tsx diff --git a/app/soapbox/features/audio/index.js b/app/soapbox/features/audio/index.js deleted file mode 100644 index 98bfc66b5..000000000 --- a/app/soapbox/features/audio/index.js +++ /dev/null @@ -1,535 +0,0 @@ -import classNames from 'clsx'; -import debounce from 'lodash/debounce'; -import throttle from 'lodash/throttle'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; - -import Icon from 'soapbox/components/icon'; -import { formatTime, getPointerPosition, fileNameFromURL } from 'soapbox/features/video'; - -import Visualizer from './visualizer'; - -const messages = defineMessages({ - play: { id: 'video.play', defaultMessage: 'Play' }, - pause: { id: 'video.pause', defaultMessage: 'Pause' }, - mute: { id: 'video.mute', defaultMessage: 'Mute sound' }, - unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' }, - download: { id: 'video.download', defaultMessage: 'Download file' }, -}); - -const TICK_SIZE = 10; -const PADDING = 180; - -export default @injectIntl -class Audio extends React.PureComponent { - - static propTypes = { - src: PropTypes.string.isRequired, - alt: PropTypes.string, - poster: PropTypes.string, - duration: PropTypes.number, - width: PropTypes.number, - height: PropTypes.number, - editable: PropTypes.bool, - fullscreen: PropTypes.bool, - intl: PropTypes.object.isRequired, - cacheWidth: PropTypes.func, - backgroundColor: PropTypes.string, - foregroundColor: PropTypes.string, - accentColor: PropTypes.string, - currentTime: PropTypes.number, - autoPlay: PropTypes.bool, - volume: PropTypes.number, - muted: PropTypes.bool, - deployPictureInPicture: PropTypes.func, - }; - - state = { - width: this.props.width, - currentTime: 0, - buffer: 0, - duration: null, - paused: true, - muted: false, - volume: 0.5, - dragging: false, - }; - - constructor(props) { - super(props); - this.visualizer = new Visualizer(TICK_SIZE); - } - - setPlayerRef = c => { - this.player = c; - - if (this.player) { - this._setDimensions(); - } - } - - _pack() { - return { - src: this.props.src, - volume: this.audio.volume, - muted: this.audio.muted, - currentTime: this.audio.currentTime, - poster: this.props.poster, - backgroundColor: this.props.backgroundColor, - foregroundColor: this.props.foregroundColor, - accentColor: this.props.accentColor, - }; - } - - _setDimensions() { - const width = this.player.offsetWidth; - const height = this.props.fullscreen ? this.player.offsetHeight : (width / (16 / 9)); - - if (this.props.cacheWidth) { - this.props.cacheWidth(width); - } - - this.setState({ width, height }); - } - - setSeekRef = c => { - this.seek = c; - } - - setVolumeRef = c => { - this.volume = c; - } - - setAudioRef = c => { - this.audio = c; - - if (this.audio) { - this.setState({ volume: this.audio.volume, muted: this.audio.muted }); - } - } - - setCanvasRef = c => { - this.canvas = c; - - this.visualizer.setCanvas(c); - } - - componentDidMount() { - window.addEventListener('scroll', this.handleScroll); - window.addEventListener('resize', this.handleResize, { passive: true }); - } - - componentDidUpdate(prevProps, prevState) { - if (prevProps.src !== this.props.src || this.state.width !== prevState.width || this.state.height !== prevState.height || prevProps.accentColor !== this.props.accentColor) { - this._clear(); - this._draw(); - } - } - - componentWillUnmount() { - window.removeEventListener('scroll', this.handleScroll); - window.removeEventListener('resize', this.handleResize); - - if (!this.state.paused && this.audio && this.props.deployPictureInPicture) { - this.props.deployPictureInPicture('audio', this._pack()); - } - } - - togglePlay = () => { - if (!this.audioContext) { - this._initAudioContext(); - } - - if (this.state.paused) { - this.setState({ paused: false }, () => this.audio.play()); - } else { - this.setState({ paused: true }, () => this.audio.pause()); - } - } - - handleResize = debounce(() => { - if (this.player) { - this._setDimensions(); - } - }, 250, { - trailing: true, - }); - - handlePlay = () => { - this.setState({ paused: false }); - - if (this.audioContext && this.audioContext.state === 'suspended') { - this.audioContext.resume(); - } - - this._renderCanvas(); - } - - handlePause = () => { - this.setState({ paused: true }); - - if (this.audioContext) { - this.audioContext.suspend(); - } - } - - handleProgress = () => { - const lastTimeRange = this.audio.buffered.length - 1; - - if (lastTimeRange > -1) { - this.setState({ buffer: Math.ceil(this.audio.buffered.end(lastTimeRange) / this.audio.duration * 100) }); - } - } - - toggleMute = () => { - const muted = !this.state.muted; - - this.setState({ muted }, () => { - this.audio.muted = muted; - }); - } - - handleVolumeMouseDown = e => { - document.addEventListener('mousemove', this.handleMouseVolSlide, true); - document.addEventListener('mouseup', this.handleVolumeMouseUp, true); - document.addEventListener('touchmove', this.handleMouseVolSlide, true); - document.addEventListener('touchend', this.handleVolumeMouseUp, true); - - this.handleMouseVolSlide(e); - - e.preventDefault(); - e.stopPropagation(); - } - - handleVolumeMouseUp = () => { - document.removeEventListener('mousemove', this.handleMouseVolSlide, true); - document.removeEventListener('mouseup', this.handleVolumeMouseUp, true); - document.removeEventListener('touchmove', this.handleMouseVolSlide, true); - document.removeEventListener('touchend', this.handleVolumeMouseUp, true); - } - - handleMouseDown = e => { - document.addEventListener('mousemove', this.handleMouseMove, true); - document.addEventListener('mouseup', this.handleMouseUp, true); - document.addEventListener('touchmove', this.handleMouseMove, true); - document.addEventListener('touchend', this.handleMouseUp, true); - - this.setState({ dragging: true }); - this.audio.pause(); - this.handleMouseMove(e); - - e.preventDefault(); - e.stopPropagation(); - } - - handleMouseUp = () => { - document.removeEventListener('mousemove', this.handleMouseMove, true); - document.removeEventListener('mouseup', this.handleMouseUp, true); - document.removeEventListener('touchmove', this.handleMouseMove, true); - document.removeEventListener('touchend', this.handleMouseUp, true); - - this.setState({ dragging: false }); - this.audio.play(); - } - - handleMouseMove = throttle(e => { - const { x } = getPointerPosition(this.seek, e); - const currentTime = this.audio.duration * x; - - if (!isNaN(currentTime)) { - this.setState({ currentTime }, () => { - this.audio.currentTime = currentTime; - }); - } - }, 15); - - handleTimeUpdate = () => { - this.setState({ - currentTime: this.audio.currentTime, - duration: this.audio.duration, - }); - } - - handleMouseVolSlide = throttle(e => { - const { x } = getPointerPosition(this.volume, e); - - if (!isNaN(x)) { - this.setState({ volume: x }, () => { - this.audio.volume = x; - }); - } - }, 15); - - handleScroll = throttle(() => { - if (!this.canvas || !this.audio) { - return; - } - - const { top, height } = this.canvas.getBoundingClientRect(); - const inView = (top <= (window.innerHeight || document.documentElement.clientHeight)) && (top + height >= 0); - - if (!this.state.paused && !inView) { - this.audio.pause(); - - if (this.props.deployPictureInPicture) { - this.props.deployPictureInPicture('audio', this._pack()); - } - - this.setState({ paused: true }); - } - }, 150, { trailing: true }); - - handleMouseEnter = () => { - this.setState({ hovered: true }); - } - - handleMouseLeave = () => { - this.setState({ hovered: false }); - } - - handleLoadedData = () => { - const { autoPlay, currentTime, volume, muted } = this.props; - - this.setState({ duration: this.audio.duration }); - - if (currentTime) { - this.audio.currentTime = currentTime; - } - - if (volume !== undefined) { - this.audio.volume = volume; - } - - if (muted !== undefined) { - this.audio.muted = muted; - } - - if (autoPlay) { - this.togglePlay(); - } - } - - _initAudioContext() { - // eslint-disable-next-line compat/compat - const AudioContext = window.AudioContext || window.webkitAudioContext; - const context = new AudioContext(); - const source = context.createMediaElementSource(this.audio); - - this.visualizer.setAudioContext(context, source); - source.connect(context.destination); - - this.audioContext = context; - } - - handleDownload = () => { - fetch(this.props.src).then(res => res.blob()).then(blob => { - const element = document.createElement('a'); - const objectURL = URL.createObjectURL(blob); - - element.setAttribute('href', objectURL); - element.setAttribute('download', fileNameFromURL(this.props.src)); - - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); - - URL.revokeObjectURL(objectURL); - }).catch(err => { - console.error(err); - }); - } - - _renderCanvas() { - requestAnimationFrame(() => { - if (!this.audio) return; - - this.handleTimeUpdate(); - this._clear(); - this._draw(); - - if (!this.state.paused) { - this._renderCanvas(); - } - }); - } - - _clear() { - this.visualizer.clear(this.state.width, this.state.height); - } - - _draw() { - this.visualizer.draw(this._getCX(), this._getCY(), this._getAccentColor(), this._getRadius(), this._getScaleCoefficient()); - } - - _getRadius() { - return parseInt(((this.state.height || this.props.height) - (PADDING * this._getScaleCoefficient()) * 2) / 2); - } - - _getScaleCoefficient() { - return (this.state.height || this.props.height) / 982; - } - - _getCX() { - return Math.floor(this.state.width / 2) || null; - } - - _getCY() { - return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient())) || null; - } - - _getAccentColor() { - return this.props.accentColor || '#ffffff'; - } - - _getBackgroundColor() { - return this.props.backgroundColor || '#000000'; - } - - _getForegroundColor() { - return this.props.foregroundColor || '#ffffff'; - } - - seekBy(time) { - const currentTime = this.audio.currentTime + time; - - if (!isNaN(currentTime)) { - this.setState({ currentTime }, () => { - this.audio.currentTime = currentTime; - }); - } - } - - handleAudioKeyDown = e => { - // On the audio element or the seek bar, we can safely use the space bar - // for playback control because there are no buttons to press - - if (e.key === ' ') { - e.preventDefault(); - e.stopPropagation(); - this.togglePlay(); - } - } - - handleKeyDown = e => { - switch (e.key) { - case 'k': - e.preventDefault(); - e.stopPropagation(); - this.togglePlay(); - break; - case 'm': - e.preventDefault(); - e.stopPropagation(); - this.toggleMute(); - break; - case 'j': - e.preventDefault(); - e.stopPropagation(); - this.seekBy(-10); - break; - case 'l': - e.preventDefault(); - e.stopPropagation(); - this.seekBy(10); - break; - } - } - - render() { - const { src, intl, alt, editable } = this.props; - const { paused, muted, volume, currentTime, buffer, dragging } = this.state; - const duration = this.state.duration || this.props.duration; - const progress = Math.min((currentTime / duration) * 100, 100); - - return ( -
-