新增列表更新管理

pull/930/merge
lyswhut 2022-04-02 14:13:58 +08:00
parent 36ac3532e6
commit 47d3c50863
13 changed files with 384 additions and 42 deletions

View File

@ -6,6 +6,7 @@
- 新增歌词偏移设置,可以在播放详情页歌词右键菜单中使用
- 新增设置-播放设置-播放错误时自动切换歌曲设置默认开启原来的行为若你不想在遇到音频加载失败、url获取失败等错误时自动切歌可以关闭此设置
- 新增设置-桌面歌词设置-自动刷新歌词置顶(当歌词置顶后仍被某些程序遮挡时可尝试启用此设置)
- 新增列表更新管理,可以在鼠标移入“我的列表”标题时出现的按钮中进入,这可以用来设置启动软件时需要自动从原平台更新的列表
### 优化

View File

@ -105,6 +105,9 @@
"list_sort_modal_by_type": "Sort categories",
"list_sort_modal_by_up": "Ascending",
"list_sort_modal_tip_confirm": "Are you sure you want to do this?",
"list_update_modal__auto_update": "auto update",
"list_update_modal__title": "List update management",
"list_update_modal__update": "Sync",
"lists__duplicate": "Duplicate song",
"lists__export": "Export",
"lists__export_part_desc": "Choose where to save the list file",

View File

@ -105,6 +105,9 @@
"list_sort_modal_by_type": "排序类别",
"list_sort_modal_by_up": "升序",
"list_sort_modal_tip_confirm": "你确定要这么做吗?",
"list_update_modal__auto_update": "自动更新",
"list_update_modal__title": "列表更新管理",
"list_update_modal__update": "立即更新",
"lists__duplicate": "重复歌曲",
"lists__export": "导出",
"lists__export_part_desc": "选择列表文件保存位置",

View File

@ -105,6 +105,9 @@
"list_sort_modal_by_type": "排序類別",
"list_sort_modal_by_up": "升序",
"list_sort_modal_tip_confirm": "你確定要這麼做嗎?",
"list_update_modal__auto_update": "自動更新",
"list_update_modal__title": "列表更新管理",
"list_update_modal__update": "立即更新",
"lists__duplicate": "重複歌曲",
"lists__export": "導出",
"lists__export_part_desc": "選擇列表文件保存位置",

View File

@ -121,3 +121,8 @@ export const removeUserList = id => {
export const getList = id => {
return allList[id] ?? []
}
export const fetchingListStatus = reactive({})
export const setFetchingListStatus = (id, status) => {
fetchingListStatus[id] = status
}

View File

@ -11,6 +11,7 @@ import useHandleEnvParams from './useHandleEnvParams'
import useEventListener from './useEventListener'
import useDeepLink from './useDeepLink'
import usePlayer from './usePlayer'
import useListAutoUpdate from './useListAutoUpdate'
export default () => {
@ -46,6 +47,7 @@ export default () => {
setting,
})
const initDeepLink = useDeepLink()
const handleListAutoUpdate = useListAutoUpdate()
getEnvParams().then(envParams => {
@ -63,6 +65,7 @@ export default () => {
initPlayer()
handleEnvParams(envParams) // 处理传入的启动参数
initDeepLink(envParams)
handleListAutoUpdate()
})
})
}

View File

