UNPKG

@hhgtech/hhg-components

Version:
313 lines (307 loc) 14.5 kB
import { a as __awaiter } from './tslib.es6-00ab44b2.js'; import React__default, { useState, useEffect, useRef, useContext, useMemo, useCallback, useLayoutEffect } from 'react'; import { Box, useMantineTheme } from '@mantine/core'; import { I as ImageWrap } from './index-50aea2c8.js'; import { T as TogetherComponentGlobalContext, c as callApi, f as getApiPath } from './utils-5e9c89a7.js'; import styled from '@emotion/styled'; import { PATHS } from './togetherApiPaths.js'; import { a as DEFAULT_IMG } from './constants-367949ba.js'; import { y as youtubeVidIdGetter } from './post-e5fb69aa.js'; import { L as Loading } from './index-4144308c.js'; const StyledSocialLinkPreview = styled.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] = useState(url ? 'loading' : 'idle'); 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 = useRef(null); const state = useExternalScript({ url: 'https://cdn.plyr.io/3.7.8/plyr.js' }); 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.createElement(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.createElement("div", { key: id, "data-id": "player", className: "plyr__video-embed" }, React__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 }, } = useContext(TogetherComponentGlobalContext); const [previewData, setPreviewData] = useState(null); useEffect(() => { (() => __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, // }), // ) callApi(getApiPath(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 = useMemo(() => { return youtubeVidIdGetter(displayUrl); }, [displayUrl]); const facebookVidId = useMemo(() => { return (displayUrl.includes('facebook.com') || displayUrl.includes('fb.watch')); }, [displayUrl]); const isReelUrl = useMemo(() => { const normalizedUrl = (displayUrl || '').toLowerCase(); return normalizedUrl.includes('/reel/') || normalizedUrl.includes('/reels/'); }, [displayUrl]); const shouldRenderEmbedVideo = useMemo(() => Boolean(youtubeVidId || facebookVidId), [facebookVidId, youtubeVidId]); const shouldRenderShortRatio = useMemo(() => { if (!autoScaleByVideoType) return false; if (isShort) return true; return displayUrl.toLowerCase().includes('/shorts/') || isReelUrl; }, [autoScaleByVideoType, displayUrl, isReelUrl, isShort]); const aspectPaddingTop = useMemo(() => (ratio ? aspectRatioStringToPaddingTop(ratio) : undefined), [ratio]); const embedWrapperRef = useRef(null); const responsiveWidth = useRef(0); const responsiveHeight = useRef(0); const [forceRerenderFB, setForceRerenderFB] = useState(false); /** Facebook iframe needs explicit width/height; keep in sync with `.link-image-wrapper` box. */ const measureFacebookIframeBox = 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). 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 useEffect(() => { const handleResize = () => measureFacebookIframeBox(true); handleResize(); window.addEventListener('resize', handleResize, { passive: true, }); return () => { window.removeEventListener('resize', handleResize); }; }, [measureFacebookIframeBox]); 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 = useMantineTheme(); return (React__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.createElement("div", { ref: embedWrapperRef, className: "link-image-wrapper" }, youtubeVidId ? (React__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.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.createElement("a", { href: displayUrl, target: "_blank", rel: "noreferrer" }, React__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.createElement(Loading, { className: "loading-spinner" })) : (React__default.createElement(ImageWrap, { className: "link-image", src: displayImage || DEFAULT_IMG, backupSrc: DEFAULT_IMG, alt: displayUrl }))))))); }; export { SocialLinkPreview as S };