Rework music player with bulma
This commit is contained in:
		
							parent
							
								
									aa721767fa
								
							
						
					
					
						commit
						9ffc145834
					
				
					 7 changed files with 154 additions and 121 deletions
				
			
		|  | @ -42,13 +42,17 @@ class MusicPlayer extends React.Component<MusicPlayerProps, State> { | |||
| 
 | ||||
|     render() { | ||||
|         return ( | ||||
|           <div id="player" style={{ height: "100%", width: "100%" }}> | ||||
|             <div className="is-flex-grow-1 is-flex is-flex-direction-column"> | ||||
|                 <div className="is-flex-grow-1 is-overflow-hidden"> | ||||
|                     <Visualizer | ||||
|                         audioContext={this.audioState.audioContext} | ||||
|                         audioSource={this.audioState.audioSourceNode} | ||||
|                     /> | ||||
|                 </div> | ||||
|                 <div className="is-flex-grow-0"> | ||||
|                     <Controls /> | ||||
|                 </div> | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,29 +11,45 @@ type ControlProps = { | |||
| 
 | ||||
| class Controls extends React.Component<ControlProps, State> { | ||||
|     render() { | ||||
|         return ( | ||||
|             <div id="playerControls" className="container-fluid fixed-bottom"> | ||||
|                 <div className="align-items-center row p-2"> | ||||
|                     <Indicator></Indicator> | ||||
|                     <div | ||||
|                         id="playerText" | ||||
|                         className="text-justify text-truncate col-6 playerControlsContent" | ||||
|                     > | ||||
|                         {this.props.title.name} - {this.props.title.album} | ||||
|         let title = ( | ||||
|             <div className="notification is-primary"> | ||||
|                 <div className="level-item">{this.props.title.name}</div> | ||||
|             </div> | ||||
|         ); | ||||
| 
 | ||||
|                     {this.props.title.name === "Journey" && | ||||
|                         this.props.title.artist === "Mseq" ? ( | ||||
|                             <div id="copyrightNotice" className="col text-center"> | ||||
|                                 <a href="http://dig.ccmixter.org/files/Mseq/54702">Journey</a> | ||||
|                by Mseq (c) copyright 2016 Licensed under a Creative | ||||
|               Commons  | ||||
|         if ( | ||||
|             this.props.title.name == "Journey" && | ||||
|             this.props.title.artist == "Mseq" | ||||
|         ) { | ||||
|             title = ( | ||||
|                 <div className="notification is-primary"> | ||||
|                     <div className="level-item"> | ||||
|                         <a href="http://dig.ccmixter.org/files/Mseq/54702"> | ||||
|                             Journey | ||||
|                         </a> | ||||
|                          by Mseq (c) copyright 2016 Licensed under a | ||||
|                         Creative Commons  | ||||
|                         <a href="http://creativecommons.org/licenses/by-nc/3.0/"> | ||||
|                             Attribution Noncommercial (3.0) | ||||
|                         </a> | ||||
|                           license. Ft: Admiral Bob,Texas Radio Fish | ||||
|                     </div> | ||||
|                         ) : null} | ||||
|                 </div> | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <div className="notification is-primary"> | ||||
|                 <div className="level"> | ||||
|                     <div className="level-left"> | ||||
|                         <Indicator></Indicator> | ||||
|                         {title} | ||||
|                     </div> | ||||
|                     <div className="level-right"> | ||||
|                         <div className="level-item"> | ||||
|                             {this.props.title.artist} | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         ); | ||||
|  |  | |||
|  | @ -22,22 +22,30 @@ class Indicator extends React.Component<Props, State> { | |||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const classes = classNames({ | ||||
|             btn: true, | ||||
|             "col-auto": true, | ||||
|         const button_classes = classNames({ | ||||
|             button: true, | ||||
|             "is-primary": true, | ||||
|             "level-item": true, | ||||
|             // TODO(tlater): Add loading logic here
 | ||||
|         }); | ||||
| 
 | ||||
|         const icon_classes = classNames({ | ||||
|             fas: true, | ||||
|             "fa-2x": true, | ||||
|             "fa-muted": this.props.muted, | ||||
|             "fa-play": this.props.playing, | ||||
|             "fa-pause": !this.props.playing, | ||||
|             "fa-play": !this.props.playing, | ||||
|             "fa-pause": this.props.playing, | ||||
|         }); | ||||
| 
 | ||||
|         return ( | ||||
|             <button | ||||
|                 type="button" | ||||
|                 id="playerIndicator" | ||||
|                 onClick={this.click.bind(this)} | ||||
|                 className={classes} | ||||
|             ></button> | ||||
|                 className={button_classes}> | ||||
|                 <span className="icon is-medium"> | ||||
|                     <i className={icon_classes}></i> | ||||
|                 </span> | ||||
|             </button> | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -80,7 +80,7 @@ class CanvasDrawer { | |||
|         }); | ||||
| 
 | ||||
|         this.renderer.setClearColor(new three.Color(0x0f0f0f)); | ||||
|         this.renderer.setSize(canvas.width, canvas.height); | ||||
|         this.renderer.setSize(canvas.width, canvas.height, false); | ||||
| 
 | ||||
|         // Set up canvas resizing
 | ||||
|         window.addEventListener("resize", this.resize.bind(this)); | ||||
|  | @ -147,28 +147,31 @@ class CanvasDrawer { | |||
| 
 | ||||
|     resize() { | ||||
|         const canvas = this.canvas; | ||||
| 
 | ||||
|         if (canvas.parentElement === null) { | ||||
|             throw Error("Could not access canvas parent for size calculation"); | ||||
|         } | ||||
| 
 | ||||
|         // Compute the height of all our siblings
 | ||||
|         let combinedHeight = 0; | ||||
|         for (let i = 0; i < canvas.parentElement.children.length; i++) { | ||||
|             const child = canvas.parentElement.children[i]; | ||||
|         // This is stupid, but by setting the canvas proportions to 0
 | ||||
|         // for a split second the browser can actually figure out the
 | ||||
|         // height of the parentElement.
 | ||||
|         //
 | ||||
|         // If the canvas is allowed to keep its height, it will
 | ||||
|         // prevent the parentElement from being able to shrink because
 | ||||
|         // its contents are filling it completely.
 | ||||
|         //
 | ||||
|         // I've seen others use `overflow: hidden` to achieve this
 | ||||
|         // (and in fact seen it render in my browser on jsfiddle), but
 | ||||
|         // that doesn't work here somehow.
 | ||||
|         canvas.height = 0; | ||||
|         canvas.width = 0; | ||||
| 
 | ||||
|             if (child != canvas) { | ||||
|                 combinedHeight += child.clientHeight; | ||||
|             } | ||||
|         } | ||||
|         let height = canvas.parentElement.clientHeight; | ||||
|         let width = canvas.parentElement.clientWidth; | ||||
| 
 | ||||
|         // The remaining space we want to fill
 | ||||
|         const remainingHeight = canvas.parentElement.clientHeight - combinedHeight; | ||||
|         canvas.height = remainingHeight; | ||||
|         canvas.width = canvas.parentElement.clientWidth; | ||||
| 
 | ||||
|         this.camera.aspect = canvas.width / remainingHeight; | ||||
|         this.camera.aspect = width / height; | ||||
|         this.camera.updateProjectionMatrix(); | ||||
|         this.renderer.setSize(canvas.width, remainingHeight); | ||||
|         this.renderer.setSize(width, height, false); | ||||
|     } | ||||
| 
 | ||||
|     stop() { | ||||
|  | @ -179,9 +182,9 @@ class CanvasDrawer { | |||
| } | ||||
| 
 | ||||
| class Visualizer extends React.Component<VisualizerProps, State> { | ||||
|     private analyser: AnalyserNode; | ||||
|     private analyser?: AnalyserNode; | ||||
|     private canvas: React.RefObject<HTMLCanvasElement>; | ||||
|     private drawer: CanvasDrawer; | ||||
|     private drawer?: CanvasDrawer; | ||||
| 
 | ||||
|     constructor(props: VisualizerProps) { | ||||
|         super(props); | ||||
|  | @ -191,10 +194,10 @@ class Visualizer extends React.Component<VisualizerProps, State> { | |||
|     render(): React.ReactNode { | ||||
|         return ( | ||||
|             <canvas | ||||
|                 id="visualizer" | ||||
|                 ref={this.canvas} | ||||
|                 style={{ width: "100%", height: "100%" }} | ||||
|             ></canvas> | ||||
|                 style={{ | ||||
|                     display: "block", | ||||
|                 }}></canvas> | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|  | @ -207,11 +210,14 @@ class Visualizer extends React.Component<VisualizerProps, State> { | |||
|         this.analyser.fftSize = 2048; | ||||
|         this.analyser.smoothingTimeConstant = 0.8; | ||||
|         this.props.audioSource.connect(this.analyser); | ||||
| 
 | ||||
|         this.drawer = new CanvasDrawer(this.analyser, this.canvas.current); | ||||
|     } | ||||
| 
 | ||||
|     componentWillUnmount(): void { | ||||
|         if (!this.drawer || !this.analyser) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.drawer.stop(); | ||||
|         this.props.audioSource.disconnect(this.analyser); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import React from "react"; | ||||
| import ReactDOM from "react-dom"; | ||||
| import { createRoot } from "react-dom/client"; | ||||
| import { Provider } from "react-redux"; | ||||
| 
 | ||||
| import { store } from "./store"; | ||||
|  | @ -10,11 +9,11 @@ import mseq from "./Mseq_-_Journey.mp3"; | |||
| 
 | ||||
| const rootElement = document.getElementById("playerUI"); | ||||
| 
 | ||||
| ReactDOM.render( | ||||
| const root = createRoot(rootElement!); | ||||
| root.render( | ||||
|     <Provider store={store}> | ||||
|         <MusicPlayer /> | ||||
|     </Provider>, | ||||
|     rootElement | ||||
|     </Provider> | ||||
| ); | ||||
| 
 | ||||
| store.dispatch(setSource(mseq)); | ||||
|  |  | |||
|  | @ -3,6 +3,6 @@ $fa-font-path: "npm:@fortawesome/fontawesome-free/webfonts"; | |||
| @import "~/node_modules/@fortawesome/fontawesome-free/scss/fontawesome"; | ||||
| @import "~/node_modules/@fortawesome/fontawesome-free/scss/solid"; | ||||
| 
 | ||||
| #playerControls { | ||||
|   background-color: #11151c; | ||||
| .is-overflow-hidden { | ||||
|   overflow: hidden !important; | ||||
| } | ||||
|  |  | |||
|  | @ -3,8 +3,8 @@ | |||
|     <link rel="stylesheet" , href="music/music.scss" /> | ||||
|   </block> | ||||
| 
 | ||||
|   <block name="footer"> | ||||
|     <div id="playerUI" class="container-fluid flex-grow-1"></div> | ||||
|   <block name="content"> | ||||
|     <div id="playerUI" class="is-flex-grow-1 is-flex"></div> | ||||
|     <script type="module" src="./music/index.tsx"></script> | ||||
|   </block> | ||||
| </extends> | ||||
|  |  | |||
		Reference in a new issue