v2.0.0
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<ul ref="dom_lists_list" class="scroll" :class="$style.listsContent">
|
||||
<li
|
||||
v-for="(item, index) in list"
|
||||
:key="item.id" :class="[$style.listsItem, { [$style.active]: item.id == boardId }, { [$style.clicked]: rightClickItemIndex == index }]"
|
||||
:aria-label="item.name" @click="handleToggleList(item.id)" @contextmenu="handleRigthClick($event, index)"
|
||||
>
|
||||
<span :class="$style.listsLabel">{{ item.name }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<base-menu
|
||||
v-model="isShowMenu"
|
||||
:menus="menus"
|
||||
:xy="menuLocation"
|
||||
item-name="name"
|
||||
@menu-click="handleMenuClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch, shallowReactive, ref, defineExpose } from '@common/utils/vueTools'
|
||||
import { getBoardsList, setBoard } from '@renderer/store/leaderboard/action'
|
||||
import { boards, Board } from '@renderer/store/leaderboard/state'
|
||||
import useMenu from './useMenu'
|
||||
import LX from '@renderer/types/lx'
|
||||
import { useRouter, useRoute } from '@common/utils/vueRouter'
|
||||
|
||||
const props = defineProps<{
|
||||
source: LX.OnlineSource
|
||||
boardId?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['show-menu'])
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const list = shallowReactive<Board['list']>([])
|
||||
const rightClickItemIndex = ref(-1)
|
||||
|
||||
const handleToggleList = (id: string) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
source: props.source,
|
||||
boardId: id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
menus,
|
||||
menuLocation,
|
||||
isShowMenu,
|
||||
showMenu,
|
||||
menuClick,
|
||||
} = useMenu({ emit, list })
|
||||
|
||||
const handleRigthClick = (event: MouseEvent, index: number) => {
|
||||
rightClickItemIndex.value = index
|
||||
showMenu(event, index)
|
||||
}
|
||||
const handleMenuClick = (action?: { action: string}) => {
|
||||
if (rightClickItemIndex.value < 0) return
|
||||
let index = rightClickItemIndex.value
|
||||
rightClickItemIndex.value = -1
|
||||
menuClick(action, index, props.source)
|
||||
}
|
||||
|
||||
|
||||
watch(() => props.source, async(source) => {
|
||||
// const source = (await getLeaderboardSetting()).source as LX.OnlineSource
|
||||
let boardList = boards[source]
|
||||
if (boardList == null) setBoard(boardList = await getBoardsList(source), source)
|
||||
list.splice(0, list.length, ...boardList.list)
|
||||
if (!props.boardId && boardList.list.length) handleToggleList(boardList.list[0].id)
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
defineExpose({ hideMenu: handleMenuClick })
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.listsContent {
|
||||
flex: auto;
|
||||
min-width: 0;
|
||||
overflow-y: scroll;
|
||||
// overflow-y: scroll !important;
|
||||
// border-right: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
.listsItem {
|
||||
position: relative;
|
||||
transition: .3s ease;
|
||||
transition-property: color, background-color;
|
||||
background-color: transparent;
|
||||
&:hover:not(.active) {
|
||||
background-color: var(--color-primary-background-hover);
|
||||
cursor: pointer;
|
||||
}
|
||||
&.active {
|
||||
// background-color:
|
||||
color: var(--color-primary);
|
||||
}
|
||||
&.selected {
|
||||
background-color: var(--color-primary-font-active);
|
||||
}
|
||||
&.clicked {
|
||||
background-color: var(--color-primary-background-hover);
|
||||
}
|
||||
&.editing {
|
||||
padding: 0 10px;
|
||||
background-color: var(--color-primary-background-hover);
|
||||
.listsLabel {
|
||||
display: none;
|
||||
}
|
||||
.listsInput {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.listsLabel {
|
||||
display: block;
|
||||
height: 100%;
|
||||
padding: 0 10px;
|
||||
font-size: 13px;
|
||||
line-height: 36px;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { computed, ref, reactive, nextTick } from '@common/utils/vueTools'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
import { addSongListDetail, playSongListDetail } from '../action'
|
||||
|
||||
export default ({
|
||||
emit,
|
||||
list,
|
||||
}) => {
|
||||
// const menuControl = reactive({
|
||||
// play: true,
|
||||
// collect: true,
|
||||
// })
|
||||
const t = useI18n()
|
||||
const menuLocation = reactive({ x: 0, y: 0 })
|
||||
const isShowMenu = ref(false)
|
||||
|
||||
const menus = computed(() => {
|
||||
return [
|
||||
{
|
||||
name: t('list__play'),
|
||||
action: 'play',
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
name: t('list__collect'),
|
||||
action: 'collect',
|
||||
disabled: false,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
const showMenu = (event, index) => {
|
||||
menuLocation.x = event.pageX
|
||||
menuLocation.y = event.pageY
|
||||
|
||||
if (isShowMenu.value) return
|
||||
emit('show-menu')
|
||||
nextTick(() => {
|
||||
isShowMenu.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const hideMenu = () => {
|
||||
isShowMenu.value = false
|
||||
}
|
||||
|
||||
|
||||
const menuClick = (action, index, source) => {
|
||||
// console.log(action)
|
||||
hideMenu()
|
||||
if (!action) return
|
||||
// const id = `board__${this.source}__${board.id}`
|
||||
const board = list[index]
|
||||
switch (action.action) {
|
||||
case 'play':
|
||||
playSongListDetail(board.id)
|
||||
break
|
||||
case 'collect':
|
||||
addSongListDetail(board.id, board.name, source)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
menus,
|
||||
menuLocation,
|
||||
isShowMenu,
|
||||
showMenu,
|
||||
menuClick,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<material-online-list
|
||||
ref="listRef"
|
||||
:page="listDetailInfo.page"
|
||||
:limit="listDetailInfo.limit"
|
||||
:total="listDetailInfo.total"
|
||||
:list="listDetailInfo.list"
|
||||
:no-item="listDetailInfo.noItemLabel"
|
||||
@show-menu="hideListsMenu"
|
||||
@play-list="handlePlayList"
|
||||
@toggle-page="togglePage"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch, defineProps, defineExpose } from '@common/utils/vueTools'
|
||||
import LX from '@renderer/types/lx'
|
||||
import useList from './useList'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
source: LX.OnlineSource
|
||||
boardId?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['show-menu'])
|
||||
|
||||
const {
|
||||
listRef,
|
||||
listDetailInfo,
|
||||
getList,
|
||||
handlePlayList,
|
||||
} = useList()
|
||||
|
||||
watch(() => props.boardId, (boardId) => {
|
||||
if (!boardId) return
|
||||
getList(boardId, 1)
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
|
||||
const hideListsMenu = () => {
|
||||
emit('show-menu')
|
||||
}
|
||||
|
||||
const togglePage = (page: number) => {
|
||||
getList(listDetailInfo.id, page)
|
||||
}
|
||||
|
||||
const hideMenu = () => {
|
||||
listRef.value.handleMenuClick()
|
||||
}
|
||||
|
||||
defineExpose({ hideMenu })
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
.container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
// import { useI18n } from '@renderer/plugins/i18n'
|
||||
// import { } from '@renderer/store/search/state'
|
||||
import { getAndSetListDetail } from '@renderer/store/leaderboard/action'
|
||||
import { listDetailInfo } from '@renderer/store/leaderboard/state'
|
||||
import { playSongListDetail } from '../action'
|
||||
|
||||
export default () => {
|
||||
const listRef = ref<any>(null)
|
||||
|
||||
const handlePlayList = (index: number) => {
|
||||
void playSongListDetail(listDetailInfo.id, listDetailInfo.list, index)
|
||||
}
|
||||
|
||||
const getList = (id: string, page: number) => {
|
||||
void getAndSetListDetail(id, page).then(() => {
|
||||
setTimeout(() => {
|
||||
if (listRef.value) listRef.value.scrollToTop()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
listRef,
|
||||
listDetailInfo,
|
||||
getList,
|
||||
handlePlayList,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { tempListMeta, userLists } from '@renderer/store/list/state'
|
||||
import { dialog } from '@renderer/plugins/Dialog'
|
||||
import syncSourceList from '@renderer/store/list/syncSourceList'
|
||||
import { getListDetail, getListDetailAll } from '@renderer/store/leaderboard/action'
|
||||
import { createUserList, setTempList } from '@renderer/store/list/action'
|
||||
import { playList } from '@renderer/core/player/action'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
|
||||
const getListId = (id: string) => `board__${id}`
|
||||
|
||||
export const addSongListDetail = async(id: string, name: string, source: LX.OnlineSource) => {
|
||||
// console.log(this.listDetail.info)
|
||||
// if (!this.listDetail.info.name) return
|
||||
const listId = getListId(id)
|
||||
const targetList = userLists.find(l => l.id == listId)
|
||||
if (targetList) {
|
||||
const confirm = await dialog.confirm({
|
||||
message: window.i18n.t('duplicate_list_tip', { name: targetList.name }),
|
||||
cancelButtonText: window.i18n.t('lists__import_part_button_cancel'),
|
||||
confirmButtonText: window.i18n.t('confirm_button_text'),
|
||||
})
|
||||
if (!confirm) return
|
||||
void syncSourceList(targetList)
|
||||
return
|
||||
}
|
||||
|
||||
const list = await getListDetailAll(id)
|
||||
await createUserList({
|
||||
name,
|
||||
id: listId,
|
||||
list,
|
||||
source,
|
||||
sourceListId: listId,
|
||||
})
|
||||
}
|
||||
|
||||
export const playSongListDetail = async(id: string, list?: LX.Music.MusicInfoOnline[], index: number = 0) => {
|
||||
let isPlayingList = false
|
||||
console.log(list)
|
||||
const listId = getListId(id)
|
||||
if (!list?.length) list = (await getListDetail(id, 1)).list
|
||||
if (list?.length) {
|
||||
await setTempList(listId, [...list])
|
||||
playList(LIST_IDS.TEMP, index)
|
||||
isPlayingList = true
|
||||
}
|
||||
const fullList = await getListDetailAll(id)
|
||||
if (!fullList.length) return
|
||||
if (isPlayingList) {
|
||||
if (tempListMeta.id == listId) {
|
||||
await setTempList(listId, [...fullList])
|
||||
}
|
||||
} else {
|
||||
await setTempList(listId, [...fullList])
|
||||
playList(LIST_IDS.TEMP, index)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div :class="$style.leaderboard">
|
||||
<div :class="$style.lists">
|
||||
<div :class="$style.listsSelect">
|
||||
<base-selection :model-value="source" :class="$style.select" :list="sourceList" item-key="id" item-name="name" @update:model-value="handleToggleSource" />
|
||||
</div>
|
||||
<BoardList ref="boardListRef" :board-id="boardId" :source="source" @show-menu="$refs.musicListRef?.hideMenu()" />
|
||||
</div>
|
||||
<div :class="$style.list">
|
||||
<MusicList ref="musicListRef" :source="source" :board-id="boardId" @show-menu="$refs.boardListRef?.hideMenu()" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, ref } from '@common/utils/vueTools'
|
||||
import { getLeaderboardSetting, setLeaderboardSetting } from '@renderer/utils/data'
|
||||
import BoardList from './BoardList/index.vue'
|
||||
import MusicList from './MusicList/index.vue'
|
||||
import { sources } from '@renderer/store/leaderboard/state'
|
||||
import { sourceNames } from '@renderer/store'
|
||||
import { useRoute, useRouter } from '@common/utils/vueRouter'
|
||||
|
||||
|
||||
const source = ref('')
|
||||
const boardId = ref(null)
|
||||
|
||||
const verifyQueryParams = async function(to, from, next) {
|
||||
let _source = to.query.source
|
||||
let _boardId = to.query.boardId
|
||||
|
||||
if (_source == null) {
|
||||
const setting = await getLeaderboardSetting()
|
||||
if (_source == null) {
|
||||
_source = setting.source
|
||||
_boardId = setting.boardId
|
||||
}
|
||||
next({
|
||||
path: to.path,
|
||||
query: { ...to.query, source: _source, boardId: _boardId },
|
||||
})
|
||||
return
|
||||
}
|
||||
next()
|
||||
source.value = _source
|
||||
boardId.value = _boardId
|
||||
setLeaderboardSetting({ source: _source, boardId: _boardId })
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BoardList,
|
||||
MusicList,
|
||||
},
|
||||
beforeRouteEnter: verifyQueryParams,
|
||||
beforeRouteUpdate: verifyQueryParams,
|
||||
setup() {
|
||||
const musicListRef = ref(null)
|
||||
const boardListRef = ref(null)
|
||||
const sourceList = computed(() => {
|
||||
return sources.map(s => ({ id: s, name: sourceNames.value[s] }))
|
||||
})
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const handleToggleSource = (id) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
source: id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
source,
|
||||
boardId,
|
||||
sourceList,
|
||||
handleToggleSource,
|
||||
musicListRef,
|
||||
boardListRef,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.leaderboard {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
.header {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
|
||||
}
|
||||
.tab {
|
||||
flex: auto;
|
||||
}
|
||||
.select {
|
||||
flex: none;
|
||||
width: 80px;
|
||||
}
|
||||
.content {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
|
||||
.lists {
|
||||
flex: none;
|
||||
width: 14.8%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
.listsHeader {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.listsSelect {
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
:global(.icon) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
>:global(.content) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
:global(.label-content) {
|
||||
background-color: transparent !important;
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
border-radius: 0;
|
||||
&:hover {
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
:global(.label) {
|
||||
color: var(--color-font) !important;
|
||||
}
|
||||
:global(.icon) {
|
||||
opacity: .6;
|
||||
transition: opacity .3s ease;
|
||||
}
|
||||
|
||||
:global(.selection-list) {
|
||||
max-height: 500px;
|
||||
box-shadow: 0 1px 8px 0 rgba(0,0,0,.2);
|
||||
li {
|
||||
// background-color: var(--color-main-background);
|
||||
line-height: 38px;
|
||||
font-size: 13px;
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
// line-height: 38px;
|
||||
// padding: 0 10px;
|
||||
border-bottom: var(--color-list-header-border-bottom);
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.list {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
// .noItem {
|
||||
|
||||
// }
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user