Skip to content

Commit

Permalink
feat(controls): Add MP3 settings controls (#1343)
Browse files Browse the repository at this point in the history
  • Loading branch information
jstoffan committed Mar 16, 2021
1 parent 81ae0c4 commit 76f6e1a
Show file tree
Hide file tree
Showing 36 changed files with 316 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ import React from 'react';
import classNames from 'classnames';
import MediaSettingsContext, { Menu, Rect } from './MediaSettingsContext';
import MediaSettingsFlyout from './MediaSettingsFlyout';
import MediaSettingsMenu from './MediaSettingsMenu';
import MediaSettingsMenuBack from './MediaSettingsMenuBack';
import MediaSettingsMenuItem from './MediaSettingsMenuItem';
import MediaSettingsRadioItem from './MediaSettingsRadioItem';
import MediaSettingsToggle, { Ref as MediaSettingsToggleRef } from './MediaSettingsToggle';
import { decodeKeydown } from '../../../../util';

export type Props = React.PropsWithChildren<{
className?: string;
}>;

export default function MediaSettingsControls({ children, className, ...rest }: Props): JSX.Element | null {
export default function MediaSettings({ children, className, ...rest }: Props): JSX.Element | null {
const [activeMenu, setActiveMenu] = React.useState(Menu.MAIN);
const [activeRect, setActiveRect] = React.useState<Rect>();
const [isFocused, setIsFocused] = React.useState(false);
Expand Down Expand Up @@ -69,7 +73,7 @@ export default function MediaSettingsControls({ children, className, ...rest }:
return (
<div
ref={controlsElRef}
className={classNames('bp-MediaSettingsControls', className, { 'bp-is-focused': isFocused })}
className={classNames('bp-MediaSettings', className, { 'bp-is-focused': isFocused })}
onKeyDown={handleKeyDown}
role="presentation"
{...rest}
Expand All @@ -81,3 +85,9 @@ export default function MediaSettingsControls({ children, className, ...rest }:
</div>
);
}

MediaSettings.Context = MediaSettingsContext;
MediaSettings.Menu = MediaSettingsMenu;
MediaSettings.MenuBack = MediaSettingsMenuBack;
MediaSettings.MenuItem = MediaSettingsMenuItem;
MediaSettings.RadioItem = MediaSettingsRadioItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import MediaSettings, { Menu } from '.';

export type Props = {
autoplay: boolean;
onAutoplayChange: (autoplay: boolean) => void;
};

export default function MediaSettingsMenuAutoplay({ autoplay, onAutoplayChange }: Props): JSX.Element {
const { setActiveMenu } = React.useContext(MediaSettings.Context);

const handleChange = (value: boolean): void => {
setActiveMenu(Menu.MAIN);
onAutoplayChange(value);
};

return (
<MediaSettings.Menu name={Menu.AUTOPLAY}>
<MediaSettings.MenuBack label={__('media_autoplay')} />
<MediaSettings.RadioItem
isSelected={!autoplay}
label={__('media_autoplay_disabled')}
onChange={handleChange}
value={false}
/>
<MediaSettings.RadioItem
isSelected={autoplay}
label={__('media_autoplay_enabled')}
onChange={handleChange}
value
/>
</MediaSettings.Menu>
);
}
32 changes: 32 additions & 0 deletions src/lib/viewers/controls/media/MediaSettings/MediaSettingsRate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import MediaSettings, { Menu } from '.';

export type Props = {
onRateChange: (rate: string) => void;
rate: string;
};

export default function MediaSettingsMenuRate({ rate, onRateChange }: Props): JSX.Element {
const { setActiveMenu } = React.useContext(MediaSettings.Context);

const handleChange = (value: string): void => {
setActiveMenu(Menu.MAIN);
onRateChange(value);
};

return (
<MediaSettings.Menu name={Menu.RATE}>
<MediaSettings.MenuBack label={__('media_speed')} />
<MediaSettings.RadioItem isSelected={rate === '0.5'} onChange={handleChange} value="0.5" />
<MediaSettings.RadioItem
isSelected={rate === '1.0'}
label={__('media_speed_normal')}
onChange={handleChange}
value="1.0"
/>
<MediaSettings.RadioItem isSelected={rate === '1.25'} onChange={handleChange} value="1.25" />
<MediaSettings.RadioItem isSelected={rate === '1.5'} onChange={handleChange} value="1.5" />
<MediaSettings.RadioItem isSelected={rate === '2.0'} onChange={handleChange} value="2.0" />
</MediaSettings.Menu>
);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mount, ReactWrapper } from 'enzyme';
import MediaSettingsControls from '../MediaSettingsControls';
import MediaSettings from '../MediaSettings';
import MediaSettingsToggle from '../MediaSettingsToggle';
import MediaSettingsFlyout from '../MediaSettingsFlyout';

describe('MediaSettingsControls', () => {
describe('MediaSettings', () => {
const getHostNode = (): HTMLDivElement => {
return document.body.appendChild(document.createElement('div'));
};
const getWrapper = (props = {}): ReactWrapper =>
mount(<MediaSettingsControls {...props} />, { attachTo: getHostNode() });
const getWrapper = (props = {}): ReactWrapper => mount(<MediaSettings {...props} />, { attachTo: getHostNode() });

describe('event handlers', () => {
test('should update the flyout and toggle button isOpen prop when clicked', () => {
Expand Down Expand Up @@ -90,7 +89,7 @@ describe('MediaSettingsControls', () => {
test('should return a valid wrapper', () => {
const wrapper = getWrapper();

expect(wrapper.getDOMNode()).toHaveClass('bp-MediaSettingsControls');
expect(wrapper.getDOMNode()).toHaveClass('bp-MediaSettings');
expect(wrapper.exists(MediaSettingsFlyout)).toBe(true);
expect(wrapper.exists(MediaSettingsToggle)).toBe(true);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import MediaSettings, { Context, Menu } from '..';
import MediaSettingsAutoplay from '../MediaSettingsAutoplay';

describe('MediaSettingsAutoplay', () => {
const getContext = (): Partial<Context> => ({ setActiveMenu: jest.fn() });
const getWrapper = (props = {}, context = getContext()): ReactWrapper =>
mount(<MediaSettingsAutoplay autoplay onAutoplayChange={jest.fn()} {...props} />, {
wrappingComponent: MediaSettings.Context.Provider,
wrappingComponentProps: { value: context },
});

describe('event handlers', () => {
test('should surface the selected item on change', () => {
const onAutoplayChange = jest.fn();
const wrapper = getWrapper({ onAutoplayChange });

wrapper.find({ value: true }).simulate('click');

expect(onAutoplayChange).toBeCalledWith(true);
});

test('should reset the active menu on change', () => {
const context = getContext();
const wrapper = getWrapper({}, context);

wrapper.find({ value: true }).simulate('click');

expect(context.setActiveMenu).toBeCalledWith(Menu.MAIN);
});
});

describe('render', () => {
test('should return a valid wrapper', () => {
const wrapper = getWrapper();

expect(wrapper.exists(MediaSettings.MenuBack)).toBe(true);
expect(wrapper.exists(MediaSettings.RadioItem)).toBe(true);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import MediaSettings, { Context, Menu } from '..';
import MediaSettingsRate from '../MediaSettingsRate';

describe('MediaSettingsRate', () => {
const getContext = (): Partial<Context> => ({ setActiveMenu: jest.fn() });
const getWrapper = (props = {}, context = getContext()): ReactWrapper =>
mount(<MediaSettingsRate onRateChange={jest.fn()} rate="1.0" {...props} />, {
wrappingComponent: MediaSettings.Context.Provider,
wrappingComponentProps: { value: context },
});

describe('event handlers', () => {
test('should surface the selected item on change', () => {
const onRateChange = jest.fn();
const wrapper = getWrapper({ onRateChange });

wrapper.find({ value: '2.0' }).simulate('click');

expect(onRateChange).toBeCalledWith('2.0');
});

test('should reset the active menu on change', () => {
const context = getContext();
const wrapper = getWrapper({}, context);

wrapper.find({ value: '2.0' }).simulate('click');

expect(context.setActiveMenu).toBeCalledWith(Menu.MAIN);
});
});

describe('render', () => {
test('should return a valid wrapper', () => {
const wrapper = getWrapper();

expect(wrapper.exists(MediaSettings.MenuBack)).toBe(true);
expect(wrapper.exists(MediaSettings.RadioItem)).toBe(true);
});
});
});
3 changes: 3 additions & 0 deletions src/lib/viewers/controls/media/MediaSettings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './MediaSettings';
export * from './MediaSettingsContext';
export { default } from './MediaSettings';
3 changes: 0 additions & 3 deletions src/lib/viewers/controls/media/MediaSettingsControls/index.ts

This file was deleted.

20 changes: 18 additions & 2 deletions src/lib/viewers/media/MP3Controls.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import React from 'react';
import DurationLabels, { Props as DurationLabelsProps } from '../controls/media/DurationLabels';
import MP3Settings, { Props as MP3SettingsProps } from './MP3Settings';
import PlayPauseToggle, { Props as PlayControlsProps } from '../controls/media/PlayPauseToggle';
import TimeControls, { Props as TimeControlsProps } from '../controls/media/TimeControls';
import VolumeControls, { Props as VolumeControlsProps } from '../controls/media/VolumeControls';
import './MP3Controls.scss';

export type Props = DurationLabelsProps & PlayControlsProps & TimeControlsProps & VolumeControlsProps;
export type Props = DurationLabelsProps &
MP3SettingsProps &
PlayControlsProps &
TimeControlsProps &
VolumeControlsProps;

export default function MP3Controls({
autoplay,
bufferedRange,
currentTime,
durationTime,
isPlaying,
onAutoplayChange,
onMuteChange,
onPlayPause,
onRateChange,
onTimeChange,
onVolumeChange,
rate,
volume,
}: Props): JSX.Element {
return (
Expand All @@ -34,7 +43,14 @@ export default function MP3Controls({
<DurationLabels currentTime={currentTime} durationTime={durationTime} />
</div>

<div className="bp-MP3Controls-group">{/* MP3 Settings Controls */}</div>
<div className="bp-MP3Controls-group">
<MP3Settings
autoplay={autoplay}
onAutoplayChange={onAutoplayChange}
onRateChange={onRateChange}
rate={rate}
/>
</div>
</div>
</div>
);
Expand Down
23 changes: 23 additions & 0 deletions src/lib/viewers/media/MP3Settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import MediaSettings, { Menu } from '../controls/media/MediaSettings';
import MediaSettingsAutoplay, { Props as AutoplayProps } from '../controls/media/MediaSettings/MediaSettingsAutoplay';
import MediaSettingsRate, { Props as RateProps } from '../controls/media/MediaSettings/MediaSettingsRate';

export type Props = AutoplayProps & RateProps;

export default function MP3Settings({ autoplay, onAutoplayChange, onRateChange, rate }: Props): JSX.Element {
const autoValue = autoplay ? __('media_autoplay_enabled') : __('media_autoplay_disabled');
const rateValue = rate === '1.0' || !rate ? __('media_speed_normal') : rate;

return (
<MediaSettings className="bp-MP3Settings">
<MediaSettings.Menu name={Menu.MAIN}>
<MediaSettings.MenuItem label={__('media_autoplay')} target={Menu.AUTOPLAY} value={autoValue} />
<MediaSettings.MenuItem label={__('media_speed')} target={Menu.RATE} value={rateValue} />
</MediaSettings.Menu>

<MediaSettingsAutoplay autoplay={autoplay} onAutoplayChange={onAutoplayChange} />
<MediaSettingsRate onRateChange={onRateChange} rate={rate} />
</MediaSettings>
);
}
4 changes: 4 additions & 0 deletions src/lib/viewers/media/MP3Viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,18 @@ class MP3Viewer extends MediaBaseViewer {

this.controls.render(
<MP3Controls
autoplay={this.isAutoplayEnabled()}
bufferedRange={this.mediaEl.buffered}
currentTime={this.mediaEl.currentTime}
durationTime={this.mediaEl.duration}
isPlaying={!this.mediaEl.paused}
onAutoplayChange={this.setAutoplay}
onMuteChange={this.toggleMute}
onPlayPause={this.togglePlay}
onRateChange={this.setRate}
onTimeChange={this.handleTimeupdateFromMediaControls}
onVolumeChange={this.setVolume}
rate={this.getRate()}
volume={this.mediaEl.volume}
/>,
);
Expand Down

0 comments on commit 76f6e1a

Please sign in to comment.