@ -1,7 +1,7 @@
import { useCommit, useRefGetter } from '@renderer/utils/vueTools'
import { getPlayList } from '@renderer/utils'
import { getPlayInfo, getSearchHistoryList } from '@renderer/utils/tools'
import { initListPosition, initListPrevSelectId } from '@renderer/utils/data'
import { initListPosition, initListPrevSelectId, initListUpdateInfo } from '@renderer/utils/data'
import music from '@renderer/utils/music'
import { log } from '@common/utils'
import {
@ -152,6 +152,7 @@ export default ({
await Promise.all([
initListPosition(), // 列表位置记录
initListPrevSelectId(), // 上次选中的列表记录
initListUpdateInfo(), // 列表更新设置
initUserApi(), // 自定义API
]).catch(err => log.error(err))
music.init() // 初始化音乐sdk

View File

@ -0,0 +1,26 @@
import { getListUpdateInfo } from '@renderer/utils/data'
import useSyncSourceList from '@renderer/utils/compositions/useSyncSourceList'
import { userLists } from '@renderer/core/share/list'
export default () => {
const syncSourceList = useSyncSourceList()
const handleSyncSourceList = async(waitUpdateLists) => {
if (!waitUpdateLists.length) return
const targetListInfo = waitUpdateLists.shift()
// console.log(targetListInfo)
await syncSourceList(targetListInfo)
handleSyncSourceList(waitUpdateLists)
}
return () => {
const waitUpdateLists = Object.entries(getListUpdateInfo())
.filter(([id, info]) => info.isAutoUpdate)
.map(([id]) => userLists.find(l => l.id === id))
.filter(_ => _)
for (let i = 2; i > 0; i--) {
handleSyncSourceList(waitUpdateLists)
}
}
}

View File

@ -1,7 +1,7 @@
import musicSdk from '../../utils/music'
import { clearLyric, clearMusicUrl } from '../../utils'
import { sync as eventSyncName, list as eventListNames } from '@renderer/event/names'
import { removeListPosition, setListPrevSelectId } from '@renderer/utils/data'
import { removeListPosition, setListPrevSelectId, removeListUpdateInfo } from '@renderer/utils/data'
import { markRawList, toRaw, markRaw } from '@renderer/utils/vueTools'
import { allList, allListInit, setInited, removeUserList, addUserList, updateList, defaultList, loveList, userLists } from '@renderer/core/share/list'
@ -337,6 +337,7 @@ const mutations = {
if (index < 0) return
removeUserList(id)
removeListPosition(id)
removeListUpdateInfo(id)
window.eventHub.emit(eventListNames.listChange, [id])
},
setUserListName(state, { id, name, isSync }) {

View File

@ -0,0 +1,34 @@
import { setListUpdateTime } from '@renderer/utils/data'
import { setFetchingListStatus } from '@renderer/core/share/list'
import { useAction, useCommit } from '@renderer/utils/vueTools'
export default () => {
const getBoardListAll = useAction('leaderboard', 'getListAll')
const getListDetailAll = useAction('songList', 'getListDetailAll')
const setList = useCommit('list', 'setList')
const fetchList = (id, source, sourceListId) => {
setFetchingListStatus(id, true)
let promise
if (/board__/.test(sourceListId)) {
const id = sourceListId.replace(/board__/, '')
promise = getBoardListAll({ id, isRefresh: true })
} else {
promise = getListDetailAll({ source, id: sourceListId, isRefresh: true })
}
return promise.finally(() => {
setFetchingListStatus(id, false)
})
}
return async targetListInfo => {
const list = await fetchList(targetListInfo.id, targetListInfo.source, targetListInfo.sourceListId)
// console.log(targetListInfo.list.length, list.length)
setList({
...targetListInfo,
list,
})
setListUpdateTime(targetListInfo.id, Date.now())
}
}

View File

@ -3,6 +3,7 @@ import { throttle } from './index'
let listPosition = {}
let listPrevSelectId
let listUpdateInfo = {}
const saveListPosition = throttle(() => {
rendererSend(NAMES.mainWindow.save_data, {
@ -43,3 +44,39 @@ export const setListPrevSelectId = id => {
listPrevSelectId = id
saveListPrevSelectId()
}
export const initListUpdateInfo = () => {
return rendererInvoke(NAMES.mainWindow.get_data, 'listUpdateInfo').then(data => {
if (!data) data = {}
// console.log(data)
listUpdateInfo = data
})
}
const saveListUpdateInfo = throttle(() => {
rendererSend(NAMES.mainWindow.save_data, {
path: 'listUpdateInfo',
data: listUpdateInfo,
})
}, 1000)
export const getListUpdateInfo = () => listUpdateInfo
export const setListAutoUpdate = (id, enable) => {
const targetInfo = listUpdateInfo[id] ?? { updateTime: '', isAutoUpdate: false }
targetInfo.isAutoUpdate = enable
listUpdateInfo[id] = targetInfo
saveListUpdateInfo()
}
export const setListUpdateTime = (id, time) => {
const targetInfo = listUpdateInfo[id] ?? { updateTime: '', isAutoUpdate: false }
targetInfo.updateTime = time
listUpdateInfo[id] = targetInfo
saveListUpdateInfo()
}
// export const setListUpdateInfo = (id, { updateTime, isAutoUpdate }) => {
// listUpdateInfo[id] = { updateTime, isAutoUpdate }
// saveListUpdateInfo()
// }
export const removeListUpdateInfo = id => {
delete listUpdateInfo[id]
saveListUpdateInfo()
}

View File

@ -0,0 +1,221 @@
<template>
<material-modal :show="visible" @close="$emit('update:visible', false)" bg-close teleport="#view">
<div :class="$style.header">
<h2>{{$t('list_update_modal__title')}}</h2>
</div>
<main class="scroll" :class="$style.main">
<ul ref="dom_list" v-if="lists.length" :class="$style.list">
<li v-for="(list, index) in lists" :key="list.id" :class="[$style.listItem, {[$style.fetching]: fetchingListStatus[list.id]}]">
<div :class="$style.listLeft">
<h3 :class="$style.text">{{list.name}} <span :class="$style.label">{{list.source}}</span></h3>
<div>
<base-checkbox :class="$style.checkbox" :id="`list_auto_update_${list.id}`" :modelValue="autoUpdate[list.id] == true"
@change="handleChangeAutoUpdate(list, $event)" :label="$t('list_update_modal__auto_update')" />
<span :class="$style.label" style="vertical-align: text-top;">{{listUpdateTimes[list.id]}}</span>
</div>
</div>
<div :class="$style.btns">
<button :class="$style.btn" :disabled="fetchingListStatus[list.id]" outline="outline" :aria-label="$t('list_update_modal__update')" @click.stop="handleUpdate(index)">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" style="transform: rotate(45deg);" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-refresh" />
</svg>
</button>
</div>
</li>
</ul>
<div :class="$style.noItem" v-else>
<p v-text="$t('no_item')"></p>
</div>
</main>
</material-modal>
</template>
<script>
import { computed, reactive } from '@renderer/utils/vueTools'
import { userLists, fetchingListStatus } from '@renderer/core/share/list'
import musicSdk from '@renderer/utils/music'
import { getListUpdateInfo, setListAutoUpdate } from '@renderer/utils/data'
export default {
props: {
visible: {
type: Boolean,
default: false,
},
listUpdateTimes: Object,
},
emits: ['update-list', 'update:visible'],
setup(props, { emit }) {
const lists = computed(() => userLists.filter(l => !!l.source && !!musicSdk[l.source]?.songList))
const autoUpdate = reactive({})
for (const [id, value] of Object.entries(getListUpdateInfo())) {
autoUpdate[id] = value.isAutoUpdate == true
}
const handleChangeAutoUpdate = (list, enable) => {
setListAutoUpdate(list.id, enable)
autoUpdate[list.id] = enable
}
const handleUpdate = index => {
emit('update-list', lists.value[index])
}
return {
lists,
autoUpdate,
fetchingListStatus,
handleUpdate,
handleChangeAutoUpdate,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.header {
flex: none;
padding: 15px;
text-align: center;
h2 {
word-break: break-all;
}
}
.main {
min-height: 200px;
width: 460px;
}
.list {
// background-color: @color-search-form-background;
font-size: 13px;
transition-property: height;
position: relative;
.listItem {
position: relative;
padding: 15px 10px;
transition: .3s ease;
transition-property: background-color, opacity;
line-height: 1.3;
// overflow: hidden;
display: flex;
flex-flow: row nowrap;
align-items: center;
&:hover {
background-color: @color-theme_2-hover;
}
// border-radius: 4px;
// &:last-child {
// border-bottom-left-radius: 4px;
// border-bottom-right-radius: 4px;
// }
&.fetching {
opacity: .5;
}
}
}
.listLeft {
flex: auto;
min-width: 0;
display: flex;
flex-flow: column nowrap;
justify-content: center;
}
.text {
flex: auto;
.mixin-ellipsis-1;
}
.checkbox {
margin-top: 3px;
font-size: 14px;
opacity: .86;
}
.label {
flex: none;
font-size: 12px;
opacity: 0.5;
padding: 0 10px;
// display: flex;
// align-items: center;
// transform: rotate(45deg);
// background-color:
}
.btns {
flex: none;
font-size: 12px;
padding: 0 5px;
display: flex;
align-items: center;
}
.btn {
background-color: transparent;
border: none;
border-radius: @form-radius;
margin-right: 5px;
cursor: pointer;
padding: 4px 7px;
color: @color-btn;
outline: none;
transition: background-color 0.2s ease;
line-height: 0;
&:last-child {
margin-right: 0;
}
svg {
height: 22px;
width: 22px;
}
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
.no-item {
position: relative;
height: 200px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
p {
font-size: 16px;
color: @color-theme_2-font-label;
}
}
each(@themes, {
:global(#root.@{value}) {
.listItem {
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
}
.btn {
color: ~'@{color-@{value}-btn}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
.no-item {
p {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
}
})
</style>

View File

@ -2,11 +2,18 @@
<div :class="$style.lists" ref="dom_lists">
<div :class="$style.listHeader">
<h2 :class="$style.listsTitle">{{$t('my_list')}}</h2>
<button :class="$style.listsAdd" @click="handleShowNewList" :aria-label="$t('lists__new_list_btn')">
<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>
</svg>
</button>
<div>
<button :class="$style.listsAdd" @click="handleShowNewList" :aria-label="$t('lists__new_list_btn')">
<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>
</svg>
</button>
<button :class="$style.listsAdd" @click="isShowListUpdateModal = true" style="transform: rotate(45deg);" :aria-label="$t('list_update_modal__title')">
<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-refresh"></use>
</svg>
</button>
</div>
</div>
<ul class="scroll" :class="[$style.listsContent, { [$style.sortable]: keyEvent.isModDown }]" ref="dom_lists_list">
<li class="default-list" :class="[$style.listsItem, {[$style.active]: defaultList.id == listId}]" :aria-label="defaultList.name" :aria-selected="defaultList.id == listId"
@ -36,19 +43,23 @@
<base-menu :menus="listsItemMenu" :location="listsData.menuLocation" item-name="name" :isShow="listsData.isShowItemMenu" @menu-click="handleListsItemMenuClick" />
<DuplicateMusicModal v-model:visible="isShowDuplicateMusicModal" :list-info="selectedDuplicateListInfo" />
<ListSortModal v-model:visible="isShowListSortModal" :list-info="selectedSortListInfo" />
<ListUpdateModal v-model:visible="isShowListUpdateModal" :list-update-times="listUpdateTimes" @update-list="handleSyncSourceList" />
</div>
</template>
<script>
import { mapMutations, mapActions } from 'vuex'
import { openSaveDir, saveLxConfigFile, selectDir, readLxConfigFile, filterFileName, openUrl } from '@renderer/utils'
import { mapMutations } from 'vuex'
import { openSaveDir, saveLxConfigFile, selectDir, readLxConfigFile, filterFileName, openUrl, dateFormat } from '@renderer/utils'
import musicSdk from '@renderer/utils/music'
import DuplicateMusicModal from './DuplicateMusicModal'
import ListSortModal from './ListSortModal'
import { defaultList, loveList, userLists } from '@renderer/core/share/list'
import ListUpdateModal from './ListUpdateModal'
import { defaultList, loveList, userLists, fetchingListStatus } from '@renderer/core/share/list'
import { ref, computed, useCommit, useCssModule } from '@renderer/utils/vueTools'
import { getList } from '@renderer/core/share/utils'
import useDarg from '@renderer/utils/compositions/useDrag'
import { getListUpdateInfo } from '@renderer/utils/data'
import useSyncSourceList from '@renderer/utils/compositions/useSyncSourceList'
export default {
name: 'MyLists',
@ -61,12 +72,15 @@ export default {
components: {
DuplicateMusicModal,
ListSortModal,
ListUpdateModal,
},
setup() {
const dom_lists_list = ref(null)
const lists = computed(() => [defaultList, loveList, ...userLists])
const setUserListPosition = useCommit('list', 'setUserListPosition')
const syncSourceList = useSyncSourceList()
const styles = useCssModule()
const { setDisabled } = useDarg({
dom_list: dom_lists_list,
@ -82,8 +96,10 @@ export default {
loveList,
userLists,
lists,
fetchingListStatus,
dom_lists_list,
setDisabledSort: setDisabled,
syncSourceList,
}
},
emits: ['show-menu'],
@ -91,6 +107,7 @@ export default {
return {
isShowDuplicateMusicModal: false,
isShowListSortModal: false,
isShowListUpdateModal: false,
listsData: {
isShowItemMenu: false,
itemMenuControl: {
@ -111,9 +128,9 @@ export default {
isShowNewList: false,
isNewLeave: false,
},
fetchingListStatus: {},
selectedDuplicateListInfo: {},
selectedSortListInfo: {},
listUpdateTimes: {},
keyEvent: {
isModDown: false,
},
@ -186,6 +203,9 @@ export default {
this.setListsScroll()
window.eventHub.on('key_mod_down', this.handle_key_mod_down)
window.eventHub.on('key_mod_up', this.handle_key_mod_up)
for (const [id, value] of Object.entries(getListUpdateInfo())) {
this.listUpdateTimes[id] = value.updateTime ? dateFormat(value.updateTime) : ''
}
},
beforeUnmount() {
window.eventHub.off('key_mod_down', this.handle_key_mod_down)
@ -199,10 +219,6 @@ export default {
'setPrevSelectListId',
'setList',
]),
...mapActions('songList', ['getListDetailAll']),
...mapActions('leaderboard', {
getBoardListAll: 'getListAll',
}),
handle_key_mod_down(event) {
if (!this.keyEvent.isModDown) {
// console.log(event)
@ -336,7 +352,7 @@ export default {
this.handleExportList(index)
break
case 'sync':
this.handleSyncSourceList(index)
this.handleSyncSourceList(userLists[index])
break
case 'remove':
this.$dialog.confirm({
@ -349,28 +365,10 @@ export default {
break
}
},
fetchList(id, source, sourceListId) {
this.fetchingListStatus[id] = true
let promise
if (/board__/.test(sourceListId)) {
const id = sourceListId.replace(/board__/, '')
promise = this.getBoardListAll({ id, isRefresh: true })
} else {
promise = this.getListDetailAll({ source, id: sourceListId, isRefresh: true })
}
return promise.finally(() => {
this.fetchingListStatus[id] = false
})
},
async handleSyncSourceList(index) {
const targetListInfo = userLists[index]
const list = await this.fetchList(targetListInfo.id, targetListInfo.source, targetListInfo.sourceListId)
async handleSyncSourceList(targetListInfo) {
await this.syncSourceList(targetListInfo)
// console.log(targetListInfo.list.length, list.length)
this.setList({
...targetListInfo,
list,
})
this.listUpdateTimes[targetListInfo.id] = dateFormat(Date.now())
},
getTargetListInfo(index) {
let list
@ -498,6 +496,10 @@ export default {
}
.listHeader {
position: relative;
display: flex;
flex-flow: row nowrap;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
justify-content: space-between;
&:hover {
.listsAdd {
opacity: 1;
@ -508,20 +510,19 @@ export default {
font-size: 12px;
line-height: 38px;
padding: 0 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
flex: none;
}
.listsAdd {
position: absolute;
right: 0;
top: 8px;
// position: absolute;
// right: 0;
margin-top: 6px;
background: none;
height: 30px;
border: none;
outline: none;
border-radius: @radius-border;
cursor: pointer;
opacity: 0;
opacity: .1;
transition: opacity @transition-theme;
color: @color-btn;
svg {
@ -530,6 +531,9 @@ export default {
&:active {
opacity: .7 !important;
}
&:hover {
opacity: .6 !important;
}
}
.listsContent {
flex: auto;