Skip to content

Commit

Permalink
refactor(plugin-interactive-video): replace SelectVideoMode with vide…
Browse files Browse the repository at this point in the history
…o plugin ui, add focus hack
  • Loading branch information
elbotho committed Dec 20, 2024
1 parent 99d36c9 commit 442d6e9
Show file tree
Hide file tree
Showing 12 changed files with 79 additions and 119 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { parseVideoUrl, VideoType } from '@editor/plugins/video/renderer'
import { VideoType } from '@editor/plugins/video/renderer'
import { VideoStaticRenderer } from '@editor/plugins/video/static'
import { parseVideoUrl } from '@editor/plugins/video/utils/parse-video-url'
import { EditorVideoDocument } from '@editor/types/editor-plugins'
import dynamic from 'next/dynamic'

Expand Down
1 change: 0 additions & 1 deletion packages/editor/src/i18n/strings/de/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ export const editStrings = {
confirmRemoveAllMarks:
'Bist du sicher, dass du alle Aufgaben löschen willst?',
addOverlayContent: 'Aufgabe an aktueller Stelle einfügen',
addVideo: 'Füge ein Video hinzu (z.B. YouTube)',
changeVideo: 'Video austauschen',
saveInfo: 'Änderungen werden automatisch gespeichert!',
},
Expand Down
1 change: 0 additions & 1 deletion packages/editor/src/i18n/strings/en/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ export const editStrings = {
removeAllMarks: 'Remove all exercises',
confirmRemoveAllMarks: 'Are you sure you want to remove all exercises?',
addOverlayContent: 'Add exercise',
addVideo: 'Add a video url (e.g. YouTube) to get started',
changeVideo: 'Change video',
saveInfo: 'Changes are continually saved!',
},
Expand Down
27 changes: 19 additions & 8 deletions packages/editor/src/plugins/interactive-video/editor.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { isTempFile } from '@editor/plugin/upload'
import { useAppSelector, selectStaticDocument } from '@editor/store'
import { useAppSelector, selectStaticDocument, focus } from '@editor/store'
import { EditorInteractiveVideoDocument } from '@editor/types/editor-plugins'
import { isVideoDocument } from '@editor/types/plugin-type-guards'
import { useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useDispatch } from 'react-redux'

import { type InteractiveVideoProps } from '.'
import { EditMode } from './editor/edit-mode'
import { SelectVideoMode } from './editor/select-video-mode'
import { InteractiveVideoToolbar } from './toolbar'
import { isValidVideoUrl } from '../video/utils/is-valid-video-url'

