@microlink/vanilla
Version:
Turn links into beautiful previews.
1,787 lines (1,720 loc) • 68.5 kB
JavaScript
import React, { useContext, forwardRef, createElement, useRef, useMemo, useCallback, useLayoutEffect, useState, useEffect } from 'react';
import { css, styled, keyframes } from 'styled-components';
import { fetchFromApi, getApiUrl as getApiUrl$1 } from '@microlink/mql';
import { createRoot } from 'react-dom';
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r);
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
function toPropertyKey(t) {
var i = toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _defineProperty(e, r, t) {
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: true,
configurable: true,
writable: true
}) : e[r] = t, e;
}
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (-1 !== e.indexOf(n)) continue;
t[n] = r[n];
}
return t;
}
function _objectWithoutProperties(e, t) {
if (null == e) return {};
var o,
r,
i = _objectWithoutPropertiesLoose(e, t);
if (Object.getOwnPropertySymbols) {
var n = Object.getOwnPropertySymbols(e);
for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]);
}
return i;
}
const _excluded$8 = ["accessibility", "debounce", "ellipsis", "is", "lines", "text"];
function ownKeys$4(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$4(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$4(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$4(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
const i = _ref => {
let {
accessibility: i = true,
debounce: u = 300,
ellipsis: c = "…",
is: o = "div",
lines: s = 3,
text: f
} = _ref,
d = _objectWithoutProperties(_ref, _excluded$8);
const v = useRef(null),
a = useRef("."),
m = _objectSpread$4(_objectSpread$4({
ref: v
}, i ? {
title: f
} : {}), d),
b = useMemo(() => "string" == typeof f && f.length > 0, [f]),
g = useCallback(() => {
if (!b) return;
const t = t => {
a.current = t, null != v.current && (v.current.textContent = t);
},
e = () => {
var t, e;
return null !== (e = null === (t = v.current) || void 0 === t ? void 0 : t.clientHeight) && void 0 !== e ? e : 0;
};
t(".");
const r = (e() + 1) * s + 1;
if (t(f), e() <= r) return;
let n = 0,
l = 0,
i = f.length;
for (; n <= i;) {
l = Math.floor((n + i) / 2);
t(f.slice(0, l).trim() + c), e() <= r ? n = l + 1 : i = l - 1;
}
t(f.slice(0, l - 1).trim() + c);
}, [c, b, s, f]);
return useLayoutEffect(() => {
if (g(), null == v.current) return;
const t = new ResizeObserver(((t, e) => {
let r;
const n = () => {
r = void 0, t();
};
return () => {
const l = null == r;
clearTimeout(r), r = setTimeout(n, e), l && t();
};
})(g, u));
return t.observe(v.current), () => t.disconnect();
}, [g, u]), b ? /*#__PURE__*/createElement(o, m, a.current) : null;
};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var ipv4 = {exports: {}};
var hasRequiredIpv4;
function requireIpv4 () {
if (hasRequiredIpv4) return ipv4.exports;
hasRequiredIpv4 = 1;
const IP_RANGES = [
// 10.0.0.0 - 10.255.255.255
/^(:{2}f{4}:)?10(?:\.\d{1,3}){3}$/,
// 127.0.0.0 - 127.255.255.255
/^(:{2}f{4}:)?127(?:\.\d{1,3}){3}$/,
// 169.254.1.0 - 169.254.254.255
/^(::f{4}:)?169\.254\.([1-9]|1?\d\d|2[0-4]\d|25[0-4])\.\d{1,3}$/,
// 172.16.0.0 - 172.31.255.255
/^(:{2}f{4}:)?(172\.1[6-9]|172\.2\d|172\.3[01])(?:\.\d{1,3}){2}$/,
// 192.168.0.0 - 192.168.255.255
/^(:{2}f{4}:)?192\.168(?:\.\d{1,3}){2}$/,
// fc00::/7
/^f[cd][\da-f]{2}(::1$|:[\da-f]{1,4}){1,7}$/,
// fe80::/10s
/^fe[89ab][\da-f](::1$|:[\da-f]{1,4}){1,7}$/,
// localhost in IPv4
/^localhost$|^0\.0\.0\.0$/];
const regex = new RegExp(`^(${IP_RANGES.map(re => re.source).join('|')})$`);
ipv4.exports = regex.test.bind(regex);
ipv4.exports.regex = regex;
return ipv4.exports;
}
var ipv6 = {exports: {}};
var hasRequiredIpv6;
function requireIpv6 () {
if (hasRequiredIpv6) return ipv6.exports;
hasRequiredIpv6 = 1;
const IP_RANGES = [
// localhost in IPv6
/^\[(::1|::)\]$/];
const regex = new RegExp(`^(${IP_RANGES.map(re => re.source).join('|')})$`);
ipv6.exports = regex.test.bind(regex);
ipv6.exports.regex = regex;
return ipv6.exports;
}
var src;
var hasRequiredSrc;
function requireSrc () {
if (hasRequiredSrc) return src;
hasRequiredSrc = 1;
src = hostname => requireIpv4()(hostname) || requireIpv6()(hostname);
return src;
}
var srcExports = requireSrc();
var isLocalAddress = /*@__PURE__*/getDefaultExportFromCjs(srcExports);
const isSSR = typeof window === 'undefined';
const castArray = value => [].concat(value);
const getPreferredMedia = (data, mediaProps) => {
let prefer;
for (let index = 0; index < mediaProps.length; index++) {
const key = mediaProps[index];
const value = data[key];
if (!isNil(value)) {
prefer = key;
break;
}
}
return prefer;
};
const isFunction = fn => typeof fn === 'function';
const isObject = obj => typeof obj === 'object';
const isNil = value => value == null;
const getUrlPath = data => isObject(data) ? data.url : data;
const someProp = (data, props) => data[props.find(prop => !isNil(data[prop]))];
const media = {
mobile: function () {
return css`
@media (max-width: 48em) {
${css(...arguments)};
}
`;
},
desktop: function () {
return css`
@media (min-width: 48em) {
${css(...arguments)};
}
`;
}
};
const getApiUrl = _ref => {
let {
apiKey,
contrast = false,
data,
endpoint,
force,
headers,
media,
prerender,
proxy,
ttl,
url
} = _ref;
return getApiUrl$1(url, {
apiKey,
audio: media.includes('audio'),
data,
endpoint,
force,
headers,
iframe: media.includes('iframe'),
palette: contrast,
prerender,
proxy,
screenshot: media.includes('screenshot'),
ttl,
video: media.includes('video')
});
};
const isLarge = cardSize => cardSize === 'large';
const isSmall = cardSize => cardSize === 'small';
const imageProxy = url => isLocalAddress(new URL(url).hostname) ? url : `https://images.weserv.nl/?${new URLSearchParams({
url,
default: url,
l: 9,
af: '',
il: '',
n: -1
}).toString()}`;
const isLazySupported = !isSSR && 'IntersectionObserver' in window;
const formatSeconds = secs => {
const secsToNum = parseInt(secs, 10);
const hours = Math.floor(secsToNum / 3600);
const minutes = Math.floor(secsToNum / 60) % 60;
const seconds = secsToNum % 60;
return [hours, minutes, seconds].filter((v, i) => v > 0 || i > 0).map(v => v >= 10 ? v : `0${v}`).join(':');
};
const clampNumber = (num, min, max) => {
switch (true) {
case num <= min:
return min;
case num >= max:
return max;
default:
return num;
}
};
const BASE_CLASSNAME = 'microlink_card';
const CONTENT_BASE_CLASSNAME = `${BASE_CLASSNAME}__content`;
const MEDIA_BASE_CLASSNAME = `${BASE_CLASSNAME}__media`;
const CONTROLS_BASE_CLASSNAME = `${MEDIA_BASE_CLASSNAME}__controls`;
const classNames = {
main: BASE_CLASSNAME,
content: CONTENT_BASE_CLASSNAME,
title: `${CONTENT_BASE_CLASSNAME}_title`,
description: `${CONTENT_BASE_CLASSNAME}_description`,
url: `${CONTENT_BASE_CLASSNAME}_url`,
mediaWrapper: `${MEDIA_BASE_CLASSNAME}_wrapper`,
media: MEDIA_BASE_CLASSNAME,
image: `${MEDIA_BASE_CLASSNAME}_image`,
videoWrapper: `${MEDIA_BASE_CLASSNAME}_video_wrapper`,
video: `${MEDIA_BASE_CLASSNAME}_video`,
audioWrapper: `${MEDIA_BASE_CLASSNAME}_audio_wrapper`,
audio: `${MEDIA_BASE_CLASSNAME}_audio`,
mediaControls: CONTROLS_BASE_CLASSNAME,
playbackControl: `${CONTROLS_BASE_CLASSNAME}_playback`,
volumeControl: `${CONTROLS_BASE_CLASSNAME}_volume`,
rwControl: `${CONTROLS_BASE_CLASSNAME}_rewind`,
ffwControl: `${CONTROLS_BASE_CLASSNAME}_fast_forward`,
rateControl: `${CONTROLS_BASE_CLASSNAME}_rate`,
progressBar: `${CONTROLS_BASE_CLASSNAME}_progress_bar`,
progressTime: `${CONTROLS_BASE_CLASSNAME}_progress_time`,
spinner: `${CONTROLS_BASE_CLASSNAME}_spinner`,
iframe: `${BASE_CLASSNAME}__iframe`
};
const _excluded$7 = ["$useNanoClamp", "children"];
function ownKeys$3(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$3(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$3(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$3(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
const Clamp = _ref => {
let {
children,
className,
lines
} = _ref;
return isNil(children) ? null : /*#__PURE__*/React.createElement(i, {
className: className,
lines: lines,
text: children,
is: "p"
});
};
const StyledClamp = styled(Clamp)`
&&& {
text-align: inherit;
font-weight: inherit;
font-family: inherit;
color: inherit;
margin: 0;
${_ref2 => {
let {
$useNanoClamp
} = _ref2;
return !$useNanoClamp && css`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`;
}}
}
`;
const CardText = _ref3 => {
let {
$useNanoClamp = true,
children
} = _ref3,
props = _objectWithoutProperties(_ref3, _excluded$7);
const textProps = $useNanoClamp ? props : _objectSpread$3(_objectSpread$3({}, props), {}, {
as: 'p',
title: children
});
return /*#__PURE__*/React.createElement(StyledClamp, _extends({
$useNanoClamp: $useNanoClamp
}, textProps), children);
};
const speed = {
short: '100ms',
medium: '150ms',
long: '300ms'
};
const animation = {
short: 'cubic-bezier(.25,.8,.25,1)',
medium: 'cubic-bezier(.25,.8,.25,1)',
long: 'cubic-bezier(.4, 0, .2, 1)'
};
const createTransition = (properties, s) => {
const suffix = `${speed[s]} ${animation[s]}`;
return properties.map(property => `${property} ${suffix}`).join(', ');
};
const transition = {
short: function () {
for (var _len = arguments.length, properties = new Array(_len), _key = 0; _key < _len; _key++) {
properties[_key] = arguments[_key];
}
return createTransition(properties, 'short');
},
medium: function () {
for (var _len2 = arguments.length, properties = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
properties[_key2] = arguments[_key2];
}
return createTransition(properties, 'medium');
},
long: function () {
for (var _len3 = arguments.length, properties = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
properties[_key3] = arguments[_key3];
}
return createTransition(properties, 'long');
}
};
// https://primer.style/design/foundations/typography
const font = {
sans: "InterUI, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif",
mono: "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace"
};
const _excluded$6 = ["autoPlay", "children", "controls", "loop", "mediaRef", "muted", "playsInline", "size"];
function ownKeys$2(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$2(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$2(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
const initialState = {};
const GlobalContext = /*#__PURE__*/React.createContext(initialState);
const GlobalState = _ref => {
let {
autoPlay,
children,
controls,
loop,
mediaRef,
muted,
playsInline,
size
} = _ref,
rest = _objectWithoutProperties(_ref, _excluded$6);
const [state, setState] = useState(initialState);
const updateState = useCallback(newState => setState(currentState => _objectSpread$2(_objectSpread$2({}, currentState), newState)), []);
const props = useMemo(() => ({
autoPlay,
controls,
loop,
mediaRef,
muted,
playsInline,
size
}), [autoPlay, controls, loop, mediaRef, muted, playsInline, size]);
const values = useMemo(() => ({
props,
state,
updateState
}), [props, state, updateState]);
return /*#__PURE__*/React.createElement(GlobalContext.Provider, {
value: values
}, children(rest));
};
/* global URL */
const REGEX_STRIP_WWW = /^www\./;
const BADGE_WIDTH = '16px';
const BADGE_HEIGHT = '12px';
const getHostname = href => {
if (isNil(href)) return '';
const {
hostname
} = new URL(href);
return hostname.replace(REGEX_STRIP_WWW, '');
};
const mobileDescriptionStyle = css`
${media.mobile`
> p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`};
`;
const Content = styled('div').attrs({
className: classNames.content
})`
display: flex;
padding: 10px 15px;
min-width: 0;
box-sizing: border-box;
${_ref => {
let {
$cardSize
} = _ref;
return css`
flex: ${!isLarge($cardSize) ? 1 : '0 0 125px'};
justify-content: ${!isSmall($cardSize) ? 'space-around' : 'space-between'};
flex-direction: ${!isSmall($cardSize) ? 'column' : 'row'};
align-items: ${!isSmall($cardSize) ? 'stretch' : 'center'};
`;
}};
`;
const Header = styled('header').attrs({
className: classNames.title
})`
text-align: left;
font-weight: bold;
margin: 0;
width: 100%;
${_ref2 => {
let {
$cardSize
} = _ref2;
return css`
flex-grow: ${!isSmall($cardSize) ? 1.2 : 0.8};
font-size: ${!isSmall($cardSize) ? '16px' : '15px'};
${isSmall($cardSize) && css`
min-width: 0;
padding-right: 14px;
`}
`;
}}
`;
const Description = styled('div').attrs({
className: classNames.description
})`
text-align: left;
font-size: 14px;
flex-grow: 2;
margin: auto 0;
line-height: 18px;
font-weight: normal;
${_ref3 => {
let {
$cardSize
} = _ref3;
return !isLarge($cardSize) && mobileDescriptionStyle;
}};
`;
const Footer = styled('footer').attrs({
className: classNames.url
})`
display: flex;
align-items: center;
justify-content: space-between;
text-align: left;
margin: 0;
flex-grow: 0;
font-weight: normal;
${_ref4 => {
let {
$cardSize
} = _ref4;
return css`
font-size: ${!isSmall($cardSize) ? '12px' : '10px'};
${!isSmall($cardSize) && 'width: 100%;'}
`;
}};
`;
const Author = styled(CardText)`
opacity: 0.75;
transition: ${transition.medium('opacity')};
will-change: opacity;
.${classNames.main}:hover & {
opacity: 1;
}
`;
const PoweredBy = styled('span').attrs({
title: 'microlink.io'
})`
background: url('https://cdn.microlink.io/logo/logo.svg') no-repeat center
center;
display: block;
margin-left: 15px;
transition: ${transition.medium('filter', 'opacity')};
will-change: filter, opacity;
&:not(:hover) {
filter: grayscale(100%);
opacity: 0.75;
}
min-width: ${BADGE_WIDTH};
width: ${BADGE_WIDTH};
background-size: ${BADGE_WIDTH};
height: ${BADGE_HEIGHT};
`;
const CardContent = () => {
const {
state: {
description,
title,
url
},
props: {
size
}
} = useContext(GlobalContext);
const isSmallCard = isSmall(size);
const formattedUrl = useMemo(() => getHostname(url), [url]);
const handleOnClick = useCallback(e => {
e.preventDefault();
window.open('https://www.microlink.io', '_blank');
}, []);
return /*#__PURE__*/React.createElement(Content, {
$cardSize: size
}, /*#__PURE__*/React.createElement(Header, {
$cardSize: size
}, /*#__PURE__*/React.createElement(CardText, {
$useNanoClamp: false
}, title)), !isSmallCard && /*#__PURE__*/React.createElement(Description, {
$cardSize: size
}, /*#__PURE__*/React.createElement(CardText, {
lines: 2
}, description)), /*#__PURE__*/React.createElement(Footer, {
$cardSize: size
}, /*#__PURE__*/React.createElement(Author, {
$useNanoClamp: false
}, formattedUrl), /*#__PURE__*/React.createElement(PoweredBy, {
onClick: handleOnClick
})));
};
const emptyStatePulse = keyframes`
0% {
background: #e1e8ed;
}
70% {
background: #cdd4d8;
}
100% {
background: #e1e8ed;
}
`;
const emptyStateImagePulse = keyframes`
0% {
background: #e1e8ed;
}
70% {
background: #dce3e8;
}
100% {
background: #e1e8ed;
}
`;
const emptyStateAnimation = css`
animation: ${emptyStatePulse} .75s linear infinite;
`;
const emptyStateImageAnimation = css`
animation: ${emptyStateImagePulse} 1.25s linear infinite;
`;
const ImageLoadCatcher = styled('img')`
height: 1px;
width: 1px;
position: absolute;
z-index: -1;
`;
const loadingOverlay = css`
&::after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: #e1e8ed;
z-index: 1;
transition: ${transition.medium('opacity', 'visibility')};
will-change: opacity;
${_ref => {
let {
$isLoading
} = _ref;
return css`
opacity: ${$isLoading ? 1 : 0};
visibility: ${$isLoading ? '$visible' : 'hidden'};
`;
}};
}
`;
const mediaSizeStyles = {
small: css`
flex: 0 0 48px;
`,
normal: css`
flex: 0 0 125px;
${media.mobile`
flex: 0 0 92px;
`}
`,
large: css`
flex: 1;
&::before {
padding-bottom: 0;
}
`
};
const StyledWrap = styled('div')`
background: transparent no-repeat center center / cover;
display: block;
overflow: hidden;
height: auto;
position: relative;
&::before {
content: '';
padding-bottom: 100%;
display: block;
}
${_ref => {
let {
$cardSize
} = _ref;
return mediaSizeStyles[$cardSize];
}};
${loadingOverlay};
`;
const Wrap$1 = props => {
const {
props: {
size
}
} = useContext(GlobalContext);
return /*#__PURE__*/React.createElement(StyledWrap, _extends({
$cardSize: size
}, props));
};
const ImageWrap = styled(Wrap$1).attrs({
className: `${classNames.media} ${classNames.image}`
})`
background-image: ${_ref => {
let {
$url
} = _ref;
return $url ? `url('${imageProxy($url)}')` : '';
}};
`;
const ImageComponent = props => {
const {
state: {
imageUrl
}
} = useContext(GlobalContext);
return /*#__PURE__*/React.createElement(ImageWrap, _extends({
$url: imageUrl
}, props));
};
var _FooterEmpty;
const MediaEmpty = styled(ImageComponent)`
${emptyStateImageAnimation};
`;
const HeaderEmpty = styled('span')`
opacity: 0.8;
height: 16px;
width: ${_ref => {
let {
$cardSize
} = _ref;
return !isSmall($cardSize) ? '60%' : '75%';
}};
display: block;
background: #e1e8ed;
margin: ${_ref2 => {
let {
$cardSize
} = _ref2;
return !isSmall($cardSize) ? '2px 0 8px' : '0 20px 0 0';
}};
${emptyStateAnimation};
${_ref3 => {
let {
$cardSize
} = _ref3;
return !isLarge($cardSize) && `
height: 15px;
`;
}};
`;
const DescriptionEmpty = styled('span')`
opacity: 0.8;
height: 14px;
width: 95%;
display: block;
position: relative;
${emptyStateAnimation};
animation-delay: 0.125s;
`;
const FooterEmpty = styled('span')`
opacity: 0.8;
height: 12px;
width: 30%;
display: block;
${emptyStateAnimation} animation-delay: .25s;
${_ref4 => {
let {
$cardSize
} = _ref4;
return !isLarge($cardSize) && `
height: 10px;
`;
}};
`;
const CardEmptyState = () => {
const {
props: {
size
}
} = useContext(GlobalContext);
const isSmallCard = isSmall(size);
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MediaEmpty, {
$cardSize: size
}), /*#__PURE__*/React.createElement(Content, {
$cardSize: size
}, /*#__PURE__*/React.createElement(HeaderEmpty, {
$cardSize: size
}), !isSmallCard ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(DescriptionEmpty, {
$cardSize: size
}), /*#__PURE__*/React.createElement(DescriptionEmpty, {
$cardSize: size,
style: {
marginBottom: '12px'
}
})) : null, _FooterEmpty || (_FooterEmpty = /*#__PURE__*/React.createElement(FooterEmpty, null))));
};
const MediaButton = styled('div')`
backface-visibility: hidden;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
transition: ${transition.short('transform')};
will-change: transform;
> svg {
display: block;
}
&:active:not(:focus) {
transform: scale(0.9);
}
`;
var _path$2, _path2$2;
const VolumeMute = props => /*#__PURE__*/React.createElement("svg", _extends({
xmlns: "http://www.w3.org/2000/svg",
viewBox: "0 0 14 14"
}, props), _path$2 || (_path$2 = /*#__PURE__*/React.createElement("path", {
fill: "#FFF",
fillRule: "evenodd",
stroke: "none",
strokeWidth: "1",
d: "M15.5 6.205l-.705-.705L13 7.295 11.205 5.5l-.705.705L12.295 8 10.5 9.795l.705.705L13 8.705l1.795 1.795.705-.705L13.705 8 15.5 6.205zM9 15a.5.5 0 01-.355-.15L4.835 11H1.5a.5.5 0 01-.5-.5v-5a.5.5 0 01.5-.5h3.335l3.81-3.85a.5.5 0 01.705 0 .5.5 0 01.15.35v13a.5.5 0 01-.5.5z",
transform: "translate(-1 -1)"
})));
const VolumeUp = props => /*#__PURE__*/React.createElement("svg", _extends({
xmlns: "http://www.w3.org/2000/svg",
viewBox: "0 0 14 14"
}, props), _path2$2 || (_path2$2 = /*#__PURE__*/React.createElement("path", {
fill: "#FFF",
fillRule: "evenodd",
stroke: "none",
strokeWidth: "1",
d: "M13.58 4.04l-.765.645a5 5 0 01-.145 6.615l.735.7a6 6 0 00.175-7.94v-.02zM10.79 6a3 3 0 01-.09 3.97l.735.68a4 4 0 00.115-5.295L10.79 6zM9 15a.5.5 0 01-.355-.15L4.835 11H1.5a.5.5 0 01-.5-.5v-5a.5.5 0 01.5-.5h3.335l3.81-3.85a.5.5 0 01.705 0 .5.5 0 01.15.35v13a.5.5 0 01-.5.5z",
transform: "translate(-1 -1)"
})));
const BottomControls = styled('div')`
z-index: 2;
position: absolute;
bottom: ${_ref => {
let {
$cardSize
} = _ref;
return isLarge($cardSize) ? 18 : 14;
}}px;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
transition: ${transition.medium('opacity')};
will-change: opacity;
`;
const VolumeIcon = styled('svg')`
stroke: #fff;
`;
const VolumeButton = styled(MediaButton).attrs({
className: classNames.volumeControl
})`
${VolumeIcon} {
width: ${_ref2 => {
let {
$cardSize
} = _ref2;
return isLarge($cardSize) ? 16 : 14;
}}px;
height: ${_ref3 => {
let {
$cardSize
} = _ref3;
return isLarge($cardSize) ? 16 : 14;
}}px;
${_ref4 => {
let {
$cardSize
} = _ref4;
return !isLarge($cardSize) && media.mobile`
width: 12px;
height: 12px;
`;
}}
}
`;
const PlaybackRateButton = styled(MediaButton).attrs({
className: classNames.rateControl
})`
font-size: ${_ref5 => {
let {
$cardSize
} = _ref5;
return isLarge($cardSize) ? 12 : 10;
}}px;
min-width: ${_ref6 => {
let {
$cardSize
} = _ref6;
return isLarge($cardSize) ? 33 : 28;
}}px;
line-height: 1;
font-weight: bold;
border: 1.5px solid #fff;
border-radius: 9999px;
padding: 1px 5px;
text-align: center;
color: #fff;
margin-left: 10px;
${_ref7 => {
let {
$cardSize
} = _ref7;
return !isLarge($cardSize) && media.mobile`
font-size: 8px;
margin-left: 8px;
min-width: 23px;
`;
}}
`;
const TimeLabel = styled('span').attrs({
className: classNames.progressTime
})`
margin: ${_ref8 => {
let {
$right
} = _ref8;
return !$right ? '0 auto 0 0' : '0 0 0 auto';
}};
font-family: ${font.mono};
font-size: 12px;
padding: 0 16px;
color: #fff;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
`;
const FooterControls = _ref9 => {
let {
$cardSize,
currentTime,
endTime,
isMuted,
onMuteClick,
onPlaybackRateClick,
playbackRate
} = _ref9;
const VolumeComponent = useMemo(() => isMuted ? VolumeMute : VolumeUp, [isMuted]);
const isLargeCard = useMemo(() => isLarge($cardSize), [$cardSize]);
return /*#__PURE__*/React.createElement(BottomControls, {
$cardSize: $cardSize
}, isLargeCard && /*#__PURE__*/React.createElement(TimeLabel, null, currentTime), /*#__PURE__*/React.createElement(VolumeButton, {
title: isMuted ? 'Unmute' : 'Mute',
$cardSize: $cardSize,
onClick: onMuteClick
}, /*#__PURE__*/React.createElement(VolumeIcon, {
as: VolumeComponent
})), /*#__PURE__*/React.createElement(PlaybackRateButton, {
title: "Playback Rate",
$cardSize: $cardSize,
onClick: onPlaybackRateClick
}, /*#__PURE__*/React.createElement("span", null, playbackRate, "x")), isLargeCard && /*#__PURE__*/React.createElement(TimeLabel, {
$right: true
}, endTime));
};
const _excluded$5 = ["$isPlaying"];
var _path$1, _path2$1;
const Pause = props => /*#__PURE__*/React.createElement("svg", _extends({
xmlns: "http://www.w3.org/2000/svg",
viewBox: "0 0 16 20"
}, props), _path$1 || (_path$1 = /*#__PURE__*/React.createElement("path", {
fill: "#FFF",
fillRule: "evenodd",
stroke: "none",
strokeWidth: "1",
d: "M12 6h-2a2 2 0 00-2 2v16a2 2 0 002 2h2a2 2 0 002-2V8a2 2 0 00-2-2zm10 0h-2a2 2 0 00-2 2v16a2 2 0 002 2h2a2 2 0 002-2V8a2 2 0 00-2-2z",
transform: "translate(-8 -6)"
})));
const Play = props => /*#__PURE__*/React.createElement("svg", _extends({
xmlns: "http://www.w3.org/2000/svg",
viewBox: "0 0 21 24"
}, props), _path2$1 || (_path2$1 = /*#__PURE__*/React.createElement("path", {
fill: "#FFF",
fillRule: "evenodd",
stroke: "none",
strokeWidth: "1",
d: "M7 28a1 1 0 01-1-1V5a1 1 0 011.501-.865l19 11a1 1 0 010 1.73l-19 11A.998.998 0 017 28z",
transform: "translate(-6 -4)"
})));
const iconSizes = {
large: '50px',
normal: '35px',
small: '20px'
};
const PlaybackIcon = styled('svg')`
stroke: #fff;
`;
const PlaybackButtonWrap = styled(MediaButton).attrs({
className: classNames.playbackControl
})`
${PlaybackIcon} {
${_ref => {
let {
$cardSize
} = _ref;
return css`
width: ${iconSizes[$cardSize]};
height: ${iconSizes[$cardSize]};
padding: ${isLarge($cardSize) ? 0 : '8px'};
${!isLarge($cardSize) && !isSmall($cardSize) && media.mobile`
width: calc(${iconSizes.small} * 1.2);
height: calc(${iconSizes.small} * 1.2);
`}
`;
}}
}
`;
const PlaybackButton = _ref2 => {
let {
$isPlaying
} = _ref2,
props = _objectWithoutProperties(_ref2, _excluded$5);
const PlaybackComponent = useMemo(() => $isPlaying ? Pause : Play, [$isPlaying]);
return /*#__PURE__*/React.createElement(PlaybackButtonWrap, _extends({
title: $isPlaying ? 'Pause' : 'Play'
}, props), /*#__PURE__*/React.createElement(PlaybackIcon, {
as: PlaybackComponent
}));
};
const SCRUBBER_SIZE = 12;
const scrubberSizeScales = {
normal: 0.8,
small: 0.9
};
const getScrubberSize = size => Math.floor(SCRUBBER_SIZE * (scrubberSizeScales[size] || 1));
const Scrubber = styled('div').attrs(_ref => {
let {
$isVisible,
$positionX
} = _ref;
return {
style: {
left: $positionX,
transform: `scale(${$isVisible ? 1 : 0.5}) translate(-50%, -50%)`,
opacity: $isVisible ? 1 : 0,
visibility: $isVisible ? '$visible' : 'hidden'
}
};
})`
position: absolute;
top: 50%;
background: #ffffff;
border-radius: 50%;
transform-origin: center center;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
transition: ${transition.short('transform', 'opacity', 'visibility')};
will-change: left, transform, opacity, visibility;
backface-visibility: hidden;
z-index: 3;
${_ref2 => {
let {
$cardSize
} = _ref2;
const scrubberSize = getScrubberSize($cardSize);
return css`
height: ${scrubberSize}px;
width: ${scrubberSize}px;
`;
}}
`;
const _excluded$4 = ["$isDragging", "$isVisible", "label", "$positionX", "size"];
const BASE_FONT_SIZE = 11;
const sizeScales$1 = {
normal: 0.8
};
const getMarkerFontSize = size => BASE_FONT_SIZE * (sizeScales$1[size] || 1);
const TooltipBase = styled('span').attrs(_ref => {
let {
$position,
$isDragging,
$visible
} = _ref;
return {
style: {
left: `${$position}px`,
top: $visible ? '-4px' : '0px',
visibility: $visible ? '$visible' : 'hidden',
opacity: $visible ? 1 : 0,
transform: `translate(-50%, ${!$isDragging ? -100 : -110}%)`
}
};
})`
position: absolute;
background: rgba(24, 25, 25, 0.75);
color: #fff;
text-shadow: 0 1px 2px rgba(24, 25, 25, 0.15);
padding: 2px 3px;
border-radius: 4px;
font-family: ${font.mono};
font-size: ${_ref2 => {
let {
$cardSize
} = _ref2;
return getMarkerFontSize($cardSize);
}}px;
line-height: 1;
transition: ${transition.medium('opacity', 'visibility', 'transform')},
${transition.long('top')};
will-change: top, left, visibility, opacity, transform;
backface-visibility: hidden;
`;
const Tooltip = /*#__PURE__*/forwardRef((_ref3, ref) => {
let {
$isDragging,
$isVisible,
label,
$positionX,
size
} = _ref3,
props = _objectWithoutProperties(_ref3, _excluded$4);
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TooltipBase, _extends({
$visible: $isVisible,
$position: $positionX,
$cardSize: size,
ref: ref,
$isDragging: $isDragging
}, props), label));
});
Tooltip.displayName = 'Tooltip';
const _excluded$3 = ["key"];
const HEIGHT$1 = 6;
const PADDING = 6;
const heightScales = {
normal: 0.7,
small: 0.6
};
const activeHeightScales = {
small: 0.9,
large: 1.4
};
const getProgressBarHeight = size => Math.floor(HEIGHT$1 * (heightScales[size] || 1));
const getProgressBarActiveHeight = size => Math.floor(HEIGHT$1 * (activeHeightScales[size] || 1));
const OuterWrap$1 = styled('div').attrs(() => ({
className: classNames.progressBar
}))`
position: relative;
padding: ${PADDING}px ${PADDING / 2}px ${PADDING / 2}px;
z-index: 2;
backface-visibility: hidden;
`;
const BarsWrap = styled('div').attrs(_ref => {
let {
$cardSize,
$isDragging
} = _ref;
if ($isDragging) {
const activeHeight = getProgressBarActiveHeight($cardSize);
return {
style: {
height: `${activeHeight}px`
}
};
}
return {};
})`
background: transparent;
border-radius: 9999px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
background: rgba(255, 255, 255, 0.15);
transition: ${transition.short('height')};
will-change: height;
pointer-events: none;
position: relative;
${_ref2 => {
let {
$cardSize
} = _ref2;
const height = getProgressBarHeight($cardSize);
const activeHeight = getProgressBarActiveHeight($cardSize);
return css`
height: ${height}px;
${OuterWrap$1}:hover & {
height: ${activeHeight}px;
}
`;
}}
`;
const ProgressLine = styled('div')`
border-radius: inherit;
height: 100%;
position: relative;
overflow: hidden;
`;
const ProgressMask = styled('div').attrs(_ref3 => {
let {
$maskScale
} = _ref3;
return {
style: {
transform: `scaleX(${$maskScale})`
}
};
})`
position: absolute;
left: 0;
top: -50%;
height: 200%;
width: 100%;
background: #ffffff;
transform-origin: left center;
will-change: transform;
`;
const ProgressHover = styled('div').attrs(_ref4 => {
let {
$cursorRatio,
$isHovering,
$progressPercent
} = _ref4;
return {
style: {
left: $progressPercent,
transform: `scaleX(${$cursorRatio})`,
opacity: $isHovering ? 1 : 0,
visibility: $isHovering ? '$visible' : 'hidden'
}
};
})`
position: absolute;
top: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.4);
transform-origin: left center;
transition: ${transition.short('opacity', 'visibility')};
will-change: left, transform, opacity, $visible;
`;
const BufferedChunk = styled('div').attrs(_ref5 => {
let {
start,
end
} = _ref5;
return {
style: {
left: `${start}px`,
right: `${end}px`
}
};
})`
background: rgba(255, 255, 255, 0.35);
position: absolute;
top: 0;
bottom: 0;
`;
const ProgressBar = _ref6 => {
let {
bufferedMedia,
cursorX,
duration,
hoveredTime,
$isDragging,
$isHovering,
onClick,
onMouseDown,
onMouseOver,
progress,
showTooltip
} = _ref6;
const {
props: {
size
}
} = useContext(GlobalContext);
const wrapRef = useRef();
const tooltipRef = useRef();
const isSmallCard = useMemo(() => isSmall(size), [size]);
const getWrapWidth = useCallback(() => {
if (wrapRef.current) {
return wrapRef.current.getBoundingClientRect().width - PADDING;
}
return 0;
}, []);
const progressRatio = useMemo(() => clampNumber(progress / duration, 0, 1), [duration, progress]);
const $progressPercent = useMemo(() => `${clampNumber(progressRatio * 100, 1, 99)}%`, [progressRatio]);
const $cursorRatio = useMemo(() => {
if (wrapRef.current) {
const wrapWidth = getWrapWidth();
const startPoint = progressRatio * wrapWidth;
const cursorPosition = cursorX - startPoint;
const width = wrapWidth - startPoint;
if (cursorPosition > 0) {
return clampNumber((cursorPosition / width).toFixed(3), 0, 0.99);
}
}
return 0;
}, [cursorX, getWrapWidth, progressRatio]);
const bufferedMediaChunks = useMemo(() => {
const wrapWidth = getWrapWidth();
return bufferedMedia.map((chunk, key) => {
const start = chunk.start * wrapWidth;
const end = wrapWidth - chunk.end * wrapWidth;
return {
key,
start,
end
};
});
}, [bufferedMedia, getWrapWidth]);
const tooltipLabel = useMemo(() => formatSeconds(hoveredTime), [hoveredTime]);
const tooltipPositionX = useMemo(() => {
if (wrapRef.current && tooltipRef.current) {
const wrapWidth = getWrapWidth();
const tooltipWidth = tooltipRef.current.getBoundingClientRect().width;
const tooltipHalf = tooltipWidth / 2;
return clampNumber(cursorX, tooltipHalf, wrapWidth - tooltipHalf);
}
return 0;
}, [cursorX, getWrapWidth]);
const mouseEvents = useMemo(() => ({
onClick,
onMouseDown,
onMouseOver
}), [onClick, onMouseDown, onMouseOver]);
const showAccessories = useMemo(() => $isDragging || $isHovering, [$isDragging, $isHovering]);
return /*#__PURE__*/React.createElement(OuterWrap$1, _extends({
$cardSize: size,
ref: wrapRef
}, mouseEvents), /*#__PURE__*/React.createElement(BarsWrap, {
$cardSize: size,
$isDragging: $isDragging
}, /*#__PURE__*/React.createElement(ProgressLine, null, /*#__PURE__*/React.createElement(ProgressHover, {
$cursorRatio: $cursorRatio,
$isHovering: $isHovering,
$progressPercent: $progressPercent
}), bufferedMediaChunks.map(_ref7 => {
let {
key
} = _ref7,
chunk = _objectWithoutProperties(_ref7, _excluded$3);
return /*#__PURE__*/React.createElement(BufferedChunk, _extends({
key: key
}, chunk));
}), /*#__PURE__*/React.createElement(ProgressMask, {
$maskScale: progressRatio
})), /*#__PURE__*/React.createElement(Scrubber, {
$cardSize: size,
$isVisible: showAccessories,
$positionX: $progressPercent
}), !isSmallCard && /*#__PURE__*/React.createElement(Tooltip, {
$isDragging: $isDragging,
$isVisible: showAccessories,
label: tooltipLabel,
$positionX: tooltipPositionX,
ref: tooltipRef,
size: size
})));
};
var _path, _path2;
const _excluded$2 = ["$cardSize"],
_excluded2$2 = ["$cardSize"],
_excluded3 = ["type", "$cardSize"];
const Backward = _ref => {
let {
$cardSize
} = _ref,
props = _objectWithoutProperties(_ref, _excluded$2);
return /*#__PURE__*/React.createElement("svg", _extends({
xmlns: "http://www.w3.org/2000/svg",
viewBox: "0 0 24 29"
}, props), _path || (_path = /*#__PURE__*/React.createElement("path", {
fill: "#FFF",
fillRule: "evenodd",
stroke: "none",
strokeWidth: "1",
d: "M4 18c0 6.627 5.373 12 12 12s12-5.373 12-12S22.627 6 16 6h-4V1L6 7l6 6V8h4c5.523 0 10 4.477 10 10s-4.477 10-10 10S6 23.523 6 18H4zm15.63 4.13a2.84 2.84 0 01-1.28-.27 2.44 2.44 0 01-.89-.77 3.57 3.57 0 01-.52-1.25 7.69 7.69 0 01-.17-1.68 7.83 7.83 0 01.17-1.68c.094-.445.27-.87.52-1.25.23-.325.535-.59.89-.77.4-.188.838-.28 1.28-.27a2.44 2.44 0 012.16 1 5.23 5.23 0 01.7 2.93 5.23 5.23 0 01-.7 2.93 2.44 2.44 0 01-2.16 1.08zm0-1.22c.411.025.8-.19 1-.55a3.38 3.38 0 00.37-1.51v-1.38a3.31 3.31 0 00-.29-1.5 1.23 1.23 0 00-2.06 0 3.31 3.31 0 00-.29 1.5v1.38a3.38 3.38 0 00.29 1.51c.195.356.575.57.98.55zm-9 1.09v-1.18h2v-5.19l-1.86 1-.55-1.06 2.32-1.3H14v6.5h1.78V22h-5.15z",
transform: "translate(-4 -1)"
})));
};
const Forward = _ref2 => {
let {
$cardSize
} = _ref2,
props = _objectWithoutProperties(_ref2, _excluded2$2);
return /*#__PURE__*/React.createElement("svg", _extends({
xmlns: "http://www.w3.org/2000/svg",
viewBox: "0 0 24 29"
}, props), _path2 || (_path2 = /*#__PURE__*/React.createElement("path", {
fill: "#FFF",
fillRule: "evenodd",
stroke: "none",
strokeWidth: "1",
d: "M26 18c0 5.523-4.477 10-10 10S6 23.523 6 18 10.477 8 16 8h4v5l6-6-6-6v5h-4C9.373 6 4 11.373 4 18s5.373 12 12 12 12-5.373 12-12h-2zm-6.36 4.13a2.81 2.81 0 01-1.28-.27 2.36 2.36 0 01-.89-.77 3.39 3.39 0 01-.47-1.25 7.12 7.12 0 01-.17-1.68 7.24 7.24 0 01.17-1.68 3.46 3.46 0 01.52-1.25 2.36 2.36 0 01.89-.77c.4-.19.838-.282 1.28-.27a2.44 2.44 0 012.16 1 5.31 5.31 0 01.7 2.93 5.31 5.31 0 01-.7 2.93 2.44 2.44 0 01-2.21 1.08zm0-1.22a1 1 0 001-.55c.22-.472.323-.99.3-1.51v-1.38a3.17 3.17 0 00-.3-1.5 1.22 1.22 0 00-2.05 0 3.18 3.18 0 00-.29 1.5v1.38a3.25 3.25 0 00.29 1.51 1 1 0 001.05.55zm-7.02-3.49c.355.035.71-.06 1-.27a.84.84 0 00.31-.68v-.08a.94.94 0 00-.3-.74 1.2 1.2 0 00-.83-.27 1.65 1.65 0 00-.89.24 2.1 2.1 0 00-.68.68l-.93-.83a5.37 5.37 0 01.44-.51 2.7 2.7 0 01.54-.4 2.55 2.55 0 01.7-.27 3.25 3.25 0 01.87-.1 3.94 3.94 0 011.06.14c.297.078.576.214.82.4.224.168.408.383.54.63.123.26.184.543.18.83a2 2 0 01-.11.67 1.82 1.82 0 01-.32.52 1.79 1.79 0 01-.47.36 2.27 2.27 0 01-.57.2V18c.219.04.431.11.63.21a1.7 1.7 0 01.85.93c.084.234.124.481.12.73a2 2 0 01-.2.92 2 2 0 01-.58.72 2.66 2.66 0 01-.89.45 3.76 3.76 0 01-1.15.16 4.1 4.1 0 01-1-.11 3.1 3.1 0 01-.76-.31 2.76 2.76 0 01-.56-.45 4.22 4.22 0 01-.44-.55l1.07-.81c.082.147.175.288.28.42.105.128.226.243.36.34.137.097.29.171.45.22a2 2 0 00.57.07 1.45 1.45 0 001-.3 1.12 1.12 0 00.34-.85v-.08a1 1 0 00-.37-.8 1.78 1.78 0 00-1.06-.28h-.76v-1.21h.74z",
transform: "translate(-4 -1)"
})));
};
const SeekIcon = styled('svg')`
stroke: #fff;
width: ${_ref3 => {
let {
$cardSize
} = _ref3;
return isLarge($cardSize) ? 30 : 24;
}}px;
height: ${_ref4 => {
let {
$cardSize
} = _ref4;
return isLarge($cardSize) ? 30 : 24;
}}px;
${_ref5 => {
let {
$cardSize
} = _ref5;
return !isLarge($cardSize) && media.mobile`
width: 0;
height: 0;
`;
}}
`;
const SeekButtonWrap = styled(MediaButton)`
margin: 0 ${_ref6 => {
let {
$cardSize
} = _ref6;
return isLarge($cardSize) ? '28px' : '3px';
}};
`;
const SeekButton = _ref7 => {
let {
type = 'rewind',
$cardSize
} = _ref7,
props = _objectWithoutProperties(_ref7, _excluded3);
const IconComponent = useMemo(() => type === 'rewind' ? Backward : Forward, [type]);
return /*#__PURE__*/React.createElement(SeekButtonWrap, _extends({
title: type === 'rewind' ? 'Rewind' : 'Forward',
$cardSize: $cardSize
}, props), /*#__PURE__*/React.createElement(SeekIcon, {
as: IconComponent,
$cardSize: $cardSize
}));
};
var _Svg;
const BASE_SIZE = 12;
const BASE_OFFSET = 6;
const offsetScales = {
normal: 0.8,
small: 0.6
};
const sizeScales = {
normal: 0.9,
small: 0.8
};
const getSpinnerOffset = size => Math.floor(BASE_OFFSET * (offsetScales[size] || 1));
const getSpinnerSize = size => Math.floor(BASE_SIZE * (sizeScales[size] || 1));
const rotate = keyframes`
100% {
transform: rotate(360deg);
}
`;
const dash = keyframes`
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
`;
const Wrap = styled(MediaButton).attrs(_ref => {
let {
$isVisible
} = _ref;
return {
style: {
opacity: $isVisible ? 1 : 0,
visibility: $isVisible ? '$visible' : 'hidden'
}
};
})(_ref2 => {
let {
$cardSize
} = _ref2;
const size = `${getSpinnerSize($cardSize)}px`;
const offset = `${getSpinnerOffset($cardSize)}px`;
return css`
position: absolute;
width: ${size};
right: ${offset};
top: ${offset};
transition: ${transition.medium('opacity', 'visibility')};
will-change: opacity, visibility;
pointer-events: none;
`;
});
const Svg = styled('svg')`
width: 100%;
animation: ${rotate} 2s linear infinite;
will-change: transform;
`;
const Circle = styled('circle')`
stroke: #fff;
stroke-linecap: round;
stroke-width: 7;
fill: none;
animation: ${dash} 1.5s ease-in-out infinite;
will-change: stroke-dasharray, stroke-dashoffset;
`;
const Spinner = _ref3 => {
let {
size,
$isVisible
} = _ref3;
return /*#__PURE__*/React.createElement(Wrap, {
$cardSize: size,
className: classNames.spinner,
$isVisible: $isVisible
}, _Svg || (_Svg = /*#__PURE__*/React.createElement(Svg, {
viewBox: "0 0 50 50"
}, /*#__PURE__*/React.createElement(Circle, {
cx: "25",
cy: "25",
r: "20"
}))));
};
const SPACE_KEY = 32;
const L_ARROW_KEY = 37;
const R_ARROW_KEY = 39;
const M_KEY = 77;
const OuterWrap = styled('div').attrs({
className: classNames.mediaControls
})`
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
transition: ${transition.long('background')}, ${transition.medium('opacity')};
will-change: background;
display: flex;
flex-direction: column;
pointer-events: auto;
${_ref => {
let {
$hasInteracted,
$isDragging,
$isPlaying
} = _ref;
const bg = 'rgba(0, 0, 0, 0.35)';
const dragBg = 'rgba(0, 0, 0, 0.2)';
const isPaused = $hasInteracted && !$isPlaying;
return css`
.${classNames.main}:hover & {
background: ${!$isDragging ? bg : dragBg};
}
.${classNames.main}:not(:hover) & {
opacity: ${!$hasInteracted || isPaused ? 1 : 0};
${isPaused && `background: ${bg}`};
}
`;
}}
`;
const InnerWrap = styled('div')`
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
`;
const ControlsTop = styled('div')`
flex: 1;
${_ref2 => {
let {
$isVisible
} = _ref2;
return !$isVisible && css`
*[class*='${classNames.mediaControls}']:not(.${classNames.progressTime}) {
transition: ${transition.medium('opacity', 'visibility')};
opacity: 0;
visibility: hidden;
}
`;
}}
`;
const getNextPlaybackRate = rate => {
switch (rate) {
case 1:
return 1.25;
case 1.25:
return 1.5;
case 1.5:
return 2;
default:
return 1;
}
};
const Controls = _ref3 => {
let {
MediaComponent,
mediaProps
} = _ref3;
const {
props: {
autoPlay,
controls,
mediaRef: propRef,
muted,
loop,
size
}
} = useContext(GlobalContext);
const [duration, setDuration] = useState(0);
const [progress, setProgress] = useState(0);
const [buffered, setBuffered] = useState([]);
const [cursorX, setCursorX] = useState(0);
const [hoveredTime, setHoveredTime] = useState(0);
const [$isPlaying, setIsPlaying] = useState(autoPlay);
const [isMuted, setIsMuted] = useState(muted);
const [isBuffering, setIsBuffering] = useState(false);
const [$isHovering, setIsHovering] = useState(false);
const [$isDragging, setIsDragging] = useState(false);
const [playbackRate, setPlaybackRate] = useState(1);
const [$hasInteracted, setHasInteracted] = useState(autoPlay);
const [pausedByDrag, setPausedByDrag] = useState(false);
const mediaRef = useRef();
const setRefs = useCallback(node => {
mediaRef.current = node;
if (propRef) {
if (isFunction(propRef)) {
propRef(node);
} else {
propRef.current = node;
}
}
}, [propRef]);
const isNotSmall = useMemo(() => !isSmall(size), [size]);
const mediaEvents = useMemo(() => ({
onCanPlay: () => setIsBuffering(false),
onLoadedMetadata: e => setDuration(e.currentTarget.duration),
onPause: () => setIsPlaying(false),
onPlay: () => setIsPlaying(true),
onPlaying: () => setIsBuffering(false),
onProgress: e => setBuffered(e.currentTarget.buffered),
onRateChange: e => setPlaybackRate(e.currentTarget.playbackRate),
onTimeUpdate: e => setProgress(e.currentTarget.currentTime),
onVolumeChange: e => setIsMuted(e.currentTarget.muted),
onWaiting: e => setIsBuffering(true)
}), []);
const evaluateCursorPosition = useCallback(event => {
if (mediaRef.current) {
const bounds = event.currentTarget.getBoundingClientRect();
const cursor = clampNumber(Math.floor(event.clientX - bounds.left), 0, bounds.width);
const time = cursor / bounds.width * mediaRef.current.duration;
return {
cursor,
time
};
}
return {
cursor: 0,
time: 0
};
}, []);
const togglePlayback = useCallback(() => {
if (mediaRef.current) {
if (mediaRef.current.paused) {
if (!$hasInteracted) {
setHasInteracted(true);
}
mediaRef.current.play();
} else {
mediaRef.current.pause();
}
}
}, [$hasInteracted]);
const jumpTo = useCallback(time => {
if (mediaRef.current) {
const t = clampNumber(time, 0, mediaRef.current.duration);
mediaRef.current.currentTime = t;
setProgress(t);
}
}, []);
const onSeekClick = useCallback((event, type) => {
event.preventDefault();
event.stopPropagation();
if (mediaRef.current) {
const {
currentTime
} = mediaRef.current;
jumpTo(type === 'rewind' ? currentTime - 10 : currentTime + 30);
}
}, [jumpTo]);
const onMuteClick = useCallback(event => {
event.preventDefault();
event.stopPropagation();
if (mediaRef.current) {
mediaRef.current.muted = !mediaRef.current.muted;
}
}, []);
const onPlaybackRateClick = useCallback(event => {
event.preventDefault();
event.stopPropagation();
if (mediaRef.current) {
mediaRef.current.playbackRate = getNextPlaybackRate(mediaRef.current.playbackRate);
}
}, []);
const onProgressBarClick = useCallback(event => {
event.preventDefault();
event.stopPropagation();
setIsDragging(false);
}, []);
const onProgressBarMouseDown = useCallback(event => {
event.preventDefault();
event.stopPropagation();
setIsDragging(true);
const {
time
} = evaluateCursorPosition(event);
jumpTo(time);
}, [evaluateCursorPosition, jumpTo]);
const onProgressBarMouseOver = useCallback(() => setIsHovering(true), []);
const onWrapClick = useCallback(event => {
event.preventDefault();
event.stopPropagation();
if ($isDragging) {
setIsDragging(false);
} else {
togglePlayback();
}
}, [$isDragging, togglePlayback]);
const onWrapMouseMove = useCallback(event => {
if (($isDragging || $isHovering) && mediaRef.current) {
event.preventDefault();
const {
cursor,
time
} = evaluateCursorPosition(event);
setHoveredTime(time);
setCursorX(cursor);
if ($isDragging) {
if (!mediaRef.current.paused) {
m