From a6fb936fead828c4e0bb014a87d249feb2791481 Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Sun, 27 Sep 2020 15:04:23 +0200
Subject: [PATCH] Cleanup and adding preload

---
 package-lock.json                   |  5 +++++
 package.json                        |  1 +
 src/routes/player/PreviewBar.tsx    | 19 +++++++++---------
 src/routes/player/PreviewViewer.tsx | 17 ++++++++++++++--
 src/routes/player/SeekBar.tsx       | 16 +++++++++------
 src/util/head/HeadPortal.tsx        |  6 ++++++
 src/util/offset/useOffset.ts        |  6 ++----
 src/util/offset/useWidth.ts         | 31 +++++++++++++++++------------
 8 files changed, 67 insertions(+), 34 deletions(-)
 create mode 100644 src/util/head/HeadPortal.tsx

diff --git a/package-lock.json b/package-lock.json
index fc55f1e..24eba99 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11415,6 +11415,11 @@
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
       "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
     },
+    "resize-observer-polyfill": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+    },
     "resolve": {
       "version": "1.15.0",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz",
diff --git a/package.json b/package.json
index cc1d043..c90d4d7 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
     "react-router-dom": "^5.2.0",
     "react-scripts": "3.4.3",
     "react-sweet-state": "^2.3.1",
+    "resize-observer-polyfill": "^1.5.1",
     "typescript": "^3.7.5"
   },
   "scripts": {
diff --git a/src/routes/player/PreviewBar.tsx b/src/routes/player/PreviewBar.tsx
index 3d1710a..fd72e87 100644
--- a/src/routes/player/PreviewBar.tsx
+++ b/src/routes/player/PreviewBar.tsx
@@ -7,9 +7,10 @@ interface Props {
     previewTrack: HTMLTrackElement | null,
     duration: number,
     position: number,
+    hidden: boolean,
 }
 
-export function PreviewBar({previewTrack, duration, position}: Props) {
+export function PreviewBar({previewTrack, duration, position, hidden}: Props) {
     const classes = useStyles();
 
     const [previewBarRef, previewHeadRef, offset] = useOffsetRef(position);
@@ -23,7 +24,8 @@ export function PreviewBar({previewTrack, duration, position}: Props) {
                 ref={previewHeadRef}
                 className={classes.previewHead}
                 style={{
-                    transform: `translate3d(${offset}px, 0, 0)`
+                    transform: `translate3d(${offset}px, 0, 0)`,
+                    opacity: hidden ? 0 : 1,
                 }}
             >
                 <PreviewViewer
@@ -32,22 +34,21 @@ export function PreviewBar({previewTrack, duration, position}: Props) {
                 />
             </div>
         </div>
-    ), [classes.previewHead, classes.previewBar, duration, offset, position, previewBarRef, previewHeadRef, previewTrack]);
+    ), [previewBarRef, classes.previewBar, classes.previewHead, previewHeadRef, offset, hidden, previewTrack, position, duration]);
 }
 
 const useStyles = createUseStyles({
     previewBar: {
         position: "relative",
         width: "40rem",
-        height: "6rem",
+        display: "flex",
+        flexDirection: "row",
+        alignContent: "stretch",
         background: "#7c7",
     },
     previewHead: {
-        position: "absolute",
-        top: 0,
-        bottom: 0,
-        left: "-5rem",
-        width: "10rem",
+        maxWidth: "136rem",
+        maxHeight: "9rem",
         display: "flex",
         flexDirection: "column-reverse",
         background: "#f00",
diff --git a/src/routes/player/PreviewViewer.tsx b/src/routes/player/PreviewViewer.tsx
index 550508a..9928f52 100644
--- a/src/routes/player/PreviewViewer.tsx
+++ b/src/routes/player/PreviewViewer.tsx
@@ -1,5 +1,6 @@
 import React, {useMemo} from "react";
 import {createUseStyles} from "react-jss";
+import {HeadPortal} from "../../util/head/HeadPortal";
 import {useImage} from "../../util/media/useImage";
 import {useTextTrackCues} from "../../util/media/useTextTrackCues";
 import {parseImageSprite} from "../../util/sprite/parseImageSprite";
@@ -19,9 +20,21 @@ export function PreviewViewer({previewTrack, position}: Props) {
     const imageSprite = useMemo(() => parseImageSprite(activeUrl), [activeUrl]);
     const image = useImage(imageSprite?.src || null);
     const sprite = useImageSprite(imageSprite, image);
+    const sources = useMemo(() => previewTrack === null ? null : Array.from(new Set(cues.map(it => {
+        const url = new URL(it.text, previewTrack.src);
+        url.hash = "";
+        return url.toString();
+    }))), [cues, previewTrack]);
     return useMemo(() => (
-        <img alt="" className={classes.preview} src={sprite || undefined}/>
-    ), [sprite]);
+        <React.Fragment>
+            <HeadPortal>
+                {sources?.map(it => (
+                    <link key={it} rel="preload" href={it} as="image" crossOrigin="anonymous"/>
+                ))}
+            </HeadPortal>
+            <img alt="" className={classes.preview} src={sprite || undefined}/>
+        </React.Fragment>
+    ), [classes.preview, sources, sprite]);
 }
 
 const useStyles = createUseStyles({
diff --git a/src/routes/player/SeekBar.tsx b/src/routes/player/SeekBar.tsx
index 61913ae..ed5ffb0 100644
--- a/src/routes/player/SeekBar.tsx
+++ b/src/routes/player/SeekBar.tsx
@@ -60,11 +60,16 @@ export function SeekBar({video, previewTrack, duration, position}: Props) {
                 }}
             />
         </div>
-    ), [classes.seekBar, classes.seekHead, isVisible, offset, onClick, onMouseLeave, onMouseMove, seekBarRef, seekHeadRef]);
+    ), [classes, isVisible, offset, onClick, onMouseLeave, onMouseMove, seekBarRef, seekHeadRef]);
 
     return (
         <React.Fragment>
-            <PreviewBar previewTrack={previewTrack} duration={duration} position={seekPosition || 0}/>
+            <PreviewBar
+                previewTrack={previewTrack}
+                duration={duration}
+                position={seekPosition || 0}
+                hidden={seekPosition === null}
+            />
             {seekBar}
         </React.Fragment>
     )
