import React from 'react'; import { Button, Checkbox, Input, Tag } from 'antd'; import { X } from 'lucide-react'; import { TAGS, iTag, tagProps } from 'App/services/NotesService'; import { useStore } from 'App/mstore'; import { Icon } from 'UI'; import { PlayerContext } from 'Components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { shortDurationFromMs } from 'App/date'; import { toast } from 'react-toastify'; import { useTranslation } from 'react-i18next'; function maskDuration(input: string): string { const digits = input.replace(/\D/g, ''); const limitedDigits = digits.slice(0, 4); if (limitedDigits.length <= 2) { return limitedDigits; } return `${limitedDigits.slice(0, 2)}:${limitedDigits.slice(2)}`; } const duration = new RegExp(/(\d{2}):(\d{2})/); function HighlightPanel({ onClose }: { onClose: () => void }) { const { t } = useTranslation(); const { uiPlayerStore, notesStore, sessionStore } = useStore(); const { editNote } = notesStore; const [message, setMessage] = React.useState(editNote?.message ?? ''); const [isPublic, setIsPublic] = React.useState(editNote?.isPublic ?? false); const { store, player } = React.useContext(PlayerContext); const currentTime = store.get().time; const startTsStr = shortDurationFromMs( editNote?.startAt ?? uiPlayerStore.highlightSelection.startTs, ); const endTsStr = shortDurationFromMs( editNote?.endAt ?? uiPlayerStore.highlightSelection.endTs, ); const [startTs, setStartTs] = React.useState(startTsStr); const [endTs, setEndTs] = React.useState(endTsStr); const [tag, setTag] = React.useState(editNote?.tag ?? TAGS[0]); const onStartChange = (e: React.ChangeEvent) => { const newState = maskDuration(e.target.value); setStartTs(newState); if (duration.test(newState)) { const [_, minutes, seconds] = duration.exec(newState) ?? []; const newTime = (parseInt(minutes) * 60 + parseInt(seconds)) * 1000; const sessLength = store.get().endTime; uiPlayerStore.toggleHighlightSelection({ enabled: true, range: [ Math.min(newTime, sessLength), uiPlayerStore.highlightSelection.endTs, ], }); } }; const onEndChange = (e: React.ChangeEvent) => { const newState = maskDuration(e.target.value); setEndTs(newState); if (duration.test(newState)) { const [_, minutes, seconds] = duration.exec(newState) ?? []; const newTime = (parseInt(minutes) * 60 + parseInt(seconds)) * 1000; const sessLength = store.get().endTime; uiPlayerStore.toggleHighlightSelection({ enabled: true, range: [ uiPlayerStore.highlightSelection.startTs, Math.min(newTime, sessLength), ], }); } }; const { playing } = store.get(); React.useEffect(() => { player.pause(); const { time } = store.get(); const { endTime } = store.get(); const distance = Math.max(endTime / 40, 2500); uiPlayerStore.toggleHighlightSelection({ enabled: true, range: [Math.max(time - distance, 0), Math.min(time + distance, endTime)], }); return () => { uiPlayerStore.toggleHighlightSelection({ enabled: false, }); notesStore.setEditNote(null); }; }, []); React.useEffect(() => { const startStr = shortDurationFromMs( uiPlayerStore.highlightSelection.startTs, ); const endStr = shortDurationFromMs(uiPlayerStore.highlightSelection.endTs); setStartTs(startStr); setEndTs(endStr); }, [ uiPlayerStore.highlightSelection.startTs, uiPlayerStore.highlightSelection.endTs, ]); React.useEffect(() => { player.pause(); }, [playing]); const addTag = (newTag: iTag) => { setTag(newTag); }; const tagActive = (checkedTag: iTag) => tag === checkedTag; const onSave = async () => { try { notesStore.setSaving(true); const playerContainer = document.querySelector('iframe')?.contentWindow?.document; let thumbnail; if (playerContainer) { thumbnail = await elementToCanvas(playerContainer); if (!thumbnail) { thumbnail = await elementToImage(thumbnail); } } const note = { message, tag, isPublic, timestamp: parseInt(currentTime, 10), startAt: parseInt(uiPlayerStore.highlightSelection.startTs, 10), endAt: parseInt(uiPlayerStore.highlightSelection.endTs, 10), thumbnail, }; if (editNote) { await notesStore.updateNote(editNote.noteId, note); toast.success(t('Highlight updated')); } else { const { sessionId } = sessionStore.current; await notesStore.addNote(sessionId, note); toast.success(t('Highlight saved. Find it in Home > Highlights')); } onClose(); } catch (e) { toast.error(t('Failed to save highlight')); } finally { notesStore.setSaving(false); } }; return (
e.stopPropagation()} >

