v2.0.0
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<material-modal :show="show" teleport="#view" @close="handleClose" @after-enter="$refs.input.focus()">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ selectedNum > 0 ? $t('music_sort__title_multiple', { num: selectedNum }) : $t('music_sort__title', { name: musicInfo ? musicInfo.name : '' }) }}</h2>
|
||||
<base-input
|
||||
ref="input"
|
||||
v-model="sortNum"
|
||||
:class="$style.input"
|
||||
type="number"
|
||||
:placeholder="$t('music_sort__input_tip')"
|
||||
@submit="handleSubmit" @blur="verify"
|
||||
/>
|
||||
<div :class="$style.footer">
|
||||
<base-btn :class="$style.btn" @click="handleSubmit">{{ $t('btn_confirm') }}</base-btn>
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
musicInfo: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
selectedNum: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
emits: ['update:show', 'confirm'],
|
||||
data() {
|
||||
return {
|
||||
sortNum: '',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(n) {
|
||||
if (n) {
|
||||
this.sortNum = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$emit('update:show', false)
|
||||
},
|
||||
verify() {
|
||||
let num = /^[1-9]\d*/.exec(this.sortNum)
|
||||
num = num ? parseInt(num[0]) : ''
|
||||
this.sortNum = num.toString()
|
||||
return num
|
||||
},
|
||||
handleSubmit() {
|
||||
let num = this.verify()
|
||||
if (this.sortNum == '') return
|
||||
this.handleClose()
|
||||
this.$emit('confirm', num)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 0 15px;
|
||||
max-width: 530px;
|
||||
min-width: 280px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
min-height: 0;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
h2 {
|
||||
font-size: 13px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
word-break: break-all;
|
||||
// text-align: center;
|
||||
padding: 15px 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
// width: 100%;
|
||||
// height: 26px;
|
||||
padding: 8px 8px;
|
||||
}
|
||||
.footer {
|
||||
margin: 20px 0 15px auto;
|
||||
}
|
||||
.btn {
|
||||
// box-sizing: border-box;
|
||||
// margin-left: 15px;
|
||||
// margin-bottom: 15px;
|
||||
// height: 36px;
|
||||
// line-height: 36px;
|
||||
// padding: 0 10px !important;
|
||||
min-width: 70px;
|
||||
// .mixin-ellipsis-1;
|
||||
|
||||
+.btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,348 @@
|
||||
<template>
|
||||
<teleport to="#view">
|
||||
<div v-show="isShow" ref="dom_container" :class="$style.container">
|
||||
<transition enter-active-class="animated-fast zoomIn" leave-active-class="animated zoomOut" @after-leave="handleAnimated">
|
||||
<div v-show="visible" :class="$style.search">
|
||||
<div :class="$style.form">
|
||||
<input
|
||||
ref="dom_input" v-model.trim="text" class="ignore-esc" :placeholder="placeholder" @input="handleDelaySearch"
|
||||
@keyup.enter="handleTemplistClick(selectIndex)" @keyup.arrow-down.prevent.exact="handleKeyDown" @keyup.arrow-up.prevent.exact="handleKeyUp"
|
||||
@keyup.escape.prevent.exact="handleKeyEsc" @keydown.control.prevent="handle_key_mod_down" @keydown.meta.prevent="handle_key_mod_down"
|
||||
@keyup.control.prevent="handle_key_mod_up" @keyup.meta.prevent="handle_key_mod_up" @contextmenu="handleContextMenu"
|
||||
>
|
||||
<button type="button" @click="handleHide">
|
||||
<slot>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 212.982 212.982" space="preserve">
|
||||
<use xlink:href="#icon-delete" />
|
||||
</svg>
|
||||
</slot>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="resultList" ref="dom_scrollContainer" class="scroll" :class="$style.list" :style="listStyle">
|
||||
<ul ref="dom_list">
|
||||
<li v-for="(item, index) in resultList" :key="item.songmid" :class="selectIndex === index ? $style.select : null" @mouseenter="selectIndex = index" @click="handleTemplistClick(index)">
|
||||
<div :class="$style.img" />
|
||||
<div :class="$style.text">
|
||||
<h3 :class="$style.text">{{ item.name }} - {{ item.singer }}</h3>
|
||||
<h3 v-if="item.meta.albumName" :class="[$style.text, $style.albumName]">{{ item.meta.albumName }}</h3>
|
||||
</div>
|
||||
<div :class="$style.source">{{ item.source }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { debounce } from '@common/utils'
|
||||
import { clipboardReadText } from '@common/utils/electron'
|
||||
import { toRaw } from '@common/utils/vueTools'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Find for something...',
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['action'],
|
||||
data() {
|
||||
return {
|
||||
text: '',
|
||||
selectIndex: -1,
|
||||
listStyle: {
|
||||
height: 0,
|
||||
maxHeight: 0,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
maxHeight: 0,
|
||||
resultList: [],
|
||||
isModDown: false,
|
||||
isShow: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
resultList(n) {
|
||||
if (this.selectIndex > -1) this.selectIndex = -1
|
||||
this.$nextTick(() => {
|
||||
const height = this.$refs.dom_list.scrollHeight
|
||||
if (height > this.maxHeight) {
|
||||
this.listStyle.height = this.maxHeight + 'px'
|
||||
this.listStyle.overflow = 'auto'
|
||||
} else {
|
||||
this.listStyle.height = height + 'px'
|
||||
this.listStyle.overflow = 'hidden'
|
||||
}
|
||||
})
|
||||
},
|
||||
list(n) {
|
||||
if (!this.visible) return
|
||||
this.handleDelaySearch()
|
||||
},
|
||||
visible(n) {
|
||||
if (!n) return
|
||||
this.isShow = true
|
||||
this.init()
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.handleDelaySearch = debounce(() => {
|
||||
this.handleSearch()
|
||||
})
|
||||
if (this.visible) this.isShow = true
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
// window.key_event.on('key_mod_down', this.handle_key_mod_down)
|
||||
// window.key_event.on('key_mod_up', this.handle_key_mod_up)
|
||||
window.key_event.on('key_mod+f_down', this.handle_key_mod_f_down)
|
||||
},
|
||||
beforeUnmount() {
|
||||
// window.key_event.off('key_mod_down', this.handle_key_mod_down)
|
||||
// window.key_event.off('key_mod_up', this.handle_key_mod_up)
|
||||
window.key_event.off('key_mod+f_down', this.handle_key_mod_f_down)
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
if (!this.visible) return
|
||||
this.handleSearch()
|
||||
this.$nextTick(() => {
|
||||
if (!this.listStyle.maxHeight) {
|
||||
this.maxHeight = this.$refs.dom_container.offsetParent.clientHeight - this.$refs.dom_list.offsetTop - 70
|
||||
this.listStyle.maxHeight = this.maxHeight + 'px'
|
||||
}
|
||||
this.$refs.dom_input.focus()
|
||||
})
|
||||
},
|
||||
handleKeyEsc() {
|
||||
if (this.text.length > 0) {
|
||||
this.text = ''
|
||||
this.resultList = []
|
||||
} else {
|
||||
this.handleHide()
|
||||
}
|
||||
},
|
||||
handle_key_mod_down() {
|
||||
console.log('handle_key_mod_down')
|
||||
if (!this.isModDown) this.isModDown = true
|
||||
},
|
||||
handle_key_mod_up() {
|
||||
if (this.isModDown) this.isModDown = false
|
||||
},
|
||||
handle_key_mod_f_down() {
|
||||
if (this.visible) this.$refs.dom_input.focus()
|
||||
},
|
||||
handleAnimated() {
|
||||
if (this.visible) return
|
||||
this.isShow = false
|
||||
},
|
||||
handleTemplistClick(index) {
|
||||
if (index < 0) return
|
||||
const id = this.resultList[index].id
|
||||
this.sendEvent('listClick', {
|
||||
index: this.list.findIndex(m => m.id == id),
|
||||
isPlay: this.isModDown,
|
||||
})
|
||||
},
|
||||
handleHide() {
|
||||
this.sendEvent('hide')
|
||||
},
|
||||
sendEvent(action, data) {
|
||||
this.$emit('action', {
|
||||
action,
|
||||
data,
|
||||
})
|
||||
},
|
||||
handleKeyDown() {
|
||||
if (!this.resultList.length) return
|
||||
this.selectIndex = this.selectIndex + 1 < this.resultList.length ? this.selectIndex + 1 : 0
|
||||
this.handleScrollList()
|
||||
},
|
||||
handleKeyUp() {
|
||||
if (!this.resultList.length) return
|
||||
this.selectIndex = this.selectIndex - 1 < -1 ? this.resultList.length - 1 : this.selectIndex - 1
|
||||
this.handleScrollList()
|
||||
},
|
||||
handleScrollList() {
|
||||
if (this.selectIndex < 0) return
|
||||
let dom = this.$refs.dom_list.children[this.selectIndex]
|
||||
let offsetTop = dom.offsetTop
|
||||
let scrollTop = this.$refs.dom_scrollContainer.scrollTop
|
||||
let top
|
||||
if (offsetTop < scrollTop) {
|
||||
top = offsetTop
|
||||
} else if (offsetTop + dom.clientHeight > this.$refs.dom_scrollContainer.clientHeight + scrollTop) {
|
||||
top = offsetTop + dom.clientHeight - this.$refs.dom_scrollContainer.clientHeight
|
||||
} else return
|
||||
this.$refs.dom_scrollContainer.scrollTo(0, top)
|
||||
},
|
||||
handleContextMenu() {
|
||||
let str = clipboardReadText()
|
||||
str = str.replace(/\t|\r\n|\n|\r/g, ' ')
|
||||
str = str.replace(/\s+/g, ' ')
|
||||
let dom_input = this.$refs.dom_input
|
||||
this.text = `${this.text.substring(0, dom_input.selectionStart)}${str}${this.text.substring(dom_input.selectionEnd, this.text.length)}`
|
||||
},
|
||||
async handleSearch() {
|
||||
if (!this.text.length) return this.resultList = []
|
||||
this.resultList = await window.lx.worker.main.searchListMusic(toRaw(this.list), this.text)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
top: 20px;
|
||||
width: 45%;
|
||||
height: @height-toolbar * 0.52;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
transition: box-shadow .4s ease, background-color @transition-normal;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
background-color: var(--color-primary-light-600-alpha-100);
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.07),
|
||||
0 2px 4px rgba(0,0,0,0.07),
|
||||
0 4px 8px rgba(0,0,0,0.07),
|
||||
0 8px 16px rgba(0,0,0,0.07),
|
||||
0 16px 32px rgba(0,0,0,0.07),
|
||||
0 32px 64px rgba(0,0,0,0.07);
|
||||
|
||||
&.active {
|
||||
.form {
|
||||
input {
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
}
|
||||
button {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.form {
|
||||
display: flex;
|
||||
height: @height-toolbar * 0.52;
|
||||
position: relative;
|
||||
input {
|
||||
flex: auto;
|
||||
// border: 1px solid;
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
background-color: transparent;
|
||||
// border-bottom: 2px solid var(--color-primary);
|
||||
// border-color: var(--color-primary);
|
||||
border: none;
|
||||
|
||||
outline: none;
|
||||
// height: @height-toolbar * .7;
|
||||
padding: 0 5px;
|
||||
overflow: hidden;
|
||||
font-size: 13.5px;
|
||||
line-height: @height-toolbar * 0.52 + 5px;
|
||||
&::placeholder {
|
||||
color: var(--color-button-font);
|
||||
}
|
||||
}
|
||||
button {
|
||||
flex: none;
|
||||
border: none;
|
||||
// background-color: @color-search-form-background;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
padding: 6px 7px;
|
||||
color: var(--color-button-font);
|
||||
transition: background-color .2s ease;
|
||||
opacity: 0.8;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
.list {
|
||||
// background-color: @color-search-form-background;
|
||||
font-size: 13px;
|
||||
transition: .3s ease;
|
||||
height: 0;
|
||||
transition-property: height;
|
||||
position: relative;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 8px 5px;
|
||||
transition: background-color .2s ease;
|
||||
line-height: 1.3;
|
||||
// overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
|
||||
&.select {
|
||||
background-color: var(--color-primary-dark-100-alpha-700);
|
||||
}
|
||||
border-radius: 4px;
|
||||
// &:last-child {
|
||||
// border-bottom-left-radius: 4px;
|
||||
// border-bottom-right-radius: 4px;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.img {
|
||||
flex: none;
|
||||
}
|
||||
.text {
|
||||
flex: auto;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
.albumName {
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
.source {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
opacity: 0.5;
|
||||
padding: 0 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// transform: rotate(45deg);
|
||||
// background-color:
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<div :class="$style.list">
|
||||
<div class="thead">
|
||||
<table>
|
||||
<thead>
|
||||
<tr v-if="actionButtonsVisible">
|
||||
<th class="num" style="width: 5%;">#</th>
|
||||
<th class="nobreak">{{ $t('music_name') }}</th>
|
||||
<th class="nobreak" style="width: 22%;">{{ $t('music_singer') }}</th>
|
||||
<th class="nobreak" style="width: 22%;">{{ $t('music_album') }}</th>
|
||||
<th class="nobreak" style="width: 9%;">{{ $t('music_time') }}</th>
|
||||
<th class="nobreak" style="width: 16%;">{{ $t('action') }}</th>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<th class="num" style="width: 5%;">#</th>
|
||||
<th class="nobreak">{{ $t('music_name') }}</th>
|
||||
<th class="nobreak" style="width: 25%;">{{ $t('music_singer') }}</th>
|
||||
<th class="nobreak" style="width: 28%;">{{ $t('music_album') }}</th>
|
||||
<th class="nobreak" style="width: 10%;">{{ $t('music_time') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div v-show="list.length" ref="dom_listContent" :class="$style.content">
|
||||
<base-virtualized-list
|
||||
v-if="actionButtonsVisible" ref="listRef" v-slot="{ item, index }" :list="list" key-name="id"
|
||||
:item-height="listItemHeight" container-class="scroll" content-class="list"
|
||||
@scroll="saveListPosition" @contextmenu.capture="handleListRightClick"
|
||||
>
|
||||
<div
|
||||
class="list-item" :class="[{ [$style.active]: playerInfo.isPlayList && playerInfo.playIndex === index }, { selected: selectedIndex == index || rightClickSelectedIndex == index }, { active: selectedList.includes(item) }, { disabled: !assertApiSupport(item.source) }]"
|
||||
@click="handleListItemClick($event, index)" @contextmenu="handleListItemRightClick($event, index)"
|
||||
>
|
||||
<div class="list-item-cell no-select num" style="flex: 0 0 5%;" @click.stop>{{ index + 1 }}</div>
|
||||
<div class="list-item-cell auto name" :aria-label="item.name">
|
||||
<span class="select name">{{ item.name }}</span>
|
||||
<span v-if="isShowSource" class="no-select label-source">{{ item.source }}</span>
|
||||
</div>
|
||||
<div class="list-item-cell" style="flex: 0 0 22%;"><span class="select" :aria-label="item.singer">{{ item.singer }}</span></div>
|
||||
<div class="list-item-cell" style="flex: 0 0 22%;"><span class="select" :aria-label="item.meta.albumName">{{ item.meta.albumName }}</span></div>
|
||||
<div class="list-item-cell" style="flex: 0 0 9%;"><span class="no-select">{{ item.interval || '--/--' }}</span></div>
|
||||
<div class="list-item-cell" style="flex: 0 0 16%; padding-left: 0; padding-right: 0;">
|
||||
<material-list-buttons :index="index" :download-btn="assertApiSupport(item.source) && item.source != 'local'" @btn-click="handleListBtnClick" />
|
||||
</div>
|
||||
</div>
|
||||
</base-virtualized-list>
|
||||
<base-virtualized-list
|
||||
v-else ref="listRef" v-slot="{ item, index }" :list="list" key-name="id"
|
||||
:item-height="listItemHeight" container-class="scroll" content-class="list"
|
||||
@scroll="saveListPosition" @contextmenu.capture="handleListRightClick"
|
||||
>
|
||||
<div
|
||||
class="list-item"
|
||||
:class="[{ [$style.active]: playerInfo.isPlayList && playerInfo.playIndex === index }, { selected: selectedIndex == index || rightClickSelectedIndex == index }, { active: selectedList.includes(item) }, { disabled: !assertApiSupport(item.source) }]"
|
||||
@click="handleListItemClick($event, index)" @contextmenu="handleListItemRightClick($event, index)"
|
||||
>
|
||||
<div class="list-item-cell no-select num" style="flex: 0 0 5%;" @click.stop>{{ index + 1 }}</div>
|
||||
<div class="list-item-cell auto name">
|
||||
<span class="select name" :aria-label="item.name">{{ item.name }}</span>
|
||||
<span v-if="isShowSource" class="no-select label-source">{{ item.source }}</span>
|
||||
</div>
|
||||
<div class="list-item-cell" style="flex: 0 0 25%;"><span class="select" :aria-label="item.singer">{{ item.singer }}</span></div>
|
||||
<div class="list-item-cell" style="flex: 0 0 28%;"><span class="select" :aria-label="item.meta.albumName">{{ item.meta.albumName }}</span></div>
|
||||
<div class="list-item-cell" style="flex: 0 0 10%;"><span class="no-select">{{ item.interval || '--/--' }}</span></div>
|
||||
</div>
|
||||
</base-virtualized-list>
|
||||
</div>
|
||||
<div v-show="!list.length" :class="$style.noItem">
|
||||
<p v-text="$t('no_item')" />
|
||||
</div>
|
||||
<common-list-add-modal
|
||||
v-model:show="isShowListAdd" :is-move="isMove" :from-list-id="listId"
|
||||
:music-info="selectedAddMusicInfo" :exclude-list-id="excludeListIds" teleport="#view"
|
||||
/>
|
||||
<common-list-add-multiple-modal
|
||||
v-model:show="isShowListAddMultiple" :from-list-id="listId"
|
||||
:is-move="isMoveMultiple" :music-list="selectedList" :exclude-list-id="excludeListIds" teleport="#view" @confirm="removeAllSelect"
|
||||
/>
|
||||
<common-download-modal v-model:show="isShowDownload" :music-info="selectedDownloadMusicInfo" teleport="#view" />
|
||||
<common-download-multiple-modal v-model:show="isShowDownloadMultiple" :list="selectedList" teleport="#view" @confirm="removeAllSelect" />
|
||||
<search-list :list="list" :visible="isShowSearchBar" @action="handleMusicSearchAction" />
|
||||
<music-sort-modal v-model:show="isShowMusicSortModal" :music-info="selectedSortMusicInfo" :selected-num="selectedNum" @confirm="sortMusic" />
|
||||
<base-menu v-model="isShowItemMenu" :menus="menus" :xy="menuLocation" item-name="name" @menu-click="handleMenuClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { clipboardWriteText } from '@common/utils/electron'
|
||||
import { assertApiSupport } from '@renderer/store/utils'
|
||||
import SearchList from './components/SearchList'
|
||||
import MusicSortModal from './components/MusicSortModal'
|
||||
import useListInfo from './useListInfo'
|
||||
import useList from './useList'
|
||||
import useMenu from './useMenu'
|
||||
import usePlay from './usePlay'
|
||||
import useMusicDownload from './useMusicDownload'
|
||||
import useMusicAdd from './useMusicAdd'
|
||||
import useSort from './useSort'
|
||||
import useMusicActions from './useMusicActions'
|
||||
import useSearch from './useSearch'
|
||||
import useListScroll from './useListScroll'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
export default {
|
||||
name: 'MusicList',
|
||||
components: {
|
||||
SearchList,
|
||||
MusicSortModal,
|
||||
},
|
||||
props: {
|
||||
listId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['show-menu'],
|
||||
setup(props, { emit }) {
|
||||
const actionButtonsVisible = appSetting['list.actionButtonsVisible']
|
||||
|
||||
let scrollIndex = null
|
||||
let isAnimation = false
|
||||
const handleRestoreScroll = (_scrollIndex, _isAnimation) => {
|
||||
scrollIndex = _scrollIndex
|
||||
isAnimation = _isAnimation
|
||||
if (isAnimation) restoreScroll(scrollIndex, isAnimation)
|
||||
console.log('handleRestoreScroll', scrollIndex, isAnimation)
|
||||
}
|
||||
const onLoadedList = () => {
|
||||
console.log('restoreScroll', scrollIndex, isAnimation)
|
||||
restoreScroll(scrollIndex, isAnimation)
|
||||
}
|
||||
|
||||
const {
|
||||
rightClickSelectedIndex,
|
||||
selectedIndex,
|
||||
dom_listContent,
|
||||
listRef,
|
||||
list,
|
||||
playerInfo,
|
||||
setSelectedIndex,
|
||||
isShowSource,
|
||||
excludeListIds,
|
||||
} = useListInfo({ props, onLoadedList })
|
||||
|
||||
const {
|
||||
selectedList,
|
||||
listItemHeight,
|
||||
handleSelectData,
|
||||
removeAllSelect,
|
||||
} = useList({ list })
|
||||
|
||||
const {
|
||||
handlePlayMusic,
|
||||
handlePlayMusicLater,
|
||||
doubleClickPlay,
|
||||
} = usePlay({ props, selectedList, list, removeAllSelect })
|
||||
|
||||
const {
|
||||
isShowListAdd,
|
||||
isMove,
|
||||
isShowListAddMultiple,
|
||||
isMoveMultiple,
|
||||
selectedAddMusicInfo,
|
||||
handleShowMusicAddModal,
|
||||
handleShowMusicMoveModal,
|
||||
} = useMusicAdd({ selectedList, list })
|
||||
|
||||
const {
|
||||
isShowDownload,
|
||||
isShowDownloadMultiple,
|
||||
selectedDownloadMusicInfo,
|
||||
handleShowDownloadModal,
|
||||
} = useMusicDownload({ selectedList, list })
|
||||
|
||||
const {
|
||||
isShowMusicSortModal,
|
||||
selectedNum,
|
||||
selectedSortMusicInfo,
|
||||
handleShowSortModal,
|
||||
sortMusic,
|
||||
} = useSort({ props, list, selectedList, removeAllSelect })
|
||||
|
||||
const {
|
||||
handleSearch,
|
||||
handleOpenMusicDetail,
|
||||
handleCopyName,
|
||||
handleRemoveMusic,
|
||||
} = useMusicActions({ props, list, removeAllSelect, selectedList })
|
||||
|
||||
const {
|
||||
menus,
|
||||
menuLocation,
|
||||
isShowItemMenu,
|
||||
showMenu,
|
||||
menuClick,
|
||||
} = useMenu({
|
||||
assertApiSupport,
|
||||
emit,
|
||||
|
||||
handleShowDownloadModal,
|
||||
handlePlayMusic,
|
||||
handlePlayMusicLater,
|
||||
handleSearch,
|
||||
handleShowMusicAddModal,
|
||||
handleShowMusicMoveModal,
|
||||
handleShowSortModal,
|
||||
handleOpenMusicDetail,
|
||||
handleCopyName,
|
||||
handleRemoveMusic,
|
||||
})
|
||||
|
||||
const {
|
||||
isShowSearchBar,
|
||||
searchList,
|
||||
handleMusicSearchAction,
|
||||
} = useSearch({
|
||||
setSelectedIndex,
|
||||
handlePlayMusic,
|
||||
listRef,
|
||||
})
|
||||
|
||||
const { saveListPosition, restoreScroll } = useListScroll({ props, listRef, list, handleRestoreScroll })
|
||||
|
||||
|
||||
const handleListItemClick = (event, index) => {
|
||||
if (rightClickSelectedIndex.value > -1) return
|
||||
handleSelectData(index)
|
||||
doubleClickPlay(index)
|
||||
}
|
||||
const handleListItemRightClick = (event, index) => {
|
||||
rightClickSelectedIndex.value = index
|
||||
showMenu(event, list.value[index], index)
|
||||
}
|
||||
const handleMenuClick = (action) => {
|
||||
let index = rightClickSelectedIndex.value
|
||||
rightClickSelectedIndex.value = -1
|
||||
menuClick(action, index)
|
||||
}
|
||||
const handleListRightClick = (event) => {
|
||||
if (!event.target.classList.contains('select')) return
|
||||
event.stopImmediatePropagation()
|
||||
let classList = dom_listContent.value.classList
|
||||
classList.add('copying')
|
||||
window.requestAnimationFrame(() => {
|
||||
let str = window.getSelection().toString()
|
||||
classList.remove('copying')
|
||||
str = str.split(/\n\n/).map(s => s.replace(/\n/g, ' ')).join('\n').trim()
|
||||
if (!str.length) return
|
||||
clipboardWriteText(str)
|
||||
})
|
||||
}
|
||||
const handleListBtnClick = ({ action, index }) => {
|
||||
switch (action) {
|
||||
case 'download':
|
||||
handleShowDownloadModal(index, true)
|
||||
break
|
||||
case 'play':
|
||||
handlePlayMusic(index, true)
|
||||
break
|
||||
case 'search':
|
||||
handleSearch(index)
|
||||
break
|
||||
case 'listAdd':
|
||||
handleShowMusicAddModal(index, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
const scrollToTop = () => {
|
||||
listRef.value.scrollTo(0, true)
|
||||
}
|
||||
|
||||
return {
|
||||
listItemHeight,
|
||||
handleListItemClick,
|
||||
selectedList,
|
||||
handleListItemRightClick,
|
||||
removeAllSelect,
|
||||
handleListBtnClick,
|
||||
rightClickSelectedIndex,
|
||||
selectedIndex,
|
||||
dom_listContent,
|
||||
listRef,
|
||||
excludeListIds,
|
||||
|
||||
menus,
|
||||
isShowItemMenu,
|
||||
menuLocation,
|
||||
handleMenuClick,
|
||||
|
||||
handleListRightClick,
|
||||
assertApiSupport,
|
||||
|
||||
isShowListAdd,
|
||||
isMove,
|
||||
isShowListAddMultiple,
|
||||
isMoveMultiple,
|
||||
selectedAddMusicInfo,
|
||||
|
||||
isShowMusicSortModal,
|
||||
selectedNum,
|
||||
selectedSortMusicInfo,
|
||||
sortMusic,
|
||||
|
||||
isShowDownload,
|
||||
isShowDownloadMultiple,
|
||||
selectedDownloadMusicInfo,
|
||||
|
||||
scrollToTop,
|
||||
|
||||
isShowSearchBar,
|
||||
searchList,
|
||||
handleMusicSearchAction,
|
||||
|
||||
list,
|
||||
playerInfo,
|
||||
|
||||
saveListPosition,
|
||||
isShowSource,
|
||||
handleRestoreScroll,
|
||||
|
||||
actionButtonsVisible,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.list {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
:global(.list-item) {
|
||||
&.active {
|
||||
color: var(--color-button-font);
|
||||
}
|
||||
}
|
||||
:global {
|
||||
.label-source {
|
||||
color: var(--color-primary);
|
||||
padding: 5px;
|
||||
font-size: .8em;
|
||||
line-height: 1;
|
||||
opacity: .75;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
min-height: 0;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.no-item {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
font-size: 24px;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
import { computed, watch, ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { isFullscreen } from '@renderer/store'
|
||||
import { getFontSizeWithScreen } from '@renderer/utils'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
const useKeyEvent = ({ handleSelectAllData }) => {
|
||||
const keyEvent = {
|
||||
isShiftDown: false,
|
||||
isModDown: false,
|
||||
}
|
||||
|
||||
const handle_key_shift_down = () => {
|
||||
if (!keyEvent.isShiftDown) keyEvent.isShiftDown = true
|
||||
}
|
||||
const handle_key_shift_up = () => {
|
||||
if (keyEvent.isShiftDown) keyEvent.isShiftDown = false
|
||||
}
|
||||
const handle_key_mod_down = () => {
|
||||
if (!keyEvent.isModDown) keyEvent.isModDown = true
|
||||
}
|
||||
const handle_key_mod_up = () => {
|
||||
if (keyEvent.isModDown) keyEvent.isModDown = false
|
||||
}
|
||||
const handle_key_mod_a_down = ({ event }) => {
|
||||
if (event.target.tagName == 'INPUT') return
|
||||
event.preventDefault()
|
||||
if (event.repeat) return
|
||||
keyEvent.isModDown = false
|
||||
handleSelectAllData()
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.key_event.off('key_shift_down', handle_key_shift_down)
|
||||
window.key_event.off('key_shift_up', handle_key_shift_up)
|
||||
window.key_event.off('key_mod_down', handle_key_mod_down)
|
||||
window.key_event.off('key_mod_up', handle_key_mod_up)
|
||||
window.key_event.off('key_mod+a_down', handle_key_mod_a_down)
|
||||
})
|
||||
window.key_event.on('key_shift_down', handle_key_shift_down)
|
||||
window.key_event.on('key_shift_up', handle_key_shift_up)
|
||||
window.key_event.on('key_mod_down', handle_key_mod_down)
|
||||
window.key_event.on('key_mod_up', handle_key_mod_up)
|
||||
window.key_event.on('key_mod+a_down', handle_key_mod_a_down)
|
||||
|
||||
return keyEvent
|
||||
}
|
||||
|
||||
export default ({ list }) => {
|
||||
const selectedList = ref([])
|
||||
|
||||
let lastSelectIndex = -1
|
||||
const listItemHeight = computed(() => {
|
||||
return Math.ceil((isFullscreen.value ? getFontSizeWithScreen() : appSetting['common.fontSize']) * 2.3)
|
||||
})
|
||||
|
||||
const removeAllSelect = () => {
|
||||
selectedList.value = []
|
||||
}
|
||||
const handleSelectAllData = () => {
|
||||
removeAllSelect()
|
||||
selectedList.value = [...list.value]
|
||||
}
|
||||
const keyEvent = useKeyEvent({ handleSelectAllData })
|
||||
|
||||
const handleSelectData = clickIndex => {
|
||||
if (keyEvent.isShiftDown) {
|
||||
if (selectedList.value.length) {
|
||||
removeAllSelect()
|
||||
if (lastSelectIndex != clickIndex) {
|
||||
let isNeedReverse = false
|
||||
if (clickIndex < lastSelectIndex) {
|
||||
let temp = lastSelectIndex
|
||||
lastSelectIndex = clickIndex
|
||||
clickIndex = temp
|
||||
isNeedReverse = true
|
||||
}
|
||||
selectedList.value = list.value.slice(lastSelectIndex, clickIndex + 1)
|
||||
if (isNeedReverse) selectedList.value.reverse()
|
||||
}
|
||||
} else {
|
||||
selectedList.value.push(list.value[clickIndex])
|
||||
lastSelectIndex = clickIndex
|
||||
}
|
||||
} else if (keyEvent.isModDown) {
|
||||
lastSelectIndex = clickIndex
|
||||
let item = list.value[clickIndex]
|
||||
let index = selectedList.value.indexOf(item)
|
||||
if (index < 0) {
|
||||
selectedList.value.push(item)
|
||||
} else {
|
||||
selectedList.value.splice(index, 1)
|
||||
}
|
||||
} else if (selectedList.value.length) {
|
||||
removeAllSelect()
|
||||
}
|
||||
}
|
||||
|
||||
watch(list, removeAllSelect)
|
||||
|
||||
return {
|
||||
selectedList,
|
||||
listItemHeight,
|
||||
removeAllSelect,
|
||||
handleSelectData,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ref, watch, computed } from '@common/utils/vueTools'
|
||||
import { playMusicInfo, playInfo } from '@renderer/store/player/state'
|
||||
import { getListMusics } from '@renderer/store/list/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
|
||||
export default ({ props, onLoadedList }) => {
|
||||
const rightClickSelectedIndex = ref(-1)
|
||||
const selectedIndex = ref(-1)
|
||||
const dom_listContent = ref(null)
|
||||
const listRef = ref(null)
|
||||
|
||||
const excludeListIds = computed(() => ([props.listId]))
|
||||
|
||||
|
||||
const list = ref([])
|
||||
watch(() => props.listId, id => {
|
||||
getListMusics(id).then(l => {
|
||||
list.value = l
|
||||
if (id != props.listId) return
|
||||
onLoadedList()
|
||||
})
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
const playerInfo = computed(() => ({
|
||||
isPlayList: playMusicInfo.listId == props.listId,
|
||||
playIndex: playInfo.playIndex,
|
||||
}))
|
||||
|
||||
const setSelectedIndex = index => {
|
||||
selectedIndex.value = index
|
||||
}
|
||||
|
||||
const isShowSource = computed(() => appSetting['list.isShowSource'])
|
||||
|
||||
return {
|
||||
rightClickSelectedIndex,
|
||||
selectedIndex,
|
||||
dom_listContent,
|
||||
listRef,
|
||||
list,
|
||||
playerInfo,
|
||||
setSelectedIndex,
|
||||
isShowSource,
|
||||
excludeListIds,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { onMounted, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { useRoute, useRouter } from '@common/utils/vueRouter'
|
||||
import { setListPosition, getListPosition } from '@renderer/utils/data'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
export default ({ props, listRef, list, handleRestoreScroll }) => {
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const saveListPosition = () => {
|
||||
setListPosition(props.listId, listRef.value?.getScrollTop() || 0)
|
||||
}
|
||||
|
||||
const handleScrollList = (index, isAnimation, callback = () => {}) => {
|
||||
listRef.value.scrollToIndex(index, -150, isAnimation, callback)
|
||||
}
|
||||
|
||||
const restoreScroll = async(index, isAnimation) => {
|
||||
// console.log(index, isAnimation)
|
||||
if (!list.value.length) return
|
||||
if (index == null) {
|
||||
let location = await getListPosition(props.listId) || 0
|
||||
if (appSetting['list.isSaveScrollLocation'] && location != null) {
|
||||
listRef.value?.scrollTo(location)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
handleScrollList(index, isAnimation)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleRestoreScroll(route.query.scrollIndex, false)
|
||||
if (route.query.scrollIndex != null) {
|
||||
router.replace({
|
||||
path: '/list',
|
||||
query: {
|
||||
id: props.listId,
|
||||
updated: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
saveListPosition()
|
||||
})
|
||||
|
||||
return {
|
||||
saveListPosition,
|
||||
restoreScroll,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import { computed, ref, shallowReactive, reactive, nextTick } from '@common/utils/vueTools'
|
||||
import musicSdk from '@renderer/utils/musicSdk'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
|
||||
export default ({
|
||||
assertApiSupport,
|
||||
emit,
|
||||
|
||||
handleShowDownloadModal,
|
||||
handlePlayMusic,
|
||||
handlePlayMusicLater,
|
||||
handleSearch,
|
||||
handleShowMusicAddModal,
|
||||
handleShowMusicMoveModal,
|
||||
handleShowSortModal,
|
||||
handleOpenMusicDetail,
|
||||
handleCopyName,
|
||||
handleRemoveMusic,
|
||||
}) => {
|
||||
const itemMenuControl = reactive({
|
||||
play: true,
|
||||
playLater: true,
|
||||
copyName: true,
|
||||
addTo: true,
|
||||
moveTo: true,
|
||||
sort: true,
|
||||
download: true,
|
||||
search: true,
|
||||
remove: true,
|
||||
sourceDetail: true,
|
||||
})
|
||||
const t = useI18n()
|
||||
const menuLocation = shallowReactive({ x: 0, y: 0 })
|
||||
const isShowItemMenu = ref(false)
|
||||
|
||||
const menus = computed(() => {
|
||||
return [
|
||||
{
|
||||
name: t('list__play'),
|
||||
action: 'play',
|
||||
disabled: !itemMenuControl.play,
|
||||
},
|
||||
{
|
||||
name: t('list__download'),
|
||||
action: 'download',
|
||||
disabled: !itemMenuControl.download,
|
||||
},
|
||||
{
|
||||
name: t('list__play_later'),
|
||||
action: 'playLater',
|
||||
disabled: !itemMenuControl.playLater,
|
||||
},
|
||||
{
|
||||
name: t('list__add_to'),
|
||||
action: 'addTo',
|
||||
disabled: !itemMenuControl.addTo,
|
||||
},
|
||||
{
|
||||
name: t('list__move_to'),
|
||||
action: 'moveTo',
|
||||
disabled: !itemMenuControl.moveTo,
|
||||
},
|
||||
{
|
||||
name: t('list__sort'),
|
||||
action: 'sort',
|
||||
disabled: !itemMenuControl.sort,
|
||||
},
|
||||
{
|
||||
name: t('list__copy_name'),
|
||||
action: 'copyName',
|
||||
disabled: !itemMenuControl.copyName,
|
||||
},
|
||||
{
|
||||
name: t('list__source_detail'),
|
||||
action: 'sourceDetail',
|
||||
disabled: !itemMenuControl.sourceDetail,
|
||||
},
|
||||
{
|
||||
name: t('list__search'),
|
||||
action: 'search',
|
||||
disabled: !itemMenuControl.search,
|
||||
},
|
||||
{
|
||||
name: t('list__remove'),
|
||||
action: 'remove',
|
||||
disabled: !itemMenuControl.remove,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const showMenu = (event, musicInfo) => {
|
||||
itemMenuControl.sourceDetail = !!musicSdk[musicInfo.source]?.getMusicDetailPageUrl
|
||||
// itemMenuControl.play =
|
||||
// itemMenuControl.playLater =
|
||||
itemMenuControl.download = assertApiSupport(musicInfo.source) && musicInfo.source != 'local'
|
||||
|
||||
menuLocation.x = event.pageX
|
||||
menuLocation.y = event.pageY
|
||||
|
||||
if (isShowItemMenu.value) return
|
||||
|
||||
emit('show-menu')
|
||||
nextTick(() => {
|
||||
isShowItemMenu.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const hideMenu = () => {
|
||||
isShowItemMenu.value = false
|
||||
}
|
||||
|
||||
const menuClick = (action, index) => {
|
||||
// console.log(action)
|
||||
hideMenu()
|
||||
if (!action) return
|
||||
switch (action.action) {
|
||||
case 'play':
|
||||
handlePlayMusic(index)
|
||||
break
|
||||
case 'playLater':
|
||||
handlePlayMusicLater(index)
|
||||
break
|
||||
case 'copyName':
|
||||
handleCopyName(index)
|
||||
break
|
||||
case 'addTo':
|
||||
handleShowMusicAddModal(index)
|
||||
break
|
||||
case 'moveTo':
|
||||
handleShowMusicMoveModal(index)
|
||||
break
|
||||
case 'sort':
|
||||
handleShowSortModal(index)
|
||||
break
|
||||
case 'download':
|
||||
handleShowDownloadModal(index)
|
||||
break
|
||||
case 'search':
|
||||
handleSearch(index)
|
||||
break
|
||||
case 'remove':
|
||||
handleRemoveMusic(index)
|
||||
break
|
||||
case 'sourceDetail':
|
||||
handleOpenMusicDetail(index)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
menus,
|
||||
menuLocation,
|
||||
isShowItemMenu,
|
||||
showMenu,
|
||||
menuClick,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useRouter } from '@common/utils/vueRouter'
|
||||
import musicSdk from '@renderer/utils/musicSdk'
|
||||
import { openUrl, clipboardWriteText } from '@common/utils/electron'
|
||||
import { dialog } from '@renderer/plugins/Dialog'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
import { removeListMusics } from '@renderer/store/list/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { toOldMusicInfo } from '@renderer/utils/index'
|
||||
|
||||
|
||||
export default ({ props, list, selectedList, removeAllSelect }) => {
|
||||
const router = useRouter()
|
||||
const t = useI18n()
|
||||
|
||||
const handleSearch = index => {
|
||||
const info = list.value[index]
|
||||
router.push({
|
||||
path: '/search',
|
||||
query: {
|
||||
text: `${info.name} ${info.singer}`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleOpenMusicDetail = index => {
|
||||
const minfo = list.value[index]
|
||||
const url = musicSdk[minfo.source].getMusicDetailPageUrl(toOldMusicInfo(minfo))
|
||||
if (!url) return
|
||||
openUrl(url)
|
||||
}
|
||||
|
||||
const handleCopyName = index => {
|
||||
const minfo = list.value[index]
|
||||
clipboardWriteText(appSetting['download.fileName'].replace('歌名', minfo.name).replace('歌手', minfo.singer))
|
||||
}
|
||||
|
||||
const handleRemoveMusic = async(index, single) => {
|
||||
if (selectedList.value.length && !single) {
|
||||
const confirm = await (selectedList.value.length > 1
|
||||
? dialog.confirm({
|
||||
message: t('lists__remove music_tip', { len: selectedList.value.length }),
|
||||
confirmButtonText: t('lists__remove_tip_button'),
|
||||
})
|
||||
: Promise.resolve(true)
|
||||
)
|
||||
if (!confirm) return
|
||||
removeListMusics({ listId: props.listId, ids: selectedList.value.map(m => m.id) })
|
||||
removeAllSelect()
|
||||
} else {
|
||||
removeListMusics({ listId: props.listId, ids: [list.value[index].id] })
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
handleSearch,
|
||||
handleOpenMusicDetail,
|
||||
handleCopyName,
|
||||
handleRemoveMusic,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ref, nextTick } from '@common/utils/vueTools'
|
||||
|
||||
export default ({ selectedList, list }) => {
|
||||
const isShowListAdd = ref(false)
|
||||
const isMove = ref(false)
|
||||
const isMoveMultiple = ref(false)
|
||||
const isShowListAddMultiple = ref(false)
|
||||
const selectedAddMusicInfo = ref(null)
|
||||
|
||||
const handleShowMusicAddModal = (index, single) => {
|
||||
if (selectedList.value.length && !single) {
|
||||
isMoveMultiple.value = false
|
||||
isShowListAddMultiple.value = true
|
||||
} else {
|
||||
isMove.value = false
|
||||
selectedAddMusicInfo.value = list.value[index]
|
||||
nextTick(() => {
|
||||
isShowListAdd.value = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleShowMusicMoveModal = (index, single) => {
|
||||
if (selectedList.value.length && !single) {
|
||||
isMoveMultiple.value = true
|
||||
isShowListAddMultiple.value = true
|
||||
} else {
|
||||
isMove.value = true
|
||||
selectedAddMusicInfo.value = list.value[index]
|
||||
nextTick(() => {
|
||||
isShowListAdd.value = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isShowListAdd,
|
||||
isMove,
|
||||
isMoveMultiple,
|
||||
isShowListAddMultiple,
|
||||
selectedAddMusicInfo,
|
||||
handleShowMusicAddModal,
|
||||
handleShowMusicMoveModal,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ref, nextTick } from '@common/utils/vueTools'
|
||||
|
||||
export default ({ selectedList, list }) => {
|
||||
const isShowDownload = ref(false)
|
||||
const isShowDownloadMultiple = ref(false)
|
||||
const musicInfo = ref(null)
|
||||
|
||||
const handleShowDownloadModal = (index, single) => {
|
||||
if (selectedList.value.length && !single) {
|
||||
isShowDownloadMultiple.value = true
|
||||
} else {
|
||||
musicInfo.value = list.value[index]
|
||||
nextTick(() => {
|
||||
isShowDownload.value = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isShowDownload,
|
||||
isShowDownloadMultiple,
|
||||
selectedDownloadMusicInfo: musicInfo,
|
||||
handleShowDownloadModal,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { addTempPlayList } from '@renderer/store/player/action'
|
||||
import { playList } from '@renderer/core/player'
|
||||
|
||||
export default ({ props, selectedList, list, removeAllSelect }) => {
|
||||
let clickTime = 0
|
||||
let clickIndex = -1
|
||||
|
||||
const handlePlayMusic = (index) => {
|
||||
playList(props.listId, index)
|
||||
}
|
||||
|
||||
const handlePlayMusicLater = (index, single) => {
|
||||
if (selectedList.value.length && !single) {
|
||||
addTempPlayList(selectedList.value.map(s => ({ listId: props.listId, musicInfo: s })))
|
||||
removeAllSelect()
|
||||
} else {
|
||||
addTempPlayList([{ listId: props.listId, musicInfo: list.value[index] }])
|
||||
}
|
||||
}
|
||||
|
||||
const doubleClickPlay = index => {
|
||||
if (
|
||||
window.performance.now() - clickTime > 400 ||
|
||||
clickIndex !== index
|
||||
) {
|
||||
clickTime = window.performance.now()
|
||||
clickIndex = index
|
||||
return
|
||||
}
|
||||
handlePlayMusic(index, true)
|
||||
clickTime = 0
|
||||
clickIndex = -1
|
||||
}
|
||||
|
||||
return {
|
||||
handlePlayMusic,
|
||||
handlePlayMusicLater,
|
||||
doubleClickPlay,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
|
||||
export default ({ setSelectedIndex, handlePlayMusic, listRef }) => {
|
||||
const isShowSearchBar = ref(false)
|
||||
const searchList = ref([])
|
||||
|
||||
const handleShowSearchBar = () => {
|
||||
isShowSearchBar.value = true
|
||||
}
|
||||
|
||||
const handleMusicSearchAction = ({ action, data: { index, isPlay } = {} }) => {
|
||||
isShowSearchBar.value = false
|
||||
switch (action) {
|
||||
case 'listClick':
|
||||
if (index < 0) return
|
||||
listRef.value.scrollToIndex(index, -150, true, () => {
|
||||
setSelectedIndex(index)
|
||||
setTimeout(() => {
|
||||
setSelectedIndex(-1)
|
||||
if (isPlay) handlePlayMusic(index)
|
||||
}, 600)
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
window.key_event.on('key_mod+f_down', handleShowSearchBar)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.key_event.off('key_mod+f_down', handleShowSearchBar)
|
||||
})
|
||||
|
||||
return {
|
||||
isShowSearchBar,
|
||||
searchList,
|
||||
handleMusicSearchAction,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { updateListMusicsPosition } from '@renderer/store/list/action'
|
||||
import { ref, nextTick } from '@common/utils/vueTools'
|
||||
|
||||
export default ({ props, list, selectedList, removeAllSelect }) => {
|
||||
const isShowMusicSortModal = ref(false)
|
||||
const selectedNum = ref(0)
|
||||
const musicInfo = ref(null)
|
||||
|
||||
const handleShowSortModal = (index, single) => {
|
||||
if (selectedList.value.length && !single) {
|
||||
selectedNum.value = selectedList.value.length
|
||||
} else {
|
||||
selectedNum.value = 0
|
||||
musicInfo.value = list.value[index]
|
||||
}
|
||||
nextTick(() => {
|
||||
isShowMusicSortModal.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const sortMusic = num => {
|
||||
num = Math.min(num, list.value.length)
|
||||
updateListMusicsPosition({
|
||||
listId: props.listId,
|
||||
position: num - 1,
|
||||
ids: (selectedNum.value ? [...selectedList.value] : [musicInfo.value]).map(m => m.id),
|
||||
})
|
||||
removeAllSelect()
|
||||
isShowMusicSortModal.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
isShowMusicSortModal,
|
||||
selectedNum,
|
||||
selectedSortMusicInfo: musicInfo,
|
||||
handleShowSortModal,
|
||||
sortMusic,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user