export function InteractiveVideoEditor(props: InteractiveVideoProps) {
const { focused, state, id } = props
const [previewActive, setPreviewActive] = useState(false)

const mounted = useRef(false)
const dispatch = useDispatch()

const staticDocument = useAppSelector(
(storeState) =>
selectStaticDocument(storeState, id) as EditorInteractiveVideoDocument
Expand All @@ -23,7 +27,17 @@ export function InteractiveVideoEditor(props: InteractiveVideoProps) {
? staticDocument.state.video.state.src
: ''

const hasVideo = !isTempFile(videoSrc) && videoSrc.length > 0
const hasVideo = !isTempFile(videoSrc) && isValidVideoUrl(videoSrc)

// refocus after adding a video (but not on first mount)
useEffect(() => {
if (!mounted.current) {
if (!hasVideo) mounted.current = true
return
}
if (!hasVideo) return
dispatch(focus(id))
}, [hasVideo, dispatch, id])

return (
<>
Expand All @@ -43,10 +57,7 @@ export function InteractiveVideoEditor(props: InteractiveVideoProps) {
staticMarks={staticMarks}
/>
) : (
<SelectVideoMode
videoId={state.video.id}
staticVideoSrc={isTempFile(videoSrc) ? '' : videoSrc}
/>
state.video.render()
)}
</>
)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React, { type RefObject } from 'react'

import type { VideoProps } from '..'
import { UploadButton } from './upload-button'
import { parseVideoUrl } from '../renderer'
import { isValidVideoUrl } from '../utils/is-valid-video-url'

interface VideoSelectionScreenProps {
state: VideoProps['state']
Expand All @@ -30,11 +30,11 @@ export function VideoSelectionScreen({
: uploadStrings.placeholderFailed

const videoUrl = src.value as string
const [, type] = parseVideoUrl(

const isValid = isValidVideoUrl(
isTempFile(state.src.value) ? '' : state.src.value
)
const couldBeValid = type !== undefined
const showErrorMessage = videoUrl.length > 5 && !couldBeValid
const showErrorMessage = videoUrl.length > 5 && !isValid

return (
<div
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/plugins/video/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { useRef, useState } from 'react'

import type { VideoProps } from '.'
import { VideoSelectionScreen } from './components/video-selection-screen'
import { parseVideoUrl, VideoRenderer } from './renderer'
import { VideoRenderer } from './renderer'
import { VideoToolbar } from './toolbar'
import { parseVideoUrl } from './utils/parse-video-url'

export const VideoEditor = (props: VideoProps) => {
const { focused, state } = props
Expand Down
5 changes: 2 additions & 3 deletions packages/editor/src/plugins/video/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { VideoEditor } from './editor'
import { parseVideoUrl } from './renderer'
import {
type EditorPlugin,
type EditorPluginProps,
object,
string,
upload,
} from '../../plugin'
import { isValidVideoUrl } from './utils/is-valid-video-url'

const videoState = object({ src: upload(''), alt: string() })

Expand All @@ -18,7 +18,6 @@ export const videoPlugin: EditorPlugin<VideoPluginState> = {
config: {},
state: videoState,
onText(value) {
const [, type] = parseVideoUrl(value)
if (type) return { state: { src: value, alt: '' } }
if (isValidVideoUrl(value)) return { state: { src: value, alt: '' } }
},
}
40 changes: 0 additions & 40 deletions packages/editor/src/plugins/video/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,43 +46,3 @@ export function VideoRenderer({ src, type }: VideoRendererProps) {
}

const videoClassName = cn(`h-full w-full border-none bg-black/30`)

export function parseVideoUrl(
inputSrc: string,
lang?: string
): [string, VideoType | undefined] {
const videoRegex = /^(https?:\/\/)?(.*?vimeo\.com\/)(.+)/
const vimeo = videoRegex.exec(inputSrc)
if (vimeo)
return [
`https://player.vimeo.com/video/${vimeo[3]}?autoplay=1`,
VideoType.Vimeo,
]
const serloAssetRegex =
/^(https:\/\/assets\.serlo\.org\/wikimedia\/)(.+)(webm)$/
const serloAsset = serloAssetRegex.exec(inputSrc)
if (serloAsset) return [inputSrc, VideoType.SerloAsset]

// TODO: update when new asset management is live
if (
inputSrc.startsWith('https://editor.serlo.dev/media/') &&
inputSrc.endsWith('video.webm')
) {
return [inputSrc, VideoType.SerloAsset]
}

const youtubeRegex =
/^(https?:\/\/)?(.*?youtube\.com\/watch\?(.*&)?v=|.*?youtu\.be\/)([a-zA-Z0-9_-]{11})/
const youtube = youtubeRegex.exec(inputSrc)
if (youtube) {
const videoId = encodeURIComponent(youtube[4])
const url = new URL(inputSrc)
const timestamp = parseInt(url.searchParams.get('t') ?? '')
const useSubtitles = url.search.includes('cc_load_policy=1')
const iframeSrc = `https://www.youtube-nocookie.com/embed/${videoId}?autoplay=1&html5=1${
useSubtitles ? `&cc_lang_pref=${lang ?? 'de'}&cc_load_policy=1` : ''
}${isNaN(timestamp) ? '' : `&start=${timestamp}`}`
return [iframeSrc, VideoType.YouTube]
}
return [inputSrc, undefined]
}
4 changes: 3 additions & 1 deletion packages/editor/src/plugins/video/static.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useStaticStrings } from '@editor/i18n/static-strings-provider'
import { parseVideoUrl, VideoRenderer } from '@editor/plugins/video/renderer'
import { VideoRenderer } from '@editor/plugins/video/renderer'
import { EditorVideoDocument } from '@editor/types/editor-plugins'

import { parseVideoUrl } from './utils/parse-video-url'

export function VideoStaticRenderer({ state: { src } }: EditorVideoDocument) {
const { lang } = useStaticStrings()

Expand Down
6 changes: 6 additions & 0 deletions packages/editor/src/plugins/video/utils/is-valid-video-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { parseVideoUrl } from './parse-video-url'

export function isValidVideoUrl(inputSrc: string) {
const [, type] = parseVideoUrl(inputSrc)
return type !== undefined
}
41 changes: 41 additions & 0 deletions packages/editor/src/plugins/video/utils/parse-video-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { VideoType } from '../renderer'

export function parseVideoUrl(
inputSrc: string,
lang?: string
): [string, VideoType | undefined] {
const videoRegex = /^(https?:\/\/)?(.*?vimeo\.com\/)(.+)/
const vimeo = videoRegex.exec(inputSrc)
if (vimeo)
return [
`https://player.vimeo.com/video/${vimeo[3]}?autoplay=1`,
VideoType.Vimeo,
]
const serloAssetRegex =
/^(https:\/\/assets\.serlo\.org\/wikimedia\/)(.+)(webm)$/
const serloAsset = serloAssetRegex.exec(inputSrc)
if (serloAsset) return [inputSrc, VideoType.SerloAsset]

// TODO: update when new asset management is live
if (
inputSrc.startsWith('https://editor.serlo.dev/media/') &&
inputSrc.endsWith('video.webm')
) {
return [inputSrc, VideoType.SerloAsset]
}

const youtubeRegex =
/^(https?:\/\/)?(.*?youtube\.com\/watch\?(.*&)?v=|.*?youtu\.be\/)([a-zA-Z0-9_-]{11})/
const youtube = youtubeRegex.exec(inputSrc)
if (youtube) {
const videoId = encodeURIComponent(youtube[4])
const url = new URL(inputSrc)
const timestamp = parseInt(url.searchParams.get('t') ?? '')
const useSubtitles = url.search.includes('cc_load_policy=1')
const iframeSrc = `https://www.youtube-nocookie.com/embed/${videoId}?autoplay=1&html5=1${
useSubtitles ? `&cc_lang_pref=${lang ?? 'de'}&cc_load_policy=1` : ''
}${isNaN(timestamp) ? '' : `&start=${timestamp}`}`
return [iframeSrc, VideoType.YouTube]
}
return [inputSrc, undefined]
}

0 comments on commit 442d6e9

Please sign in to comment.