forked from fedi/mastodon
Change volume control and download buttons in web UI (#14122)
* Fix audio download button not starting download in web UI * Fix volume controls on audio and video players in web UI * Remove download button from video player in web UI
This commit is contained in:
parent
aaf91abffa
commit
419ad6248b
|
@ -347,9 +347,8 @@ class Status extends ImmutablePureComponent {
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
poster={status.getIn(['account', 'avatar_static'])}
|
poster={status.getIn(['account', 'avatar_static'])}
|
||||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
||||||
peaks={[0]}
|
|
||||||
width={this.props.cachedMediaWidth}
|
width={this.props.cachedMediaWidth}
|
||||||
height={70}
|
height={110}
|
||||||
cacheWidth={this.props.cacheMediaWidth}
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Icon from 'mastodon/components/icon';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import { encode, decode } from 'blurhash';
|
import { encode, decode } from 'blurhash';
|
||||||
import { getPointerPosition } from 'mastodon/features/video';
|
import { getPointerPosition, fileNameFromURL } from 'mastodon/features/video';
|
||||||
|
|
||||||
const digitCharacters = [
|
const digitCharacters = [
|
||||||
'0',
|
'0',
|
||||||
|
@ -150,10 +150,8 @@ class Audio extends React.PureComponent {
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
poster: PropTypes.string,
|
poster: PropTypes.string,
|
||||||
duration: PropTypes.number,
|
duration: PropTypes.number,
|
||||||
peaks: PropTypes.arrayOf(PropTypes.number),
|
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
preload: PropTypes.bool,
|
|
||||||
editable: PropTypes.bool,
|
editable: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
cacheWidth: PropTypes.func,
|
cacheWidth: PropTypes.func,
|
||||||
|
@ -171,17 +169,6 @@ class Audio extends React.PureComponent {
|
||||||
color: { r: 255, g: 255, b: 255 },
|
color: { r: 255, g: 255, b: 255 },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hard coded in components.scss
|
|
||||||
// Any way to get ::before values programatically?
|
|
||||||
volWidth = 50;
|
|
||||||
volOffset = 70;
|
|
||||||
|
|
||||||
volHandleOffset = v => {
|
|
||||||
const offset = v * this.volWidth + this.volOffset;
|
|
||||||
|
|
||||||
return (offset > 110) ? 110 : offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlayerRef = c => {
|
setPlayerRef = c => {
|
||||||
this.player = c;
|
this.player = c;
|
||||||
|
|
||||||
|
@ -364,20 +351,11 @@ class Audio extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseVolSlide = throttle(e => {
|
handleMouseVolSlide = throttle(e => {
|
||||||
const rect = this.volume.getBoundingClientRect();
|
const { x } = getPointerPosition(this.volume, e);
|
||||||
const x = (e.clientX - rect.left) / this.volWidth; // x position within the element.
|
|
||||||
|
|
||||||
if(!isNaN(x)) {
|
if(!isNaN(x)) {
|
||||||
let slideamt = x;
|
this.setState({ volume: x }, () => {
|
||||||
|
this.audio.volume = x;
|
||||||
if (x > 1) {
|
|
||||||
slideamt = 1;
|
|
||||||
} else if(x < 0) {
|
|
||||||
slideamt = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ volume: slideamt }, () => {
|
|
||||||
this.audio.volume = slideamt;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 60);
|
}, 60);
|
||||||
|
@ -395,6 +373,14 @@ class Audio extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}, 150, { trailing: true });
|
}, 150, { trailing: true });
|
||||||
|
|
||||||
|
handleMouseEnter = () => {
|
||||||
|
this.setState({ hovered: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave = () => {
|
||||||
|
this.setState({ hovered: false });
|
||||||
|
}
|
||||||
|
|
||||||
_initAudioContext () {
|
_initAudioContext () {
|
||||||
const context = new AudioContext();
|
const context = new AudioContext();
|
||||||
const analyser = context.createAnalyser();
|
const analyser = context.createAnalyser();
|
||||||
|
@ -430,6 +416,24 @@ class Audio extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 () {
|
_renderCanvas () {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this._clear();
|
this._clear();
|
||||||
|
@ -593,13 +597,10 @@ class Audio extends React.PureComponent {
|
||||||
render () {
|
render () {
|
||||||
const { src, intl, alt, editable } = this.props;
|
const { src, intl, alt, editable } = this.props;
|
||||||
const { paused, muted, volume, currentTime, duration, buffer, darkText, dragging } = this.state;
|
const { paused, muted, volume, currentTime, duration, buffer, darkText, dragging } = this.state;
|
||||||
|
|
||||||
const volumeWidth = muted ? 0 : volume * this.volWidth;
|
|
||||||
const volumeHandleLoc = muted ? this.volHandleOffset(0) : this.volHandleOffset(volume);
|
|
||||||
const progress = (currentTime / duration) * 100;
|
const progress = (currentTime / duration) * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('audio-player', { editable, 'with-light-background': darkText })} ref={this.setPlayerRef} style={{ width: '100%', height: this.state.height || this.props.height }}>
|
<div className={classNames('audio-player', { editable, 'with-light-background': darkText })} ref={this.setPlayerRef} style={{ width: '100%', height: this.state.height || this.props.height }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
<audio
|
<audio
|
||||||
src={src}
|
src={src}
|
||||||
ref={this.setAudioRef}
|
ref={this.setAudioRef}
|
||||||
|
@ -657,18 +658,17 @@ class Audio extends React.PureComponent {
|
||||||
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
||||||
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
||||||
|
|
||||||
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
<div className={classNames('video-player__volume', { active: this.state.hovered })} ref={this.setVolumeRef} onMouseDown={this.handleVolumeMouseDown}>
|
||||||
|
<div className='video-player__volume__current' style={{ width: `${volume * 100}%`, backgroundColor: this._getColor() }} />
|
||||||
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px`, backgroundColor: this._getColor() }} />
|
|
||||||
|
|
||||||
<span
|
<span
|
||||||
className={classNames('video-player__volume__handle')}
|
className={classNames('video-player__volume__handle')}
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
style={{ left: `${volumeHandleLoc}px`, backgroundColor: this._getColor() }}
|
style={{ left: `${volume * 100}%`, backgroundColor: this._getColor() }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span>
|
<span className='video-player__time'>
|
||||||
<span className='video-player__time-current'>{formatTime(currentTime)}</span>
|
<span className='video-player__time-current'>{formatTime(currentTime)}</span>
|
||||||
<span className='video-player__time-sep'>/</span>
|
<span className='video-player__time-sep'>/</span>
|
||||||
<span className='video-player__time-total'>{formatTime(this.state.duration || Math.floor(this.props.duration))}</span>
|
<span className='video-player__time-total'>{formatTime(this.state.duration || Math.floor(this.props.duration))}</span>
|
||||||
|
@ -676,11 +676,7 @@ class Audio extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='video-player__buttons right'>
|
<div className='video-player__buttons right'>
|
||||||
<button type='button' title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)}>
|
<button type='button' title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} onClick={this.handleDownload}><Icon id='download' fixedWidth /></button>
|
||||||
<a className='video-player__download__icon' href={this.props.src} download>
|
|
||||||
<Icon id='download' fixedWidth />
|
|
||||||
</a>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -118,8 +118,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
||||||
poster={status.getIn(['account', 'avatar_static'])}
|
poster={status.getIn(['account', 'avatar_static'])}
|
||||||
height={110}
|
height={150}
|
||||||
preload
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
|
|
|
@ -19,7 +19,6 @@ const messages = defineMessages({
|
||||||
close: { id: 'video.close', defaultMessage: 'Close video' },
|
close: { id: 'video.close', defaultMessage: 'Close video' },
|
||||||
fullscreen: { id: 'video.fullscreen', defaultMessage: 'Full screen' },
|
fullscreen: { id: 'video.fullscreen', defaultMessage: 'Full screen' },
|
||||||
exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
|
exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
|
||||||
download: { id: 'video.download', defaultMessage: 'Download file' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const formatTime = secondsNum => {
|
export const formatTime = secondsNum => {
|
||||||
|
@ -87,6 +86,14 @@ export const getPointerPosition = (el, event) => {
|
||||||
return position;
|
return position;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fileNameFromURL = str => {
|
||||||
|
const url = new URL(str);
|
||||||
|
const pathname = url.pathname;
|
||||||
|
const index = pathname.lastIndexOf('/');
|
||||||
|
|
||||||
|
return pathname.substring(index + 1);
|
||||||
|
};
|
||||||
|
|
||||||
export default @injectIntl
|
export default @injectIntl
|
||||||
class Video extends React.PureComponent {
|
class Video extends React.PureComponent {
|
||||||
|
|
||||||
|
@ -126,17 +133,6 @@ class Video extends React.PureComponent {
|
||||||
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
|
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hard-coded in components.scss
|
|
||||||
// Any way to get ::before values programatically?
|
|
||||||
volWidth = 50;
|
|
||||||
volOffset = 70;
|
|
||||||
|
|
||||||
volHandleOffset = v => {
|
|
||||||
const offset = v * this.volWidth + this.volOffset;
|
|
||||||
|
|
||||||
return (offset > 110) ? 110 : offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlayerRef = c => {
|
setPlayerRef = c => {
|
||||||
this.player = c;
|
this.player = c;
|
||||||
|
|
||||||
|
@ -206,20 +202,12 @@ class Video extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseVolSlide = throttle(e => {
|
handleMouseVolSlide = throttle(e => {
|
||||||
const rect = this.volume.getBoundingClientRect();
|
const { x } = getPointerPosition(this.volume, e);
|
||||||
const x = (e.clientX - rect.left) / this.volWidth; //x position within the element.
|
|
||||||
|
|
||||||
if(!isNaN(x)) {
|
if(!isNaN(x)) {
|
||||||
let slideamt = x;
|
this.setState({ volume: x }, () => {
|
||||||
|
this.video.volume = x;
|
||||||
if(x > 1) {
|
});
|
||||||
slideamt = 1;
|
|
||||||
} else if(x < 0) {
|
|
||||||
slideamt = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.video.volume = slideamt;
|
|
||||||
this.setState({ volume: slideamt });
|
|
||||||
}
|
}
|
||||||
}, 60);
|
}, 60);
|
||||||
|
|
||||||
|
@ -421,9 +409,6 @@ class Video extends React.PureComponent {
|
||||||
const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, link, editable } = this.props;
|
const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, link, editable } = this.props;
|
||||||
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||||
const progress = (currentTime / duration) * 100;
|
const progress = (currentTime / duration) * 100;
|
||||||
|
|
||||||
const volumeWidth = (muted) ? 0 : volume * this.volWidth;
|
|
||||||
const volumeHandleLoc = (muted) ? this.volHandleOffset(0) : this.volHandleOffset(volume);
|
|
||||||
const playerStyle = {};
|
const playerStyle = {};
|
||||||
|
|
||||||
let { width, height } = this.props;
|
let { width, height } = this.props;
|
||||||
|
@ -510,18 +495,18 @@ class Video extends React.PureComponent {
|
||||||
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay} autoFocus={detailed}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay} autoFocus={detailed}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
||||||
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
||||||
|
|
||||||
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
<div className={classNames('video-player__volume', { active: this.state.hovered })} onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
||||||
|
<div className='video-player__volume__current' style={{ width: `${volume * 100}%` }} />
|
||||||
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
|
|
||||||
<span
|
<span
|
||||||
className={classNames('video-player__volume__handle')}
|
className={classNames('video-player__volume__handle')}
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
style={{ left: `${volumeHandleLoc}px` }}
|
style={{ left: `${volume * 100}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(detailed || fullscreen) && (
|
{(detailed || fullscreen) && (
|
||||||
<span>
|
<span className='video-player__time'>
|
||||||
<span className='video-player__time-current'>{formatTime(currentTime)}</span>
|
<span className='video-player__time-current'>{formatTime(currentTime)}</span>
|
||||||
<span className='video-player__time-sep'>/</span>
|
<span className='video-player__time-sep'>/</span>
|
||||||
<span className='video-player__time-total'>{formatTime(duration)}</span>
|
<span className='video-player__time-total'>{formatTime(duration)}</span>
|
||||||
|
@ -535,7 +520,6 @@ class Video extends React.PureComponent {
|
||||||
{(!onCloseVideo && !editable && !fullscreen) && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
{(!onCloseVideo && !editable && !fullscreen) && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
||||||
{(!fullscreen && onOpenVideo) && <button type='button' title={intl.formatMessage(messages.expand)} aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
{(!fullscreen && onOpenVideo) && <button type='button' title={intl.formatMessage(messages.expand)} aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
||||||
{onCloseVideo && <button type='button' title={intl.formatMessage(messages.close)} aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
{onCloseVideo && <button type='button' title={intl.formatMessage(messages.close)} aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
||||||
<button type='button' title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)}><a className='video-player__download__icon' href={this.props.src} download><Icon id={'download'} fixedWidth /></a></button>
|
|
||||||
<button type='button' title={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5309,13 +5309,21 @@ a.status-card.compact:hover {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.with-light-background {
|
.video-player__volume::before,
|
||||||
.video-player__seek::before {
|
.video-player__seek::before {
|
||||||
color: rgba($black, 0.35);
|
background: rgba($white, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-player__seek__seek {
|
&.with-light-background {
|
||||||
color: rgba($black, 0.2);
|
color: $black;
|
||||||
|
|
||||||
|
.video-player__volume::before,
|
||||||
|
.video-player__seek::before {
|
||||||
|
background: rgba($black, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player__seek__buffer {
|
||||||
|
background: rgba($black, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-player__buttons button {
|
.video-player__buttons button {
|
||||||
|
@ -5333,10 +5341,6 @@ a.status-card.compact:hover {
|
||||||
.video-player__time-current {
|
.video-player__time-current {
|
||||||
color: $black;
|
color: $black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-player__volume::before {
|
|
||||||
background: rgba($black, 0.35);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-player__seek::before,
|
.video-player__seek::before,
|
||||||
|
@ -5364,6 +5368,7 @@ a.status-card.compact:hover {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
|
color: $white;
|
||||||
|
|
||||||
&.editable {
|
&.editable {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
@ -5476,6 +5481,10 @@ a.status-card.compact:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
&__buttons {
|
&__buttons {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 30px;
|
||||||
|
align-items: center;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -5494,6 +5503,7 @@ a.status-card.compact:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
flex: 0 0 auto;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
@ -5508,6 +5518,13 @@ a.status-card.compact:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__time {
|
||||||
|
display: inline;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
&__time-sep,
|
&__time-sep,
|
||||||
&__time-total,
|
&__time-total,
|
||||||
&__time-current {
|
&__time-current {
|
||||||
|
@ -5517,7 +5534,6 @@ a.status-card.compact:hover {
|
||||||
|
|
||||||
&__time-current {
|
&__time-current {
|
||||||
color: $white;
|
color: $white;
|
||||||
margin-left: 60px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__time-sep {
|
&__time-sep {
|
||||||
|
@ -5531,9 +5547,22 @@ a.status-card.compact:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
&__volume {
|
&__volume {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: inline-flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
display: inline;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.no-reduce-motion & {
|
||||||
|
transition: all 100ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
overflow: visible;
|
||||||
|
width: 50px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
|
@ -5543,8 +5572,9 @@ a.status-card.compact:hover {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
left: 70px;
|
left: 0;
|
||||||
bottom: 20px;
|
top: 50%;
|
||||||
|
transform: translate(0, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__current {
|
&__current {
|
||||||
|
@ -5552,8 +5582,9 @@ a.status-card.compact:hover {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
left: 70px;
|
left: 0;
|
||||||
bottom: 20px;
|
top: 50%;
|
||||||
|
transform: translate(0, -50%);
|
||||||
background: lighten($ui-highlight-color, 8%);
|
background: lighten($ui-highlight-color, 8%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5563,8 +5594,10 @@ a.status-card.compact:hover {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
bottom: 16px;
|
top: 50%;
|
||||||
left: 70px;
|
left: 0;
|
||||||
|
margin-left: -6px;
|
||||||
|
transform: translate(0, -50%);
|
||||||
transition: opacity .1s ease;
|
transition: opacity .1s ease;
|
||||||
background: lighten($ui-highlight-color, 8%);
|
background: lighten($ui-highlight-color, 8%);
|
||||||
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
|
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
|
||||||
|
|
Loading…
Reference in a new issue