Skip to content

Commit

Permalink
feat: add PlayMode, PlayVolume and play logic
Browse files Browse the repository at this point in the history
  • Loading branch information
uniquemo committed May 12, 2020
1 parent 0fbe910 commit a4d1c1d
Show file tree
Hide file tree
Showing 14 changed files with 368 additions and 136 deletions.
53 changes: 53 additions & 0 deletions src/components/Layout/Footer/PlayMode/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react'
import { Tooltip, Icon, IconName } from '@blueprintjs/core'

import { getPlayMode, MODE } from 'helpers/play'
import { PlayMusicDispatchContext, ACTIONS } from 'reducers/playMusic'

const MODE_ORDER = [MODE.PLAY_IN_ORDER, MODE.SINGLE_CYCLE, MODE.SHUFFLE_PLAYBACK]

const MODE_MAP: IDictionary<{
label: string,
icon: IconName
}> = {
[MODE.PLAY_IN_ORDER]: {
label: '顺序播放',
icon: 'sort'
},
[MODE.SINGLE_CYCLE]: {
label: '单曲循环',
icon: 'repeat'
},
[MODE.SHUFFLE_PLAYBACK]: {
label: '随机播放',
icon: 'random'
}
}

const { useState, useContext } = React

const PlayMode = () => {
const dispatch = useContext(PlayMusicDispatchContext)
const [mode, setMode] = useState(getPlayMode())

const handleClick = () => {
const idx = MODE_ORDER.findIndex(m => m === mode)
const nextMode = MODE_ORDER[(idx + 1) % (MODE_ORDER.length)]
setMode(nextMode)

dispatch({
type: ACTIONS.SET_PLAY_MODE,
payload: {
playMode: nextMode
}
})
}

return (
<Tooltip content={MODE_MAP[mode].label}>
<Icon icon={MODE_MAP[mode].icon} onClick={handleClick} />
</Tooltip>
)
}

export default PlayMode
65 changes: 65 additions & 0 deletions src/components/Layout/Footer/PlayOperations/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react'
import { Icon } from '@blueprintjs/core'

import { PlayMusicStateContext, PlayMusicDispatchContext, AudioContext, ACTIONS } from 'reducers/playMusic'
import styles from './style.module.css'

const { useContext } = React

const PlayOperations = () => {
const audioInfo = useContext(AudioContext)
const state = useContext(PlayMusicStateContext)
const dispatch = useContext(PlayMusicDispatchContext)
const { musicId, playList } = state

const togglePlayStatus = () => {
if (audioInfo.state?.paused) {
audioInfo.controls?.play()
} else {
audioInfo.controls?.pause()
}
}

const play = (prev?: boolean) => {
const len = playList.length
if (!len) {
return
}

const index = playList.findIndex(({ id }) => id === musicId)
let nextIndex = -1

if (index > -1) {
nextIndex = prev ? (index - 1 + len) % len : (index + 1) % len
} else {
nextIndex = 0
}

dispatch({
type: ACTIONS.PLAY,
payload: {
musicId: playList[nextIndex].id,
music: playList[nextIndex]
}
})
}

const playPrev = () => play(true)
const playNext = () => play()

return (
<>
<div className={styles.prev} onClick={playPrev}>
<Icon icon='step-backward' />
</div>
<div className={styles.pause} onClick={togglePlayStatus}>
<Icon icon={audioInfo.state?.paused ? 'play' : 'pause'} />
</div>
<div className={styles.next} onClick={playNext}>
<Icon icon='step-forward' />
</div>
</>
)
}

export default PlayOperations
28 changes: 28 additions & 0 deletions src/components/Layout/Footer/PlayOperations/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@value colors: "~styles/colors.module.css";
@value red from colors;

.prev, .next, .pause {
cursor: pointer;
}

.prev svg,
.next svg {
width: 20px;
height: 20px;
color: red;
}

.pause {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: red;
display: flex;
align-items: center;
justify-content: center;
margin: 0 20px;

& svg {
color: #fff;
}
}
4 changes: 1 addition & 3 deletions src/components/Layout/Footer/PlayRecord/PlayList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import React from 'react'

import List from '../List'
import { IMyMusic } from 'apis/types/business'
import { getPlayList } from 'helpers/play'
import { PlayMusicStateContext, PlayMusicDispatchContext, ACTIONS } from 'reducers/playMusic'

const { useContext } = React

