@hhgtech/hhg-components
Version:
Hello Health Group common components
320 lines (311 loc) • 15 kB
JavaScript
'use strict';
var tslib_es6 = require('./tslib.es6-af09a0ba.js');
var React = require('react');
var core = require('@mantine/core');
var index$1 = require('./index-6b44ec2b.js');
var togetherComponentGlobalContext = require('./utils-3a3800c0.js');
var styled = require('@emotion/styled');
var togetherApiPaths = require('./togetherApiPaths.js');
var constants = require('./constants-817a109a.js');
var post = require('./post-c2582f7d.js');
var index = require('./index-29d0bb65.js');
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefault(React);
var styled__default = /*#__PURE__*/_interopDefault(styled);
const StyledSocialLinkPreview = styled__default["default"].div `
.link-info-container {
padding: 0.5rem 1rem;
background: ${(props) => props.color || '#f4faff'};
}
.link-image-wrapper {
position: relative;
overflow: hidden;
width: 100%;
background-color: #000;
padding-top: ${(props) => { var _a; return (_a = props.aspectPaddingTop) !== null && _a !== void 0 ? _a : (props.isShort ? '179.58%' : '56.25%'); }};
&.fetching {
background: lightgray;
}
.loading-spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.link-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: ${(props) => { var _a; return (_a = props.posterFit) !== null && _a !== void 0 ? _a : 'cover'; }};
object-position: center;
}
}
a {
text-decoration: none;
}
&[data-is-marrybaby='true'] {
overflow: hidden;
border-radius: 1rem;
}
`;
const useExternalScript = ({ url, onLoad, onError }) => {
const [state, setState] = React.useState(url ? 'loading' : 'idle');
React.useEffect(() => {
if (!url) {
setState('idle');
return;
}
let script = document.querySelector(`script[src="${url}"]`);
const handleLoadScript = () => {
setState('ready');
onLoad === null || onLoad === void 0 ? void 0 : onLoad();
};
const handleErrorScript = () => {
setState('error');
onError === null || onError === void 0 ? void 0 : onError();
};
if (!script) {
script = document.createElement('script');
script.type = 'application/javascript';
script.src = url;
script.async = true;
document.body.appendChild(script);
script.addEventListener('load', handleLoadScript);
script.addEventListener('error', handleErrorScript);
}
script.addEventListener('load', handleLoadScript);
script.addEventListener('error', handleErrorScript);
return () => {
script.removeEventListener('load', handleLoadScript);
script.removeEventListener('error', handleErrorScript);
};
}, [url]);
return state;
};
const YoutubePlyr = ({ id, autoplay, posterFit = 'cover', }) => {
const ref = React.useRef(null);
const state = useExternalScript({ url: 'https://cdn.plyr.io/3.7.8/plyr.js' });
React.useEffect(() => {
var _a;
if (!document.getElementById('hhg-plyr-player')) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'https://cdn.plyr.io/3.7.8/plyr.css';
link.id = 'hhg-plyr-player';
document.head.appendChild(link);
}
const canInit = state === 'ready' || typeof Plyr !== 'undefined';
if (!canInit)
return;
const container = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.querySelector('[data-id="player"]');
if (!container)
return;
// No `ratio` here: parent `.link-image-wrapper` already sets aspect (padding-top / optional `ratio` prop).
// Inner Plyr aspect + maxWidth would desync iframe from that box; fill container instead.
const player = new Plyr(container, {
autoplay,
muted: true,
});
return () => {
player.destroy();
};
}, [state, autoplay, id]);
const posterBgSize = posterFit === 'cover' ? 'cover !important' : 'contain !important';
return (React__default["default"].createElement(core.Box, { ref: ref, sx: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#000',
'&, & > div': {
width: '100%',
height: '100%',
},
'& .plyr': {
width: '100%',
height: '100%',
maxWidth: 'none',
},
'& .plyr__video-wrapper': {
height: '100%',
width: '100%',
},
'& .plyr__video-embed': {
paddingBottom: '0 !important',
height: '100% !important',
position: 'relative',
},
'& .plyr__video-embed iframe': {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
},
'& .plyr__poster': {
backgroundSize: posterBgSize,
backgroundPosition: 'center !important',
backgroundRepeat: 'no-repeat',
width: '100% !important',
height: '100% !important',
top: 0,
left: 0,
},
} },
React__default["default"].createElement("div", { key: id, "data-id": "player", className: "plyr__video-embed" },
React__default["default"].createElement("iframe", { width: "100%", height: "100%", src: `https://www.youtube.com/embed/${id}?mute=1&enablejsapi=1&rel=0`, allow: "autoplay; fullscreen", allowFullScreen: true, title: "YouTube preview", frameBorder: "0" }))));
};
/** `width:height` (e.g. `16:9`, `9:16`) — sets preview box aspect via padding-top. */
function aspectRatioStringToPaddingTop(ratio) {
const m = ratio.trim().match(/^(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)$/);
if (!m)
return undefined;
const w = Number(m[1]);
const h = Number(m[2]);
if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
return undefined;
}
return `${(h / w) * 100}%`;
}
const SocialLinkPreview = ({ url, image, className, style, baseUrl = '', autoPlay = true, isShort = false, autoScaleByVideoType = false, ratio, posterFit = 'cover', onPushStatus, }) => {
const { data: { locale }, } = React.useContext(togetherComponentGlobalContext.TogetherComponentGlobalContext);
const [previewData, setPreviewData] = React.useState(null);
React.useEffect(() => {
(() => tslib_es6.__awaiter(void 0, void 0, void 0, function* () {
if (image)
return;
if (!url)
return setPreviewData(null);
setPreviewData((_data) => (Object.assign(Object.assign({}, _data), { isFetching: true })));
// fetch('./api/fetch-meta?url=' + encodeURIComponent(url))
// .then((res) => res.json())
// .then((res) =>
// setPreviewData({
// url,
// image: res?.image || res?.logo,
// }),
// )
togetherComponentGlobalContext.callApi(togetherComponentGlobalContext.getApiPath(togetherApiPaths.PATHS.FETCH_PREVIEW, {
_locale: locale,
}, undefined, baseUrl), 'POST', {
data: {
link: url,
},
headers: {
'Content-Type': 'application/json',
},
}, !!baseUrl)
.then((res) => {
var _a, _b;
setPreviewData({
url,
image: ((_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.image) || ((_b = res === null || res === void 0 ? void 0 : res.data) === null || _b === void 0 ? void 0 : _b.logo),
});
})
.catch(() => {
setPreviewData(null);
});
}))();
}, [url, image]);
const displayImage = image || (previewData === null || previewData === void 0 ? void 0 : previewData.image);
const displayUrl = (previewData === null || previewData === void 0 ? void 0 : previewData.url) || url;
const youtubeVidId = React.useMemo(() => {
return post.youtubeVidIdGetter(displayUrl);
}, [displayUrl]);
const facebookVidId = React.useMemo(() => {
return (displayUrl.includes('facebook.com') || displayUrl.includes('fb.watch'));
}, [displayUrl]);
const isReelUrl = React.useMemo(() => {
const normalizedUrl = (displayUrl || '').toLowerCase();
return normalizedUrl.includes('/reel/') || normalizedUrl.includes('/reels/');
}, [displayUrl]);
const shouldRenderEmbedVideo = React.useMemo(() => Boolean(youtubeVidId || facebookVidId), [facebookVidId, youtubeVidId]);
const shouldRenderShortRatio = React.useMemo(() => {
if (!autoScaleByVideoType)
return false;
if (isShort)
return true;
return displayUrl.toLowerCase().includes('/shorts/') || isReelUrl;
}, [autoScaleByVideoType, displayUrl, isReelUrl, isShort]);
const aspectPaddingTop = React.useMemo(() => (ratio ? aspectRatioStringToPaddingTop(ratio) : undefined), [ratio]);
const embedWrapperRef = React.useRef(null);
const responsiveWidth = React.useRef(0);
const responsiveHeight = React.useRef(0);
const [forceRerenderFB, setForceRerenderFB] = React.useState(false);
/** Facebook iframe needs explicit width/height; keep in sync with `.link-image-wrapper` box. */
const measureFacebookIframeBox = React.useCallback((looseThreshold) => {
var _a;
const rect = (_a = embedWrapperRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
if (!(rect === null || rect === void 0 ? void 0 : rect.width))
return;
const w = Math.floor(rect.width);
const h = Math.floor(rect.height);
let toChangeSize = false;
if (looseThreshold) {
if (!responsiveWidth.current ||
Math.abs((responsiveWidth.current - w) / rect.width) > 0.2) {
responsiveWidth.current = w;
toChangeSize = true;
}
if (!responsiveHeight.current ||
Math.abs((responsiveHeight.current - h) / (rect.height || 1)) > 0.2) {
responsiveHeight.current = h;
toChangeSize = true;
}
}
else {
if (w !== responsiveWidth.current || h !== responsiveHeight.current) {
responsiveWidth.current = w;
responsiveHeight.current = h;
toChangeSize = true;
}
}
if (toChangeSize) {
setForceRerenderFB((v) => !v);
}
}, []);
// After aspect ratio / layout updates, iframe must match `link-image-wrapper` (not only on window resize).
React.useLayoutEffect(() => {
measureFacebookIframeBox(false);
}, [
aspectPaddingTop,
ratio,
shouldRenderShortRatio,
displayUrl,
measureFacebookIframeBox,
]);
// FACEBOOK Video is not responsive for desktop browser
// => hack: set size to same as container, if size change too large, re-render facebook iframe with new config
React.useEffect(() => {
const handleResize = () => measureFacebookIframeBox(true);
handleResize();
window.addEventListener('resize', handleResize, {
passive: true,
});
return () => {
window.removeEventListener('resize', handleResize);
};
}, [measureFacebookIframeBox]);
React.useEffect(() => {
if ((previewData === null || previewData === void 0 ? void 0 : previewData.image) && (previewData === null || previewData === void 0 ? void 0 : previewData.url)) {
onPushStatus === null || onPushStatus === void 0 ? void 0 : onPushStatus(true);
}
}, [previewData]);
const _theme = core.useMantineTheme();
return (React__default["default"].createElement(StyledSocialLinkPreview, { className: `${className} no-replace-click`, style: style, color: _theme.colors[_theme.primaryColor][0], isShort: shouldRenderShortRatio, aspectPaddingTop: aspectPaddingTop, posterFit: posterFit }, shouldRenderEmbedVideo && (!image || isReelUrl) ? (React__default["default"].createElement("div", { ref: embedWrapperRef, className: "link-image-wrapper" }, youtubeVidId ? (React__default["default"].createElement(YoutubePlyr, { key: `${youtubeVidId}-${ratio !== null && ratio !== void 0 ? ratio : 'auto'}`, id: youtubeVidId, autoplay: autoPlay, isShort: shouldRenderShortRatio, ratio: ratio, posterFit: posterFit })) : facebookVidId ? (React__default["default"].createElement("iframe", { key: String(forceRerenderFB), src: `https://www.facebook.com/plugins/video.php?href=${encodeURIComponent(displayUrl)}&autoplay=${autoPlay ? 1 : 0}&show_text=false&t=0&width=${responsiveWidth.current}&height=${responsiveHeight.current}`, style: {
position: 'absolute',
top: '50%',
left: '50%',
border: 'none',
overflow: 'hidden',
height: responsiveHeight.current,
width: responsiveWidth.current,
transform: 'translate(-50%, -50%)',
}, scrolling: "no", frameBorder: "0", allowFullScreen: true, allow: "autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share", loading: "lazy" })) : null)) : (React__default["default"].createElement("a", { href: displayUrl, target: "_blank", rel: "noreferrer" },
React__default["default"].createElement("div", { className: `link-image-wrapper ${(previewData === null || previewData === void 0 ? void 0 : previewData.isFetching) ? 'fetching' : ''}` }, (previewData === null || previewData === void 0 ? void 0 : previewData.isFetching) ? (React__default["default"].createElement(index.Loading, { className: "loading-spinner" })) : (React__default["default"].createElement(index$1.ImageWrap, { className: "link-image", src: displayImage || constants.DEFAULT_IMG, backupSrc: constants.DEFAULT_IMG, alt: displayUrl })))))));
};
exports.SocialLinkPreview = SocialLinkPreview;