From e63d77cd8240bfdcd8ad8e7345d9815ba1e90dd0 Mon Sep 17 00:00:00 2001 From: an-lee Date: Thu, 28 Mar 2024 10:35:32 +0800 Subject: [PATCH] Fix layout (#454) * player layout autosize * fix video layout * improve style --- enjoy/src/i18n/en.json | 2 +- enjoy/src/main/window.ts | 12 +- enjoy/src/preload.ts | 11 ++ .../components/audios/audio-player.tsx | 26 ++-- .../medias/media-current-recording.tsx | 87 +++++++------ .../medias/media-player-controls.tsx | 7 +- .../components/medias/media-player.tsx | 114 +++++++++++------- .../components/medias/media-provider.tsx | 3 +- .../renderer/components/medias/media-tabs.tsx | 25 ++-- .../components/videos/video-player.tsx | 23 ++-- .../context/media-player-provider.tsx | 64 +++++++++- enjoy/src/types/enjoy-app.d.ts | 4 + 12 files changed, 239 insertions(+), 139 deletions(-) diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index c6ff64d5..85944cec 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -296,7 +296,7 @@ "releaseToStop": "Release to stop", "deleteRecording": "delete recording", "deleteRecordingConfirmation": "Are you sure to delete this recording?", - "myRecordings": "my recordings", + "myRecordings": "recordings", "noRecordingForThisSegmentYet": "No recordings for this segment yet. Press R to start recording.", "lastYear": "last year", "less": "less", diff --git a/enjoy/src/main/window.ts b/enjoy/src/main/window.ts index 0a923afd..9ffffbb8 100644 --- a/enjoy/src/main/window.ts +++ b/enjoy/src/main/window.ts @@ -440,16 +440,20 @@ ${log} // Create the browser window. const mainWindow = new BrowserWindow({ icon: "./assets/icon.png", - width: 1920, - height: 1080, - minWidth: 1440, - minHeight: 900, + width: 1440, + height: 900, + minWidth: 1024, + minHeight: 768, webPreferences: { preload: path.join(__dirname, "preload.js"), spellcheck: false, }, }); + mainWindow.on("resize", () => { + mainWindow.webContents.send("window-on-resize", mainWindow.getBounds()); + }); + mainWindow.webContents.setWindowOpenHandler(() => { return { action: "allow" }; }); diff --git a/enjoy/src/preload.ts b/enjoy/src/preload.ts index d1f4a9d3..71275da5 100644 --- a/enjoy/src/preload.ts +++ b/enjoy/src/preload.ts @@ -34,6 +34,17 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", { }, version, }, + window: { + onResize: ( + callback: ( + event: IpcRendererEvent, + bounds: { x: number; y: number; width: number; height: number } + ) => void + ) => ipcRenderer.on("window-on-resize", callback), + removeListeners: () => { + ipcRenderer.removeAllListeners("window-on-resize"); + }, + }, system: { preferences: { mediaAccess: (mediaType: "microphone" | "camera") => { diff --git a/enjoy/src/renderer/components/audios/audio-player.tsx b/enjoy/src/renderer/components/audios/audio-player.tsx index e8378323..db2a4935 100644 --- a/enjoy/src/renderer/components/audios/audio-player.tsx +++ b/enjoy/src/renderer/components/audios/audio-player.tsx @@ -1,4 +1,4 @@ -import { useEffect, useContext, useRef } from "react"; +import { useEffect, useContext, useState } from "react"; import { MediaPlayerProviderContext } from "@renderer/context"; import { MediaLoadingModal, @@ -7,43 +7,45 @@ import { MediaTabs, MediaCurrentRecording, MediaPlayer, + LoaderSpin, } from "@renderer/components"; import { useAudio } from "@renderer/hooks"; export const AudioPlayer = (props: { id?: string; md5?: string }) => { const { id, md5 } = props; - const { setMedia } = useContext(MediaPlayerProviderContext); + const { setMedia, layout } = useContext(MediaPlayerProviderContext); const { audio } = useAudio({ id, md5 }); useEffect(() => { if (!audio) return; - setMedia(audio); }, [audio]); + if (!layout) return ; + return (
-
-
-
+
+
+
-
+
-
-
- +
+
+
-
+
-
+
diff --git a/enjoy/src/renderer/components/medias/media-current-recording.tsx b/enjoy/src/renderer/components/medias/media-current-recording.tsx index 3a2a7316..75ee6f46 100644 --- a/enjoy/src/renderer/components/medias/media-current-recording.tsx +++ b/enjoy/src/renderer/components/medias/media-current-recording.tsx @@ -46,8 +46,9 @@ import { formatDuration } from "@renderer/lib/utils"; import { useHotkeys } from "react-hotkeys-hook"; export const MediaCurrentRecording = (props: { height?: number }) => { - const { height = 192 } = props; + const { height } = props; const { + layout, isRecording, setIsRecording, currentRecording, @@ -281,9 +282,9 @@ export const MediaCurrentRecording = (props: { height?: number }) => { }); return () => { - ws.destroy(); + ws?.destroy(); }; - }, [ref, currentRecording, isRecording]); + }, [ref, currentRecording, isRecording, height]); useEffect(() => { setIsComparing(false); @@ -315,7 +316,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => { } const subscriptions = [ - regions.on("region-created", () => {}), + regions.on("region-created", () => { }), regions.on("region-clicked", (region, e) => { e.stopPropagation(); @@ -383,16 +384,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => { useEffect(() => { calContainerWidth(); - window.addEventListener("resize", () => { - calContainerWidth(); - }); - - return () => { - window.removeEventListener("resize", () => { - calContainerWidth(); - }); - }; - }, [currentRecording, isRecording]); + }, [currentRecording, isRecording, layout?.width]); useHotkeys( ["Ctrl+R", "Meta+R"], @@ -410,7 +402,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => { [player] ); - if (isRecording) return ; + if (isRecording) return ; if (!currentRecording?.src) return (
@@ -480,27 +472,31 @@ export const MediaCurrentRecording = (props: { height?: number }) => { setIsRecording={setIsRecording} /> - + { + height >= 192 && <> + - + + + } @@ -522,17 +518,16 @@ export const MediaCurrentRecording = (props: { height?: number }) => { > = 80 - ? "text-green-500" - : currentRecording.pronunciationAssessment - .pronunciationScore >= 60 - ? "text-yellow-600" - : "text-red-500" - : "" - } + ${currentRecording.pronunciationAssessment + ? currentRecording.pronunciationAssessment + .pronunciationScore >= 80 + ? "text-green-500" + : currentRecording.pronunciationAssessment + .pronunciationScore >= 60 + ? "text-yellow-600" + : "text-red-500" + : "" + } `} /> {t("pronunciationAssessment")} diff --git a/enjoy/src/renderer/components/medias/media-player-controls.tsx b/enjoy/src/renderer/components/medias/media-player-controls.tsx index d1182fb9..fedd0629 100644 --- a/enjoy/src/renderer/components/medias/media-player-controls.tsx +++ b/enjoy/src/renderer/components/medias/media-player-controls.tsx @@ -456,7 +456,7 @@ export const MediaPlayerControls = () => { }, [grouping]); return ( -
+
@@ -480,11 +480,10 @@ export const MediaPlayerControls = () => { {PLAYBACK_RATE_OPTIONS.map((rate, i) => (
{ setPlaybackRate(rate); }} diff --git a/enjoy/src/renderer/components/medias/media-player.tsx b/enjoy/src/renderer/components/medias/media-player.tsx index d46134db..8a2734a1 100644 --- a/enjoy/src/renderer/components/medias/media-player.tsx +++ b/enjoy/src/renderer/components/medias/media-player.tsx @@ -41,6 +41,7 @@ const MAX_ZOOM_RATIO = 4.0; export const MediaPlayer = () => { const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext); const { + layout, media, currentTime, setRef, @@ -132,16 +133,7 @@ export const MediaPlayer = () => { useEffect(() => { calContainerWidth(); - window.addEventListener("resize", () => { - calContainerWidth(); - }); - - return () => { - window.removeEventListener("resize", () => { - calContainerWidth(); - }); - }; - }, []); + }, [layout.width]); return (
@@ -192,38 +184,44 @@ export const MediaPlayer = () => { - + { + layout.name === "lg" && ( + <> + - + + + ) + } @@ -239,6 +237,40 @@ export const MediaPlayer = () => { + { + layout.name === "sm" && ( + <> + { + if (zoomRatio > MIN_ZOOM_RATIO) { + const nextZoomRatio = ZOOM_RATIO_OPTIONS.reverse().find( + (rate) => rate < zoomRatio + ); + setZoomRatio(nextZoomRatio || MIN_ZOOM_RATIO); + } + }} + > + + {t("zoomOut")} + + + { + setDisplayInlineCaption(!displayInlineCaption); + if (pitchChart) { + pitchChart.options.scales.x.display = !displayInlineCaption; + pitchChart.update(); + } + }} + > + + {t("inlineCaption")} + + + ) + } { diff --git a/enjoy/src/renderer/components/medias/media-provider.tsx b/enjoy/src/renderer/components/medias/media-provider.tsx index 7e2de34e..98144b2d 100644 --- a/enjoy/src/renderer/components/medias/media-provider.tsx +++ b/enjoy/src/renderer/components/medias/media-provider.tsx @@ -20,8 +20,9 @@ export const MediaProvider = () => { if (!media?.src) return null; return ( -
+
{ diff --git a/enjoy/src/renderer/components/medias/media-tabs.tsx b/enjoy/src/renderer/components/medias/media-tabs.tsx index bfd8131c..617315fa 100644 --- a/enjoy/src/renderer/components/medias/media-tabs.tsx +++ b/enjoy/src/renderer/components/medias/media-tabs.tsx @@ -24,15 +24,13 @@ export const MediaTabs = () => { return (
{media.mediaType === "Video" && (
setTab("provider")} > {t("player")} @@ -40,25 +38,22 @@ export const MediaTabs = () => { )}
setTab("transcription")} > {t("transcription")}
setTab("recordings")} > {t("myRecordings")}
setTab("info")} > {t("mediaInfo")} diff --git a/enjoy/src/renderer/components/videos/video-player.tsx b/enjoy/src/renderer/components/videos/video-player.tsx index 0c7d1da4..153e61c4 100644 --- a/enjoy/src/renderer/components/videos/video-player.tsx +++ b/enjoy/src/renderer/components/videos/video-player.tsx @@ -7,12 +7,13 @@ import { MediaTabs, MediaCurrentRecording, MediaPlayer, + LoaderSpin, } from "@renderer/components"; import { useVideo } from "@renderer/hooks"; export const VideoPlayer = (props: { id?: string; md5?: string }) => { const { id, md5 } = props; - const { setMedia } = useContext(MediaPlayerProviderContext); + const { setMedia, layout } = useContext(MediaPlayerProviderContext); const { video } = useVideo({ id, md5 }); useEffect(() => { @@ -21,29 +22,31 @@ export const VideoPlayer = (props: { id?: string; md5?: string }) => { setMedia(video); }, [video]); + if (!layout) return ; + return (
-
-
-
+
+
+
-
+
-
-
- +
+
+
-
+
-
+
diff --git a/enjoy/src/renderer/context/media-player-provider.tsx b/enjoy/src/renderer/context/media-player-provider.tsx index 3afd4931..e9deaee3 100644 --- a/enjoy/src/renderer/context/media-player-provider.tsx +++ b/enjoy/src/renderer/context/media-player-provider.tsx @@ -11,8 +11,10 @@ import { TimelineEntry } from "echogarden/dist/utilities/Timeline.d.js"; import { IPA_MAPPING } from "@/constants"; import { toast } from "@renderer/components/ui"; import { Tooltip } from "react-tooltip"; +import { debounce } from "lodash"; type MediaPlayerContextType = { + layout: { name: string, width: number, height: number, upperWrapper: string, lowerWrapper: string, playerWrapper: string, panelWrapper: string, playerHeight: number }; media: AudioType | VideoType; setMedia: (media: AudioType | VideoType) => void; setMediaProvider: (mediaProvider: HTMLAudioElement | null) => void; @@ -68,15 +70,35 @@ type MediaPlayerContextType = { export const MediaPlayerProviderContext = createContext(null); +const LAYOUT = { + sm: { + name: 'sm', + upperWrapper: "h-[calc(100vh-27.5rem)]", + lowerWrapper: "h-[23rem]", + playerWrapper: "h-[9rem] mb-2", + panelWrapper: "h-16", + playerHeight: 128, + }, + lg: { + name: 'lg', + upperWrapper: "h-[calc(100vh-37.5rem)]", + lowerWrapper: "h-[33rem]", + panelWrapper: "h-20", + playerWrapper: "h-[13rem] mb-4", + playerHeight: 192, + }, +} + export const MediaPlayerProvider = ({ children, }: { children: React.ReactNode; }) => { - const height = 192; const minPxPerSec = 150; const { EnjoyApp } = useContext(AppSettingsProviderContext); + const [layout, setLayout] = useState<{ name: string, width: number, height: number, upperWrapper: string, lowerWrapper: string, playerWrapper: string, panelWrapper: string, playerHeight: number }>(); + const [media, setMedia] = useState(null); const [mediaProvider, setMediaProvider] = useState( null @@ -126,7 +148,7 @@ export const MediaPlayerProvider = ({ const ws = WaveSurfer.create({ container: ref.current, - height, + height: layout.playerHeight, waveColor: "#eaeaea", progressColor: "#c0d6df", cursorColor: "#ff0054", @@ -196,7 +218,7 @@ export const MediaPlayerProvider = ({ const canvasId = options?.canvasId || `pitch-contour-${region.id}-canvas`; canvas.id = canvasId; canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; + canvas.style.height = `${layout.playerHeight}px`; pitchContourWidthContainer.appendChild(canvas); pitchContourWidthContainer.style.position = "absolute"; @@ -204,7 +226,7 @@ export const MediaPlayerProvider = ({ pitchContourWidthContainer.style.left = "0"; pitchContourWidthContainer.style.width = `${width}px`; - pitchContourWidthContainer.style.height = `${height}px`; + pitchContourWidthContainer.style.height = `${layout.playerHeight}px`; pitchContourWidthContainer.style.marginLeft = `${offsetLeft}px`; pitchContourWidthContainer.classList.add( "pitch-contour", @@ -317,6 +339,16 @@ export const MediaPlayerProvider = ({ ); }; + const calculateHeight = () => { + if (window.innerHeight <= 1080) { + setLayout({ ...LAYOUT.sm, width: window.innerWidth, height: window.innerHeight }); + } else { + setLayout({ ...LAYOUT.lg, width: window.innerWidth, height: window.innerHeight }); + } + } + + const deboundeCalculateHeight = debounce(calculateHeight, 100); + /* * When wavesurfer is decoded, * set up event listeners for wavesurfer @@ -432,15 +464,37 @@ export const MediaPlayerProvider = ({ * and mediaProvider is available */ useEffect(() => { + if (!layout?.playerHeight) return; + if (!media) return; + if (!ref) return; + if (!mediaProvider) return; + initializeWavesurfer(); setDecoded(false); setDecodeError(null); - }, [media, ref, mediaProvider]); + + return () => { + if (wavesurfer) wavesurfer.destroy(); + } + }, [media, ref, mediaProvider, layout?.playerHeight]); + + useEffect(() => { + calculateHeight(); + + EnjoyApp.window.onResize((event, bounds) => { + deboundeCalculateHeight(); + }) + + return () => { + EnjoyApp.window.removeListeners(); + } + }, []) return ( <> Promise; version: string; }; + window: { + onResize: (callback: (event, bounds: any) => void) => void; + removeListeners: () => void; + }; system: { preferences: { mediaAccess: (mediaType: "microphone") => Promise;