const PlayHistory = () => {
const state = useContext(PlayMusicStateContext)
const dispatch = useContext(PlayMusicDispatchContext)
const data = state.playList.length ? state.playList : getPlayList()

const handleDoubleClick = (item: IMyMusic) => {
dispatch({
Expand All @@ -26,7 +24,7 @@ const PlayHistory = () => {

return (
<List
data={data}
data={state.playList}
onDoubleClick={handleDoubleClick}
onClear={handleClear}
/>
Expand Down
33 changes: 33 additions & 0 deletions src/components/Layout/Footer/PlayVolume/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import { Icon } from '@blueprintjs/core'

import ProgressBar from 'components/ProgressBar'
import { AudioContext } from 'reducers/playMusic'
import styles from './style.module.css'

const { useContext } = React

const PlayVolume = () => {
const audioInfo = useContext(AudioContext)

const handleBarClick = (percent: number) => {
audioInfo.controls?.volume(percent)
}

const donePercent = Number((audioInfo.state?.volume || 0).toFixed(2))

return (
<div className={styles.root}>
<Icon icon='volume-off' />
<div className={styles.progress}>
<ProgressBar
className={styles.bar}
donePercent={donePercent}
onBarClick={handleBarClick}
/>
</div>
</div>
)
}

export default PlayVolume
21 changes: 21 additions & 0 deletions src/components/Layout/Footer/PlayVolume/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@value colors: "~styles/colors.module.css";
@value tipsColor from colors;

.root {
display: flex;
align-items: center;
justify-content: space-between;
}

.progress {
width: 130px;
margin-left: 3px;
border-bottom: 1px solid tipsColor;
position: relative;
}

.bar {
position: absolute;
top: -1px;
left: 0;
}
57 changes: 9 additions & 48 deletions src/components/Layout/Footer/ProgressBar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,26 @@
import React from 'react'

import BaseProgressBar from 'components/ProgressBar'
import { formatTime } from 'helpers/time'
import { AudioContext } from 'reducers/playMusic'
import styles from './style.module.css'

const { useRef, useContext } = React
const { useContext } = React

const ProgressBar = () => {
const audioInfo = useContext(AudioContext)
const barRef = useRef<HTMLDivElement | null>()

const getPercent = (event: React.MouseEvent<HTMLDivElement>) => {
const clickX = event.pageX
const percent = barRef.current
? clickX / barRef.current.offsetWidth
: 0

return percent
}

const handleBarClick = (event: React.MouseEvent<HTMLDivElement>) => {
const percent = getPercent(event)
audioInfo.controls?.seek((audioInfo.state?.duration || 0) * percent)
}

// TODO: Fix Drag Interaction
const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault()
event.dataTransfer.dropEffect = 'link'
}

const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault()
const percent = getPercent(event)
audioInfo.controls?.seek((audioInfo.state?.duration || 0) * percent)
}

const donePercent = audioInfo.state?.duration
? (audioInfo.state?.time / audioInfo.state.duration)
: 0

return (
<div
className={styles.root}
onClick={handleBarClick}
ref={(ref) => barRef.current = ref}
onDragOver={handleDragOver}
onDrop={handleDrop}
>
<div
className={styles.doneWrap}
style={{ width: `${donePercent * 100}%` }}
>
<div className={styles.done}></div>
<div className={styles.controllDot} draggable>
<div className={styles.label}>
{formatTime(audioInfo.state?.time)}
</div>
</div>
</div>
</div>
<BaseProgressBar
donePercent={donePercent}
renderLabel={() => formatTime(audioInfo.state?.time)}
onBarClick={(percent) => {
audioInfo.controls?.seek((audioInfo.state?.duration || 0) * percent)
}}
/>
)
}

Expand Down
40 changes: 10 additions & 30 deletions src/components/Layout/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,19 @@ import Artists from 'components/Artists'
import AudioTimer from './AudioTimer'
import ProgressBar from './ProgressBar'
import PlayRecord from './PlayRecord'
import { PlayMusicStateContext, AudioContext } from 'reducers/playMusic'
import PlayMode from './PlayMode'
import PlayOperations from './PlayOperations'
import PlayVolume from './PlayVolume'
import { PlayMusicStateContext } from 'reducers/playMusic'
import styles from './style.module.css'

const { useContext, useState } = React

const Footer = () => {
const [showPlayRecord, setShowPlayRecord] = useState(false)

const audioInfo = useContext(AudioContext)
const state = useContext(PlayMusicStateContext)
const { musicId, music } = state

const togglePlayStatus = () => {
if (audioInfo.state?.paused) {
audioInfo.controls?.play()
} else {
audioInfo.controls?.pause()
}
}

const togglePlayRecord = () => setShowPlayRecord(!showPlayRecord)

return (
Expand All @@ -49,34 +42,21 @@ const Footer = () => {
</div>

<div className={styles.operations}>
<div className={styles.prev}>
<Icon icon='step-backward' />
</div>
<div className={styles.pause} onClick={togglePlayStatus}>
<Icon icon={audioInfo.state?.paused ? 'play' : 'pause'} />
</div>
<div className={styles.next}>
<Icon icon='step-forward' />
</div>
<PlayOperations />
</div>

<div className={styles.otherOperations}>
<div>
<Tooltip content='列表循环'>
<Icon icon='swap-horizontal' />
</Tooltip>
<div className={styles.item}>
<PlayMode />
</div>
<div onClick={togglePlayRecord}>
<div onClick={togglePlayRecord} className={styles.item}>
<Tooltip content='打开播放列表'>
<Icon icon='menu-closed' className={showPlayRecord ? 'active': ''} />
</Tooltip>
</div>
<div>
<Tooltip content='显示歌词'>
</Tooltip>
<div className={styles.item}>
<PlayVolume />
</div>
<div><Icon icon='volume-off' /></div>
</div>

<PlayRecord
Expand Down
Loading

0 comments on commit a4d1c1d

Please sign in to comment.