From 95bae7cb614f6377a7431fe4cf8432eb93645914 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Mon, 29 May 2023 16:05:14 +0000 Subject: [PATCH] improve handlers & style --- packages/app/src/pages/lyrics/index.jsx | 379 +++++++++++++++-------- packages/app/src/pages/lyrics/index.less | 80 ++++- 2 files changed, 314 insertions(+), 145 deletions(-) diff --git a/packages/app/src/pages/lyrics/index.jsx b/packages/app/src/pages/lyrics/index.jsx index 96bd37e8..ac3c91dd 100644 --- a/packages/app/src/pages/lyrics/index.jsx +++ b/packages/app/src/pages/lyrics/index.jsx @@ -1,10 +1,8 @@ import React from "react" import classnames from "classnames" -import { Button } from "antd" +import Marquee from "react-fast-marquee" -import UseAnimations from "react-useanimations" -import LoadingAnimation from "react-useanimations/lib/loading" -import { Icons } from "components/Icons" +import Controls from "components/Player/Controls" import Image from "components/Image" @@ -12,6 +10,16 @@ import request from "comty.js/handlers/request" import "./index.less" +function RGBStringToValues(rgbString) { + if (!rgbString) { + return [0, 0, 0] + } + + const rgb = rgbString.replace("rgb(", "").replace(")", "").split(",").map((v) => parseInt(v)) + + return [rgb[0], rgb[1], rgb[2]] +} + function composeRgbValues(values) { let value = "" @@ -37,12 +45,33 @@ function calculateLineTime(line) { return line.endTimeMs - line.startTimeMs } +function isOverflown(element) { + if (!element) { + console.log("element is null") + return false + } + + return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth; +} + class PlayerController extends React.Component { state = { - hovering: false, colorAnalysis: null, - currentState: null, currentDragWidth: 0, + titleOverflown: false, + + currentDuration: 0, + currentTime: 0, + + currentPlaying: app.cores.player.getState("currentAudioManifest"), + loading: app.cores.player.getState("loading") ?? false, + playbackStatus: app.cores.player.getState("playbackStatus") ?? "stopped", + + audioMuted: app.cores.player.getState("audioMuted") ?? false, + volume: app.cores.player.getState("audioVolume"), + + syncModeLocked: app.cores.player.getState("syncModeLocked"), + syncMode: app.cores.player.getState("syncMode"), } events = { @@ -50,16 +79,37 @@ class PlayerController extends React.Component { this.setState({ colorAnalysis }) }, "player.seek.update": (seekTime) => { - const updatedState = this.state.currentState - - updatedState.time = seekTime - this.setState({ - currentState: updatedState, + currentTime: seekTime, }) }, + "player.status.update": (data) => { + this.setState({ playbackStatus: data }) + }, + "player.current.update": (data) => { + this.setState({ titleOverflown: false }) + + this.setState({ currentPlaying: data }) + }, + "player.syncModeLocked.update": (to) => { + this.setState({ syncModeLocked: to }) + }, + "player.syncMode.update": (to) => { + this.setState({ syncMode: to }) + }, + "player.mute.update": (data) => { + this.setState({ audioMuted: data }) + }, + "player.volume.update": (data) => { + this.setState({ audioVolume: data }) + }, + "player.loading.update": (data) => { + this.setState({ loading: data }) + }, } + titleRef = React.createRef() + startSync() { // create a interval to get state from player if (this.syncInterval) { @@ -69,7 +119,15 @@ class PlayerController extends React.Component { this.syncInterval = setInterval(() => { const currentState = app.cores.player.currentState() - this.setState({ currentState }) + this.setState({ + currentDuration: currentState.duration, + currentTime: currentState.time, + colorAnalysis: currentState.colorAnalysis, + }) + + const titleOverflown = isOverflown(this.titleRef.current) + + this.setState({ titleOverflown: titleOverflown }) }, 800) } @@ -82,13 +140,21 @@ class PlayerController extends React.Component { } onClickTooglePlayButton = () => { - if (this.state.currentState?.playbackStatus === "playing") { + if (this.state?.playbackStatus === "playing") { app.cores.player.playback.pause() } else { app.cores.player.playback.play() } } + updateVolume = (value) => { + app.cores.player.volume(value) + } + + toogleMute = () => { + app.cores.player.toogleMute() + } + componentDidMount() { for (const event in this.events) { app.eventBus.on(event, this.events[event]) @@ -99,6 +165,26 @@ class PlayerController extends React.Component { } this.startSync() + + // // create a intersection observer to check if title is overflown + // // if the entire title is not visible, we will use marquee + // this.titleObserver = new IntersectionObserver((entries) => { + // for (const entry of entries) { + // if (entry.isIntersecting) { + // this.setState({ titleOverflown: false }) + // } else { + // this.setState({ titleOverflown: true }) + // } + // } + // }, { + // root: null, + // rootMargin: "0px", + // threshold: 1.0, + // }) + + console.log(this.titleRef.current) + + //this.titleObserver.observe(this.titleRef.current) } componentWillUnmount() { @@ -121,70 +207,91 @@ class PlayerController extends React.Component { } render() { + //const bgColor = RGBStringToValues(getComputedStyle(document.documentElement).getPropertyValue("--background-color-accent-values")) + return
{ - this.setState({ hovering: true }) - }} - onMouseLeave={() => { - this.setState({ hovering: false }) - }} className={classnames( "player_controller", - { - ["player_controller--hovering"]: this.state.hovering || this.state.dragging, - } )} >
- {this.state.currentState?.manifest?.title} + { +

+ { + this.state.plabackState === "stopped" ? "Nothing is playing" : <> + {this.state.currentPlaying?.title ?? "Nothing is playing"} + + } +

+ } + + {this.state.titleOverflown && + +

+ { + this.state.plabackState === "stopped" ? "Nothing is playing" : <> + {this.state.currentPlaying?.title ?? "Nothing is playing"} + + } +

+
}
- {this.state.currentState?.manifest?.artist} - {this.state.currentState?.manifest?.album} + { + this.state.currentPlaying?.artist && <> +

+ {this.state.currentPlaying?.artist ?? "Unknown"} +

+ { + this.state.currentPlaying?.album && <> + - +

+ {this.state.currentPlaying?.album ?? "Unknown"} +

+ + } + + }
-
- -
+
@@ -197,7 +304,7 @@ class PlayerController extends React.Component { }} onMouseUp={(e) => { const rect = e.currentTarget.getBoundingClientRect() - const seekTime = this.state.currentState?.duration * (e.clientX - rect.left) / rect.width + const seekTime = this.state.currentDuration * (e.clientX - rect.left) / rect.width this.onDragEnd(seekTime) }} @@ -210,7 +317,7 @@ class PlayerController extends React.Component { >
@@ -234,20 +341,11 @@ export default class SyncLyrics extends React.Component { colorAnalysis: null, - classnames: [ - { - name: "cinematic-mode", - enabled: false, - }, - { - name: "centered-player", - enabled: false, - }, - { - name: "video-canvas-enabled", - enabled: false, - } - ] + classnames: { + "cinematic-mode": false, + "centered-player": false, + "video-canvas-enabled": false, + } } visualizerRef = React.createRef() @@ -274,6 +372,56 @@ export default class SyncLyrics extends React.Component { } } + toogleClassName = (className, to) => { + if (typeof to === "undefined") { + to = !this.state.classnames[className] + } + + if (to) { + if (this.state.classnames[className] === true) { + return false + } + + //app.message.info("Toogling on " + className) + + this.setState({ + classnames: { + ...this.state.classnames, + [className]: true + }, + }) + + return true + } else { + if (this.state.classnames[className] === false) { + return false + } + + //app.message.info("Toogling off " + className) + + this.setState({ + classnames: { + ...this.state.classnames, + [className]: false + }, + }) + + return true + } + } + + toogleVideoCanvas = (to) => { + return this.toogleClassName("video-canvas-enabled", to) + } + + toogleCenteredControllerMode = (to) => { + return this.toogleClassName("centered-player", to) + } + + toogleCinematicMode = (to) => { + return this.toogleClassName("cinematic-mode", to) + } + isCurrentLine = (line) => { if (!this.state.currentLine) { return false @@ -342,9 +490,13 @@ export default class SyncLyrics extends React.Component { if (data.canvas_url) { //app.message.info("Video canvas loaded") + console.log(`[SyncLyrics] Video canvas loaded`) + this.toogleVideoCanvas(true) } else { //app.message.info("No video canvas available for this song") + console.log(`[SyncLyrics] No video canvas available for this song`) + this.toogleVideoCanvas(false) } @@ -352,10 +504,13 @@ export default class SyncLyrics extends React.Component { if (data.lines.length === 0 || data.syncType !== "LINE_SYNCED") { //app.message.info("No lyrics available for this song") + console.log(`[SyncLyrics] No lyrics available for this song, sync type [${data.syncType}]`) + this.toogleCinematicMode(false) this.toogleCenteredControllerMode(true) } else { //app.message.info("Lyrics loaded, starting sync...") + console.log(`[SyncLyrics] Starting sync with type [${data.syncType}]`) this.toogleCenteredControllerMode(false) this.startLyricsSync() @@ -365,65 +520,11 @@ export default class SyncLyrics extends React.Component { this.setState({ loading: false, syncType: data.syncType, - canvas_url: data.canvas_url, + canvas_url: data.canvas_url ?? null, lyrics: data.lines, }) } - toogleClassName = (className, to) => { - let currentState = this.state.classnames.find((c) => c.name === className) - - if (!currentState) { - return false - } - - if (typeof to === "undefined") { - to = !currentState?.enabled - } - - if (to) { - if (currentState.enabled) { - return false - } - - //app.message.info("Toogling on " + className) - - currentState.enabled = true - - this.setState({ - classnames: this.state.classnames, - }) - - return true - } else { - if (!currentState.enabled) { - return false - } - - //app.message.info("Toogling off " + className) - - currentState.enabled = false - - this.setState({ - classnames: this.state.classnames, - }) - - return true - } - } - - toogleVideoCanvas = (to) => { - return this.toogleClassName("video-canvas-enabled", to) - } - - toogleCenteredControllerMode = (to) => { - return this.toogleClassName("centered-player", to) - } - - toogleCinematicMode = (to) => { - return this.toogleClassName("cinematic-mode", to) - } - startLyricsSync = () => { // create interval to sync lyrics if (this.syncInterval) { @@ -495,8 +596,16 @@ export default class SyncLyrics extends React.Component { if (this.state.canvas_url) { if (line.words === "♪" || line.words === "♫" || line.words === " " || line.words === "") { + //console.log(`[SyncLyrics] Toogling cinematic mode on because line is empty`) + this.toogleCinematicMode(true) } else { + //console.log(`[SyncLyrics] Toogling cinematic mode off because line is not empty`) + + this.toogleCinematicMode(false) + } + } else { + if (this.state.classnames["cinematic-mode"] === true) { this.toogleCinematicMode(false) } } @@ -587,14 +696,10 @@ export default class SyncLyrics extends React.Component { "lyrics_viewer", { ["text_dark"]: this.state.colorAnalysis?.isDark ?? false, - ...this.state.classnames.map((classname) => { + ...Object.entries(this.state.classnames).reduce((acc, [key, value]) => { return { - [classname.name]: classname.enabled, - } - }).reduce((a, b) => { - return { - ...a, - ...b, + ...acc, + [key]: value, } }, {}), }, @@ -628,7 +733,7 @@ export default class SyncLyrics extends React.Component { className="lyrics_viewer_thumbnail" >
diff --git a/packages/app/src/pages/lyrics/index.less b/packages/app/src/pages/lyrics/index.less index fc28df44..327592c9 100644 --- a/packages/app/src/pages/lyrics/index.less +++ b/packages/app/src/pages/lyrics/index.less @@ -1,4 +1,7 @@ @enabled-video-canvas-opacity: 0.4; +// in px +@cover-width: 150px; +@left-panel-width: 300px; .lyrics_viewer { display: flex; @@ -66,10 +69,24 @@ border-radius: 18px; - gap: 50px; + gap: 0; + + padding: 20px 40px; + + .player_controller_left { + width: 100%; + max-width: 100%; + min-width: 100%; + } .player_controller_cover { width: 0px; + + min-width: 0px; + + img { + min-width: 0px; + } } .player_controller_info { @@ -87,7 +104,7 @@ -webkit-backdrop-filter: blur(0px) } - .lyrics_viewer_background { + .lyrics_viewer_video_canvas { video { opacity: 1; } @@ -264,13 +281,18 @@ transition: all 150ms ease-in-out; + .marquee-container { + gap: 60px; + } + .player_controller { + box-sizing: border-box; display: flex; flex-direction: row; align-items: center; - width: 25vw; + width: 100%; min-width: 350px; max-width: 500px; @@ -299,7 +321,7 @@ overflow: hidden; - &.player_controller--hovering { + &:hover { .player_controller_controls { height: 8vh; max-height: 100px; @@ -326,7 +348,10 @@ align-items: center; justify-content: center; - width: 10vw; + width: @cover-width; + min-width: @cover-width; + max-width: @cover-width; + height: 100%; img { @@ -340,6 +365,7 @@ } .player_controller_left { + flex: 0; display: flex; flex-direction: column; @@ -347,6 +373,7 @@ justify-content: center; height: 100%; + width: @left-panel-width; transition: all 150ms ease-in-out; @@ -354,7 +381,9 @@ display: flex; flex-direction: column; - align-items: flex-start; + //align-items: flex-start; + + width: 100%; gap: 10px; @@ -364,12 +393,45 @@ font-size: 1.5rem; font-weight: 600; - color: var(--background-color-contrast) + width: 100%; + + color: var(--background-color-contrast); + + h4 { + margin: 0; + } + + .player_controller_info_title_text { + transition: all 150ms ease-in-out; + + width: 90%; + + overflow: hidden; + + // do not wrap text + white-space: nowrap; + + &.overflown { + opacity: 0; + height: 0px; + } + } } .player_controller_info_artist { - font-size: 0.8rem; + display: flex; + flex-direction: row; + + align-items: center; + + gap: 7px; + + font-size: 0.6rem; font-weight: 400; + + h3 { + margin: 0; + } } } } @@ -385,6 +447,8 @@ padding: 10px; + width: 100%; + height: 0px; opacity: 0;