{editNote ? t('Edit ') : ''} {t('Highlight')}

{t( 'Save key moments from sessions. Access them anytime on the ‘Highlights’ page to share with your team.', )}
setMessage(e.target.value)} placeholder="Enter Comments" maxLength={200} rows={6} value={message} className="rounded-lg" autoFocus />
{message.length} {t('/200 characters remaining')}
{t('From')}
{t('To')}
{TAGS.map((tag) => ( addTag(tag)} key={tag} className="cursor-pointer rounded-lg hover:bg-indigo-lightest mr-0" color={tagProps[tag]} bordered={false} >
{tagActive(tag) ? ( ) : null} {tag}
))}
setIsPublic(e.target.checked)} value={isPublic} className="ms-2" > {t('Visible to team members')}
); } window.__debugElementToImage = (el) => elementToImage(el).then((img) => { const a = document.createElement('a'); a.href = img; a.download = 'highlight.png'; a.click(); }); async function elementToImage(el: any) { const constraints = { video: { displaySurface: 'browser', preferCurrentTab: true, }, preferCurrentTab: true, monitorTypeSurfaces: 'exclude', audio: false, }; try { const stream = await navigator.mediaDevices.getDisplayMedia(constraints); const track = stream.getVideoTracks()[0]; const imageCapture = new ImageCapture(track); const bitmap = await imageCapture.grabFrame(); track.stop(); const canvas = document.createElement('canvas'); canvas.width = bitmap.width; canvas.height = bitmap.height; canvas.getContext('2d').drawImage(bitmap, 0, 0); return canvas.toDataURL('image/png'); } catch (e) { toast.error('Failed to capture screen image'); } } function elementToCanvas(doc: Document) { const el = doc.body; const srcMap = new WeakMap(); return import('@codewonders/html2canvas').then(({ default: html2canvas }) => { const images = doc.querySelectorAll('img'); images.forEach((img) => { const sameOrigin = new URL(img.src, location.href).origin === location.origin; if (!sameOrigin) { srcMap.set(img, img.src); img.src = ''; } }); return html2canvas(el, { scale: 1, allowTaint: false, foreignObjectRendering: true, useCORS: true, logging: true, }) .then((canvas) => { images.forEach((img) => { if (srcMap.has(img)) img.src = srcMap.get(img)!; }); return canvas.toDataURL('image/png'); }) .catch((e) => { console.log(e); return undefined; }); }); } const convertAllImagesToBase64 = (proxyURL, cloned) => { const pendingImagesPromises = []; const pendingPromisesData = []; const images = cloned.getElementsByTagName('img'); for (let i = 0; i < images.length; i += 1) { const promise = new Promise((resolve, reject) => { pendingPromisesData.push({ index: i, resolve, reject, }); }); pendingImagesPromises.push(promise); } for (let i = 0; i < images.length; i += 1) { fetch(`${proxyURL}?url=${images[i].src}`) .then((response) => response.json()) .then((data) => { const pending = pendingPromisesData.find((p) => p.index === i); images[i].src = data; pending.resolve(data); }) .catch((e) => { const pending = pendingPromisesData.find((p) => p.index === i); pending.reject(e); }); } return Promise.all(pendingImagesPromises); }; export default observer(HighlightPanel);