import { AppState , PlaylistTrack , WebampWindow , WindowId , WindowInfo , LoadedURLTrack , WindowPositions , PlaylistStyle , TransitionType , MediaStatus , TimeMode , SkinImages , Cursors , SkinRegion , GenLetterWidths , MilkdropMessage , DummyVizData , } from "./types" ; import { createSelector , defaultMemoize } from "reselect" ; import * as Utils from "./utils" ; import { BANDS , TRACK_HEIGHT , WINDOW_RESIZE_SEGMENT_WIDTH , WINDOW_RESIZE_SEGMENT_HEIGHT , WINDOW_WIDTH , MEDIA_STATUS , MEDIA_TAG_REQUEST_STATUS , WINDOWS , VISUALIZERS , } from "./constants" ; import { createPlaylistURL } from "./playlistHtml" ; import * as fromTracks from "./reducers/tracks" ; import * as fromDisplay from "./reducers/display" ; import * as fromEqualizer from "./reducers/equalizer" ; import * as fromMedia from "./reducers/media" ; import * as fromWindows from "./reducers/windows" ; import * as MarqueeUtils from "./marqueeUtils" ; import { generateGraph } from "./resizeUtils" ; import { SerializedStateV1 } from "./serializedStates/v1Types" ; export const getSliders = ( state ) => state . equalizer . sliders ; export const getEqfData = createSelector ( getSliders , ( sliders ) => { const preset = { name : "Entry1" , preamp : Utils . denormalizeEqBand ( sliders . preamp ) , } ; BANDS . forEach ( ( band ) => { preset [ `hz${ band }` ] = Utils . denormalizeEqBand ( sliders [ band ] ) ; } ) ; const eqfData = { presets : [ preset ] , type : "Winamp EQ library file v1.1" , } ; return eqfData ; } ) ; export const getTracks = ( state ) => state . tracks ; export const getTrackUrl = ( state ) => { return ( id ) : string | null => { return state . tracks [ id ] ?. url ; } ; } ; export const getTrackOrder = ( state ) => state . playlist . trackOrder ; export const getTrackCount = createSelector ( getTrackOrder , ( trackOrder ) => trackOrder . length ) ; export const getOrderedTracks = createSelector ( getTracks , getTrackOrder , ( tracks , trackOrder ) => trackOrder . filter ( ( id ) => tracks [ id ] ) ) ; export const getUserTracks = createSelector ( getTracks , getTrackOrder , ( tracks , trackOrder ) => trackOrder . map ( ( id ) => { const track = tracks [ id ] ; return { url : track . url , metaData : { artist : track . artist || "" , title : track . title || "" , album : track . album , albumArtUrl : track . albumArtUrl || "" , } , } ; } ) ) ; const getOrderedTrackObjects = createSelector ( getTracks , getOrderedTracks , ( tracks , trackOrder ) : PlaylistTrack [ ] => trackOrder . map ( ( id ) => tracks [ id ] ) ) ; export const getSelectedTrackIds = ( state ) : Set < number > => { return state . playlist . selectedTracks ; } ; export const getSelectedTrackObjects = createSelector ( getOrderedTrackObjects , getSelectedTrackIds , ( tracks , selectedIds ) => tracks . filter ( ( track ) => selectedIds . has ( track . id ) ) ) ; const runningTimeFromTracks = ( tracks ) => tracks . reduce ( ( time , track ) => time + Number ( track . duration ) , 0 ) ; const getTotalRunningTime = createSelector ( getOrderedTrackObjects , runningTimeFromTracks ) ; const getSelectedRunningTime = createSelector ( getSelectedTrackObjects , runningTimeFromTracks ) ; export const getRunningTimeMessage = createSelector ( getTotalRunningTime , getSelectedRunningTime , ( totalRunningTime , selectedRunningTime ) => `${ Utils . getTimeStr ( selectedRunningTime ) }/${ Utils . getTimeStr ( totalRunningTime ) }` ) ; export const getCurrentTrackIndex = ( state ) : number => { const { playlist } = state ; if ( playlist . currentTrack == null ) { return - 1 ; } return playlist . trackOrder . indexOf ( playlist . currentTrack ) ; } ; export const getCurrentTrackNumber = createSelector ( getCurrentTrackIndex , ( currentTrackIndex ) => currentTrackIndex + 1 ) ; export const getCurrentTrackId = ( state ) => state . playlist . currentTrack ; export const getRandomTrackId = ( state ) : number | null => { const { playlist : { trackOrder , currentTrack } , } = state ; if ( trackOrder . length === 0 ) { return null ; } let nextId ; do { nextId = trackOrder [ Math . floor ( trackOrder . length * Math . random ( ) ) ] ; } while ( nextId === currentTrack && trackOrder . length > 1 ) ; return nextId ; } ; export const getNextTrackId = ( state , n = 1 ) => { const { playlist : { trackOrder } , media : { repeat , shuffle } , } = state ; if ( shuffle ) { return getRandomTrackId ( state ) ; } const trackCount = getTrackCount ( state ) ; if ( trackCount === 0 ) { return null ; } const currentIndex = getCurrentTrackIndex ( state ) ; let nextIndex = currentIndex + n ; if ( repeat ) { nextIndex = nextIndex % trackCount ; if ( nextIndex < 0 ) { nextIndex += trackCount ; } return trackOrder [ nextIndex ] ; } if ( currentIndex === trackCount - 1 && n > 0 ) { return null ; } else if ( currentIndex === 0 && n < 0 ) { return null ; } nextIndex = Utils . clamp ( nextIndex , 0 , trackCount - 1 ) ; return trackOrder [ nextIndex ] ; } ; export const getGenWindows = ( state ) => { return state . windows . genWindows ; } ; export const getWindowOpen = createSelector ( getGenWindows , ( genWindows ) => { return ( windowId ) => genWindows [ windowId ] . open ; } ) ; export const getWindowHidden = createSelector ( getMilkdropWindowEnabled , ( milkdropWindowEnabled ) => { return ( windowId ) => { return windowId === WINDOWS . MILKDROP && ! milkdropWindowEnabled ; } ; } ) ; export const getWindowShade = createSelector ( getGenWindows , ( genWindows ) => { return ( windowId ) => genWindows [ windowId ] . shade ; } ) ; export const getWindowSize = createSelector ( getGenWindows , ( genWindows ) => { return ( windowId ) => genWindows [ windowId ] . size ; } ) ; export const getWindowPositions = createSelector ( getGenWindows , ( windows ) : WindowPositions => Utils . objectMap ( windows , ( w ) => w . position ) ) ; const BASE_WINDOW_HEIGHT = 58 ; export const getNumberOfVisibleTracks = createSelector ( getWindowSize , ( getWindowSize_ ) => { const playlistSize = getWindowSize_ ( "playlist" ) ; return Math . floor ( ( BASE_WINDOW_HEIGHT + WINDOW_RESIZE_SEGMENT_HEIGHT * playlistSize [ 1 ] ) / TRACK_HEIGHT ) ; } ) ; export const getOverflowTrackCount = createSelector ( getTrackCount , getNumberOfVisibleTracks , ( trackCount , numberOfVisibleTracks ) => Math . max ( 0 , trackCount - numberOfVisibleTracks ) ) ; const _getPlaylistScrollPosition = ( state ) => state . display . playlistScrollPosition ; export const getPlaylistScrollPosition = createSelector ( getOverflowTrackCount , _getPlaylistScrollPosition , ( overflowTrackCount , playlistScrollPosition ) => { if ( overflowTrackCount === 0 ) { return 0 ; } return Math . round ( ( Math . round ( ( overflowTrackCount * playlistScrollPosition ) / 100 ) / overflowTrackCount ) * 100 ) ; } ) ; export const getScrollOffset = createSelector ( _getPlaylistScrollPosition , getTrackCount , getNumberOfVisibleTracks , ( playlistScrollPosition , trackCount , numberOfVisibleTracks ) => { const overflow = Math . max ( 0 , trackCount - numberOfVisibleTracks ) ; return Utils . percentToIndex ( playlistScrollPosition / 100 , overflow + 1 ) ; } ) ; export const getVisibleTrackIds = createSelector ( getScrollOffset , getTrackOrder , getNumberOfVisibleTracks , ( offset , trackOrder , numberOfVisibleTracks ) => trackOrder . slice ( offset , offset + numberOfVisibleTracks ) ) ; export function getAllTracksAreVisible ( state ) { return getVisibleTrackIds ( state ) . length === state . playlist . trackOrder . length ; } export const getTrackIsVisibleFunction = createSelector ( getVisibleTrackIds , ( visibleTrackIds ) => { return ( id ) => visibleTrackIds . includes ( id ) ; } ) ; export const getVisibleTracks = createSelector ( getVisibleTrackIds , getTracks , ( visibleTrackIds , tracks ) => visibleTrackIds . map ( ( id ) => tracks [ id ] ) ) ; export const getPlaylist = ( state ) => state . playlist ; export const getDuration = ( state ) : number | null => { const { playlist , tracks } = state ; if ( playlist . currentTrack == null ) { return null ; } const currentTrack = tracks [ playlist . currentTrack ] ; return currentTrack && currentTrack . duration ; } ; export const getTrackDisplayName = createSelector ( getTracks , ( tracks ) => { return defaultMemoize ( ( trackId ) => fromTracks . getTrackDisplayName ( tracks , trackId ) ) ; } ) ; export const getCurrentTrackDisplayName = createSelector ( getCurrentTrackId , getTrackDisplayName , ( id , getName ) => { return getName ( id ) ; } ) ; export const getMediaStatus = ( state ) : MediaStatus | null => { return state . media . status ; } ; export const getMediaIsPlaying = ( state ) => state . media . status === MEDIA_STATUS . PLAYING ; export const getCurrentTrack = createSelector ( getCurrentTrackId , getTracks , ( trackId , tracks ) : PlaylistTrack | null => { return trackId == null ? null : tracks [ trackId ] ; } ) ; export const getCurrentlyPlayingTrackIdIfLoaded = createSelector ( getMediaIsPlaying , getCurrentTrack , ( mediaIsPlaying , currentTrack ) => { if ( ! mediaIsPlaying || ! currentTrack || currentTrack . mediaTagsRequestStatus === MEDIA_TAG_REQUEST_STATUS . INITIALIZED ) { return null ; } return currentTrack . id ; } ) ; export const getCurrentTrackInfo = createSelector ( getCurrentTrack , ( track ) : LoadedURLTrack | null => { if ( track == null ) { return null ; } return { url : track . url , metaData : { title : track . title || null , artist : track . artist || null , album : track . album || null , albumArtUrl : track . albumArtUrl || null , } , } ; } ) ; export const getMinimalMediaText = createSelector ( getCurrentTrackNumber , getCurrentTrackDisplayName , ( trackNumber , name ) => ( name == null ? null : `${ trackNumber }. ${ name }` ) ) ; export const getMediaText = createSelector ( getMinimalMediaText , getDuration , ( minimalMediaText , duration ) => minimalMediaText == null ? null : `${ minimalMediaText } (${ Utils . getTimeStr ( duration ) })` ) ; export const getNumberOfTracks = ( state ) => getTrackOrder ( state ) . length ; const getPlaylistDuration = createSelector ( getTracks , ( tracks ) => Object . values ( tracks ) . reduce ( ( total , track ) => total + ( track . duration || 0 ) , 0 ) ) ; export const getPlaylistURL = createSelector ( getNumberOfTracks , getPlaylistDuration , getTrackOrder , getTracks , getTrackDisplayName , ( numberOfTracks , playlistDuration , trackOrder , tracks , getDisplayName ) => createPlaylistURL ( { numberOfTracks , averageTrackLength : Utils . getTimeStr ( playlistDuration / numberOfTracks ) , playlistLengthMinutes : Math . floor ( playlistDuration / 60 ) , playlistLengthSeconds : Math . floor ( playlistDuration % 60 ) , tracks : trackOrder . map ( ( id , i ) => `${ i + 1 }. ${ getDisplayName ( id ) } (${ Utils . getTimeStr ( tracks [ id ] . duration ) })` ) , } ) ) ; const WINDOW_HEIGHT = 116 ; const SHADE_WINDOW_HEIGHT = 14 ; function getWPixelSize ( w , doubled ) { const [ width , height ] = w . size ; const doubledMultiplier = doubled && w . canDouble ? 2 : 1 ; const pix = { height : WINDOW_HEIGHT + height * WINDOW_RESIZE_SEGMENT_HEIGHT , width : WINDOW_WIDTH + width * WINDOW_RESIZE_SEGMENT_WIDTH , } ; return { height : ( w . shade ? SHADE_WINDOW_HEIGHT : pix . height ) * doubledMultiplier , width : pix . width * doubledMultiplier , } ; } export function getFocusedWindow ( state ) { return state . windows . focused ; } export function getWindowPosition ( state ) { return ( windowId ) => state . windows . genWindows [ windowId ] . position ; } export function getPositionsAreRelative ( state ) { return state . windows . positionsAreRelative ; } export function getDoubled ( state ) { return state . display . doubled ; } export function getLlamaMode ( state ) { return state . display . llama ; } export function getZIndex ( state ) { return state . display . zIndex ; } export const getWindowSizes = createSelector ( getGenWindows , getDoubled , ( windows , doubled ) => { return Utils . objectMap ( windows , ( w ) => getWPixelSize ( w , doubled ) ) ; } ) ; export const getWindowPixelSize = createSelector ( getWindowSizes , ( sizes ) => { return ( windowId ) => sizes [ windowId ] ; } ) ; const getWindowOrder = ( state ) : WindowId [ ] => state . windows . windowOrder ; export const getNormalizedWindowOrder = createSelector ( getWindowOrder , getGenWindows , ( windowOrder , genWindows ) : WindowId [ ] => { return [ WINDOWS . MAIN , ... windowOrder . filter ( ( windowId ) => windowId !== WINDOWS . MAIN && genWindows [ windowId ] != null ) , ] ; } ) ; export const getWindowsInfo = createSelector ( getWindowSizes , getWindowPositions , getNormalizedWindowOrder , ( sizes , positions , windowOrder ) : WindowInfo [ ] => { return windowOrder . map ( ( key ) => ( { key , ... sizes [ key ] , ... positions [ key ] , } ) ) ; } ) ; export const getWindowGraph = createSelector ( getWindowsInfo , generateGraph ) ; const defaultPlaylistStyle = { normal : "#00FF00" , current : "#FFFFFF" , normalbg : "#000000" , selectedbg : "#0000C6" , font : "Arial" , } ; export function getSkinColors ( state ) { return state . display . skinColors ; } export const getSkinPlaylistStyle = ( state ) : PlaylistStyle => { return state . display . skinPlaylistStyle || defaultPlaylistStyle ; } ; export const getVisualizerStyle = ( state ) : string => { const milkdrop = state . windows . genWindows [ WINDOWS . MILKDROP ] ; if ( milkdrop != null && milkdrop . open ) { return VISUALIZERS . MILKDROP ; } return fromDisplay . getVisualizerStyle ( state . display ) ; } ; export const getVolume = ( state ) => state . media . volume ; export const getBalance = ( state ) => state . media . balance ; export const getShuffle = ( state ) => state . media . shuffle ; export const getRepeat = ( state ) => state . media . repeat ; export const getChannels = createSelector ( getCurrentTrack , ( track ) : number | null => { return track != null ? track . channels || null : null ; } ) ; export const getTimeElapsed = ( state ) : number => { return state . media . timeElapsed ; } ; export function getSerlializedState ( state ) { return { version : 1 , media : fromMedia . getSerializedState ( state . media ) , equalizer : fromEqualizer . getSerializedState ( state . equalizer ) , display : fromDisplay . getSerializedState ( state . display ) , windows : fromWindows . getSerializedState ( state . windows ) , } ; } export function getEqualizerEnabled ( state ) { return state . equalizer . on ; } export function getEqualizerAuto ( state ) { return state . equalizer . auto ; } export function getBrowserWindowSize ( state ) { return state . windows . browserWindowSize ; } export const getOpenWindows = createSelector ( getGenWindows , ( genWindows ) => Utils . objectFilter ( genWindows , ( w ) => w . open ) ) ; export const getStackedLayoutPositions = createSelector ( getOpenWindows , getDoubled , ( openWindows , doubled ) : WindowPositions => { let offset = 0 ; return Utils . objectMap ( openWindows , ( w ) => { const position = { x : 0 , y : offset } ; offset += getWPixelSize ( w , doubled ) . height ; return position ; } ) ; } ) ; export const getUserInputFocus = ( state ) : string | null => { return state . userInput . focus ; } ; export const getUserInputScrubPosition = ( state ) : number => { return state . userInput . scrubPosition ; } ; export const getMarqueeText = ( state ) : string => { const defaultText = "Winamp 2.91" ; if ( state . userInput . userMessage != null ) { return state . userInput . userMessage ; } switch ( getUserInputFocus ( state ) ) { case "balance" : return MarqueeUtils . getBalanceText ( state . media . balance ) ; case "volume" : return MarqueeUtils . getVolumeText ( state . media . volume ) ; case "position" : const duration = getDuration ( state ) ; if ( duration == null ) { return defaultText ; } return MarqueeUtils . getPositionText ( duration , getUserInputScrubPosition ( state ) ) ; case "double" : return MarqueeUtils . getDoubleSizeModeText ( state . display . doubled ) ; case "eq" : const band = state . userInput . bandFocused ; if ( band == null ) { return defaultText ; } return MarqueeUtils . getEqText ( band , state . equalizer . sliders [ band ] ) ; default : break ; } if ( state . playlist . currentTrack != null ) { const mediaText = getMediaText ( state ) ; if ( mediaText == null ) { return defaultText ; } return mediaText ; } return defaultText ; } ; export const getKbps = createSelector ( getCurrentTrack , ( track ) : string | null => { return track != null ? track . kbps || null : null ; } ) ; export const getKhz = createSelector ( getCurrentTrack , ( track ) : string | null => { return track != null ? track . khz || null : null ; } ) ; export function getMilkdropMessage ( state ) { return state . milkdrop . message ; } export function getMilkdropWindowEnabled ( state ) { return state . milkdrop . display === "WINDOW" ; } export function getMilkdropDesktopEnabled ( state ) { return state . milkdrop . display === "DESKTOP" ; } export function getMilkdropFullscreenEnabled ( state ) { return state . milkdrop . display === "FULLSCREEN" ; } export function getPresets ( state ) { return state . milkdrop . presets ; } export function getButterchurn ( state ) { return state . milkdrop . butterchurn ; } export function getPresetTransitionType ( state ) { return state . milkdrop . transitionType ; } export function getCurrentPresetIndex ( state ) { return state . milkdrop . currentPresetIndex ; } export function getCurrentPreset ( state ) { const index = getCurrentPresetIndex ( state ) ; if ( index == null ) { return null ; } const preset = state . milkdrop . presets [ index ] ; if ( preset == null || preset . type === "UNRESOLVED" ) { return null ; } return preset . preset ; } export function getPresetNames ( state ) { return state . milkdrop . presets . map ( ( preset ) => preset . name ) ; } export function getPresetOverlayOpen ( state ) { return state . milkdrop . overlay ; } export function getPresetsAreCycling ( state ) { return state . milkdrop . cycling ; } export function getRandomizePresets ( state ) { return state . milkdrop . randomize ; } export function getClosed ( state ) { return state . display . closed ; } export function getSkinImages ( state ) { return state . display . skinImages ; } export function getSkinCursors ( state ) { return state . display . skinCursors ; } export function getSkinRegion ( state ) { return state . display . skinRegion ; } export function getSkinLetterWidths ( state ) { return state . display . skinGenLetterWidths ; } export function getPreampLineUrl ( state ) { return state . display . skinImages . EQ_PREAMP_LINE ; } export function getLineColorsUrl ( state ) { return state . display . skinImages . EQ_GRAPH_LINE_COLORS ; } export const getPreampLineImage = createSelector ( getPreampLineUrl , async ( url ) : Promise < HTMLImageElement | null > => { if ( url == null ) { return null ; } return Utils . imgFromUrl ( url ) ; } ) ; export const getLineColorsImage = createSelector ( getLineColorsUrl , async ( url ) : Promise < HTMLImageElement | null > => { if ( url == null ) { return null ; } return Utils . imgFromUrl ( url ) ; } ) ; export function getDummyVizData ( state ) { return state . display . dummyVizData ; } export function getMarqueeStep ( state ) { return state . display . marqueeStep ; } export function getNetworkConnected ( state ) { return state . network . connected ; } export function getTimeMode ( state ) { return state . media . timeMode ; } export function getLoading ( state ) { return state . display . loading ; } export function getWorking ( state ) { return state . display . working ; } export function getAvaliableSkins ( state ) { return state . settings . availableSkins ; }