Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix next app router support #41

Merged
merged 2 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,28 @@
import { useInView } from 'react-intersection-observer'
import { useMediaQueries } from '@react-hook/media-query'
import { useEffect, type ReactElement, useRef, useCallback, type MutableRefObject } from 'react'
import type { LazyVideoProps } from './types/lazyVideoTypes';
import { fillStyles, transparentGif } from './lib/styles'
import type { LazyVideoProps } from '../types/lazyVideoTypes';
import { fillStyles, transparentGif } from '../lib/styles'

type VideoSourceProps = {
src: Required<LazyVideoProps>['src']
videoLoader: LazyVideoProps['videoLoader']
type LazyVideoClientProps = Omit<LazyVideoProps,
'videoLoader' | 'src' | 'sourceMedia'
> & {
srcUrl?: string
mediaSrcs?: Record<string, string>
}

type ResponsiveVideoSourceProps = Pick<Required<LazyVideoProps>,
'src' | 'videoLoader' | 'sourceMedia'
> & {
type ResponsiveVideoSourceProps = {
mediaSrcs: Required<LazyVideoClientProps>['mediaSrcs']
videoRef: VideoRef
}

type VideoRef = MutableRefObject<HTMLVideoElement | undefined>

// An video rendered within a Visual that supports lazy loading
export default function LazyVideo({
src, sourceMedia, videoLoader,
export default function LazyVideoClient({
srcUrl, mediaSrcs,
alt, fit, position, priority, noPoster, paused,
}: LazyVideoProps): ReactElement {
}: LazyVideoClientProps): ReactElement {

// Make a ref to the video so it can be controlled
const videoRef = useRef<HTMLVideoElement>()
Expand Down Expand Up @@ -67,11 +68,6 @@ export default function LazyVideo({
// Simplify logic for whether to load sources
const shouldLoad = priority || inView

// Multiple media queries and a loader func are necessary for responsive
const useResponsiveSource = sourceMedia
&& sourceMedia?.length > 1
&& !!videoLoader

// Render video tag
return (
<video
Expand Down Expand Up @@ -100,39 +96,21 @@ export default function LazyVideo({
}}>

{/* Implement lazy loading by not adding the source until ready */}
{ shouldLoad && (useResponsiveSource ?
<ResponsiveSource { ...{ src, videoLoader, sourceMedia, videoRef }} /> :
<Source {...{ src, videoLoader }} />
{ shouldLoad && (mediaSrcs ?
<ResponsiveSource { ...{ mediaSrcs, videoRef }} /> :
<source src={ srcUrl } type='video/mp4' />
)}
</video>
)
}

// Return a simple source element
function Source({
src, videoLoader
}: VideoSourceProps): ReactElement | undefined {
let srcUrl
if (videoLoader) srcUrl = videoLoader({ src })
else if (typeof src == 'string') srcUrl = src
if (!srcUrl) return
return (<source src={ srcUrl } type='video/mp4' />)
}

// Switch the video asset depending on media queries
function ResponsiveSource({
src, videoLoader, sourceMedia, videoRef
mediaSrcs, videoRef
}: ResponsiveVideoSourceProps): ReactElement | undefined {

// Prepare a hash of source URLs and their media query constraint in the
// style expected by useMediaQueries
const queries = Object.fromEntries(sourceMedia.map(media => {
const url = videoLoader({ src, media })
return [url, media]
}))

// Find the src url that is currently active
const { matches } = useMediaQueries(queries)
const { matches } = useMediaQueries(mediaSrcs)
const srcUrl = getFirstMatch(matches)

// Reload the video since the source changed
Expand Down
64 changes: 64 additions & 0 deletions packages/react/src/LazyVideo/LazyVideoServer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { ReactElement } from 'react'
import type { LazyVideoProps } from '../types/lazyVideoTypes'
import LazyVideoClient from './LazyVideoClient'

// This wrapper function exists to take Function props and make them
// serializable for the LazyVideoClient component, which is a Next.js style
// client component.
export default function LazyVideo(
props: LazyVideoProps
): ReactElement | undefined {

// Destructure some props
const {
src,
sourceMedia,
videoLoader,
} = props

// Multiple media queries and a loader func are necessary for responsive
const useResponsiveSource = sourceMedia
&& sourceMedia?.length > 1
&& !!videoLoader

// Vars that will be conditionally populated
let srcUrl, mediaSrcs

// Prepare a hash of source URLs and their media query constraint in the
// style expected by useMediaQueries.
if (useResponsiveSource) {
const mediaSrcEntries = sourceMedia.map(media => {
const url = videoLoader({ src, media })
return [url, media]
})
// If the array ended up empty, abort
if (mediaSrcEntries.filter(([url]) => !!url).length == 0) return

// Make the hash
mediaSrcs = Object.fromEntries(mediaSrcEntries)

// Make a simple string src url
} else {
if (videoLoader) srcUrl = videoLoader({ src })
else if (typeof src == 'string') srcUrl = src
if (!srcUrl) return // If no url could be built, abort
}

// Render client component
return (
<LazyVideoClient {...{
...props,

// Remove client-unfriendly props
videoLoader: undefined,
src: undefined,
sourceMedia: undefined,

// Add client-friendly props
srcUrl,
mediaSrcs,
}}
/>
)
}

3 changes: 3 additions & 0 deletions packages/react/src/LazyVideo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Export the server component as the defalut LazyVideo component
import LazyVideoServer from './LazyVideoServer'
export default LazyVideoServer
2 changes: 1 addition & 1 deletion packages/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ReactVisual from './ReactVisual'
import LazyVideo from './LazyVideo'
import LazyVideo from './LazyVideo/LazyVideoServer'
import VisualWrapper from './VisualWrapper'
import { collectDataAttributes } from './lib/attributes'

Expand Down