mirror of
https://github.com/netbirdio/docs.git
synced 2026-08-25 01:01:27 +02:00
Add a how-to page for the new Draft Mode in Control Center: build a change on a working copy of the canvas, review the exact API requests, and deploy everything as one batch. A single running example (giving DevOps HTTPS access to a not-yet-installed staging server) carries through entering a draft, the canvas toolbar, node interactions, placeholder-peer installs, and Review & Deploy. Along the way: - Nest Control Center in the sidebar (Overview + Draft Mode) and update the overview page: Users view in the intro and quick start, an Edit Nodes section covering live edits vs Draft Mode, permissions notes including the Network Admin setup-key limitation, and a HashRedirect for the renamed #editing-policies-from-the-graph anchor. - Add a shared <Video> component for screen recordings: lazy playback via IntersectionObserver, visible controls, preload="metadata", and no autoplay under prefers-reduced-motion. - Optimize media: re-encode recordings (H.264 CRF 26, 30 fps, faststart, audio stripped) and losslessly recompress screenshots, cutting the page's media payload from 6.3 MB to 1.2 MB. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
53 lines
1.4 KiB
JavaScript
53 lines
1.4 KiB
JavaScript
import { useEffect, useRef } from 'react'
|
|
import clsx from 'clsx'
|
|
|
|
/**
|
|
* Looping screen-recording embed for docs pages.
|
|
*
|
|
* Recordings load lazily and play only while on screen: `preload="metadata"`
|
|
* avoids buffering full files up front, and an IntersectionObserver starts
|
|
* playback when the video scrolls into view and pauses it when it leaves.
|
|
* Controls stay visible so the loop can be paused (WCAG 2.2.2), and autoplay
|
|
* is skipped entirely for users who prefer reduced motion.
|
|
*
|
|
* Usage:
|
|
* <Video src="/docs-static/img/manage/example.mp4" label="What the recording shows" />
|
|
*/
|
|
export function Video({ src, label, className, ...props }) {
|
|
const ref = useRef(null)
|
|
|
|
useEffect(() => {
|
|
const video = ref.current
|
|
if (!video) return
|
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
|
|
|
|
const observer = new IntersectionObserver(
|
|
([entry]) => {
|
|
if (entry.isIntersecting) {
|
|
video.play().catch(() => {})
|
|
} else {
|
|
video.pause()
|
|
}
|
|
},
|
|
{ threshold: 0.25 }
|
|
)
|
|
observer.observe(video)
|
|
return () => observer.disconnect()
|
|
}, [])
|
|
|
|
return (
|
|
<video
|
|
ref={ref}
|
|
src={src}
|
|
loop
|
|
muted
|
|
playsInline
|
|
controls
|
|
preload="metadata"
|
|
aria-label={label}
|
|
className={clsx('imagewrapper-big', className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|