新增单个列表导入/导出功能
parent
51be62bc29
commit
ce7bca5b36
|
@ -15,7 +15,7 @@ module.exports = {
|
||||||
'*-height', '*-width',
|
'*-height', '*-width',
|
||||||
'flex', '::-webkit-scrollbar',
|
'flex', '::-webkit-scrollbar',
|
||||||
'top', 'left', 'bottom', 'right',
|
'top', 'left', 'bottom', 'right',
|
||||||
'border-radius',
|
'border-radius', 'gap',
|
||||||
],
|
],
|
||||||
selectorBlackList: ['html', 'ignore-to-rem'],
|
selectorBlackList: ['html', 'ignore-to-rem'],
|
||||||
replace: true,
|
replace: true,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
### 新增
|
### 新增
|
||||||
|
|
||||||
- 新增歌词简体中文转繁体中文,当软件语言被设置为繁体中文后,播放歌曲的歌词也将自动转成繁体中文显示
|
- 新增歌词简体中文转繁体中文,当软件语言被设置为繁体中文后,播放歌曲的歌词也将自动转成繁体中文显示
|
||||||
|
- 为方便分享歌曲列表,新增单个列表导入/导出功能,可在右击“我的列表”里的列表名后弹出的菜单中使用
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,9 @@ transition(enter-active-class="animated fadeIn"
|
||||||
leave-active-class="animated fadeOut")
|
leave-active-class="animated fadeOut")
|
||||||
div(:class="$style.modal" v-show="show" @click="bgClose && close()")
|
div(:class="$style.modal" v-show="show" @click="bgClose && close()")
|
||||||
transition(:enter-active-class="inClass"
|
transition(:enter-active-class="inClass"
|
||||||
:leave-active-class="outClass")
|
:leave-active-class="outClass"
|
||||||
|
@after-leave="$emit('after-leave', $event)"
|
||||||
|
)
|
||||||
div(:class="$style.content" v-show="show" @click.stop)
|
div(:class="$style.content" v-show="show" @click.stop)
|
||||||
header(:class="$style.header")
|
header(:class="$style.header")
|
||||||
button(type="button" @click="close" v-if="closeBtn")
|
button(type="button" @click="close" v-if="closeBtn")
|
||||||
|
@ -141,6 +143,7 @@ export default {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
max-width: 76%;
|
max-width: 76%;
|
||||||
|
min-width: 220px;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"date_format_second": "{num} seconds ago",
|
"cancel_button_text": "Cancel",
|
||||||
|
"confirm_button_text": "OK",
|
||||||
|
"date_format_hour": "{num} hours ago",
|
||||||
"date_format_minute": "{num} minutes ago",
|
"date_format_minute": "{num} minutes ago",
|
||||||
"date_format_hour": "{num} hours ago"
|
"date_format_second": "{num} seconds ago"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,35 @@
|
||||||
{
|
{
|
||||||
"lists_new_list_btn": "Create list",
|
"action": "Manage",
|
||||||
"lists_new_list_input": "New list...",
|
"album": "Album",
|
||||||
"lists_rename": "Rename",
|
"default_list": "Recently Played",
|
||||||
"lists_moveup": "Move Up",
|
"list_add_to": "Add to ...",
|
||||||
"lists_movedown": "Move Down",
|
"list_copy_name": "Copy name",
|
||||||
"lists_sync": "Update",
|
"list_download": "Download",
|
||||||
"lists_remove": "Remove",
|
"list_move_to": "Move to ...",
|
||||||
"list_play": "Play",
|
"list_play": "Play",
|
||||||
"list_play_later": "Play later",
|
"list_play_later": "Play later",
|
||||||
"list_copy_name": "Copy name",
|
|
||||||
"list_add_to": "Add to ...",
|
|
||||||
"list_move_to": "Move to ...",
|
|
||||||
"list_sort": "Adjust position",
|
|
||||||
"list_download": "Download",
|
|
||||||
"list_search": "Search",
|
|
||||||
"list_remove": "Remove",
|
"list_remove": "Remove",
|
||||||
|
"list_search": "Search",
|
||||||
|
"list_sort": "Adjust position",
|
||||||
"list_source_detail": "Song Page",
|
"list_source_detail": "Song Page",
|
||||||
"default_list": "Recently Played",
|
"lists_export": "Export",
|
||||||
|
"lists_export_part_desc": "Choose where to save the list file",
|
||||||
|
"lists_import": "Import",
|
||||||
|
"lists_import_part_button_cancel": "No",
|
||||||
|
"lists_import_part_button_confirm": "Overwrite",
|
||||||
|
"lists_import_part_confirm": "The imported list ({importName}) has the same ID as the local list ({localName}). Do you overwrite the local list?",
|
||||||
|
"lists_import_part_desc": "Select list file",
|
||||||
|
"lists_movedown": "Move Down",
|
||||||
|
"lists_moveup": "Move Up",
|
||||||
|
"lists_new_list_btn": "Create list",
|
||||||
|
"lists_new_list_input": "New list...",
|
||||||
|
"lists_remove": "Remove",
|
||||||
|
"lists_rename": "Rename",
|
||||||
|
"lists_sync": "Update",
|
||||||
|
"loding_list": "Loading...",
|
||||||
"love_list": "Favorites",
|
"love_list": "Favorites",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
"no_item": "Nothing's here...",
|
||||||
"singer": "Artist",
|
"singer": "Artist",
|
||||||
"album": "Album",
|
"time": "Length"
|
||||||
"action": "Manage",
|
|
||||||
"time": "Length",
|
|
||||||
"loding_list": "Loading...",
|
|
||||||
"no_item": "Nothing's here..."
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"date_format_second": "{num}秒前",
|
"cancel_button_text": "我不",
|
||||||
|
"confirm_button_text": "好的",
|
||||||
|
"date_format_hour": "{num}小时前",
|
||||||
"date_format_minute": "{num}分钟前",
|
"date_format_minute": "{num}分钟前",
|
||||||
"date_format_hour": "{num}小时前"
|
"date_format_second": "{num}秒前"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,35 @@
|
||||||
{
|
{
|
||||||
"lists_new_list_btn": "新建列表",
|
"action": "操作",
|
||||||
"lists_new_list_input": "新列表...",
|
"album": "专辑",
|
||||||
"lists_rename": "重命名",
|
"default_list": "试听列表",
|
||||||
"lists_moveup": "上移",
|
"list_add_to": "添加到...",
|
||||||
"lists_movedown": "下移",
|
"list_copy_name": "复制歌曲名",
|
||||||
"lists_sync": "更新",
|
"list_download": "下载",
|
||||||
"lists_remove": "删除",
|
"list_move_to": "移动到...",
|
||||||
"list_play": "播放",
|
"list_play": "播放",
|
||||||
"list_play_later": "稍后播放",
|
"list_play_later": "稍后播放",
|
||||||
"list_copy_name": "复制歌曲名",
|
|
||||||
"list_source_detail": "歌曲详情页",
|
|
||||||
"list_add_to": "添加到...",
|
|
||||||
"list_move_to": "移动到...",
|
|
||||||
"list_sort": "调整位置",
|
|
||||||
"list_download": "下载",
|
|
||||||
"list_search": "搜索",
|
|
||||||
"list_remove": "删除",
|
"list_remove": "删除",
|
||||||
"default_list": "试听列表",
|
"list_search": "搜索",
|
||||||
|
"list_sort": "调整位置",
|
||||||
|
"list_source_detail": "歌曲详情页",
|
||||||
|
"lists_export": "导出",
|
||||||
|
"lists_export_part_desc": "选择列表文件保存位置",
|
||||||
|
"lists_import": "导入",
|
||||||
|
"lists_import_part_button_cancel": "不要啊",
|
||||||
|
"lists_import_part_button_confirm": "覆盖掉",
|
||||||
|
"lists_import_part_confirm": "导入的列表({importName})与本地列表({localName})的ID相同,是否覆盖本地列表?",
|
||||||
|
"lists_import_part_desc": "选择列表文件",
|
||||||
|
"lists_movedown": "下移",
|
||||||
|
"lists_moveup": "上移",
|
||||||
|
"lists_new_list_btn": "新建列表",
|
||||||
|
"lists_new_list_input": "新列表...",
|
||||||
|
"lists_remove": "删除",
|
||||||
|
"lists_rename": "重命名",
|
||||||
|
"lists_sync": "更新",
|
||||||
|
"loding_list": "加载中...",
|
||||||
"love_list": "收藏",
|
"love_list": "收藏",
|
||||||
"name": "歌曲名",
|
"name": "歌曲名",
|
||||||
|
"no_item": "列表竟然是空的...",
|
||||||
"singer": "歌手",
|
"singer": "歌手",
|
||||||
"album": "专辑",
|
"time": "时长"
|
||||||
"action": "操作",
|
|
||||||
"time": "时长",
|
|
||||||
"loding_list": "加载中...",
|
|
||||||
"no_item": "列表竟然是空的..."
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"date_format_second": "{num}秒前",
|
"cancel_button_text": "取消",
|
||||||
|
"confirm_button_text": "好的",
|
||||||
|
"date_format_hour": "{num}小時前",
|
||||||
"date_format_minute": "{num}分鐘前",
|
"date_format_minute": "{num}分鐘前",
|
||||||
"date_format_hour": "{num}小時前"
|
"date_format_second": "{num}秒前"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,35 @@
|
||||||
{
|
{
|
||||||
"lists_new_list_btn": "新建列表",
|
"action": "操作",
|
||||||
"lists_new_list_input": "新列表...",
|
"album": "專輯",
|
||||||
"lists_rename": "重命名",
|
"default_list": "試聽列表",
|
||||||
"lists_moveup": "上移",
|
"list_add_to": "添加到...",
|
||||||
"lists_movedown": "下移",
|
"list_copy_name": "複製歌曲名",
|
||||||
"lists_sync": "更新",
|
"list_download": "下載",
|
||||||
"lists_remove": "刪除",
|
"list_move_to": "移動到...",
|
||||||
"list_play": "播放",
|
"list_play": "播放",
|
||||||
"list_play_later": "稍後播放",
|
"list_play_later": "稍後播放",
|
||||||
"list_copy_name": "複製歌曲名",
|
|
||||||
"list_add_to": "添加到...",
|
|
||||||
"list_move_to": "移動到...",
|
|
||||||
"list_sort": "調整位置",
|
|
||||||
"list_download": "下載",
|
|
||||||
"list_search": "搜索",
|
|
||||||
"list_remove": "刪除",
|
"list_remove": "刪除",
|
||||||
|
"list_search": "搜索",
|
||||||
|
"list_sort": "調整位置",
|
||||||
"list_source_detail": "歌曲詳情頁",
|
"list_source_detail": "歌曲詳情頁",
|
||||||
"default_list": "試聽列表",
|
"lists_export": "導出",
|
||||||
|
"lists_export_part_desc": "選擇列表文件保存位置",
|
||||||
|
"lists_import": "導入",
|
||||||
|
"lists_import_part_button_cancel": "不要啊",
|
||||||
|
"lists_import_part_button_confirm": "覆蓋掉",
|
||||||
|
"lists_import_part_confirm": "導入的列表({importName})與本地列表({localName})的ID相同,是否覆蓋本地列表?",
|
||||||
|
"lists_import_part_desc": "選擇列表文件",
|
||||||
|
"lists_movedown": "下移",
|
||||||
|
"lists_moveup": "上移",
|
||||||
|
"lists_new_list_btn": "新建列表",
|
||||||
|
"lists_new_list_input": "新列表...",
|
||||||
|
"lists_remove": "刪除",
|
||||||
|
"lists_rename": "重命名",
|
||||||
|
"lists_sync": "更新",
|
||||||
|
"loding_list": "加載中...",
|
||||||
"love_list": "收藏列表",
|
"love_list": "收藏列表",
|
||||||
"name": "歌曲名",
|
"name": "歌曲名",
|
||||||
|
"no_item": "列表竟然是空的...",
|
||||||
"singer": "歌手",
|
"singer": "歌手",
|
||||||
"album": "專輯",
|
"time": "時長"
|
||||||
"action": "操作",
|
|
||||||
"time": "時長",
|
|
||||||
"loding_list": "加載中...",
|
|
||||||
"no_item": "列表竟然是空的..."
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<Modal :show="visible" @close="handleCancel" @after-leave="afterLeave" :closeBtn="false">
|
||||||
|
<main :class="$style.main">{{message}}</main>
|
||||||
|
<footer :class="$style.footer">
|
||||||
|
<Btn :class="$style.btn" v-if="showCancel" @click="handleCancel">{{cancelBtnText}}</Btn>
|
||||||
|
<Btn :class="$style.btn" @click="handleComfirm">{{confirmBtnText}}</Btn>
|
||||||
|
</footer>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Modal from '@renderer/components/material/Modal'
|
||||||
|
import Btn from '@renderer/components/material/Btn'
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
Btn,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
message: '',
|
||||||
|
showCancel: false,
|
||||||
|
cancelButtonText: '',
|
||||||
|
confirmButtonText: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
cancelBtnText() {
|
||||||
|
return this.cancelButtonText || this.$t('base.cancel_button_text')
|
||||||
|
},
|
||||||
|
confirmBtnText() {
|
||||||
|
return this.confirmButtonText || this.$t('base.confirm_button_text')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
const el = this.$el
|
||||||
|
el.parentNode.removeChild(el)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
afterLeave(el, done) {
|
||||||
|
this.$destroy()
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
|
||||||
|
},
|
||||||
|
handleComfirm() {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" module>
|
||||||
|
|
||||||
|
.main {
|
||||||
|
flex: auto;
|
||||||
|
min-height: 50px;
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
max-width: 320px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
flex: none;
|
||||||
|
padding: 0 15px 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,50 @@
|
||||||
|
import Dialog from './Dialog'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
import store from '@renderer/store'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
message: '',
|
||||||
|
showCancel: false,
|
||||||
|
cancelButtonText: '',
|
||||||
|
confirmButtonText: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialog = {
|
||||||
|
install(Vue, options) {
|
||||||
|
const DialogConstructor = Vue.extend(Dialog)
|
||||||
|
|
||||||
|
const dialog = function Dialog(options) {
|
||||||
|
const { message, showCancel, cancelButtonText, confirmButtonText } =
|
||||||
|
Object.assign({}, defaultOptions, typeof options == 'string' ? { message: options } : options || {})
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let instance = new DialogConstructor({ i18n, store }).$mount(document.createElement('div'))
|
||||||
|
|
||||||
|
// 属性设置
|
||||||
|
instance.visible = true
|
||||||
|
instance.message = message
|
||||||
|
instance.showCancel = showCancel
|
||||||
|
instance.cancelButtonText = cancelButtonText
|
||||||
|
instance.confirmButtonText = confirmButtonText
|
||||||
|
|
||||||
|
// 挂载
|
||||||
|
document.getElementById('container').appendChild(instance.$el)
|
||||||
|
|
||||||
|
instance.handleCancel = () => {
|
||||||
|
instance.visible = false
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.handleComfirm = () => {
|
||||||
|
instance.visible = false
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
dialog.confirm = options => dialog({ ...options, showCancel: true })
|
||||||
|
|
||||||
|
Vue.prototype.$dialog = dialog
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.use(dialog)
|
|
@ -1,2 +1,3 @@
|
||||||
// import './axios'
|
// import './axios'
|
||||||
|
import './Dialog'
|
||||||
import './Tips'
|
import './Tips'
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
getMusicUrl as getMusicUrlFormStorage,
|
getMusicUrl as getMusicUrlFormStorage,
|
||||||
setMusicUrl,
|
setMusicUrl,
|
||||||
assertApiSupport,
|
assertApiSupport,
|
||||||
|
filterFileName,
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import { NAMES, rendererInvoke } from '@common/ipc'
|
import { NAMES, rendererInvoke } from '@common/ipc'
|
||||||
|
|
||||||
|
@ -33,7 +34,6 @@ const dls = {}
|
||||||
const tryNum = {}
|
const tryNum = {}
|
||||||
let isRuningActionTask = false
|
let isRuningActionTask = false
|
||||||
|
|
||||||
const filterFileName = /[\\/:*?#"<>|]/g
|
|
||||||
|
|
||||||
// getters
|
// getters
|
||||||
const getters = {
|
const getters = {
|
||||||
|
@ -378,9 +378,9 @@ const actions = {
|
||||||
statusText: '待下载',
|
statusText: '待下载',
|
||||||
url: null,
|
url: null,
|
||||||
// songmid: musicInfo.songmid,
|
// songmid: musicInfo.songmid,
|
||||||
fileName: `${rootState.setting.download.fileName
|
fileName: filterFileName(`${rootState.setting.download.fileName
|
||||||
.replace('歌名', musicInfo.name)
|
.replace('歌名', musicInfo.name)
|
||||||
.replace('歌手', musicInfo.singer)}.${ext}`.replace(filterFileName, ''),
|
.replace('歌手', musicInfo.singer)}.${ext}`),
|
||||||
progress: {
|
progress: {
|
||||||
downloaded: 0,
|
downloaded: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
|
|
|
@ -293,11 +293,11 @@ const mutations = {
|
||||||
const targetMusicInfo = targetList.list.find(item => item.songmid == id)
|
const targetMusicInfo = targetList.list.find(item => item.songmid == id)
|
||||||
if (targetMusicInfo) Object.assign(targetMusicInfo, data)
|
if (targetMusicInfo) Object.assign(targetMusicInfo, data)
|
||||||
},
|
},
|
||||||
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, isSync }) {
|
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, position, isSync }) {
|
||||||
if (!isSync) {
|
if (!isSync) {
|
||||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||||
action: 'create_user_list',
|
action: 'create_user_list',
|
||||||
data: { name, id, list, source, sourceListId },
|
data: { name, id, list, source, sourceListId, position },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,7 +311,11 @@ const mutations = {
|
||||||
source,
|
source,
|
||||||
sourceListId,
|
sourceListId,
|
||||||
}
|
}
|
||||||
state.userList.push(newList)
|
if (position == null) {
|
||||||
|
state.userList.push(newList)
|
||||||
|
} else {
|
||||||
|
state.userList.splice(position + 1, 0, newList)
|
||||||
|
}
|
||||||
allListUpdate(newList)
|
allListUpdate(newList)
|
||||||
}
|
}
|
||||||
this.commit('list/listAddMultiple', { id, list, isSync: true })
|
this.commit('list/listAddMultiple', { id, list, isSync: true })
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { shell, clipboard } from 'electron'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc'
|
import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc'
|
||||||
import iconv from 'iconv-lite'
|
import iconv from 'iconv-lite'
|
||||||
|
import { gzip, gunzip } from 'zlib'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取两个数之间的随机整数,大于等于min,小于max
|
* 获取两个数之间的随机整数,大于等于min,小于max
|
||||||
|
@ -433,3 +434,39 @@ export const setMusicUrl = (musicInfo, type, url) => rendererSend(NAMES.mainWind
|
||||||
url,
|
url,
|
||||||
})
|
})
|
||||||
export const clearMusicUrl = () => rendererSend(NAMES.mainWindow.clear_music_url)
|
export const clearMusicUrl = () => rendererSend(NAMES.mainWindow.clear_music_url)
|
||||||
|
|
||||||
|
export const gzipData = str => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
gzip(str, (err, result) => {
|
||||||
|
if (err) return reject(err)
|
||||||
|
resolve(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const gunzipData = buf => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
gunzip(buf, (err, result) => {
|
||||||
|
if (err) return reject(err)
|
||||||
|
resolve(result.toString())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveLxConfigFile = async(path, data) => {
|
||||||
|
if (!path.endsWith('.lxmc')) path += '.lxmc'
|
||||||
|
fs.writeFile(path, await gzipData(JSON.stringify(data)), 'binary', err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const readLxConfigFile = async path => {
|
||||||
|
let isJSON = path.endsWith('.json')
|
||||||
|
let data = await fs.promises.readFile(path, isJSON ? 'utf8' : 'binary')
|
||||||
|
if (!data || isJSON) return data
|
||||||
|
data = await gunzipData(Buffer.from(data, 'binary'))
|
||||||
|
return data.toString('utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const fileNameRxp = /[\\/:*?#"<>|]/g
|
||||||
|
export const filterFileName = name => name.replace(fileNameRxp, '')
|
||||||
|
|
|
@ -7,9 +7,13 @@
|
||||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='70%' viewBox='0 0 24 24' space='preserve')
|
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='70%' viewBox='0 0 24 24' space='preserve')
|
||||||
use(xlink:href='#icon-list-add')
|
use(xlink:href='#icon-list-add')
|
||||||
ul.scroll(:class="$style.listsContent" ref="dom_lists_list")
|
ul.scroll(:class="$style.listsContent" ref="dom_lists_list")
|
||||||
li(:class="[$style.listsItem, defaultList.id == listId ? $style.active : null]" :tips="defaultList.name" @click="handleListToggle(defaultList.id)")
|
li(:class="[$style.listsItem, defaultList.id == listId ? $style.active : null]" :tips="defaultList.name"
|
||||||
|
@contextmenu="handleListsItemRigthClick($event, -2)"
|
||||||
|
@click="handleListToggle(defaultList.id)")
|
||||||
span(:class="$style.listsLabel") {{defaultList.name}}
|
span(:class="$style.listsLabel") {{defaultList.name}}
|
||||||
li(:class="[$style.listsItem, loveList.id == listId ? $style.active : null]" :tips="loveList.name" @click="handleListToggle(loveList.id)")
|
li(:class="[$style.listsItem, loveList.id == listId ? $style.active : null]" :tips="loveList.name"
|
||||||
|
@contextmenu="handleListsItemRigthClick($event, -1)"
|
||||||
|
@click="handleListToggle(loveList.id)")
|
||||||
span(:class="$style.listsLabel") {{loveList.name}}
|
span(:class="$style.listsLabel") {{loveList.name}}
|
||||||
li.user-list(
|
li.user-list(
|
||||||
:class="[$style.listsItem, item.id == listId ? $style.active : null, listsData.rightClickItemIndex == index ? $style.clicked : null, fetchingListStatus[item.id] ? $style.fetching : null]"
|
:class="[$style.listsItem, item.id == listId ? $style.active : null, listsData.rightClickItemIndex == index ? $style.clicked : null, fetchingListStatus[item.id] ? $style.fetching : null]"
|
||||||
|
@ -74,7 +78,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapMutations, mapGetters, mapActions } from 'vuex'
|
import { mapMutations, mapGetters, mapActions } from 'vuex'
|
||||||
import { throttle, scrollTo, clipboardWriteText, assertApiSupport, openUrl } from '../utils'
|
import { throttle, scrollTo, clipboardWriteText, assertApiSupport, openUrl, openSaveDir, saveLxConfigFile, selectDir, readLxConfigFile, filterFileName } from '../utils'
|
||||||
import musicSdk from '../utils/music'
|
import musicSdk from '../utils/music'
|
||||||
export default {
|
export default {
|
||||||
name: 'List',
|
name: 'List',
|
||||||
|
@ -105,6 +109,8 @@ export default {
|
||||||
isShowItemMenu: false,
|
isShowItemMenu: false,
|
||||||
itemMenuControl: {
|
itemMenuControl: {
|
||||||
rename: true,
|
rename: true,
|
||||||
|
import: true,
|
||||||
|
export: true,
|
||||||
sync: false,
|
sync: false,
|
||||||
moveup: true,
|
moveup: true,
|
||||||
movedown: true,
|
movedown: true,
|
||||||
|
@ -190,6 +196,16 @@ export default {
|
||||||
action: 'rename',
|
action: 'rename',
|
||||||
disabled: !this.listsData.itemMenuControl.rename,
|
disabled: !this.listsData.itemMenuControl.rename,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: this.$t('view.list.lists_import'),
|
||||||
|
action: 'import',
|
||||||
|
disabled: !this.listsData.itemMenuControl.export,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: this.$t('view.list.lists_export'),
|
||||||
|
action: 'export',
|
||||||
|
disabled: !this.listsData.itemMenuControl.export,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: this.$t('view.list.lists_sync'),
|
name: this.$t('view.list.lists_sync'),
|
||||||
action: 'sync',
|
action: 'sync',
|
||||||
|
@ -722,10 +738,25 @@ export default {
|
||||||
}).catch(_ => _)
|
}).catch(_ => _)
|
||||||
},
|
},
|
||||||
handleListsItemRigthClick(event, index) {
|
handleListsItemRigthClick(event, index) {
|
||||||
const source = this.userList[index].source
|
let source
|
||||||
this.listsData.itemMenuControl.sync = !!source && !!musicSdk[source].songList
|
switch (index) {
|
||||||
this.listsData.itemMenuControl.moveup = index > 0
|
case -1:
|
||||||
this.listsData.itemMenuControl.movedown = index < this.userList.length - 1
|
case -2:
|
||||||
|
this.listsData.itemMenuControl.rename = false
|
||||||
|
this.listsData.itemMenuControl.remove = false
|
||||||
|
this.listsData.itemMenuControl.sync = false
|
||||||
|
this.listsData.itemMenuControl.moveup = false
|
||||||
|
this.listsData.itemMenuControl.movedown = false
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this.listsData.itemMenuControl.rename = true
|
||||||
|
this.listsData.itemMenuControl.remove = true
|
||||||
|
source = this.userList[index].source
|
||||||
|
this.listsData.itemMenuControl.sync = !!source && !!musicSdk[source]?.songList
|
||||||
|
this.listsData.itemMenuControl.moveup = index > 0
|
||||||
|
this.listsData.itemMenuControl.movedown = index < this.userList.length - 1
|
||||||
|
break
|
||||||
|
}
|
||||||
this.listsData.rightClickItemIndex = index
|
this.listsData.rightClickItemIndex = index
|
||||||
this.listsData.menuLocation.x = event.currentTarget.offsetLeft + event.offsetX
|
this.listsData.menuLocation.x = event.currentTarget.offsetLeft + event.offsetX
|
||||||
this.listsData.menuLocation.y = event.currentTarget.offsetTop + event.offsetY - this.$refs.dom_lists_list.scrollTop
|
this.listsData.menuLocation.y = event.currentTarget.offsetTop + event.offsetY - this.$refs.dom_lists_list.scrollTop
|
||||||
|
@ -771,6 +802,12 @@ export default {
|
||||||
dom.querySelector('input').focus()
|
dom.querySelector('input').focus()
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
case 'import':
|
||||||
|
this.handleImportList(index)
|
||||||
|
break
|
||||||
|
case 'export':
|
||||||
|
this.handleExportList(index)
|
||||||
|
break
|
||||||
case 'sync':
|
case 'sync':
|
||||||
this.handleSyncSourceList(index)
|
this.handleSyncSourceList(index)
|
||||||
break
|
break
|
||||||
|
@ -946,6 +983,97 @@ export default {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
handleExportList(index) {
|
||||||
|
let list
|
||||||
|
switch (index) {
|
||||||
|
case -2:
|
||||||
|
list = this.defaultList
|
||||||
|
break
|
||||||
|
case -1:
|
||||||
|
list = this.loveList
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
list = this.userList[index]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (!list) return
|
||||||
|
openSaveDir({
|
||||||
|
title: this.$t('view.list.lists_export_part_desc'),
|
||||||
|
defaultPath: `lx_list_part_${filterFileName(list.name)}.lxmc`,
|
||||||
|
}).then(async result => {
|
||||||
|
if (result.canceled) return
|
||||||
|
const data = JSON.parse(JSON.stringify({
|
||||||
|
type: 'playListPart',
|
||||||
|
data: list,
|
||||||
|
}))
|
||||||
|
for await (const item of data.data.list) {
|
||||||
|
if (item.otherSource) delete item.otherSource
|
||||||
|
if (item.lrc) delete item.lrc
|
||||||
|
}
|
||||||
|
saveLxConfigFile(result.filePath, data)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleImportList(index) {
|
||||||
|
let list
|
||||||
|
switch (index) {
|
||||||
|
case -2:
|
||||||
|
list = this.defaultList
|
||||||
|
break
|
||||||
|
case -1:
|
||||||
|
list = this.loveList
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
list = this.userList[index]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (!list) return
|
||||||
|
|
||||||
|
selectDir({
|
||||||
|
title: this.$t('view.list.lists_import_part_desc'),
|
||||||
|
properties: ['openFile'],
|
||||||
|
filters: [
|
||||||
|
{ name: 'Play List Part', extensions: ['json', 'lxmc'] },
|
||||||
|
{ name: 'All Files', extensions: ['*'] },
|
||||||
|
],
|
||||||
|
}).then(async result => {
|
||||||
|
if (result.canceled) return
|
||||||
|
let listData
|
||||||
|
try {
|
||||||
|
listData = JSON.parse(await readLxConfigFile(result.filePaths[0]))
|
||||||
|
} catch (error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (listData.type !== 'playListPart') return
|
||||||
|
const targetList = this.lists.find(l => l.id == listData.data.id)
|
||||||
|
if (targetList) {
|
||||||
|
const confirm = await this.$dialog.confirm({
|
||||||
|
message: this.$t('view.list.lists_import_part_confirm', { importName: listData.data.name, localName: targetList.name }),
|
||||||
|
cancelButtonText: this.$t('view.list.lists_import_part_button_cancel'),
|
||||||
|
confirmButtonText: this.$t('view.list.lists_import_part_button_confirm'),
|
||||||
|
})
|
||||||
|
if (confirm) {
|
||||||
|
listData.data.name = list.name
|
||||||
|
this.setList({
|
||||||
|
name: listData.data.name,
|
||||||
|
id: listData.data.id,
|
||||||
|
list: listData.data.list,
|
||||||
|
source: listData.data.source,
|
||||||
|
sourceListId: listData.data.sourceListId,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listData.data.id += `__${Date.now()}`
|
||||||
|
}
|
||||||
|
this.createUserList({
|
||||||
|
name: listData.data.name,
|
||||||
|
id: listData.data.id,
|
||||||
|
list: listData.data.list,
|
||||||
|
source: listData.data.source,
|
||||||
|
sourceListId: listData.data.sourceListId,
|
||||||
|
position: Math.max(index, -1),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -329,16 +329,16 @@ import {
|
||||||
setWindowSize,
|
setWindowSize,
|
||||||
getSetting,
|
getSetting,
|
||||||
saveSetting,
|
saveSetting,
|
||||||
|
saveLxConfigFile,
|
||||||
|
readLxConfigFile,
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { rendererSend, rendererInvoke, rendererOn, NAMES, rendererOff } from '@common/ipc'
|
import { rendererSend, rendererInvoke, rendererOn, NAMES, rendererOff } from '@common/ipc'
|
||||||
import { mergeSetting, isMac } from '../../common/utils'
|
import { mergeSetting, isMac } from '../../common/utils'
|
||||||
import apiSourceInfo from '../utils/music/api-source-info'
|
import apiSourceInfo from '../utils/music/api-source-info'
|
||||||
import fs from 'fs'
|
|
||||||
import languageList from '@renderer/lang/languages.json'
|
import languageList from '@renderer/lang/languages.json'
|
||||||
import { base as eventBaseName } from '../event/names'
|
import { base as eventBaseName } from '../event/names'
|
||||||
import * as hotKeys from '../../common/hotKey'
|
import * as hotKeys from '../../common/hotKey'
|
||||||
import { mainWindow as eventsNameMainWindow, winLyric as eventsNameWinLyric } from '../../main/events/_name'
|
import { mainWindow as eventsNameMainWindow, winLyric as eventsNameWinLyric } from '../../main/events/_name'
|
||||||
import { gzip, gunzip } from 'zlib'
|
|
||||||
import music from '../utils/music'
|
import music from '../utils/music'
|
||||||
|
|
||||||
let hotKeyTargetInput
|
let hotKeyTargetInput
|
||||||
|
@ -815,7 +815,7 @@ export default {
|
||||||
async importSetting(path) {
|
async importSetting(path) {
|
||||||
let settingData
|
let settingData
|
||||||
try {
|
try {
|
||||||
settingData = JSON.parse(await this.handleReadFile(path))
|
settingData = JSON.parse(await readLxConfigFile(path))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -830,12 +830,12 @@ export default {
|
||||||
type: 'setting',
|
type: 'setting',
|
||||||
data: Object.assign({ version: this.settingVersion }, this.setting),
|
data: Object.assign({ version: this.settingVersion }, this.setting),
|
||||||
}
|
}
|
||||||
this.handleSaveFile(path, JSON.stringify(data))
|
saveLxConfigFile(path, JSON.stringify(data))
|
||||||
},
|
},
|
||||||
async importPlayList(path) {
|
async importPlayList(path) {
|
||||||
let listData
|
let listData
|
||||||
try {
|
try {
|
||||||
listData = JSON.parse(await this.handleReadFile(path))
|
listData = JSON.parse(await readLxConfigFile(path))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -867,12 +867,12 @@ export default {
|
||||||
if (item.otherSource) delete item.otherSource
|
if (item.otherSource) delete item.otherSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.handleSaveFile(path, JSON.stringify(data))
|
saveLxConfigFile(path, JSON.stringify(data))
|
||||||
},
|
},
|
||||||
async importAllData(path) {
|
async importAllData(path) {
|
||||||
let allData
|
let allData
|
||||||
try {
|
try {
|
||||||
allData = JSON.parse(await this.handleReadFile(path))
|
allData = JSON.parse(await readLxConfigFile(path))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -906,7 +906,7 @@ export default {
|
||||||
if (item.otherSource) delete item.otherSource
|
if (item.otherSource) delete item.otherSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.handleSaveFile(path, JSON.stringify(allData))
|
saveLxConfigFile(path, JSON.stringify(allData))
|
||||||
},
|
},
|
||||||
handleImportAllData() {
|
handleImportAllData() {
|
||||||
selectDir({
|
selectDir({
|
||||||
|
@ -1198,35 +1198,6 @@ export default {
|
||||||
handleTrayShowChange(isShow) {
|
handleTrayShowChange(isShow) {
|
||||||
this.current_setting.tray.isToTray = isShow
|
this.current_setting.tray.isToTray = isShow
|
||||||
},
|
},
|
||||||
async handleSaveFile(path, data) {
|
|
||||||
if (!path.endsWith('.lxmc')) path += '.lxmc'
|
|
||||||
fs.writeFile(path, await this.gzip(data), 'binary', err => {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async handleReadFile(path) {
|
|
||||||
let isJSON = path.endsWith('.json')
|
|
||||||
let data = await fs.promises.readFile(path, isJSON ? 'utf8' : 'binary')
|
|
||||||
if (!data || isJSON) return data
|
|
||||||
data = await this.gunzip(Buffer.from(data, 'binary'))
|
|
||||||
return data.toString('utf8')
|
|
||||||
},
|
|
||||||
gzip(str) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
gzip(str, (err, result) => {
|
|
||||||
if (err) return reject(err)
|
|
||||||
resolve(result)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
gunzip(buf) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
gunzip(buf, (err, result) => {
|
|
||||||
if (err) return reject(err)
|
|
||||||
resolve(result.toString())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getApiStatus() {
|
getApiStatus() {
|
||||||
let status
|
let status
|
||||||
if (window.globalObj.userApi.status) status = this.$t('view.setting.basic_source_status_success')
|
if (window.globalObj.userApi.status) status = this.$t('view.setting.basic_source_status_success')
|
||||||
|
|
Loading…
Reference in New Issue