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'; 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 { uiPlayerStore, notesStore, sessionStore } = useStore(); const editNote = notesStore.editNote; 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().playing; React.useEffect(() => { player.pause(); const time = store.get().time; const endTime = store.get().endTime; 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) => { return tag === checkedTag; }; const onSave = async () => { try { notesStore.setSaving(true) const playerContainer = document.querySelector('iframe')?.contentWindow?.document; let thumbnail; if (playerContainer) { thumbnail = await elementToImage(playerContainer); } const note = { message, tag: 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('Highlight updated'); } else { const sessionId = sessionStore.current.sessionId; await notesStore.addNote(sessionId, note); toast.success('Highlight saved. Find it in Home > Highlights'); } onClose(); } catch (e) { toast.error('Failed to save highlight'); } finally { notesStore.setSaving(false); } } return (
e.stopPropagation()} >

{editNote ? 'Edit ' : ''}Highlight

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}/200 characters remaining
From
To
{TAGS.map((tag) => ( addTag(tag)} key={tag} className="cursor-pointer rounded-lg hover:bg-indigo-50 mr-0" color={tagProps[tag]} bordered={false} >
{tagActive(tag) ? ( ) : null} {tag}
))}
setIsPublic(e.target.checked)} value={isPublic} className="ms-2" > 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(); }); function elementToImage(doc: Document) { const el = doc.body; const srcMap = new WeakMap() return import('html2canvas').then(({ default: html2canvas }) => { const images = doc.querySelectorAll('img'); images.forEach((img) => { srcMap.set(img, img.src); img.src = "" }) return html2canvas( el, { scale: 1, allowTaint: true, foreignObjectRendering: false, useCORS: false, logging: true, height: 900, width: 1200, x: 0, y: 0, } ).then((canvas) => { images.forEach((img) => { img.src = srcMap.get(img) }) return canvas.toDataURL('img/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);