license import { Alignment , Button , ButtonGroup , Classes , Menu , MenuItem , Navbar , NavbarDivider , NavbarGroup , Popover , Tooltip , } from "@blueprintjs/core" ; import { css , StyleSheet } from "aphrodite" ; import React from "react" ; import * as logoSvg from "./logo.svg" ; import { Auth } from "./auth" ; import FileMenu from "./FileMenu" ; export type ViewMode = "view" | "edit" | "both" ; export const MODE_VIEW = "view" ; export const MODE_BOTH = "both" ; export const MODE_EDIT = "edit" ; export const MIN_BOTH_WIDTH = 630 ; export const MIN_REASONABLE_WIDTH = 470 ; interface Player { addChangeListener ( fn ) : void ; destroy ( ) : void ; getDuration ( ) : number ; pause ( ) : void ; play ( ) : void ; removeChangeListener ( fn ) : void ; seek ( time ) : void ; } interface Props { auth : Auth | null ; canExport : boolean ; colourScheme : "vs-dark" | "vs" ; inSandbox : boolean ; isDirty : boolean ; readOnly : boolean ; loggedIn : boolean ; midi : ArrayBuffer | null ; mode : ViewMode ; online : boolean ; sandboxIsDirty : boolean ; song : string | undefined ; windowWidth : number ; songURL : string | null ; onModeChanged ( mode ) : void ; onShowClone ( ) : void ; onShowMakelily ( tool ) : void ; onShowNew ( ) : void ; onShowOpen ( ) : void ; onExportLy ( ) : any ; onExportMIDI ( ) : any ; onExportPDF ( ) : any ; onShowPublish ( ) : void ; onDeleteSong ( song ) : void ; onLoadSong ( song ) : void ; onShowAbout ( ) : void ; onSignIn ( ) : void ; onSignOut ( ) : void ; setColourScheme ( colourScheme ) : void ; } interface State { played : boolean ; player : Player | null ; playing : boolean ; timeInSeconds : number ; } /**
 * Renders the top of the app.
 */ export default class Header extends React . PureComponent < Props > { state : State = { played : false , player : null , playing : false , timeInSeconds : 0 , } ; UNSAFE_componentWillReceiveProps ( props ) { if ( props . midi !== this . props . midi && this . state . player ) { const player = this . state . player ; this . setState ( { player : null , playing : false , } , ( ) => { player . destroy ( ) ; } ) ; } } render ( ) { const { auth , canExport , colourScheme , readOnly , inSandbox , mode , midi , onDeleteSong , onExportLy , onExportMIDI , onExportPDF , onLoadSong , onShowAbout , onShowClone , onShowOpen , onShowNew , onShowPublish , onSignIn , onSignOut , online , setColourScheme , songURL , windowWidth , } = this . props ; const { played , playing } = this . state ; const viewMode = windowWidth < MIN_BOTH_WIDTH && ( < React . Fragment > < Button onClick = { this . handleSetView } active = { mode === MODE_VIEW } icon = "eye-open" / > < Button onClick = { this . handleSetEdit } active = { mode === MODE_EDIT } icon = "edit" / > < /React.Fragment>); let playButton = ( < Button onClick = { playing ? this . handlePause : this . handlePlay } icon = { playing ? "pause" : "play" } disabled = { ! midi } > { playing ? "Pause" : "Play" } < /Button>); if ( ! midi ) { playButton = ( < Tooltip content = { "No MIDI data found. Make sure you have " + "a \\midi {} and a \\layout {} in your \\score {}." } > { playButton } < /Tooltip>); } return ( < Navbar > < NavbarGroup align = { Alignment . LEFT } > { windowWidth >= MIN_REASONABLE_WIDTH && ( < img src = { logoSvg } className = { css ( styles . logo ) } alt = "" / > ) } { windowWidth >= MIN_BOTH_WIDTH && ( < div className = { Classes . NAVBAR_HEADING } > Hacklily < /div>)} { windowWidth >= MIN_BOTH_WIDTH && < NavbarDivider />} < ButtonGroup > < Popover content = { < FileMenu auth = { auth } canCreateNew = { online } canSave = { online && ! readOnly } canSaveAs = { online && ! inSandbox } canExport = { canExport } colourScheme = { colourScheme } onDeleteSong = { onDeleteSong } onExportLy = { onExportLy } onExportMIDI = { onExportMIDI } onExportPDF = { onExportPDF } onLoadSong = { onLoadSong } onShowAbout = { onShowAbout } onShowClone = { onShowClone } onShowOpen = { onShowOpen } onShowPublish = { onShowPublish } onShowNew = { onShowNew } onSignIn = { onSignIn } onSignOut = { onSignOut } setColourScheme = { setColourScheme } songURL = { songURL } / > } autoFocus = { false } > < Button icon = "document" rightIcon = "caret-down" > { windowWidth >= MIN_REASONABLE_WIDTH && < span > File < /span>} < /Button> < /Popover> { windowWidth >= MIN_BOTH_WIDTH && ( < Popover content = { < Menu > < MenuItem icon = "blank" text = "Clef&hellip;" onClick = { this . handleShowClef } / > < MenuItem icon = "blank" text = "Key Signature&hellip;" onClick = { this . handleShowKey } / > < MenuItem icon = "blank" text = "Time Signature&hellip;" onClick = { this . handleShowTime } / > < MenuItem icon = "music" text = "Notes&hellip;" onClick = { this . handleShowNotes } / > < /Menu>} autoFocus={false}> < Button icon = "draw" rightIcon = "caret-down" > Insert < /Button> < /Popover>)} { viewMode } { played && midi != null ? this . renderTime ( ) : playButton } < /ButtonGroup> < /NavbarGroup> < /Navbar>); } private handleFastForward = ( ) : void => { if ( ! this . state . player ) { return ; } this . state . player . seek ( this . state . timeInSeconds + 4 ) ; } ; private handlePause = async ( ) : Promise < void > => { this . setState ( { playing : false , } ) ; const { player } = this . state ; if ( player ) { player . pause ( ) ; } } ; private handlePlay = async ( ) : Promise < void > => { if ( ! this . props . midi ) { alert ( "No MIDI data found. Make sure you have " + "a \\midi {} and a \\layout {} in your \\score {}." ) ; return ; } let player ; if ( this . state . player ) { player = this . state . player ; } else { const { playerFromMIDIBuffer } = await import ( "hackmidi" ) ; player = await playerFromMIDIBuffer ( this . props . midi , "https://www.hacklily.org/hackmidi/samples/" ) ; this . setState ( { player , } ) ; player . addChangeListener ( this . handlePlaying ) ; } player . play ( ) ; this . setState ( { played : true , playing : true , } ) ; } ; private handlePlaying = ( timeInSeconds , playing ) : void => { const wasPlaying = this . state . playing ; this . setState ( { playing , timeInSeconds , } ) ; const player = this . state . player ; if ( ! player ) { console . log ( "handlePlaying called, but there's no song" ) ; return ; } if ( wasPlaying && ! playing && timeInSeconds === 0 ) { this . setState ( { player : null , } , ( ) => { setTimeout ( ( ) => { player . destroy ( ) ; } , 0 ) ; } ) ; } } ; private handleSetView = ( ) : void => { this . props . onModeChanged ( MODE_VIEW ) ; } ; private handleSetEdit = ( ) : void => { this . props . onModeChanged ( MODE_EDIT ) ; } ; private handleRewind = ( ) : void => { if ( ! this . state . player ) { return ; } this . state . player . seek ( Math . max ( 0 , this . state . timeInSeconds - 4 ) ) ; } ; private handleShowClef = ( ) : void => { this . props . onShowMakelily ( "clef" ) ; } ; private handleShowKey = ( ) : void => { this . props . onShowMakelily ( "key" ) ; } ; private handleShowTime = ( ) : void => { this . props . onShowMakelily ( "time" ) ; } ; private handleShowNotes = ( ) : void => { this . props . onShowMakelily ( "notes" ) ; } ; private renderTime ( ) { const { playing , timeInSeconds } = this . state ; const { windowWidth } = this . props ; let fmtTime = String ( Math . floor ( timeInSeconds * 100 ) / 100 ) ; if ( fmtTime . split ( "." ) [ 1 ] == null ) { fmtTime += ".00" ; } while ( fmtTime . split ( "." ) [ 1 ] . length < 2 ) { fmtTime += "0" ; } return ( < React . Fragment > < Button title = "Rewind" icon = "fast-backward" onClick = { this . handleRewind } / > < Button title = { playing ? "Pause" : "Play" } onClick = { playing ? this . handlePause : this . handlePlay } icon = { playing ? "pause" : "play" } / > { windowWidth >= MIN_REASONABLE_WIDTH && ( < Button className = { css ( styles . playTime ) + " " + Classes . MONOSPACE_TEXT } disabled = { true } > { fmtTime } < /Button>)} < Button icon = "fast-forward" title = "Fast-forward" onClick = { this . handleFastForward } / > < /React.Fragment>); } } const styles = StyleSheet . create ( { logo : { marginRight : 16 , padding : "9px 0" , transform : "scale(-1, 1)" , width : 32 , } , playTime : { textAlign : "left" , width : 80 , justifyContent : "flex-end" , } , } ) ;