14 changed files with 318 additions and 100 deletions
@ -0,0 +1,39 @@ |
|||||||
|
interface Props { |
||||||
|
className?: string; |
||||||
|
radius?: number; |
||||||
|
percentage: number; |
||||||
|
backingRingClassname?: string; |
||||||
|
} |
||||||
|
|
||||||
|
export function ProgressRing(props: Props) { |
||||||
|
const radius = props.radius ?? 40; |
||||||
|
|
||||||
|
return ( |
||||||
|
<svg |
||||||
|
className={`${props.className ?? ""} -rotate-90`} |
||||||
|
viewBox="0 0 100 100" |
||||||
|
> |
||||||
|
<circle |
||||||
|
className={`fill-transparent stroke-denim-700 stroke-[15] opacity-25 ${ |
||||||
|
props.backingRingClassname ?? "" |
||||||
|
}`}
|
||||||
|
r={radius} |
||||||
|
cx="50" |
||||||
|
cy="50" |
||||||
|
/> |
||||||
|
<circle |
||||||
|
className="fill-transparent stroke-current stroke-[15] transition-[stroke-dashoffset] duration-150" |
||||||
|
r={radius} |
||||||
|
cx="50" |
||||||
|
cy="50" |
||||||
|
style={{ |
||||||
|
strokeDasharray: `${2 * Math.PI * radius} ${2 * Math.PI * radius}`, |
||||||
|
strokeDashoffset: `${ |
||||||
|
2 * Math.PI * radius - |
||||||
|
(props.percentage / 100) * (2 * Math.PI * radius) |
||||||
|
}`,
|
||||||
|
}} |
||||||
|
/> |
||||||
|
</svg> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
import { useInitialized } from "@/video/components/hooks/useInitialized"; |
||||||
|
import { ControlMethods, useControls } from "@/video/state/logic/controls"; |
||||||
|
import { useInterface } from "@/video/state/logic/interface"; |
||||||
|
import { useEffect, useRef } from "react"; |
||||||
|
import { useHistory, useLocation } from "react-router-dom"; |
||||||
|
|
||||||
|
function syncRouteToPopout( |
||||||
|
location: ReturnType<typeof useLocation>, |
||||||
|
controls: ControlMethods |
||||||
|
) { |
||||||
|
const parsed = new URLSearchParams(location.search); |
||||||
|
const value = parsed.get("modal"); |
||||||
|
if (value) controls.openPopout(value); |
||||||
|
else controls.closePopout(); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO make closing a popout go backwords in history
|
||||||
|
// TODO fix first event breaking (clicking on page somehow resolves it)
|
||||||
|
export function useSyncPopouts(descriptor: string) { |
||||||
|
const history = useHistory(); |
||||||
|
const videoInterface = useInterface(descriptor); |
||||||
|
const controls = useControls(descriptor); |
||||||
|
const intialized = useInitialized(descriptor); |
||||||
|
const loc = useLocation(); |
||||||
|
|
||||||
|
const lastKnownValue = useRef<string | null>(null); |
||||||
|
|
||||||
|
const controlsRef = useRef<typeof controls>(controls); |
||||||
|
useEffect(() => { |
||||||
|
controlsRef.current = controls; |
||||||
|
}, [controls]); |
||||||
|
|
||||||
|
// sync current popout to router
|
||||||
|
useEffect(() => { |
||||||
|
const popoutId = videoInterface.popout; |
||||||
|
if (lastKnownValue.current === popoutId) return; |
||||||
|
lastKnownValue.current = popoutId; |
||||||
|
// rest only triggers with changes
|
||||||
|
|
||||||
|
if (popoutId) { |
||||||
|
const params = new URLSearchParams([["modal", popoutId]]).toString(); |
||||||
|
history.push({ |
||||||
|
search: params, |
||||||
|
state: "popout", |
||||||
|
}); |
||||||
|
} else { |
||||||
|
history.push({ |
||||||
|
search: "", |
||||||
|
state: "popout", |
||||||
|
}); |
||||||
|
} |
||||||
|
}, [videoInterface, history]); |
||||||
|
|
||||||
|
// sync router to popout state (but only if its not done by block of code above)
|
||||||
|
useEffect(() => { |
||||||
|
if (loc.state === "popout") return; |
||||||
|
|
||||||
|
// sync popout state
|
||||||
|
syncRouteToPopout(loc, controlsRef.current); |
||||||
|
}, [loc]); |
||||||
|
|
||||||
|
// mount hook
|
||||||
|
const routerInitialized = useRef(false); |
||||||
|
useEffect(() => { |
||||||
|
if (routerInitialized.current) return; |
||||||
|
if (!intialized) return; |
||||||
|
syncRouteToPopout(loc, controlsRef.current); |
||||||
|
routerInitialized.current = true; |
||||||
|
}, [loc, intialized]); |
||||||
|
} |
Loading…
Reference in new issue