@@ -74,14 +79,13 @@ const useStyles = createUseStyles({
     seekBar: {
         position: "relative",
         width: "40rem",
+        display: "flex",
+        flexDirection: "row",
+        alignContent: "stretch",
         height: "5rem",
         background: "#77c",
     },
     seekHead: {
-        position: "absolute",
-        top: 0,
-        bottom: 0,
-        left: "-0.05rem",
         width: "0.1rem",
         background: "#f00",
     }
diff --git a/src/util/head/HeadPortal.tsx b/src/util/head/HeadPortal.tsx
new file mode 100644
index 0000000..8b70c22
--- /dev/null
+++ b/src/util/head/HeadPortal.tsx
@@ -0,0 +1,6 @@
+import React from "react";
+import ReactDOM from "react-dom";
+
+export function HeadPortal({children}: React.PropsWithChildren<{}>) {
+    return ReactDOM.createPortal(children, document.head);
+}
diff --git a/src/util/offset/useOffset.ts b/src/util/offset/useOffset.ts
index 05cdc57..811ed38 100644
--- a/src/util/offset/useOffset.ts
+++ b/src/util/offset/useOffset.ts
@@ -4,8 +4,6 @@ export const useOffset = <T extends HTMLElement, U extends HTMLElement>(parent:
     const parentWidth = useWidth(parent);
     const childWidth = useWidth(child);
 
-    const offset = parentWidth * position;
-    const minOffset = childWidth / 2;
-    const maxOffset = parentWidth - minOffset;
-    return Math.max(minOffset, Math.min(offset, maxOffset));
+    const offset = parentWidth * position - childWidth / 2;
+    return Math.max(0, Math.min(offset, parentWidth - childWidth));
 }
diff --git a/src/util/offset/useWidth.ts b/src/util/offset/useWidth.ts
index 6d53d44..1321cb5 100644
--- a/src/util/offset/useWidth.ts
+++ b/src/util/offset/useWidth.ts
@@ -1,21 +1,26 @@
 import {useEffect, useState} from "react";
+import ResizeObserver from 'resize-observer-polyfill';
 
 export const useWidth = <T extends HTMLElement>(it: T | null) => {
     const [width, setWidth] = useState<number>(0);
     useEffect(() => {
-        let animationFrame: number | null = null;
-        const listener = () => {
-            animationFrame = window.requestAnimationFrame(() => {
-                animationFrame = null;
-                setWidth(it?.offsetWidth || 0);
-            });
-        };
-        listener();
-        window.addEventListener("resize", listener);
-        return () => {
-            window.removeEventListener("resize", listener);
-            if (animationFrame !== null) {
-                window.cancelAnimationFrame(animationFrame);
+        if (it !== null) {
+            let animationFrame: number | null = null;
+            const listener = () => {
+            };
+            listener();
+            const observer = new ResizeObserver(([element]) => {
+                animationFrame = window.requestAnimationFrame(() => {
+                    animationFrame = null;
+                    setWidth(element.contentRect.width || 0);
+                });
+            })
+            observer.observe(it);
+            return () => {
+                observer.unobserve(it);
+                if (animationFrame !== null) {
+                    window.cancelAnimationFrame(animationFrame);
+                }
             }
         }
     }, [it]);
-- 
GitLab