From e173a26236a2039466ac272e49ac7908a8dc870f Mon Sep 17 00:00:00 2001 From: Jinke Li Date: Sun, 7 Feb 2021 18:06:46 +0800 Subject: [PATCH] feat: react-drag-listview => sortablejs for support audio list sort on mobile #223 --- CN.md | 6 +- README.md | 4 +- example/example.js | 4 +- index.d.ts | 7 +- package.json | 5 +- src/components/AudioListsPanel.js | 108 ++++++++++++++---------------- src/config/propTypes.js | 3 +- src/config/sortable.js | 6 ++ src/index.js | 44 ++++++++++-- src/styles/audioListsPanel.less | 5 +- src/styles/vars.less | 1 + yarn.lock | 20 +++--- 12 files changed, 123 insertions(+), 90 deletions(-) create mode 100644 src/config/sortable.js diff --git a/CN.md b/CN.md index 580fa05b..4f0f117b 100644 --- a/CN.md +++ b/CN.md @@ -98,7 +98,7 @@ ReactDOM.render( > 中文版本文档可能不完整, 请以英文版为准, 维护两个版本太累了 | 属性 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | +| --- | --- | --- | --- | --- | --- | --- | | className | `string` | `-` | 附加的 className | | audioLists | `object[]` | `-` | 播放列表 : {name: "YOUR_AUDIO_NAME",singer: "YOUR_AUDIO_SINGER_NAME",cover: "YOUR_AUDIO_COVER",musicSrc: "YOUR_AUDIO_SRC"} | | theme | `string` | `dark` | 播放器主题 可选 'light'(白天) 和 'dark'(黑夜) 两种 | @@ -152,7 +152,7 @@ ReactDOM.render( | onAudioListsPanelChange | `function(panelVisible)` | `-` | 播放列表打开或关闭的 钩子函数 | | onThemeChange | `function(theme)` | `-` | 主题切换后的 钩子函数 | | onModeChange | `function(mode)` | `-` | 模式切换发生改变时的 钩子函数 | -| onAudioListsDragEnd | `function(fromIndex,toIndex)` | `-` | 列表歌曲拖拽后 钩子函数 | +| onAudioListsSortEnd | `function(fromIndex,toIndex)` | `-` | 列表歌曲拖拽后 钩子函数 | | onAudioLyricChange | `function(lineNum, currentLyric)` | `-` | 当前播放的歌词改变回调 | | getContainer | `() => HTMLElement` \| `Selectors` | `document.body` | 播放器挂载的节点 默认在 body | | getAudioInstance | `(instance: HTMLAudioElement) => void` | `-` | 获取原始的 audio 实例, 可以用它所有的 api 做你想做的事情 | @@ -396,7 +396,7 @@ export interface ReactJkMusicPlayerProps { onModeChange?: (mode: ReactJkMusicPlayerMode) => void onAudioListsPanelChange?: (panelVisible: boolean) => void onAudioPlayTrackChange?: (fromIndex: number, endIndex: number) => void - onAudioListsDragEnd?: ( + onAudioListsSortEnd?: ( currentPlayId: string, audioLists: Array, audioInfo: ReactJkMusicPlayerAudioInfo, diff --git a/README.md b/README.md index 0ab2666e..20dc0253 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ npm install react-jinke-music-player --save - [x] Support theme switch - [x] Support typescript (d.ts) - [x] Support lyric +- [x] Support audio list sortable - [x] Play list - [x] Full player features - [x] [Server-Side Rendering](#bulb-server-side-rendering) @@ -182,7 +183,7 @@ ReactDOM.render( | onAudioListsPanelChange | `function(panelVisible)` | `-` | audio lists panel change handle | | onThemeChange | `function(theme)` | `-` | theme change handle | | onModeChange | `function(mode)` | `-` | mode change handle | -| onAudioListsDragEnd | `function(fromIndex,endIndex)` | `-` | audio lists drag end handle | +| onAudioListsSortEnd | `function(oldIndex,newIndex)` | `-` | audio lists sort end handle, use [SortableJS](https://github.com/SortableJS/Sortable) | | onAudioLyricChange | `function(lineNum, currentLyric)` | `-` | audio lyric change handle | | getContainer | `() => HTMLElement` \| `Selectors` | `document.body` | Return the mount node for Music player | | getAudioInstance | `(instance: HTMLAudioElement) => void` | `-` | get origin audio element instance , you can use it do everything | @@ -201,6 +202,7 @@ ReactDOM.render( | renderAudioTitle | `(audioInfo, isMobile) => ReactNode` | `-` | use `locale.audioTitle` to set `audio` tag title, the api can render custom jsx element for display | | mobileMediaQuery | `string` | `(max-width: 768px) and (orientation : portrait)` | custom mobile media query string, eg use the mobile version UI on iPad. | | volumeFade | `{ fadeIn: number(ms), fadeOut: number(ms) }` | `-` | audio fade in and out. [Detail](#bulb-audio-volume-fade-in-and-fade-out) | +| sortableOptions | `{ fadeIn: number(ms), fadeOut: number(ms) }` | `{swap: true, animation: 100, swapClass: 'audio-lists-panel-sortable-highlight-bg'}` | [SortableJs Options](https://github.com/SortableJS/Sortable#options) | ## :bulb: Custom operation ui diff --git a/example/example.js b/example/example.js index eee81d7b..eb0cfcd8 100644 --- a/example/example.js +++ b/example/example.js @@ -342,8 +342,8 @@ const options = { console.log('audio lists panel visible:', panelVisible) }, - onAudioListsDragEnd(fromIndex, endIndex) { - console.log('audio lists drag end:', fromIndex, endIndex) + onAudioListsSortEnd(oldIndex, newIndex) { + console.log('audio lists sort end:', oldIndex, newIndex) }, onAudioLyricChange(lineNum, currentLyric) { diff --git a/index.d.ts b/index.d.ts index 62881596..43be9dc3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -174,11 +174,7 @@ export interface ReactJkMusicPlayerProps { audioLists: Array, audioInfo: ReactJkMusicPlayerAudioInfo, ) => void - onAudioListsDragEnd?: ( - currentPlayId: string, - audioLists: Array, - audioInfo: ReactJkMusicPlayerAudioInfo, - ) => void + onAudioListsSortEnd?: (oldIndex: number, newIndex: number) => void showDownload?: boolean showPlay?: boolean showReload?: boolean @@ -238,6 +234,7 @@ export interface ReactJkMusicPlayerProps { fadeIn?: number fadeOut?: number } + sortableOptions?: object } export default class ReactJkMusicPlayer extends React.PureComponent< diff --git a/package.json b/package.json index f9b48278..a4d95490 100644 --- a/package.json +++ b/package.json @@ -101,8 +101,8 @@ "prop-types": "^15.7.2", "rc-slider": "^9.7.1", "rc-switch": "^3.2.2", - "react-drag-listview": "^0.1.8", - "react-draggable": "^4.4.3" + "react-draggable": "^4.4.3", + "sortablejs": "^1.13.0" }, "bundlesize": [ { @@ -126,6 +126,7 @@ "@semantic-release/changelog": "^5.0.1", "@semantic-release/git": "^9.0.0", "@types/jest": "^26.0.20", + "@types/sortablejs": "^1.10.6", "all-contributors-cli": "^6.19.0", "autoprefixer": "^10.2.4", "babel-core": "^7.0.0-bridge.0", diff --git a/src/components/AudioListsPanel.js b/src/components/AudioListsPanel.js index b88df0af..1a0e8d93 100644 --- a/src/components/AudioListsPanel.js +++ b/src/components/AudioListsPanel.js @@ -1,6 +1,6 @@ import cls from 'classnames' import React, { memo } from 'react' -import ReactDragListView from 'react-drag-listview/lib/ReactDragListView' +import SORTABLE_CONFIG from '../config/sortable' const AudioListsPanel = ({ audioLists, @@ -13,7 +13,6 @@ const AudioListsPanel = ({ glassBg, remove, removeId, - audioListsDragEnd, isMobile, locale, icon, @@ -60,65 +59,58 @@ const AudioListsPanel = ({ })} > {audioLists.length >= 1 ? ( - -
    - {audioLists.map((audio) => { - const { name, singer } = audio - const isCurrentPlaying = playId === audio.id - return ( -
  • + {audioLists.map((audio) => { + const { name, singer } = audio + const isCurrentPlaying = playId === audio.id + return ( +
  • onPlay(audio.id)} + > + + + {isCurrentPlaying && loading + ? icon.loading : isCurrentPlaying - ? locale.clickToPauseText - : locale.clickToPlayText - } - className={cls( - 'audio-item', - { playing: isCurrentPlaying }, - { pause: !playing }, - { remove: removeId === audio.id }, - )} - onClick={() => onPlay(audio.id)} - > - - - {isCurrentPlaying && loading - ? icon.loading - : isCurrentPlaying - ? playing - ? icon.pause - : icon.play - : undefined} - - - - {name} + ? playing + ? icon.pause + : icon.play + : undefined} - - {singer} + + + {name} + + + {singer} + + {remove && ( + + {icon.close} - {remove && ( - - {icon.close} - - )} -
  • - ) - })} -
-
+ )} + + ) + })} + ) : ( <> {icon.empty} diff --git a/src/config/propTypes.js b/src/config/propTypes.js index 1f466cd7..67fba931 100644 --- a/src/config/propTypes.js +++ b/src/config/propTypes.js @@ -64,7 +64,7 @@ export default { onModeChange: PropTypes.func, onAudioListsPanelChange: PropTypes.func, onAudioPlayTrackChange: PropTypes.func, - onAudioListsDragEnd: PropTypes.func, + onAudioListsSortEnd: PropTypes.func, onAudioLyricChange: PropTypes.func, showDownload: PropTypes.bool, showPlay: PropTypes.bool, @@ -119,4 +119,5 @@ export default { fadeIn: PropTypes.number, fadeOut: PropTypes.number, }), + sortableOptions: PropTypes.object, } diff --git a/src/config/sortable.js b/src/config/sortable.js new file mode 100644 index 00000000..2651608e --- /dev/null +++ b/src/config/sortable.js @@ -0,0 +1,6 @@ +export default { + selector: 'audio-lists-panel-content-wrap', + swapClass: 'audio-lists-panel-sortable-highlight-bg', + swap: true, + animation: 100, +} diff --git a/src/index.js b/src/index.js index b0d925d8..9dcffdf9 100644 --- a/src/index.js +++ b/src/index.js @@ -13,6 +13,7 @@ import Switch from 'rc-switch' import React, { cloneElement, createRef, PureComponent } from 'react' import { createPortal } from 'react-dom' import Draggable from 'react-draggable' +import Sortable, { Swap } from 'sortablejs' import AudioListsPanel from './components/AudioListsPanel' import CircleProcessBar from './components/CircleProcessBar' import { @@ -58,6 +59,7 @@ import { DEFAULT_VOLUME, DEFAULT_REMOVE_ID, } from './config/player' +import SORTABLE_CONFIG from './config/sortable' import LOCALE_CONFIG from './locale' import Lyric from './lyric' import { @@ -188,6 +190,8 @@ export default class ReactJkMusicPlayer extends PureComponent { fadeIn: 0, fadeOut: 0, }, + // https://github.com/SortableJS/Sortable#options + sortableOptions: {}, } static propTypes = PROP_TYPES @@ -674,7 +678,6 @@ export default class ReactJkMusicPlayer extends PureComponent { remove={remove} onDelete={this.onDeleteAudioLists} removeId={removeId} - audioListsDragEnd={this.onAudioListsDragEnd} locale={locale} /> {/* 播放模式提示框 */} @@ -1629,19 +1632,23 @@ export default class ReactJkMusicPlayer extends PureComponent { this.setState({ theme }) } - onAudioListsDragEnd = (fromIndex, toIndex) => { + onAudioListsSortEnd = ({ oldIndex, newIndex }) => { + if (oldIndex === newIndex) { + return + } + const { playId, audioLists } = this.state const _audioLists = [...audioLists] - const item = _audioLists.splice(fromIndex, 1)[0] - _audioLists.splice(toIndex, 0, item) + const item = _audioLists.splice(oldIndex, 1)[0] + _audioLists.splice(newIndex, 0, item) // 如果拖动正在播放的歌曲 播放Id 等于 拖动后的index - const _playId = fromIndex === playId ? toIndex : playId + const _playId = oldIndex === playId ? newIndex : playId this.setState({ audioLists: _audioLists, playId: _playId }) - this.props.onAudioListsDragEnd && - this.props.onAudioListsDragEnd(fromIndex, toIndex) + this.props.onAudioListsSortEnd && + this.props.onAudioListsSortEnd(oldIndex, newIndex) this.props.onAudioListsChange && this.props.onAudioListsChange( @@ -1649,6 +1656,15 @@ export default class ReactJkMusicPlayer extends PureComponent { _audioLists, this.getBaseAudioInfo(), ) + + // TODO: remove + if (this.props.onAudioListsDragEnd) { + // eslint-disable-next-line no-console + console.warn( + '[Deprecated] onAudioListsDragEnd is deprecated. please use onAudioListsSortEnd(oldIndex, newIndex){}', + ) + this.props.onAudioListsDragEnd(oldIndex, newIndex) + } } saveLastPlayStatus = () => { @@ -2313,6 +2329,7 @@ export default class ReactJkMusicPlayer extends PureComponent { this.removeMobileListener() this.removeLyric() this._onDestroyed() + this.sortable && this.sortable.destroy() } onAudioCanPlay = () => { @@ -2324,6 +2341,18 @@ export default class ReactJkMusicPlayer extends PureComponent { }) } + initSortableAudioLists = () => { + Sortable.mount(new Swap()) + + const { sortableOptions } = this.props + const { selector, ...defaultOptions } = SORTABLE_CONFIG + this.sortable = new Sortable(document.querySelector(`.${selector}`), { + onEnd: this.onAudioListsSortEnd, + ...defaultOptions, + ...sortableOptions, + }) + } + // eslint-disable-next-line camelcase UNSAFE_componentWillReceiveProps(nextProps) { const { @@ -2391,5 +2420,6 @@ export default class ReactJkMusicPlayer extends PureComponent { this.addSystemThemeListener() this.initPlayer() this.onGetAudioInstance() + this.initSortableAudioLists() } } diff --git a/src/styles/audioListsPanel.less b/src/styles/audioListsPanel.less index 0daee088..40528084 100644 --- a/src/styles/audioListsPanel.less +++ b/src/styles/audioListsPanel.less @@ -1,9 +1,10 @@ @import './vars.less'; @import './mixins.less'; -.audio-lists-panel-drag-line { - border: 1px solid @primary-color !important; +.audio-lists-panel-sortable-highlight-bg { + background-color: @lists-sortable-highlight-bg !important; } + .audio-lists-panel { overflow: hidden; position: fixed; diff --git a/src/styles/vars.less b/src/styles/vars.less index 8e893122..ca0f6767 100644 --- a/src/styles/vars.less +++ b/src/styles/vars.less @@ -9,6 +9,7 @@ @controller-bg-light: @primary-color-light; @panel-bg-dark: @primary-color-dark; @controller-bg-dark: darken(@primary-color-light, 10%); +@lists-sortable-highlight-bg: fade(@primary-color, 15%); @border-radius: 4px; @music-player-panel-height: 80px; diff --git a/yarn.lock b/yarn.lock index 0744a6ec..cbfdfcdf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2017,6 +2017,11 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== +"@types/sortablejs@^1.10.6": + version "1.10.6" + resolved "https://registry.npm.taobao.org/@types/sortablejs/download/@types/sortablejs-1.10.6.tgz?cache=0&sync_timestamp=1605057223578&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fsortablejs%2Fdownload%2F%40types%2Fsortablejs-1.10.6.tgz#98725ae08f1dfe28b8da0fdf302c417f5ff043c0" + integrity sha1-mHJa4I8d/ii42g/fMCxBf1/wQ8A= + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -2922,7 +2927,7 @@ babel-preset-jest@^26.6.2: babel-plugin-jest-hoist "^26.6.2" babel-preset-current-node-syntax "^1.0.0" -babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.22.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= @@ -11246,14 +11251,6 @@ react-dom@16.13.1: prop-types "^15.6.2" scheduler "^0.19.1" -react-drag-listview@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/react-drag-listview/-/react-drag-listview-0.1.8.tgz#e04de326ecbe97d6ad84fb68664e405765e30d72" - integrity sha512-ZJnjFEz89RPZ1DzI8f6LngmtsmJbLry/pMz2tEqABxHA+d8cUFRmVPS1DxZdoz/htc+uri9fCdv4dqIiPz0xIA== - dependencies: - babel-runtime "^6.26.0" - prop-types "^15.5.8" - react-draggable@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.3.tgz#0727f2cae5813e36b0e4962bf11b2f9ef2b406f3" @@ -12325,6 +12322,11 @@ socks@~2.3.2: ip "1.1.5" smart-buffer "^4.1.0" +sortablejs@^1.13.0: + version "1.13.0" + resolved "https://registry.npm.taobao.org/sortablejs/download/sortablejs-1.13.0.tgz#3ab2473f8c69ca63569e80b1cd1b5669b51269e9" + integrity sha1-OrJHP4xpymNWnoCxzRtWabUSaek= + sorted-object@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/sorted-object/-/sorted-object-2.0.1.tgz#7d631f4bd3a798a24af1dffcfbfe83337a5df5fc"